在 BindingList 中的 属性 更改后更新对象 属性
Update object property after property in BindingList is changed
我对 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 知道 DataGrid 中的变化,以便在网格发生变化时显示更新的 AvgPricePerRoom?我使用 BindingList 是因为我读到它与 ObservableCollection 不同,它也会在其项目的属性发生变化时引发事件(除了性能问题),但我如何处理此事件并在此示例中通知我的 AvgPricePerRoom?
(不是上面所有的代码对我的问题都很重要,对此我很抱歉...)
绑定集合时,有3种变化通知你必须注意:
- 列表中 adding/removes 的通知。这是唯一实际处理的 Notification ObservableCollection。
- 属性 公开 ObservableCollection(在您的示例中为 StreetList)的通知。 OC 非常不擅长大规模更改(没有 AddRange 函数),因此通常最好在代码中创建一个新列表,只在最后一步公开它。
- 每个 属性 街景模型的更改通知。
我找到了一个解决方案,虽然我不确定是否 "right way" 可以这样做:
按照建议,我将 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 解决方案吗?
我对 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 知道 DataGrid 中的变化,以便在网格发生变化时显示更新的 AvgPricePerRoom?我使用 BindingList 是因为我读到它与 ObservableCollection 不同,它也会在其项目的属性发生变化时引发事件(除了性能问题),但我如何处理此事件并在此示例中通知我的 AvgPricePerRoom?
(不是上面所有的代码对我的问题都很重要,对此我很抱歉...)
绑定集合时,有3种变化通知你必须注意:
- 列表中 adding/removes 的通知。这是唯一实际处理的 Notification ObservableCollection。
- 属性 公开 ObservableCollection(在您的示例中为 StreetList)的通知。 OC 非常不擅长大规模更改(没有 AddRange 函数),因此通常最好在代码中创建一个新列表,只在最后一步公开它。
- 每个 属性 街景模型的更改通知。
我找到了一个解决方案,虽然我不确定是否 "right way" 可以这样做:
按照建议,我将 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 解决方案吗?