2011-10-21 127 views
5

我遇到了虛擬化如何與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(); 
     } 
    } 
} 
+0

奇怪!我試過你在做什麼,但沒有運氣!當我將行級別項目的IsSelected屬性設置爲false(這是雙向綁定到DataGridRow的IsSelected屬性)時,即使滾動也不會選擇任何行。他們都未被選中。 : -/ –

+0

你可以發佈你的代碼嗎? – Rachel

+0

我會嘗試去掉我的代碼併發布它。這個問題可能發生在大約20%的時間。 80%的時間,它會取消選擇一切正常。您是否嘗試過超過1200行並選擇了全部?這就是我正在做的。 – user832747

回答

0

這可能是一個線程問題。我看到您正在鎖定您在鎖塊內更新的同一對象,即訂​​單對象

您應該始終鎖定一個專門聲明用於鎖定關鍵字的單獨新對象實例,例如,

private readonly object _lockObject = new Object(); 

lock(_lockObject) 
{ 
    orders.Add(...); 
} 

嘗試將上述代碼更改爲您的鎖定塊並查看問題是否得到解決。