如何判断ViewModel属性是由源(ViewModel)还是目标(View)设置?
区分双向绑定中ViewModel属性的更新来源
方案1:利用IEditableObject接口追踪编辑状态
WPF在通过双向绑定更新ViewModel属性时,会自动调用IEditableObject的BeginEdit方法。我们可以借助这个特性,在属性setter中判断当前是否处于编辑状态,以此区分更新来源是View还是ViewModel自身。
示例代码:
public class MyViewModel : INotifyPropertyChanged, IEditableObject { private bool _isInEditMode; private string _myProperty; public string MyProperty { get => _myProperty; set { if (_myProperty != value) { _myProperty = value; OnPropertyChanged(); // 根据编辑状态判断更新来源 if (_isInEditMode) { // 来自View的更新 HandleViewInitiatedUpdate(value); } else { // ViewModel自身发起的更新 HandleVmInitiatedUpdate(value); } } } } public void BeginEdit() => _isInEditMode = true; public void EndEdit() => _isInEditMode = false; public void CancelEdit() => _isInEditMode = false; // INotifyPropertyChanged实现 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private void HandleViewInitiatedUpdate(string value) { // 处理View触发的更新逻辑 } private void HandleVmInitiatedUpdate(string value) { // 处理ViewModel自身触发的更新逻辑 } }
方案2:拆分属性分离更新来源
放弃单一属性的双向绑定,拆分为两个属性:一个专门接收View的输入(仅单向绑定View到ViewModel),另一个由ViewModel自身维护(单向绑定ViewModel到View)。这种方式逻辑最清晰,完全避免了来源混淆的问题。
示例代码:
public class MyViewModel : INotifyPropertyChanged { // 供View绑定,接收View的输入(View→ViewModel单向绑定) private string _viewInputProperty; public string ViewInputProperty { get => _viewInputProperty; set { if (_viewInputProperty != value) { _viewInputProperty = value; OnPropertyChanged(); // 这里肯定是View触发的更新 HandleViewInitiatedUpdate(value); // 同步到ViewModel内部属性(如果需要) VmInternalProperty = value; } } } // ViewModel内部维护的属性(ViewModel→View单向绑定) private string _vmInternalProperty; public string VmInternalProperty { get => _vmInternalProperty; set { if (_vmInternalProperty != value) { _vmInternalProperty = value; OnPropertyChanged(); // 这里肯定是ViewModel自身触发的更新 HandleVmInitiatedUpdate(value); // 同步到View输入属性(如果需要,避免循环更新) if (_viewInputProperty != value) { _viewInputProperty = value; OnPropertyChanged(nameof(ViewInputProperty)); } } } } // INotifyPropertyChanged实现省略... }
在View中,将UserControl的依赖属性分别绑定到这两个属性:
<!-- View→ViewModel绑定 --> <local:MyUserControl MyDependencyProperty="{Binding ViewInputProperty, Mode=OneWayToSource}" /> <!-- ViewModel→View绑定 --> <local:MyUserControl MyDependencyProperty="{Binding VmInternalProperty, Mode=OneWay}" />
(注:如果需要双向同步效果,可根据业务逻辑在两个属性的setter中互相同步,避免循环更新即可)
方案3:自定义SetProperty方法添加标记
在ViewModel的基类中封装一个带来源标记的SetProperty方法,ViewModel自身更新属性时调用带标记的重载,而绑定触发的setter调用默认重载,以此区分来源。
示例代码:
public class ViewModelBase : INotifyPropertyChanged { protected bool SetProperty<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null) { return SetProperty(ref backingField, value, false, propertyName); } protected bool SetProperty<T>(ref T backingField, T value, bool isVmInitiated, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(backingField, value)) return false; backingField = value; OnPropertyChanged(propertyName); if (isVmInitiated) { HandleVmInitiatedUpdate(propertyName, value); } else { HandleViewInitiatedUpdate(propertyName, value); } return true; } // 处理不同来源更新的虚方法,子类可重写 protected virtual void HandleViewInitiatedUpdate(string propertyName, object value) { } protected virtual void HandleVmInitiatedUpdate(string propertyName, object value) { } // INotifyPropertyChanged实现省略... } public class MyViewModel : ViewModelBase { private string _myProperty; public string MyProperty { get => _myProperty; set => SetProperty(ref _myProperty, value); // 绑定触发的更新,无标记 } // ViewModel自身更新时调用带标记的重载 public void UpdatePropertyFromVm(string newValue) { SetProperty(ref _myProperty, newValue, isVmInitiated: true); } }
内容的提问来源于stack exchange,提问作者Astro
相关产品推荐
相关产品推荐

