WPF Click on control inside ListBoxItem does not select ListBoxItem - c#

Hi I could not find any similar problem so I posted new question. In code below I create ListBox control with ListBoxItems that each contains radio button inside. When I click on the radio button it gets selects but parent ListBoxItem does not (ListBoxItem is not highlighted). How can I solve this issue?
<ListBox Margin="0, 5, 0, 0" ItemsSource="{Binding mySource, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" SelectionMode="Single">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- Rabio template -->
<RadioButton GroupName="radiosGroup"
Margin="10, 2, 5, 2"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.SelectedSetting}"
CommandParameter="{Binding SomeId, Mode=OneWay}"
Content="{Binding FileNameWithoutExtensions, Mode=OneWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

You can achieve this by applying the following ItemContainerStyle to your ListBox which uses Trigger on property IsKeyboardFocusWithin to select it.
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>

I have a listbox that displays ListBoxItems vertically, horizontally, and has all sorts of child buttons contained within each ListBoxItem.
The problem I ran into (like others) is when you click on a child button contained in the ListBoxItem, the ListBoxItem is not selected and you can not get the ListBoxItem.SelectedIndex value (because clicking on the button does not select the ListBoxItem).
I had some problems implementing the above xaml code because clicking on my GroupBox header would cause my selected ListBoxItem to lose focus.
The best solution I found on the web for this problem was to add a couple lines of code to the button's mouse click event to determine the parent control and then set the ListBoxItem.IsSelected = true.
After this is done, the ListBoxItem.SelectedIndex will contain the correct index value for the item selected. In my code, DataContext is set on the Listbox like this: DataContext="{StaticResource VM_Programs}"
Here's the VB code behind for the button event:
Private Sub YourButton_Click(sender As Object, e As RoutedEventArgs)
Dim clicked As Object = (TryCast(e.OriginalSource, FrameworkElement)).DataContext
Dim lbitem As ListBoxItem
lbitem = YourListboxName.ItemContainerGenerator.ContainerFromItem(clicked)
lbitem.IsSelected = True
MsgBox("The listbox item (" + YourListboxName.SelectedIndex.ToString + ") is now selected")
End Sub

Related

Make an event bubble up from the children upwards

