【问题标题】:WPF Data Binding between ViewModel in Window and ViewModel in UserControlWindow 中的 ViewModel 和 UserControl 中的 ViewModel 之间的 WPF 数据绑定
【发布时间】:2017-04-04 07:54:21
【问题描述】:

我有一个窗口和在这个窗口中使用的用户控件。

我做了以下事情:

  1. 将 window 属性绑定到 window 的 viewmodel 属性。
  2. 将 usercontrol 属性绑定到 usercontrol 的 viewmodel 属性。
  3. 将用户控件的属性绑定到窗口的视图模型属性。

在我看来,如果我通过特定值设置窗口属性,用户控件的视图模型的属性将被设置为相同的值。

但是 usercontrol 的 viewmodel 的属性没有设置为相同的值。

这是完整的代码。

MainWindow.xaml.cs

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Window1 w = new Window1();
            w.Mode = RegisterMode.Update;
            w.Show();
        }
    }

Window1.xaml

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MVVMTest2" x:Name="window" x:Class="MVVMTest2.Window1"
        Title="Window1" Height="300" Width="300">
    <Window.DataContext>
        <local:WindowViewModel></local:WindowViewModel>
    </Window.DataContext>
    <Grid>
        <local:UserControl1 x:Name="uc1" HorizontalAlignment="Left" Height="100" Margin="77,116,0,0" VerticalAlignment="Top" Width="100" Mode="{Binding DataContext.Mode, ElementName=window, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <Button Content="Button" HorizontalAlignment="Left" Margin="156,43,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
    </Grid>
</Window>

