読者です 読者をやめる 読者になる 読者になる

開発記その 10 - Action の後に Action -

のんびりと作ってるわけですが、やっとアカウントの作成に取り掛かりました。

アカウントはダイアログあげて、そのダイアログの中で作ろうと思ったわけです。で、ダイアログが閉じたら親画面にあるアカウントの一覧にアカウントが反映されるって寸法です。

で、壁にぶち当たりました。 ViewModel からダイアログってどう呼び出そうかと。なんかまた、画面遷移みたいな形にしようかとも思ったのですが、あまり美味しくなさそうなんで、 Action で ShowDialogAction ってのを作って、呼び出し・表示することにしました。

で、壁にぶち当たりました。ダイアログが閉じたときに、先に書いたように一覧に反映させたいので親画面の ViewModel のメソッドを呼び出したいのです。ダイアログでの結果を、一覧に反映させるだけならオブジェクトを引き回してやればできると思うのですが、これもあまり美味しくなさそうで、たぶん、似たようなこういうパターンって結構ほかにもありそう。

  • ダイアログをあげて、その結果をうけて何か処理
  • 何か処理して、その結果を受けて画面遷移
  • アニメーションさせて、完了後に何か処理

などなど。

ってことで、 Action の後に Action を呼び出すような形にしました。

で、ここでひねってみたのが ShowDialogAction が発行する ActionCompleted イベント。たぶん Behavior 系をいろいろ作りそうな気がしたので、 INotifyActionCompleted ってインターフェイスを切って、それを ActionCompletedEventTrigger から読むようにしました。

public interface INotifyActionCompleted {
        
  event EventHandler<ActionCompletedEventArgs> ActionCompleted;

}
public class ActionCompletedTrigger : TriggerBase<DependencyObject> {

  protected override void OnAttached() {
    base.OnAttached();
    if(!(this.AssociatedObject is INotifyActionCompleted)){
      return;
    }
    INotifyActionCompleted target = 
      this.AssociatedObject as INotifyActionCompleted;
    target.ActionCompleted -= 
      AssociatedObject_ActionCompleted;
    target.ActionCompleted += 
      AssociatedObject_ActionCompleted;
  }

  protected override void OnDetaching() {
    base.OnDetaching();
    if (!(this.AssociatedObject is INotifyActionCompleted)) {
      return;
    }
    INotifyActionCompleted target = 
      this.AssociatedObject as INotifyActionCompleted;
    target.ActionCompleted -= 
      AssociatedObject_ActionCompleted;
  }

  private void AssociatedObject_ActionCompleted(object sender, 
    ActionCompletedEventArgs e) {
    
    this.InvokeActions(e);
  }
}


ActionCompletedイベントを発行する ShowDialogAction は以下のような感じ。

protected override void Invoke(object parameter) {

  if (!this.IsExecutableType()) {
    return;
  }
  ChildWindow window = Activator.CreateInstance(this.DialogType) as ChildWindow;
  if (window == null) {
    return;
  }
  bool isDialogBase = window is DialogBase;
  window.Closed += (s, e) => {
    if (this.ActionCompleted != null) {
      if (!window.DialogResult.HasValue || !window.DialogResult.Value) {
        return;
      }
      object param = isDialogBase ? ((DialogBase)window).ResultObject : null;
      this.ActionCompleted(this, new ActionCompletedEventArgs(param));
    }
  };
  if (isDialogBase) {
    ((DialogBase)window).Show(this.Parameter);
  } else {
    window.Show();
  }  
}

実際の XAML は以下のような感じ。

<HyperlinkButton Content="新しいアカウントを作成">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="Click">
      <localInteractivity:ShowDialogAction 
        DialogType="{Binding Dialogs.DialogViews.CreateNewAccountDialog, Source={StaticResource DialogWrapper}}">
        <i:Interaction.Triggers>
          <localInteractivity:ActionCompletedTrigger>
            <localInteractivity:InvokeMethodAction 
              TargetObject="{Binding DataContext, ElementName=Self}" 
              MethodName="AddedNewAccount">
              <localInteractivity:InvokeParameter PropertyName="Result" />
            </localInteractivity:InvokeMethodAction>
          </localInteractivity:ActionCompletedTrigger>
        </i:Interaction.Triggers>
      </localInteractivity:ShowDialogAction>
    </i:EventTrigger>
  </i:Interaction.Triggers>
</HyperlinkButton>

こうしといてあげることで、呼び出されたダイアログも、呼び出されたあとのメソッドもかなりすっきりしました。オブジェクトの取り回しとかもあまり意識せず、ダイアログはダイアログの結果を自身のプロパティにセットするだけ、呼び出された後のメソッドも引数を利用するだけ、それぞれの間は 各 Trigger や Action の設定で取り持つ形です。


これ作りながら、条件判定とかも付け加えられそうと思ったのですが、たぶんそれやると Action/Trigger の構造が複雑になりすぎてわけわからんようになりそうなのでいったん保留にしました。


※今回のチェックイン
http://moneybook.codeplex.com/SourceControl/changeset/changes/56058