AFAIK bubbling means that if an event is triggered on the children, the same event will be triggered on the parents.
I have this piece of code:
<ListView Name="lvFiles" SelectedItem="{Binding SelectedTabFilePath, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Path=SelectedItem.Files,ElementName=trvFiles, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=Name}"
Command="{Binding DataContext.OpenFileFromTreeView,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"
CommandParameter="{Binding DataContext,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"
Background="Transparent"
BorderBrush="Transparent"
>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The problem is that if I click on the button, the property SelectedTabFilePath from SelectedItem will not be filled with data (because I didn't click on the item of the ListView).
So, my question is:
Is there any way to trigger the Click event of the button and that of the ListView too?
Or if not, what would be the way to set the SelectedTabFilePath from the SelectedItem attribute when I click on that button, too?
The Click event is indeed bubbling, which means it will be routed up the elements of visual tree until the root element is reached. However, the Button itself handles the event, because its Command property was bound. That means its parent elements, including ListViewItem (the item container) will ignore the event, therfore not select the item.
There is a workaround using the fact that the Button receives keyboard focus upon clicking. You add an item container style with a trigger that sets the IsSelected property of the ListViewItem once the keyboard focus is within, denoted by the IsKeyboardFocusWithin property.
<ListView ...>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
<!-- ...your other markup. -->
</ListView>
how to handle WPF listbox selectionchanged event using MVVM
I think you could use interaction to trigger the same command(OpenFileFromTreeView) when the listbox selection changes.
Instead of:
AncestorType={x:Type ListView}
What if you try:
AncestorType={x:Type ListViewItem}

How to make clicking a Button inside a ListBoxItem select the item?

I have a ListBox defined as follows:
<ListBox
DisplayMember="Name"
ItemsSource="{Binding Persons}"
ItemTemplate="{StaticResource PeopleDataTemplate}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
Name="listOfPeople">
</ListBox>
The Person class contains two properties:
public string FirstName { get; set; }
public string LastName { get; set; }
XAML:
<UserControl.Resources>
<DataTemplate x:Key="PeopleDataTemplate">
<StackPanel>
<Image
Width="75"
Height="75"
Source="{Binding Picture}">
</Image>
<TextBlock FontSize="14" TextWrapping="Wrap" Text="{Binding Name}" />
<Button Command="{Binding DataContext.AddCommand , ElementName=main}" Content="Add" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
If I click the Image, the ListBoxItem gets selected but if I click the Button, the ListBoxItem does not get selected. Why?
The simplest solution is to add corresponding triggers to the ListBox.ItemContainerStyle to delegate focus:
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Why doesn't the item get selected?
TL;DR: because the mouse down event is handled by the Button and never reaches the ListBoxItem.
What triggers the IsSelected property on each ListBoxItem is the MouseRightButtonDownEvent routed event declared on UIElement whose handling is overriden by ListBoxItem here. However, this MouseDownEvent is a bubbling routed event, meaning that it climbs up the visual tree until marked as handled by something on its way:
If you click your Image: Image receives MouseDown but does not handle it. It bubbles up to StackPanel, same story. Bubbles up to ListBoxItem. Bingo, it is handled there and calls a method on the parent ListBox that marks the item as selected.
If you click your Button however: the Button receives the MouseDown event and immediately marks it as Handled here. The event propagation is stopped and the event never bubbles up to the ListBoxItem which never gets selected.
How to get around this?
The MVVM way
In your AddCommand implementation, add some code to change the IsSelected property to true. Since you bind to IsSelected, this will select your ListBoxItem.
The XAML way
Add a Click event handler to the Button to manually mark its parent ListBoxItem as Selected.
As pointed out by #Sinatr, you could also react to focus events to select any item that gets the focus. Subscribe to the GotFocus event of each of your StackPanel for example and find the parent ListBox (on which you call UnselectAll()) and find the parent ListBoxItem (on which you call Focus()).

WPF MVVM: Set focus to current selected item in Datagrid on button click

I have a MVVM WPF app in Visual Studio 2008 and NET Framework 3.5 SP1. Among other controls, this app have a datagrid and a button.
Button:
<Button Grid.Column="1" Command="{Binding CalculateCommand}" FocusManager.FocusedElement="{Binding ElementName=myDataGrid}" HorizontalAlignment="Right">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=myDataGrid, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Image VerticalAlignment="Center" Source="/MyWPFApp;component/Images/Calculate.png"></Image>
<TextBlock VerticalAlignment="Center">Calculate</TextBlock>
</StackPanel>
</Button>
What I am trying to do is to set focus on the current selected item in the datagrid once button is clicked so when I click on up/down arrow key in the keyboard I can move to any other item in the datagrid.
So I have tried to set FocusManager.FocusedElement property in the button but it is not working. I have also tried to set as is without specifying the Path:
FocusManager.FocusedElement="{Binding ElementName=myDataGrid}"
In the first attempt, without setting the Path in the property:
FocusManager.FocusedElement="{Binding ElementName=myDataGrid}"
when I click on down of up arrow key in the keyboard (after clicking button), it change the focus to another control in the UI that is not the current selected item in the datagrid.
In the second attempt, setting the Path in the property:
FocusManager.FocusedElement="{Binding ElementName=myDataGrid, Path=SelectedItem}"
it simply does nothing, neither no focus in the current selected item in the datagrid nor in any other control.
Also I have tried an attached behaviour as said here but it is not working:
<Button Grid.Column="1" Command="{Binding CalculateCommand}" classes:EventFocusAttachment.ElementToFocus="{Binding ElementName=myDataGrid}" HorizontalAlignment="Right">
Another attempt:
It works on second key click, first click is ignored.
private void Button_Click_1(object sender, RoutedEventArgs e)
{
if (myDataGrid.SelectedIndex > -1)
{
var selectedRow = (Microsoft.Windows.Controls.DataGridRow)myDataGrid.ItemContainerGenerator.ContainerFromIndex(myDataGrid.SelectedIndex);
FocusManager.SetIsFocusScope(selectedRow, true);
FocusManager.SetFocusedElement(selectedRow, selectedRow);
}
}
#Evk Solution works perfectly.
The following should work:
if (myDataGrid.SelectedIndex > -1) {
var container = (DataGridRow) myDataGrid.ItemContainerGenerator.ContainerFromIndex(myDataGrid.SelectedIndex);
if (container != null) {
container.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
}
You can put that code into Button.Click event (nothing wrong here, what we are doing is completely view-only thing) or if you don't like code-behind you can create attached property\behavior from that.

Xaml Listbox item focus issue

I am using a xaml UserControl as part of a WPF application. i have created a list box which i have populated with data from a text search. this data appears on buttons which are used to select the users desired option from the search.
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
Margin="2">
<ListBox ItemsSource="{Binding Path=CrewOptions}"
HorizontalContentAlignment="Stretch"
BorderThickness="0"
SelectionMode="Multiple"
Name="CrewOptionsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Button x:Name="irrelevant"
Height="28px"
Background="#F4F3E9"
Margin="2,2,2,2"
Content="{Binding irrelevant1, TargetNullValue={x:Static sys:String.Empty}}"
Command="{Binding irrelevant2}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}"
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</ScrollViewer>
This works fine, however when i tab from the textbox into the list box and then use the arrow keys to select an option, the enter key press does not select the button. Instead, i have to tab once more to focus on the button before pressing enter to select it.
Is there any way to avoid having to hit the last tab key to focus on the button?
Open to both Xaml and C# solutions (preferably MVVM)
Hi you can add below code in ListBox this will resolve focus issue on ListBoxItem.
<ListView.ItemContainerStyle>
<Style TargetType="ContentControl">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListView.ItemContainerStyle>
The reason for this is that you have cascading controls. When you use the arrow keys the List box is the active control, therefore all events will be fired based on the list box, not the button.
One way forward is to assign a keypressed event on the selected item, then initiate the function that will be triggered by the button.
something like:
listBox_keyPressed()
{
if(selecteditem)
{
DoSomethingFor(selectedItem);
}
}

WPF Listview empty text

How to show in the WPF Listview using the GridView an empty text (like in ASP.net), e.g. "please select a person" or "0 items founded"?
This XAML will do something similar, it has a visible ListView showing a list and a hidden message and switches visibility when the list is empty using a trigger.
The code below will work with any IList or ICollection but the same technique can be used with any data source, like always, if you want the display to update when you add or remove items you need to use an ObservableCollection or similar.
The ContentPresenter is there because you can only use triggers inside a template or a style, so we put our controls inside a DataTemplate and use the ContentPresenter to show it.
If you want the message to appear inside the ListView than all you have to do is remove the Setter that hides the ListView and add some margin to the TextBlock to position it where the first item in the ListVIew should be.
<ContentPresenter Content="{Binding}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<Grid>
<ListView Name="list" ItemsSource="{Binding MyList}"/>
<TextBlock Name="empty" Text="No items found" Visibility="Collapsed"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding MyList.Count}" Value="0">
<Setter TargetName="list" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="empty" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
Bind it to a DataSource + Property that returns the text you want ?
Slot in a dummy object whose String representation is the text you want..

Categories