我目前正在开发用户界面,用户可以通过三个单独的滑块选择三个值。我在滑块前面有三个文本块来指示特定滑块的当前值:
在.xaml:
中<Label Content="Sample Selection" FontSize="16" FontStyle="Italic" FontWeight="Bold" HorizontalAlignment="Left" Margin="0,-30,0,0" VerticalAlignment="Top"/>
<Label Content="Patient Samples (max 64)" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" FontStyle="Italic"/>
<Slider x:Name="SampleAmountSlider" HorizontalAlignment="Left" Margin="181,14,0,0" VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="64" ValueChanged="SampleAmountSlider_ValueChanged" IsSnapToTickEnabled="True"/>
<TextBlock x:Name="SampleSliderValue" HorizontalAlignment="Left" Margin="165,16,0,0" TextWrapping="Wrap" Text="0" VerticalAlignment="Top"/>
<Label Content="Calibrators (max 7)" HorizontalAlignment="Left" Margin="10,36,0,0" VerticalAlignment="Top" FontStyle="Italic"/>
<Slider x:Name="CalAmountSlider" HorizontalAlignment="Left" Margin="181,40,0,0" VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="7" ValueChanged="CalAmountSlider_ValueChanged" IsSnapToTickEnabled="True"/>
<TextBlock x:Name="CalSliderValue" HorizontalAlignment="Left" Margin="165,42,0,0" TextWrapping="Wrap" Text="0" VerticalAlignment="Top"/>
<Label Content="Control Samples (max 4)" HorizontalAlignment="Left" Margin="10,62,0,0" VerticalAlignment="Top" FontStyle="Italic"/>
<Slider x:Name="ControlAmountSlider" HorizontalAlignment="Left" Margin="181,66,0,0" VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="4" ValueChanged="ControlAmountSlider_ValueChanged" IsSnapToTickEnabled="True"/>
<TextBlock x:Name="ControlSliderValue" HorizontalAlignment="Left" Margin="165,68,0,0" TextWrapping="Wrap" Text="0" VerticalAlignment="Top"/>
<Label Content="Total Sample Preparations Selected:" HorizontalAlignment="Left" Margin="10,105,0,0" VerticalAlignment="Top" FontWeight="Bold" FontStyle="Italic"/>
<TextBlock x:Name="TotalPrepValue" HorizontalAlignment="Left" Margin="225,110,0,0" FontWeight="Bold" FontStyle="Italic" Text="0" VerticalAlignment="Top"/>
在.xaml.cs中:
private void SampleAmountSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
SampleSliderValue.Text = Math.Round(e.NewValue, 0).ToString();
}
private void CalAmountSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
CalSliderValue.Text = Math.Round(e.NewValue, 0).ToString();
}
private void ControlAmountSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
ControlSliderValue.Text = Math.Round(e.NewValue, 0).ToString();
}
我想要的是最后一个文本块(名为TotalPrepValue),其中包含患者样本量,校准量和对照样本量之和。
我不确定我是否要求很多或者事情不清楚(如果是,请告诉我。我会尽快回答)。 问题是我是一个非常缺乏经验的程序员,但愿意学习! 提前感谢您的帮助!
答案 0 :(得分:2)
就个人而言,这就是我要做的。但是,如果你喜欢它,那么请去@JerryNixon answer here,因为它基本上只是一个重新考虑使用滑块而不是他的例子,他应该得到更多的赞扬。通常情况下我直接指向你,但我知道当你刚刚开始使用某些东西并且更清晰的PoC更有用时它是怎么回事。
无论如何,在这里,一个漂亮的图片开始...
XAML;
<Window.Resources>
<local:SumConverter x:Key="MySumConverter" />
</Window.Resources>
<Grid>
<StackPanel VerticalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="Slider">
<Setter Property="Margin" Value="10"/>
<Setter Property="Width" Value="200"/>
<Setter Property="Minimum" Value="0"/>
<Setter Property="Maximum" Value="100"/>
</Style>
</StackPanel.Resources>
<Slider x:Name="Slider1"></Slider>
<Slider x:Name="Slider2"></Slider>
<Slider x:Name="Slider3"></Slider>
<TextBlock HorizontalAlignment="Center" TextAlignment="Center">
<Run Text="{Binding Value, ElementName=Slider1}"/>
<LineBreak/><LineBreak/>
<Run Text="{Binding Value, ElementName=Slider2}"/>
<LineBreak/><LineBreak/>
<Run Text="{Binding Value, ElementName=Slider3}"/>
<LineBreak/>
<Run Text="______________________"/>
<LineBreak/><LineBreak/>
<Run>
<Run.Text>
<MultiBinding Converter="{StaticResource MySumConverter}"
StringFormat="{}{0:C}"
FallbackValue="Error" TargetNullValue="Null">
<Binding Path="Value" ElementName="Slider1"/>
<Binding Path="Value" ElementName="Slider2"/>
<Binding Path="Value" ElementName="Slider3"/>
</MultiBinding>
</Run.Text>
</Run>
</TextBlock>
</StackPanel>
</Grid>
转换器;
public class SumConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
double _Sum = 0;
if (values == null)
return _Sum;
foreach (var item in values)
{
double _Value;
if (double.TryParse(item.ToString(), out _Value))
_Sum += _Value;
}
return _Sum;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
希望这会有所帮助,欢呼。
答案 1 :(得分:1)
正如其他人所建议的那样,您可以在此方案中使用IMultiValueConverter
。但我认为,虽然在其他情况下这是一个有用的工具,但这对于这个工作来说是错误的工具。原因在于,在这种情况下,它将用于使UI元素的不当使用永久存储为存储非UI数据的位置。
在编写WPF程序时,如果您承诺遵循MVVM风格的WPF打算使用,那么您将获得更好的服务。术语&#34; MVVM&#34;字面意思是&#34;模型,视图,视图模型&#34;。从这个角度来看,总会有专用的适配器&#34;模型和视图之间的类型。但根据我的经验,MVVM范例的重要部分是严格保持视图逻辑与模型逻辑分离,这通常可以在没有&#34;视图模型&#34的额外层的情况下完成;类型。这将MVVM放在与MVC(&#34;模型,视图,控制器&#34;)和MVP(&#34;模型,视图,演示者&#34;)相同的工具集中。
所有这些的关键在于您有一些业务逻辑,它在模型数据结构中表示,由提供某种形式的值更改通知的类型实现(在WPF中,主要机制是INotifyPropertyChanged
),然后还查看完全单独表示的逻辑(在WPF中,视图主要是,在很多情况下完全在XAML中声明)。
在您的示例中,这意味着我们需要一个表示您感兴趣的数据的模型数据结构:样本,校准器,控件和总预备计数。最后一个只是前三个的总和。只要我们有一个可以跟踪这些的类,并在其他任何三个更改时正确更新求和值,我们就可以将它直接绑定到XAML中声明的视图,而无需使用任何C#代码隐藏所有
例如:
class ViewModel : INotifyPropertyChanged
{
private int _sampleCount;
public int SampleCount
{
get { return _sampleCount; }
set { _UpdateField(ref _sampleCount, value, OnCountChanged); }
}
private int _calibratorCount;
public int CalibratorCount
{
get { return _calibratorCount; }
set { _UpdateField(ref _calibratorCount, value, OnCountChanged); }
}
private int _controlCount;
public int ControlCount
{
get { return _controlCount; }
set { _UpdateField(ref _controlCount, value, OnCountChanged); }
}
private int _totalPrepCount;
public int TotalPrepCount
{
get { return _totalPrepCount; }
set { _UpdateField(ref _totalPrepCount, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnCountChanged(int previousValue)
{
TotalPrepCount = SampleCount + CalibratorCount + ControlCount;
}
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
注意:
INotifyPropertyChanged
界面只有一个成员PropertyChanged
事件。你会发现在处理MVVM风格的代码时,有一个实际实现这个事件的基类,以及一个辅助方法,如上面显示的_UpdateField()
方法,属性设置者可以调用它来处理每个这样的财产所需的重复逻辑。在上面的示例中,为了简化示例,我将所有这些逻辑组合到一个类中,但是您可能希望保留一个合适的基类(我和许多其他人已经配置) Visual Studio中的代码片段可以轻松地将此样板代码插入到项目中。)使用如此定义的视图模型,XAML被简化为如下所示:
<Window x:Class="TestSO45170241SliderExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:TestSO45170241SliderExample"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:ViewModel/>
</Window.DataContext>
<Grid>
<Label Content="Sample Selection" FontSize="16" FontStyle="Italic" FontWeight="Bold"
HorizontalAlignment="Left" Margin="0,-30,0,0" VerticalAlignment="Top"/>
<Label Content="Patient Samples (max 64)" HorizontalAlignment="Left" Margin="10,10,0,0"
VerticalAlignment="Top" FontStyle="Italic"/>
<Slider HorizontalAlignment="Left" Margin="181,14,0,0"
VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="64"
Value="{Binding SampleCount}" IsSnapToTickEnabled="True"/>
<TextBlock HorizontalAlignment="Left" Margin="165,16,0,0"
TextWrapping="Wrap" Text="{Binding SampleCount}" VerticalAlignment="Top"/>
<Label Content="Calibrators (max 7)" HorizontalAlignment="Left" Margin="10,36,0,0"
VerticalAlignment="Top" FontStyle="Italic"/>
<Slider HorizontalAlignment="Left" Margin="181,40,0,0"
VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="7"
Value="{Binding CalibratorCount}" IsSnapToTickEnabled="True"/>
<TextBlock HorizontalAlignment="Left" Margin="165,42,0,0"
TextWrapping="Wrap" Text="{Binding CalibratorCount}" VerticalAlignment="Top"/>
<Label Content="Control Samples (max 4)" HorizontalAlignment="Left" Margin="10,62,0,0"
VerticalAlignment="Top" FontStyle="Italic"/>
<Slider HorizontalAlignment="Left" Margin="181,66,0,0"
VerticalAlignment="Top" Cursor="Hand" Width="160" Maximum="4"
Value="{Binding ControlCount}" IsSnapToTickEnabled="True"/>
<TextBlock HorizontalAlignment="Left" Margin="165,68,0,0"
TextWrapping="Wrap" Text="{Binding ControlCount}" VerticalAlignment="Top"/>
<Label Content="Total Sample Preparations Selected:" HorizontalAlignment="Left"
Margin="10,105,0,0" VerticalAlignment="Top" FontWeight="Bold" FontStyle="Italic"/>
<TextBlock HorizontalAlignment="Left" Margin="225,110,0,0"
FontWeight="Bold" FontStyle="Italic" Text="{Binding TotalPrepCount}"
VerticalAlignment="Top"/>
</Grid>
</Window>
(旁白:除了改为支持MVVM方法之外,我根本没有修改你的基本UI声明。我同意你想要开始熟悉的另一件事是如何采取WPF的各种布局容器和元素样式功能的优点。但我认为在这里介绍它们会让事情变得混乱。通过保持原始UI大部分完整,你可以专注于那些与你原来的不同的东西,帮助您更好地理解数据绑定方面而不会分心。)
在此实现中,有{em> no 代码添加到MainWindow.xaml.cs
文件中。该文件中的所有内容都是构造函数中对InitializeComponent()
的默认调用,由Visual Studio的WPF项目模板提供。
另一方面,在XAML中,我已将绑定直接替换为Slider.Value
属性的事件处理程序订阅。另请注意,TextBlock.Text
属性也绑定到相同的属性。通过这种方式,WPF完成了在仅限业务逻辑的数据结构中存储滑块值以及在视图中的文本字段中重新显示这些值的所有繁重工作。您将注意到WPF甚至处理各种数据类型之间的转换:视图模型存储int
值,但滑块使用double
,文本块当然使用string
。
当然,TotalPrepCount
字段也绑定到感兴趣的TextBlock.Text
属性以供显示。
最后,我要注意,即使在您的简单示例中,您还有其他地方可能需要应用此数据绑定方法。特别是,每个滑块都有最大值,这些值被硬编码到视图中。 MVVM的观点是视图不必封装有关业务逻辑的任何知识。这包括不必知道允许的全部值(*)。
因此,您的视图模型也可以包含MaxSampleCount
属性,它绑定到Slider.Maximum
属性和Label.Content
属性。在后一种情况下,您可以使用Binding.StringFormat
属性将值合并到文本中。例如:
<Label Content="{Binding MaxSampleCount, StringFormat=Patient Samples (max {0})" ... />
我很乐意承认,当我第一次开始尝试使用WPF时,经过多年使用UI API,如本机Win32控件,MFC,Windows Forms,Java的API(Swing,AWT,SWT),甚至Mac OS的Cocoa框架(使用一种数据绑定形式,但不是像XAML那样的声明性UI),我努力改变我的想法,使其远离所有其他API中使用的过程方法,以适应与WPF一起使用的混合声明和程序方法。
但是我试图严格地从MSDN提供的文档中学习它,MSDN最好是密集编写的,通常很难遵循,在很多情况下,完全没用。如果有人刚刚向我展示了一个像我上面所展示的那样的例子(他们确实存在,甚至那时候......我只是不知道他们在哪里),我当时就会看到这么简单基本的MVVM方法是,如果遵循这种方法,可以多快地编写WPF程序。
我希望上述内容可以帮助您有效地使用WPF,并以易于理解的方式向您展示基础知识。