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,但事实并非如此。
有趣的是,我的大多数附加行为的实现方式似乎受到了一个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;