2
votes

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.

2
Does the output window show a binding fail on that IsOpened property? I suspect it will. You'll likely need to do something to help it find it, like a RelativeSource perhaps. If not, you might need UpdateSourceTrigger=PropertyChanged on it. - DonBoitnott
Hi, are you sure that your ListViewItem has the same DataContext that it's needed? You might check that through visual tree debugger by looking at properties. - pjrki
DonBoitnett: Adding the UpdateSourceTrigger to the binding alone did not help, do I have to add a Notify somewhere to trigger it? Also I'm not sure yet how RelativeSource works, it does not allow me to search by AncestorType Rectangle, IDE says it's an invalid WPF type. pjrki: When looking at the properties of the Rectangle I see that the DataContext is correctly inherited from its parent control. It remains black when property value of DataContext is false. - Johan van Tiel
Do you implement INotifyPropertyChanged? - mm8

2 Answers

0
votes

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));
    }
}
0
votes

If you're sure that this listbox has the same DataContext that IsOpened property exists I'd try to resolve it by setting up RelativeSource to the listbox itself:

<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, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}" Value="True">
                    <Setter Property="Fill" Value="Black"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Rectangle.Style>
</Rectangle>