【问题标题】:How to create a UserControl in the MVVM pattern?如何在 MVVM 模式中创建 UserControl?
【发布时间】:2015-07-22 07:50:35
【问题描述】:

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

在我的应用程序中,有一个DataGrid,用户可以在其中选择一个条目。 DataGridSelectedItemTwoWay-绑定到 ViewModel 设置为 DataContext 的字段。当用户选择一个条目时,该字段会正确更新(测试)。在包含DataGridPage 中,该字段通过XAML 绑定到在MVVM 模式中设计的自定义UserControlDependencyProperty:它拥有自己的ViewModel,设置为DataContext。问题是UserControlDependencyProperty 不会在字段更改时更新,即使INotifyPropertyChanged 接口已正确实现(请参阅下一个最小工作示例中与传统控件的比较)。

此示例由Label 和裸露的ViewModelUserControl 组成,作为DataContextUserControl1MainWindow 使用,并且将绑定与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";
}

文件 UserControl1.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 不会。

我做错了什么?有没有办法让它工作?

非常感谢您提供任何线索。

【问题讨论】:

  • 运行时是否遇到任何绑定错误?
  • 尽管我的问题解决了,但您提出了一个观点:没有任何更新的事实本身就是一个错误,但对调试并不友好。

标签: c# wpf xaml mvvm datagrid


【解决方案1】:

首先,您无需在 UserControl 中添加任何内容,只需添加 XAML。把UserControl的代码全部去掉试试看。

让我们解释一下原因:

Content="{Binding Text}" 您在用户控件 xaml 中设置它,它已绑定到 ViewModelWindow。那行得通。并在

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

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

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

在这种情况下,Text 是依赖属性,而不是 datacontext 属性。

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

【讨论】:

    【解决方案2】:

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

    我将名称 root 给了 UserControl1Grid 并设置了它的 DataContext

    root.DataContext = view_model_usercontrol;
    

    代替:

    DataContext = view_model_usercontrol;
    

    一切正常。

    快乐的结局:)

    【讨论】:

    • 当您需要您的 UserControl 拥有自己的 ViewModel 时,这是可以的。但情况并非总是如此,有时不设置 DataContext 并使用相对绑定是可行的方法,即使对于 MVVM 也是如此。无论如何,避免this.DataContext = this:P
    • 好吧,我喜欢把事情分开:DependencyProperty 传入的数据流,传输到 ViewModel,ViewModel 设置为 DataContext,用于通过 Binding 流出数据。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-08
    • 2012-03-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多