![](/img/trans.png)
[英]Refresh ObservableCollection<T> when a property in T changes - WPF MVVM
[英]WPF MVVM: How to reflect changes of ObservableCollection in the UI
我在WPF中相對較新,並且正在嘗試了解MVVM模式以及ObservableCollection如何進行數據綁定,以便構建我正在使用MVVM處理的應用程序。 我創建了一個具有MainWindow的應用程序示例,根據用戶按下哪個按鈕,將顯示不同的視圖(UserControl)。 總體思路是,用戶將可以訪問數據庫中某些元素的數據(例如:客戶,產品等),並且能夠添加新元素以及編輯或刪除現有元素。
因此,分別有一個CustomerView和它的CustomerViewModel,以及一個ProductView和它的ProductViewModel。 此外,還有兩個表示模型的類(Customer.cs和Product.cs)。 項目的結構顯示在此處 。
MainWindow.xaml如下:
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:CustomerViewModel}">
<views:CustomerView DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:ProductViewModel}">
<views:ProductView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="80*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="btnCustomers" Click="btnCustomers_Click" Content="Customers" Width="80" Height="50" Margin="10"/>
<Button x:Name="btnProducts" Click="btnProducts_Click" Content="Products" Width="80" Height="50" Margin="10"/>
</StackPanel>
<Grid Grid.Column="1">
<ContentControl Grid.Column="0" Content="{Binding}"/>
</Grid>
</Grid>
以及MainWindow.xaml.cs背后的代碼:
public partial class MainWindow : Window
{
public CustomerViewModel customerVM;
public ProductViewModel productVM;
public MainWindow()
{
InitializeComponent();
}
private void btnCustomers_Click(object sender, RoutedEventArgs e)
{
if (customerVM == null)
{
customerVM = new CustomerViewModel();
}
this.DataContext = customerVM;
}
private void btnProducts_Click(object sender, RoutedEventArgs e)
{
if (productVM == null)
{
productVM = new ProductViewModel();
}
this.DataContext = productVM;
}
}
最后,CustomerView.xaml如下所示:
<UserControl.Resources>
<viewModel:CustomerViewModel x:Key="customerVM"/>
<!-- Styling code here...-->
</UserControl.Resources>
<Grid DataContext="{StaticResource ResourceKey=customerVM}">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="7*"/>
<RowDefinition Height="3*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Text="Customers" FontSize="18"/>
</Grid>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="cmbCustomers" Grid.Column="0" VerticalAlignment="Top"
IsEditable="True"
Text="Select customer"
ItemsSource="{Binding}"
DisplayMemberPath="FullName" IsSynchronizedWithCurrentItem="True">
</ComboBox>
<StackPanel Grid.Column="1" Margin="5">
<StackPanel Orientation="Horizontal">
<TextBlock Grid.Column="0" Text="Id:" />
<TextBlock Grid.Column="1" x:Name="txtId" Text="{Binding Path=Id}" FontSize="16"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Grid.Column="0" Text="Name:" />
<TextBlock Grid.Column="1" x:Name="txtFirstName" Text="{Binding Path=FirstName}" FontSize="16"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Grid.Column="0" Text="Surname:" />
<TextBlock Grid.Column="1" x:Name="txtLastName" Text="{Binding Path=LastName}" FontSize="16"/>
</StackPanel>
</StackPanel>
</Grid>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="btnAddNew" Content="Add New" Click="btnAddNew_Click"/>
<Button x:Name="btnDelete" Content="Delete Customer" Click="btnDelete_Click"/>
</StackPanel>
</Grid>
和CustomerViewModel.cs:
public class CustomerViewModel : ObservableCollection<Customer>
{
public CustomerViewModel()
{
LoadCustomers();
}
private void LoadCustomers()
{
for (int i = 1; i <= 5; i++)
{
var customer = new Customer()
{
Id = i,
FirstName = "Customer_" + i.ToString(),
LastName = "Surname_" + i.ToString()
};
this.Add(customer);
}
}
public void AddNewCustomer(int id)
{
var customer = new Customer()
{
Id = id,
FirstName = "Customer_" + id.ToString(),
LastName = "Surname_" + id.ToString()
};
Add(customer);
}
}
請注意,ProductView.xaml和ProductViewModel.cs是相似的。 當前,當用戶按下MainWindow的“客戶”或“產品”按鈕時,將顯示相應的視圖,並根據LoadCustomers(或LoadProducts)方法加載集合,該方法由ViewModel的構造函數調用。 同樣,當用戶從組合框選擇其他對象時,其屬性也會正確顯示(即ID,Name等)。 問題是當用戶添加新(或刪除現有)元素時。
問題1 :更新元素的已更改可觀察集合並在UI(組合框,屬性等)中反映其更改的正確和最佳方法是什么?
問題2 :在測試該項目期間,我注意到ViewModels的構造函數(因此是LoadCustomers&LoadProducts方法)被調用了兩次。 但是,僅當用戶分別按下“客戶”或“產品”按鈕時才調用它。 是否也通過XAML數據綁定來調用? 這是最佳實現嗎?
您的第一個問題基本上是用戶體驗,沒有正確或“最佳”的方法。 您肯定會最終使用某種ItemsControl
,但是哪種很大程度上取決於您希望用戶如何與之交互。
第二個問題,您的代碼有一些錯誤:
<viewModel:CustomerViewModel x:Key="customerVM"/>
實例化一個新的視圖模型,除了主應用程序創建的模型之外
Grid DataContext="{StaticResource ResourceKey=customerVM}"
然后使用此“本地”視圖模型,而忽略了從主應用程序繼承的模型
這就是為什么您看到構造函數觸發兩次,而您正在構造兩個實例的原因! 消除本地VM,不要在網格上分配DC。 其他事宜:
<views:ProductView DataContext="{Binding}"/>
DataContext分配是完全不必要的,因為它位於數據模板中,因此已經設置了數據上下文 <ContentControl Grid.Column="0" Content="{Binding}"/>
好吧,您應該有一個“ MainViewModel”及其使用的屬性 。 不要讓它成為整個數據上下文
缺少用於單擊按鈕的命令(與上面的項目符號有關)
MVVM中的列表需要3種變更通知:
作為高級選項,請考慮公開CollectionView而不是原始Collection。 WPF GUI元素不綁定到原始Collection,僅綁定到CollectionViews。 但是,如果您不交給他們一個,他們就會自己創造一個。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.