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 を使用したアプリを見る限り、当然のように上記の処理が入っているので、常識のようだ。。。

2014年1月26日日曜日

仮想化してるのに表示が遅い

WPF で ListBox を横方向にスクロールするために下記のようなコードを書いた。
<ListBox
    HorizontalAlignment="Stretch"
    ItemsSource="{Binding Hoge}"
    VerticalAlignment="Stretch"
    VirtualizingPanel.IsVirtualizing="True"
    >
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>
このコードで実行したところ、描画が非常に遅くなった。
仮想化が効いていない?と悩み調べたところ、
http://stackoverflow.com/questions/2143655/wpf-list-boxes-and-virtualization
らしい。

VirtualizingPanel のプロパティを指定しているのに、VirtualizingPanel とは無縁の StackPanel を使っていれば仮想化されないのは当然だ。
<ListBox
    HorizontalAlignment="Stretch"
    ItemsSource="{Binding Hoge}"
    VerticalAlignment="Stretch"
    VirtualizingPanel.IsVirtualizing="True"
    >
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>
これで仮想化されるようになった。

2013年9月29日日曜日

Command が無いコントロールにマウスのイベントを登録する

Button などに Click イベントを登録する際、多分普通は Command を使う。
ただ Button 以外の、例えば Grid や Canvas などの Command プロパティが存在しないコントロールに Click イベントを登録する場合、どうすれよいか。

UIElement.InputBindings を使うらしい。

MainWindowViewModel という ViewModel クラスがあり、CommandParameter に指定された文字列をメッセージボックスで表示するコマンドが定義されているとする。

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vm="clr-namespace:WPFSample.ViewModels"
        x:Class="WPFSample.Views.MainWindow"
        Height="300"
        Width="300"
        >
    <Window.DataContext>
        <vm:MainWindowViewModel />
    </Window.DataContext>

    <Grid>
        <Canvas Background="Lime"
                Margin="10"
                >
            <Canvas.InputBindings>
                <!-- 左クリック -->
                <MouseBinding Command="{Binding LeftClickCommand}"
                              CommandParameter="左クリック"
                              MouseAction="LeftClick"
                              />
               
                <!-- 右クリック -->
                <MouseBinding Command="{Binding RightClickCommand}"
                              CommandParameter="右クリック"
                              MouseAction="RightClick"
                              />
            </Canvas.InputBindings>
        </Canvas>
    </Grid>
</Window>
これでキャンバスを左クリックすれば「左クリック」と書かれたメッセージボックスが表示され、右クリックすれば「右クリック」と書かれたメッセージボックスが表示される。