绑定到DataGridRow.IsSelected属性时Wpf DataGrid虚拟化问题
我对虚拟化如何与WPF DataGrid协同工作存在问题 我正在使用MVVM并将所有行viewmodels绑定到IsSelected属性。我需要偶尔取消选择所有行,因此我通过将基础行viewmodels更新为IsSelected=false来实现这一点 这似乎在一开始工作正常,它取消选择所有内容。我已经使用调试消息和设置条件断点验证了所有行视图模型都设置为false 然而,当我在网格中滚动时,有一个问题。我看到一些行实际上被选中了。当IsSelected属性设置为true时,它有一个条件断点,在我滚动网格之前,它实际上不会中断。所以,当我向下滚动时,有时会有东西将一些行viewmodels更新回IsSelected=true 我不太明白到底发生了什么,或者它的调用堆栈。。。有人能给我解释一下到底发生了什么吗?我想这和虚拟化有关。我认为虚拟化可能是在循环使用DataGridRow,而反过来又在将我的viewmodels更新回IsSelected=true。但是,这在两种虚拟化模式(回收和标准)下都会发生。我原以为每次都会重新创建DataGridRows绑定到DataGridRow.IsSelected属性时Wpf DataGrid虚拟化问题,wpf,mvvm,datagrid,virtualization,Wpf,Mvvm,Datagrid,Virtualization,我对虚拟化如何与WPF DataGrid协同工作存在问题 我正在使用MVVM并将所有行viewmodels绑定到IsSelected属性。我需要偶尔取消选择所有行,因此我通过将基础行viewmodels更新为IsSelected=false来实现这一点 这似乎在一开始工作正常,它取消选择所有内容。我已经使用调试消息和设置条件断点验证了所有行视图模型都设置为false 然而,当我在网格中滚动时,有一个问题。我看到一些行实际上被选中了。当IsSelected属性设置为true时,它有一个条件断点,在
MyApp.exe!MyApp.MyViewModel.IsSelected.set(bool value = true) Line 67 C#
[Native to Managed Transition]
PresentationFramework.dll!MS.Internal.Data.PropertyPathWorker.SetValue(object item, object value) + 0x106 bytes
PresentationFramework.dll!MS.Internal.Data.ClrBindingWorker.UpdateValue(object value) + 0xa3 bytes
PresentationFramework.dll!System.Windows.Data.BindingExpression.UpdateSource(object value = true) + 0x99 bytes
PresentationFramework.dll!System.Windows.Data.BindingExpressionBase.UpdateValue() + 0x66 bytes
PresentationFramework.dll!System.Windows.Data.BindingExpression.Update(bool synchronous) + 0x4f bytes
PresentationFramework.dll!System.Windows.Data.BindingExpressionBase.Dirty() + 0x30 bytes
PresentationFramework.dll!System.Windows.Data.BindingExpression.SetValue(System.Windows.DependencyObject d, System.Windows.DependencyProperty dp, object value) + 0x27 bytes
WindowsBase.dll!System.Windows.DependencyObject.SetValueCommon(System.Windows.DependencyProperty dp = {System.Windows.DependencyProperty}, object value = true, System.Windows.PropertyMetadata metadata = {System.Windows.FrameworkPropertyMetadata}, bool coerceWithDeferredReference = false, bool coerceWithCurrentValue = true, System.Windows.OperationType operationType = Unknown, bool isInternal) + 0x3c7 bytes
WindowsBase.dll!System.Windows.DependencyObject.SetCurrentValueInternal(System.Windows.DependencyProperty dp, object value) + 0x35 bytes
PresentationFramework.dll!System.Windows.Controls.Primitives.Selector.ItemSetIsSelected(object item, bool value) + 0xb2 bytes
PresentationFramework.dll!System.Windows.Controls.Primitives.Selector.OnGeneratorStatusChanged(object sender, System.EventArgs e) + 0xf8 bytes
PresentationFramework.dll!System.Windows.Controls.ItemContainerGenerator.SetStatus(System.Windows.Controls.Primitives.GeneratorStatus value) + 0x81 bytes
PresentationFramework.dll!System.Windows.Controls.ItemContainerGenerator.Generator.System.IDisposable.Dispose() + 0x4a bytes
PresentationFramework.dll!System.Windows.Controls.VirtualizingStackPanel.MeasureOverride(System.Windows.Size constraint) + 0x976 bytes
PresentationFramework.dll!System.Windows.Controls.Primitives.DataGridRowsPresenter.MeasureOverride(System.Windows.Size constraint) + 0x28 bytes
PresentationFramework.dll!System.Windows.FrameworkElement.MeasureCore(System.Windows.Size availableSize) + 0x1d6 bytes
PresentationCore.dll!System.Windows.UIElement.Measure(System.Windows.Size availableSize) + 0x207 bytes
PresentationCore.dll!System.Windows.ContextLayoutManager.UpdateLayout() + 0x1d9 bytes
PresentationCore.dll!System.Windows.ContextLayoutManager.UpdateLayoutCallback(object arg) + 0x19 bytes
PresentationCore.dll!System.Windows.Media.MediaContext.InvokeOnRenderCallback.DoWork() + 0x10 bytes
PresentationCore.dll!System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks() + 0x6f bytes
PresentationCore.dll!System.Windows.Media.MediaContext.RenderMessageHandlerCore(object resizedCompositionTarget = null) + 0x8a bytes
PresentationCore.dll!System.Windows.Media.MediaContext.RenderMessageHandler(object resizedCompositionTarget) + 0x2c bytes
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0x53 bytes
WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object source = {System.Windows.Threading.Dispatcher}, System.Delegate method, object args, int numArgs, System.Delegate catchHandler = null) + 0x42 bytes
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() + 0x8d bytes
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object state) + 0x38 bytes
mscorlib.dll!System.Threading.ExecutionContext.runTryCode(object userData) + 0x51 bytes
更新: 我正在发布视图和视图模型的代码。通过调用“selectallrows”调用将所有行视图模型更新为IsSelected=true,我能够始终如一地重现它。在此之后,我上下滚动一点以查看所有选定的行。然后我调用“取消选择所有行”,所有行视图模型都应该取消选择。向下滚动时,我可以看到rowviewmodels随后被更新为IsSelected=true(通过调试消息)。如果我打断并看到这是什么,我会得到上面的堆栈跟踪 视图模型:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;
namespace WpfDataGridVirtualization
{
public interface IOrderViewModel : INotifyPropertyChanged
{
Guid Key { get; }
decimal Level { get; }
bool IsSelected { get; set; }
}
public class OrderViewModel : NotifyPropertyChanged, IOrderViewModel
{
private string _market;
private int _quantity;
private decimal _level;
private bool _isSelected;
public OrderViewModel(OrderData orderData)
{
Key = Guid.NewGuid();
Market = orderData.Market;
IsSelected = false;
Quantity = orderData.Quantity;
Level = orderData.Level;
}
public Guid Key { get; private set; }
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value)
System.Diagnostics.Debug.WriteLine("setting isselected to true");
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
public string Market
{
get { return _market; }
private set
{
_market = value;
RaisePropertyChanged("Market");
}
}
public int Quantity
{
get { return _quantity; }
private set
{
_quantity = value;
RaisePropertyChanged("Quantity");
}
}
public decimal Level
{
get { return _level; }
private set
{
_level = value;
RaisePropertyChanged("Level");
}
}
}
public class OrderData
{
public OrderData(string market, int qty, decimal level)
{
Key = Guid.NewGuid();
Market = market;
Quantity = qty;
Level = level;
}
public Guid Key { get; set; }
public string Market { get; set; }
public int Quantity { get; set; }
public decimal Level { get; set; }
}
public class OrderCollectionViewModel : NotifyPropertyChanged, IDisposable
{
private readonly ObservableCollection<IOrderViewModel> _orders = new ObservableCollection<IOrderViewModel>();
public ObservableCollection<IOrderViewModel> Orders { get { return _orders; } }
public void AddOrders(IEnumerable<OrderData> orders)
{
orders.ToList().ForEach(o => AddOrder(o));
}
private void AddOrder(OrderData order)
{
var viewModel = _orders.Where(o => o.Key == order.Key).SingleOrDefault();
if (viewModel == null)
{
viewModel = new OrderViewModel(order);
lock (_orders)
{
_orders.Add(viewModel);
}
}
viewModel.IsSelected = false;
}
public void ApplyFiltering()
{
UnSelectAll();
}
public void SelectAll(bool select)
{
UpdateAllOrders(row =>
{
row.IsSelected = select;
});
}
public void SelectSingleRow(Guid key)
{
UpdateAllOrders(row =>
{
row.IsSelected = true;
});
}
public IEnumerable<IOrderViewModel> GetSelected()
{
lock (_orders)
{
return _orders.Where(s => s.IsSelected).ToList();
}
}
public void UnSelectAll()
{
var count = 0;
UpdateAllOrders(row =>
{
row.IsSelected = false;
count++;
});
System.Diagnostics.Debug.WriteLine("{0} orders were unselected", count);
}
private void UpdateAllOrders(Action<IOrderViewModel> action)
{
lock (_orders)
{
_orders.ToList().ForEach(action);
}
}
public void Dispose()
{
_orders.Clear();
}
public class OrderSorter : IComparer
{
public int Compare(object x, object y)
{
var orderX = x as OrderViewModel;
var orderY = y as OrderViewModel;
var result = orderX.Market.CompareTo(orderY.Market);
if (result != 0)
return result;
return orderX.Level.CompareTo(orderY.Level);
}
}
}
public class OrderGridViewModel : NotifyPropertyChanged, IDisposable
{
private ICommand _selectAllOrdersCommand;
private ICommand _unselectAllOrdersCommand;
public OrderGridViewModel()
{
OrderCollection = new OrderCollectionViewModel();
InitializeOrders();
}
public ObservableCollection<IOrderViewModel> Orders { get { return OrderCollection.Orders; } }
public OrderCollectionViewModel OrderCollection { get; private set; }
public ICommand SelectAllOrdersCommand
{
get { return _selectAllOrdersCommand ?? (_selectAllOrdersCommand = new RelayCommand(p => OrderCollection.SelectAll(true))); }
}
public ICommand UnSelectAllOrdersCommand
{
get { return _unselectAllOrdersCommand ?? (_unselectAllOrdersCommand = new RelayCommand(p => OrderCollection.ApplyFiltering())); }
}
private void InitializeOrders()
{
OrderCollection.AddOrders(OrderDataHelper.GetOrderData());
}
public void Dispose()
{
OrderCollection.Dispose();
}
}
public static class OrderDataHelper
{
public static IEnumerable<OrderData> GetOrderData()
{
Dictionary<int, string> marketMap = new Dictionary<int, string>()
{
{0, "AUD"},
{1, "EUR"},
{2, "USD"},
{3, "CAD"},
{4, "CHF"},
{5, "BOBL"},
{6, "EMiniNasdaq"},
{7, "Corn"},
{8, "Oil"},
{9, "Starch"},
};
var multiplyFactor = 1;
for (int j = 0; j < 10; j++)
{
var market = marketMap[j];
for (int i = 0; i < 50 * multiplyFactor; i++)
yield return new OrderData(market, 1000000, 100);
for (int i = 0; i < 50 * multiplyFactor; i++)
yield return new OrderData(market, 1000000, 100);
for (int i = 0; i < 5 * multiplyFactor; i++)
yield return new OrderData(market, 1000000, 100);
for (int i = 0; i < 2 * multiplyFactor; i++)
yield return new OrderData(market, 1000000, 100);
for (int i = 0; i < 5 * multiplyFactor; i++)
yield return new OrderData(market, 1000000, 100);
for (int i = 0; i < 5 * multiplyFactor; i++)
yield return new OrderData(market, 1000000, 100);
for (int i = 0; i < 5 * multiplyFactor; i++)
yield return new OrderData(market, 1000000, 100);
}
}
}
}
使用系统;
使用系统集合;
使用System.Collections.Generic;
使用System.Collections.ObjectModel;
使用系统组件模型;
使用System.Linq;
使用System.Windows.Input;
命名空间WpfDataGridVirtualization
{
公共接口IOrderViewModel:INotifyPropertyChanged
{
Guid键{get;}
十进制级别{get;}
布尔被选为{get;set;}
}
公共类OrderViewModel:NotifyPropertyChanged,IOrderViewModel
{
私人弦乐市场;
私人国际单位数量;
私有十进制_级;
私立学校当选;
public OrderViewModel(OrderData OrderData)
{
Key=Guid.NewGuid();
Market=orderData.Market;
IsSelected=false;
数量=orderData.Quantity;
Level=orderData.Level;
}
公共Guid密钥{get;private set;}
公选学校
{
获取{return}isSelected;}
设置
{
如果(值)
System.Diagnostics.Debug.WriteLine(“设置为true”);
_isSelected=值;
RaisePropertyChanged(“IsSelected”);
}
}
公共弦市场
{
获取{return\u market;}
专用设备
{
_市场=价值;
融资产权变更(“市场”);
}
}
公共整数
{
获取{返回_数量;}
专用设备
{
_数量=价值;
增加财产变更(“数量”);
}
}
公共十进制级别
{
获取{return\u level;}
专用设备
{
_级别=值;
提升属性更改(“级别”);
}
}
}
公共类OrderData
{
公共订单数据(字符串市场、整数数量、小数级别)
{
Key=Guid.NewGuid();
市场=市场;
数量=数量;
级别=级别;
}
公共Guid密钥{get;set;}
公共字符串市场{get;set;}
公共整数数量{get;set;}
公共十进制级别{get;set;}
}
公共类OrderCollectionViewModel:NotifyPropertyChanged,IDisposable
{
私有只读ObservableCollection _orders=新ObservableCollection();
公共可观察收集订单{get{return\u Orders;}}
公共无效添加订单(IEnumerable订单)
{
ForEach(o=>AddOrder(o));
}
专用void AddOrder(OrderData order)
{
var viewModel=_orders.Where(o=>o.Key==order.Key).SingleOrDefault();
if(viewModel==null)
{
viewModel=新订单viewModel(订单);
锁定(_命令)
{
_orders.Add(viewModel);
}
}
viewModel.IsSelected=false;
}
公共无效应用过滤()
{
取消选择全部();
}
公共void SelectAll(bool select)
{
更新领主(行=>
{
row.IsSelected=选择;
});
}
public void SelectSingleRow(Guid键)
{
更新领主(行=>
{
row.IsSelected=true;
});
}
公共IEnumerable GetSelected()
{
锁定(_命令)
{
return_orders.Where(s=>s.IsSelected.ToList();
}
}
public void UnSelectAll()
{
var计数=0;
更新领主(行=>
{
row.IsSelected=false;
计数++;
});
System.Diagnostics.Debug.WriteLine(“未选择{0}个订单”,计数);
}
私人无效更新领主(行动法案)
<Window x:Class="WpfDataGridVirtualization.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="600" Width="800" Closing="WindowClosing">
<DockPanel>
<DockPanel x:Name="dockHeader" DockPanel.Dock="Top" Background="Transparent">
<Button Content="Select All Orders" Margin="2" Command="{Binding SelectAllOrdersCommand}" />
<Button Content="UnSelect All Orders" Margin="2" Command="{Binding UnSelectAllOrdersCommand}" />
<DockPanel/>
</DockPanel>
<DockPanel DockPanel.Dock="Top">
<DataGrid x:Name="dgOrders" Margin="5"
ItemsSource="{Binding OrderCollection.Orders}"
IsReadOnly="True"
AutoGenerateColumns="False"
SelectionUnit="FullRow"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Standard"
>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="IsSelected" Binding="{Binding IsSelected}" />
<DataGridTextColumn Header="Market" Binding="{Binding Market}" />
<DataGridTextColumn Header="Quantity" Binding="{Binding Quantity}" />
<DataGridTextColumn Header="Level" Binding="{Binding Level}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</DockPanel>
</Window>
using System.Windows;
namespace WpfDataGridVirtualization
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly OrderGridViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new OrderGridViewModel();
DataContext = _viewModel;
}
private OrderGridViewModel GetViewModel()
{
return DataContext as OrderGridViewModel;
}
private void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
GetViewModel().Dispose();
}
}
}
private readonly object _lockObject = new Object();
lock(_lockObject)
{
orders.Add(...);
}