MVVM 从 ViewModel 刷新数据网格,即使集合没有改变

MVVM Refresh Datagrid from ViewModel even when collection doesn't change

我正在编写一个应用程序来读取和分析我公司软件使用的一些日志。有多种类型的日志,但为了描述我的问题,我们只取两种。即 TypeATypeB 的日志。我设计了一个 class 来保存一行日志数据,名为 LogLine 如下所示。

public class LogLine
{
    public long LineNum { get; set; }
    public string Msg { get; set; }
}

这是我的problem/requirement。

在我的主 ViewModel 中,我想在应用程序加载时只读取每种类型的日志一次。读取 TypeA 次日志,并存储在 ObservableCollectionLogLine 实例中,对 TypeB 执行相同的操作。然后根据我的选择 DataGrid 显示一种类型的日志,如果我在任何时候单击一个按钮,相同的 DataGrid 应该显示另一种类型的日志。请注意,我的日志数据没有改变,我只是想显示我选择的日志。

为此,我创建了三个 classes,即 ControllerMainControllerAControllerB。最后两个像这样从前者派生:

public class ControllerMain
{
    public ControllerMain()
    {
        LogLineList = new ObservableCollection<LogLine>();
    }

    private ObservableCollection<LogLine> logLineList;
    public ObservableCollection<LogLine> LogLineList
    {
        get { return logLineList; }
        set { logLineList = value; }
    }
}

public class ControllerA : ControllerMain
{
    public ControllerA() { }
    // More stuff here
}

public class ControllerB : ControllerMain
{
    public ControllerB() { }
    // More stuff here
}

正如您所猜测的那样,ControllerA 旨在保存 TypeA 的日志,以及这些日志独有的相关属性和方法。 TypeB 日志也是如此。

在我的 ViewModel 中,我有上面每个 classes 的实例,并且在应用程序加载时我读取日志数据并存储在适当的 class 对象中。

    public ControllerMain COMMON_LOG { get; set; }
    public ControllerA A_LOG { get; set; }
    public ControllerB B_LOG { get; set; }

    public ViewModelMain()
    {
        isAType = true;
        ClickCommand = new CustomCommand(ClickCmd, CanClickCmd);

        A_LOG = new ControllerA
        {
            // This simulates reading logs from files - done only once
            LogLineList = DataService.GetAData()
        };
        B_LOG = new ControllerB
        {
            // This simulates reading logs from files - done only once
            LogLineList = DataService.GetBData()
        };

        // This simulates switching to already loaded logs.
        // When I do this the log lines don't change, but I want to refresh the datagrid and display correct info.
        LoadAppropriateLog();
    }

    private void LoadAppropriateLog()
    {
        if (isAType)
        {
            COMMON_LOG = A_LOG;
            isAType = false;
        }
        else
        {
            COMMON_LOG = B_LOG;
            isAType = true;
        }
    }

我的 View 绑定到 COMMON_LOG 实例,如下所示:

<DataGrid Grid.Row="0" Margin="5"
          Name="dgLogs"
          AutoGenerateColumns="False" SelectionUnit="CellOrRowHeader"
          ItemsSource="{Binding COMMON_LOG.LogLineList}">

然后点击一个按钮,我调用上面的 LoadAppropriateLog() 方法,所以它会简单地将适当类型的实例分配给 COMMON_LOG 这是我用来数据的实例绑定。

问题是,当我这样做时,由于每个实例 LogLineList 中的实际数据没有改变,DataGrid 不会自动更新以反映我选择的日志。

有没有办法在我每次切换日志类型后从我的 ViewModel 手动刷新 DataGrid

如果您想 运行 项目并查看,这里有一个下载 link:

Download the VS Project

如果您要绑定到 XAML 中 class 的 属性,或者

  1. 属性 在绑定第一次看到它后永远不应该改变它的值,并且通常应该是只读的,只是为了避免事故——或者
  2. class 应该实现 INotifyPropertyChanged 而 属性 应该在其 setter 中提出 PropertyChanged

在您的例子中,您正在更改 COMMON_LOG 的值,而您永远不会更改其 LogLineList 的值。

tl;dr:因此您的主视图模型需要实现 INotifyPropertyChanged,并在 setter 中为 COMMON_LOG 提高 PropertyChanged 任何不做这些事情的都不是视图模型。

LogLineList 作为 ObservableCollection 不会完成任何事情:class 所做的是在添加、删除或替换项目时发出通知。在绑定看到它之后的任何时候都不会发生这种情况。 ObservableCollection 的那些实例甚至不知道主视图模型的存在,因此当其属性更改时,它们肯定不会引发通知事件。他们也不应该:每个人都对他自己的通知负责。

事实上,如果您做出了这些集合在初始化后永不更改的设计决定,请使用 ReadOnlyCollection 而不是 ObservableCollection。创建一个很容易:调用 List<T>.AsReadOnly<T>()。对于任何 IEnumerable<T>,只需调用 e.ToList().AsReadOnly()ObservableCollection 信号 "you can add stuff to this"。但没有人应该。所以不要给他们想法。