如何将 View 控件正确绑定到 ViewModel 列表 (WPF MVVM) [英] how to correctly bind a View control to a ViewModel List (WPF MVVM)

查看:46
本文介绍了如何将 View 控件正确绑定到 ViewModel 列表 (WPF MVVM)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要使用 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.

所以在你的代码中你不应该尝试创建StackPanelsEllipses

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 of List<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 属性的 DataTemplateConverters: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使用 GroupBoxCheckBoxes 来显示列表的窗口

Update 4.4.2016 16:30 Window 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 属性并绑定 ItemsSourceObservableCollection 并将此列表与您的模型同步 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)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