UserControlViewModel 不更新 UserControl 中的绑定 属性

UserControlViewModel doesn't update binded property in UserControl

我希望能够从我的用户控件视图模型中更改主 window 中的 属性。

这是连接

我可以在它们之间进行绑定,但问题是当我从底层(UserControlViewModel)更新我的 属性 时,它不会在 UserControl 或在我的 MainWindowViewModel.

这是我所有的代码(我也上传了项目on my google drive

MainWindow.xaml

<Window x:Class="WpfApplicationViewToViewModel.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:WpfApplicationViewToViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="367" Width="624">
    <StackPanel>
        <local:UserControl1 TextInUserControl="{Binding DataContext.TextInMainWindowViewModel,
            Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
        </local:UserControl1>


        <Button Content="Test MainWindow VM" Command="{Binding CommandTestMWVM}" ></Button>
        <Separator></Separator>

    </StackPanel>
</Window>

MainVindow.xaml.cs

using System.Windows;

namespace WpfApplicationViewToViewModel
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }


    }
}

MainWindowViewModel.cs

using System;
using System.Windows;
using System.Windows.Input;

namespace WpfApplicationViewToViewModel
{
    class MainWindowViewModel : ViewModelBase
    {
        public string TextInMainWindowViewModel
        {
            get
            {
                return _textInMainWindowViewModel;
            }
            set
            {
                _textInMainWindowViewModel = value;
                RaisePropertyChanged("TextInMainWindowViewModel");
            }
        }
        private string _textInMainWindowViewModel { get; set; }

        //test button
        public MainWindowViewModel()
        {
            _commandTestMWVM = new RelayCommand(new Action<object>(TestMWVM));
        }

        #region [Command] CommandTestMWVM
        public ICommand CommandTestMWVM
        {
            get { return _commandTestMWVM; }
        }
        private ICommand _commandTestMWVM;
        private void TestMWVM(object obj)
        {
            TextInMainWindowViewModel = TextInMainWindowViewModel + "MWVM";
            MessageBox.Show("TextInMainWindowModel " + TextInMainWindowViewModel);
        }
        #endregion
    }
}

UserControl1.xaml(仅包含两个用于测试目的的按钮)

<UserControl x:Class="WpfApplicationViewToViewModel.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:WpfApplicationViewToViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel>
        <Button Content="Test UC" Click="Button_Click"></Button>
        <Button Content="Test UCVM" Command="{Binding CommandTestUCVM}" ></Button>
    </StackPanel>
</UserControl>

UserControl1.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplicationViewToViewModel
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        private  UserControl1ViewModel VM = new UserControl1ViewModel();

        public UserControl1()
        {
            InitializeComponent();
            this.DataContext = VM;

            //
            //does not work because breaks binding somewhere
            //string propertyInViewModel = "TextInUserControlViewModel";
            //var bindingViewMode = new Binding(propertyInViewModel) { Mode = BindingMode.TwoWay };
            //this.SetBinding(TextInUserControlProperty, bindingViewMode);

        }

        //dependency property declaration
        public static DependencyProperty TextInUserControlProperty =
            DependencyProperty.Register("TextInUserControl",
                typeof(string),
                typeof(UserControl1)

                );


        public string TextInUserControl
        {
            get {

                return (DataContext as UserControl1ViewModel).TextInUserControlViewModel;
            }
            set
            {
                (DataContext as UserControl1ViewModel).TextInUserControlViewModel = value;
                this.SetValue(TextInUserControlProperty, value);
            }
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            TextInUserControl = TextInUserControl + "UC";
            MessageBox.Show("TextInUserControl : " + TextInUserControl);
        }
    }
}

UserControl1ViewModel.cs

using System;
using System.Windows;
using System.Windows.Input;

namespace WpfApplicationViewToViewModel
{
    class UserControl1ViewModel : ViewModelBase
    {

        private string _textInViewModel;
        public string TextInUserControlViewModel
        {
            get { return _textInViewModel; }
            set {
                _textInViewModel = value;
                RaisePropertyChanged("TextInUserControlViewModel");
            } }

        //test button
        public UserControl1ViewModel()
        {
            _commandTestUCVM = new RelayCommand(new Action<object>(TestUCVM));
        }

