【问题标题】:Changes in ObservableCollection do not update ListViewObservableCollection 中的更改不会更新 ListView
【发布时间】:2014-11-02 09:54:58
【问题描述】:

我已经阅读了看板中的所有相关文章,但仍然无法解决将 ObservableCollection 绑定到 ListView 时遇到的问题。

我有一个 CLogEntry 模型类,它基本上包装了一个字符串。

/// Model of LogEntry
public class CLogEntry:INotifyPropertyChanged
{
    /// Fields
    private string _logEntry;

    /// Property
    public string LogEntry
    {
        get { return _logEntry; }

        set
        {
            _logEntry = value;
            RaisePropertyChanged("LogEntry");
        }
    }

    /// PropertyChanged event handler
    public event PropertyChangedEventHandler PropertyChanged;

    /// Constructor
    public CLogEntry(string logEntry)
    {
        this.LogEntry = logEntry;
    }

    /// Property changed Notification        
    public void RaisePropertyChanged(string propertyName)
    {
        // take a copy to prevent thread issues
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

在我的 ViewModel 中,我有一个 ObservableCollection,它保存着我的 CLogEntry 对象以及它的相应公共属性。

class CLoggerViewModel : INotifyPropertyChanged
{
    /// Memory Appender object
    private CMemoryAppender _memoryAppender;
    /// ObservableCollection for LogEntries
    private ObservableCollection<CLogEntry> _logEntries;

    /// Property to expose ObservableCollection for UI
    public ObservableCollection<CLogEntry> LogEntries
    {
       get { return _logEntries; }
    }

    /// Event for PropertyChanged Notification
    public event PropertyChangedEventHandler PropertyChanged;

    /// Constructor of viewModel
    public CLoggerViewModel()
    {
        this._logEntries = new ObservableCollection<CLogEntry>();
        this._memoryAppender = new CMemoryAppender();
        this._memoryAppender.PropertyChanged += new PropertyChangedEventHandler(OnMemoryAppenderPropertyChanged);
        this._memoryAppender.LogContentChanged += new LoggingEventHandler(OnLogContentChanged);
    }

    /// Update collection
    public void OnLogContentChanged(object sender, LoggingEventArgs e)
    {
        ///  Here i add LogEntries event based to my collection.
        ///  For simplicity i just used a temporarly string here.
        string[] tmpString = { "A", "B", "C", "D" };

        foreach (string s in tmpString)
        {
            this.LogEntries.Add(new CLogEntry(s));
        }
    }

    /// Any of the properties of the MemoryAppender objects has changed
    private void OnMemoryAppenderPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        this.RaisePropertyChanged(e.PropertyName);
    }

    /// PropertyChanged EventHandler
    public void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

我的 ListView 的 XAML 代码如下:

<ListView x:Name="lstLogs" DataContext ="{Binding LoggerViewModel}"  ItemsSource="{Binding LogEntries}" Margin="5,5,5,5" Grid.Column="1" Grid.Row="0">
    <ListView.View>
        <GridView x:Name="grdLogs">
            <GridViewColumn Header="Log Entry"  DisplayMemberBinding="{Binding Path=LogEntries}"/>
        </GridView>
    </ListView.View>
</ListView>

我的问题是列表没有显示任何数据。 但是当我调试代码时,我可以看到我的 ObservableCollection 属性被调用,并且我的集合包含我添加的所有 LogEntries。 所以我假设 CollectionChanged 事件被触发并且 UI 正在调用我的 LogEntries 属性。 但我不明白为什么 ListView 不显示任何数据。

我的 XAML 代码有问题还是模型和/或 ViewModel 有问题?

编辑:

最后是线程问题。 由于 ObervableCollection 是由 UI 线程创建的,因此如果另一个线程正在添加/操作集合,它会引发异常。 为了摆脱这个问题,我找到了以下实现异步 ObservableCollection 的解决方案。

以下链接帮助我使其正常工作: Stackoverflow Implementing Async ObservableCollection

【问题讨论】:

  • 如何设置DataContext?当你实例化LogEntries 属性时?
  • 请使用 Snoop 在运行时检查您的 DataContext 和绑定表达式
  • @blindemeis:恶搞告诉我:DataContext - [Logger.CLoggerViewModel] {Path=LoggerViewModel}。但是对于 BindingExpression 我不确定。我刚刚在 Spoof 中找到 ListView 的 Binding.XmlNamespaceManager 和 BindingGroup 的属性。但是这两个属性在运行时都没有任何值。

标签: wpf listview data-binding observablecollection


【解决方案1】:

如果 DataContext 是您的视图模型 (CLoggerViewModel),那么 Itemssource 绑定应该是:

  <ListView ItemsSource="{Binding LogEntries}" Margin="5,5,5,5" Grid.Column="1" Grid.Row="0">

您的 LogEntry 的绑定表达式应该只是 {Binding LogEntry}

  <GridViewColumn Header="Log Entry"  DisplayMemberBinding="{Binding Path=LogEntry}"/>

编辑:

  • 在 XAML 中忘记 IntelliSense!
  • 您的 ListView ItemsSource 必须绑定到您的 Viewmodel CLoggerViewModel 中的 Property LogEntries
  • GridViewColumn DisplayMemberBinding 必须绑定到您的类 CLogEntry 中的 Property LogEntry

编辑:到您的最新更新

DataContext ="{Binding LoggerViewModel}" --> 那是什么?这意味着您需要在当前 Datacontext 上拥有一个名为 LoggerViewModel 的公共属性。我不认为那是你想要的。您的 Viewmodel 代码看起来不错,但问题是您的 XAML 和设置您的 Datacontext。所以请发布您设置 DataContext 的代码。

编辑:工作代码

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<ListView ItemsSource="{Binding LogEntries}">
    <ListView.View>
        <GridView >
            <GridViewColumn Header="Log Entry"  DisplayMemberBinding="{Binding Path=LogEntry}"/>
        </GridView>
    </ListView.View>
</ListView>
</Window>

cs

public partial class MainWindow : Window
{
    private CLoggerViewModel _vm = new CLoggerViewModel();
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = _vm;
    }
}

public class CLogEntry : INotifyPropertyChanged
{
    /// Fields
    private string _logEntry;

