sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

WPF Prism サンプルコードの学習6(公式外サンプル1)

f:id:shikaku_sh:20211013180838p:plain:w400

前回まで Prism の公式サンプルを確認しました。補足として、Microsoftokazukiさんもサンプルを公開してくれていたので、こちらも見ていこうと思います。

サンプルは 2017 年 1 月公開のものなので、少し古いです。注意しましょう。

1. Bootstrap

これはサンプル1の内容で、完全にカバーできていると思います。

2. ViewModelLocator

モジュール化していませんが、View と ViewModel を紐づけるミニマムなサンプルです。

Dependency 属性が解決できないかもしれませんが、現在と名前空間が異なると思います。

using Microsoft.Practices.Unity;
using ViewModelLocatorSampleApp.Models;

namespace ViewModelLocatorSampleApp.ViewModels
{
    class ShellViewModel
    {
        [Dependency]
        public MessageProvider MessageProvider { get; set; }
    }
}
using Unity;
using ViewModelLocatorSampleApp.Models;

namespace ViewModelLocatorSampleApp.ViewModels
{
    class ShellViewModel
    {
        [Dependency]
        public MessageProvider MessageProvider { get; set; }
    }
}

3. Module

これはサンプル2の内容です。

4. MVVM の基本クラス

7.1 Modules.AppConfig のやり方でモジュールを実装したやり方です。コマンドは、11 DelegateCommand でフォローしています。

新しい要素は、ErrorsContainer です。

<StackPanel>
    <Label Content="入力"
            Target="{Binding ElementName=TextBoxInput}" />
    <TextBox x:Name="TextBoxInput" 
                Text="{Binding Input, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    <TextBlock x:Name="TextBlockErrorMessage" 
                Text="{Binding ElementName=TextBoxInput, Path=(Validation.Errors)/ErrorContent}"/>
</StackPanel>
class ErrorsContainerSampleViewModel : BindableBase, INotifyDataErrorInfo
{
    public string HeaderText { get; } = "ErrorContainerSample";

    private string input;

    [Required(ErrorMessage = "入力してください")]
    public string Input
    {
        get { return this.input; }
        set { this.SetProperty(ref this.input, value); }
    }


    public ErrorsContainerSampleViewModel()
    {
        this.ErrorsContainer = new ErrorsContainer<string>(
            x => this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(x)));
    }

    #region Validation
    private ErrorsContainer<string> ErrorsContainer { get; }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if(!base.SetProperty<T>(ref storage, value, propertyName))
        {
            return false;
        }

        var context = new ValidationContext(this)
        {
            MemberName = propertyName
        };

        var errors = new List<ValidationResult>();
        if (!Validator.TryValidateProperty(value, context, errors))
        {
            this.ErrorsContainer.SetErrors(propertyName, errors.Select(x => x.ErrorMessage));
        }
        else
        {
            this.ErrorsContainer.ClearErrors(propertyName);
        }

        return true;
    }

    public bool HasErrors
    {
        get
        {
            return this.ErrorsContainer.HasErrors;
        }
    }

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

Prism には、INotifyDataErrorInfo の実装を補助する ErrorsContainer が追加されていて、これを利用すると簡単に入力値の検証をすることができるようになる。

には、INotifyDataErrorInfo は以下のインターフェースを実装する必要がある:

  • bool HasErrors { get; }
  • event EventHandler ErrorsChanged;
  • IEnumerable GetErrors(string propertyName);

DataAnnotations は前からある機能なので詳細を割愛しますが、以下のようにインターフェースの実装を ErrorsContainer に対応をおまかせできる。

<Grid>
<Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox x:Name="TextBoxInput" Grid.Row="0" 
            Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            />
<TextBlock Grid.Row="1"
            Text="{Binding ElementName=TextBoxInput, Path=(Validation.Errors)/ErrorContent}"
            Foreground="Red" Margin="10"/>
</Grid>
[Required(ErrorMessage = "入力してください")]
public string Text
{
    get => _text;
    set
    {
        CheckErrors(value);
        SetProperty(ref _text, value); 
    }
}

public MainWindowViewModel()
{
    ErrorsContainer = new ErrorsContainer<string>(
        p => this.ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(p))
    );
}

private ErrorsContainer<string> ErrorsContainer { get; }

public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool HasErrors => ErrorsContainer.HasErrors;
public IEnumerable GetErrors(string propertyName) => ErrorsContainer.GetErrors(propertyName);

public void CheckErrors(string value, [CallerMemberName] string propertyName = null)
{
    var context = new ValidationContext(this)
    {
        MemberName = propertyName
    };

    var errors = new List<ValidationResult>();

    if (!Validator.TryValidateProperty(value, context, errors))
    {
        ErrorsContainer.SetErrors(propertyName, errors.Select(x => x.ErrorMessage));
    }
    else
    {
        ErrorsContainer.ClearErrors(propertyName);
    }
}

ErrorsContainer にまとめつつ、プロパティ名でさらにまとめてあるので、Text プロパティのエラー検知に加えて、Text2, Text3... と検知したいプロパティを増やしても、INotifyDataErrorInfo インターフェースの実装はもちろん、CheckErrors も修正なしで運用することができる。

CheckErrors は、以下のように SetProperty を override してやってしまうことも。
- protected override bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null)

5. InteractionRequest

おそらくこれは、公式サンプルから消された 25. Interactivity - NotificationRequest に相当する機能だと思います。

IInteractionRequestAware というインターフェースは存在していませんし、INotification というインターフェースも存在しません。(おそらく)

現在は非推奨ということでよいと思います。

6. Navigation

  1. Navigation と同じ内容だけど、コーディングされている内容がバージョン違いでちょっと異なっています。あと、KeepAlive も含まれていたりするので、まとめてある。

7. EventAggregator

  1. UsingEventAggregator と同じ内容。書き方としては、公式サンプルのほうがキレイになっているんで、見るべき点はないと思います。

8. ModuleLoadSeq

7.4 Modules - LoadManual のような内容。コーディングテクニックというよりも、そういうものなので、理解だけしておけばよいと思います。

9. RegionBehavior

IRegionBehavior は現在も存在しているインターフェースです。WPF の Behavior を Region でやるための機能です。サンプルでは IDispose を ViewModel で実行するようなものになっています。

これは公式サンプルにはないものなので、知っておいてもよいと思います。

10. ModuleCatalog

7.1 Modules - AppConfig のような内容です。コーディングテクニックというよりも、そういうものなので、理解だけしておけばよいと思います。

まとめ

2と9が、参考になる可能性があると思います。

参考