【问题标题】:Update object property after property in BindingList is changed更改 BindingList 中的属性后更新对象属性
【发布时间】:2018-05-14 21:04:30
【问题描述】:

我对 WPF 和 MVVM 非常陌生,目前这让我很沮丧。我目前的问题是:

我有以下 XAML:

<Window.DataContext>
    <local:MainViewModel />
</Window.DataContext>

<WrapPanel Orientation="Horizontal">
    <ComboBox ItemsSource="{Binding StreetList}" SelectedItem="{Binding SelectedStreet}" DisplayMemberPath="StreetName" IsSynchronizedWithCurrentItem="True"/>
    <DataGrid ItemsSource="{Binding SelectedStreet.HouseList}" AutoGenerateColumns="False" >
        <DataGrid.Columns>
            <DataGridTextColumn Header="YearBuilt" Binding="{Binding YearBuilt}" />
            <DataGridTextColumn Header="Price" Binding="{Binding Price}" />
            <DataGridTextColumn Header="#Rooms" Binding="{Binding Rooms}" />
        </DataGrid.Columns>
    </DataGrid>
    <TextBox Text="{Binding AvgPricePerRoom, Mode=OneWay}" />
</WrapPanel>

地点:

public class MainViewModel : ViewModelBase
{
    public BindingList<StreetViewModel> StreetList { get { return _streetList; } }
    private BindingList<StreetViewModel> _streetList;

    public StreetViewModel SelectedStreet
    {
        get { return _selectedStreet; }
        set { _selectedStreet = (StreetViewModel)value.Clone(); RaisePropertyChanged(); RaisePropertyChanged(nameof(AvgPricePerRoom)); }
    }
    private StreetViewModel _selectedStreet;

    public double AvgPricePerRoom
    {
        get
        {
            return
              _selectedStreet.HouseList[0].Price / _selectedStreet.HouseList[0].Rooms / 3 +
              _selectedStreet.HouseList[1].Price / _selectedStreet.HouseList[1].Rooms / 3 +
              _selectedStreet.HouseList[2].Price / _selectedStreet.HouseList[2].Rooms / 3;
        }
    }

    public MainViewModel()
    {
        _streetList = new BindingList<StreetViewModel>();
        fill_HouseGroupList();
    }

    void fill_HouseGroupList() { ... }
}

和(已编辑):

public class StreetViewModel : ViewModelBase, ICloneable
{
    public string StreetName
    {
        get { return _streetName; }
        set { _streetName = value; RaisePropertyChanged(); }
    }
    private string _streetName;


    private BindingList<HouseViewModel> _houseList;

    private void HouseList_ListChanged(object sender, ListChangedEventArgs e)
    {
        throw new NotImplementedException();
    }

    public StreetViewModel() { }

    public StreetViewModel(string streetName) : this()
    {
        _streetName = streetName;

        _houseList = new BindingList<HouseViewModel>();

        HouseList.ListChanged += HouseList_ListChanged;
    }

    public BindingList<HouseViewModel> HouseList
    {
        get
        {
            if(_houseList.Count == 0)
            {
                switch (_streetName)
                {
                    case "Main street":
                        _houseList.Add(new HouseViewModel(new House(2015, 600, 9)));
                        _houseList.Add(new HouseViewModel(new House(1929, 20, 4)));
                        _houseList.Add(new HouseViewModel(new House(1969, 30, 6)));
                        break;
                    case "School street":
                        _houseList.Add(new HouseViewModel(new House(2017, 50, 10)));
                        _houseList.Add(new HouseViewModel(new House(1930, 10, 5)));
                        _houseList.Add(new HouseViewModel(new House(1970, 20, 7)));
                        break;
                    case "Garden street":
                        _houseList.Add(new HouseViewModel(new House(2014, 1, 15)));
                        _houseList.Add(new HouseViewModel(new House(1935, 20, 11)));
                        _houseList.Add(new HouseViewModel(new House(1978, 20, 9)));
                        break;
                }
            }
            return _houseList;
        }
    }

    public object Clone()
    {
        var houseGroupCopy = new StreetViewModel(this._streetName);

        houseGroupCopy._houseList = new BindingList<HouseViewModel>();
        foreach (var house in _houseList)
        {
            houseGroupCopy._houseList.Add(new HouseViewModel(new House(house.YearBuilt, house.Price, house.Rooms)));
        }

        return houseGroupCopy;
    }
}

加:

public class HouseViewModel : ViewModelBase
{
    public House House
    {
        get { return _house; }
        set { _house = value;  RaisePropertyChanged(); }
    }
    private House _house;

    public int YearBuilt
    {
        get { return _house.YearBuilt; }
        set { _house.YearBuilt = value; RaisePropertyChanged(); }
    }

    public double Price
    {
        get { return _house.Price; }
        set { _house.Price = value; RaisePropertyChanged(); }
    }

