マウスカーソルの形状を変える。

検索してる最中や保存している最中等々、「処理中ですよ」という状態を示す為に、カーソル形状を変えることがある。
よくある形状は砂時計かな。
普通に変更するのなら、
Mouse.Override = Cousors.Wait;//砂時計に

とする。戻すときは、
Mouse.Override = null;//元に戻す

となる。ところで、MVVMパターンでこのカーソルをどう変えよう?と悩んだ事がある。

マウスカーソルって「View」だよな?じゃあ、Viewで変えるべきだよな?

という変なこだわりの元、ViewModel側で上の処理を行いたくなかったので、Commandとして実装した。
簡単に書くと以下のとおり。

1.View側に依存関係プロパティを用意する。
2.View側でコマンドのインスタンスを用意する。
3.データバインディングを設定する。
4.ViewModel側でプロパティを用意する。
5.DataContextが設定されたら、2で作ったインスタンスを1のプロパティに設定する。
(続き)
1.View側に依存関係プロパティを用意する。
マスターコンテンツとViewModelクラスの連携で実装した、TawamureContentsクラスに実装する。
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Data;

namespace TawamureDays.Controls {

using TawamureDays.Commands;//RelayCommandクラスを格納中
using TawamureDays.Extensions;

/// <summary>
/// マスターページならぬ、マスターコンテンツ用コントロール。
/// </summary>
[System.Windows.Markup.ContentProperty("Content")]
public class TawamureContents : ContentControl {

/// <summary>
/// カーソル変更用コマンドを取得|設定します。
/// </summary>
public ICommand ChangeCursorCommand {
get { return (ICommand)GetValue(ChangeCursorCommandProperty); }
set { SetValue(ChangeCursorCommandProperty, value); }
}

/// <summary>カーソル変更用コマンド</summary>
public static readonly DependencyProperty ChangeCursorCommandProperty =
DependencyProperty.Register("ChangeCursorCommand", typeof(ICommand),
typeof(TawamureContents), new UIPropertyMetadata(null));
}
}

2.View側でコマンドのインスタンスを用意する。
3.データバインディングを設定する。
今回、このコマンドの実行する内容がViewに属するので。コマンドのインスタンスを用意するのは、View側となる。

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Data;

namespace TawamureDays.Controls {

using TawamureDays.Commands;//RelayCommandクラスを格納中
using TawamureDays.Extensions;

/// <summary>
/// マスターページならぬ、マスターコンテンツ用コントロール。
/// </summary>
[System.Windows.Markup.ContentProperty("Content")]
public class TawamureContents : ContentControl {

/// <summary>カーソル変更用コマンド</summary>
private RelayCommand changeCursorCommand_;

/// <summary>
/// コンストラクタ
/// </summary>
public TawamureContents() {
//データバインディングを設定します。
BindingOperations.SetBinding(
this,
TawamureContents.ChangeCursorCommandProperty,
new Binding("ChangeCursorCommand"){
Mode = BindingMode.OneWayToSource});

//インスタンスを生成します。
changeCursorCommand_ = new RelayCommand(ExecuteChangeCursorCommand);
return;
}
}
}
OneWayToSourceというモードは、Target(View)側からSource(ViewModel)側に更新を伝えるというもので、OneWayの逆になる。ただし、バインディング直後は、ソース側から値が流れてくるらしく、ViewModelがDataContextに設定されてから、コマンドのインスタンスを設定してやる必要がある。
なお、バインディング内の"ChangeCursorCommand"は、ViewModel側で用意すべきプロパティ名である。

4.ViewModel側でプロパティを用意する。
using System;
using System.Windows.Input;

namespace TawamureDays.ViewModels {

using TawamureDays.Commands;

/// <summary>
/// ウィンドウ用ViewModelの基底クラス
/// </summary>
public class BaseWindowVM : BaseViewModel {

/// <summary>
/// カーソル変更コマンドを取得|設定します。
/// </summary>
public ICommand ChangeCursorCommand {
get; set;
}
}
}

5.DataContextが設定されたら、2で作ったインスタンスを1のプロパティに設定する。
上にも書いたけど、コマンドを設定するのは、DataContextが設定されてからとなる。DataContextへの設定を検知するために、OnPropertyChangedメソッドをオーバーライドする。
namespace TawamureDays.Controls {

using TawamureDays.Commands;//RelayCommandクラスを格納中
using TawamureDays.Extensions;

/// <summary>
/// マスターページならぬ、マスターコンテンツ用コントロール。
/// </summary>
[System.Windows.Markup.ContentProperty("Content")]
public class TawamureContents : ContentControl {

#region ContentControlメンバ

/// <summary>
/// 依存関係プロパティ値 変更イベントハンドラ
/// </summary>
/// <param name="e">イベント引数</param>
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) {
base.OnPropertyChanged(e);

if (e.Property.Name == "DataContext") {
if (e.NewValue != null) {
//カーソル変更用コマンドを設定します。
//このタイミングで設定することで、VM側に設定されます。
this.ChangeCursorCommand = changeCursorCommand_;
}
}

return;
}

#endregion
}
}
これでViewModel側のプロパティに実体ができる筈。ViewModel側で実体を確認できたら成功となる。
おまけ
これを開発者に使ったもらうんだけど、一々nullチェックしたり、カーソルを元に戻したりする実装が必要になるので、仕事では、ラップしたメソッドを使ってもらう事にした。
namespace TawamureDays.ViewModels {

using TawamureDays.Commands;

/// <summary>
/// ウィンドウ用ViewModelの基底クラス
/// </summary>
public class BaseWindowVM : BaseViewModel {

/// <summary>
/// 指定のアクションを実行します。その間、カーソルを指定の状態に変更します。
/// </summary>
/// <param name="action">アクション</param>
/// <param name="cursor">Action実行中のカーソル</param>
protected void ActionWithChangingCursor(Action action, Cursor cursor) {
if (action == null) {
return;
}

try {
if (this.ChangeCursorCommand != null) {
try {
this.ChangeCursorCommand.Execute(cursor);
action();

} finally {
if (this.ChangeCursorCommand != null) {
//カーソルを元に戻します。
this.ChangeCursorCommand.Execute(null);
}
}

} else {
action();
}

} finally {
action = null;
cursor = null;
}
}
}
}

なお、使い方は、

this.ActionWithChangingCursor(() => {
//ここに長い処理を記述します。
//実行中は砂時計になる筈。
//終わると通常に戻ります。
}, Cursors.Wait);

あと、ウィンドウ終了時にコマンドのクリアを忘れずに実装する事(本稿では省略)。
どうでもいいけど、メソッド名長いなぁ。ActionWhileCusorでも良いかもな。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m