0

I have a WinUI 3 ListView that displays a list of items. Every item has a ToggleSwitch and a Expander. When i click on the ToggleSwitch or the Expander the ListView selection does not change.

I found some solutions for WPF but they dont work in WinUI 3:

Selecting a Textbox Item in a Listbox does not change the selected item of the listbox

How can I do this for WinUI 3 so that the associated ListViewItem is selected when the ToggleSwitch or Expander is selected?

Effektor
  • 11
  • 3

2 Answers2

0

You could handle the Tapped event for the Expander and ToggleSwitch and programmatically set the SelectedItem property of the ListView:

private void OnTapped(object sender, TappedRoutedEventArgs e)
{
    FrameworkElement element = (FrameworkElement)sender;
    lv.SelectedItem = element.DataContext;
}

XAML:

<ListView x:Name="lv">
    <ListView.ItemTemplate>
        <DataTemplate>
            ...
            <Expander Tapped="OnTapped" ... />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
mm8
  • 163,881
  • 10
  • 57
  • 88
  • I tried this first but this works with the ``Expander`` but not with the ``ToggleSwitch``. The ``Tapped`` event won't be fired even with ``IsTapEnabled`` set to ``true``. Since ``RightTapped`` and ``DoubleTapped`` works, I guess this is a **WinUI** bug. I'll create an issue in the repo – Andrew KeepCoding Sep 07 '22 at 22:06
-1

If you don't want to change selection when you do something programmatically, you can do it this way.

.xaml

<StackPanel>
    <Button
        Command="{x:Bind ViewModel.TestCommand}"
        Content="Click" />
    <ListView
        x:Name="ListViewControl"
        ItemsSource="{x:Bind ViewModel.Items}"
        SelectionMode="Single">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="local:Item">
                <StackPanel>
                    <ToggleSwitch Toggled="ToggleSwitch_Toggled" />
                    <Expander Expanding="Expander_Expanding" IsExpanded="{x:Bind IsChecked, Mode=OneWay}" />
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</StackPanel>

.xaml.cs

private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
    if (ViewModel.IsProgrammatical is false)
    {
        ListViewControl.SelectedItem = (sender as ToggleSwitch)?.DataContext;
    }
}

private void Expander_Expanding(Expander sender, ExpanderExpandingEventArgs args)
{
    if (ViewModel.IsProgrammatical is false)
    {
        ListViewControl.SelectedItem = sender.DataContext;
    }
}

ViewModel.cs

public partial class Item : ObservableObject
{
    [ObservableProperty]
    private string text = string.Empty;
    [ObservableProperty]
    private bool isChecked;
}

public partial class MainWindowViewModel : ObservableObject
{
    public bool IsProgrammatical { get; set; }

    [ObservableProperty]
    private List<Item> items = new()
    {
        { new Item() { Text = "A", IsChecked = false,} },
        { new Item() { Text = "B", IsChecked = false,} },
        { new Item() { Text = "C", IsChecked = false,} },
    };

    [RelayCommand]
    private void Test()
    {
        IsProgrammatical = true;
        Items[1].IsChecked = !Items[1].IsChecked;
        IsProgrammatical = false;
    }
}

Workaround

In this case, the source collection is untouchable and we can't use a flag if the property was changed programmatically or not, we need to use the Tapped event to make the item selected. But unfortunately, the ToggleSwitch's Tapped event is not fired (at least in my environment). Might be a WinUI bug (issue posted here).

As a workaround, at least until this bug gets fixed, you can use the ToggleButton. I tested it out and the Tapped event is fired.

<DataTemplate x:DataType="local:Item">
    <StackPanel>
        <ToggleButton Tapped="ToggleButton_Tapped" />
        <Expander Tapped="Expander_Tapped" IsExpanded="{x:Bind IsChecked, Mode=OneWay}" />
    </StackPanel>
</DataTemplate>
private void ToggleButton_Tapped(object sender, TappedRoutedEventArgs e)
{
    ListViewControl.SelectedItem = (sender as ToggleSwitch)?.DataContext;
}

private void Expander_Tapped(Expander sender, TappedRoutedEventArgs e)
{
    ListViewControl.SelectedItem = sender.DataContext;
}
Andrew KeepCoding
  • 7,040
  • 2
  • 14
  • 21
  • Unfortunately, this solution does not work as intended. When i programmatically expand an Expander or programmatically change the state of a ToggleSwitch, the respective event is triggered and the ListViewItem is selected. But it should only be selected by the user. – Effektor Sep 07 '22 at 01:36
  • Sorry, forgot to mention that I'm using the CommunityTookit.Mvvm to implement this sample, but it's irrelevant. – Andrew KeepCoding Sep 07 '22 at 02:07
  • This looks unnecessarily complicated to me. My ListView is bound to an external ObservableCollection and automatically updates the contents as needed. So I don't know how to set the flag IsProgrammatical, as you suggested, if the ListView is automatically updated by the bound collection. – Effektor Sep 07 '22 at 15:42
  • Then you should go with @mm8's solution but unfortunately the ``ToggleSwitch``'s ``Tapped`` event is not fired (at least in my environment). – Andrew KeepCoding Sep 07 '22 at 22:14
  • I found a walkaround and updated my answer. – Andrew KeepCoding Sep 07 '22 at 22:39
  • The workaround seems to work, thanks! – Effektor Sep 10 '22 at 00:01