绑定到DataGridRow.IsSelected属性时,Wpf DataGrid虚拟化问题

时间:2011-10-21 02:18:10

标签: wpf mvvm datagrid virtualization

我遇到了虚拟化如何与WPF DataGrid一起使用的问题。

我正在使用MVVM并将所有行视图模型绑定到IsSelected属性。我需要偶尔取消选择所有行,所以我通过将我的基础行视图模型更新为IsSelected = false来执行此操作。

这开始似乎工作正常,它取消选择一切。我已经验证使用调试消息并设置条件断点将所有行视图模型设置为false。

但是,当我滚动网格时会出现问题。我看到实际选择了一些行。当IsSelected属性设置为true时,我在IsSelected属性上有一个条件断点,在滚动网格之前,这实际上并没有中断。所以,当我向下滚动时,某些东西偶尔会将某些行视图模型更新回IsSelected = true?

我不太明白发生了什么,或者是它的调用堆栈......有人可以向我解释实际发生了什么吗?我认为这与虚拟化有关。我认为可能虚拟化正在回收DataGridRow,反过来又将我的viewmodels更新回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 

更新: 我正在发布我的View和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();
        }
    }
}

1 个答案:

答案 0 :(得分:0)

这可能是一个线程问题。我看到你正在获取锁定在锁定块内的同一对象的锁定,即命令对象

您应该始终使用专门声明用于锁定关键字的单独新对象实例锁定,例如

private readonly object _lockObject = new Object();

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

尝试对锁定块进行上述代码更改,并查看问题是否得到解决。