0
votes

Within a Listbox control I have a Data Template which consists of text and a button. Given the nature of Silverlight/WPF when I click on the button within the listbox item the button event is trapped before the listbox item is selected. Therefore if I am trying to pass the record ID of the selected listbox item I am currently only able to do so by first clicking and selecting the listbox item and then clicking on the button.

Is there a way to promote the selection of the listbox item so that when the listbox items are created I have the ability to click on the button within the listbox item and some event (selectionChanged ?) is invoked which would allow me to capture the selected record id and use it for some other action ( pass as a parameter in a method etc). I'm using Simple MVVM toolkit for this implementation so I was wondering if this could be handled in the viewModel or if I would need to handle this in the controls code behind and then push the selection to the viewModel.

The listbox control is presented as:

<ListBox x:Name="ResultListBox"
             HorizontalAlignment="Stretch"
             Background="{x:Null}"
             Grid.Row="1"
             BorderThickness="0" HorizontalContentAlignment="Stretch"
             ItemContainerStyle="{StaticResource ListBoxItemStyle1}"
             ItemsSource="{Binding SearchResults[0].Results}"
             ScrollViewer.HorizontalScrollBarVisibility="Disabled"
             Style="{StaticResource ListBoxStyle1}">

        <ListBox.ItemTemplate>

            <DataTemplate>
                <dts:TypeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch">
                    <!--  Template 1  -->
                    <formatter:TypeTemplateSelector.CFSTemplate>
                        <DataTemplate>
                            <qr:ucIndex_Product />
                        </DataTemplate>
                    </formatter:TypeTemplateSelector.CFSTemplate>

                    <!--  Template 2  -->
                    <formatter:TypeTemplateSelector.PersonTemplate>
                        <DataTemplate>
                            <qr:ucIndex_Person  />
                        </DataTemplate>
                    </formatter:TypeTemplateSelector.PersonTemplate>

            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

Within the datatemplate (user control) resides the button along with a number of other fields. I'll omit that code for the time being unless requested.

Thanks in advance!

3

3 Answers

2
votes

Put this in your ListBox.Resources

<Style TargetType="{x:Type ListBoxItem}">
    <EventSetter Event="PreviewGotKeyboardFocus" Handler="SelectCurrentItem"/>
</Style>

And this in the Code Behind

protected void SelectCurrentItem(object sender, KeyboardFocusChangedEventArgs e)
{
    ListBoxItem item = (ListBoxItem)sender;
    item.IsSelected = true;
}

You could use the following code as well which doesn't use code-behind, however it only keeps the ListBoxItem selected for as long as it has KeyBoard focus. Once focus leaves, the item becomes unselected

<Style TargetType="ListBoxItem">
  <Style.Triggers>
    <Trigger Property="IsKeyboardFocusWithin" Value="True">
      <Setter Property="IsSelected" Value="True" />
    </Trigger>
  </Style.Triggers>
</Style>

EDIT

Since Silverlight doesn't have EventSetters, you can use the ListBox's Loaded event and add the following to your code behind:

private void ResultListBox_Loaded(object sender, RoutedEventArgs e)
{
    ListBox list = (ListBox)sender;
    list.GotFocus += ResultListBox_GotFocus;
}

void ResultListBox_GotFocus(object sender, RoutedEventArgs e)
{
    var item = FindAncester<ListBoxItem>((DependencyObject)e.OriginalSource);
    if (item != null) item.IsSelected = true;
}

T FindAncester<T>(DependencyObject current) 
    where T : DependencyObject
{
    current = VisualTreeHelper.GetParent(current);

    while (current != null)
    {
        if (current is T)
        {
            return (T)current;
        }
        current = VisualTreeHelper.GetParent(current);
    };
    return null;
}

This captures the Focus event for the ListBox, takes the control that triggered the focus event and traverses up the visual tree to find the ListBoxItem objects, and sets it's Selected value to true.

1
votes

Rachel's solution works great. The one issue I did find in this approach was that it does place total focus on the selected item. As a result the user would be required to double click within the control to place focus on other items such as selectable text or other button. After working with this a bit more I discovered you can also resolve this by setting the listbox selected items to the data context of the object you are clicking on etc. This works well as allows you to set this to any UI object within the control.

   ListBox.SelectedItem = ((HyperlinkButton)sender).DataContext;

In this example I had Hyperlink buttons within the data template. Clicking on them would then set the focus to the selected listbox item.

1
votes

rlcrews got it right! Use DataContext:

ObservableCollection<Employee> employees1;
...
listBox1.ItemsSource = employees1;
...

//DataTemplate in ListBox has a button with following event
private void bnPromoteEmployee_Click(object sender, RoutedEventArgs e)
{
  Employee emp1 = (Employee)((Button)sender).DataContext;
  emp1.Promote();
}