WPF Listview button selecting item - wpf

I have a button in a Listview column. When I press the button, the row does not select. I found one answer to the problem by using:
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
This works to select the row, but when the row loses focus it doesn't stay selected, and I need to be able to select multiple rows.
I also tried this solution:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewGotKeyboardFocus" Handler="SelectCurrentItem"/>
</Style>
</ListView.ItemContainerStyle>
And in code behind:
private void SelectCurrentItem(object sender, KeyboardFocusChangedEventArgs e)
{
ListViewItem item = (ListViewItem) sender;
item.IsSelected = true;
}
This is closer to what I want, but now this affect selecting multiple items and now the user has to click twice on another item before it will select.
I also tried re-selecting the row in the click handler by using:
ListViewItem selectedRow = ((FrameworkElement)sender).DataContext as ListViewItem;
and
((sender as FrameworkElement).TemplatedParent as ListViewItem).IsSelected = true;
but these give me a null error. Any suggestions for what I'm doing wrong?
I have to use a Listview for other reasons, or I would just use a Datagrid, which works great.

Try adding a handler on the button click that reselects the row.

Related

WPF: Remove dotted border around focused ListView

When my application change state to normal i want to set focus over my ListView:
private void Window_StateChanged(object sender, EventArgs e)
{
if (WindowState == WindowState.Normal)
ListViewUsers.Focus();
}
I am doind that in order to use Up & Down arrows Keys to navigate my ListViewItem instead of click on my ListView first to set focus.
So this works fine except the fact that i have this dotted border around my ListView.
I try to add this line to my ListViewItem Style:
<Style x:Key="ListBoxItemStyle1" TargetType="{x:Type ListViewItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/> ....
But unfortunately i still see this dotted border.
Any seggestions ?
It works fine for me.
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
How you apply style? Are you sure that ListView items use this style?

ZIndex on ObservableCollection bound ListBox

I have a ListBox bound to an ObservableCollection, with a canvas as an ItemsPanel. Everything works as expected - I've implemented dragging of the items succesfuly - but the problem is that I can't set the ZIndex of the clicked item. Debuging shows that all the items have a ZIndex of 0, which looks strange to me. What I want is to bring the item to front when clicked and send to back when released. Could someone give me any ideas? Please feel free to ask for any code that might be useful.
Update: This is the ItemsContainerStyle, defined as a Window Resource
<Style x:Key="MediaContainerStyle" TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding MediaPosition.X,UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="Canvas.Top" Value="{Binding MediaPosition.Y,UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="Panel.ZIndex" Value="{Binding ZIndex,UpdateSourceTrigger=PropertyChanged}"/>
</Style>
and the template for the item
<DataTemplate x:Key="MediaDataTemplate">
<views:MediaItemView MouseDown="OnMediaItemMouseDown"
MouseMove="OnMediaItemMouseMove"/>
</DataTemplate>
where MediaItemView is a user control.
In the code behind, I do
void OnMediaItemMouseDown(Object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == Pressed)
{
FrameworkElement feItem = sender as FrameworkElement;
MediaViewModel vmItem = feItem.DataContext as MediaViewModel;
vmItem.ZIndex = vm.MainMedia.Count;
// Keep the click point
pClick = e.GetPosition(feItem);
}
}
where vm is an instance of my underlying viewmodel, containing a Double property ZIndex
And, of course, it was right before my eyes! The answer, (taken from Modify ZIndex of an Items in an ItemsControl) was to add a trigger to my ItemContainerStyle (which has a ListBoxItem as TargetType) for IsSelected property. So...
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="99"/>
</Trigger>
</Style.Triggers>
will do!

DataGrid trailing space after last column

In a wpf datagrid if the datagrid is wider than the sum of the column widths, you get trailing space. By default, clicking in this area does not select the row nor does the selection row highlighting cover this area.
How can you register clicks from this area into selecting the appropriate row and also allow the selection row highlighting to extend into this area.
This question:
WPF DataGrid full row selection
is similar but I cannot add a dummy column nor set my column widths to *.
<DataGrid Name="dg">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseLeftButtonDown" Handler="DataGridRow_MouseLeftButtonDown" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource {x:Static SystemColors.HighlightBrushKey}}" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
with this code behind
private void DataGridRow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
dg.SelectedIndex = (sender as DataGridRow).GetIndex();
}
should work.

