2010年3月22日月曜日

第29回 ステータスバーにメッセージを表示する

前回のスレッド化に伴い例外処理を追加しないといけないのですが、
どうせならエラーの内容を表示したいなと思って、
先にステータスバーを作ることにしました。

単純に作るんならすごく簡単なんですが、
一応MVVM式(と思っている)で作ろうと思いました。
なのでModelから。

単純にメッセージのイベントを飛ばすことと、
ある程度の数のログを持っておくことをやるクラスです。
アクセスしやすいようにstaticクラスにしています。
本格的にイベントログを取りたいのならちゃんとした.netの流儀でやるべきかもしれませんが、
そこまでやんなくてもいいかなと思っているので適当に作っています。


 enum EventType {
  /// <summary>
  /// 単純な進捗メッセージ
  /// </summary>
  Progress,
  /// <summary>
  /// エラー
  /// ※ある程度頻繁に起こりえるもの(リクエストのタイムアウトとか)
  /// </summary>
  Error,
  /// <summary>
  /// 致命的なエラー
  /// ※これはずっと残るようにする予定
  /// </summary>
  CriticalError
 }

 class MessageInfo {
  public EventType Type { set; get; }

  /// <summary>
  /// メッセージの要約(1行に収まる程度)
  /// </summary>
  public string Message { set; get; }

  /// <summary>
  /// メッセージの詳細
  /// (必要に応じて)
  /// </summary>
  public string Detail { set; get; }

  /// <summary>
  /// メッセージが発行された時間
  /// </summary>
  public DateTime Time { set; get; }
 }

 class MessageAddedEventArgs : EventArgs {
  public MessageAddedEventArgs(MessageInfo newItem) {
   Item = newItem;
  }

  /// <summary>
  /// 新しく追加されたメッセージ
  /// </summary>
  public MessageInfo Item { private set; get; }
 }

 /// <summary>
 /// イベントやエラーなどの情報を登録する
 /// 登録された情報はViewModelで拾ってWindowに表示されたり、ログに出力されたりする(予定)
 /// </summary>
 static class Message {
  #region private member
  private static object lockObj = new object();
  private static List<MessageInfo> _messageLog = new List<MessageInfo>();
  private const int _logNum = 200;
  private static List<MessageInfo> _criticalErrorLog = new List<MessageInfo>();
  #endregion

  /// <summary>
  /// メッセージが追加された通知イベント
  /// </summary>
  static public event EventHandler<MessageAddedEventArgs> MessageAdded;

  static Message() {
   MessageAdded += (sender, e) => {};
  }

  /// <summary>
  /// メッセージを追加する
  /// </summary>
  /// <param name="type"></param>
  /// <param name="message">メッセージの簡略表示(1行で収まる程度)</param>
  /// <param name="detail">詳細</param>
  static public void Add(EventType type, string message, string detail) {

   var item = new MessageInfo() {
    Type = type,
    Message = message,
    Detail = detail,
    Time = DateTime.Now
   };

   //複数のスレッドから同時にメッセージが飛んでくるかもしれないのでlockする
   lock (lockObj) {
    _messageLog.Add(item);
    if (_messageLog.Count > _logNum) {
     _messageLog.RemoveAt(0);
    }

    if (type == EventType.CriticalError) {
     _criticalErrorLog.Add(item);
    }
   }

   var handler = MessageAdded;
   handler(null, new MessageAddedEventArgs(item));
  }

  /// <summary>
  /// メッセージを追加する
  /// </summary>
  /// <param name="type"></param>
  /// <param name="message">メッセージの簡略表示(1行で収まる程度)</param>
  static public void Add(EventType type, string message) {
   Add(type, message, "");
  }
 }

どこから呼ばれるかわからないのでlockで排他処理しています。

StatusBarを表すViewModelを作ります。
簡単ですね。イベントが来るたびに文字列を更新するだけです。


 public class StatusBarViewModel : ViewModelBase {
  #region private member
  private string _text;
  private string _recentMessages;
  private List<string> _resentMessagesList = new List<string>();
  private const int _recentNumMax = 10;
  #endregion

  public StatusBarViewModel()
   : base("") {
   Message.MessageAdded += this.OnMessageAdded;
  }

  /// <summary>
  /// 一番最新のメッセージを表す文字列
  /// </summary>
  public string Text {
   set {
    if (_text != value) {
     _text = value;
     OnPropertyChanged("Text");
    }
   }

   get {
    return _text;
   }
  }

  /// <summary>
  /// 最近のメッセージを表す文字列(改行で区切られた1つの文字列)
  /// </summary>
  public string RecentMassages {
   set {
    if (_recentMessages != value) {
     _recentMessages = value;
     OnPropertyChanged("RecentMassages");
    }

   }
   get {
    return _recentMessages;
   }
  }

  private void OnMessageAdded(object sender, MessageAddedEventArgs e) {
   string text = string.Format("[{0}] {1}", e.Item.Time.ToLocalTime().ToString("HH:mm"), e.Item.Message);
   Text = text;

   _resentMessagesList.Add(text);
   if (_resentMessagesList.Count > _recentNumMax) {
    _resentMessagesList.RemoveAt(0);
   }
   RecentMassages = string.Join(Environment.NewLine, _resentMessagesList.ToArray());
  }
 }


Viewも簡単です。
UserControlを作って


<UserControl x:Class="WTwitter.View.StatusBarView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 <StatusBar>
  <StatusBarItem>
   <TextBlock Text="{Binding Path=Text}"/>
   <StatusBarItem.ToolTip>
    <TextBlock Text="{Binding Path=RecentMassages}"/>
   </StatusBarItem.ToolTip>
  </StatusBarItem>
 </StatusBar>
</UserControl>



MainWindowにDataTemplateを追加します。


  <DataTemplate DataType="{x:Type vm:StatusBarViewModel}">
   <vw:StatusBarView/>
  </DataTemplate>


あとはContentPresenterを追加して、
MainWindowViewModelにStatusBarViewModelにアクセスするプロパティを持たせています。

今回のソースコード
http://wtwitter.codeplex.com/SourceControl/changeset/view/43590

関係ないけど、追加する機能どうしようかなーと悩みはじめたので、
他でも宣伝していますが、アンケートをはじめました。
簡単な2問だけなので、よかったらご協力ください。

・ついったークライアントに必須の機能
http://poll.fm/1qn9k
・ついったークライアントに求めるもの
http://poll.fm/1qna2

1 件のコメント:

  1. よく考えたら複数スレッドから呼び出されることを考えてlockしていたら
    どこで元スレッドにInvokeするかを考えないといけないですね。
    もしくは別スレッドからはMessage.Addを呼び出さない設計とか。
    これも次回以降の課題で!

    返信削除