简体   繁体   中英

ItemsControl ItemSource Binding not updating

I used to just create a block of text by converting a list of strings to one string with newlines. This Binding worked; updated when it was supposed to and all, but I'm trying to move the list of text into an ItemsControl as they will need to be hyperlinks at some point in the future. Problem: The ItemsControl does not change when the PropertyChangeEvent is fired. The Relevant Code is as follows:

Xaml

<local:BaseUserControl x:Class="BAC.Windows.UI.Views.ErrorsView"
             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:local="clr-namespace:BAC.Windows.UI.Views"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">

             ...

            <ItemsControl ItemsSource="{Binding Path=ErrorMessages}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}"></TextBlock>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>

            <!--<TextBlock VerticalAlignment="Center" Visibility="{Binding ErrorMessages, Converter={StaticResource VisibleWhenNotEmptyConverter}}" Text="{Binding ErrorMessages, Converter={StaticResource ErrorMessagesToTextConverter}}">

            (What I used to use)

            </TextBlock>-->


 ...

</local:BaseUserControl>

ViewModel

using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using ASI.Core.Core;
using ASI.Core.DTO;
using ASI.Core.Extensions;
using ASI.Core.Mappers;
using BAC.Core.Resources;
using BAC.Core.Services;
using BAC.Core.ViewModels.Views; 

namespace BAC.Core.ViewModels
{
    public interface IErrorsViewModel : IViewModel<IErrorsView>
    {
    }

    public class ErrorsViewModel : BaseViewModel<IErrorsView>, IErrorsViewModel
    {
        ...

        private readonly ErrorDTO _errorDTO;
        private readonly ErrorDTO _warningDTO;

        public ErrorsViewModel(...) : base(view)
        {
            ...

            //Just added this string to know that it's at least binding. This Message displays, and never changes.
            ErrorMessages = new List<string>() {"Simple Message"};

            //Tells the View to bind dataContext to Viewmodel
            Edit();
        }

        private void errorDTOOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            ErrorDTO dto;
            if (!string.Equals(propertyChangedEventArgs.PropertyName, nameof(dto.HasError))) return;

            ErrorMessages.Clear();
            _errorDTO.ErrorMessages.Each(x => ErrorMessages.Add(Constants.Captions.Errors + ": " + x));
            _warningDTO.ErrorMessages.Each(x => ErrorMessages.Add(Constants.Captions.Warnings + ": " + x));

            OnPropertyChanged(() => ErrorMessages);
            OnPropertyChanged(() => HasError);
            OnPropertyChanged(() => HasWarning);
        }

        ...

        public bool HasError => _errorDTO.HasError;

        public bool HasWarning => _warningDTO.HasError;

        public IList<string> ErrorMessages { get; set; }

        ...
}

And just because I know people may ask to see it...

   public class BaseNotifyPropertyChanged : INotifyPropertyChanged
   {
      public event PropertyChangedEventHandler PropertyChanged;
      [NotifyPropertyChangedInvocator]
      protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
      {
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }

      public void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
      {
         var body = propertyExpression.Body as MemberExpression;
         if (body != null)
            OnPropertyChanged(body.Member.Name);
      }

       protected void OnEvent(Action action)
       {
           try
           {
               action();
           }
           catch
           { }
       }
   }

I'm sure it's something stupidy simple I'm doing, but the harder I look, the more I get frusterated by what should something simple. Why does the binding work for all other conrols except ItemSource? What's so special about it?

So I was able to get your code to work by using an ObservableCollection instead of the List. The ObservableCollection generates a list changed notification automatically when its collection is changed. Below is my sample code. I use a timer to update the error list every second.

<Window x:Class="TestEer.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TestEer"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
    <ItemsControl ItemsSource="{Binding Path=ErrorMessages}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

using System.Collections.ObjectModel;
using System.Timers;
using System.Windows;
using System.Windows.Data;

namespace TestEer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private Timer _timer;
    private readonly object _sync = new object( );
    public MainWindow( )
    {
        InitializeComponent( );
        BindingOperations.EnableCollectionSynchronization( ErrorMessages, _sync );
        _timer = new Timer
        {
            AutoReset = true,
            Interval = 1000
        };

        _timer.Elapsed += _timer_Elapsed;
        _timer.Enabled = true;
        _timer.Start( );
    }

    private void _timer_Elapsed( object sender, ElapsedEventArgs e )
    {
        ErrorMessages.Add( $"Error @ {e.SignalTime}" );
    }

    public ObservableCollection<string> ErrorMessages { get; } = new ObservableCollection<string>( );
}
}

I'll also add anotehr explanation (Even though I know this is old).

The reason this will not update the property is that the List object is not actually changing, so the ListView will not update the list. The only way to do this without using "ObservableCollection" is to create a brand new list on each property change like so:

    private void errorDTOOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
    {
        if (!string.Equals(propertyChangedEventArgs.PropertyName, nameof(dto.HasError))) return;
            OnPropertyChanged(() => ErrorMessages);
    }

    public List<string> ErrorMessages => getErrorMessages();

    private List<string> getErrorMessages() {
        //create list in a manner of your choosing
    }

Hopefully that helps people when they run into this.

We set up the OnPropertyChanged() method in the get set methods before the constructor and this seemed to work!

private bool _theString;
public bool TheString
{
    get { return _theString; }
    set { _theString = value; OnPropertyChanged(); }
}

Use {Binding TheString} in your .xaml.

Hope this helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM