Problems with Wpf DataGrid virtualization when binding to the DataGridRow.IsSelected property

I'm having problems with how virtualization works with WPF DataGrid.

I use MVVM and bind all my row view models to the IsSelected property. I need to deselect all rows occasionally, so I do this by updating my base row models with IsSelected = false.

It seems that at first it works fine and it does not display everything. I checked that all line models in the line are set to false using debugging messages and setting conditional breakpoints.

However, there is a problem when I scroll the grid. I see that some of the lines are indeed selected. I have a conditional breakpoint in the IsSelected property when it is set to true, and it does not actually break until I loop through the grid. So, as I scroll through the scroll, does something sometimes update some of the line view modes back to IsSelected = true?

I can’t understand what is happening, or a column for him ... can someone explain to me what is actually happening? I assume this is due to virtualization. I thought that perhaps virtualization is processing the DataGridRow and, in turn, updating my view modes to IsSelected = true. However, this happens with virtualization mode (Recycling and Standard). I would think that DataGridRows will be recreated every time?

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 

UPDATE: View ViewModels. , " ", IsSelected = true. , . "Unselect All rows", . , , 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();
        }
    }
}
+5
1

. , , , ..

, , .

private readonly object _lockObject = new Object();

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

, .

0

All Articles