2014年2月25日火曜日

Livet.Commands.ViewModelCommand.RaiseCanExecuteChanged で NullReferenceException が発生する

WPF のライブラリに Livet というものがある。
現状全く使いこなせていないが、とても素晴らしいものだと思う。

さて、この Livet には ViewModelCommand という ICommand の実装があるのだが、どうも使い方を間違っているようで RaiseCanExecuteChanged を呼び出すと NullReferenceException が発生する。

当初、説明書きなぞ一切読まない自分は下記のようなコードを書いていた。
<!-- MainWindow.xaml -->
<TextBox
    HorizontalAlignment="Left"
    Height="23"
    Margin="10,10,0,0"
    TextWrapping="Wrap"
    Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
    VerticalAlignment="Top"
    Width="120"
    />
        
<Button
    Command="{Binding RunCommand}"
    Content="Run"
    HorizontalAlignment="Left"
    Margin="135,9,0,0"
    VerticalAlignment="Top"
    Width="75"
    />

// MainWindowViewModel.cs
public MainWindowViewModel()
{
    this.RunCommand = new ViewModelCommand(ExecuteRunCommand, CanExecuteRunCommand);
}

public ViewModelCommand RunCommand { private set; get; }

private void ExecuteRunCommand()
{
    DoSomething(this.Text);
}

private bool CanExecuteRunCommand()
{
    return !string.IsNullOrEmpty(this.Text);
}

private string _text;
public string Text
{
    set
    {
        if(this._text != value)
        {
            this._text = value;
            RaisePropertyChanged();
            this.RunCommand.RaiseCanExecuteChanged();
        }
    }
    get
    {
        return this._text;
    }
}
このコードでテキストボックスに何か入力すると NullReferenceException が発生する。
ぐぐってもいまいち解決方法がわからない。

仕方がないので GitHub に上がっている Livet の RaiseCanExecuteChanged の実装をのぞきながら確認していく。
するとどうも Livet.DispatcherHelper.UIDispatcher が null なのが原因なようだ。

このキーワードでぐぐるとどうも App.OnStartup 等で下記のような処理を入れなくてはいけないらしい。
protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    Livet.DispatcherHelper.UIDispatcher = this.Dispatcher;
}
これにより無事 NullReferenceException を回避できた。

知らないとハマる。
GitHub 等で公開されている Livet を使用したアプリを見る限り、当然のように上記の処理が入っているので、常識のようだ。。。