WinRTXAML Toolkit BindableSelection工作不正常

本文关键字:不正常 工作 BindableSelection Toolkit WinRTXAML | 更新日期: 2023-09-27 17:58:35

绑定ListView的SelectedItems属性时遇到问题。我在ViewModel中有一个属性,看起来像这样:

private ObservableCollection<string> _FilteredCountries;
    public ObservableCollection<string> FilteredCountries
    {
        get { return _FilteredCountries; }
        set
        {
            if (value != _FilteredCountries)
            {
                _FilteredCountries = value;
                OnPropertyChanged("FilteredCountries");
            }
        }
    }

在XAML中,我在弹出窗口中创建了这样的ListView:

<Popup>
   <ListView 
      ItemsSource="{Binding CountryList}"
      SelectionMode="Multiple"
      extensions:ListViewExtensions.BindableSelection="{Binding FilteredCountries, Mode=TwoWay}">
</Popup>

当我第一次打开弹出窗口并选择一些项目时,FilteredCountrys集合会更改并包含所选项目。但在我关闭弹出窗口并再次打开它以选择或取消选择更多项目后,FilteredCountrys集合不会改变,它与第一次之后保持不变。在我看来,绑定模式设置为OneTime,但事实并非如此。

WinRTXAML Toolkit BindableSelection工作不正常

有趣的是,我的大多数附加行为的实现方式似乎受到了一个bug的影响。基本上是为了避免泄露绑定到持久视图模型的视图-当控件卸载时,我会分离行为处理程序,但当控件的同一实例再次加载时(当弹出窗口重新打开时),它永远不会重新连接。我将需要修改我如何实施我的附加行为。此外,当第一次将列表与视图模型结合时,该行为假设选择为空,因此需要更新才能在您的场景中工作。

目前的解决方案是始终使用ListView的新实例,但也要使用行为的更新版本。要每次使用新的ListView实例,请执行以下操作:

<Popup x:Name="CountryListPopup" IsOpen="False" Grid.RowSpan="2" Grid.ColumnSpan="2">
    <Grid x:Name="CountryListPopupGrid" Background="#323232" Opacity="0.8">
        <Grid.Resources>
            <DataTemplate
                x:Name="ListViewTemplate">
                <ListView
                    Grid.Row="1"
                    Grid.Column="1"
                    ItemsSource="{Binding CountryList}"
                    Background="WhiteSmoke"
                    BorderThickness="4"
                    BorderBrush="#323232"
                    SelectionMode="Multiple"
                    extensions:ListViewExtensions.BindableSelection="{Binding FilteredCountries, Mode=TwoWay}">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <StackPanel
                                Orientation="Horizontal"
                                Margin="20">
                                <TextBlock
                                    Text="{Binding}"
                                    Margin="30,0,0,0"
                                    VerticalAlignment="Center"
                                    FontSize="18" />
                            </StackPanel>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </DataTemplate>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="10*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Button x:Name="CountryOKButton" Content="OK" FontSize="26" Click="CountryOKButton_OnClick" Grid.Row="2" Grid.Column="1"/>
    </Grid>
</Popup>

在你的代码后面:

private ListView _listViewInstance;
private void CountryListButton_OnClick(object sender, RoutedEventArgs e)
{
    CountryListPopupGrid.Width = Window.Current.Bounds.Width;
    CountryListPopupGrid.Height = Window.Current.Bounds.Height;
    _listViewInstance = (ListView)ListViewTemplate.LoadContent();
    CountryListPopupGrid.Children.Add(_listViewInstance);
    CountryListPopup.IsOpen = true;
}
private void CountryOKButton_OnClick(object sender, RoutedEventArgs e)
{
    MainPageViewModel vm = this.DataContext as MainPageViewModel;
    foreach (string filteredCountry in vm.FilteredCountries)
    {
        Debug.WriteLine(filteredCountry);
    }
    CountryListPopup.IsOpen = false;
    CountryListPopupGrid.Children.Remove(_listViewInstance);
    _listViewInstance = null;
}

行为的更新版本:

