Resize WPF ListBox selection box

2019-06-02 06:17发布

问题:

For a project I have implemented a small IntelliSense like control which is nothing than a ListBox. Its DataTemplate consist of a StackPanel holding one Image and one TextBlock. Nothing else. As you can see in the first screenshot of my control, the selection box of the ListBox selects the whole item (which usually is exactly what one would expect):

However my "stolen" icons from VS11 are low-quality so I wanted to adjust the selection like Visual Studio does:

You can see that only the text is selected (the visual representation does ignore the image/icon) and I want to know how I can implement this behavior, too.

EDIT: The icons are just GIF files with a transparent background. I will replace them with better ones, but nevertheless I am interestes in how to get the desired behavior.

回答1:

Here's a solution by replacing the ListBoxItem control template. In my test I just display two text strings, with the second one highlighted.

<Page.Resources>
    <Style x:Key="ItemStyle" TargetType="ListBoxItem">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <ContentPresenter/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="Selected" TargetType="{x:Type TextBlock}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Path=IsSelected, 
                         RelativeSource={RelativeSource Mode=FindAncestor, 
                         AncestorType={x:Type ListBoxItem}}}" Value="True">
                <Setter Property="Background" 
                        Value="{x:Static SystemColors.HighlightBrush}"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>

    <DataTemplate x:Key="ItemTemplate" DataType="{x:Type ViewModel:DataItem}">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto" SharedSizeGroup="c1"/>
                <ColumnDefinition Width="auto" SharedSizeGroup="c2"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Image Source="../Images/Collapse.png"/>
            <TextBlock Grid.Column="1" Text="{Binding Name}" Margin="0,0,5,0"/>
            <TextBlock Style="{StaticResource Selected}" 
                       Grid.Column="2" Text="{Binding Description}"/>
        </Grid>
    </DataTemplate>
</Page.Resources>

<Page.DataContext>
    <Samples:Page3ViewModel/>
</Page.DataContext>

<Grid>
    <ListBox 
        Grid.IsSharedSizeScope="True"
        SelectedIndex="2"
        HorizontalContentAlignment="Stretch"
        ItemsSource="{Binding Items}" 
        ItemContainerStyle="{StaticResource ItemStyle}"
        ItemTemplate="{StaticResource ItemTemplate}"/>
</Grid>

The view model contains a collection of simple data items

public class Page3ViewModel
{
    public Page3ViewModel()
    {
        Items = new List<DataItem>
            {
                new DataItem{Name = "One", Description = "First"},
                new DataItem{Name = "Two", Description = "Second"},
                new DataItem{Name = "Three", Description = "Third"},
                new DataItem{Name = "Four", Description = "Fourth"},
            };
    }
    public IEnumerable<DataItem> Items { get; private set; }
}

Giving



回答2:

Your problem is due to the way WPF renders each item in the ListBox. It uses an ItemContainerStyle which wraps each item in a ListBoxItem. It is this ListBoxItem that contains the content to be displayed (in your case a StackPanel that contains an Image and a TextBlock).

By default, the ListBoxItem displays the blue rectangle around all content that it displays.

I have come up with a solution but it is a hack. Simply make your image larger and have the background pixel colour match the background colour of the Listbox (in my case, white) and use the following XAML.

<ListBox Margin="5"
         ItemsSource="{Binding Items}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal"
                        Margin="-2,0,0,0">
                <Image Source="Save-icon.png" />
                <TextBlock Margin="5,8,0,0"
                           Text="{Binding}" />
            </StackPanel>
        </DataTemplate>
     </ListBox.ItemTemplate>
</ListBox>

This is the result:

Update

I have come up with a modification which makes this a little less of a hack. In my ItemTemplate I have surrounded the image with a Border which uses binding to get its background colour from it's parent ListBox. (The image no longer has a border around it).

Update the XAML to this:

<ListBox Margin="5"
         ItemsSource="{Binding Items}"
         Background="LightSteelBlue">

    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal"
                        Margin="-2,0,0,0">
                <Border Background="{Binding Path=Background, RelativeSource={RelativeSource AncestorType=ListBox}}"
                        Padding="10">
                    <Image Source="Save-icon.png"/>
                 </Border>
                 <TextBlock Margin="5,8,0,0"
                            Text="{Binding}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>

</ListBox>

This is the new result:

Now all you need to do is update the background colour of the ListBox and everything will adjust automatically. E.G.

<ListBox Margin="20"
         ItemsSource="{Binding Items}"
         Background="PeachPuff">