        #region [Command] CommandTestUCVM
        public ICommand CommandTestUCVM
        {
            get { return _commandTestUCVM; }
        }
        private ICommand _commandTestUCVM;
        private void TestUCVM(object obj)
        {
            TextInUserControlViewModel = TextInUserControlViewModel + "UCVM";
            MessageBox.Show("TextInUserControlViewModel : " + TextInUserControlViewModel);
        }
        #endregion
    }
}

非常感谢任何帮助,因为我已经尝试弄清楚这个系统(从 mainwindow 读取 usercontrols viewmodel)将近一个星期了。

为了让我的问题更清楚:

TextInUserControl <=> TextInMainWindowViewModel : 工作成功

TextInUserControl => TextInUserControlViewModel有效,但是当我更改 TextInUserControlViewModel 时,TextInUserControl 不会自动更新。

有没有办法让 TextInUserControl 知道 TextInUserControlViewModel 已更改?

您应该在 MainWindowViewModel

中调用 RaisePropertyChanged("TextInMainWindowViewModel");

您正在将 UserControl 的 DataContext 设置为 UserControl1ViewModel 实例,然后将 TextInUserControl 属性 绑定到 DataContext.TextInMainWindowViewModel,这导致它看起来对于 属性 UserControl1ViewModel.DataContext.TextInMainWindowViewModel,它不存在。

使用 WPF/MVVM 的首要规则之一:永远不要在用户控件后面的代码中设置 this.DataContext = x; ,除非您打算永远不向该控件传递任何外部值。

相反,您可能想要的是将 UserControl1ViewModel 的实例添加到 MainWindowViewModel,并将 UserControl.DataContext 绑定到该实例。

例如,

class MainWindowViewModel : ViewModelBase
{
    // add this property
    public UserControl1ViewModel UserControlData { ... }

    public string TextInMainWindowViewModel { ... }
    public ICommand CommandTestMWVM { ... }
}
<!-- change binding to this -->
<local:UserControl1 DataContext="{Binding UserControlData}" />

并删除 UserControl 构造函数中的以下内容

this.DataContext = VM;

我已经使用 "bridge property" 解决了这个问题。我复制了可能对其他有同样问题的人有帮助的解决方案:

UserControl1.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplicationViewToViewModel
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
            this.DataContext = new UserControl1ViewModel(); 
            /*
            [Bridge Binding ©]
            It's not possible to bind 3 properties.
            So this bridge binding handles the communication
            */
            string propertyInViewModel = "TextInUserControlViewModel";
            var bindingViewMode = new Binding(propertyInViewModel);
            bindingViewMode.Mode = BindingMode.TwoWay;
            this.SetBinding(BridgeBetweenUCandVWProperty, bindingViewMode);


        }
        #region Bridge Property
        public static DependencyProperty BridgeBetweenUCandVWProperty =
            DependencyProperty.Register("BridgeBetweenUCandVW",
        typeof(string),
        typeof(UserControl1),
        new PropertyMetadata(BridgeBetweenUCandVWPropertyChanged)
        );

        public string BridgeBetweenUCandVW
        {
            get
            {
                return (string)GetValue(BridgeBetweenUCandVWProperty);
            }
            set
            {
                this.SetValue(BridgeBetweenUCandVWProperty, value);
            }
        }

        private static void BridgeBetweenUCandVWPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((UserControl1)d).TextInUserControl = (string)e.NewValue;
        }
        #endregion

        #region TextInUserControl Property
        public static DependencyProperty TextInUserControlProperty =
            DependencyProperty.Register("TextInUserControl",
                typeof(string),
                typeof(UserControl1),
                new PropertyMetadata(OnTextInUserControlPropertyChanged)
                );

        private static void OnTextInUserControlPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((UserControl1ViewModel)((UserControl)d).DataContext).TextInUserControlViewModel = (string)e.NewValue;
        }

        public string TextInUserControl
        {
            get {
               return (string)GetValue(TextInUserControlProperty);
            }
            set
            {
                this.SetValue(TextInUserControlProperty, value);
            }
        }
        #endregion
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            TextInUserControl += "[UC]";
            MessageBox.Show("TextInUserControl : " + TextInUserControl);
        }
    }
}