C# WPF MVVM - 当项目添加到 VM 实例中的 ObservableCollection 时,视图不会改变
C# WPF MVVM - View doesn't change when Item Added to ObservableCollection in VM Instance
描述
我目前正在构建一个应用程序,如果用户在主视图上找到的组合框上触发事件,当用户触发事件到添加项目,它成功地将它添加到集合中,但不会更新 ObservableCollection 绑定的 ItemsControl 中的 UI。
问题
当 ObservableCollection 在另一个 ViewModel 中添加了一个项目时,UI 没有更新。
工作列表
JobList 派生自 ObservableCollection<Job>
类型,允许在加载应用程序时使用 xml 加载稍后存储的数据,它有 6 个项目的限制。
class JobList : ObservableCollection<Job>
{
public JobList () : base ()
{
if (JobListExists)
LoadExistingJobList();
}
private bool JobListExists
{
get {
bool result = false;
if (File.Exists(recentsPath + @"\RecentItems.xml"))
{
result = true;
}
return result;
}
}
private void LoadExistingJobList ()
{
using (var reader = new StreamReader(recentsPath + @"\RecentItems.xml"))
{
XmlSerializer deserializer = new XmlSerializer(typeof(List<Job>),
new XmlRootAttribute("Jobs"));
List<Job> jobs = (List<Job>)deserializer.Deserialize(reader);
foreach (Job job in jobs)
{
Add(job);
}
}
}
public void AddToCollection (Job job)
{
Insert(0, job);
if (Count == 7)
RemoveAt(6);
}
}
主视图
MainView 用于 MainWindow 并存储 SearchBox(可在多个位置使用)和存储 CurrentView 的 ContentControl。当从此处发生事件时,绑定到 ContentControl 的视图不会发生后续更改。
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Border Background="#272537"
CornerRadius="15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Source="/Images/LegIcon.png" Width="30" Height="30" />
<StackPanel Grid.Row="1">
<RadioButton Height="40" Margin="5" Content="/Images/Home.png" Style="{StaticResource MenuButtonTheme}" IsChecked="True" Command="{Binding HomeViewCommand}"/>
<RadioButton Height="40" Margin="5" Content="/Images/Globals.png" Style="{StaticResource MenuButtonTheme}" Command="{Binding GlobalViewCommand}"/>
</StackPanel>
<RadioButton Grid.Row="1" Height="40" Margin="5" Content="/Images/Settings.png" VerticalAlignment="Bottom" Style="{StaticResource MenuButtonTheme}"/>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<ComboBox Width="190"
Height="40"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5"
Grid.Column="1"
Style="{StaticResource SearchBoxTheme}"
ItemsSource="{Binding CurrentDropdownData}"
IsEditable="true"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True">
<ComboBox.InputBindings>
<KeyBinding Gesture="Enter"
Command="{Binding SearchBoxCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ComboBox}}}"/>
</ComboBox.InputBindings>
</ComboBox>
<Button Name="MinimizeButton" Style="{StaticResource MinimizeButton}" Margin="5" Width="40" Click="MinimizeButton_Click" />
</StackPanel>
<ContentControl Grid.Row="1"
Grid.Column="1"
Content="{Binding CurrentView}"/>
</Grid>
</Border>
MainViewModel
class MainViewModel : ObservableObject
{
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand GlobalViewCommand { get; set; }
public RelayCommand SearchBoxCommand { get; set; }
public HomeViewModel HomeVM { get; set; }
public GlobalViewModel GlobalVM { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
private ICollectionView _currentDropdownData;
public ICollectionView CurrentDropdownData
{
get { return _currentDropdownData; }
set
{
_currentDropdownData = value;
OnPropertyChanged("CurrentDropdownData");
}
}
public MainViewModel()
{
CurrentDropdownData = CollectionViewSource.GetDefaultView(DataProvider.GetProjectItems());
HomeVM = new HomeViewModel();
GlobalVM = new GlobalViewModel();
CurrentView = HomeVM;
HomeViewCommand = new RelayCommand(o =>
{
CurrentDropdownData = CollectionViewSource.GetDefaultView(DataProvider.GetProjectItems());
CurrentView = HomeVM;
SearchBoxCommand = new RelayCommand(e =>
{
((HomeViewModel)CurrentView).Collection.AddToCollection((Job)CurrentDropdownData.CurrentItem);
});
});
GlobalViewCommand = new RelayCommand(o =>
{
CurrentDropdownData = CollectionViewSource.GetDefaultView(DataProvider.GetGlobalItems());
CurrentView = GlobalVM;
SearchBoxCommand = new RelayCommand(e =>
{
});
});
SearchBoxCommand = new RelayCommand(e =>
{
((HomeViewModel)CurrentView).Collection.AddToCollection((Job)CurrentDropdownData.CurrentItem);
});
}
}
主页视图
主页视图仅使用 ItemsControl 将“集合”JobList 引用为 DataBinding 来显示当前选择的作业
<UserControl.DataContext>
<viewModel:HomeViewModel/>
</UserControl.DataContext>
<ItemsControl x:Name="TilePanel" ItemsSource="{Binding Collection, Mode=TwoWay}" xmlns:model="clr-namespace:JobMate_2.MVVM.Model">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type model:Job}">
<Border Background="#353340"
CornerRadius="5"
Margin="5"
Height="50">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name, Mode=OneWay}"
Foreground="White"
Margin="5"
FontFamily="/Fonts/#Poppins"
x:Name="JobTitle"/>
<Button Grid.Column="1"
Content="."
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="2.5"
Visibility="Hidden"/>
<UniformGrid Columns="3" Grid.Row="1">
<Button Content="Shared" Margin="2.5" Style="{StaticResource FileLocationButton}"/>
<Button Content="Job Data" Margin="2.5" Style="{StaticResource FileLocationButton}"/>
<Button Content="Proc. Data" Margin="2.5" Style="{StaticResource FileLocationButton}"/>
</UniformGrid>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
HomeViewModel
HomeViewModel 非常简单地存储在视图中用于数据绑定的集合。
class HomeViewModel : ObservableObject
{
private JobList _collection;
public JobList Collection
{
get { return _collection; }
set
{
_collection = value;
OnPropertyChanged("Collection");
}
}
public HomeViewModel ()
{
Collection = new JobList()
{
new Job () { Name = "Test" },
new Job () { Name = "Test 2" },
new Job () { Name = "Test 3" }
};
}
}
只能有一个原因。
Window 和 UserControl.
中有不同的 HomeViewModel
实例
UserControl 不必创建自己的数据上下文。
他应该通过绑定到 CurrentView 从 Window 获取它。
如果您在 Window 中创建一个 UserControl 实例,并在其数据上下文中使用 MainViewModel,那么您需要这样的绑定:
<UserControl *****
***********
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CurrentView}">
<d:UserControl.DataContext>
<viewModel:HomeViewModel/>
</d:UserControl.DataContext>
描述
我目前正在构建一个应用程序,如果用户在主视图上找到的组合框上触发事件,当用户触发事件到添加项目,它成功地将它添加到集合中,但不会更新 ObservableCollection 绑定的 ItemsControl 中的 UI。
问题
当 ObservableCollection 在另一个 ViewModel 中添加了一个项目时,UI 没有更新。
工作列表
JobList 派生自 ObservableCollection<Job>
类型,允许在加载应用程序时使用 xml 加载稍后存储的数据,它有 6 个项目的限制。
class JobList : ObservableCollection<Job>
{
public JobList () : base ()
{
if (JobListExists)
LoadExistingJobList();
}
private bool JobListExists
{
get {
bool result = false;
if (File.Exists(recentsPath + @"\RecentItems.xml"))
{
result = true;
}
return result;
}
}
private void LoadExistingJobList ()
{
using (var reader = new StreamReader(recentsPath + @"\RecentItems.xml"))
{
XmlSerializer deserializer = new XmlSerializer(typeof(List<Job>),
new XmlRootAttribute("Jobs"));
List<Job> jobs = (List<Job>)deserializer.Deserialize(reader);
foreach (Job job in jobs)
{
Add(job);
}
}
}
public void AddToCollection (Job job)
{
Insert(0, job);
if (Count == 7)
RemoveAt(6);
}
}
主视图
MainView 用于 MainWindow 并存储 SearchBox(可在多个位置使用)和存储 CurrentView 的 ContentControl。当从此处发生事件时,绑定到 ContentControl 的视图不会发生后续更改。
<Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext>
<Border Background="#272537"
CornerRadius="15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Source="/Images/LegIcon.png" Width="30" Height="30" />
<StackPanel Grid.Row="1">
<RadioButton Height="40" Margin="5" Content="/Images/Home.png" Style="{StaticResource MenuButtonTheme}" IsChecked="True" Command="{Binding HomeViewCommand}"/>
<RadioButton Height="40" Margin="5" Content="/Images/Globals.png" Style="{StaticResource MenuButtonTheme}" Command="{Binding GlobalViewCommand}"/>
</StackPanel>
<RadioButton Grid.Row="1" Height="40" Margin="5" Content="/Images/Settings.png" VerticalAlignment="Bottom" Style="{StaticResource MenuButtonTheme}"/>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<ComboBox Width="190"
Height="40"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5"
Grid.Column="1"
Style="{StaticResource SearchBoxTheme}"
ItemsSource="{Binding CurrentDropdownData}"
IsEditable="true"
DisplayMemberPath="Name"
IsSynchronizedWithCurrentItem="True">
<ComboBox.InputBindings>
<KeyBinding Gesture="Enter"
Command="{Binding SearchBoxCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ComboBox}}}"/>
</ComboBox.InputBindings>
</ComboBox>
<Button Name="MinimizeButton" Style="{StaticResource MinimizeButton}" Margin="5" Width="40" Click="MinimizeButton_Click" />
</StackPanel>
<ContentControl Grid.Row="1"
Grid.Column="1"
Content="{Binding CurrentView}"/>
</Grid>
</Border>
MainViewModel
class MainViewModel : ObservableObject
{
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand GlobalViewCommand { get; set; }
public RelayCommand SearchBoxCommand { get; set; }
public HomeViewModel HomeVM { get; set; }
public GlobalViewModel GlobalVM { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
private ICollectionView _currentDropdownData;
public ICollectionView CurrentDropdownData
{
get { return _currentDropdownData; }
set
{
_currentDropdownData = value;
OnPropertyChanged("CurrentDropdownData");
}
}
public MainViewModel()
{
CurrentDropdownData = CollectionViewSource.GetDefaultView(DataProvider.GetProjectItems());
HomeVM = new HomeViewModel();
GlobalVM = new GlobalViewModel();
CurrentView = HomeVM;
HomeViewCommand = new RelayCommand(o =>
{
CurrentDropdownData = CollectionViewSource.GetDefaultView(DataProvider.GetProjectItems());
CurrentView = HomeVM;
SearchBoxCommand = new RelayCommand(e =>
{
((HomeViewModel)CurrentView).Collection.AddToCollection((Job)CurrentDropdownData.CurrentItem);
});
});
GlobalViewCommand = new RelayCommand(o =>
{
CurrentDropdownData = CollectionViewSource.GetDefaultView(DataProvider.GetGlobalItems());
CurrentView = GlobalVM;
SearchBoxCommand = new RelayCommand(e =>
{
});
});
SearchBoxCommand = new RelayCommand(e =>
{
((HomeViewModel)CurrentView).Collection.AddToCollection((Job)CurrentDropdownData.CurrentItem);
});
}
}
主页视图
主页视图仅使用 ItemsControl 将“集合”JobList 引用为 DataBinding 来显示当前选择的作业
<UserControl.DataContext>
<viewModel:HomeViewModel/>
</UserControl.DataContext>
<ItemsControl x:Name="TilePanel" ItemsSource="{Binding Collection, Mode=TwoWay}" xmlns:model="clr-namespace:JobMate_2.MVVM.Model">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type model:Job}">
<Border Background="#353340"
CornerRadius="5"
Margin="5"
Height="50">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="25"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name, Mode=OneWay}"
Foreground="White"
Margin="5"
FontFamily="/Fonts/#Poppins"
x:Name="JobTitle"/>
<Button Grid.Column="1"
Content="."
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Margin="2.5"
Visibility="Hidden"/>
<UniformGrid Columns="3" Grid.Row="1">
<Button Content="Shared" Margin="2.5" Style="{StaticResource FileLocationButton}"/>
<Button Content="Job Data" Margin="2.5" Style="{StaticResource FileLocationButton}"/>
<Button Content="Proc. Data" Margin="2.5" Style="{StaticResource FileLocationButton}"/>
</UniformGrid>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
HomeViewModel
HomeViewModel 非常简单地存储在视图中用于数据绑定的集合。
class HomeViewModel : ObservableObject
{
private JobList _collection;
public JobList Collection
{
get { return _collection; }
set
{
_collection = value;
OnPropertyChanged("Collection");
}
}
public HomeViewModel ()
{
Collection = new JobList()
{
new Job () { Name = "Test" },
new Job () { Name = "Test 2" },
new Job () { Name = "Test 3" }
};
}
}
只能有一个原因。
Window 和 UserControl.
中有不同的 HomeViewModel
实例
UserControl 不必创建自己的数据上下文。
他应该通过绑定到 CurrentView 从 Window 获取它。
如果您在 Window 中创建一个 UserControl 实例,并在其数据上下文中使用 MainViewModel,那么您需要这样的绑定:
<UserControl *****
***********
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CurrentView}">
<d:UserControl.DataContext>
<viewModel:HomeViewModel/>
</d:UserControl.DataContext>