WPF DataGridで仮想化がどのように機能するかに問題があります。DataGridRow.IsSelectedプロパティへのバインド時にWpf DataGrid仮想化の問題が発生する
MVVMを使用していて、すべての行ビューモデルをIsSelectedプロパティにバインドしています。私は時々すべての行を選択解除する必要があるので、私は基本的な行のビューモデルをIsSelected = falseに更新することでこれを行います。
これは最初は正常に動作していて、すべてを選択解除しています。すべての行ビューモデルがデバッグメッセージを使用してfalseに設定され、条件付きブレークポイントが設定されていることを確認しました。
ただし、グリッドをスクロールするときに問題が発生します。私はいくつかの行が実際に選択されて参照してください。 Trueに設定されている場合、IsSelectedプロパティに条件付きブレークポイントがあり、グリッドをスクロールするまで実際にブレークしません。だから、私はscrolldownとして、何かが時々IsSelected = trueに行ビューモデルのいくつかを更新しますか?
実際に何が起こっているのか、誰かに説明することができますか?私はそれが仮想化と関係があると考えています。私は、仮想化が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
UPDATE:私は私のビューとの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();
}
}
}
奇妙な!私はあなたがしていることを試みたが、運がなかった!行レベルの項目の 'IsSelected'プロパティをfalseに設定すると(これはDataGridRowのIsSelectedプロパティに2つの方法でバインドされています)、スクロールしてもすべての行が選択されません。彼らはすべて未選択のままです。 : -/ –
あなたのコードを投稿できますか? – Rachel
私は自分のコードを削除して投稿しようとします。この問題はおそらく約20%の時間で起こります。 80%の時間で、それはすべてうまく選択解除されます。あなたは1200以上の行を試してみて、それらをすべて選択しましたか?これは私がやっていることです。 – user832747