using System.Collections.ObjectModel;
using System.Collections.Specialized;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace WinRTXamlToolkit.Controls.Extensions
{
    /// <summary>
    /// Extension methods and attached properties for the ListView class.
    /// </summary>
    public static class ListViewExtensions
    {
        #region BindableSelection
        /// <summary>
        /// BindableSelection Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty BindableSelectionProperty =
            DependencyProperty.RegisterAttached(
                "BindableSelection",
                typeof (object),
                typeof (ListViewExtensions),
                new PropertyMetadata(null, OnBindableSelectionChanged));
        /// <summary>
        /// Gets the BindableSelection property. This dependency property 
        /// indicates the list of selected items that is synchronized
        /// with the items selected in the ListView.
        /// </summary>
        public static ObservableCollection<object> GetBindableSelection(DependencyObject d)
        {
            return (ObservableCollection<object>)d.GetValue(BindableSelectionProperty);
        }
        /// <summary>
        /// Sets the BindableSelection property. This dependency property 
        /// indicates the list of selected items that is synchronized
        /// with the items selected in the ListView.
        /// </summary>
        public static void SetBindableSelection(
            DependencyObject d,
            ObservableCollection<object> value)
        {
            d.SetValue(BindableSelectionProperty, value);
        }
        /// <summary>
        /// Handles changes to the BindableSelection property.
        /// </summary>
        /// <param name="d">
        /// The <see cref="DependencyObject"/> on which
        /// the property has changed value.
        /// </param>
        /// <param name="e">
        /// Event data that is issued by any event that
        /// tracks changes to the effective value of this property.
        /// </param>
        private static void OnBindableSelectionChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            dynamic oldBindableSelection = e.OldValue;
            dynamic newBindableSelection = d.GetValue(BindableSelectionProperty);
            if (oldBindableSelection != null)
            {
                var handler = GetBindableSelectionHandler(d);
                SetBindableSelectionHandler(d, null);
                handler.Detach();
            }
            if (newBindableSelection != null)
            {
                var handler = new ListViewBindableSelectionHandler(
                    (ListViewBase)d, newBindableSelection);
                SetBindableSelectionHandler(d, handler);
            }
        }
        #endregion
        #region BindableSelectionHandler
        /// <summary>
        /// BindableSelectionHandler Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty BindableSelectionHandlerProperty =
            DependencyProperty.RegisterAttached(
                "BindableSelectionHandler",
                typeof (ListViewBindableSelectionHandler),
                typeof (ListViewExtensions),
                new PropertyMetadata(null));
        /// <summary>
        /// Gets the BindableSelectionHandler property. This dependency property 
        /// indicates BindableSelectionHandler for a ListView - used
        /// to manage synchronization of BindableSelection and SelectedItems.
        /// </summary>
        public static ListViewBindableSelectionHandler GetBindableSelectionHandler(
            DependencyObject d)
        {
            return
                (ListViewBindableSelectionHandler)
                d.GetValue(BindableSelectionHandlerProperty);
        }
        /// <summary>
        /// Sets the BindableSelectionHandler property. This dependency property 
        /// indicates BindableSelectionHandler for a ListView - used to manage synchronization of BindableSelection and SelectedItems.
        /// </summary>
        public static void SetBindableSelectionHandler(
            DependencyObject d,
            ListViewBindableSelectionHandler value)
        {
            d.SetValue(BindableSelectionHandlerProperty, value);
        }
        #endregion
        #region ItemToBringIntoView
        /// <summary>
        /// ItemToBringIntoView Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty ItemToBringIntoViewProperty =
            DependencyProperty.RegisterAttached(
                "ItemToBringIntoView",
                typeof (object),
                typeof (ListViewExtensions),
                new PropertyMetadata(null, OnItemToBringIntoViewChanged));
        /// <summary>
        /// Gets the ItemToBringIntoView property. This dependency property 
        /// indicates the item that should be brought into view.
        /// </summary>
        public static object GetItemToBringIntoView(DependencyObject d)
        {
            return (object)d.GetValue(ItemToBringIntoViewProperty);
        }
        /// <summary>
        /// Sets the ItemToBringIntoView property. This dependency property 
        /// indicates the item that should be brought into view when first set.
        /// </summary>
        public static void SetItemToBringIntoView(DependencyObject d, object value)
        {
            d.SetValue(ItemToBringIntoViewProperty, value);
        }
        /// <summary>
        /// Handles changes to the ItemToBringIntoView property.
        /// </summary>
        /// <param name="d">
        /// The <see cref="DependencyObject"/> on which
        /// the property has changed value.
        /// </param>
        /// <param name="e">
        /// Event data that is issued by any event that
        /// tracks changes to the effective value of this property.
        /// </param>
        private static void OnItemToBringIntoViewChanged(
            DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            object newItemToBringIntoView =
                (object)d.GetValue(ItemToBringIntoViewProperty);
            if (newItemToBringIntoView != null)
            {
                var listView = (ListView)d;
                listView.ScrollIntoView(newItemToBringIntoView);
            }
        }
        #endregion
        /// <summary>
        /// Scrolls a vertical ListView to the bottom.
        /// </summary>
        /// <param name="listView"></param>
        public static void ScrollToBottom(this ListView listView)
        {
            var scrollViewer = listView.GetFirstDescendantOfType<ScrollViewer>();
            scrollViewer.ScrollToVerticalOffset(scrollViewer.ScrollableHeight);
        }
    }
    public class ListViewBindableSelectionHandler
    {
        private ListViewBase _listView;
        private dynamic _boundSelection;
        private readonly NotifyCollectionChangedEventHandler _handler;
        public ListViewBindableSelectionHandler(
            ListViewBase listView, dynamic boundSelection)
        {
            _handler = OnBoundSelectionChanged;
            Attach(listView, boundSelection);
        }
        private void Attach(ListViewBase listView, dynamic boundSelection)
        {
            _listView = listView;
            _listView.Unloaded += OnListViewUnloaded;
            _listView.SelectionChanged += OnListViewSelectionChanged;
            _boundSelection = boundSelection;
            _listView.SelectedItems.Clear();
            foreach (object item in _boundSelection)
            {
                if (!_listView.SelectedItems.Contains(item))
                {
                    _listView.SelectedItems.Add(item);
                }
            }
            var eventInfo =
                _boundSelection.GetType().GetDeclaredEvent("CollectionChanged");
            eventInfo.AddEventHandler(_boundSelection, _handler);
            //_boundSelection.CollectionChanged += OnBoundSelectionChanged;
        }
        private void OnListViewSelectionChanged(
            object sender, SelectionChangedEventArgs e)
        {
            foreach (dynamic item in e.RemovedItems)
            {
                if (_boundSelection.Contains(item))
                {
                    _boundSelection.Remove(item);
                }
            }
            foreach (dynamic item in e.AddedItems)
            {
                if (!_boundSelection.Contains(item))
                {
                    _boundSelection.Add(item);
                }
            }
        }
        private void OnBoundSelectionChanged(
            object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action ==
                NotifyCollectionChangedAction.Reset)
            {
                _listView.SelectedItems.Clear();
                foreach (var item in _boundSelection)
                {
                    if (!_listView.SelectedItems.Contains(item))
                    {
                        _listView.SelectedItems.Add(item);
                    }
                }
                return;
            }
            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    if (_listView.SelectedItems.Contains(item))
                    {
                        _listView.SelectedItems.Remove(item);
                    }
                }
            }
            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    if (!_listView.SelectedItems.Contains(item))
                    {
                        _listView.SelectedItems.Add(item);
                    }
                }
            }
        }
        private void OnListViewUnloaded(object sender, RoutedEventArgs e)
        {
            Detach();
        }
        internal void Detach()
        {
            _listView.Unloaded -= OnListViewUnloaded;
            _listView.SelectionChanged -= OnListViewSelectionChanged;
            _listView = null;
            var eventInfo =
                _boundSelection.GetType().GetDeclaredEvent("CollectionChanged");
            eventInfo.RemoveEventHandler(_boundSelection, _handler);
            _boundSelection = null;
        }
    }
}

如果你不想每次都使用一个新的ListView,你需要在行为中注释掉这一行:_listView.Unloaded += OnListViewUnloaded;