PropertyChanged event handler always null in Win Phone 8 Application
Asked Answered
A

2

0

When I click the 'Add Some Thing' button on this simple Win Phone 8 application (built with VS 2012 Pro - its what I have), nothing happens. Why?

The repo of this example code is on bitbucket.org at: TestItemsControlInWinPhone8App

The MainPage.xaml contains:

<phone:PhoneApplicationPage
x:Class="TestItemsControlInWinPhone8App.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">

<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <!--TitlePanel contains the name of the application and page title-->
    <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
        <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Button x:Name="AddSomeThing"
                    Content="Add Some Thing"
                    Grid.Row="0"
                    Click="AddSomeThing_Click"/>
            <ItemsControl x:Name="LotsOfThingsItemsControl"
                          Grid.Row="1"
                          ItemsSource="{Binding Mode=OneWay}"
                          FontSize="{StaticResource PhoneFontSizeSmall}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid Height="Auto" Width="Auto"
                          VerticalAlignment="Center"
                          HorizontalAlignment="Center"
                          Background="Orange">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            <TextBlock Grid.Row="0" Text="{Binding Path=Id, Mode=OneWay}"/>
                            <TextBlock Grid.Row="1"
                                       Text="------------------------"/>
                        </Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </StackPanel>

    <!--ContentPanel - place additional content here-->
    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">

    </Grid>
</Grid>

</phone:PhoneApplicationPage>

Please note that ItemsControl ItemsSource="{Binding Path=Things} has also been tried as just plain ItemsControl ItemsSource="{Binding}".

Both have the same result; the first five Thing objects display and clicking the "Add Some Thing" button adds another Thing to LotsOfThings.

The contents of the DataTemplate being a TextBlock do, in fact, display the first 5 Thing objects.

But clicking the button does NOT update the display, which persists in displaying only the original 5 Thing objects.

The code behind (MainPage.xaml.cs) reads:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using     TestItemsControlInWinPhone8App.Resources;

namespace TestItemsControlInWinPhone8App
{
public partial class MainPage : PhoneApplicationPage
{
    // Constructor
    public MainPage()
    {
        InitializeComponent();

        this.DataContext = new LotsOfThings(5);
    }

    private void AddSomeThing_Click(object sender, RoutedEventArgs e)
    {
        LotsOfThings lot = this.DataContext as LotsOfThings;

        lot.Add(new Thing());
    }
}
}

Note that in the Page constructor this.DataContext = new LotsOfThings(5); works and when the Page first displays, 5 Thing objects are displayed.

To be clear, what doesn't work is that a latter call of the AddSomeThing_click() button handler will add another Thing to LotsOfThings but only the original 5 Thing objects display; nothing more, even if there are many more Thing objects present on LotsOfThings according to the debugger.

What I notice using the debugger is that whenever OnPropertyChanged(...) is called handler is null. That is clearly important but I have no idea why that is happening having at this point followed all the remedial help I can find searching the web.

Why?

Contents of Thing.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestItemsControlInWinPhone8App
{
public class Thing : INotifyPropertyChanged
{
    private string _Id = Guid.NewGuid().ToString();
    public string Id
    {
        get
        {
            return _Id;
        }
        set { }
    }

    #region Constructor
    public Thing()
    {
        this.OnPropertyChanged( "Id");
    }
    #endregion

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string pPropertyName)
    {
        System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(pPropertyName));
        }
    }
    #endregion
}
}

Contents of LotsOfThings.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestItemsControlInWinPhone8App
{
class LotsOfThings : INotifyPropertyChanged, IList<Thing>
{
    private List<Thing> _things = new List<Thing>();
    public List<Thing> Things
    {
        get {
            return _things;
        }
        set { }
    }

    public LotsOfThings( int pNumberOfThings)
    {
        for( int x = 0; x < pNumberOfThings; x++){
            this.Add( new Thing());
        }
        OnPropertyChanged("Things");
    }

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string pName)
    {
        System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(pName));
        }
    }
    #endregion

    #region IList<T> methods
    public int IndexOf(Thing item)
    {
        return _things.IndexOf(item);
    }

    public void Insert(int index, Thing item)
    {
        _things.Insert(index, item);
    }

    public void RemoveAt(int index)
    {
        _things.RemoveAt(index);
    }

    public Thing this[int index]
    {
        get
        {
            return _things[index];
        }
        set
        {
            _things[index] = value;
        }
    }

    public void Add(Thing item)
    {
        _things.Add(item);

        OnPropertyChanged("Things");
    }

    public void Clear()
    {
        _things.Clear();
    }

    public bool Contains(Thing item)
    {
        return _things.Contains(item);
    }

    public void CopyTo(Thing[] array, int arrayIndex)
    {
        _things.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return _things.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(Thing item)
    {
        return _things.Remove(item);
    }

    public IEnumerator<Thing> GetEnumerator()
    {
        return _things.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _things.GetEnumerator();
    }
    #endregion
}
}

If you need to just download the application or look at it with a better interface you can find it here: TestItemsControlInWinPhone8App

Thank you.

PS. I have read and, I think, followed all the advice I can find on Stackoverflow and elsewhere on the net about null handlers passed into OnPropertyChanged() methods and usage of ItemsControl that I can find.

Acetanilide answered 19/5, 2016 at 17:16 Comment(0)
A
1

Ed Plunkett put me on the right path but the answer was a little more complicated so I'm laying out what I have learned in this answer.

