WPFを使う-Commandでエラーが出た場合

Commandの実行でエラーが出た場合の対処方法の一例を考えてみた。

初めに

ボタン等で、処理を実行する場合、ICommandインターフェースを持つコマンドを実行している。実際には、PrismのDelegateCommandを使っている。

コマンドの実行可否は、DelegateCommandのCanExecuteMethodを割り当て、その中で行うことで、事前チェックはできる。ただ、実行途中でエラーがあった場合はどうすればいいか。

ユーザーに確認をさせる必要があれば、PrismのInteractionRequestを使うようにすればいいと思う。ただ、Model側からどのように制御するのが望ましいのかは別途考える必要があるが。

ここでは、ユーザー確認は必要とせず、とりあえず実行した結果、どうなったのかをユーザーに通知する場合を考えてみた。

INotifyDataErrorInfoを使う

コマンドの実行結果を検証結果として位置付けてみて、ViewModel側にINotifyDataErrorInfoを実装してみた。

ViewModel側

public class MainWindowViewModel : BindableBase, INotifyDataErrorInfo {
    private string _title = "Prism Application";

	public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

	public string Title
    {
        get { return _title; }
        set { SetProperty(ref _title, value); }
    }

    public MainWindowViewModel()
    {
		Command = new DelegateCommand(() => ActionCommand(), () => CheckBox).ObservesProperty(() => CheckBox);
		this.ErrorsContainer = new ErrorsContainer<string>(
			x => this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(x)));
	}

	private void ActionCommand() {
		ErrorsContainer.ClearErrors();
		if (ComboBoxData == "Error") {
			ErrorsContainer.SetErrors(nameof(Command), new List<string>() { TextData });
		}
	}

	public string ComboBoxData { get; set; }
	public List<string> ComboList { get; set; }
	public string TextData { get; set; }
	bool IsChecked_ = false;
	public bool CheckBox {
		get => IsChecked_;
		set {
			SetProperty(ref IsChecked_, value);
			ErrorsContainer.ClearErrors();
		}
	}
	public DelegateCommand Command { get; set; }

	private ErrorsContainer<string> ErrorsContainer { get; }

	public bool HasErrors {
		get => ErrorsContainer.HasErrors;
	}

	public IEnumerable GetErrors(string propertyName) {
		return this.ErrorsContainer.GetErrors(propertyName);
	}
}

Commandというプロパティがコマンドの実行用で、エラーがあった場合、Commandに対してエラー情報を設定するようにした。それがActionCommandメソッド部分。

View側

<Window x:Class="test1.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="232" Width="299">
    <Window.Resources>
        <Style x:Key="WithError" TargetType="{x:Type Button}">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)/ErrorContent}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    
    <StackPanel>
        <ContentControl prism:RegionManager.RegionName="ContentRegion" />
        <ComboBox Text="{Binding ComboBoxData}" ItemsSource="{Binding ComboList}" IsEditable="True"/>
        <CheckBox IsChecked="{Binding CheckBox, Mode=TwoWay}" Content="CheckBox"/>
        <TextBox Text="{Binding TextData}"/>
        <Button Command="{Binding Command,ValidatesOnDataErrors=True}" Content="Command1" ToolTip="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)/ErrorContent}"/>
        <Button Command="{Binding Command,ValidatesOnDataErrors=True}" Style="{StaticResource WithError}" Content="Command2"/>
        <Button Command="{Binding Command,ValidatesOnDataErrors=True}" Content="Command3" ToolTip="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
        <Button Command="{Binding Command,ValidatesOnDataErrors=True}" Content="Command4" ToolTip="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
        <TextBlock>hogehoge</TextBlock>
    </StackPanel>
</Window>

View側は、テストとして4種類の書き方をしてみている。

実行結果

ボタンを押すと、コンボボックスの内容がErrorの場合、テキストエリアの内容をエラーメッセージを出すというもの。

一応、Commandに対してエラーを設定すると、どのボタンもエラー状態を表す赤枠が表示され、マウスを置いておくと、エラーメッセージがポップアップ表示されている。

コマンド内でエラーがあった場合、一応、この方法も適用可能なことが分かった。

補足:View側の書き方の違い

一応、どれもToolTipにエラーの内容(ErrorContent)をバインドしている。

Command2

MSDNのバインディングの検証の実装で提示されているやり方。
ToolTipに表示させるだけだと、たぶん過剰な書き方になるのかな。エラーの視覚化をもっと複雑にし、かつ、それを広範囲の部品に適用するのであれば、このようにリソースとして定義しているのもいいのかもしれない。

Command1, 3, 4

直接ToolTipにバインドしている。

単純にツールチップにメッセージを表示させるだけであれば、こちらの方がわかりやすいかもしれない。Command4はCommand1の別の書き方になるようで、CurrentItemは/(スラッシュ)で表すことができるらしいので、Command1の「(Validation.Errors)/ErrorContent」というのが一番スマートな書き方になるらしい。

Command3はMSDNに記載があった記述方法をそのまま持ってきたもので「(Validation.Errors)[0].ErrorContent」と0番目の要素を指定しているのだが、この記述では、エラーが解除された場合、デバッグモードでエラーメッセージ?が出てくる。

System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'ValidationError') from '(Validation.Errors)' (type 'ReadOnlyObservableCollection`1'). BindingExpression:Path=(0)[0].ErrorContent; DataItem='Button' (Name=''); target element is 'Button' (Name=''); target property is 'ToolTip' (type 'Object') ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: 指定された引数は、有効な値の範囲内にありません。
パラメーター名:index'

調べてみると、エラーが解除されたときには、Validation.Errorsがクリアされるので、0番目にアクセスできず、上記メッセージが出てくるとのこと。そのため、CurrentItemでの記述にする方がいいとのこと。

 

コメント

タイトルとURLをコピーしました