WPF - 如何保持依赖项属性和视图模型属性同步?
在WPF中,我一直在试图弄清楚如何保持一个视图依赖属性和它的一个视图模型的属性同步一段时间没有任何运气。我对这个主题进行了大量的研究,但是没有一个建议的解决方案适用于我,我希望有人能够帮助我找到我失踪的东西。WPF - 如何保持依赖项属性和视图模型属性同步?
我尝试了很多在这篇文章中建议的东西,Twoway-bind view's DependencyProperty to viewmodel's property?,因为我看过的所有东西看起来都是最有前途的,但从来没有能够得到我期待的结果。
我写了一个简单的程序来演示我遇到的问题。其中我将MainWindowViewModel中的属性IntValue设置为2,然后将其绑定到在UserControl IncrementIntView中创建的依赖项属性。然后当我在IncrementIntView中按下按钮时,它将IntValue的值增加1。这一切工作的用户控件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>
我可以看到你的代码是从MainWindowViewModel的财产传递给INTVALUE IncrementIntView的依赖属性到IncrementIntViewModel的属性。
递增按钮正在更新IncrementIntViewModel的IntValue属性。不幸的是,IncrementIntViewModel中发生的任何事情都不会反射回IncrementIntView的IntValue依赖项属性。双向模式不在IncrementIntView的依赖项属性和IncrementIntViewModel的属性之间,但它位于MainWindowViewModel的属性和IncrementIntView的依赖项属性之间。
简单的解决方案:将MainWindow的标签绑定到IncrementIntViewModel的IntValue属性而不打扰视图的属性。
<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;
}
另外其他的解决办法:上述解决方案类似,但我们并不需要作出的Singleton实例MainWindowViewModel,因为MainWindow是以单例开始的。
在IncrementIntViewModel.cs
public void IncrementInt()
{
IntValue++;
((MainWindowViewModel)App.Current.MainWindow.DataContext).IntValue = IntValue;
}
如果你的意图是从IncrementViewModel的财产IncrementView的依赖属性更新INTVALUE,那么你可能会问,为什么你需要这样做,因为MVVM应该到V之间的分离和VM。 V应该看虚拟机,但不是相反。
您的其他解决方案和其他解决方案都可以正常工作,但正如您所说这对MVVM无效。我将退后一步并重新评估我的方法。谢谢您的帮助。 – ThatBaldGuy13
请注意'ElementName = ViewModel'完全是多余的。由于MainWindowViewModel实例已分配给Window的DataContext,因此不需要显式指定Binding源。绑定应该是'IntValue =“{Binding IntValue,Mode = TwoWay}”',因为'UpdateSourceTrigger = PropertyChanged'也是默认值。 – Clemens