Wpf ItemTemplate CurrentItem - wpf

I have a simple ListBox.ItemTemplate containing a Label and a TextBox bound to a CSLA Bindable List. When I select the TextBox the CurrentItem does not change, it only changes if I select the Label. I have IsSynchronizedWithCurrentItem='True'.
<ListBox x:Name="ItemsDataGrid"
ItemsSource="{Binding Source={StaticResource AuditItems},Path=Items}"
IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"></ColumnDefinition>
<ColumnDefinition Width="100"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Column="0"
Content="{Binding Path=TypeRef}" />
<TextBox x:Name="TextBoxQty"
Grid.Column="1"
Text="{Binding Path=TaliQty}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Try adding this to your ListBox. It selects the item any time any contained element (like TextBox) gets keyboard focus. A similar method could also be used with just a simple setter in the Trigger but that tends to interfere with the CurrentItem setting on the ICollectionView:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Trigger.EnterActions>
<BeginStoryboard x:Name="SetSelected">
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame KeyTime="0:00" Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="SetSelected"/>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>

This is happening because the TextBox is handling the MouseDown event. As it is set to bubble up it will not reach the containing ListBoxItem. The simplest way to fix this would be to just handle the selection of the ListBoxItems in the PreviewMouseDown, which will occur and tunnel down before the actual MouseDown event bubbles up.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="PreviewMouseDown"
Handler="ListBoxItem_PreviewMouseDown" />
</Style>
</ListBox.ItemContainerStyle>
And in the Code behind for the xaml file:
private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
var item = (sender as ListBoxItem);
if (item != null)
item.IsSelected = true;
}

Related

CheckBox Opacity bound to ViewModel takes no effect in DoubleAnimation

I have got a ListBox.ItemTemplate that has got a ToggleButton and inside of it a checkbox and a ProgressBarEdit from devexpress.
Then I have a style that targets the checkbox. When the mouse is over the checkbox it changes its opacity.
The double animation and the binding works fine, however the listbox itemssource is bound and I would like to make all the checkboxes opacity change when the mouse is over any checkbox and not just one.
I tried to bind Storyboard.TargetProperty to my viewmodel property but it is not a dependency property so I cannot do it.
This is the animation:
<Style x:Key="FadeOutButton" TargetType="{x:Type CheckBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, ElementName=chkImport}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
Storyboard.TargetProperty="Opacity"
To="1" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:1"
Storyboard.TargetProperty="Opacity"
To="0.02" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
This is part of the ListBox control:
<ListBox x:Name="FileDetailsListBox"
Grid.Column="2"
BorderThickness="0"
ItemsSource="{Binding FileDetailsList}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<ToggleButton x:Name="FileToggleButton"
Margin="-1,0,-1,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
BorderThickness="0,0">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<CheckBox x:Name="chkImport"
Margin="0,0,0,3"
HorizontalContentAlignment="Center"
IsChecked="{Binding Import,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
Opacity="{Binding DataContext.ImportOpacity,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
ElementName=ImportView}"
Style="{StaticResource FadeOutButton}" />
<dxe:ProgressBarEdit Grid.Row="2"
Height="20"
Margin="-3,0,-3,0"
EditValue="{Binding ProgressValue}"
ShowBorder="False"
StyleSettings="{Binding IsMarquee,
Converter={StaticResource conv}}" />
</Grid>
</ControlTemplate>
</ToggleButton.Template>
</ToggleButton>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
How can I change all the checkboxes opacity from the Listbox when Control.MouseEnter is triggered?
I thought the way to go was binding the ImportOpacity property from my viewmodel in the style, but the animation does not allow it.
Thanks
Update:
I cannot post images because I am new here, but I would like to change the Opacity for all the checkboxes created by the datatemplate when the mouse enter in any checkbox. The animation works fine for individual checkboxes like the code does at the moment.
My viewmodel has got a property called "ImportOpacity" and it is bound to the checkbox.opacity (you can see it in the xaml above) My idea was to use this property to make the Opacity from 0 to 100 when the mouse is over any checkbox.
Instead of animate the Opacity property of the CheckBox and bind it to ImportOpacity with TwoWay mode, I think you should animate the ImportOpacity property in your ViewModeldirectly.
Here is a reference about how to animate property in ViewModel.

clicking the hyperlink inside WPF listview is not selecting the corresponding row

