1

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>

初めてポップアップを開いていくつかのアイテムを選択すると、FilteredCountriesコレクションが変更され、選択したアイテムが含まれます。しかし、ポップアップを閉じて再度開き、FilteredCountriesコレクションが変更されないアイテムをさらに選択または選択解除した後も、最初のときと同じままです。これは、バインディングモードがOneTimeに設定されているように見えますが、そうではありません。

4

1 に答える 1

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;

于 2013-02-08T22:13:55.777 に答える