    /// Property
    public string LogEntry
    {
        get { return _logEntry; }

        set
        {
            _logEntry = value;
            RaisePropertyChanged("LogEntry");
        }
    }

    /// PropertyChanged event handler
    public event PropertyChangedEventHandler PropertyChanged;

    /// Constructor
    public CLogEntry(string logEntry)
    {
        this.LogEntry = logEntry;
    }

    /// Property changed Notification        
    public void RaisePropertyChanged(string propertyName)
    {
        // take a copy to prevent thread issues
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

class CLoggerViewModel : INotifyPropertyChanged
{
    /// Memory Appender object
    //private CMemoryAppender _memoryAppender;
    /// ObservableCollection for LogEntries
    private ObservableCollection<CLogEntry> _logEntries;

    /// Property to expose ObservableCollection for UI
    public ObservableCollection<CLogEntry> LogEntries
    {
        get { return _logEntries; }
    }

    /// Event for PropertyChanged Notification
    public event PropertyChangedEventHandler PropertyChanged;

    /// Constructor of viewModel
    public CLoggerViewModel()
    {
        this._logEntries = new ObservableCollection<CLogEntry>();
        //dunno what CMemoryAppender is
        //this._memoryAppender = new CMemoryAppender();
        //this._memoryAppender.PropertyChanged += new PropertyChangedEventHandler(OnMemoryAppenderPropertyChanged);
        //this._memoryAppender.LogContentChanged += new LoggingEventHandler(OnLogContentChanged);

        //thats why i fill my collection here
        string[] tmpString = { "A", "B", "C", "D" };

        foreach (string s in tmpString)
        {
            this.LogEntries.Add(new CLogEntry(s));
        }
    }
    /// Any of the properties of the MemoryAppender objects has changed
    private void OnMemoryAppenderPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        this.RaisePropertyChanged(e.PropertyName);
    }

    /// PropertyChanged EventHandler
    public void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

【讨论】:

  • @blindemeis:我根据您的提示更改了 XAML,如我编辑的帖子中所示。但现在我的问题是,我将对象添加到集合中的 foreach 循环迭代在将第一个对象添加到列表后停止。我之前遇到过这个问题,并认为可以通过将字符串包装在一个类中来解决。我已经在这个线程中发布了这个,因为我没有意识到问题是由 WPF 部分引起的。 stackoverflow.com/questions/25722641/…
  • ItemsSource 绑定应该是 LogEntries 而不是 LoggerViewModel。
  • @blindemeis:这就是我所拥有的 -> ItemsSource="{Binding LogEntries}" 。我之前在我的帖子中更新了这个。就像我说的。因为我这样做了,所以我的 foreach 循环在添加第一个对象后停止向集合添加对象。
  • 你的 ctor 中有没有这个:_logEntries = new ObservableCollection(); ?我的测试项目适用于您的代码。顺便说一句,我在 DisplayMemberBinding 中有一点错误。查看我的更新
  • @blindemeis:是的,我在构造函数中实例化了它。我相应地更新了我的帖子。但如果我使用你的 XAML,它就不起作用。我发现如果我设置DisplayMemberBinding="{Binding Path=LogEntry}" IntelliSense 不会为我提供属性 LogEntry。我刚得到我的 VM 提供的 LogEntries。所以我只能绑定整个集合而不是 LogEntry 属性本身。会不会是我的 ViewModel 或 Model 有问题?
猜你喜欢
  • 1970-01-01
  • 2016-04-22
  • 2019-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多