C# WPF ListView SelectedItem keep value on lost focus

43 Views Asked by At

Problem: Losing ListView Item focus and as a consequence reset of SelectedItem property when I try to push button on another panel different from panel with ListView. I need to keep focus (optional) and keep SelectedItem value enter image description here If I click "Test1" value in red border is right. But after click "Test3" it reset. Problem in styling of ListView. If remove trigger in ListControl.xaml (see full code below)

<Trigger Property="IsKeyboardFocusWithin" Value="True">
    <Setter Property="IsSelected" Value="True" />
</Trigger>

this problem will be resolved. But in this case pushing to "Test1" & "Test2" buttons not focused row and not set SelectedItem value.

MainWindow.xaml:

<Window x:Class="ListViewSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:localVM="clr-namespace:ListViewSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <localVM:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <localVM:ListControl></localVM:ListControl>
    </Grid>
</Window>

ListControl.xaml:

<UserControl x:Class="ListViewSample.ListControl"
             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" 
             xmlns:local="clr-namespace:ListViewSample"
             d:DataContext="{d:DesignInstance Type=local:MainViewModel}"
             mc:Ignorable="d" 
             d:DesignHeight="450"
             d:DesignWidth="500">
    <UserControl.Resources>
        <SolidColorBrush x:Key="WindowBackground" Color="White" Opacity="0.75"/>
        <Style x:Key="ListViewItemStyle" TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Left" />
            <Style.Triggers>
                <Trigger Property="IsKeyboardFocusWithin" Value="True">
                    <Setter Property="IsSelected" Value="True" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <ListView Grid.Row="0" ItemsSource="{Binding ParametersList}" SelectedItem="{Binding SelectedItem}"  ItemContainerStyle="{StaticResource ListViewItemStyle}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <Button Content="Test1" Margin="5" Height="30" Width="50" Command="{Binding PushButtonPnl}" />
                        <Button Content="Test2" Margin="5" Height="30" Width="50" Command="{Binding PushButtonPnl}" />
                        <TextBlock Text="{Binding Id}" />
                        <TextBlock Text="{Binding Name}" />
                        <TextBlock Text="{Binding Value}" />
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <Border x:Name="stkEasyLocatePanel" Grid.Row="0" Margin="5" Padding="5" BorderBrush="Black" BorderThickness="1,1,1,1" CornerRadius="5" Background="{DynamicResource WindowBackground}" Width="400" HorizontalAlignment="Right" >
            <StackPanel>
                <Border Margin="5" Padding="5" BorderBrush="Red" BorderThickness="1,1,1,1" CornerRadius="5">
                    <TextBlock Text="{Binding SelectedItem.Value}" FontSize="40" Foreground="Red" VerticalAlignment="Center"/>
                </Border>
                <StackPanel Orientation="Horizontal">
                    <Ellipse Margin="5" Height="30" Width="30" HorizontalAlignment="Left" VerticalAlignment="Center" Stroke="Black" Fill="Red"/>
                    <Ellipse Margin="5" Height="30" Width="30" HorizontalAlignment="Left" VerticalAlignment="Center" Stroke="Black" Fill="Orange"/>
                    <Ellipse Margin="5" Height="30" Width="30" HorizontalAlignment="Left" VerticalAlignment="Center" Stroke="Black" Fill="Yellow"/>
                    <Ellipse Margin="5" Height="30" Width="30" HorizontalAlignment="Left" VerticalAlignment="Center" Stroke="Black" Fill="Green"/>
                    <Ellipse Margin="5" Height="30" Width="30" HorizontalAlignment="Left" VerticalAlignment="Center" Stroke="Black" Fill="LightBlue"/>
                    <Ellipse Margin="5" Height="30" Width="30" HorizontalAlignment="Left" VerticalAlignment="Center" Stroke="Black" Fill="Blue"/>
                    <Ellipse Margin="5" Height="30" Width="30" HorizontalAlignment="Left" VerticalAlignment="Center" Stroke="Black" Fill="Violet"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <Button Content="Test3" Margin="5" Height="30" Width="50" Command="{Binding PushButtonPnl}" />
                </StackPanel>
            </StackPanel>
        </Border>

        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Button Content="Add" Margin="5" Height="30" Width="50" Command="{Binding PushButton}"/>
            <ToggleButton Content="&gt;&gt;" Margin="5" Height="30" Width="50">
                <ToggleButton.Triggers>
                    <EventTrigger RoutedEvent="ToggleButton.Unchecked">
                        <BeginStoryboard>
                            <Storyboard x:Name="HideStackPanel">
                                <DoubleAnimation Storyboard.TargetName="stkEasyLocatePanel" Storyboard.TargetProperty="Width" From="0" To="400" Duration="0:0:1.0">
                                    <DoubleAnimation.EasingFunction>
                                        <PowerEase EasingMode="EaseIn" />
                                    </DoubleAnimation.EasingFunction>
                                </DoubleAnimation>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Content">
                                    <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="&gt;&gt;" />
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="ToggleButton.Checked">
                        <BeginStoryboard>
                            <Storyboard x:Name="ShowStackPanel">
                                <DoubleAnimation Storyboard.TargetName="stkEasyLocatePanel" Storyboard.TargetProperty="Width" From="400" To="0" Duration="0:0:1.0">
                                    <DoubleAnimation.EasingFunction>
                                        <PowerEase EasingMode="EaseIn" />
                                    </DoubleAnimation.EasingFunction>
                                </DoubleAnimation>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Content">
                                    <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="&lt;&lt;" />
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </ToggleButton.Triggers>
            </ToggleButton>
        </StackPanel>
    </Grid>
