Search code examples
c#wpf

how to get selected info in wrappanel in WPF?


First, let's look at a piece of code like this:

<Window x:Class="WpfTest.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfTest"
        mc:Ignorable="d"
        Title="Window1" Height="500" Width="800">
    <Window.Resources>
        <Style x:Key="b" TargetType="Border">
            <Style.Setters>
                <Setter Property="Width" Value="200" />
                <Setter Property="Height" Value="200" />
                <Setter Property="BorderBrush" Value="Black" />
                <Setter Property="BorderThickness" Value="2" />
                <Setter Property="Margin" Value="4" />
            </Style.Setters>
        </Style>
    </Window.Resources>
    <WrapPanel>
        <Border Style="{StaticResource b}" />
        <Border Style="{StaticResource b}" />
        <Border Style="{StaticResource b}" />
        <Border Style="{StaticResource b}" />
    </WrapPanel>
</Window>

Then, the effect I want to achieve is that When I click a Border,the Border's bordercolor changes to red,if there was an other Border I clicked before,this Border's bordercolor would be black(equivalent to changing back to the original color).

The feasible method I have come up with so far is that bind a mouseclick RoutedEvent to each border, and let the wrappanel listen to this event. When the event is triggered, let the wrappanel traverse its children. If there is a child's name equals the event source's name, change this border color and don't forget setting all other children to black.

But I think this approach is a bit troublesome. Is there a more elegant and simple way to handle it?(Do not use mvvm or other frameworks)

In addition, I tried an idea, that is, when the border is clicked, assign the name of the border to the datacontext of the wrappanel, and then set the datatrigger for the borders. The condition is that the datacontext of the wrappanel is the same as the border's name. However, after testing, this is not possible.The value of "value" cannot be {binding ...}.


Solution

  • You may use a ListBox with a WrapPanel as ItemsPanel and the most simple ItemContainerStyle without all the standard item state visualization.

    Add a DataTrigger to the Border Style that binds to the IsSelected state of the ListBoxItem container of each Border. You also have to add a transparent Background to make the Border hit-test visible.

    <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled">
        <ListBox.Resources>
            <Style x:Key="b" TargetType="Border">
                <Setter Property="Width" Value="200"/>
                <Setter Property="Height" Value="200"/>
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="BorderBrush" Value="Black"/>
                <Setter Property="BorderThickness" Value="2"/>
                <Setter Property="Margin" Value="4"/>
                <Style.Triggers>
                    <DataTrigger
                        Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
                        Value="True">
                        <Setter Property="BorderBrush" Value="Red"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ListBox.Resources>
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListBoxItem">
                            <ContentPresenter/>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ListBox.ItemContainerStyle>
        <Border Style="{StaticResource b}"/>
        <Border Style="{StaticResource b}"/>
        <Border Style="{StaticResource b}"/>
        <Border Style="{StaticResource b}"/>
    </ListBox>
    

    You may alternatively move the Border into the ItemContainerStyle, but then you would have to add some kind of data items to the ListBox's Items or ItemsSource collection, e.g. a string with four characters as shown below.

    Instead of a DataTrigger in the Border Style, you could now use a somewhat simpler Trigger on the IsSelected property in the ControlTemplate.

    <ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled"
             ItemsSource="1234">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListBoxItem">
                            <Border x:Name="border" Width="200" Height="200"
                                    Background="Transparent" BorderBrush="Black"
                                    BorderThickness="2" Margin="4"/>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsSelected" Value="True">
                                    <Setter TargetName="border"
                                            Property="BorderBrush" Value="Red"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
    

    Most certainly you would now use a collection of real data items as the ItemsSource, and show item data in the Border via a ContentPresenter child element.