TemplateStudio icon indicating copy to clipboard operation
TemplateStudio copied to clipboard

ListViewItemSelectionBehavior throws System.InvalidCastException when you click next to visible ListViewItem

Open wdefender opened this issue 2 years ago • 2 comments

Describe the bug

ListViewItemSelectionBehavior throws System.InvalidCastException when you click next to visible ListViewItem

To Reproduce Steps to reproduce the behavior:

  1. Create an application which contains ContentGridPage using Windows Template Studio.
  2. Launch the application.
  3. Navigate to ContentGrid.
  4. Click anywhere on empy area of the ContentGridPage.
  5. See error.

Expected behavior

ListViewItemSelectionBehavior does not throw any exceptions.

Screenshots

Additional context

Tested on WPF, Debug, AnyCPU.

wdefender avatar Oct 28 '21 08:10 wdefender

confirmed with the following.

    <genTemplate:Metadata>
        <genTemplate:Item Name="generator" Value="Windows Template Studio"/>
        <genTemplate:Item Name="wizardVersion" Version="v4.1.21179.1" />
        <genTemplate:Item Name="templatesVersion" Version="v4.1.21179.1" />
        <genTemplate:Item Name="projectType" Value="SplitView" />
        <genTemplate:Item Name="framework" Value="MVVMToolkit" />
        <genTemplate:Item Name="platform" Value="Wpf" />
    </genTemplate:Metadata>

Does not happen with CodeBehind.

mrlacey avatar Oct 30 '21 16:10 mrlacey

There are a few ways to address this. Just catching and suppressing the error is probably the simplest.

A slightly better way to handle this is to check the type of the DataContext of the selectedItem matches the type required by the command. Doing this requires a bit of reflection, so:

    private void SelectItem(RoutedEventArgs args)
    {
        if (Command != null
            && args.OriginalSource is FrameworkElement selectedItem
            && Command.CanExecute(selectedItem.DataContext))
        {
            Command.Execute(selectedItem.DataContext);
        }
    }

becomes

    private void SelectItem(RoutedEventArgs args)
    {
        if (Command != null
            && args.OriginalSource is FrameworkElement selectedItem
+           && selectedItem.DataContext.GetType() == Command.GetType().GetGenericArguments()[0]
            && Command.CanExecute(selectedItem.DataContext))
        {
            Command.Execute(selectedItem.DataContext);
        }
    }

As a workaround, this doesn't account for non-generic types and so if the app needed to handle those too, they'd need to be considered as well.

The reason this is necessary is because the original code didn't appreciate that SelectItem will be called on whichever control in the UI stack is under the mouse when the left button is clicked. The naming of the variable suggests that it assumes the click will only ever be on an item but this issue highlights that this might not always be the case. A different approach to the above would be to check that the source of the item passed to SelectItem isn't the container of the items. So:

    private void SelectItem(RoutedEventArgs args)
    {
        if (Command != null
+           && !(args.OriginalSource is ScrollViewer)
            && args.OriginalSource is FrameworkElement selectedItem
            && Command.CanExecute(selectedItem.DataContext))
        {
            Command.Execute(selectedItem.DataContext);
        }
    }

This second option is probably a better solution for implementing in the actual templates, but I'm open to discussion of options.

mrlacey avatar Nov 01 '21 16:11 mrlacey