プロパティ変更待機
BindableBaseのPropertyChangedイベントでオブジェクトのプロパティ変更をトリガーにして何らかの処理を行うことはよくあるが、一連の処理の途中でプロパティの変更を待機したい場合、PropertyChangedイベントをどう使っていいのかわからない。
・・・ if (hoge.Foo) { // こんな感じで書くとイベントハンドラの解除ができない hoge.PropertyChanged += (s, e) => { if (e.PropertyName != "Foo") { return; } ・・・ }; // イベントハンドラを解除できるように独立したメソッドにするとコードの流れが分断されてしまう。 // 上の書き方でも十分分断されているが・・・ }
だもんで、こんな感じで待機できるものを作ってみた。
・・・ if (hoge.Foo) { await hoge.WaitPropertyChangeAsync<bool>("Foo"); }
public class PropertyChangedAwaitable : BindableBase { private Dictionary<string, TaskCompletionSource<bool>> _propertyChangedNotifiers = new Dictionary<string, TaskCompletionSource<bool>>(); public Task<T> WaitPropertyChangeAsync<T>(string propertyName) { lock (_propertyChangedNotifiers) { TaskCompletionSource<bool> tcs = null; if (!_propertyChangedNotifiers.TryGetValue(propertyName, out tcs)) { tcs = new TaskCompletionSource<bool>(); _propertyChangedNotifiers[propertyName] = tcs; } // 待機しているTaskの数だけプロパティ取得のコードが実行されてしまうのがいまいち。 // プロパティ取得は1回だけで、それを待機しているTaskに通知できればいいのだが・・・ return tcs.Task.ContinueWith(t => (T)GetType().GetRuntimeProperty(propertyName).GetValue(this), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } } protected override void OnPropertyChanged([CallerMemberName] string propertyName = null) { lock (_propertyChangedNotifiers) { TaskCompletionSource<bool> tcs = null; if (_propertyChangedNotifiers.TryGetValue(propertyName, out tcs)) { _propertyChangedNotifiers.Remove(propertyName); tcs.SetResult(true); } } base.OnPropertyChanged(propertyName); } }
MessageDialogの連続表示
public static class MessageDialogExtensions { // 最後にContinuousShowAsyncに指定されたMessageDialogのShowAsyncの戻り値 private static Task<IUICommand> _current = Task.FromResult<IUICommand>(null); // 同時にContinuousShowAsyncが呼ばれた時に_currentの変更を同期化するためのロックオブジェクト private static object _lockContinuous = new object(); /// <summary> /// MessageDialogを表示中に別のMessageDialogを単純に表示しようとするとエラーになるので、エラーにせずに順番に表示するためのメソッド。 /// 使い方はShowAsyncと同じ。 /// このメソッドを呼んだ後にキャンセルはできないので注意。 /// </summary> /// <param name="dialog"></param> /// <returns></returns> public static async Task<IUICommand> ContinuousShowAsync(this MessageDialog dialog) { lock (_lockContinuous) { Func<Task<IUICommand>, Task<IUICommand>> chain = async (t) => { return await dialog.ShowAsync().AsTask(); }; _current = _current.ContinueWith(chain).Unwrap(); } return await _current; } }
複数のキャンセル待ち
async/awaitで非同期のコードを書いているとCancellationTokenを良く使う。
複数のCancellationTokenを使う場合、「すべて」のキャンセルを待機するにはCancellationTokenSource#CreateLinkedTokenSource()を使えばいいらしいが、「いずれか」のキャンセルを待機する仕組みがないようなので作ってみた。
#2013/08/15追記
CancellationTokenSource#CreateLinkedTokenSource()は引数に指定した「いずれかの」Tokenのキャンセル待ちのために使用できます。
日本語のMSDNには一部「すべての」という記述がありますが誤訳と思われます。
英語のMSDNを確認してください。
public class MultiCancellationToken : IDisposable { private List<CancellationTokenRegistration> _cancellationTokenRegistrationList = new List<CancellationTokenRegistration>(); private CancellationTokenSource _cts = new CancellationTokenSource(); private bool _disposed; public MultiCancellationToken(params CancellationToken[] tokens) { foreach (var token in tokens) { _cancellationTokenRegistrationList.Add(token.Register(() => { Cancel(); })); } } private void Cancel() { lock (this) { if (_cts.IsCancellationRequested) { return; } _cts.Cancel(); foreach (var token in _cancellationTokenRegistrationList) { token.Dispose(); } _cancellationTokenRegistrationList.Clear(); } } public CancellationToken Token { get { return _cts.Token; } } protected void Dispose(bool dispose) { if (!dispose || _disposed) { return; } _disposed = true; lock (this) { foreach (var token in _cancellationTokenRegistrationList) { token.Dispose(); } _cancellationTokenRegistrationList.Clear(); _cts.Dispose(); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~MultiCancellationToken() { Dispose(false); } }
c#のDisposeの流儀がいまいちわかってない・・・
円マークの罠
長年この業界にいるが、utf-8に円マークを表す文字コードが2つあることを初めて知った。
0x5Cと0xA5。
0x5Cの方はバックスラッシュに割り当てられているが、表示上だけ円マークになってるんだとか。
今までバックスラッシュと円マークが同じコードだと思っていたのはこっちのことだったのか。
これ以外にちゃんとした円マークがあったとは知らなかった。
キーボードには円マークのキーとバックスラッシュのキーがそれぞれあるが、どっちを使っても入力される文字は0x5Cのようだ。
0xA5はどうやって入力するんだ?
文字コードまでは調べてないが、全角のシャープ記号は確か3つある。
そのうち一つは「いげた」という文字で、実際にはシャープ記号とは違うらしいのだが。
見た目は非常に良く似ている。
まぎらわしい。
Popupの罠
PopupにはIsLightDismissEnabledというプロパティがあって、ユーザーがPopup以外のところを操作するとPopupが勝手に閉じるようにできる。
まあ便利ではあるのだが、キーボード操作には反応しないことが判明。
ケース1
LayoutAwarePageを基にして画面を作っていると、Altキーと左右キーで画面を切り替えることができるが、この時にPopupが表示されていても閉じることはなく、画面が切り替わった後も表示されたまま。
ケース2
WindowsキーとZキーでアプリバーを表示し、アプリバー内のボタンにフォーカスがある状態でEnterキーを押すとボタンを押したことになるが、この時にPopupが表示されていても閉じることはない。
さてどうしたものか・・・
PopupとかPageに個別にロジック仕込めばどうにでもなるが、こういうのは共通化したいしな。
Popupの表示/非表示をどこかで一元管理して、LayoutAwarePageのOnNavigateFromとかアプリバーのOpenedで消すか?