Search code examples
c#wpfxamlobservablecollectiondatatrigger

DataTrigger inside ItemTemplate-DataTemplate not triggering upon property change


I'm quite new to WPF and I'm currently trying to figure out how to add stylized DataTemplates to a control, a ListBox in this case.

I'm trying to make an item browser with ListBox and highlight the ListBoxItem with a custom color when it's clicked by using a colored rectangle in the background.

The ListBox is linked to a ObservableCollection with several properties including an IsOpened property.

<ListBox x:Name="MainPage_Entries" Height="643" Canvas.Left="10" Canvas.Top="10" Width="230">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Border BorderBrush="White" BorderThickness="1">
                <WrapPanel Margin="-2,0,0,0" HorizontalAlignment="Stretch">
                    <Grid HorizontalAlignment="Stretch">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="30"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Rectangle Margin="0" HorizontalAlignment="Stretch" Height="40" Grid.ColumnSpan="2">
                            <Rectangle.Style>
                                <Style TargetType="Rectangle">
                                    <Setter Property="Fill" Value="Gray"/>
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsOpened}" Value="True">
                                            <Setter Property="Fill" Value="Black"/>
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Rectangle.Style>
                        </Rectangle>

                        <Image Source="{Binding SourceImage}" Height="30"/>
                        <TextBlock Margin="3,1,3,0" Text="{Binding SetName}" Grid.Column="1" VerticalAlignment="Top" Width="188" FontFamily="Arial" FontSize="16" Foreground="White"/>
                        <TextBlock Margin="3,0,3,5" Text="{Binding OutputName}" Grid.Column="1" VerticalAlignment="Bottom" Width="188" Foreground="White" FontSize="10" FontStyle="Italic"/>
                    </Grid>
                </WrapPanel>
            </Border>


        </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <EventSetter Event="PreviewMouseLeftButtonDown" Handler="ListBoxItem_ClickDown"/>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

Upon clicking the ListBoxItem the IsOpened property of the selected item gets set to False and the clicked one to True. Even though the property change succeeds in the ObservableCollection, the trigger remains untouched and the background remains Black (initialized value of IsOpened is True).

EDIT:

The DataTrigger has gotten changed to this so far:

<DataTrigger Binding="{Binding Path=IsOpened, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}" Value="True">
    <Setter Property="Fill" Value="Black"/>
</DataTrigger>

And the current state of the class of the entry:

public class EntryItem : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public string SetName { get; set; }

    public string SourceImage { get; set; }

    public EntryItem(string name)
    {
        SetName = name;
        IsOpened = true;
    }

    private bool _isOpened;
    public bool IsOpened
    {
        get { return _isOpened; }
        set {
            _isOpened = value;
            NotifyPropertyChanged();
        }
    }
}

By adding these the background of all entries remain gray even when one "IsOpened".

EDIT 2:

Removing the RelativeSource bit made it work.
Conclusion: Implement INotifyPropertyChanged.


Solution

  • The class where the IsOpened property is defined should implement the INotifyPropertyChanged interface and and raise the PropertyChanged event when the IsOpened property is set:

    class DataObject : INotifyPropertyChanged
    {
        private bool _isOpened;
        public bool IsOpened
        {
            get { return _isOpened; }
            set { _isOpened = value; NotifyPropertyChanged(); }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }