Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use ListBox within Focus Scope

Short story: A ListBox inside a focus scope doesn't allow an item to be selected.

Long story:

I am trying to make a context-sensitive widget for data entry. I have the main panel with several fields. Below that, I have a panel with FocusManager.FocusScope="True". This panel will be filled with the relevant widget for the field which currently has focus. For example, selecting a date field would show a calendar at the bottom of the screen.

I have several controls that the require the user to select one of many values from a list. I put a ListBox into the focus scope, but I can't select any items. When something is selected (programmatically) and you click on the ListBox, it deselects everything.

I tested a few events, and it isn't picking up MouseDown events, but it is picking up MouseMove events. It fires GotFocus whenever I click on an item, but it never fires LostFocus. I'm not sure what this means, but I hope it can be helpful to someone who is reading this.

Here is the code that I am using to display the context-sensitive widget. I have the following XAML in my Window:

<Grid x:Name="EntryWidget" FocusManager.IsFocusScope="True">
    <Grid.Resources>
        <ListBox x:Key="List" ItemsSource="{Binding}" />
    </Grid.Resources>
</Grid>

I use the Window.GotFocus routed event to update the widget to the appropriate control, like so:

private void Window_GotFocus(object sender, RoutedEventArgs e)
{
    FrameworkElement focus = (FrameworkElement)FocusManager.GetFocusedElement(this);
    EntryWidget.Children.Clear(); // Could this be the culprit?
    object tag = focus.Tag;
    if (tag != null)
    {
        if (EntryWidget.Resources.Contains(tag))
        {
            EntryWidget.Children.Add(EntryWidget.Resources[tag] as UIElement);
        }
    }
}

So:

  1. Is there a way to get the ListBox to work within a focus scope?

  2. Or is there another list control that works better inside a focus scope?

  3. Or am I taking the wrong approach by using focus scopes? My requirements: The user must be able to select an item from a scrollable list, which will enter the value into the current field. The current field should not lose focus.

like image 767
Kendall Frey Avatar asked Jan 05 '12 14:01

Kendall Frey


2 Answers

My answer would be to not use focus scope for this.

You're kindof defeating the purpose of focus. What if someone can't use the mouse and wants to use the keyboard instead?

why not just have selection in the list then set focus back to the original control, instead of doing all this extra work with focus scopes?

like image 111
John Gardner Avatar answered Sep 28 '22 09:09

John Gardner


When you click the ListBox or an item it contains, you are setting the keyboard focus of your application to the ListBox. This happens regardless of focus scopes you've defined.

It is possible to define a focus scope for the upper panel (the one containing the fields), and have the ListBox's SelectionChanged set focus to the upper panel, which will actually set focus to the item that was focused in that panel before any ListBox item was clicked.

<StackPanel>
    <StackPanel x:Name="upperPanel" FocusManager.IsFocusScope="True" GotFocus="upperPanel_GotFocus">
        <TextBox x:Name="TextBox1"></TextBox>
        <TextBox x:Name="TextBox2"></TextBox>
    </StackPanel>
    <StackPanel>
        <ListBox x:Name="ListBox1" SelectionChanged="ListBox_SelectionChanged">
            <ListBoxItem>First</ListBoxItem>
            <ListBoxItem>Second</ListBoxItem>
            <ListBoxItem>Third</ListBoxItem>
        </ListBox>

        <ListBox x:Name="ListBox2" SelectionChanged="ListBox_SelectionChanged">
            <ListBoxItem>4</ListBoxItem>
            <ListBoxItem>5</ListBoxItem>
            <ListBoxItem>6</ListBoxItem>
        </ListBox>
    </StackPanel>
</StackPanel>

private IInputElement focusedElement = null;

private void upperPanel_GotFocus(object sender, RoutedEventArgs e)
{
    focusedElement = e.Source as IInputElement;
}

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    UpdateTextBoxValue((e.AddedItems[0] as ListBoxItem).Content.ToString());
}

private void UpdateTextBoxValue(string text)
{
    TextBox focusedTextBox = focusedElement as TextBox;
    if (focusedTextBox != null)
    {
        focusedTextBox.Text = text;
    }

    upperPanel.Focus();
}

The currently active control in the upperPanel scope is kept up-to-date by the GotFocus event handler, and the UpdateTextBoxValue method is in charge of setting the text on the active TextBox control and setting the focus back to it.

I'm assuming you are familiar with the terms I was using (keyboard focus/logical focus); if not, you can have a look at Focus Overview or you can just ask.

like image 37
Adi Lester Avatar answered Sep 28 '22 09:09

Adi Lester