2010年1月24日日曜日

第7回(後編) 複数のタイムラインを表示する

前編からの続きです。

ViewModelを説明します。
(ちなみに、TimelineItemViewModelの方は変更ありません。)



 class TimelineViewModel {
  private ObservableCollection<TimelineItemViewModel> _allItems = new ObservableCollection<TimelineItemViewModel>();
  private DispatcherTimer _timer = new DispatcherTimer();
  private Timeline _timeline;

  /// <summary>
  /// コンストラクタ
  /// </summary>
  /// <param name="displayName">ビューに表示するときの名前</param>
  /// <param name="target">ViewModelが表示対象とするタイムライン</param>
  public TimelineViewModel(string displayName, Timeline target) {
   _displayName = displayName;
   _timeline = target;

   //タイムラインにアイテムが追加されたら通知を受け取るように設定する
   _timeline.StatusAdded += this.OnStatusAdded;

   //定期更新のタイマーを設定する
   _timer.Interval = TimeSpan.FromMinutes(1);
   _timer.Tick += this.OnUpdateTimerTicked;
  }

  /// <summary>
  /// ViewModelが対象とするすべてのアイテム
  /// </summary>
  public ObservableCollection<TimelineItemViewModel> AllItems {
   get { return _allItems; }
  }

  private string _displayName;
  /// <summary>
  /// ビューに表示する場合の名称
  /// </summary>
  public string DisplayName {
   get { return _displayName; }
  }

  /// <summary>
  /// タイムラインを最新に更新する(ネットワークにアクセスする)
  /// </summary>
  public void Update() {
   _timeline.Update();
  }

  /// <summary>
  /// Updateのタイマーが発生したときに呼び出されるメソッド
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private void OnUpdateTimerTicked(object sender, EventArgs e) {
   Update();
  }

  /// <summary>
  /// 定期更新のタイマーを作動させる
  /// </summary>
  public void StartUpdateTimer() {
   _timer.Start();
  }

  /// <summary>
  /// 定期更新のタイマーを止める
  /// </summary>
  public void StopUpdateTimer() {
   _timer.Stop();
  }

  /// <summary>
  /// タイムラインにStatusが追加されたときに呼び出されるメソッド
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e">追加されたアイテムを保持しているクラス</param>
  public void OnStatusAdded(object sender, StatusAddedEventArgs e) {
   _allItems.Add(new TimelineItemViewModel(e.Item));
  }
 }



タイマーを持ってきましたが、前回から使っていたので、これに関連する部分はすぐわかると思います。



_timeline.StatusAdded += this.OnStatusAdded;




の部分で、前編で説明したEverntHandlerに、
「Statusが追加されたらOnStatusAddedを呼んでくれ」ということを設定しています。

あとは、Viewで表示するときのためにDisplayNameを作っています。

次に、メインウィンドウを表すViewModelを新しく作ります。



 class MainWindowViewModel {
  public MainWindowViewModel() {
  }

  /// <summary>
  /// ViewModelの初期化
  /// </summary>
  public void Initialize() {
   //Modelの作成
   var list = new List<TimelineViewModel>() {
    new TimelineViewModel( "User",
     new Timeline("http://twitter.com/statuses/user_timeline/yuki1090.json")),
    new TimelineViewModel("Public",
     new Timeline("http://twitter.com/statuses/public_timeline.json"))
   };

   //作成したModelの追加と作動開始
   foreach (var vm in list) {
    Timelines.Add(vm);
    vm.Update();//initialize時に1回更新する
    vm.StartUpdateTimer();
   }
  }

  private ObservableCollection<TimelineViewModel> _timelines = new ObservableCollection<TimelineViewModel>();

  /// <summary>
  /// MainViewに表示するすべてのタイムライン
  /// </summary>
  public ObservableCollection<TimelineViewModel> Timelines {
   get { return _timelines; }
  }
 }




試しに自分のタイムラインと、Publicタイムラインを表示するようにしてみました。
まずInitialize()では2つのTimelineを作って、これを保持します。動作も開始させます。
Viewに対してはTimelinesですべてのタイムラインを公開します。

Viewに当たるWindow1クラスは以下のようになります。



 /// <summary>
 /// Window1.xaml の相互作用ロジック
 /// </summary>
 public partial class Window1 : Window {
  MainWindowViewModel viewModel;
  public Window1() {
   viewModel = new MainWindowViewModel();
   this.DataContext = viewModel;

   InitializeComponent();
  }

  private void Window_Loaded(object sender, RoutedEventArgs e) {
   viewModel.Initialize();
  }
 }


こっちはここまで単純になりました。
DataContextはMainWindowViewModelになりました。
コンストラクタではViewModelを作成して、
ウィンドウがLoadされたときにViewModelを初期化するだけです。
※ViewModelはInitialize()はViewのコンストラクタにいれて、
LoadedはViewModelの動作開始みたいな作りにした方がいいかも、
とこれを書いている時点で思いました。

最後にWindowのXAML



<Window x:Class="WTwitter.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WTwitter" Height="300" Width="300" 
 Loaded="Window_Loaded">
 <TabControl ItemsSource="{Binding Path=Timelines}">
  <TabControl.ItemTemplate>
   <DataTemplate>
    <TextBlock Text="{Binding Path=DisplayName}"/>
   </DataTemplate>
  </TabControl.ItemTemplate>
  <TabControl.ContentTemplate>
   <DataTemplate>
    <ListBox ItemsSource="{Binding Path=AllItems}"
     ScrollViewer.HorizontalScrollBarVisibility="Disabled"
     HorizontalContentAlignment="Stretch">
     <ListBox.ItemTemplate>
      <DataTemplate>
       <Border BorderBrush="LightGray" BorderThickness="1" CornerRadius="5" Margin="1">
        <DockPanel>
         <Image Source="{Binding Path=ProfileImageUrl}" Width="32" Height="32" DockPanel.Dock="Left"/>
         <TextBlock Text="{Binding Path=Name}" DockPanel.Dock="Top"/>
         <TextBlock Text="{Binding Path=Text}" TextWrapping="Wrap"/>
        </DockPanel>
       </Border>
      </DataTemplate>
     </ListBox.ItemTemplate>
    </ListBox>
   </DataTemplate>
  </TabControl.ContentTemplate>
 </TabControl>
</Window>



大きな構造として外側をTabControlで包みました。

でもItemTemplateがタブの上部を表していて、
ContentTemplateが下のメイン領域を表していることがわかればあとは簡単だと思います。

ItemTemplateではViewModelに新しく作ったDisplayNameを表示する用にしています。
ContentTemplateは前回とほとんど変わっていません。
タブの項目としてMainWindowViewModelに登録されている
Timeline(つまりTimelineViewModel)の数だけ表示されて、
タブを押したときの中身はその選択されたTimelineViewModelに登録されている
AllItemsが表示されます。

ここまでのソース

http://wtwitter.codeplex.com/SourceControl/changeset/view/38815

0 件のコメント:

コメントを投稿