WPF TabControl - Select different tab when TabItem Visibility changes

I have a TabControl on a UserControl backed by a ViewModel, and the Visibility of one of the tab items is bound to a property on the ViewModel.
<TabControl x:Name="myTabControl">
<TabItem Header="Tab 1" />
<TabItem Header="Tab 2" Visibility="{Binding HasData, Converter={StaticResource boolToVisibilityConverter}}"/>
</TabControl>
When the Visibility of the TabItem changes, it collapses (hides) the TabItem header, but it continues displaying its content.
I want the TabControl to switch to the visible tab when the other tab is hidden, and was a little surprised to find out it doesn't happen automatically.
Attaching an event handler to the SelectionChanged event of the TabControl shows that TabItem.IsSelected (and TabControl.SelectedItem) is not even affected when the TabItem.Visibility changes (is this a bug?!).
I've tried both a property trigger:
<!-- This doesn't compile because of TargetName on the Setter, think you can only use it in Control Templates.
I don't know how to refer to the parent TabControl from within the TabItem style. -->
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Collapsed">
<Setter TargetName="myTabControl" Property="SelectedIndex" Value="0" />
</Trigger>
</Style.Triggers>
</Style>
</TabControl.ItemContainerStyle>
and a data trigger:
<!-- This doesn't quite work, it affects the Visibility of the TabItem's content too -->
<TabControl.Style>
<Style TargetType="{x:Type TabControl}" BasedOn="{StaticResource {x:Type TabControl}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem.Visibility, ElementName=tabControl}"
Value="Collapsed">
<Setter Property="SelectedIndex" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
I can't get the triggers to work, and there's no VisibilityChanged event I can handle, so I'm kind of stuck and would appreciate some help.
The TabItem class has an IsVisibleChanged event that you can use.
Bind SelectedIndex of TabControl to a property. And change the value of this property to the index of tab which you want to display whenever you change the visibility to collapse of tab item.
You could add this event handler to the code behind. It will test your control in the first place and on changes to tab visibility due to bindings.
Instead of doing this OnLoaded of course it makes total sense to put this into an attached Property. (AutoSelect?) . The code is the same. You get called in the first place and attach events to IsVisibleChanged. Then the only trick is to use a lambda (Parameter binding) to get the TabControl instance into the event callback. I am posting this solution, because it is shorter.
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
{
var tabControl = (TabControl) sender;
// register visibility changed to react on changes
foreach (TabItem item in tabControl.Items)
{
item.IsVisibleChanged += (mSender, ev) => item_IsVisibleChanged(mSender, ev, tabControl);
}
// if current selected tab is invisible, find and select first visible one.
if (!((TabItem) tabControl.SelectedItem).IsVisible)
{
foreach (TabItem item in tabControl.Items)
{
if (item.IsVisible)
{
tabControl.SelectedItem = item;
return;
}
}
}
}
private static void item_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e, TabControl tabControl)
{
// just became IsVisible = false
if ((bool)e.NewValue == false)
{
if (tabControl == null) return;
ItemCollection items = tabControl.Items;
foreach (UIElement item in items)
{
if (item.IsVisible)
{
tabControl.SelectedItem = item;
return;
}
}
}
}

How to use IsKeyboardFocusWithin and IsSelected together?

