@ugaya40 ブログのやつ、エラー履歴のViewへの通知みたいな一方通行なものは、URL みたいにRx使って解決する方がよくないかな?
2011-02-23 19:20:17 via みについ to @ugaya40
という呟きを見て、あぁ確かに出来そうだと思ったので岩永さんのコードは見てないのですけど、ちょっくら遊んでみました。なるべくReactive Extensionsの生のクラスを使ってMessengerが提供してるような機能を実現してみようと思います。でも、普通はラップする形で実装するのがいいと思います。
メッセージクラスの作成
とりあえず、送るデータの入れ物を定義します。
public interface IMessage { Action<object> Callback { get; set; } object Parameter { get; set; } } public class Message<T, R> : IMessage { public Action<R> Callback { get; set; } public T Parameter { get; set; } public Message(T parameter, Action<R> callback) { this.Parameter = parameter; this.Callback = callback; } #region IMessage メンバー Action<object> IMessage.Callback { get { return (object o) => this.Callback((R)o); } set { this.Callback = (R r) => value(r); } } object IMessage.Parameter { get { return this.Parameter; } set { this.Parameter = (T)value; } } #endregion }
IMessengerというタイプセーフじゃない感じのインターフェースを継承する感じでタイプセーフっぽいMessageクラスを定義します。
TriggerとActionを定義する
そして、このIMessageを投げてくるIObservableを監視するトリガーを定義します。
public class ObservableTrigger : TriggerBase<FrameworkElement> { public IObservable<IMessage> Observable { get { return (IObservable<IMessage>)GetValue(ObservableProperty); } set { SetValue(ObservableProperty, value); } } // 我ながらさいてー public static readonly DependencyProperty ObservableProperty = DependencyProperty.Register("Observable", typeof(IObservable<IMessage>), typeof(ObservableTrigger), new UIPropertyMetadata(null, (s, e) => { // とりあえずということで古いオブジェクトのことは考慮しない var o = e.NewValue as IObservable<IMessage>; o.Subscribe(m => { ((ObservableTrigger)s).InvokeActions(m); }); })); } // とりあえずメッセージボックスだすアクションは作っとく public class MessageBoxAction : TriggerAction<FrameworkElement> { protected override void Invoke(object parameter) { var message = parameter as Message<string, MessageBoxResult>; var r = MessageBox.Show(message.Parameter, "確認", MessageBoxButton.OKCancel); // コールバックで結果を返す message.Callback(r); } }
あとは、これでSubject
// 戻りもIObservableなのでFirstで待つなりSubscribeするなりお好きに public static class ObserverExtensions { public static IObservable<R> Send<T, R>(this IObserver<Message<T, R>> self, T parameter) { var subject = new AsyncSubject<R>(); self.OnNext(new Message<T, R>( parameter, r => { subject.OnNext(r); subject.OnCompleted(); })); return subject.AsObservable(); } }
ViewModelの定義
そして、MainWindowViewModelを以下のように定義します。
public class MainWindowViewModel : NotificationObject { // こいつがメッセンジャーかわり private Subject<Message<string, MessageBoxResult>> subject = new Subject<Message<string, MessageBoxResult>>(); // メッセージを投げるコマンド private DelegateCommand alertCommand; // 画面に表示するメッセージ private string message; public DelegateCommand AlertCommand { get { return this.alertCommand = this.alertCommand ?? new DelegateCommand(() => { // メッセージを送って結果を待つ this.Message = subject.Send("こんにちは") .First() .ToString(); }); } } public string Message { get { return this.message; } set { this.message = value; base.RaisePropertyChanged(() => Message); } } // ViewへIObservableとして公開しておく。このプロパティにTriggerがバインドする public IObservable<Message<string, MessageBoxResult>> Observable { get { return subject.AsObservable(); } } }
Viewを作る
ViewはボタンとテキストブロックとTriggerとActionでさくっといきます。
<Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:l="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"> <Window.DataContext> <l:MainWindowViewModel /> </Window.DataContext> <Grid> <i:Interaction.Triggers> <l:ObservableTrigger Observable="{Binding Path=Observable}"> <l:MessageBoxAction /> </l:ObservableTrigger> </i:Interaction.Triggers> <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="button1" VerticalAlignment="Top" Width="75" Command="{Binding Path=AlertCommand}" /> <TextBlock HorizontalAlignment="Left" Margin="12,41,0,0" Name="textBlock1" Text="{Binding Path=Message}" VerticalAlignment="Top" /> </Grid> </Window>