I have been reading all the related articles here in the board but I still can't solve my problem that I have when binding an ObservableCollection to a ListView.
I have a CLogEntry model class which basically wraps a string.
/// 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));
}
}
}
In my ViewModel I have an ObservableCollection that holds my CLogEntry objects as well as the corresponding public property for it.
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));
}
}
}
My XAML code for the ListView is the following:
<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>
My Problem is that the list does not show any data. But when I debug the code I can see that my property for the ObservableCollection gets called and that my collection holds all the LogEntries that I add. So I assume that the CollectionChanged event gets fired and the UI is calling my LogEntries property. But I don't understand why the ListView does not show any data.
Is there a problem with my XAML code or is it a problem in model and/or ViewModel?
EDIT:
Finally the problem was a threading issue. Since the ObervableCollection is created by the UI thread it throws an exception if another thread is adding/manipulating the collection. To get rid of this problem i found the following solution that implements an Asynchronous ObservableCollection.
Following links helped me to get it working: Stackoverflow Implementing Async ObservableCollection
if the DataContext is your viewmodel (CLoggerViewModel) then the Itemssource binding should be:
<ListView ItemsSource="{Binding LogEntries}" Margin="5,5,5,5" Grid.Column="1" Grid.Row="0">
and the binding expression to your LogEntry should simply be {Binding LogEntry}
<GridViewColumn Header="Log Entry" DisplayMemberBinding="{Binding Path=LogEntry}"/>
EDIT:
EDIT: to your latest updates
DataContext ="{Binding LoggerViewModel}" --> What is that? this means you need a public property called LoggerViewModel on your current Datacontext. i dont think thats what you want. your Viewmodel code looks ok, but the problem is your XAML and setting your Datacontext. so pls post the code where you set the DataContext.
EDIT: working code
<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));
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With