15

I have a WPF ListBox, and I've added some 'FooBar' objects as items (by code). FooBars aren't WPF objects, just dumb class with an overwritten ToString() function.

Now, when I change a property which influences the ToString, I'd like to get the ListBox to update.

  1. How can I do this 'quick and dirty' (like repaint).
  2. Is dependency properties the way to go on this?
  3. Is it worth it/always advisable, to create a wpf wrapper class for my FooBars?

Thanks...

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
Benjol
  • 63,995
  • 54
  • 186
  • 268

5 Answers5

14

Your type should implement INotifyPropertyChanged so that a collection can detect the changes. As Sam says, pass string.Empty as the argument.

You also need to have the ListBox's data source be a collection that provides change notification. This is done via the INotifyCollectionChanged interface (or the not-so-WPF IBindingList interface).

Of course, you need the INotifyCollectionChanged interface to fire whenever one of the member INotifyPropertyChanged items fires its event. Thankfully there are a few types in the framework that provide this logic for you. Probably the most suitable one is ObservableCollection<T>. If you bind your ListBox to an ObservableCollection<FooBar> then the event chaining will happen automatically.

On a related note, you don't have to use a ToString method just to get WPF to render the object in the way that you want. You can use a DataTemplate like this:

<ListBox x:Name="listBox1">
    <ListBox.Resources>
        <DataTemplate DataType="{x:Type local:FooBar}">
            <TextBlock Text="{Binding Path=Property}"/>
        </DataTemplate>
    </ListBox.Resources>
</ListBox>

In this way you can control the presentation of the object where it belongs -- in the XAML.

EDIT 1 I noticed your comment that you're using the ListBox.Items collection as your collection. This won't do the binding required. You're better off doing something like:

var collection = new ObservableCollection<FooBar>();
collection.Add(fooBar1);

_listBox.ItemsSource = collection;

I haven't checked that code for compilation accuracy, but you get the gist.

EDIT 2 Using the DataTemplate I gave above (I edited it to fit your code) fixes the problem.

It seems strange that firing PropertyChanged doesn't cause the list item to update, but then using the ToString method isn't the way that WPF was intended to work.

Using this DataTemplate, the UI binds correctly to the exact property.

I asked a question on here a while back about doing string formatting in a WPF binding. You might find it helpful.

EDIT 3 I'm baffled as to why this is still not working for you. Here's the complete source code for the window I'm using.

