WinRTXAML Toolkit BindableSelection无法正常工作

时间:2013-02-08 15:58:16

标签: c# xaml windows-8 windows-runtime winrt-xaml

我有一个问题绑定到ListView的SelectedItems属性。 我在ViewModel中有一个如下所示的属性:

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

在XAML中,我在Popup中创建了一个像这样的ListView:

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

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

1 个答案:

答案 0 :(得分:4)

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

现在的修复方法是始终使用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;

相关问题