7

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

Community
  • 1
  • 1
ck84vi
  • 1,556
  • 7
  • 27
  • 49
  • How you set the `DataContext`? And when you instantiate `LogEntries` property? – har07 Sep 09 '14 at 04:47
  • pls use Snoop to check your DataContext and Binding Expression at runtime – blindmeis Sep 09 '14 at 05:38
  • @blindemeis: Spoof is telling me: DataContext - [Logger.CLoggerViewModel] {Path=LoggerViewModel}. But for the BindingExpression im not sure. i just found Properties for Binding.XmlNamespaceManager and BindingGroup for the ListView in Spoof. But both Properties dont hold any value during runtime. – ck84vi Sep 09 '14 at 06:15

1 Answers1

2

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:

  • forget IntelliSense in XAML!
  • your ListView ItemsSource have to bind to the Property LogEntries in your Viewmodel CLoggerViewModel
  • the GridViewColumn DisplayMemberBinding have to bind to the Property LogEntry in your class CLogEntry

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));
        }
    }
}
blindmeis
  • 22,175
  • 7
  • 55
  • 74
  • @blindemeis: I changed the XAML according to your hint as shown in my edited post. But now my problem is that my foreach loop iteration where i add the objects to the collection gets stopped after adding the first object to the list. I had this problem before and thought it was solved by wrapping the string in a class. I posted this in this thread already because i didnt realize that the problem is caused by the WPF part. http://stackoverflow.com/questions/25722641/add-string-array-to-observablecollectionstring-does-not-work – ck84vi Sep 09 '14 at 05:50
  • the ItemsSource Binding should be LogEntries and not LoggerViewModel. – blindmeis Sep 09 '14 at 07:03
  • @blindemeis: Thats what i have -> ItemsSource="{Binding LogEntries}" . I have updated this before in my post. As i said. Since i did this my foreach loop stops adding objects to the collection after adding the first object. – ck84vi Sep 09 '14 at 07:38
  • did you have this in your ctor: _logEntries = new ObservableCollection(); ? my test project works with your code. btw i had a little error in DisplayMemberBinding. see my update – blindmeis Sep 09 '14 at 08:25
  • @blindemeis: Yes, i instantiate it in the constructor. I update my post accordingly. But if i use your XAML it doesnt work. What i found is that if i set `DisplayMemberBinding="{Binding Path=LogEntry}"` IntelliSense does not offer me the Property LogEntry. I just get LogEntries which is provided by my VM. So i just can bind the entire collection not the LogEntry property itself. Could it be that i have any problem in my ViewModel or Model? – ck84vi Sep 09 '14 at 10:46
  • @blindemeis: You added the strings to the list i CTOR. I this way it works perfectly. MemoryAppender is a object of a class that i wrote to get logmessages from log4net. Basically i want to show the log4net output on my UI in a ListView. Your approach has guided me in the right direction. No i know that the binding and XAML part works fine. Many thanks for your help! No i will try to find out why it breaks my foreach loop if i perform it (add objects to collection) in an eventhandler. – ck84vi Sep 10 '14 at 01:17
  • The problem is that i get an Exception if i add objects outside the CTOR to the collection -> An ItemsControl is inconsistent with its items source! So i think the main problem is a threading issue. For any hints to solve this im super thankful! – ck84vi Sep 10 '14 at 04:11
  • create a new question with the exception you get – blindmeis Sep 10 '14 at 05:12