首页 > 解决方案 > ObservableCollection<> 不通知视图

问题描述

我一直在寻找一段时间,我只是找不到我做错了什么。

我有一个我在视图中显示的名称列表,在我的视图中我创建了一个 itemsControl,ItemsSource 设置为 ViewModel 中的 observableCollection。目的是概述 nxn 表中的可用名称。用户应该能够使用顶部的搜索框过滤结果。

首先,我使用列表列表进行了尝试,该列表不起作用,因为视图没有根据搜索框中的字符串进行更新。我发现我实际上应该使用 ObservableCollection,因为它实现了 INotifyCollectionChanged。下面我尝试实现一个 ObservableCollection,但是当这个集合发生变化时我无法更新视图。

查看(请参阅 Xaml 部分中的 ItemsControl):

Xaml 资源

<UserControl.Resources>
    
    <DataTemplate x:Key="DataTemplate_Level2">
        <Button Content="{Binding}" Height="150" Width="150" Margin="20,20,20,20">
            <Button.Style>
                <Style TargetType="{x:Type Button}">
                    <Setter Property="Background" Value="#2ED99A"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type Button}">
                                <Border Background="{TemplateBinding Background}">
                                    <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="#2EB9D9"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Background" Value="#333f4a"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Button.Style>
        </Button>
    </DataTemplate>

    <DataTemplate x:Key="DataTemplate_Level1">
        <ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </DataTemplate>
    
</UserControl.Resources>

Xaml

<Grid Margin="10,10,10,10" VerticalAlignment="Stretch">
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    
    <Grid Grid.Row="0">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left">
            <TextBlock Text="{Binding HcfOverviewModel.NSharedModules, StringFormat=Shared Circuits: {0}}"></TextBlock>
            <TextBlock Margin="30,0,0,30" Text="{Binding HcfOverviewModel.NPrivateModules, StringFormat=Private Circuits: {0}}"></TextBlock>
        </StackPanel>
        <Button Grid.Column="1" VerticalAlignment="Top" MaxHeight="20" Content="See Private Circuits"></Button>
    </Grid>
    <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Hidden">
        <ItemsControl HorizontalAlignment="Center" VerticalAlignment="Top" ItemsSource="{Binding Modules, UpdateSourceTrigger=PropertyChanged}" ItemTemplate="{DynamicResource DataTemplate_Level1}"/>
    </ScrollViewer>
</Grid>

视图模型:

class HcfOverviewViewModel:BaseViewModel
{
    private HcfOverviewModel _hcfOverviewModel;
    private string _pathToSharedModules;
    //private List<List<string>> _modules;
    private ObservableCollection<ObservableCollection<string>> _modules;

    public HcfOverviewViewModel()
    {
        _pathToSharedModules = @"C:\Users\scamphyn\source\repos\TSD\TSD\TempTestFolder";
        _hcfOverviewModel = new HcfOverviewModel();
        _hcfOverviewModel.NSharedModules = CheckDirectory.FindModules(_pathToSharedModules).Length;
        _hcfOverviewModel.SharedModuleNames = CleanModuleNames(CheckDirectory.FindModules(_pathToSharedModules));
        //_modules = CreateGridLayout(_hcfOverviewModel.SharedModuleNames);
        _modules = CreateGrid(_hcfOverviewModel.SharedModuleNames);
    }

    public HcfOverviewModel HcfOverviewModel
    {
        get => _hcfOverviewModel;
    }
    
    public void Filter(string searchString)
    {
        Console.WriteLine(searchString);
        if (searchString == "")
        {
            //Modules = CreateGridLayout(_hcfOverviewModel.SharedModuleNames);
            Modules = CreateGrid(_hcfOverviewModel.SharedModuleNames);
        }
        else
        {
            Modules.Clear();
            string[] filteredNames = _hcfOverviewModel.SharedModuleNames.Where(n => n.Contains(searchString)).ToArray();
            //Modules = CreateGridLayout(filteredNames);
            Modules = CreateGrid(filteredNames);
        }
    }

    private string[] CleanModuleNames(string[] listToClean)
    {
        List<string> moduleNames = new List<string>();
        foreach (string s in listToClean)
        {
            string[] listPath = s.Split('\\');
            string name = listPath[listPath.Length - 1];
            moduleNames.Add(name);
        }
        return moduleNames.ToArray();
    }

