我正在处理使用WPF和MVVM显示油箱液位的明显简单问题。
为了显示坦克,我使用了一个内部有矩形的DockPanel。矩形的高度根据罐中的液体量而变化。在坦克的顶部,我有一个TextBlock,显示坦克中存在的液体量。 我已经在XAML中定义了这个:
<DockPanel x:Name="tankView" HorizontalAlignment="Left" Height="212" VerticalAlignment="Top" Width="144" DataContext="{Binding Source={StaticResource TankViewModel}}">
<TextBlock x:Name="oilQuantity" HorizontalAlignment="Right" VerticalAlignment="Top" DockPanel.Dock="Top" Margin="0,0,10,0" Text = "{Binding TxtOilQuantity, Mode=OneWay}"/>
<Rectangle x:Name="oilLevel" Fill="Green" Height="66" Stroke="Black" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" DockPanel.Dock="Bottom"/>
</DockPanel>
您还可以看到我已经创建了一个TankViewModel类和一个遵循MVVM模式的TankModel类。
显示TextBlock中的液体数量很简单,数据绑定可以完美地完成工作。但是,当涉及到矩形的高度时,会出现一些问题,因为我无法找到正确分离View和ViewModel之间关注点的方法。
矩形的高度取决于最大容量和水箱中液体的数量,这样我就可以得到一个数字告诉我水箱的填充百分比,如下:
public class TankViewModel : INotifyPropertyChanged
{
private TankModel tankModel = new TankModel(2500);
public int IntFilledPercentage {
get {
if (tankModel.OilQuantity == 0)
return 0;
else
return Convert.ToInt32(((double)tankModel.OilQuantity / tankModel.capacity) * 100);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
但是,我无法将此属性直接绑定到矩形的高度,并且为这样的属性赋予值听起来就像View应该负责的那样。 为了实现这一点,我必须在视图中插入一些代码,将这个百分比值转换为矩形的高度。
我可以通过实现View的OnPropertyChanged()回调来实现这一目标吗?
您对如何简化我已实施的架构有任何建议吗?
答案 0 :(得分:4)
通过使用一点数学,您可以使用视图模型中的百分比值轻松完成此操作。你知道视图中矩形的最大高度(它可能是一个静态值)。然后当前高度=最大高度乘以百分比值。
可以使用the Binding.Converter property with an IValueConverter
在XAML中执行此类操作。请参阅this post,这是相关的。
这是一个示例转换器:
internal sealed class OilLevelConverter : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var percentage = (decimal) value;
var maxLevel = System.Convert.ToInt32((string) parameter);
var currentLevel = System.Convert.ToInt32(maxLevel * percentage);
return currentLevel;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
这是您的App.xaml
:
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
StartupUri="MainWindow.xaml">
<Application.Resources>
<local:ViewModel x:Key="ViewModel" />
<local:OilLevelConverter x:Key="OilLevelConverter"/>
</Application.Resources>
</Application>
这是一个示例窗口XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350"
Width="525"
DataContext="{StaticResource ViewModel}" >
<Grid>
<Rectangle Fill="#FFF4F4F5"
HorizontalAlignment="Left"
Height="{Binding Path=OilLevel,
Converter={StaticResource OilLevelConverter},
ConverterParameter=100}" Margin="183,132,0,0"
Stroke="Black" VerticalAlignment="Top" Width="100"/>
</Grid>
</Window>
注意:我遗漏了只有一个属性的ViewModel
:OilLevel
。
答案 1 :(得分:2)
补充rory。如果您不想使用固定的Converter参数但使用某个容器的实际高度,也可以使用MultiBinding
和IMultiValueConverter
。
这允许使用父母的实际身高来设置液体的高度。
在我的例子中,我使用带边框的网格来代表gui上的液体罐。
视图模型:
public class MainWindowViewModel : PropertyChangedBase // from Calburn.Micro (see nuget)
{
private int _liquidPerc;
public MainWindowViewModel()
{
LiquidPercentage = 25;
}
public int LiquidPercentage
{
get { return _liquidPerc; }
set
{
if (value == _liquidPerc) return;
_liquidPerc= value;
NotifyOfPropertyChange(() => LiquidPercentage);
}
}
}
转换器:
/// <summary>
/// Converter which expects two params. percentage and maximum height
/// </summary>
public class LiquidLevelConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var percentage = (int) values[0];
var maxHeight = (double) values[1];
return percentage*maxHeight*0.01;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Window x:Class="UiLiquedTankDemo.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:local="clr-namespace:UiLiquedTankDemo"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MainWindowViewModel x:Key="ViewModel" />
<local:LiquidLevelConverter x:Key="LiquidLevelConverter" />
</Window.Resources>
<DockPanel DataContext="{StaticResource ViewModel}">
<!-- move the slider to move the level of the liquid -->
<Slider Minimum="0" Maximum="100" Value="{Binding LiquidPercentage}"
DockPanel.Dock="Bottom"
Margin="0"/>
<!-- Liquid container representation using a grid -->
<Grid Name="LiquidContainer" Margin="200,5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Grid.Row="1" Background="Blue" Margin="0">
<Border.Height>
<MultiBinding Converter="{StaticResource LiquidLevelConverter}">
<Binding Path="LiquidPercentage"></Binding>
<Binding Path="ActualHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}}"></Binding>
</MultiBinding>
</Border.Height>
</Border>
<Border Grid.Row="0" Grid.RowSpan="2" BorderBrush="Black" BorderThickness="1" />
</Grid>
</DockPanel>
</Window>
答案 2 :(得分:1)
使用ValueConverter
从填充的IntFilledPercentage
的ViewModel值转换为矩形的Height
的View值。
您可以将IntFilledPercentage
ViewModel属性绑定到Rectangle
的{{1}}属性,然后在Height
中将百分比转换为实际可视单元类。
Converter
转换器实现IValueConverter接口。在这种情况下,您只需要实现<Rectangle x:Name="oilLevel" Fill="Green" Height="{Binding IntFilledPercentage, Mode=OneWay, Converter={StaticResource PercentageToHeightConverter}" Stroke="Black" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" DockPanel.Dock="Bottom"/>
。