Code behind:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace StackOverflow.ListBoxBindingExample
{
    public partial class Window1
    {
        private readonly FooBar _fooBar;

        public Window1()
        {
            InitializeComponent();

            _fooBar = new FooBar("Original value");

            listBox1.ItemsSource = new ObservableCollection<FooBar> { _fooBar };
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            _fooBar.Property = "Changed value";
        }
    }

    public sealed class FooBar : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string m_Property;

        public FooBar(string initval)
        {
            m_Property = initval;
        }

        public string Property
        {
            get { return m_Property; }
            set
            {
                m_Property = value;
                OnPropertyChanged("Property");
            }
        }

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

XAML:

<Window x:Class="StackOverflow.ListBoxBindingExample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:StackOverflow.ListBoxBindingExample"
    Title="Window1" Height="300" Width="300">
    <DockPanel LastChildFill="True">
        <Button Click="button1_Click" DockPanel.Dock="Top">Click Me!</Button>
        <ListBox x:Name="listBox1">
            <ListBox.Resources>
                <DataTemplate DataType="{x:Type local:FooBar}">
                    <TextBlock Text="{Binding Path=Property}"/>
                </DataTemplate>
            </ListBox.Resources>
        </ListBox>
    </DockPanel>
</Window>
Community
  • 1
  • 1
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • It worked, thank you. I'm just doing a proto GUI at the moment, so I don't need anything more snazzy, but I'm looking at the string formating just in case. – Benjol Feb 17 '09 at 06:44
  • In this particular case you not *need* to use `ObservableCollection`. Try to replace it by `List` in the code above and it will continue to work fine. In fact `ObservableCollection` even doesn’t know about changes of properties in its items. `ObservableCollection` doesn’t fire neither `CollectionChanged` nor `PropertyChanged` when you change Property of `FooBar` (can't put test code here because there is no enough place). But if you you are going to add/remove items from the list `ObservableCollection` is the most correct way to do it. – Ilya Serbis Nov 04 '12 at 22:25
  • 1
    Only solution in 4 questions that worked for me...particularly the DataTemplate. – DonBoitnott Jul 01 '21 at 20:30
5

The correct solution here is to use an ObservableCollection<> for your ListBox IetmsSource property. WPF will automatically detect any changes in this collection's contents and force the corresponding ListBox to update to reflect the changes.

You may want to read this MSDN article for more information. It was written to specifically explain how to handle this scenario

http://msdn.microsoft.com/en-us/magazine/dd252944.aspx?pr=blog

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
1

Try implementing the INotifyPropertyChanged interface on your FooBar objects. When they change, raise PropertyChanged events, passing string.Empty as the property name. That should do the trick.

Samuel Jack
  • 32,712
  • 16
  • 118
  • 155
  • Yes, but no. No-one seems to be signed up to my event :( – Benjol Feb 16 '09 at 13:04
  • It’s not enough. You should also "tell" to ListBox from which of your object’s property it should get a string to display. DataTemplate is not the only way to do this. The simplest way is to add `DisplayMemberPath="PropertyName"` attribute to the ListBox. – Ilya Serbis Nov 04 '12 at 22:06
1

Here is the C# code I have working for this:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace ListboxOfFoobar
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ObservableCollection<FooBar> all = (ObservableCollection<FooBar>)FindResource("foobars");
            all[0].P1 = all[0].P1 + "1";
        }
    }
    public class FooBar : INotifyPropertyChanged
    {
        public FooBar(string a1, string a2, string a3, string a4)
        {
            P1 = a1;
            P2 = a2;
            P3 = a3;
            P4 = a4;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        private String p1;
        public string P1
        {
            get { return p1; }
            set
            {
                if (value != this.p1)
                {
                    this.p1 = value;
                    NotifyPropertyChanged("P1");
                }
            }
        }
        private String p2;
        public string P2
        {
            get { return p2; }
            set
            {
                if (value != this.p2)
                {
                    this.p2 = value;
                    NotifyPropertyChanged("P2");
                }
            }
        }
        private String p3;
        public string P3
        {
            get { return p3; }
            set
            {
                if (value != this.p3)
                {
                    this.p3 = value;
                    NotifyPropertyChanged("P3");
                }
            }
        }
        private String p4;
        public string P4
        {
            get { return p4; }
            set
            {
                if (value != this.p4)
                {
                    this.p4 = value;
                    NotifyPropertyChanged("P4");
                }
            }
        }
        public string X
        {
            get { return "Foooooo"; }
        }
    }
    public class Foos : ObservableCollection<FooBar>
    {
        public Foos()
        {
            this.Add(new FooBar("a", "b", "c", "d"));
            this.Add(new FooBar("e", "f", "g", "h"));
            this.Add(new FooBar("i", "j", "k", "l"));
            this.Add(new FooBar("m", "n", "o", "p"));
        }
    }
}

Here is the XAML:

<Window x:Class="ListboxOfFoobar.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ListboxOfFoobar"
    xmlns:debug="clr-namespace:System.Diagnostics;assembly=System"

    Title="Window1" Height="300" Width="300"        
        >
    <Window.Resources>
        <local:Foos x:Key="foobars" />
        <DataTemplate x:Key="itemTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock MinWidth="80" Text="{Binding Path=P1}"/>
                <TextBlock MinWidth="80" Text="{Binding Path=P2}"/>
                <TextBlock MinWidth="80" Text="{Binding Path=P3}"/>
                <TextBlock MinWidth="80" Text="{Binding Path=P4}"/>
            </StackPanel>
        </DataTemplate>

    </Window.Resources>

    <DockPanel>
        <ListBox DockPanel.Dock="Top"
         ItemsSource="{StaticResource foobars}"
         ItemTemplate="{StaticResource itemTemplate}" Height="229" />
        <Button  Content="Modify FooBar" Click="Button_Click" DockPanel.Dock="Bottom" />
    </DockPanel>
</Window>

Pressing the Button causes the first property of the first FooBar to be updated and for it to show in the ListBox.

hughdbrown
  • 47,733
  • 20
  • 85
  • 108
0

If the collection object you use to store the items is an observablecollection<> then this is handled for you.

i.e if the collection is changed any controls databound to it will be updated and vice versa.

Blounty
  • 3,342
  • 22
  • 21