【问题标题】:Updating binding for ObservableCollection member property without INotifyPropertyChanged在没有 INotifyPropertyChanged 的​​情况下更新 ObservableCollection 成员属性的绑定
【发布时间】:2015-03-30 07:01:15
【问题描述】:

我有一个 WPF View 绑定到实现 INotifyPropertyChangedViewModel。在那个View 中,我有一个DataGrid,其项目绑定到ViewModel 中的ObservableCollectionObservableCollection 成员本身不实现 INotifyPropertyChanged。在 DataGrid 中,我有三列 - 两列可编辑的 DatePicker 列和一个只读的 Checkbox 列,当用户选择包含当前日期的日期范围时,应该检查它们。

绑定在第一次加载视图时起作用,但是当我更改其他两列中的日期时,Checkbox 的绑定没有更新,大概是因为 MyDomainObject 类没有实现 INotifyPropertyChanged。我真的不想在这里实现那个接口,因为ObservableCollection 是从 Web 服务返回的类型,这样做会迫使我创建和维护该类型的副本,而且我有很多类似的场景在我的应用程序中。有什么办法可以强制复选框更新?如果可能的话,我更愿意避免使用后面的代码——如果我必须打破这条规则,我知道该怎么做。

下面的代码应该能大致说明问题:

XAML:

<DataGrid ItemsSource="{Binding MyDomainObjectCollection}">
    <DataGrid.Columns>

        <DataGridTemplateColumn Header="Start Date">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding StartDate, StringFormat=d}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <DatePicker SelectedDate="{Binding StartDate, UpdateSourceTrigger=PropertyChanged}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="End Date">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding EndDate, StringFormat=d}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <DatePicker SelectedDate="{Binding EndDate, UpdateSourceTrigger=PropertyChanged}" />
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="Is Active">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding IsActive, Mode=OneWay}" IsEnabled="False" />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

    </DataGrid.Columns>
</DataGrid>

查看模型:

public class MyViewModel : INotifyPropertyChanged {

    ObservableCollection<MyDomainObject> _myDomainObjectCollection;

    public ObservableCollection<MyDomainObject> MyDomainObjectCollection {
        get { return this._myDomainObjectCollection; }
        set { this._myDomainObjectCollection = value; this.OnPropertyChanged(); }
    }

    [...]

}

域对象:

public class MyDomainObject {

    public DateTime StartDate { get; set; }

    public DateTime EndDate { get; set; }

    public bool IsActive {
        get { return StartDate < DateTime.Now && EndDate > DateTime.Now; }
    }

}

