WPF - 如何使依赖属性和视图模型属性保持同步?

时间:2017-08-28 23:17:17

标签: c# wpf mvvm data-binding dependency-properties

在WPF中,我一直在试图弄清楚如何保持视图依赖项属性和其中一个视图模型的属性同步一段时间,现在没有任何运气。我已经对这个问题进行了大量的研究,但没有一个建议的解决方案对我有用,我希望有人能帮助我找到我所缺少的东西。

我尝试了这篇文章中建议的许多内容Twoway-bind view's DependencyProperty to viewmodel's property?,因为我读到的所有内容看起来都是最有希望的,但却无法得到我想要的结果。

我写了一个简单的程序来证明我遇到的问题。在其中我将MainWindowViewModel中的属性IntValue设置为2,然后将其绑定到UserControl IncrementIntView中创建的依赖项属性。然后,当我按下IncrementIntView中的按钮时,它会将IntValue的值增加1。这一切都在UserControl IncrementIntView中正常工作,但我无法弄清楚如何将更新的IntValue发送回MainWindowViewModel,它保持设置为2.

IncrementIntView.xaml.cs

public partial class IncrementIntView : UserControl
{
    public int IntValue
    {
        get { return (int)GetValue(IntValueProperty); }
        set { SetValue(IntValueProperty, value); }
    }

    public static readonly DependencyProperty IntValueProperty =
        DependencyProperty.Register("IntValue", typeof(int), typeof(IncrementIntView),
                                        new PropertyMetadata(-1, new PropertyChangedCallback(IntValueChanged)));

    private static void IntValueChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        IncrementIntView detailGroup = dependencyObject as IncrementIntView;
        if (e.NewValue != null)
        {
            detailGroup.ViewModel.IntValue = (int)e.NewValue;
        }
    }

    public IncrementIntView()
    {
        InitializeComponent();
    }
}

IncrementIntViewModel.cs

public class IncrementIntViewModel : ViewModelBase
{
    private int intValue;
    public int IntValue
    {
        get { return intValue; }
        set { SetProperty(ref intValue, value); }
    }

    public IncrementIntViewModel()
    {
        incrementIntCommand = new Command(IncrementInt);
    }

    private Command incrementIntCommand;
    public Command IncrementIntCommand { get { return incrementIntCommand; } }
    public void IncrementInt()
    {
        IntValue++;
    }
}

IncrementIntView.xaml

<UserControl.DataContext>
    <local:IncrementIntViewModel x:Name="ViewModel" />
</UserControl.DataContext>

<Grid>
    <StackPanel Orientation="Horizontal">
        <Label Content="{Binding IntValue}" />
        <Button Content="Increment" Command="{Binding IncrementIntCommand}" Width="75" />
    </StackPanel>
</Grid>

MainWindow.xaml.cs

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

MainWindowViewModel.cs

public class MainWindowViewModel : ViewModelBase
{
    private int intValue = 2;
    public int IntValue
    {
        get { return intValue; }
        set { SetProperty(ref intValue, value); }
    }
}

MainWindow.xaml

<Window.DataContext>
    <local:MainWindowViewModel x:Name="ViewModel"/>
</Window.DataContext>

<Grid>
    <StackPanel Margin="10">
        <local:IncrementIntView IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
        <Label Content="{Binding IntValue}" />
    </StackPanel>
</Grid>

1 个答案:

答案 0 :(得分:0)

我可以看到你的代码将IntValue从MainWindowViewModel的属性传递给IncrementIntView的依赖属性到IncrementIntViewModel的属性。

增量按钮正在更新IncrementIntViewModel的IntValue属性。不幸的是,IncrementIntViewModel中发生的任何事情都没有被反映回IncrementIntView的IntValue依赖项属性。 TwoWay模式不在IncrementIntView的依赖属性和IncrementIntViewModel属性之间,但它在MainWindowViewModel属性和IncrementIntView的依赖属性之间。

简单的解决方案:将MainWindow的标签绑定到IncrementIntViewModel的IntValue属性,而不会打扰View的属性。

<local:IncrementIntView x:Name="iiv" IntValue="{Binding IntValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ElementName=ViewModel}" />
<Label Content="{Binding DataContext.IntValue, ElementName=iiv}" />
<!--You need to specify DataContext.IntValue, because you have same name for both view's dependency property and viewmodel's property-->

在这里你可以看到MainWindowViewModel的IntValue并不那么重要,因为它只是将值传递给IncrementIntViewModel一次,并且永远不会更新值。

另一个解决方案:您需要将值更改重新触发回MainViewModel的属性。

首先,MainViewModel和IncrementIntViewModel之间没有连接。一种解决方案是使MainViewModel成为单例,这样当在IncrementIntViewModel内部完成增量时,您也想要更新MainViewModel的属性。

在MainViewModel.cs

public static MainWindowViewModel SingletonInstance { get; set; }
public MainWindowViewModel()
{
    if (SingletonInstance == null)
    {
        SingletonInstance = this;
    }
}

在IncrementIntViewModel.cs

public void IncrementInt()
{
    IntValue++;
    MainWindowViewModel.SingletonInstance.IntValue = IntValue;
}

另一个解决方案:与上述解决方案类似,但我们不需要生成MainWindowViewModel的Singleton实例,因为MainWindow是单例开头的。

在IncrementIntViewModel.cs

public void IncrementInt()
{
    IntValue++;
    ((MainWindowViewModel)App.Current.MainWindow.DataContext).IntValue = IntValue;
}

如果您打算将IncrevalViewModel属性中的IntValue更新为IncrementView的依赖属性,那么您可能会问为什么需要这样做,因为MVVM应该在V和VM之间分离。 V应该关注VM,但不是相反。