我遇到了虛擬化如何與WPF DataGrid一起工作的問題。當綁定到DataGridRow.IsSelected屬性時Wpf DataGrid虛擬化問題
我正在使用MVVM並將我所有的行viewmodels綁定到IsSelected屬性。我需要偶爾取消選擇所有行,所以我通過將底層行視圖模型更新爲IsSelected = false來完成此操作。
這似乎起初運作良好,它取消選擇一切。我已使用調試消息和設置條件斷點驗證了所有行視圖模型都設置爲false。
但是,當我滾動網格時出現問題。我看到一些行實際上被選中。當它被設置爲true時,我在IsSelected屬性上有一個條件斷點,並且在我滾動網格之前,它不會真正中斷。所以,當我scrolldown時,偶爾會將某些行視圖模型更新回IsSelected = true?
我不太明白髮生了什麼,或者它的callstack ...有人可以向我解釋實際發生的事情嗎?我認爲這與虛擬化有關。我認爲,也許虛擬化正在回收DataGridRow,並反過來將我的視圖模型更新回IsSelected = true。但是,這在虛擬化模式(回收和標準)下都會發生。我會認爲每次都會重新創建DataGridRows?
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
更新: 我張貼了我的查看和的ViewModels的代碼。我可以通過調用「Select All rows」調用將所有行視圖模型更新爲IsSelected = true來一致地重現它。在此之後,我上下滾動一下以查看所有選定的行。然後我調用「取消選擇所有行」,並且所有的行視圖模型都應該被取消選中。向下滾動時,我可以看到rowviewmodels然後被更新爲IsSelected = true(通過調試消息)。如果我打破並看到這是什麼調用,我得到上面的堆棧跟蹤。
的ViewModels:
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);
}
}
}
}
查看
<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();
}
}
}
奇怪!我試過你在做什麼,但沒有運氣!當我將行級別項目的IsSelected屬性設置爲false(這是雙向綁定到DataGridRow的IsSelected屬性)時,即使滾動也不會選擇任何行。他們都未被選中。 : -/ –
你可以發佈你的代碼嗎? – Rachel
我會嘗試去掉我的代碼併發布它。這個問題可能發生在大約20%的時間。 80%的時間,它會取消選擇一切正常。您是否嘗試過超過1200行並選擇了全部?這就是我正在做的。 – user832747