WPF MVVM Why cant I update the bindings from a collection to a ListBox?

Answered WPF MVVM Why cant I update the bindings from a collection to a ListBox?

  • Thursday, May 03, 2012 2:02 PM
     
     

    My test project has a view with two sections - the first section contains a ListBox bound to a collection of strings called LogMessages.  When a new message comes in, I want the view to automatically update and display the new message.  I have a second section that contains a textbox, and this also displays messages (from Log4net).  This does update automatically.  Below is my XML and class.  My real application will keep section 1, and not section 2.  What am I doing wrong for section 1 not to automatically update?  If I put the view in a test window, whenever I open the test window all messages appear in both windows, so I know the binding is correct. 

    <UserControl x:Class="PreampDebugApplication.View.LogMessageView"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:res="clr-namespace:PreampDebugApplication.Logging"
                 xmlns:my="clr-namespace:PreampDebugApplication.ViewModel"
                 mc:Ignorable="d"
                 d:DesignHeight="219" d:DesignWidth="495">
        <UserControl.Resources>
            <!--<res:NotifyAppender x:Key="Log" />-->
            <res:NotifyAppender x:Key="Log" />
        </UserControl.Resources>
       
        <Border BorderBrush="LightGray"
                BorderThickness="1"
                CornerRadius="5"
                Margin="4"
                Padding="4"
                SnapsToDevicePixels="True" >
            <Grid Width="auto">
                <Grid.RowDefinitions>
                    <RowDefinition MaxHeight ="20" Height="*"></RowDefinition>
                    <RowDefinition Height="auto"></RowDefinition>
                    <RowDefinition Height="auto"></RowDefinition>
                </Grid.RowDefinitions>
               
                <TextBlock Grid.Row="0" MaxHeight="20" VerticalAlignment="Top" FontSize="12" FontWeight="Bold" Text="Log Messages" HorizontalAlignment="Center"></TextBlock>

                <Grid Grid.Row="1"  DataContext="{StaticResource Log}" Height="100" Width="auto">
                    <ScrollViewer>                  
                        <ListBox ItemsSource="{Binding Appender.LogMessages, Mode=Default, UpdateSourceTrigger=PropertyChanged}" >
                        </ListBox>
                    </ScrollViewer>
                </Grid>
                <Grid Grid.Row="2"  DataContext="{StaticResource Log}" Height="100" Width="auto" >
                    <ScrollViewer>

                        <TextBox
                            Text="{Binding Appender.Notification, Mode=Default}"
                            Margin="12"/>
                    </ScrollViewer>
                </Grid>

            </Grid>
        </Border>
    </UserControl>

    Notifier Class:

         public string Notification
            {
                get
                {
                    return _notification; ;
                }
                set
                {
                    if (_notification != value)
                    {
                        _notification = value;
                        OnPropertyChanged(new PropertyChangedEventArgs("Notification"));
                    }
                }
            }

            public SortedList<DateTime, string> LogMessages
            {
                get
                {
                    return mLogMessages;
                }
                set
                {
                    mLogMessages = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("LogMessages")); ;
                }
            }
            /// <summary>
            /// Raise the change notification.
            /// </summary>
            private void OnChange()
            {
                //PropertyChangedEventHandler handler = _propertyChanged;
                //if (handler != null)
                //{
                //    handler(this, new PropertyChangedEventArgs(string.Empty));
                //}
              
            }

            /// <summary>
            /// Get a reference to the log instance.
            /// </summary>
            public NotifyAppender Appender
            {
                get
                {
                    return Log.Appender;
                }

            }

            /// <summary>
            /// Append the log information to the notification.
            /// </summary>
            /// <param name="loggingEvent">The log event.</param>
            protected override void Append(LoggingEvent loggingEvent)
            {
                StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
                Layout.Format(writer, loggingEvent);
                Notification += writer.ToString();

                LogMessages.Add(DateTime.Now, writer.ToString());
                OnPropertyChanged(new PropertyChangedEventArgs("LogMessages"));
            }

            public virtual void OnPropertyChanged(PropertyChangedEventArgs e)
            {
                if (PropertyChanged != null) PropertyChanged(this, e);
            }

      
        }


    Al

All Replies

  • Thursday, May 03, 2012 2:39 PM
     
     
    Appender.LogMessages needs to be a DependencyProperty or needs to fire a NotifyPropertyChanged event.
  • Thursday, May 03, 2012 3:07 PM
     
     

    I have an OnPropertyChanged event being raised on the Setter of Appender.LogMessages, but I don't see it getting set when attempt to "add" a new message to the collection.

      public SortedList<DateTime, string> LogMessages
            {
                get
                {
                    return mLogMessages;
                }
                set
                {
                    mLogMessages = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("LogMessages")); ;
                }
            }

    What do you mean by a dependency property?


    Al

  • Friday, May 04, 2012 12:48 AM
     
     Answered Has Code

    I've only done this up quickly so it's probably not 100% accurate but it might give you some direction.

    Using the current code a property changed notification will only be raised when the collection object as a whole changes. In order to notify the view of changes to the items in the collection, such as when they are added or removed, you need to use a type of collection which raises the CollectionChanged event. ObservableCollection supports this and should keep the view up to date with the items in the collection.

    If you need to keep the log messages sorted by their date you could bind to a CollectionViewSource rather than directly to the collection. This will leave the underlying collection unchanged though, so you need to be aware that the collection won't be sorted when accessing it in code (I think).

    This shows how you would use a CollectionViewSource to sort the ObservableCollection, in XAML. Note the addition of the scm xml namespace. This is required as SortDescription for the collection view is defined in that namespace. I've only included the relevant sections of code.

    <UserControl x:Class="PreampDebugApplication.View.LogMessageView"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:res="clr-namespace:PreampDebugApplication.Logging"
                 xmlns:my="clr-namespace:PreampDebugApplication.ViewModel" 	 
    	     xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"	 
                 mc:Ignorable="d" 
                 d:DesignHeight="219" d:DesignWidth="495">
        <UserControl.Resources>
            <res:NotifyAppender x:Key="Log" />
    		
    	<!-- This should sort the log messages by Key (datetime) -->
    	<CollectionViewSource x:Key="LogMessages" Source="{Binding Appender.LogMessages}">
    		<CollectionViewSource.SortDescriptions>
    			<scm:SortDescription PropertyName="Key" />
    		</CollectionViewSource.SortDescriptions>
    	</CollectionViewSource>
    		
        </UserControl.Resources>
       
       ...
    	
    	<ScrollViewer>                   
    		<ListBox ItemsSource="{Binding Source={StaticResource LogMessages}}" >
    		</ListBox>
    	</ScrollViewer> 
    
    	...
    	
    </UserControl>

    And in the notifier class...

    Notifier Class:
    
    
            public ObervableCollection<KeyValuePair<datetime, string>> LogMessages
            {
                get
                {
                    return mLogMessages;
                }
                set
                {
                    mLogMessages = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("LogMessages")); ;
                }
            }
            
        }

    Hopefully that'll help a bit, but let me know if any of it is unclear or incorrect.

  • Tuesday, May 15, 2012 6:57 AM
    Moderator
     
     
    We are temporarily marking this as "Answer", if you have any concerns or new findings; please feel free to let me know.
    Best regards.

    Annabella Luo[MSFT]
    MSDN Community Support | Feedback to us