3
votes

I am writing an application with a listbox allowing multi-selection (SelectionMode=Multiple); the items in the lisbox are ingredients for a recipe.

Unfortunately, clicking on a listbox item selects this item, which may not be desired. I would like the following scenario:

  • the user clicks on the listbox in order to select the listbox (the listbox itself, not an item)
  • the user scrolls to the right item and selects it

What I did is style the ListBoxItem to include a checkbox and a ContentPresenter (like in this blog). Still, clicking on the ingredient name selects it. So, I trap the MouseDown event on the textblock containing the ingredient name, find the underlying ListBoxItem, call Focus() on it and set the Handled property of the event to true.

Now, the Listbox item has the focus but is not selected. Using the up and down keys shows that the focus was on the right item. My problem is that the user cannot see that he has clicked on the right item. The dotted rectangle is not shown on this item. Here is the result:

alt text

And here is what I'd like:

alt text

I've tried calling private WPF methods, like KeyboardNavigation.ShowFocusVisual, I've tried sending keystrokes to the listbox (when done by a human, pressing the right cursor key or the Alt key makes the dotted rectangle appear).

Any idea ?

2

2 Answers

4
votes

SendInput is the only way I've found which gets past this. From this link.

PInvoke to SendInput – this is the official way to simulate input. It pushes the input through all of the expected code paths, and is indistinguishable from real input.

An easy way to use this is with InputSimulator from CodePlex.

Adding a reference to InputSimulator.dll we can do something like this

private bool m_waitingForFocusVisualStyle = false;
private void ListBoxItem_GotFocus(object sender, RoutedEventArgs e)
{
    if (m_waitingForFocusVisualStyle == false)
    {
        m_waitingForFocusVisualStyle = true;
        InputSimulator.SimulateKeyDown(VirtualKeyCode.TAB);
        InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.SHIFT, VirtualKeyCode.TAB);
    }
    else
    {
        m_waitingForFocusVisualStyle = false;
    }
}

But this might not be ideal for a lot of reasons (Shift+Tab to the ListBoxItem for example)

A better idea is probably to remove the FocusVisualStyle for ListBoxItem and add your own in the ControlTemplate like this. (Copied from Blend and added a "FocusVisualStyle" from standard FocusVisualStyle)

<ListBox ...>
    <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
            <Setter Property="Template" Value="{StaticResource ListBoxItemTemplate}" />
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

<ControlTemplate x:Key="ListBoxItemTemplate" TargetType="{x:Type ListBoxItem}">
    <Grid>
        <Rectangle Grid.ZIndex="1"
                    Name="focusVisualStyle"
                    StrokeThickness="1"
                    Stroke="Black"
                    StrokeDashArray="1 2"
                    SnapsToDevicePixels="true"
                    Visibility="Hidden"/>
        <Border x:Name="Bd"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                Background="{TemplateBinding Background}"
                Padding="{TemplateBinding Padding}"
                SnapsToDevicePixels="true">
            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
        </Border>
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="IsFocused" Value="True">
            <Setter TargetName="focusVisualStyle" Property="Visibility" Value="Visible"/>
        </Trigger>
        <Trigger Property="IsSelected" Value="true">
            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
        </Trigger>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsSelected" Value="true"/>
                <Condition Property="Selector.IsSelectionActive" Value="false"/>
            </MultiTrigger.Conditions>
            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
        </MultiTrigger>
        <Trigger Property="IsEnabled" Value="false">
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>
0
votes

I have found Meleak's answer very helpful, but using GotFocus does not work for me. Instead I have bind the even handler to PreviewMouseLeftButtonDown even. Now you don't need boolean property to store the state and the code is very simple:

    void SlideCanvasPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
            InputSimulator.SimulateKeyDown(VirtualKeyCode.TAB);
            InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.SHIFT, VirtualKeyCode.TAB);  
    }

This does the job for me very good.

P.S. I use this style - it has some fine animation of moving dashed rectanle