knick
knick

Reputation: 971

Updating the bindings of a DataTemplate within a DataTemplate

I have the following xaml;

    <DataTemplate DataType="{x:Type NameSpace:Node}">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Item.Value}"/>
            <ContentControl Content="{Binding Item}"/>
        </StackPanel>
    </DataTemplate>

    <DataTemplate DataType="{x:Type NameSpace:Item}">
        <TextBlock Text="{Binding Value}" />
    </DataTemplate>

When I use the Node template to display a Node object, I get two TextBlocks which both display the same value. So far so good. The problem occurs when Item changes. When the Node class fires the INotifyPropertyChanged event, the TextBlock in the Node DataTemplate updates as expected but the TextBlock in the Item DataTemplate does not update.

How can I get the Item DataTemplate to update its bindings when the Node class fires the IPropertyChanged event?

Update It turns out the above does work for the following simple scenario;

Xaml

        <DataTemplate DataType="{x:Type DataTemplateExample:Node}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Item.Value}"/>
                <ContentControl Content="{Binding Item}"/>
            </StackPanel>
        </DataTemplate>

        <DataTemplate DataType="{x:Type DataTemplateExample:Item}">
            <TextBlock Text="{Binding Value}" />
        </DataTemplate>
    </Window.Resources>

    <Grid>
        <StackPanel Orientation="Vertical">
            <ContentControl Content="{Binding MyNode}"/>
            <Button Command="{Binding ChangeMyNodeItem}">Change Item</Button>
        </StackPanel>
    </Grid>
</Window>

c#

public class MainViewModel
{
    private readonly Node myNode;
    private readonly DelegateCommand changeMyNodeItemCmd;

    public MainViewModel()
    {
        myNode = new Node {Item = new Item("a")};
        changeMyNodeItemCmd = new DelegateCommand(()=>
            myNode.Item = new Item("b"));
    }

    public Node MyNode { get { return myNode; } }

    public ICommand ChangeMyNodeItem
    {
        get { return changeMyNodeItemCmd; }
    }
}

public class Node : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private Item item;
    public Item Item
    {
        set
        {
            item = value;
            if (PropertyChanged != null)
                PropertyChanged(this,new PropertyChangedEventArgs("Item"));
        }
        get { return item; }
    }
}

public class Item
{
    private readonly string value;

    public Item(string value)
    {
        this.value = value;
    }

    public string Value
    {
        get { return value; }
    }
}

In my real scenario I was using proxies, and I think this is what was getting WPF confused. Item was not actually changing - it was being remapped.

Ultimately I solved the problem using a solution similar to what ShadeOfGray proposed. But I should point out that this is not necessary unless you are using proxies.

Upvotes: 2

Views: 3210

Answers (1)

ShadeOfGrey
ShadeOfGrey

Reputation: 1266

From what you have posted I think you're firing the NotifyPropertyChanged in the wrong class. Something like that should work properly in your scenario.

Updated according to the comment:

public class Node : INotifyPropertyChanged
{
    private Item item;

    public event PropertyChangedEventHandler PropertyChanged;

    public Item Item
    {
        get
        {
            return item;
        }

        set
        {
            item = value;

            this.NotifyPropertyChanged("Item");

            if (item != null)
            {
                item.ForcePropertyChanged("Value");
            }
        }
    }

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

public class Item : INotifyPropertyChanged
{
    private string itemValue;

    public Item()
    {
        this.Value = string.Empty;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public string Value
    {
        get
        {
            return itemValue;
        }

        set
        {
            itemValue = value;

            NotifyPropertyChanged("Value");
        }
    }

    public void ForcePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

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

Upvotes: 3

Related Questions