I have following listview with a gridview column and cell template for that gridview column. But when I click on the "hyperlink", the corresponding gridview row is not getting selected.
Could anyone give me a solution please
DATA TEMPLATE
<DataTemplate x:Key="smTemplate">
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="{Binding SM}" />
<TextBlock>
<Hyperlink x:Name="tHLink" Click="thL_Click" KeyboardNavigation.IsTabStop="True">
<TextBlock Text="TH" />
</Hyperlink>
</TextBlock>
</StackPanel>
</DataTemplate>
LIST VIEW
<ListView Focusable="True">
<ListView.View>
<GridView>
<GridViewColumn Header="DM" CellTemplate="{StaticResource smTemplate}" />
</GridView>
</ListView.View>
</ListView>
try changing ListViewItem style so when it gains keyboard focus it will be automatically selected:
<ListView>
<!-- ..... -->
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<EventTrigger RoutedEvent="PreviewGotKeyboardFocus">
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected">
<DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:0" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Hyperlink overrides OnMouseLeftButtonDown and OnMouseLeftButtonUp. In those methods, e.Handled = true is set after processing your event handlers, so you can't override it by attaching events to the control in XAML. In order to fix the behavior to suit your needs, you'll need to create a class derived from Hyperlink and override mouse handling events.
However, all this is unnecessary. Judging by you code, Hyperlink causes more problems than it solves, so I suggest replacing it with a single TextBlock control with a custom style.
<TextBlock Text="TH" TextDecorations="Underline" Cursor="Hand"
Foreground="{x:Static SystemColors.HotTrackBrush}"/>
[Not a good answer]
I find a good solution here. and I just copied and pasted following code and I used it. its worked. even if you click on white blank , the corresponding row will be selected.
<ControlTemplate TargetType="ListViewItem" x:Key="rowStyle1">
<Grid x:Name="backGroundPanel">
<GridViewRowPresenter Content="{TemplateBinding Content}" />
<Rectangle Fill="White" Opacity="0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="backGroundPanel" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="columnHeaderContianerStyle" TargetType="ListViewItem">
<Setter Property="Template" Value="{StaticResource ResourceKey=rowStyle1}"/>
</Style>
and I set property "ItemContainerStyle" of listview like code below:
<ListView ItemContainerStyle="{DynamicResource columnHeaderContianerStyle}" ... >
[a revised answer]
The above solution is not working if we are interested in capturing mouse in column control. because the rectangle in backGroundPanel capture mouse events ,then it prevents mouse events being captured by GridViewRowPresenter. so we must use a strategy that have 2 features:
Mouse events should be captured by inner column controls.
When user clicked every Where in the row, The row must be selected.
so, the solution get simpler this time. I used below code for my ListView's ItemContainerStyle.
<Style x:Key="columnHeaderContianerStyle" TargetType="ListViewItem">
<EventSetter Event="PreviewMouseDown" Handler="ListViewItem_PreviewMouseDown"></EventSetter>
</Style>
and event Handler is :
private void ListViewItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (sender is ListViewItem)
{
ListViewItem s = (ListViewItem)sender;
s.IsSelected = true;
}
}
It works fine with 2 mentioned features.

Navigating Out Of A ListBox In WPF

