问题描述
我需要使用 WPF MVVM 将一个列表绑定到一个 UniformGrid 到一个 WP 窗口中.
I need to bind a List to a UniformGrid into a WP Window using WPF MVVM.
我想做这样的事情:
进入我的虚拟机:
private List<Rat> rats;
private UniformGrid uniformGrid;
public List<Rat> Rats
{
get { return rats; }
set
{
if (rats != value)
{
//update local list value
rats = value;
//create View UniformGrid
if (uniformGrid == null)
uniformGrid = new UniformGrid() { Rows=10};
else
uniformGrid.Children.Clear();
foreach(Rat rat in value)
{
StackPanel stackPanel = new StackPanel();
Ellipse ellipse = new Ellipse(){Height=20, Width=20, Stroke= Brushes.Black};
if (rat.Sex== SexEnum.Female)
ellipse.Fill= Brushes.Pink;
else
ellipse.Fill= Brushes.Blue;
stackPanel.Children.Add(ellipse );
TextBlock textBlock = new TextBlock();
textBlock.Text= rat.Name + " (" + rat.Age +")";
stackPanel.Children.Add( textBlock );
uniformGrid.Children.Add(stackPanel);
}
OnPropertyChanged("Rats");
}
}
}
当需要通过事件将列表刷新到视图中时,VM 会得到正确通知.所以此时我需要我的视图正确绑定到虚拟机.我是这样做的:
The VM is correctly informed when the list needs to be refreshed into the view via an event.So at this point I would need my View to be correctly bound to the VM. I made it this way:
<GroupBox x:Name="GB_Rats" Content="{Binding Rats}" Header="Rats" HorizontalAlignment="Left" Height="194" Margin="29,10,0,0" VerticalAlignment="Top" Width="303">
这是正确的全局方法吗?
Is this the correct global approch?
具体来说,当试图运行代码时,这一行无法执行:
Concretely, when attempting to run the code, this line fails to execute:
uniformGrid = new UniformGrid() { Rows=10};
->
An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationCore.dll
Additional information: The calling thread must be STA, because many UI components require this.
这让我觉得从 MVVM 的角度来看我不应该这样做.
This lets me think that I should not proceed this way from a MVVM point of view.
感谢您的帮助.
推荐答案
ViewModel
不应该实例化任何 UI 控件,这应该是 View 的责任.
The ViewModel
isn't supposed to instantiate any UI controls, this should be the View's responsibility.
所以在你的代码中你不应该尝试创建StackPanels
、Ellipses
等
So in your code you shouldn't try to create StackPanels
, Ellipses
etc.
也尝试使用已经有更改通知的类型 - for 而不是List
使用 ObservableCollection
MSDN,我不建议在值改变时替换整个列表.
Also try to use the types that already have Change notification - for instead ofList<T>
use ObservableCollection<T>
MSDN, i wouldn't recommend replacing a whole list when its value change.
在 MVVM 模式中执行此操作的正确方法是为 Rat
创建一个 DataTemplate
,如下所示:
The right way to do this in the MVVM pattern is to create a DataTemplate
for the Rat
like this:
视图模型:
public class MainWindowViewModel
{
public ObservableCollection<Rat> Rats { get; set; } =
new ObservableCollection<Rat>()
{
new Rat()
{
Name = "Fred",
Age = "19",
Sex = SexEnum.Male
},
new Rat()
{
Name = "Martha",
Age = "21",
Sex = SexEnum.Female
}
};
}
模型 - 大鼠和性别:
Model - Rat and Sex:
public class Rat
{
public SexEnum Sex { get; set; }
public string Name { get; set; }
public string Age { get; set; }
}
public enum SexEnum
{
Female,
Male
}
如果您想以两种颜色之一显示 Sex 的 Models 值,您应该为此使用 IValueConverter
:
As you want to present the Models value of Sex in one of two colors you should use a IValueConverter
for that:
[ValueConversion(typeof(SexEnum), typeof(Brush))]
public class SexToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is SexEnum))
throw new ArgumentException("value not of type StateValue");
SexEnum sv = (SexEnum)value;
//sanity checks
if (sv == SexEnum.Female)
return Brushes.Red;
return Brushes.Blue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
然后在您的窗口中使用它:
This is then used in your window:
窗口:
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
xmlns:ViewModel="WpfApplication1.VM"
xmlns:Converters ="clr-namespace:WpfApplication1.Converters"
>
<Grid>
<Grid.Resources>
<Converters:SexToColorConverter x:Key="SexToBrushConverter"></Converters:SexToColorConverter>
</Grid.Resources>
<ComboBox x:Name="comboBox" ItemsSource="{Binding Rats}" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="120">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="10" Height="10" Fill="{Binding Sex, Converter={StaticResource SexToBrushConverter}}"></Ellipse>
<TextBlock Margin="5" Text="{Binding Name}"></TextBlock>
<TextBlock Margin="5" Text="{Binding Age}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
注意分配给 ComboBox.ItemTemplate
属性的 DataTemplate
和 Converters:SexToColorConverter
的声明及其用于更改Fill
绑定中椭圆的颜色.
Note the DataTemplate
that is assigned to the ComboBox.ItemTemplate
property and the declaration of the Converters:SexToColorConverter
and its usage to change the color of the ellipse in the Fill
binding.
更新 4.4.2016 16:30使用 GroupBox
和 CheckBoxes
来显示列表的窗口
Update 4.4.2016 16:30Window using a GroupBox
with CheckBoxes
to display the list
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
xmlns:Converters ="clr-namespace:WpfApplication1.Converters"
xmlns:vm="clr-namespace:WpfApplication1.VM">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.Resources>
<Converters:SexToColorConverter x:Key="SexToBrushConverter"></Converters:SexToColorConverter>
</Grid.Resources>
<GroupBox>
<ItemsControl ItemsSource="{Binding Rats}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Margin="5"></CheckBox>
<Ellipse Width="10" Height="10" Fill="{Binding Sex, Converter={StaticResource SexToBrushConverter}}"></Ellipse>
<TextBlock Margin="5" Text="{Binding Name}"></TextBlock>
<TextBlock Margin="5" Text="{Binding Age}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</Grid>
</Window>
我想目标也是选择 Rats,这取决于您想要成为 MVVM 纯粹主义者的程度,您将添加一个 RatViewModels
列表,该列表具有 bool IsChecked 属性并绑定 ItemsSource
到 ObservableCollection
并将此列表与您的模型同步 List
I guess the target is to also select the Rats, depending on how MVVM purist you want to be you'd add a List of RatViewModels
, that have a bool IsChecked property and bind the ItemsSource
to a ObservableCollection<RatViewModel>
and synchronize this list with your Models List<Rat>
这篇关于如何将 View 控件正确绑定到 ViewModel 列表 (WPF MVVM)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!