如何在MVVM模式中创建UserControl



目前,在实际的应用程序开发中,我正在努力解决在MVVM模式中使用自定义UserControl的问题。

在我的应用程序中,有一个DataGrid,用户可以在其中选择条目。DataGridSelectedItemTwoWay绑定到ViewModel集的字段DataContext。当用户选择一个条目时,该字段被适当地更新(测试)。在保存DataGridPage中,该字段通过XAML绑定到MVVM模式中设计的自定义UserControlDependencyProperty:它暴露了自己的ViewModel,设置为DataContext。问题是,即使INotifyPropertyChanged接口被正确实现,当字段发生变化时,UserControlDependencyProperty也没有更新(参见下一个最小工作示例中与传统控件的比较)。

本例由Label组成,并将ViewModelUserControl作为DataContext, UserControl1MainWindow消耗,并与Label的绑定进行比较。

文件MainWindow.xaml:

<Window x:Class="UserControlWithinUserControlDataContext.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Local="clr-namespace:UserControlWithinUserControlDataContext"
        Title="MainWindow"
        Height="350" Width="525"
        >
    <StackPanel Orientation="Horizontal"
                >
        <ListBox SelectedItem="{Binding Text, Mode=TwoWay}"
                 x:Name="listbox"
                 Height="150"
                 >
        </ListBox>
        <Local:UserControl1 Text="{Binding Text, Mode=OneWay}"
                            Height="50" Width="150"
                            />
        <Label Content="{Binding Text, Mode=OneWay}"
               />
    </StackPanel>
</Window>

MainWindow.xaml.cs:

 public partial class MainWindow : Window
    {
        public ViewModelWindow view_model_window
        {
            get { return _view_model; }
        }
        private ViewModelWindow _view_model = new ViewModelWindow();
        public MainWindow()
        {
            InitializeComponent();
            DataContext = view_model_window;
            IList<String> list = new List<String>();
            list.Add("A");
            list.Add("B");
            list.Add("C");
            listbox.ItemsSource = list;
        }
    }

MainWindow的ViewModel,文件ViewModelWindow.cs:

public class ViewModelWindow : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public String Text
    {
        get { return text; }
        set
        {
            if (text != value)
            {
                text = value;
                NotifyPropertyChanged("Text");
            }
        }
    }
    private String text = "Bli";
}

文件usercontrol .xaml:

<UserControl x:Class="UserControlWithinUserControlDataContext.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Label Content="{Binding Text}"
               Background="Magenta"
               HorizontalAlignment="Stretch"
               />
    </Grid>
</UserControl>

代码隐藏文件UserControl1.xaml.cs:

 public partial class UserControl1 : UserControl
    {
        public ViewModelUserControl view_model_usercontrol
        {
            get { return _view_model; }
        }
        private ViewModelUserControl _view_model = new ViewModelUserControl();
        public String Text
        {
            get { return (String)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(String), typeof(UserControl1),
                new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender,
                        new PropertyChangedCallback(TextPropertyChangedCallback)));
        private static void TextPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UserControl1 user_control = d as UserControl1;
            if(user_control != null)
            {
                user_control.view_model_usercontrol.Text = user_control.Text;
            }
        }
        public UserControl1()
        {
            InitializeComponent();
            DataContext = view_model_usercontrol;
        }
    }

UserControl1的ViewModel,文件ViewModelUserControl.cs:

public class ViewModelUserControl : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public String Text
    {
        get { return text; }
        set
        {
            if (text != value)
            {
                text = value;
                NotifyPropertyChanged("Text");
            }
        }
    }
    private String text = "";
}

正如你在执行这段代码时看到的,MainWindowLabel被更新了,而UserControl1Label没有。

我做错了什么?有办法让它起作用吗?

首先,您不需要在UserControl中添加任何内容,只需添加XAML。删除UserControl的所有代码并尝试。

让我们解释一下原因:

Content="{Binding Text}"你在usercontrol xaml中设置了这个,它被绑定到ViewModelWindow。这是可行的。然后删除

  <Local:UserControl1 => Text="{Binding Text, Mode=OneWay}"

Ok,但是在其他情况下,在用户控件中定义属性是正确的吗?,这是正确的,为了这样做:

<UserControl x:Name="UserControlInstance"...>
<Label Content="{Binding Text, ElementName=UserControlInstance}" ...>

这里的Text是依赖属性,而不是数据上下文属性。

尝试第一个选项,然后第二个选项只定义一个依赖属性,在这种情况下,像你一样绑定依赖属性。提示一下,如果依赖属性在可视元素树中,就像你的例子一样,你不需要调用回调。

感谢Juan的回答,以下是在MVVM模式中构思UserControl的解决方案:

我将UserControl1Grid命名为root,并设置其DataContext:

root.DataContext = view_model_usercontrol;

代替:

DataContext = view_model_usercontrol;

一切正常

大团圆结局

最新更新