    public ObservableCollection<ObservableCollection<string>> CreateGrid(string[] moduleNames)
    {
        ObservableCollection<ObservableCollection<string>> lsts = new ObservableCollection<ObservableCollection<string>>();
        int circuitsPerRow = 4;
        int _nRows = moduleNames.Length / circuitsPerRow;
        Queue<string> modules = new Queue<string>(moduleNames);
        for(int i = 0; i <= _nRows; i++)
        {
            lsts.Add(new ObservableCollection<string>());
            for (int j = 0; j < circuitsPerRow; j++)
            {
                if (modules.Count != 0)
                {
                    lsts[i].Add(modules.Dequeue());
                }
                else
                {
                    break;
                }
            }
        }
        return lsts;
    }

    public List<List<string>> CreateGridLayout(string[] moduleNames)
        //To Create the grid in HcfOverview
    {
        List<List<string>> lsts = new List<List<string>>(); // create list of list to store nxn matrix data in.
        int circuitsPerRow = 4;
        //Determine correct size nxn
        int _nRows = moduleNames.Length / circuitsPerRow;
        //Create Queue
        Queue<string> modules = new Queue<string>(moduleNames);
        //Create data for ItemControls
        for (int i = 0; i <= _nRows; i++) // number of rows
        {
            lsts.Add(new List<string>());
            
            for (int j = 0; j < circuitsPerRow; j++) // number of columns
            {
                if (modules.Count != 0)
                {
                    lsts[i].Add(modules.Dequeue());
                }
                else {
                    break;
                }
            }
        }
        return lsts;
    }

    public ObservableCollection<ObservableCollection<string>> Modules
    {
        get => _modules;
        set {
            _modules = value;
            OnPropertyChanged("Modules");
        }
    }

}

“_modules”是我的 ObservableCollection 填充在 CreateGrid 方法中。

这是它在窗口中的样子:

在此处输入图像描述

当我检测到更新时,搜索框位于“父”视图/视图模型中,我在上面显示的视图模型中调用过滤器方法来更新 ObservableCollection。

如何解决这个问题?

更新:

因为我已经被这个问题困扰了一周,所以我决定创建一个新的“项目”并检查我是否可以让它在一个简化的项目中工作。在这个简化的项目中,我设法添加和删除行。我使用了与以前完全相同的方法。所以我的 observablecollection 的绑定和更新是正确的。我现在想知道这个问题是否在于我如何加载这个 UserControl。显示此数据的 UserControl 是通过另一个 View 加载的。见下面的代码:

<ContentControl Grid.Row="2" Content="{Binding CurrentHcfViewModel}">
        <ContentControl.Resources>
            <DataTemplate DataType="{x:Type ViewModels:HcfOverviewViewModel}">
                <views:UserControlHcfOverview/>
            </DataTemplate>
        </ContentControl.Resources>
    </ContentControl>

我的 HcfOverviewViewModel 被加载到存在于不同视图中的用户控件中。这会对 ObeservableCollection 及其在 HcfOverviewViewModel 中的 View 的更新产生影响吗?

标签: c#wpfmvvmbindingobservablecollection

解决方案


我发现我实际上应该使用 ObservableCollection,因为它实现了 INotifyCollectionChanged。

由于您进行更新的方式,它没有像您认为的那样工作。当一个人将引用从一个更改ObservableCollection为另一个ObservableCollection时,这不会发送INotifyCollectionChanged消息。只有添加/删除/清除调用会在活动引用的集合上生成该消息。

当您重置参考时,您正在告诉ItemsControl完全清除自身,然后显示正在清除的内容。

Modules创建初始集合后,您无需更改它。您的逻辑还存在其他问题,除非有人重写您的自定义控件/逻辑,否则无法完全回答这个问题。

无论如何,您都需要从不更改引用开始,然后以与 parent 相同的模式处理更改内部集合项ObservableCollection


请注意,某些控件Null在设置为新列表时需要设置。不清楚这是否也使问题复杂化,因为您立即更改为较新的参考,而新的参考顶级似乎并没有改变整体ItemsControl


推荐阅读