本記事では、DataGrid の行を右クリック → 編集メニュー → 編集ダイアログ表示 → 保存 or キャンセル → ウィンドウを閉じる
という一連の流れを、MVVM + CommunityToolkit.Mvvm を使った実装としてまとめます。
この記事で扱う構造は以下のとおりです。
🔧 構成概要
- DataGrid 行の右クリックメニュー(View)
- EditCommand を受けて編集処理をする MainViewModel
- 編集ダイアログ(View / ViewModel)
- Messenger を利用してダイアログを閉じる仕組み
- DialogService(ViewModel からウィンドウを開くための仲介)
- ダイアログを閉じるためのイベント & メッセージクラス(← 追記した部分)
1. DataGrid 行に右クリックメニューを追加する(View)
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<!-- DataGrid 全体の DataContext(MainViewModel) を Tag に保持 -->
<Setter Property="Tag"
Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGrid}}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="編集..."
Command="{Binding PlacementTarget.Tag.EditCommand,
RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
ポイント
-
CommandParameter="{Binding}"により、右クリックされた行のデータ(EmployeeRecord)が渡される -
PlacementTarget.Tagから MainViewModel を取得してEditCommandを実行
2. MainViewModel が行編集を処理する
[RelayCommand(CanExecute = nameof(CanEdit))]
private async Task EditAsync(EmployeeRecord? record)
{
var target = record ?? SelectedEmployee;
if (target is null)
{
return;
}
var editable = (EmployeeRecord)target.Clone();
var updated = _dialogService.ShowEditDialog(editable);
if (updated is null)
{
return;
}
updated.Id = target.Id;
await _databaseService.UpdateAsync(updated);
await LoadAsync();
}
3. 編集ダイアログの View(EditEmployeeWindow)
<Window x:Class="MvvmSampleContext.Views.EditEmployeeWindow"
Title="{Binding Title}"
Height="260"
Width="420"
WindowStartupLocation="CenterOwner">
<Grid Margin="16">
<!-- 名前 -->
<TextBlock Text="名前" />
<TextBox Grid.Column="1"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<!-- 社員番号 -->
<TextBlock Grid.Row="1" Text="社員番号" />
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding EmployeeNumber, UpdateSourceTrigger=PropertyChanged}" />
<!-- 部署 -->
<TextBlock Grid.Row="2" Text="部署" />
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding Department, UpdateSourceTrigger=PropertyChanged}" />
<!-- エラー表示 -->
<TextBlock Grid.Row="3"
Grid.ColumnSpan="2"
Foreground="Tomato"
Text="{Binding ErrorMessage}" />
<!-- ボタン -->
<StackPanel Grid.Row="4" Grid.ColumnSpan="2" Orientation="Horizontal">
<Button Content="キャンセル" Command="{Binding CancelCommand}" />
<Button Content="保存" Command="{Binding SaveCommand}" />
</StackPanel>
</Grid>
</Window>
4. 編集ダイアログ ViewModel(EditEmployeeViewModel)
[RelayCommand]
private void Save()
{
if (string.IsNullOrWhiteSpace(Name) || string.IsNullOrWhiteSpace(EmployeeNumber))
{
ErrorMessage = "名前と社員番号は必須です。";
return;
}
ErrorMessage = string.Empty;
var updated = new EmployeeRecord
{
Id = _original.Id,
Name = Name.Trim(),
EmployeeNumber = EmployeeNumber.Trim(),
Department = Department.Trim(),
};
UpdatedRecord = updated;
_messenger.Send(new DialogCloseRequestedMessage(
new DialogCloseRequestedEventArgs(true, updated)));
}
[RelayCommand]
private void Cancel()
{
UpdatedRecord = null;
_messenger.Send(new DialogCloseRequestedMessage(
new DialogCloseRequestedEventArgs(false, null)));
}
5. ダイアログサービス(DialogService)
public EmployeeRecord? ShowEditDialog(EmployeeRecord editable)
{
var vm = new EditEmployeeViewModel(editable);
var window = new EditEmployeeWindow
{
DataContext = vm,
Owner = Application.Current.MainWindow,
};
var result = window.ShowDialog();
if (result == true)
{
return vm.UpdatedRecord;
}
return null;
}
6. ✔ 必須:ダイアログを閉じるための EventArgs & Message
編集ダイアログは、ViewModel → Messenger → Window の順で閉じられます。
6-1. DialogCloseRequestedEventArgs(必須)
using System;
using MvvmSampleContext.Models;
namespace MvvmSampleContext.ViewModels;
public class DialogCloseRequestedEventArgs : EventArgs
{
public DialogCloseRequestedEventArgs(bool dialogResult, EmployeeRecord? updatedRecord)
{
DialogResult = dialogResult;
UpdatedRecord = updatedRecord;
}
public bool DialogResult { get; }
public EmployeeRecord? UpdatedRecord { get; }
}
6-2. DialogCloseRequestedMessage(必須)
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace MvvmSampleContext.ViewModels;
public sealed class DialogCloseRequestedMessage : ValueChangedMessage<DialogCloseRequestedEventArgs>
{
public DialogCloseRequestedMessage(DialogCloseRequestedEventArgs value)
: base(value)
{
}
}
6-3. EditEmployeeWindow でメッセージを受け取る
public EditEmployeeWindow()
{
InitializeComponent();
WeakReferenceMessenger.Default.Register<EditEmployeeWindow, DialogCloseRequestedMessage>(
this,
static (recipient, message) =>
{
recipient.OnCloseRequested(message.Value);
});
}
private void OnCloseRequested(DialogCloseRequestedEventArgs args)
{
DialogResult = args.DialogResult;
Close();
}
🔚 全体の流れ(まとめ)
- DataGrid 行を右クリック → 編集メニューを押す
- EditCommand が実行され、対象 EmployeeRecord が渡される
- DialogService が編集ウィンドウを表示
- 編集ダイアログで SaveCommand → Messenger.Send()
- Window がメッセージを受信し、DialogResult を設定して Close()
- DialogService が UpdatedRecord を返す
- MainViewModel が DB 更新 → 再読み込み
この記事とコードをそのまま使えば、MVVMらしく整った 「右クリック編集 → ダイアログ → 更新」 が実現できます。