WPFToolkit AutoCompleteBox non si associa correttamente all'interno di ListView

Aug 21 2020

Dopo aver faticato un po 'per iniziare con il controllo AutoCompleteBox di WPFToolkit , sto affrontando un altro problema quando provo a utilizzare un AutoCompleteBox all'interno di un ListView , si lega quasi perfettamente ma per un motivo lo ignoro all'inizio non mostra ValueMemberPathe invece ci prova per convertire l'oggetto in una stringa che fornisce Namespace.objectinvece del ValueMemberPathvalore corretto , tuttavia quando si seleziona un altro elemento nel riquadro di completamento automatico funziona perfettamente e non ne mostra altri Namespace.object.

Ecco il mio codice, puoi semplicemente copiarlo e incollarlo per ottenere lo stesso risultato (non dimenticare di aggiungere DotNetProjects.WpfToolkit.Input in 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-Behind (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;
    }
}

Risposte

thatguy Aug 21 2020 at 17:48

Per qualche motivo il cambio di selezione che aggiorna il testo in base a ValueMemberPathnon viene attivato quando si annidano i file AutoCompleteBoxin ListView. L' SelectionChangedevento non si accende nemmeno. Non sono riuscito a capire perché esattamente e se si tratta di un bug o meno. Tuttavia, posso mostrarti una soluzione alternativa usando il Microsoft.Xaml.Behaviors.Wpfpacchetto.

È possibile creare un'azione trigger che reimposta SelectedIteme la assegna di nuovo 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);
   }
}

Questa azione trigger può essere utilizzata per l' Loadedevento di AutoCompleteBox.

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

Ciò causerà un aggiornamento della selezione e del testo senza modificare la proprietà associata.