</UserControl>

MainViewModel.cs:

using System.Collections.ObjectModel;
using System.Windows;
using Prism.Commands;
using Prism.Mvvm;

namespace ListViewSample
{
    public class ParameterItem
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public double Value { get; set; }

        public ParameterItem(int id, string name, double value)
        {
            Id = id;
            Name = name;
            Value = value;
        }
    }

    public class MainViewModel : BindableBase
    {
        int idx = 0;
        double vle = 123.45d;

        public ObservableCollection<ParameterItem> ParametersList { get; set; }

        private ParameterItem _selectedItem;
        public ParameterItem SelectedItem
        {
            get => _selectedItem;
            set
            { 
                SetProperty(ref _selectedItem, value, nameof(SelectedItem));
            }
        }

        public DelegateCommand PushButton { get; }
        public DelegateCommand PushButtonPnl { get; set; }

        public MainViewModel() 
        {
            ParametersList = new();

            PushButton = new DelegateCommand(() => 
            {
                Something();
            });

            Something();

            PushButtonPnl = new DelegateCommand(() =>
            {
                var txt = _selectedItem == null ? "NULL" : _selectedItem.Value.ToString();
                MessageBox.Show(txt);
            });
        }

        private void Something()
        {
            ParametersList.Add(new ParameterItem(idx++, $"Par{idx}", vle * idx));
        }
    }
}
1

There are 1 best solutions below

0
BionicCode On

When the trigger becomes invalid it will restore the original value of the ListBoxItem.IsSelected property, which was false.

The default behavior is already correct. Focus moves away but the item remains selected. This visual state is known as "SelectedUnfocused" (see ListBoxItem States).

To keep the visual selected feedback effect you must override the ControlTemplate to implement the "SelectedUnfocused" state animation (recommended) or implement the selected trigger new (as this will remove the default triggers and therefore also remove the "SelectedUnfocused" visual state).

For example:

<ListBox>
  <ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="ListBoxItem">
            <Border Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Padding="{TemplateBinding Padding}">
              <ContentPresenter />
            </Border>

            <ControlTemplate.Triggers>
              <Trigger Property="IsMouseOver"
                       Value="True">
                <Setter Property="Background">
                  <Setter.Value>
                    <SolidColorBrush Color="{x:Static SystemColors.HighlightColor}"
                                     Opacity="0.1" />
                  </Setter.Value>
                </Setter>
                <Setter Property="BorderBrush">
                  <Setter.Value>
                    <SolidColorBrush Color="{x:Static SystemColors.HighlightColor}"
                                     Opacity="0.4" />
                  </Setter.Value>
                </Setter>
              </Trigger>
              <Trigger Property="IsSelected"
                       Value="True">
                <Setter Property="Background">
                  <Setter.Value>
                    <SolidColorBrush Color="{x:Static SystemColors.HighlightColor}"
                                     Opacity="0.2" />
                  </Setter.Value>
                </Setter>
                <Setter Property="BorderBrush">
                  <Setter.Value>
                    <SolidColorBrush Color="{x:Static SystemColors.HighlightColor}"
                                     Opacity="0.6" />
                  </Setter.Value>
                </Setter>
              </Trigger>
              <Trigger Property="IsEnabled"
                       Value="False">
                <Setter Property="Foreground"
                        Value="{x:Static SystemColors.GrayTextBrush}" />
              </Trigger>
            </ControlTemplate.Triggers>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </ListBox.ItemContainerStyle>
</ListBox>