I have a button above a listbox which has it's items in a horizontal allignment. If my button has focus and I press the down key on the keyboard then the listbox get focus. If I then press key up then the listbox still has focus. Can this behaviour be changed so when I press key up my button has focus again?
EDIT: I should add I'm using MVVM so would like to keep the solution out of codebehind if possable.
Below is the complete code for my listbox.
<ListBox Grid.Row="2" ItemsSource="{Binding Movies}" TextSearch.TextPath="Title" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel IsItemsHost="True" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Template>
<ControlTemplate>
<Grid>
<ScrollViewer VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Disabled">
<ItemsPresenter />
</ScrollViewer>
</Grid>
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="poster">
<Image x:Name="posterImage" RenderOptions.BitmapScalingMode="LowQuality" Source="{Binding Poster}" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}, Path=ActualHeight}">
</Image>
<TextBlock Text="{Binding PosterOverlayText}" Style="{DynamicResource PosterOverlayText}" Width="{Binding ActualWidth, ElementName=posterImage}"/>
<Grid.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="st" ScaleX="0.85" ScaleY="{Binding ScaleX, RelativeSource={RelativeSource Self}}"/>
</TransformGroup>
</Grid.LayoutTransform>
<Grid.InputBindings>
<MouseBinding Command="{x:Static Commands:MediaFiles.PlaySelectedMovie}" Gesture="LeftDoubleClick" />
</Grid.InputBindings>
</Grid>
<DataTemplate.Resources>
<CubicEase x:Key="ease" EasingMode="EaseOut"/>
</DataTemplate.Resources>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},Path=IsSelected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.3"
EasingFunction="{StaticResource ease}"
Storyboard.TargetName="st"
Storyboard.TargetProperty="ScaleX"
To="1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Duration="0:0:0.3"
EasingFunction="{StaticResource ease}"
Storyboard.TargetName="st"
Storyboard.TargetProperty="ScaleX"
To="0.85"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent"/>
</Style.Resources>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.InputBindings>
<KeyBinding Command="{x:Static Commands:MediaFiles.PlaySelectedMovie}" Gesture="ENTER"/>
</ListBox.InputBindings>
</ListBox>
Don't know if there is a straight-forward MVVM accepted solution to this problem. The reason is that ListBox actually uses the key up and key down events to navigate through the list items (which is the desired effect most of the times) whereas the buttons will let the events go to the FocusManager class.
However, as for everything out there, there are workarounds so here's how I would do it:
First of all you need to intercept the Key-Down event and look for directional inputs (up-down-left-right). I would think that you would still like to navigate with the keys in your list so, in the event handler I would make sure to test if I am on the first (or last) element and then use the MoveFocus() method with TraversalRequest - FocusNavigationDirection.Up / Down / Next / Previous to move the focus.
Last, to ensure your code-behind is clean, create a new ListBox derived control and have your event there. Actually, in the derived control, don't use the event, overide OnKeyDown method. This way, you are writing the focus logic in your custom control and not the View.
Code:
protected override void OnKeyDown(KeyEventArgs e)
{
if ((e.Key == Key.Up ||e.Key == Key.Left) && SelectedIndex == 0)
{
//.Previous will navigate correctly to the last element
this.MoveFocus(new TraversalRequest(FocusNavigationDirection.Previous));
return;
}
if (e.Key == Key.Down && this.SelectedIndex == this.Items.Count - 1)
{
//.Next will navigate to the first item in the list rather than skip to the next control. We will use Down or Right.
//this.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
this.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down));
return;
}
if (e.Key == Key.Right && this.SelectedIndex == this.Items.Count - 1)
{
this.MoveFocus(new TraversalRequest(FocusNavigationDirection.Right));
return;
}
base.OnKeyUp(e);
}

How to Animate Templated Parent's Panel.ZIndex in WPF?

