简体   繁体   中英

refresh bindings in itemscontrol wpf

i have a simple itemscontrol binding to a list of Entries object. The button updates the LastUpdated of each item in the List. How do I raise property changed event so that LastUpdated field is updated in the ItemsControl. I have simplified my example just to figure out the binding issue. My real sample uses PRISM and third party controls.

C# Code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;

namespace TestItemsControl
{
    public class Entry
    {
        public string Name { get; set; }

        public DateTime LastUpdated { get; set; }
    }
}

namespace TestItemsControl
{
    public class TestViewModel: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public List<Entry> Entries { get; set; }

        public ICommand UpdateCmd { get; set; }

        public TestViewModel()
        {
            this.Entries = new List<Entry>();
            this.Entries.Add(new Entry{ Name = "1", LastUpdated = DateTime.Now });
            this.Entries.Add(new Entry { Name = "2", LastUpdated = DateTime.Now });
            this.Entries.Add(new Entry { Name = "3", LastUpdated = DateTime.Now });
        }

        public void Refresh()
        {
            if (this.PropertyChanged!= null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs("Entries"));
            }
        }
    }
}

XAML:

<Application x:Class="TestItemsControl.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TestItemsControl"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <local:TestViewModel x:Key="viewModel"/>
    </Application.Resources>
</Application>


<Window x:Class="TestItemsControl.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:TestItemsControl"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" DataContext="{StaticResource viewModel}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <ItemsControl Grid.Row="0" ItemsSource="{Binding Entries}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock Text="{Binding LastUpdated}"/>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <Button Grid.Row="1" Content="Update" Click="Button_Click"/>
    </Grid>
</Window>

Then you have to rewrite your Entry class

public class Entry: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public string Name { get; set; }

    DateTime lastUD;
    public DateTime LastUpdated
    {
        get
        {
            return lastUD;
        }
        set
        {
            lastUD = value;
            if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("LastUpdated"));
        }
    }
}

Also, change List<Entry> to ObservableCollection<Entry> :

namespace TestItemsControl
{
    public class TestViewModel
    {
        public ObservableCollection<Entry> Entries=new ObservableCollection<Entry>();

        public ICommand UpdateCmd { get; set; }

        public TestViewModel()
        {
            this.Entries.Add(new Entry{ Name = "1", LastUpdated = DateTime.Now });
            this.Entries.Add(new Entry { Name = "2", LastUpdated = DateTime.Now });
            this.Entries.Add(new Entry { Name = "3", LastUpdated = DateTime.Now });
        }
    }
}

This way you don't have to call Refesh function.

You have to Check for Null if noone subscribes to the Event

public class Entry : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public string Name { get; set; }

    DateTime lastUD;
    public DateTime LastUpdated
    {
        get
        {
            return lastUD;
        }
        set
        {
            lastUD = value;
            if(PropertyChanged != NULL)
               PropertyChanged(this, new PropertyChangedEventArgs("LastUpdated"));
        }
    }
}

I think you have to options here:

  1. Create a method like this:

     protected void OnPropertyChanged(string) { var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } 

Then whenever you want to update a property you go:

OnPropertyChanged("TheNameOfTheProperty");

or if you want to update every properties in the class:

OnPropertyChanged(string.Empty);

If you're unsure which property to update when, I recommend you go with the latter one.

  1. You could, but I recommend you not to do it this way: Instantiate a new list, add the updated values, clear the original list, set the original list to the new list and then update the property name of the list.

You are not binding the UpdateCmd and calling the Refresh anywhere, unless it is in the Click handler, but you should use MVVM approach to do this.

Change the Click event handler to a command binding from your viewmodel in the xaml like so

<Button Grid.Row="1" Content="Update" Command={Binding UpdateCmd}/>

Then in the viewmodel's constructor bind create a new RelayCommand or what ever the ICommand implementation class is like so where the contructor takes in the action delegate.

this.UpdateCmd  = new RelayCommand(this.Update);

Also you should maybe change the Refresh into Update which updates the timestamps for the entries.

public void Update()
{
    foreach (var entry in this.Entries)
    {
        entry.LastUpdated = DateTime.Now;
    }

    if (this.PropertyChanged!= null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs("Entries"));
    }
}

But you should really just implement the INotifyPropertyChanged on your Entry model as well and raise the event on the property setters, the bindings would be updated without notifying that the entire collection should be updated.

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