I ran into a strange issue...
It looks like resizing Grid's columns using a GridSplitter disables (or otherwise deactivates) the trigger defined on a Grid's column.
Here's my setup:
A Grid has 3 columns, defined as follows:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition>
<ColumnDefinition.Style>
<Style>
<Setter Property="ColumnDefinition.Width" Value="Auto"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=OpenItemViewModels.Count}" Value="0">
<Setter Property="ColumnDefinition.Width" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<ColumnDefinition>
<ColumnDefinition.Style>
<Style>
<Setter Property="ColumnDefinition.Width" Value="4*"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=OpenItemViewModels.Count}" Value="0">
<Setter Property="ColumnDefinition.Width" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
</Grid.ColumnDefinitions>
The expectation is that when there are no items that constitute ItemsSource for the control in the third column, 0 width will be assigned to the second and third columns (hosting the GridSplitter and the auxiliary items control, respectively).
This works well as long as I don't touch the Splitter (when all the tabs in the auxiliary control are closed, only the first column remains visible).
The problems start if I move the splitter, thus effectively changing the proportion between columns ##0 and 2. In such scenario, these columns' width is not reset when all the items in the right-hand control are closed.
I suspect this has something to do with the GridSplitter "overruling" my definitions in XAML.
Can someone please confirm / disprove this theory, and suggest how to work around the problem?
I had the same problem for rowdefinition. Gridsplitter will override whatever we give in style or setters. It can be solved using animation (since animation has the highest priority in dependency property value resolution). Do the same for the third column.
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition>
<ColumnDefinition.Style>
<Style>
<Setter Property="ColumnDefinition.Width" Value="Auto" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=OpenItemViewModels.Count}" Value="0">
<DataTrigger.EnterActions>
<BeginStoryboard Name="BeginStoryboard1">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Width">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{x:Static GridLength.Auto}" />
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="BeginStoryboard1" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
I came up with a helper class that helps solve the problem of collapsible columns/rows that are also resized with GridSplitter.
public class CollapsibleRowDefinition : RowDefinition
{
public static readonly DependencyProperty IsCollapsedProperty = DependencyProperty.Register(
"IsCollapsed",
typeof(bool),
typeof(CollapsibleRowDefinition),
new FrameworkPropertyMetadata(
false,
(s,e) => { ((CollapsibleRowDefinition) s).IsCollapsed = (bool)e.NewValue; }));
private bool isCollapsed = false;
public CollapsibleRowDefinition()
{
DependencyPropertyDescriptor.FromProperty(RowDefinition.HeightProperty, typeof(RowDefinition)).AddValueChanged(this,
(sender, args) =>
{
if (!this.IsCollapsed)
{
this.ExpandedHeight = this.Height;
}
});
}
public GridLength CollapsedHeight { get; set; }
public GridLength ExpandedHeight { get; set; }
public bool IsCollapsed
{
get { return this.isCollapsed; }
set
{
if (this.isCollapsed != value)
{
this.isCollapsed = value;
this.Height = value ? this.CollapsedHeight : this.ExpandedHeight;
}
}
}
}
markup then goes like this
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="5"/>
<c:CollapsibleRowDefinition CollapsedHeight="20" ExpandedHeight="*" IsCollapsed="{Binding ElementName=Btn_BottomCollapse, Path=IsChecked}"/>
</Grid.RowDefinitions>
<GridSplitter Grid.Row="2" HorizontalAlignment="Stretch"
IsEnabled="{Binding ElementName=Btn_BottomCollapse, Path=IsChecked}"/>
I had the same problem...
The only thing I was able to work out was something like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" x:Name="theColumn"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Expander Grid.Column="0" x:Name="theExpander" Expander.Collapsed="theExpander_Collapsed">
...
</Expander>
<GridSplitter Grid.Column="0" HorizontalAlignment="Right" Width="5">
<GridSplitter.Style>
<Style TargetType="{x:Type GridSplitter}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=theExpander, Path=IsExpanded}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</GridSplitter.Style>
</GridSplitter>
<Grid Grid.Column="1">
...
</Grid>
</Grid>
And the code behind:
private void theExpander_Collapsed(object sender, RoutedEventArgs e)
{
theColumn.Width = GridLength.Auto;
}
It's not the way I would prefer to do this, but trying to use a style trigger on the column definition just does not work.
I took Terrence's solution (comment in Ghostriders answer) and fixed a little problem that the default values were sometimes not correctly applied.
The source can be found here: https://gist.github.com/medarion/5ff6d04be5630748e8bf92006d0b4472
Usage is unchanged:
<Grid.ColumnDefinitions>
<controls:CollapsibleColumnDefinition
CollapsedWidth="0" ExpandedWidth="500" IsExpanded="{Binding HeaderVisible}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
Related
I have a Grid with three columns - left, right, and a grid splitter in between. I need to hide one of the panels. What I need is to set Width=0 for two Splitter columns, and for the Panel column. However, this approach works well only from code. When I use styles or value converters, it works only in some cases.
Everything works as expeted until I move the splitter, panels hide but leave white space (for the case #2/Styles), or doesn't hide one of the panels (for case #3/IValueConverter). Only the "code behind" way works correctly in all cases. The GIF image below illustrates the behavior.
The code is shown below. The main idea is to set Width and MaxWidth properties for grid colums to 0, and then back to * for the panel, and to Auto for Splitter.
1. The way it works (code behind):
private void TogglePanelVisibility(bool isVisible)
{
if (isVisible)
{
// Restore saved parameters:
_mainWindow.ColumnPanel.Width = new GridLength(_columnPanelWidth, GridUnitType.Star);
_mainWindow.ColumnPanel.MaxWidth = double.PositiveInfinity;
_mainWindow.ColumnSplitter.Width = new GridLength(_columnSplitterWidth, GridUnitType.Auto);
_mainWindow.ColumnSplitter.MaxWidth = double.PositiveInfinity;
return;
}
// Save parameters:
_columnSplitterWidth = _mainWindow.ColumnSplitter.Width.Value;
_columnPanelWidth = _mainWindow.ColumnPanel.Width.Value;
// Hide panel:
_mainWindow.ColumnPanel.Width = new GridLength(0);
_mainWindow.ColumnPanel.MaxWidth = 0;
_mainWindow.ColumnSplitter.Width = new GridLength(0);
_mainWindow.ColumnSplitter.MaxWidth = 0;
}
2. The way it doesn't work (XAML Style)
<Window.Resources>
<Style x:Key="showColumnStar" TargetType="{x:Type ColumnDefinition}">
<Style.Setters>
<Setter Property="Width" Value="*" />
<Setter Property="MaxWidth" Value="{x:Static system:Double.PositiveInfinity}" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding IsPanelVisible}" Value="False">
<DataTrigger.Setters>
<Setter Property="Width" Value="0" />
<Setter Property="MaxWidth" Value="0" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="showColumnAuto" TargetType="{x:Type ColumnDefinition}">
<Style.Setters>
<Setter Property="Width" Value="Auto" />
<Setter Property="MaxWidth" Value="{x:Static system:Double.PositiveInfinity}" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding IsPanelVisible}" Value="False">
<DataTrigger.Setters>
<Setter Property="Width" Value="0" />
<Setter Property="MaxWidth" Value="0" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- ... -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" Style="{StaticResource showColumnAuto}" />
<ColumnDefinition Width="*" Style="{StaticResource showColumnStar}" />
</Grid.ColumnDefinitions>
<!-- ... -->
</Grid>
3. And the last scenario using value converters
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="{Binding IsPanelVisible, Converter={StaticResource BoolToGridSizeConverter}, ConverterParameter='Auto'}" />
<ColumnDefinition Width="{Binding IsPanelVisible, Converter={StaticResource BoolToGridSizeConverter}, ConverterParameter='*'}" />
</Grid.ColumnDefinitions>
<!-- ... -->
</Grid>
C#:
internal class BoolToGridRowColumnSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var param = parameter as string;
var unitType = GridUnitType.Star;
if (param != null && string.Compare(param, "Auto", StringComparison.InvariantCultureIgnoreCase) == 0)
{
unitType = GridUnitType.Auto;
}
return ((bool)value == true) ? new GridLength(1, unitType) : new GridLength(0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
There is one more thing I learning from debugging the case #3 - on an attempt to show or hide the panel after moving the splitter, the Converted is calling only once.
Could you please tell me what is happening, why the cases #2 and #3 do not work properly?
The full source code of the demo project are on GitHub.
Thank you in advance!
Could you please tell me what is happening, why the cases #2 and #3 do not work properly?
Because the GridSplitter sets the local value of the Width property of the ColumnDefinition and local values always take precedence over style setter values as explained in the docs.
So you have to set the local value of the Width property like you do in the code-behind.
I cloned your Code:
as for #2:
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" Style="{StaticResource showColumnAuto}" />
<ColumnDefinition Width="Auto" Style="{StaticResource showColumnStar}" />
</Grid.ColumnDefinitions>
setting the 3rd column to "Auto" did the trick
for #3:
if you debug your solution and set a breakpoint in your converter, you will see, that after moving your Splitter in the 3rd block, the converter is only reached once, so for the column with the splitter. Moving the splitter destroys your Width-binding on the 3rd Column (as #mm8 pointed out in his answer).
so either you manage to redefine your Binding after the splitter moved (e.g. PreviewMouseLeftButtonUp event) or just let this approach go.
What i want to do is collapse the bottom section of the WPF ui based on the checkbox toggle. This mostly works as expected so far. Below as you step through the images you'll see the collapse stops working once the grid splitter is moved. I do not understand why or how to fix this.
Start of the application, appears correct.
Toggle the checkbox and the botttom section disappears as expected.
Toggle the checkbox once more and then move the splitter vertically, everything appears as expected.
Now toggle the checkbox one last time and you'll notice the top section doesn't fill the application like it once did before. This is where it appears broken! I would expect the yellow section to fill the UI like it did in the initial toggle.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="250" Width="525"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<CheckBox Name="ToggleVisibility" Margin="10" IsChecked="True" Content="Toggle Bottom Section"></CheckBox>
<StackPanel Background="#feca00" Grid.Row="1">
<TextBlock FontSize="20" Foreground="#58290A">Top Section</TextBlock>
</StackPanel>
<GridSplitter Grid.Row="2" Height="5" HorizontalAlignment="Stretch">
<GridSplitter.Style>
<Style TargetType="GridSplitter">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleVisibility, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</GridSplitter.Style>
</GridSplitter>
<StackPanel Grid.Row="3" Background="LightBlue">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleVisibility, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock FontSize="20" Foreground="black">Bottom Section</TextBlock>
</StackPanel>
</Grid>
</Window>
The reason why this happens is that when you move the splitter it overwrites the Height property of the last RowDefinition and sets it to actual height of whatever is presented in the according row of the Grid - the second StackPanel (bottom section) in your case. It no longer is set to Auto, so even if you collapse the bottom section the row maintains it's height - hence the empty space.
I sometimes find myself in a similar situation, and what I do then is to span the control, which I want to take up all the space, across the remaining rows using Grid.RowSpan attached property. In your case you could achieve it by applying the following style to your first StackPanel (top section):
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=ToggleVisibility}" Value="False">
<Setter Property="Grid.RowSpan" Value="3" />
</DataTrigger>
</Style.Triggers>
</Style>
Then the StackPanel will span to the bottom of the Grid regardless of the following rows' heights.
EDIT
Since you're not satisfied with spanning the control, here's another solution: derive from RowDefinition class and wire your own visibility logic. Here's an example:
public class MyRowDefinition : RowDefinition
{
static MyRowDefinition()
{
HeightProperty.OverrideMetadata(
typeof(MyRowDefinition),
new FrameworkPropertyMetadata
{
CoerceValueCallback = CoerceHeightPropertyValue
});
}
private static object CoerceHeightPropertyValue(DependencyObject d, object baseValue)
{
if (Equals(d.GetValue(IsVisibleProperty), false))
return new GridLength(0d);
else
return baseValue;
}
public bool IsVisible
{
get { return (bool)GetValue(IsVisibleProperty); }
set { SetValue(IsVisibleProperty, value); }
}
public static readonly DependencyProperty IsVisibleProperty =
DependencyProperty.Register(
"IsVisible",
typeof(bool),
typeof(MyRowDefinition),
new FrameworkPropertyMetadata
{
DefaultValue = true,
PropertyChangedCallback = IsVisiblePropertyChanged
});
private static void IsVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.InvalidateProperty(RowDefinition.HeightProperty);
}
}
Then use that class instead of RowDefinition in your grid and bind the IsVisible property to the check state of the checkbox.
It looks like the StackPanel and the GridSplitter are collapsed, but that Grid.Row 3 is not. I'm not sure why the Auto height does no close it out. EDIT: I see that #Grx70 has explained it.
This: Hide grid row in WPF
suggests that setting all children to Visibility.Collapsed ought to work, but you have the option of setting the height of Grid.Row 3 to 0.
I think the Height="5" of your GridSplitter might also keep it taking up space, taking precedence over the style setting (it is collapsed and gets Height 0 from that, but gets Height 5 from the Height="5", which wins (but it is still invisible). It might be better to but that in the style too:
<Setter Property="Height" Value="5" />
Once the gridsplitter is used to resize a grid the row * will not reclaim the space when the other rows are collapsed.
I have the following grid in a master detail view with three rows. A data grid on top a splitter in the middle and a contentcontrol view in the last row. The splitter has a close button on it to collapse the detail. This all works with the exception that once the user resizes using the gridsplitter.
<Grid Margin="3,0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Style="{StaticResource CollapsableRow}"/><!-- Splitter Here -->
<RowDefinition Style="{StaticResource CollapsableRow}"/>
</Grid.RowDefinitions>
The GridSplitter style:
<Style x:Key="gridSplitterStyle" TargetType="{x:Type GridSplitter}">
<Setter Property="Visibility" Value="{Binding IsItemSelected, Converter={StaticResource BoolToShow},ConverterParameter='Visible|Collapsed'}" />
<Setter Property="Width" Value="Auto"/>
<Setter Property="Height" Value="14"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Border.BorderBrush" Value="#FF6593CF" />
<Setter Property="Border.BorderThickness" Value="0,1,0,0" />
<Setter Property="UIElement.SnapsToDevicePixels" Value="True" />
<Setter Property="UIElement.Focusable" Value="False" />
<Setter Property="Control.Padding" Value="7,7,7,7" />
<Setter Property="Cursor" Value="SizeNS" /></Style>
Like I said the collapse works correctly unless the gridsplitter is used to resize. After that the whitespace stays.
EDIT:
H.B. and codenaked had simple and consistant suggestions so and I attempted to implement them w/o success in a data trigger:
<Style x:Key="CollapsableRow" TargetType="{x:Type RowDefinition}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, Converter={StaticResource IsNullConverter}}" Value="True">
<Setter Property="RowDefinition.Height" Value="0"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem, Converter={StaticResource IsNullConverter}}" Value="False">
<Setter Property="RowDefinition.Height" Value="Auto"/>
</DataTrigger>
</Style.Triggers>
</Style>
Since the grid splitter and detail were already being hidden Visibility was the obvious choice to reset the next row definition height.
/// <summary>
/// Grid splitter that show or hides the following row when the visibility of the splitter is changed.
/// </summary>
public class HidableGridSplitter : GridSplitter {
GridLength height;
public HidableGridSplitter()
{
this.IsVisibleChanged += HideableGridSplitter_IsVisibleChanged;
this.Initialized += HideableGridSplitter_Initialized;
}
void HideableGridSplitter_Initialized(object sender, EventArgs e)
{
//Cache the initial RowDefinition height,
//so it is not always assumed to be "Auto"
Grid parent = base.Parent as Grid;
if (parent == null) return;
int rowIndex = Grid.GetRow(this);
if (rowIndex + 1 >= parent.RowDefinitions.Count) return;
var lastRow = parent.RowDefinitions[rowIndex + 1];
height = lastRow.Height;
}
void HideableGridSplitter_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Grid parent = base.Parent as Grid;
if (parent == null) return;
int rowIndex = Grid.GetRow(this);
if (rowIndex + 1 >= parent.RowDefinitions.Count) return;
var lastRow = parent.RowDefinitions[rowIndex + 1];
if (this.Visibility == Visibility.Visible)
{
lastRow.Height = height;
}
else
{
height = lastRow.Height;
lastRow.Height = new GridLength(0);
}
}
You can use animation to solve the row/column definition override by gridsplitter. See my answer to a similar question at GridSplitter overrides ColumnDefinition's style trigger?
If you use the GridSplitter the Heights are no longer Auto but concrete values. You need to manually change the values back either using a style or events and code behind, e.g. this resets a auto-size column on double-click:
private void ColumnSplitter_DoubleClick(object sender, MouseButtonEventArgs e)
{
if (!ColumnTreeView.Width.IsAuto) ColumnTreeView.Width = new GridLength();
}
Based on what you provided, the GridSplitter will resize the previous and next rows. You can see this in action with this code:
<Grid Margin="3,0">
<Grid.RowDefinitions>
<RowDefinition x:Name="row0" Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition x:Name="row2" Height="Auto" />
</Grid.RowDefinitions>
<Border Background="Red" >
<TextBlock Text="{Binding ElementName=row0, Path=Height}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<GridSplitter Grid.Row="1" Style="{StaticResource gridSplitterStyle}" HorizontalAlignment="Stretch" />
<Border Background="Blue" Grid.Row="2" MinHeight="50">
<TextBlock Text="{Binding ElementName=row2, Path=Height}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</Grid>
The last row's size will actually change from Auto to a fixed height. So even if you collapse the content in that row, it will still take up the specified space. You'd need to reset the row to Height="Auto" to truly collapse it with it's content.
I have a xaml grid defined as:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
The first column will contain a TextBlock and the second column a TextBox for data capture.
How can I toggle the visibility of first column?
The solution to my problem was to change the width of the first column to "Auto". Then I set up the bindings of my first textbox so that its Visibility property was set to Collapsed (not hidden) which results in the column not being rendered.
<ColumnDefinition>
<ColumnDefinition.Style>
<Style TargetType="ColumnDefinition">
<Setter Property="Width" Value="*" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsColumnVisible}" Value="False">
<Setter Property="Width" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
Please do implement INotifyPropertyChanged in your ViewModel
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;
}