I'm attempting to bring a templated item forward (ZIndex) in a databound stackpanel. Since a stackpanel creates a ContentPresenter for each item within it, my visual tree looks like this:
ItemsControl
Border
ItemsPresenter
StackPanel
ContentPresenter
ToggleButton
ContentPresenter
ToggleButton
ContentPresenter
ToggleButton
When a ToggleButton is clicked, it fires off a storyboard like this:
<Storyboard x:Key="MyStoryboard" >
<Int32AnimationUsingKeyFrames BeginTime="0:0:0.000" Duration="0:0:0.350"
Storyboard.TargetProperty="TemplatedParent.(Panel.ZIndex)" >
<Int32KeyFrameCollection>
<DiscreteInt32KeyFrame KeyTime="0:0:0.000" Value="99" />
</Int32KeyFrameCollection>
</Int32AnimationUsingKeyFrames>
</Storyboard>
<Style x:Key="SubStateOptionButtonStyle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<!-- Toggle Button Contents here -->
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MyStoryboard}" />
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
However, the TemplatedParent.(Panel.ZIndex) gives me a strange exception:
Value cannot be null.
Parameter name: dp
at System.Windows.DependencyObject.GetValue(DependencyProperty dp)
at System.Windows.Media.Animation.Storyboard.ProcessComplexPath(HybridDictionary clockMappings, DependencyObject targetObject, PropertyPath path, AnimationClock animationClock, HandoffBehavior handoffBehavior, Int64 layer)
at System.Windows.Media.Animation.Storyboard.ClockTreeWalkRecursive(Clock currentClock, DependencyObject containingObject, INameScope nameScope, DependencyObject parentObject, String parentObjectName, PropertyPath parentPropertyPath, HandoffBehavior handoffBehavior, HybridDictionary clockMappings, Int64 layer)
at System.Windows.Media.Animation.Storyboard.ClockTreeWalkRecursive(Clock currentClock, DependencyObject containingObject, INameScope nameScope, DependencyObject parentObject, String parentObjectName, PropertyPath parentPropertyPath, HandoffBehavior handoffBehavior, HybridDictionary clockMappings, Int64 layer)
at System.Windows.Media.Animation.Storyboard.BeginCommon(DependencyObject containingObject, INameScope nameScope, HandoffBehavior handoffBehavior, Boolean isControllable, Int64 layer)
at System.Windows.Media.Animation.BeginStoryboard.Begin(DependencyObject targetObject, INameScope nameScope, Int64 layer)
etc.
I've double checked via code-behind that the ToggleButton's TemplatedParent is indeed the ContentPresenter and I can set the ZIndex with something like this:
Panel.SetZIndex((sender as ToggleButton).TemplatedParent as UIElement, 99);
But I'd like to animate it with a storyboard, preferably with straight XAML, since it needs to stay forward for the duration of the storyboard, then go back to where it was.
I've considered inheriting from ItemsControl to prevent the ContentPresenter from being created but that's a heavy-handed solution to something that seems like it should be easy.
Here is the answer to this question:
http://social.msdn.microsoft.com/Forums/en/wpf/thread/7b9b8209-063d-46b2-a03f-98b393cf9514
(by Min Zhu [MSFT])
Your original code doesn't work because TemplatedParent is not a dependency property. So it is not a valid path for animation.
The code TI82 suggests is correct. It still doesn't work because the TemplatedParent is the Button. But the element you want to animate is the item container.
To animate the item container, you can use ItemsControl's ItemContainerStyle. Button.Click is a bubbling routed event, so it will bubble from the button to the item container as well.
Try the following code.
<Window.Resources>
<Storyboard x:Key="MyStoryboard">
<ColorAnimation Storyboard.TargetName="ButtonRect"
Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)"
To="Red"
Duration="0:0:0.350"
FillBehavior="Stop"/>
</Storyboard>
<Style x:Key="MyButtonStyle" TargetType="Button">
<Setter Property="Panel.ZIndex" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContentPresenter}, Mode=OneWayToSource, Path=(Panel.ZIndex)}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Height="60" Margin="0,0,0,-10" >
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="15">
<Rectangle x:Name="ButtonRect" RadiusX="15" RadiusY="15" Fill="AliceBlue" />
</Border>
<TextBlock Text="{Binding}" FontSize="15" TextAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard Storyboard="{StaticResource MyStoryboard}" />
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="MyButtonTemplate">
<Grid>
<Button Style="{StaticResource MyButtonStyle}" />
</Grid>
</DataTemplate>
<Storyboard x:Key="MyStoryboard2">
<Int32AnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.ZIndex)"
BeginTime="0:0:0.000" Duration="0:0:0.350" FillBehavior="Stop">
<Int32KeyFrameCollection>
<DiscreteInt32KeyFrame KeyTime="0:0:0.000" Value="99" />
</Int32KeyFrameCollection>
</Int32AnimationUsingKeyFrames>
</Storyboard>
<Style x:Key="MyItemContainerStyle">
<Style.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource MyStoryboard2}"/>
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="ABCDEF"
ItemTemplate="{StaticResource MyButtonTemplate}"
ItemContainerStyle="{StaticResource MyItemContainerStyle}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>

Set ListBoxItem.IsSelected when child TextBox is Focused