First, I could have used ObservableCollection<T> - Ed is right. But I would have lost some of the IList<T> features I wanted. Besides I was trying to follow the practice in XAML Deep Dive ... (min 40-49) and they did not use ObservableCollection<T>.

Turns out I had mistakenly used INotifyPropertyChanged instead of INotifyCollectionChanged. The second interface has a slightly more complicated handler documented in the answer to this Stackoverflow question about calling OnCollectionChanged.

My research before asking this question found a bunch of ways to also get a null event handler. One was to call the handler with a misspelled property name (e.g. OnPropertyChanged("thing") when you should use OnPropertyChanged("Thing") because that is what the property is actually called - assuming you are dealing with properties and not collections. Another way to get a null event handler was to not bind the right object to the right content or container control. Here, take a look at "stack overflow C# why is my handler null".

And to finally put a stake in the heart of this problem I did a little research in the direction of Ed's hint that I become more familiar with the difference between List<T>, ObservableCollection<T> and INotifyPropertyChanged and found that excellent page.

I hope that helps. And thanks Ed; all my up-votes to you.

P.S. I have updated the repository of my test code to have the fix and git tag-ged the original version as broken and the fixed version as fixed.

Acetanilide answered 19/5, 2016 at 21:52 Comment(0)
D
3

_things needs to be ObservableCollection<Thing>. List<T> doesn't implement INotifyCollectionChanged, and hence doesn't raise notifications when its contents change. ObservableCollection<Thing> does, which will enable the UI to know when it needs to add items to the list.

The simple, easy, standard way of doing things is to expose an ObservableCollection as a property. If you replace the whole collection with a new one, raise PropertyChanged("Things"); when items are added/removed, the ObservableCollection will raise appropriate events without your needing to do anything. Experienced WPF folks reading your code will know what they're looking at.

To get it working the way you had in mind, you would have to call OnPropertyChanged("Things") in the methods that alter the Things collection; I haven't tested that, but I think it ought to work (the reason it might not is that the actual collection object returned by Things has not changed; the control might see that and choose not to update; but as I said I haven't tested that). Then you could bind Things to an ItemsSource on a control and maybe it ought to work. But then you could have other classes altering Things, because it's public. It would be a mess to try to chase down all the loose ends. Much easier to use ObservableCollection.

If you want to bind LotsOfThings itself to ItemsSource, you'll have to implement INotifyCollectionChanged on LotsOfThings, which gets to be a real hassle to rewrite all that by hand, and I'm not sure what it buys you. You could just make LotsOfThings a subclass of ObservableCollection<Thing> -- that starts you off with a complete and bulletproof INotifyCollectionChanged implementation, for free.

Disarray answered 19/5, 2016 at 17:24 Comment(7)
If I make LotsOfThings inherit INotifyPropertyChanged isn't that enough? That seems to be what Laurent and Jaime do in their MVA tutorial "XAML Deep Dive ..." (channel9.msdn.com/Series/xamldeepdive/05), see minutes 40-49. Thank you!Acetanilide
Clearly it's not enough; if it were enough, you wouldn't be here asking how to get it to work. Updating my answer.Disarray
Ed, thanks. I'm puzzled because the examples I have seen for this platform says nothing about IObservable<LotsOfThings> being necessary. Isn't data binding supposed to take care of this subscription process?Acetanilide
@Acetanilide That's not documentation, that's Channel 9. I'm at work so I can't watch the video, but if they left that out, they should take that video off the internet ASAP. I've been thinking about writing my own tutorial entitled How to Get Started with WPF Without Going Compeletly Insane and Climbing a Clock Tower with a Rifle, and I'm one step closer now.Disarray
Ed, put your effort to produce that video up on Patreon and I'll donate. I am already half-way "up the clock tower" as it is.Acetanilide
If you have a suggestion as to how to modify the code to use IObservable<LotsOfThings>.Subscribe( IObserver<LotsOfThings> observer) saving me more blind alleys searching the web I'd be most grateful. :-)Acetanilide
@Acetanilide I don't think XAML UI pays any attention to IObservable. The two big ones with WPF viewmodels are INotifyPropertyChanged for a vm to notify UI of changes to the values of its properties, and INotifyCollectionChanged for a collection property to notify UI of items being added to/removed from the collection.Disarray
A
1

Ed Plunkett put me on the right path but the answer was a little more complicated so I'm laying out what I have learned in this answer.

First, I could have used ObservableCollection<T> - Ed is right. But I would have lost some of the IList<T> features I wanted. Besides I was trying to follow the practice in XAML Deep Dive ... (min 40-49) and they did not use ObservableCollection<T>.

Turns out I had mistakenly used INotifyPropertyChanged instead of INotifyCollectionChanged. The second interface has a slightly more complicated handler documented in the answer to this Stackoverflow question about calling OnCollectionChanged.

My research before asking this question found a bunch of ways to also get a null event handler. One was to call the handler with a misspelled property name (e.g. OnPropertyChanged("thing") when you should use OnPropertyChanged("Thing") because that is what the property is actually called - assuming you are dealing with properties and not collections. Another way to get a null event handler was to not bind the right object to the right content or container control. Here, take a look at "stack overflow C# why is my handler null".

And to finally put a stake in the heart of this problem I did a little research in the direction of Ed's hint that I become more familiar with the difference between List<T>, ObservableCollection<T> and INotifyPropertyChanged and found that excellent page.

I hope that helps. And thanks Ed; all my up-votes to you.

P.S. I have updated the repository of my test code to have the fix and git tag-ged the original version as broken and the fixed version as fixed.

Acetanilide answered 19/5, 2016 at 21:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.