Проблемы виртуализации Wpf DataGrid при привязке к свойству DataGridRow.IsSelected - PullRequest
5 голосов
/ 21 октября 2011

У меня проблемы с тем, как виртуализация работает с WPF DataGrid.

Я использую MVVM и привязываю все мои модели представления строк к свойству IsSelected.Мне иногда нужно отменить выбор всех строк, поэтому я делаю это, обновляя свои базовые модели представления строк до IsSelected = false.

Сначала это работает нормально, и все отменяется.Я проверил, что для всех моделей представления строк установлено значение false с помощью сообщений отладки и установки условных точек останова.

Однако при прокрутке сетки возникает проблема.Я вижу, что некоторые строки на самом деле выбраны.У меня есть условная точка останова для свойства IsSelected, когда оно установлено в true, и это фактически не нарушается, пока я не прокручиваю сетку.Итак, когда я прокручиваю, что-то иногда обновляет некоторые из моделей представления строк до IsSelected = true?

Я не совсем понимаю, что происходит, или стека вызовов для этого ... может кто-нибудь объяснить мнечто на самом деле происходит?Я предполагаю, что это как-то связано с виртуализацией.Я подумал, что, возможно, виртуализация перерабатывает DataGridRow, и, в свою очередь, обновляет мои view-модели до IsSelected = true.Однако это происходит с обоими режимами виртуализации (Recycling и Standard).Я бы подумал, что 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.Я могу воспроизвести его последовательно, вызвав вызов «Выбрать все строки», чтобы обновить все модели представления строк до IsSelected = true.После этого я немного прокручиваю вверх и вниз, чтобы увидеть все выбранные строки.Затем я вызываю «Отменить выбор всех строк», и все модели представления строк должны быть отменены.Когда я прокручиваю вниз, я вижу, что модели строк просмотра затем обновляются до 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);
            }
        }
    }
}

View

<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 голосов
/ 02 ноября 2011

Это может быть проблема с многопоточностью.Я вижу, что вы получаете блокировку для того же объекта, который вы обновляете внутри блока блокировки, т. Е. Заказывает объект

. Всегда следует блокировать с помощью отдельного нового экземпляра объекта, специально объявленного для использования с ключевым словом блокировки, например

private readonly object _lockObject = new Object();

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

Попробуйте с вышеуказанными изменениями кода в вашем блоке блокировки и посмотрите, решена ли проблема.

...