I have a typical MVVM scenario:
I have a ListBox that is binded to a List of StepsViewModels.
I define a DataTemplate so that StepViewModels are rendered as StepViews.
The StepView UserControl have a set of labels and TextBoxs.
What I want to do is to select the ListBoxItem that is wrapping the StepView when a textBox is focused. I've tried to create a style for my TextBoxs with the following trigger:
<Trigger Property="IsFocused" Value="true">
<Setter TargetName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Property="IsSelected" Value="True"/>
</Trigger>
But I get an error telling me that TextBoxs don't have an IsSelected property. I now that but the Target is a ListBoxItem.
How can I make it work?
There is a read-only property IsKeyboardFocusWithin that will be set to true if any child is focused. You can use this to set ListBoxItem.IsSelected in a Trigger:
<ListBox ItemsSource="{Binding SomeCollection}" HorizontalAlignment="Left">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Width="100" Margin="5" Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As Jordan0Day correctly pointed out there can be indeed big problems using IsKeyboardFocusWithin solution. In my case a Button in a Toolbar which regards to the ListBox was also not working anymore. The same problem with focus. When clicking the button the ListBoxItem does loose the Focus and the Button updated its CanExecute method, which resulted in disabling the button just a moment before the button click command should be executed.
For me a much better solution was to use a ItemContainerStyle EventSetter as described in this post: ListboxItem selection when the controls inside are used
XAML:
<Style x:Key="MyItemContainer.Style" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="LightGray"/>
<EventSetter Event="GotKeyboardFocus" Handler="OnListBoxItemContainerFocused" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="backgroundBorder" Background="White">
<ContentPresenter Content="{TemplateBinding Content}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="backgroundBorder" Property="Background" Value="#FFD7E6FC"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
EventHandler in the code behind of the view:
private void OnListBoxItemContainerFocused(object sender, RoutedEventArgs e)
{
(sender as ListBoxItem).IsSelected = true;
}
One way to achieve that is by implementing a custom behavior using an attached property. Basically, the attached property would be applied to the ListBoxItem using a style, and would hook up to their GotFocus event. That even fires if any descendant of the control gets the focus, so it is suitable for this task. In the event handler, IsSelected is set to true.
I wrote up a small example for you:
The Behavior Class:
public class MyBehavior
{
public static bool GetSelectOnDescendantFocus(DependencyObject obj)
{
return (bool)obj.GetValue(SelectOnDescendantFocusProperty);
}
public static void SetSelectOnDescendantFocus(
DependencyObject obj, bool value)
{
obj.SetValue(SelectOnDescendantFocusProperty, value);
}
public static readonly DependencyProperty SelectOnDescendantFocusProperty =
DependencyProperty.RegisterAttached(
"SelectOnDescendantFocus",
typeof(bool),
typeof(MyBehavior),
new UIPropertyMetadata(false, OnSelectOnDescendantFocusChanged));
static void OnSelectOnDescendantFocusChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ListBoxItem lbi = d as ListBoxItem;
if (lbi == null) return;
bool ov = (bool)e.OldValue;
bool nv = (bool)e.NewValue;
if (ov == nv) return;
if (nv)
{
lbi.GotFocus += lbi_GotFocus;
}
else
{
lbi.GotFocus -= lbi_GotFocus;
}
}
static void lbi_GotFocus(object sender, RoutedEventArgs e)
{
ListBoxItem lbi = sender as ListBoxItem;
lbi.IsSelected = true;
}
}
The Window XAML:
<Window x:Class="q2960098.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:q2960098">
<Window.Resources>
<DataTemplate x:Key="UserControlItemTemplate">
<Border BorderBrush="Black" BorderThickness="5" Margin="10">
<my:UserControl1/>
</Border>
</DataTemplate>
<XmlDataProvider x:Key="data">
<x:XData>
<test xmlns="">
<item a1="1" a2="2" a3="3" a4="4">a</item>
<item a1="a" a2="b" a3="c" a4="d">b</item>
<item a1="A" a2="B" a3="C" a4="D">c</item>
</test>
</x:XData>
</XmlDataProvider>
<Style x:Key="MyBehaviorStyle" TargetType="ListBoxItem">
<Setter Property="my:MyBehavior.SelectOnDescendantFocus" Value="True"/>
</Style>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource UserControlItemTemplate}"
ItemsSource="{Binding Source={StaticResource data}, XPath=//item}"
HorizontalContentAlignment="Stretch"
ItemContainerStyle="{StaticResource MyBehaviorStyle}">
</ListBox>
</Grid>
</Window>
The User Control XAML:
<UserControl x:Class="q2960098.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UniformGrid>
<TextBox Margin="10" Text="{Binding XPath=#a1}"/>
<TextBox Margin="10" Text="{Binding XPath=#a2}"/>
<TextBox Margin="10" Text="{Binding XPath=#a3}"/>
<TextBox Margin="10" Text="{Binding XPath=#a4}"/>
</UniformGrid>
</UserControl>
If you create a User Control and then use it as the DataTemplate It seems to work cleaner.
Then you don't have to use the dirty Style Triggers that Don't work 100% of the time.
Edit: Someone else already had the same answer on a different question: https://stackoverflow.com/a/7555852/2484737
Continuing on Maexs' answer, using an EventTrigger instead of an EventSetter removes the need for code-behind:
<Style.Triggers>
<EventTrigger RoutedEvent="GotKeyboardFocus">
<BeginStoryboard>
<Storyboard >
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected" >
<DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:0"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>

Resources