質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.46%
DataGrid

GUIの一種であり、データを表の形式でみることが可能です。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

Q&A

1回答

278閲覧

[C#][WPF]DataGridのセル・行コミット時に、重複をチェックし、無効な値をコミットした際にキャンセルしたい

saunaland

総合スコア1

DataGrid

GUIの一種であり、データを表の形式でみることが可能です。

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

WPF

Windows Presentation Foundation (WPF) は、魅力的な外観のユーザー エクスペリエンスを持つ Windows クライアント アプリケーションを作成するための次世代プレゼンテーション システムです

0グッド

0クリップ

投稿2024/03/12 07:59

前提

お世話になっております。
C# WPF で、リストのデータを表示するため、DataGridコントロールを配置しています。
DataGridコントロールのデータソースには、自作クラスのインスタンスが格納されたObservableCollectionを使用します。
配置されるViewは、PrismのDialogServiceで呼び出したカスタムダイアログのUserControlです。

実現したいこと

DataGridの編集時、セル・行それぞれコミットする際、無効な値(重複など)が含まれている場合にコミットをせずに編集状態を続行したい。
また、コミットを取り消された際、ViewModel側で処理を走らせたいので、できればXamlとViewModelクラスで完結させたい(MVVMの考え方として正しいかどうかはわかりません。Viewに関わる処理ならコードビハインドで処理を行っても良いのか?)

該当のソースコード

C#

1using Prism.Commands; 2using Prism.Mvvm; 3using Prism.Services.Dialogs; 4using Reactive.Bindings; 5using Reactive.Bindings.ObjectExtensions; 6using System; 7using System.Collections.Generic; 8using System.Collections.ObjectModel; 9using System.Linq; 10 11namespace EditDialog.ViewModels 12{ 13 public class EditDeviceDialogViewModel : BindableBase, IDialogAware 14 { 15 public IDialogService _dialogService; 16 17 /// <summary> 18 /// 表示されるクラス 19 /// </summary> 20 public ReactiveProperty<ExampleClass> _exampleClass { get; } = new(); 21 22 /// <summary> 23 /// Exampleインスタンスを格納するBinding用のObservableCollection 24 /// </summary> 25 public ObservableCollection<ExampleClass> ExampleClasses 26 { 27 get { return this._exampleClass; } 28 } 29 30 public DelegateCommand EndEditingRowCommand { get; private set; } 31 32 public EditDeviceDialogViewModel(IDialogService dialogService) 33 { 34 _dialogService = dialogService; 35 EndEditingRowCommand = new DelegateCommand(EndEditingRow); 36 37 // データソースからリストを読み出し、ExampleClassesに格納する処理 38 } 39 40 void EndEditingRow() 41 { 42 // 編集のコミットとコマンドを紐付ける事はできるが、コミットの取り消し動作を実装できなかった 43 } 44 45 public string Title => "Example"; 46 47 public event Action<IDialogResult>? RequestClose; 48 49 public bool CanCloseDialog() 50 { 51 return true; 52 } 53 54 public void OnDialogClosed() 55 { 56 } 57 58 public void OnDialogOpened(IDialogParameters parameters) 59 { 60 } 61 } 62 63 // モデルクラス 64 public record ExampleClass 65 { 66 public int Id { get; set; } 67 68 public string? ExampleName { get; set; } 69 70 public bool Enabled { get; set; } 71 } 72}

Xaml

1<UserControl x:Class="ExampleProject.Views.EditDialog" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:bh="http://schemas.microsoft.com/xaml/behaviors" 5 xmlns:prism="http://prismlibrary.com/" 6 prism:ViewModelLocator.AutoWireViewModel="True"> 7 <prism:Dialog.WindowStyle> 8 <Style TargetType="Window"> 9 <Setter Property="Height" Value="700"/> 10 <Setter Property="Width" Value="1200"/> 11 <Setter Property="ResizeMode" Value="NoResize"/> 12 </Style> 13 </prism:Dialog.WindowStyle> 14 <Grid> 15 <DataGrid ItemsSource="{Binding ExampleList}" AutoGenerateColumns="False" > 16 <bh:Interaction.Triggers> 17 <bh:EventTrigger EventName="RowEditEnding"> 18 <bh:InvokeCommandAction Command="{Binding EndEditingRowCommand}"/> 19 </bh:EventTrigger> 20 </bh:Interaction.Triggers> 21 <DataGrid.Columns> 22 <DataGridTextColumn Header="id" Binding="{Binding Id}" Width="Auto" /> 23 <DataGridTextColumn Header="example__name" Binding="{Binding ExampleName}" Width="Auto"/> 24 <DataGridCheckBoxColumn Header="enabled" Binding="{Binding Enabled}" Width="Auto"/> 25 </DataGrid.Columns> 26 </DataGrid> 27</Grid> 28</UserControl> 29

試したこと

DataGridのCellEditEnding、及びRowEditEndingイベントをコマンドにバインドしてコミットを検知する方法を考えましたが、検知はできてもコミット取り消し動作を実装する方法までたどり着けませんでした。

補足情報(FW/ツールのバージョンなど)

Visual Studio 2022
C# 10.0
Prism.Unity 8.1.97
ReactiveProperty.WPF 9.4.1

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

まず細かな動作検証をする場合は、(Prismのテンプレートではなく)素の状態かつコードビハインドで確認されたほうがいいと思います。

DataGridの編集時、セル・行それぞれコミットする際、無効な値(重複など)が含まれている場合にコミットをせずに編集状態を続行したい。

例えばIdにアルファベットを入力すると、行ヘッダーに赤い!とセルの枠が赤くなると思います。
この時セルの編集は終了せず、数値を入力するかEscキーでキャンセルするまでほかのセルの編集はできません。

行単位(オブジェクト毎)のバリデーションも可能です。
方法: DataGrid コントロールを使用して検証を実装する - WPF .NET Framework | Microsoft Learn

オブジェクトの変更のコミット・ロールバックに対応する場合は、IEditableObjectを実装します。
IEditableObject インターフェイス (System.ComponentModel) | Microsoft Learn

DataGridのCellEditEnding、及びRowEditEndingイベントをコマンドにバインドしてコミットを検知する方法を考えましたが、検知はできてもコミット取り消し動作を実装する方法までたどり着けませんでした。

xml

1<bh:InvokeCommandAction Command="{Binding EndEditingRowCommand}" PassEventArgsToCommand="True" />

cs

1public class EditDeviceDialogViewModel : BindableBase, IDialogAware 2{ 3 public DelegateCommand<DataGridRowEditEndingEventArgs> EndEditingRowCommand { get; } 4 private void EndEditingRow(DataGridRowEditEndingEventArgs e) 5 { 6 Debug.WriteLine($"EndEditingRow: {e.EditAction}"); 7 }

とすればDataGridRowEditEndingEventArgsは取得可能です(MVVM的にどうかは置いておいて)
EditActionでコミットかキャンセルかを判別できますが、IEditableObjectを実装するなら不要ではないでしょうか。


Viewに関わる処理ならコードビハインドで処理を行っても良いのか?

個人的には良い(MVVMを逸脱していない)と思っていますが、ビヘイビアに追い出せばMVVM厳格派の方も納得するでしょうw
【WPF】Behaviorの実装方法【MVVM】 #WPF - Qiita

実装例追記

無効な値(重複など)が含まれている場合にコミットをせずに編集状態を続行したい。

ユニークな値はIdのつもりでしょうか?
そのような値はデータベースの自動採番等で、ユーザーが編集する類のものではないような...

それともユーザー名のような、任意の値でいいけど被ってはいけないものでしょうか?
validation - Wpf datagrid validationrule for unique field - Stack Overflow

xml

1<Window 2 x:Class="Q3g1ql395qrqjbx.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:local="clr-namespace:Q3g1ql395qrqjbx" 6 Width="400" 7 Height="400"> 8 <Window.Resources> 9 <Style 10 x:Key="errorStyle" 11 BasedOn="{x:Static DataGridTextColumn.DefaultEditingElementStyle}" 12 TargetType="{x:Type TextBox}"> 13 <Setter Property="Validation.ErrorTemplate"> 14 <Setter.Value> 15 <ControlTemplate> 16 <StackPanel> 17 <Grid> 18 <AdornedElementPlaceholder x:Name="element" /> 19 <Border 20 Width="{Binding AdornedElement.ActualWidth, ElementName=element}" 21 HorizontalAlignment="Left" 22 BorderBrush="Red" 23 BorderThickness="1" /> 24 </Grid> 25 <ItemsControl 26 Background="Snow" 27 DisplayMemberPath="ErrorContent" 28 Foreground="Red" 29 ItemsSource="{Binding}" /> 30 </StackPanel> 31 </ControlTemplate> 32 </Setter.Value> 33 </Setter> 34 </Style> 35 </Window.Resources> 36 37 <Grid> 38 <DataGrid 39 AutoGenerateColumns="False" 40 InitializingNewItem="DataGrid_InitializingNewItem" 41 ItemsSource="{Binding Examples}" 42 RowValidationErrorTemplate="{x:Null}"> 43 <DataGrid.Resources> 44 <CollectionViewSource x:Key="examples" Source="{Binding Examples}" /> 45 </DataGrid.Resources> 46 <DataGrid.Columns> 47 <DataGridTextColumn 48 Width="*" 49 Binding="{Binding Id}" 50 Header="Id" 51 IsReadOnly="True" /> 52 53 <DataGridTextColumn 54 Width="5*" 55 EditingElementStyle="{StaticResource errorStyle}" 56 Header="Name"> 57 <DataGridTextColumn.Binding> 58 <Binding Path="Name" UpdateSourceTrigger="PropertyChanged"> 59 <Binding.ValidationRules> 60 <local:UniqueNameRule 61 CurrentCollection="{StaticResource examples}" 62 ValidatesOnTargetUpdated="True" 63 ValidationStep="UpdatedValue" /> 64 </Binding.ValidationRules> 65 </Binding> 66 </DataGridTextColumn.Binding> 67 </DataGridTextColumn> 68 69 <DataGridCheckBoxColumn Binding="{Binding Enabled}" Header="Enabled" /> 70 </DataGrid.Columns> 71 </DataGrid> 72 </Grid> 73</Window>

cs

1using System.Collections.ObjectModel; 2using System.ComponentModel; 3using System.Globalization; 4using System.Windows; 5using System.Windows.Controls; 6using System.Windows.Data; 7 8namespace Q3g1ql395qrqjbx; 9 10 11public partial class MainWindow : Window 12{ 13 public ObservableCollection<Example> Examples { get; } 14 15 public MainWindow() 16 { 17 InitializeComponent(); 18 DataContext = this; 19 20 Examples = new(Enumerable.Range(1, 5).Select(x => new Example { Id = x, Name = $"Example{x}", })); 21 } 22 23 private void DataGrid_InitializingNewItem(object sender, InitializingNewItemEventArgs e) 24 { 25 if (e.NewItem is Example example) 26 example.Id = Examples.Max(x => x.Id) + 1; 27 } 28} 29 30public record Example : IEditableObject 31{ 32 public int Id { get; set; } 33 public string? Name { get; set; } 34 public bool Enabled { get; set; } 35 36 private Example? backup; 37 public void BeginEdit() => backup = this with { }; 38 public void CancelEdit() => (Id, Name, Enabled) = (backup!.Id, backup.Name, backup.Enabled); 39 public void EndEdit() => backup = null; 40} 41 42// [validation - Wpf datagrid validationrule for unique field - Stack Overflow](https://stackoverflow.com/questions/10548357/wpf-datagrid-validationrule-for-unique-field) 43public class UniqueNameRule : ValidationRule 44{ 45 public CollectionViewSource? CurrentCollection { get; set; } 46 47 public override ValidationResult Validate(object value, CultureInfo cultureInfo) 48 { 49 if (value is BindingExpression { DataItem: Example current }) 50 { 51 foreach (var other in (Collection<Example>)CurrentCollection!.Source) 52 { 53 if (ReferenceEquals(current, other)) continue; 54 if (current.Name == other.Name) 55 return new ValidationResult(false, $"{current.Name}はすでに使用されています。"); 56 } 57 } 58 59 return ValidationResult.ValidResult; 60 } 61}

アプリ動画

投稿2024/03/12 12:47

編集2024/03/14 11:17
TN8001

総合スコア9412

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

saunaland

2024/03/14 11:57

>TN8001 様 回答有り難うございます。 検証中で返信返せず申し訳ありません。 >ユニークな値はIdのつもりでしょうか? >そのような値はデータベースの自動採番等で、ユーザーが編集する類のものではないような... IDなのですが、仕様上ユーザが編集する必要があります。 バインドするモデルクラスに、Idとなるプロパティが複数あり、複合キーになってまして、そのうちのいくつかはユーザが編集する必要があるものとなっております。
TN8001

2024/03/14 12:57

なるほど...(データベースは触らないのであんまりわかっていませんがw DataGridはあんまり凝った(実用的な)例がなくって難しいですね(追記コードもなかなかうまく動かなくって時間がかかってしまった^^; 業務アプリでは皆さん有償コントロールを使ってるんですかねぇ??
len_souko

2024/03/14 22:25

未確認ですが、参考になるかも? https://marunaka-blog.com/wpf-eventtrigger-event-binding-mvvm/9213/ > イベントのデータを格納するクラス(EventArgs)をコマンドパラメーターとして指定したい場合は、PassEventArgsToCommandをtrueにします。 https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.controls.datagrid.roweditending?view=windowsdesktop-8.0#system-windows-controls-datagrid-roweditending > このイベントを取り消すには、 Cancel イベント ハンドラーで 引数の プロパティを e に true 設定します。 取り消された場合、行は編集モードのままです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.46%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問