【WPF】DataGridの一括選択の実装
解決済
回答 2
投稿
- 評価
- クリップ 0
- VIEW 6,826

退会済みユーザー
現在DataGridのアイテムの一括選択を右クリックで実装しようとしています。
DataGridのMouseRightButtonUpイベントで処理を行っており、
始めに選択されていたRowの場所とShiftを押されながら右クリックされた場所の
インデックスを使用しforで回して間のアイテムのIsSelectedをTrueにしていくという処理なのですが、
DataGridのインスタンスからRowを取得するときの
ItemContainerGenerator.ContainerFromIndex()でnullを返してくる場合がありうまくいきません。
nullを返してくる条件なのですが、Item数が多くScrollBarが出ている状態で画面外になっているアイテムが
nullを返してきています。
いろいろ調べて、UpdateLayout()やScrollIntoView()を所得前に行うようにしてみたのですが解決できませんでした。
なにか解決策があればぜひ教えていただきたいです。
よろしくお願いいたします。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
原因
ほとんどのWPFのリストコントロール(DataGridやListView等)は既定で仮想化されており、描画範囲外のコントロールが存在しません。
このため、コントロールベースでこの手の操作を実装をしようとすると失敗します。
対策
対策は2つあります。1つはアイテムコンテナの仮想化を止めること、もう1つはデータバインディングによって選択済みのアイテムを取得することです。基本的には後者の方がパフォーマンス上ベターです。
サンプル
データバインディングを使用した選択行を取得する方法を紹介します。どこまでMVVMに準拠するのかによってコーディングは変わってきますが、今回ご紹介するのは最も簡単と思われる方法です。必要であればこちらをBehavior等にすると良いでしょう。
CommandParameterにDataGridのSelectedItemsをバインドすると、選択されたItemのコレクションを取得できます。
<DataGrid x:Name="MyDataGrid" ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<!-- 下記のようにすればMouseRightButtonUpイベントとバインドできます -->
<i:EventTrigger EventName="MouseRightButtonUp">
<i:InvokeCommandAction
Command="{Binding ShowSelectedCommand}"
CommandParameter="{Binding ElementName=MyDataGrid, Path=SelectedItems}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Presenter/ViewModelクラス上の実装
public ICommand ShowSelectedCommand
{
get
{
// DelegateCommandは一般的なICommand実装を想定していますが、分からなければ再度聞いてください
return _ShowSelectedCommand = _ShowSelectedCommand ??
new DelegateCommand(showSelected);
}
}
private ICommand _ShowSelectedCommand;
private void showSelected(object parameter)
{
// SelectedItemsの型はSelectedItemCollectionという型ですが非公開です
// IList型に一旦キャストした後、Castメソッドで目的のコレクションに変換します。
var items = (parameter as IList)?.Cast<MyItem>();
if (items != null)
{
// このようにすれば、MyItemsから選択されたMyItemの一覧を取得できます。
// ここで目的のItemのIsSelectedプロパティをtrueに設定すれば良いでしょう。
MessageBox.Show(string.Join(",", items.Select(item => item.Index)));
}
}
サンプルはSystem.Windows.Interactivityを使用しています。
プロジェクトの参照にSystem.Windows.Interactivityを追加し、XAMLのWindow/UserControlには以下の名前空間を定義してください。
<Window ...
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
/>
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
これを呼ぶのではダメなのでしょうか?
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.22%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2016/12/13 12:28
2016/12/15 10:34
元々この処理自体が初めから自分で考えたわけではなくプロジェクト上の同じような場所から持ってきたものになります。
ソートやフィルターなどでの並べ替え等を考慮してのコードビハインド上の処理かなと思うのですがViewModel側で実装できるでしょうか?
2016/12/15 10:54
上記で提案した方法の場合、BindされたItemsの選択されたItemだけのコレクションがshowSelectedでは取得できているため、選択対象への操作が必要な場合に大本のItemsに触れる必要はありません。
お話から察するに、コレクションビューは使用されていると思います。
ICollectionView view = CollectionViewSource.GetDefaultView(MyItems);
このソートは大本のMyItemsには影響を与えません。しかし、ShowSelectedCommandに渡されたパラメーター側のアイテムの一覧は選択されたMyItemが、「選択順」で入ってきます。
(例えば、1,2,3,8,5とコントロールキーで押して対象選択してから右クリックするとサンプルでは1,2,3,8,5と表示される。)
MyItemのプロパティを変更するだけなら
MessageBox.Show(string.Join(",", items.Select(item => item.Index)));
であった部分を
items.ForEach(item => item.IsSelected = true);
とするだけでよいでしょうし、選択済みリストを作成する場合には
items.ToList() とするだけで別のコレクションにできます。
必要なら再ソートします。
で、これを次の処理にパスすればいいです。
2016/12/15 11:26
選択すること自体を実行したいってことですね。
すみません、これについては仮想化を止める以外の方法を知らないので即答できません。
DataGridの仮想化を止める方法についても書いておきます。
これをすると行数が大量にある場合は動作が遅くなることは留意しておいてください。
DataGridのプロパティに下記を追加してください。
VirtualizingStackPanel.IsVirtualizing="False"
これでItemContainerGenerator.ContainerFromIndex()はnullを返してこなくなるはずです。
この場合描画範囲外の部分についてもDependencyObjectを取得できますので、IsSelectedプロパティをtrueにしてあげてください。
通常範囲選択は標準で実行できるので質問の意味を取り違えていました。申し訳ないです。
2016/12/15 14:06
少し勘違いされていたみたいですが回答としてはとても参考になりました。
やはり仮想化をoffにする方法はパフォーマンス的に厳しかったのですが、無事仮想化のままでやりたいことを行えました!
ありがとうございます!
2016/12/15 14:11