1
votes

I'm trying to create a listbox that shows a thumbnail view of page content, for an app with one canvas but multiple 'pages'. Yes, that's probably not the best place to start from but for historical reasons that's what I have. I've implemented the ListBox with data binding to a singleton 'WorkBook' that has an ObservableCollection of PageData (everything that appears on a page, including it's background). What I really really want is to be able to change the Border colour of a ListBoxItem when it's selected and keep that Border colour while it's content is the currently selected item in the class that hosts the collection.

My problems are:- 1/ I can't get the ListBox to select the 1st item on program startup.

2/ when the ListBox loses focus the SelectedIndex is always -1 (so no selection)

3/ adding to the ListBox results in no selection (SelectedIndex == -1)

4/ using triggers, I can set the border of the selectedItem but this is lost when the ListBox loses focus. As the ListBoxItem shows an image that is opaque the 'standard' way of making selection colours stay when out of focus doesn't work - ie

<Style x:Key="PsThumb">
                <Style.Resources>
                    <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="White"></SolidColorBrush>
                    <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="White"></SolidColorBrush>
                </Style.Resources>
</Style>

My code for the ListBox is as follows :-

<ListBox  x:Name="PageSorter" Style="{StaticResource PsThumb}"  Width="148" BorderThickness="4" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
        VerticalAlignment="Stretch" ItemsSource="{Binding Pages,Source={StaticResource WorkBook}}" SelectedItem="{Binding Path=CurrentPageData, Mode=TwoWay}" 
         AllowDrop="True" ScrollViewer.VerticalScrollBarVisibility="Auto">
                <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                         <Border x:Name="border" BorderBrush="DarkGray" BorderThickness="4" Margin="2,4,2,4" CornerRadius="5">
                             <Border.Effect>
                                 <DropShadowEffect ShadowDepth="6"/>
                             </Border.Effect>
                             <Image Source="{Binding Thumbnail}" Width="130" Height="95" Stretch="Fill"/>
                         </Border>
                    </Grid>
                        <DataTemplate.Triggers>
                            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},Path=IsSelected}" Value="True">
                                <Setter TargetName="border" Property="BorderBrush" Value="Red"></Setter>
                            </DataTrigger>
                        </DataTemplate.Triggers>
                    </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
1
I can't get the code to show up, not allowed to add it in a comment :(Hoverfrog

1 Answers

0
votes

The simplest way to achieve what you're asking is to bind the DataTrigger to a property inside the items of the ListBox. In the example below, I added the Selected property on my Contact class:

public class Contact : INPCBase
{
    public string Name { get; set; }

    private bool _Selected;
    public bool Selected 
    {
        get { return _Selected; }
        set
        {
            _Selected = value;
            NotifyPropertyChanged("Selected");
        }
    }
}

I then bind my ListBox to a List<Contact>. When the user checks the CheckBox, we change the Selected property and in turn trigger the Style:

<Window x:Class="WpfApp.ListboxKeepSelectionWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="ListboxKeepSelectionWindow" Height="277" Width="343"
    xmlns:me="clr-namespace:WpfApp">

<Window.Resources>
    <me:ContactList x:Key="sample"/>

    <Style TargetType="ListBoxItem" x:Key="SelectedListBoxItemStyle">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=Selected}" Value="True">
                <Setter Property="BorderBrush" Value="Orange"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

<Grid>
    <ListBox Name="lbxContacts"
             ItemsSource="{StaticResource ResourceKey=sample}"
             SelectionMode="Extended"
             ItemContainerStyle="{StaticResource ResourceKey=SelectedListBoxItemStyle}" 
             SelectionChanged="lbxContacts_SelectionChanged">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <CheckBox IsChecked="{Binding Path=Selected}">
                        <TextBlock Text="{Binding Path=Name}"/>
                    </CheckBox>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

To allow the user to select items without using the checkbox, I added this small event on the ListBox. Since the Contact class implements the INotifyPropertyChanged interface, the value of the CheckBox is updated so the user knows the selection worked:

    private void lbxContacts_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        foreach (Contact c in e.AddedItems)
        {
            c.Selected = !c.Selected;
        }
    }

When you want to get a list of selected items, you just use a LINQ query on the ItemsSource to get items where Selected == true.