Home > Mobile >  WPF C# Button Template and ContentTemplate not updating after NotifyPropertyChanged
WPF C# Button Template and ContentTemplate not updating after NotifyPropertyChanged

Time:01-15

I have this old software of mine which I made without following any particular pattern. At the time I found myself stuck with the following problem:

I'm creating some buttons and put them in a Grid. Those button are dynamically created and their creation logic is the following:

Button btn = new Button();
btn.Template = this.FindResource("template") as ControlTemplate;
btn.ContentTemplate = this.FindResource("contentTemplate") as DataTemplate;

var binding = new Binding
{
        Source = sourceItem;
}
btn.SetBinding(Button.ContentProperty, binding);

grid.Children.Add(btn);

The sourceItem's class implements INotifyPropertyChanged has two properties:

public class SourceItemClass: INotifyPropertyChanged
{
  private bool _online;
  public virtual bool Online
  {
    get => _online;
    protected set
    {
      if (_online != value)
      {
        _online = value;
        NotifyPropertyChanged();
      }
    }
  }

  private bool _error;
  public virtual bool Error
  {
    get => _error;
    protected set
    {
      if (_error!= value)
      {
        _error = value;
        NotifyPropertyChanged();
      }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
  protected void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Which I bound to the button content template like this:

<DataTemplate x:Key="contentTemplate" DataType="{x:Type classes:SourceItemClass}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="0" Text="Some text"/>

        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>

            <TextBlock Grid.Row="1" Text="Some text" />

            <Grid.Style>
                <Style>
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Path= Online}" Value="true">
                            <Setter Property="Grid.Background" Value="{StaticResource bckgndImg}"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Grid.Style>
        </Grid>

        <Grid.Style>
            <Style>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=Online}" Value="True">
                        <Setter Property="TextBlock.Foreground" Value="Black"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=Online}" Value="False">
                        <Setter Property="TextBlock.Foreground" Value="red"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Grid.Style>
    </Grid>
</DataTemplate>

And to the control template like this:

<ControlTemplate x:Key="template" TargetType="{x:Type Button}">
    <Border x:Name="buttonBorder">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <Storyboard>
                        <ThicknessAnimation Storyboard.TargetName="buttonBorder" Storyboard.TargetProperty="BorderThickness"
                                            To="{TemplateBinding BorderThickness}"
                                            Duration="0:0:0.1"/>
                        <ColorAnimation Storyboard.TargetName="buttonBorder"
                                               Storyboard.TargetProperty="BorderBrush.Color"
                                               To="{TemplateBinding BorderBrush}"
                                               Duration="0:0:0.1"/>
                        <ThicknessAnimation Storyboard.TargetName="buttonBorder"
                                               Storyboard.TargetProperty="Padding"
                                               To="{TemplateBinding Padding}"
                                               Duration="0:0:0.1"/>
                        <ThicknessAnimation Storyboard.TargetName="buttonContentPresenter"
                                               Storyboard.TargetProperty="Margin"
                                               To="{TemplateBinding Margin}"
                                               Duration="0:0:0.1"/>
                    </Storyboard>
                </VisualState>
                <VisualState Name="MouseOver">
                    <Storyboard>
                        <ThicknessAnimation Storyboard.TargetName="buttonBorder" Storyboard.TargetProperty="BorderThickness"
                                                    To="3"
                                                    Duration="0:0:0.1"/>
                        <ColorAnimation Storyboard.TargetName="buttonBorder" Storyboard.TargetProperty="BorderBrush.Color"
                                                To="Orange"
                                                Duration="0:0:0.1"/>
                        <ThicknessAnimation Storyboard.TargetName="buttonBorder" Storyboard.TargetProperty="Padding"
                                                    To="5"
                                                    Duration="0:0:0.1"/>
                        <ThicknessAnimation Storyboard.TargetName="buttonContentPresenter" Storyboard.TargetProperty="Margin"
                                                    To="-8"
                                                    Duration="0:0:0.1"/>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <ContentPresenter x:Name="buttonContentPresenter" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
    </Border>

    <ControlTemplate.Triggers>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding Content.Online, RelativeSource={RelativeSource Self}}" Value="false"/>
            </MultiDataTrigger.Conditions>
            <Setter TargetName="buttonBorder" Property="Background" Value="{StaticResource img1}"/>
        </MultiDataTrigger>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding Content.Online, RelativeSource={RelativeSource Self}}" Value="True"/>
                <Condition Binding="{Binding Content.Error, RelativeSource={RelativeSource Self}}" Value="True"/>
            </MultiDataTrigger.Conditions>
            <Setter TargetName="buttonBorder" Property="Background" Value="{StaticResource img2}"/>
        </MultiDataTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

Problem is: the UI doesn't update when either Online and Error change their values. I'm trying to figure thins out since too long at this point. Right now I'm periodically recreating buttons, which is not good. What am I missing?

CodePudding user response:

Wouldn't it be better to use an ItemsCollection in XAML rather than directly in code?

Something like:

<ItemsControl ItemsSource="{Binding ButtonDefinitions}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="{Binding Name}">
                <Button.Template>
                    <!-- template goes here... -->
                </Button.Template>
                <Button.ContentTemplate>
                    <!-- content template goes here... -->
                </Button.ContentTemplate>
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Or if the templates are stored in a ResourceDictionary:

<ItemsControl ItemsSource="{Binding ButtonDefinitions}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="{Binding Name}" 
                    Template="{StaticResource template}"
                    ContentTemplate="{StaticResource contentTemplate}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

This keeps your UI separate to your logic.

CodePudding user response:

I assume that the problem is the binding in the ControlTemplate. But the provided code should be simplified first. I do not see a problem with the Button instantiation in the code behind. I attach a sample code that creates the button in the XAML. I suggest you start with my sample code that shows how to bind in a control that has both a ContentTemplate and a ControlTemplate. Hope it helps..

<Window.Resources>
        <DataTemplate x:Key="contentTemplate" DataType="{x:Type local:SourceItemClass}">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition  Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <TextBlock  Grid.Row="0" Text="Message From Content Template"/>
                <TextBlock Grid.Row="1" Text="{Binding Online}"/>
            </Grid>
        </DataTemplate>
        <ControlTemplate x:Key="template" TargetType="Button">
            <Border BorderBrush="Aqua"  BorderThickness="6">
                <StackPanel>
                     <ContentPresenter x:Name="buttonContentPresenter" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
                    <TextBlock Text="Message From Template"/>
                    <TextBlock Text="{Binding Content.Online,RelativeSource={RelativeSource TemplatedParent}}"/>
                </StackPanel>
            </Border>
        </ControlTemplate>
    </Window.Resources>
    <Grid Name="grid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Button Grid.Column="0" Click="Button_Click">Change Online</Button>
        <Button Grid.Column="1" Content="{Binding }" 
                ContentTemplate="{StaticResource ResourceKey=contentTemplate}" 
                Template="{StaticResource template}"
                
                /> 
    </Grid> 
  • Related