WPFToolkit AutoCompleteBox ne se lie pas correctement dans ListView

Aug 21 2020

Après avoir lutté un peu pour commencer avec le contrôle AutoCompleteBox de WPFToolkit , je suis confronté à un autre problème lorsque j'essaie d'utiliser une AutoCompleteBox dans un ListView , il se lie presque parfaitement, mais pour une raison que j'ignore, il ne s'affiche pas au début ValueMemberPathet essaie à la place pour convertir l'objet en chaîne qui donne Namespace.objectau lieu de la ValueMemberPathvaleur appropriée , cependant lors de la sélection d'un autre élément dans l'AutoCompleteBox cela fonctionne parfaitement et il n'en montre aucun autre Namespace.object.

Voici mon code, vous pouvez simplement le copier et le coller pour obtenir le même résultat (n'oubliez pas d'ajouter DotNetProjects.WpfToolkit.Input dans NuGet Package Manager) :

  • Namespace.MainWindow.xaml
<Window x:Class="Namespace.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:local="clr-namespace:Namespace"
        mc:Ignorable="d"
        xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=DotNetProjects.Input.Toolkit"
        Title="AutoCompleteBox in ListView" Height="300" Width="350" WindowStartupLocation="CenterScreen">
    
    <!-- Required Template to show the names of the Items in the ItemsList -->
    <Window.Resources>
        <DataTemplate x:Key="AutoCompleteBoxItemTemplate">
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Background="Transparent">
                <Label Content="{Binding Name}"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    
    <StackPanel Margin="5">
        <StackPanel Orientation="Horizontal" Margin="0 5 0 0">
            <StackPanel Width="{Binding ElementName=FirstColumnWidth, Path=ActualWidth}">
                <TextBlock Text="ACB binded to Cart.Item"/>
                
                <!-- ACB that binds correctly -->
                <toolkit:AutoCompleteBox 
                                  ItemsSource="{Binding Path=ItemsList}"
                                   ValueMemberPath="Name"
                                   SelectedItem="{Binding Path=Cart.Item, Mode=TwoWay}"
                                   ItemTemplate="{StaticResource AutoCompleteBoxItemTemplate}"/>
            </StackPanel>

            <StackPanel Margin="15 0 0 0">
                <TextBlock Text="Value of Cart.Item.Name"/>
                <TextBlock Text="{Binding Path=Cart.Item.Name}"/>
            </StackPanel>
        </StackPanel>

        <TextBlock Margin="0 30 0 0" HorizontalAlignment="Center" Text="ListView with CartsList as ItemsListSource"/>
        <ListView ItemsSource="{Binding CartsList}">
            <ListView.View>
                <GridView>
                    <GridViewColumn x:Name="FirstColumnWidth">
                        <GridViewColumn.Header>
                            <TextBlock Text="ACB binded to each Cart.Item"/>
                        </GridViewColumn.Header>
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <!-- ACB that doesn't bind correctly -->
                                <toolkit:AutoCompleteBox
                                  ItemsSource="{
                                        Binding RelativeSource={RelativeSource AncestorType=Window},
                                        Path=DataContext.ItemsList}"
                                   ValueMemberPath="Name"
                                   SelectedItem="{Binding Path=Item, Mode=TwoWay}"
                                   ItemTemplate="{StaticResource AutoCompleteBoxItemTemplate}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

                    <GridViewColumn >
                        <GridViewColumn.Header>
                            <TextBlock Text="Value of each Cart.Item.Name"/>
                        </GridViewColumn.Header>
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Path=Item.Name}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </StackPanel>
</Window>
  • Code derrière (MainWindow.xaml.cs)
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;

namespace Namespace
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        // INPC Implementation
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        // The list that contains Items that will be chosen in a Cart
        private ObservableCollection<Item> _ItemsList;
        public ObservableCollection<Item> ItemsList
        {
            get => _ItemsList;
            set
            {
                _ItemsList = value;
                OnPropertyChanged();
            }
        }

        // The list that contains Carts that will be shown in the ListView
        private ObservableCollection<Cart> _CartsList;
        public ObservableCollection<Cart> CartsList
        {
            get => _CartsList;
            set
            {
                _CartsList = value;
                OnPropertyChanged();
            }
        }

        // A signle Cart
        private Cart _Cart;
        public Cart Cart
        {
            get => _Cart;
            set
            {
                _Cart = value;
                OnPropertyChanged();
            }
        }

        public MainWindow()
        {
            DataContext = this;
            InitializeComponent();

            // Populating ItemsList
            ItemsList = new ObservableCollection<Item>()
            {
                new Item("T-shirt"), new Item("Jeans"), new Item("Boots"),
            };

            // Populating CartsList
            CartsList = new ObservableCollection<Cart>()
            {
                new Cart(ItemsList[0]),
                new Cart(ItemsList[2]),
                new Cart(ItemsList[1]),
                new Cart(ItemsList[0]),
                new Cart(ItemsList[1]),
            };

            // Setting an Item to Cart
            Cart = new Cart(ItemsList[2]);

        }
    }

    // Cart Object
    public class Cart
    {
        public Item Item { get; set; }

        public Cart(Item item) => Item = item;
    }

    // Item Object
    public class Item
    {
        // Important to be private set so it cannot be changed
        public string Name { get; private set; }

        public Item(string name) => Name = name;
    }
}

Réponses

thatguy Aug 21 2020 at 17:48

Pour une raison quelconque, le changement de sélection qui met à jour le texte en fonction du ValueMemberPathn'est pas déclenché lors de l'imbrication du AutoCompleteBoxdans le ListView. L' SelectionChangedévénement ne se déclenche même pas. Je ne pouvais pas comprendre pourquoi exactement et s'il s'agissait d'un bogue ou non. Cependant, je peux vous montrer une solution de contournement en utilisant le Microsoft.Xaml.Behaviors.Wpfpackage.

Vous pouvez créer une action de déclenchement qui réinitialise le SelectedItemet l'affecte à nouveau AutoCompleteBox.

public class ForceUpdateSelectedItemAction : TriggerAction<AutoCompleteBox>
{
   protected override void Invoke(object parameter)
   {
      var selectedItem = AssociatedObject.SelectedItem;
      AssociatedObject.SetCurrentValue(AutoCompleteBox.SelectedItemProperty, null);
      AssociatedObject.SetCurrentValue(AutoCompleteBox.SelectedItemProperty, selectedItem);
   }
}

Cette action de déclenchement peut être utilisée pour l' Loadedévénement du AutoCompleteBox.

<toolkit:AutoCompleteBox ...>
   <b:Interaction.Triggers>
      <b:EventTrigger EventName="Loaded">
         <local:ForceUpdateSelectedItemAction/>
      </b:EventTrigger>
   </b:Interaction.Triggers>
</toolkit:AutoCompleteBox>

Cela entraînera une mise à jour de la sélection et du texte sans modifier la propriété liée.