    public int Rooms
    {
        get { return _house.Rooms; }
        set { _house.Rooms = value; RaisePropertyChanged(); }
    }

    public HouseViewModel(House house)
    {
        _house = house;
    }
}

...所以在我的视图的 DataGrid 中,我可以在我的 HouseList 中编辑 House 项目的属性,这是一个 BindingList。 如何使属性 AvgPricePerRoom(显示在 TextBox 中)知道 DataGrid 中的更改,以便在网格更改时显示更新的 AvgPricePerRoom?我之所以使用 BindingList,是因为我读到与 ObservableCollection 不同,它在其项目的属性发生变化时也会引发一个事件(除了性能问题),但在这个示例中如何处理此事件并通知我的 AvgPricePerRoom?

(并非上面的所有代码对我的问题都很重要,对此我深表歉意……)

【问题讨论】:

  • 无关提示:而不是RaisePropertyChanged("AvgPricePerRoom");,更喜欢RaisePropertyChanged(nameof(AvgPricePerRoom));。这避免了使用字符串表示属性名称,以及一个可能会更改而不更改另一个的风险。
  • 另一个提示:注意HouseList 属性检查是否_houseList==null,如果是,它会尝试在空对象上调用Add。因此,如果这实际上是可能的,你就会得到一个 NullReferenceException。
  • 感谢@Richardissimo,我编辑了代码

标签: c# wpf mvvm observablecollection bindinglist


【解决方案1】:

在绑定集合时,有 3 种变更通知需要注意:

  1. 从列表中添加/删除的通知。这是唯一真正需要注意的 Notification ObservableCollection。
  2. 用于公开 ObservableCollection 的属性的通知(在您的示例中为 StreetList)。 OC 在大规模更改时非常糟糕(没有 AddRange 函数),因此通常最好在代码中创建一个新列表,仅将其作为最后一步公开。
  3. StreetViewModel 的每个属性的更改通知。

【讨论】:

  • 我的代码可能过于嵌套,但我关注的是由 DataGrid 表示的 BindingList&lt;HouseViewModel&gt; HouseList - 特别是您的第 3 点。带有 AvgPricePerRoom 的 TextBox 将如何始终根据 DataGrid 中的每次更改显示更新的结果?
  • 一旦你有了适当的 ChangeNotification,Binding 类就会处理细节。它所做的只是在两端(ViewModel 和 View)注册必要的事件,并将更改分配给另一端(如果是 TwoWayMode)。
  • 此外,您可能希望将 AvgPricePerRoom 转换为具有支持字段的完整属性。现在它是一个带有计算返回值的 get-only 属性,它要求你从其他地方调用它的 ChangeNotification。而是创建一个“CalculateAverage”函数来计算平均值并将其分配给属性,并让属性提高它自己的更改通知。
  • 所以我编辑了代码,我的 HouseList 现在应该通过 HouseList.ListChanged += HouseList_ListChanged; 监听其项目的变化。但是,当在数据网格中编辑项目时,HouseList_ListChanged 处理程序永远不会被调用,我不明白为什么。
  • 我没有看到你在 HouseList 中提出任何形式的 ChangeNotification,甚至没有以 HouseList 的名义提出。
【解决方案2】:

我找到了一个解决方案,但我不确定这是否是“正确的方法”:

按照建议,我将 AvgPricePerRoom 变成了带有支持字段的完整属性,并添加了一个设置 AvgPricePerRoom 的函数:

void CalculateAverage()
{
    AvgPricePerRoom =
          _selectedStreet.HouseList[0].Price / _selectedStreet.HouseList[0].Rooms / 3 +
          _selectedStreet.HouseList[1].Price / _selectedStreet.HouseList[1].Rooms / 3 +
          _selectedStreet.HouseList[2].Price / _selectedStreet.HouseList[2].Rooms / 3;
}

然后我将SelectedStreet属性更改为:

public StreetViewModel SelectedStreet
{
    get { return _selectedStreet; }
    set
    {
        _selectedStreet = value.Clone() as StreetViewModel;
        CalculateAverage();
        _selectedStreet.HouseList.ListChanged += (o, e) => CalculateAverage();
        RaisePropertyChanged();
    }
}

这是一个合适的 MVVM 解决方案吗?

【讨论】:

  • 我会将 CalculateAverage() 放在 ChangeNotifciation 之后。否则更新可能会以非常奇怪和矛盾的顺序发生。唯一的其他方法是让 StreetViewModel 注册它自己的 PropertyChangeNotificaiton。但 taht 实际上只是一个选项更改。携带这样的代码正是 Properties 或 Get/Set 函数的设计目的。那和继承。
猜你喜欢
  • 2018-12-31
  • 1970-01-01
  • 2013-01-18
  • 1970-01-01
  • 2012-03-01
  • 2018-03-01
  • 1970-01-01
  • 2012-06-15
  • 1970-01-01
相关资源
最近更新 更多