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>