Window1.xaml.cs

    public partial class Window1 : Window
    {
        public RegisterMode Mode
        {
            get { return (RegisterMode)GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Mode.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ModeProperty =
            DependencyProperty.Register("Mode", typeof(RegisterMode), typeof(Window1), new PropertyMetadata(RegisterMode.None));

        public Window1()
        {
            InitializeComponent();

            Binding modeBinding = new Binding();
            modeBinding.Source = this.DataContext;
            modeBinding.Path = new PropertyPath("Mode");
            modeBinding.Mode = BindingMode.TwoWay;
            modeBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            this.SetBinding(Window1.ModeProperty, modeBinding);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show((uc1.DataContext as UCViewModel).Mode.ToString());
        }
    }

WindowViewModel.cs

    public class WindowViewModel : INotifyPropertyChanged
    {
        RegisterMode mode = RegisterMode.None;
        public event PropertyChangedEventHandler PropertyChanged;

        public RegisterMode Mode
        {
            get { return this.mode; }
            set
            {
                this.mode = value;
                RaisePropertyChanged("Mode");
            }
        }

        void RaisePropertyChanged(string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

UserControl1.xaml

<UserControl x:Class="MVVMTest2.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" 
             xmlns:local="clr-namespace:MVVMTest2"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.DataContext>
        <local:UCViewModel></local:UCViewModel>
    </UserControl.DataContext>
    <Grid>

    </Grid>
</UserControl>

UserControl1.xaml.cs

    public partial class UserControl1 : UserControl
    {


        public RegisterMode Mode
        {
            get { return (RegisterMode)GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Mode.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ModeProperty =
            DependencyProperty.Register("Mode", typeof(RegisterMode), typeof(UserControl1), new PropertyMetadata(RegisterMode.None));


        public UserControl1()
        {
            InitializeComponent();

            Binding modeBinding = new Binding();
            modeBinding.Source = this.DataContext;
            modeBinding.Path = new PropertyPath("Mode");
            modeBinding.Mode = BindingMode.TwoWay;
            modeBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            this.SetBinding(UserControl1.ModeProperty, modeBinding);
        }
    }

UCViewModel.cs

    public class UCViewModel : INotifyPropertyChanged
    {
        RegisterMode mode = RegisterMode.None;
        public event PropertyChangedEventHandler PropertyChanged;

        public RegisterMode Mode
        {
            get { return this.mode; }
            set
            {
                this.mode = value;
                RaisePropertyChanged("Mode");
            }
        }

        void RaisePropertyChanged(string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));                
            }
        }
    }

我做错了什么? 请告诉我。

【问题讨论】:

  • 您永远不应该明确设置 UserControl 的 DataContext,因为这样做可以有效地防止 DataContext 从 UserControl 的父级继承。参见例如这里:stackoverflow.com/a/28982771/1136211
  • @Clemens 那么如何在构造函数中更改绑定代码?请给我看点东西。
  • UserControl 和 Window1 的构造函数中不应有任何绑定代码。相反,当声明它们的实例时,它们的属性应该在 XAML 中绑定,例如&lt;local:UserControl1 Mode="{Binding Path=Mode, Mode=TwoWay}" .../&gt;。绑定源是从 Window 继承的 DataContext。
  • @YHKim 我只是想澄清你想要发生的事情,你想让 UCViewModel.Mode 获得在 WindowViewModel.Mode 中设置的值吗?
  • @janonimus 我想将 Mode 值从 Window1 发送到 UCViewModel。所以 MessageBox.Show((uc1.DataContext as UCViewModel).Mode.ToString());应该显示“更新”。

标签: c# wpf xaml mvvm data-binding


【解决方案1】:

使用ViewModels 之间的组合尝试此解决方案。

Window1.xaml

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MVVMTest2" x:Name="window" x:Class="MVVMTest2.Window1"
        Title="Window1" Height="300" Width="300"
        Mode={Binding UcViewModel.Mode}> <!-- Added this binding -->

    <!-- MOVED THIS TO THE CODE-BEHIND
    <Window.DataContext>
        <local:WindowViewModel></local:WindowViewModel>
    </Window.DataContext>
    -->

    <Grid>
        <local:UserControl1 x:Name="uc1" HorizontalAlignment="Left" Height="100" Margin="77,116,0,0" VerticalAlignment="Top" Width="100" 
                            Mode="{Binding UcViewModel.Mode}"/>
        <Button Content="Button" HorizontalAlignment="Left" Margin="156,43,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
    </Grid>
</Window>

Window1.xaml.cs

public partial class Window1 : Window
{
    public RegisterMode Mode
    {
        get { return (RegisterMode)GetValue(ModeProperty); }
        set { SetValue(ModeProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Mode.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ModeProperty =
        DependencyProperty.Register("Mode", typeof(RegisterMode), typeof(Window1), new PropertyMetadata(RegisterMode.None));

    public Window1()
    {
        InitializeComponent();

        /* THIS IS NOT NEEDED
        Binding modeBinding = new Binding();
        modeBinding.Source = this.DataContext;
        modeBinding.Path = new PropertyPath("Mode");
        modeBinding.Mode = BindingMode.TwoWay;
        modeBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        this.SetBinding(Window1.ModeProperty, modeBinding)
        */

        // Use the following instead:
        this.DataContext = new WindowViewModel();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MessageBox.Show((uc1.DataContext as UCViewModel).Mode.ToString());
    }
}

UserControl1.xaml

<UserControl x:Class="MVVMTest2.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" 
             xmlns:local="clr-namespace:MVVMTest2"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

<!-- THIS PART IS NOT NEEDED
     <UserControl.DataContext>
        <local:UCViewModel></local:UCViewModel>
    </UserControl.DataContext>
 -->
    <Grid>

    </Grid>
</UserControl>

UserControl1.xaml.cs

public partial class UserControl1 : UserControl
{

    public RegisterMode Mode
    {
        get { return (RegisterMode)GetValue(ModeProperty); }
        set { SetValue(ModeProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Mode.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ModeProperty =
        DependencyProperty.Register("Mode", typeof(RegisterMode), typeof(UserControl1), new PropertyMetadata(RegisterMode.None));


    public UserControl1()
    {
        InitializeComponent();

        /* THIS IS NOT NEEDED
        Binding modeBinding = new Binding();
        modeBinding.Source = this.DataContext;
        modeBinding.Path = new PropertyPath("Mode");
        modeBinding.Mode = BindingMode.TwoWay;
        modeBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        this.SetBinding(UserControl1.ModeProperty, modeBinding);
        */
    }
}

WindowViewModel.cs

public class WindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    /* Remove this:
    RegisterMode mode = RegisterMode.None;
    public RegisterMode Mode
    {
        get { return this.mode; }
        set
        {
            this.mode = value;
            RaisePropertyChanged("Mode");
        }
    }
    */

    // Use composition instead
    UCViewModel _ucViewModel = null;
    public UCViewModel UcViewModel
    {
        get 
        { 
            if (_ucViewModel == null)
            {
                _ucViewModel = new UCViewModel();
            }
            return _ucViewModel; 
        }
    }

    void RaisePropertyChanged(string propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

还有一件事,您可能希望将 ViewModel 属性 Mode 重命名为另一个名称,例如 RegMode。原因是它在绑定期间在 XAML 代码中令人困惑。

从这里: {Binding Mode, Mode=TwoWay} 或这个: {Binding Path=Mode, Mode=TwoWay} 对于这个不那么令人困惑的: {Binding RegMode, Mode=TwoWay}

【讨论】:

  • 感谢您的回答。这接近我想要的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-17
  • 1970-01-01
  • 1970-01-01
  • 2018-10-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多