【问题讨论】:

    标签: c# .net wpf mvvm data-binding


    【解决方案1】:

    如果您不想在域对象上实现INotifyPropertyChanged 接口,您可以使用与您的 DomainObject 具有相同接口并实现 INotifyPropertyChanged 的​​ ViewModel 包装您的 DomainObject

    public class MyDomainObjectViewModel : INotifyPropertyChanged {
    
      private MyDomainObject _domainObject;
    
      public MyDomainObjectViewModel(MyDomainObject domainObject){
        _domainObject = domainObject;
      }
    
      public DateTime StartDate { 
        get{
          return _domainObject.StartDate;
        }
        set{
          _domainObject.StartDate = value;
          RaisePropertyChanged("StartDate");
          RaisePropertyChanged("IsActive");
        }
      }
    
      public DateTime EndDate { 
        get{
          return _domainObject.EndDate ;
        }
        set{
          _domainObject.EndDate = value;
          RaisePropertyChanged("EndDate");
          RaisePropertyChanged("IsActive");
        }
      }
    
      public bool IsActive {
        get { return StartDate < DateTime.Now && EndDate > DateTime.Now; }
      }
    
    }
    

    您的 ObservableCollection 将是 ObservableCollection&lt;MyDomainObjectViewModel&gt; 类型。

    【讨论】:

    • 嗨 Jedof,是的,这是我的后备计划。问题是我可能有几十个类似的场景,两次有效地定义同一个对象是相当大的维护开销,一次使用 INotifyPropertyChanged,一次没有,所以我希望可能有某种技巧可以在视图模型中使用强制更新绑定。
    • 在四处寻找替代方案后,我最终采用了这个解决方案。我敢肯定,DynamicProxy 会很好地工作,并且总体上会减少代码,但可读性是我决定的一个因素,这是更简单的解决方案。谢谢!
    【解决方案2】:

    几年前我也遇到过同样的问题。我找到的解决方案使用动态对象作为实现 INotifyPropertyChanged 的​​代理对象。它通过反射挂钩 getter 和 setter,并在代理更改时更新源对象。

    public class DynamicProxy : DynamicObject, INotifyPropertyChanged
    {
        private readonly PropertyDescriptorCollection mPropertyDescriptors;
    
    
        protected PropertyInfo GetPropertyInfo(string propertyName)
        {
            return
                ProxiedObject.GetType()
                    .GetProperties()
                    .FirstOrDefault(propertyInfo => propertyInfo.Name == propertyName);
        }
    
        protected virtual void SetMember(string propertyName, object value)
        {
            var propertyInfo = GetPropertyInfo(propertyName);
    
            if (propertyInfo.PropertyType == value.GetType())
            {
                propertyInfo.SetValue(ProxiedObject, value, null);
            }
            else
            {
                var underlyingType = Nullable.GetUnderlyingType(propertyInfo.PropertyType);
    
                if (underlyingType != null)
                {
                    var propertyDescriptor = mPropertyDescriptors.Find(propertyName, false);
    
                    var converter = propertyDescriptor.Converter;
                    if (converter != null && converter.CanConvertFrom(typeof (string)))
                    {
                        var convertedValue = converter.ConvertFrom(value);
                        propertyInfo.SetValue(ProxiedObject, convertedValue, null);
                    }
                }
            }
            RaisePropertyChanged(propertyName);
        }
    
        protected virtual object GetMember(string propertyName)
        {
            return GetPropertyInfo(propertyName).GetValue(ProxiedObject, null);
        }
    
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    
        protected virtual void RaisePropertyChanged(string propertyName)
        {
            OnPropertyChanged(propertyName);
        }
    
        #region constructor
    
        public DynamicProxy()
        {
        }
    
        public DynamicProxy(object proxiedObject)
        {
            ProxiedObject = proxiedObject;
            mPropertyDescriptors = TypeDescriptor.GetProperties(ProxiedObject.GetType());
        }
    
        #endregion
    
        public override bool TryConvert(ConvertBinder binder, out object result)
        {
            if (binder.Type == typeof (INotifyPropertyChanged))
            {
                result = this;
                return true;
            }
    
            if (ProxiedObject != null && binder.Type.IsInstanceOfType(ProxiedObject))
            {
                result = ProxiedObject;
                return true;
            }
            return base.TryConvert(binder, out result);
        }
    
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = GetMember(binder.Name);
            return true;
        }
    
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            SetMember(binder.Name, value);
            return true;
        }
    
        #region public properties
    
        public object ProxiedObject { get; set; }
    
        #endregion
    
        #region INotifyPropertyChanged Member
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        #endregion
    }
    

    假设我们有一个这样的域对象:

    public class DomainObject
    {
        public string Name { get; set; }
    }
    

    还有这个观点:

    <Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBox 
            VerticalAlignment="Top"
            Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}">            
        </TextBox>
    </Grid>
    </Window>
    

    并像这样为其分配一个代理:

    DataContext = new DynamicProxy(new DomainObject());
    

    【讨论】:

    • 非常感谢您的建议。我必须完全诚实地说我不喜欢在视图和域对象之间放置代理的想法,因为它会使我的团队其他成员的代码更难阅读和调试,但我很感激这是一个潜在可行的解决方案。我将与我的团队讨论,但我仍然希望有某种手动触发更新的方法,如果存在这样的方法,会更简单一些。
    猜你喜欢
    • 2021-02-11
    • 2011-09-02
    • 2017-04-19
    • 1970-01-01
    • 1970-01-01
    • 2013-03-30
    • 1970-01-01
    • 1970-01-01
    • 2011-03-01
    相关资源
    最近更新 更多