StepUp
StepUp

Reputation: 38094

DataTrigger binded to property in ViewModel is not fired in the Button of DataGrid

I've created ControlTemplates:

<Window.Resources>
    <ControlTemplate x:Key="imgNo" TargetType="{x:Type Control}">
        <Image Source="pack://application:,,,/Images/up.png"/>
    </ControlTemplate>

    <ControlTemplate x:Key="imgUp" TargetType="{x:Type Control}">
        <!--<TextBlock  Text="Up"/>-->
        <Image Source="pack://application:,,,/Images/up.png"/>
    </ControlTemplate>

    <ControlTemplate x:Key="imgDown" TargetType="{x:Type Control}">
        <Image Source="pack://application:,,,/Images/downArrow.png"/>
    </ControlTemplate>

    <DataTemplate x:Key="ButtonOneDataTemplate">
        <Control x:Name="theControl" Template="{DynamicResource imgNo}" />
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding IsImageChanged}" Value="true">
                <Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgUp}" />
            </DataTrigger>
            <DataTrigger Binding="{Binding IsImageChanged}" Value="false">
                <Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgDown}" />
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>
</WindowResources>

and Button in DataGrid which uses above ControlTemplates:

<DataGrid ItemsSource="{Binding Persons}" Grid.Row="1" AutoGenerateColumns="False">
   <DataGrid.Columns>
     <DataGridTextColumn Binding="{Binding IdPerson}">
       <DataGridTextColumn.HeaderTemplate>                        
          <DataTemplate>
            <Border Background="Violet">
             <StackPanel>
                 <Button ContentTemplate="{StaticResource ButtonOneDataTemplate}" 
                 Command="{Binding DataContext.HelloCommand, RelativeSource=
                  {RelativeSource AncestorType=Window}}"
                  CommandParameter="{Binding DataContext.Hello,   
                RelativeSource={RelativeSource AncestorType=DataGrid}}"/> 
             </StackPanel>
            </Border>
         </DataTemplate>
       </DataGridTextColumn.HeaderTemplate>                   
     </DataGridTextColumn>
   </DataGrid.Columns>
 </DataGrid>                    

My ViewModel:

public class MainWindowViewModel:ViewModelBase
{
    public RelayCommand HelloCommand { get; set; }
    public MainWindowViewModel()
    {
       LoadPersons();
       HelloCommand = new RelayCommand(SayHello);
    }

    int helloCounter = 0;
    private void SayHello(object obj)
    {
       if (helloCounter % 2 == 0)
          IsImageChanged = true;
       else
          IsImageChanged = false;           
       helloCounter++;
    }


    private bool isImageChanged=true;
    public bool IsImageChanged
    {
       get { return isImageChanged; }
       set { isImageChanged = value;
             OnPropertyChanged("IsImageChanged");
           }
    }
}

What I want is when I click on the button <Button ContentTemplate="{StaticResource ButtonOneDataTemplate}"/>, then Template should be replaced to {DynamicResource imgDown} or {DynamicResource imgUp}. DataTrigger depends on IsImageChanged value.

However, if I click on the Button, then DataTrigger is not fired(Controltemplates such as imgUp, imgDown are not changed). How can I achieve this from my ViewModel?

Upvotes: 0

Views: 461

Answers (1)

Anton Danylov
Anton Danylov

Reputation: 1491

Problem is that DataGrid column is not a part of a visual tree, and because of that it does not inherit DataContext. To be able to use DataTriggers in your ButtonOneDataTemplate you need that button, you applying this template to, has correct DataContext. There is a trick, how to provide DataContext to elements that are not in VisualTree, described here

Applying that solution to your code we'll have the following:

Proxy

public class BindingProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

Window.Resources

    <local:BindingProxy x:Key="proxy" Data="{Binding}" />

    <DataTemplate x:Key="ButtonOneDataTemplate">
        <Control x:Name="theControl" Template="{DynamicResource imgNo}" Foreground="Orange"/>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.IsImageChanged}" Value="True">
                <Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgUp}" />
            </DataTrigger>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.IsImageChanged}" Value="False">
                <Setter TargetName="theControl" Property="Template" Value="{DynamicResource imgDown}" />
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>

HeaderTemplate

<DataGridTextColumn.HeaderTemplate>
    <DataTemplate>
        <Border Background="Violet">
            <StackPanel>
                <Button ContentTemplate="{StaticResource ButtonOneDataTemplate}" 
                        DataContext="{Binding Path=Data, Source={StaticResource proxy}}"
                        Command="{Binding DataContext.ButtonClick, RelativeSource={RelativeSource AncestorType=Window}}"
                        CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGrid}}"/>
            </StackPanel>
        </Border>
    </DataTemplate>
</DataGridTextColumn.HeaderTemplate>

Upvotes: 2

Related Questions