I have a style defined for my ListBoxItems with a trigger to set a background color when IsSelected is True:
<Style x:Key="StepItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Padding="0" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#40a0f5ff"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This style maintains the selected item even when the ListBox and ListBoxItem loses focus, which in my case is an absolute must.
The problem is that I also want the ListBoxItem to be selected when one of its TextBox's child gets focused. To achieve this I add a trigger that sets IsSelected to true when IsKeyboardFocusWithin is true:
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected.
How can I keep both behaviours?
When your listbox looses focus, it will set selected item to null because of your trigger. You can select on focus using some code behind that will not unselect when you loose focus.
XAML:
<Window x:Class="SelectedTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel>
<TextBox Text="Loose focus here" />
<ListBox Name="_listBox" ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" GotFocus="OnChildGotFocus">
<TextBox Text="{Binding .}" Margin="10" />
<TextBox Text="{Binding .}" Margin="10" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" SnapsToDevicePixels="true" Background="Transparent">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</StackPanel>
</Window>
Code behind:
private void OnChildGotFocus(object sender, RoutedEventArgs e)
{
_listBox.SelectedItem = (sender as StackPanel).DataContext;
}
"When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected."
Actually, I don't think it has lost that original behavior. What I suspect is happening is you're clicking directly in the textbox from somewhere else so the underlying ListBoxItem never actually became selected. If it did however, you'd see the selection would still remain after you left as you want.
You can test this by forcing the ListBoxItem to be selected by clicking directly on it (side-note: you should always give it a background, even if just 'transparent' so it can receive mouse clicks, which it won't if it's null) or even just hitting 'Shift-Tab' to set the focus there, back from the textbox.
However, that doesn't solve your issue, which is that the TextBox gets the focus but doesn't let the underlying ListBoxItem know about it.
The two approaches you can use for that are an event trigger or an attached behavior.
The first is an event trigger on the IsKeyboardFocusWithinChanged event where you set 'IsSelected' to true if the keyboard focus changed to true. (Note: Sheridan's answer does a faux-change-notification but it should not be used in cases where you can multi-select in the list because everything becomes selected.) But even an event trigger causes issues because you lose the multi-select behaviors such as toggling or range-clicking, etc.
The other (and my preferred approach) is to write an attached behavior which you set on the ListBoxItem, either directly, or via a style if you prefer.
Here's the attached behavior. Note: You again would need to handle the multi-select stuff if you want to implement that. Also note that although I'm attaching the behavior to a ListBoxItem, inside I cast to UIElement. This way you can also use it in ComboBoxItem, TreeViewItem, etc. Basically any ContainerItem in a Selector-based control.
public class AutoSelectWhenAnyChildGetsFocus
{
public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
"Enabled",
typeof(bool),
typeof(AutoSelectWhenAnyChildGetsFocus),
new UIPropertyMetadata(false, Enabled_Changed));
public static bool GetEnabled(DependencyObject obj){ return (bool)obj.GetValue(EnabledProperty); }
public static void SetEnabled(DependencyObject obj, bool value){ obj.SetValue(EnabledProperty, value); }
private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var attachEvents = (bool)e.NewValue;
var targetUiElement = (UIElement)sender;
if(attachEvents)
targetUiElement.IsKeyboardFocusWithinChanged += TargetUiElement_IsKeyboardFocusWithinChanged;
else
targetUiElement.IsKeyboardFocusWithinChanged -= TargetUiElement_IsKeyboardFocusWithinChanged;
}
static void TargetUiElement_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var targetUiElement = (UIElement)sender;
if(targetUiElement.IsKeyboardFocusWithin)
Selector.SetIsSelected(targetUiElement, true);
}
}
...and you simply add this as a property setter in your ListBoxItem's style
<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" />
This of course assumes you've imported an XML namespace called 'behaviors' that points to the namespace where the class is contained. You can put the class itself in a shared 'Helper' library, which is what we do. That way, everywhere we want it, its a simple property set in the XAML and the behavior takes care of everything else.
I figured out that IsKeyboardFocusWithin is not the best solution.
What I did in this case was to set the style on all of the controls used as DataTemplate to send the GotFocus-event to be handled in code behind. Then, in code behind, I searched up the visual tree (using VisualTreeHelper) to find the ListViewItem and set IsSelected to true. This way it does not "touch" the DataContext and works just with the View elements.
<Style TargetType="{x:Type Control}" x:Key="GridCellControlStyle">
...
<EventSetter Event="GotFocus" Handler="SelectListViewItemOnControlGotFocus"/>
...
private void SelectListViewItemOnControlGotFocus(object sender, RoutedEventArgs e)
{
var control = (Control)sender;
FocusParentListViewItem(control);
}
private void FocusParentListViewItem(Control control)
{
var listViewItem = FindVisualParent<ListViewItem>(control);
if (listViewItem != null)
listViewItem.IsSelected = true;
}
public static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
var correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}

Resources