誰かのつぶやきに返答すると、どの発言に対する返答かという情報が
in_reply_to_status_idに入っています。
これをたどれば、会話を追うことができます。
今回は、返答するときに、投稿画面にこれまでの流れが表示されるようにしたいと思います。
また、twitterは最新のつぶやきが上に来ますが、
会話をたどるときは古い方が上の方が読みやすいですよね?
というわけで、古い方が上になるようにもできるようにします。
投稿画面の完成イメージ↓
まず、どうやって会話をピックアップするかですが、
(プログラム中の単位としての)Timelineだけで処理すると、
あるTimelineには自分の発言が入ってなかったりして、会話がすべて拾えない可能性があります。
まずTimelineの一覧を取得して、Timelineごとに検索して・・・とやるのもいいのですが、
今回はTimelineのアイテムをキャッシュするクラスを作ります。
次に、ModelでキャッシュするかViewModelでキャッシュするかですが、
ViewModelを再利用しよう、ということでViewModelに置きました。
あとから思い返せば、会話構造を取り出したりするので
Modelに置くのが順当のような気がしますが、
作ってしまった後だし、まだModelの方がいいと確信がもてるわけでもないのでこのまま。。
まずキャッシュを管理するクラス
class TimelineItemVMCache { #region private member private static TimelineItemVMCache _instance = new TimelineItemVMCache(); private List<TimelineItemViewModel> _items = new List<TimelineItemViewModel>(); private readonly long MaxItemNum = 1000; #endregion #region Constructor private TimelineItemVMCache() { } #endregion #region Property /// <summary> /// シングルトンのインスタンス /// </summary> public static TimelineItemVMCache Instance { get { return _instance; } } #endregion #region Public Method /// <summary> /// キャッシュにアイテムを追加する /// </summary> /// <param name="item">追加するアイテム</param> public void Add(TimelineItemViewModel item) { if (_items.Contains(item)) { return; } _items.Add(item); if (_items.Count > MaxItemNum) { _items.RemoveAt(0); } } /// <summary> /// キャッシュにアイテムが含まれるかどうかを調べる /// </summary> /// <param name="item"></param> /// <returns></returns> public bool Contains(TimelineItemViewModel item) { return _items.Contains(item); } /// <summary> /// 返答関係をたどって一連の会話を抽出する /// </summary> /// <param name="target">起点となるアイテム</param> /// <returns>一連のTimelineItemViewModel。引数target自身を含む</returns> public IEnumerable<TimelineItemViewModel> GetSequenceOfConversation(TimelineItemViewModel target) { TimelineItemViewModel result = null; TimelineItemViewModel next = target; yield return target; do { result = GetParentOfConversation(next); if (result != null) { yield return result; } next = result; } while (result != null); } /// <summary> /// 返答関係にあるアイテムの、元発言の方のアイテムを取得する /// </summary> /// <param name="target">調べたいアイテム</param> /// <returns>targetがあるアイテムの返答である場合には、そのアイテムを返す。targetが返答でない場合はnullを返す</returns> public TimelineItemViewModel GetParentOfConversation(TimelineItemViewModel target) { if (target.InReplyTo == null) { return null; } return _items.FirstOrDefault( item => (target.TypeOfSourceTimeline == item.TypeOfSourceTimeline && target.InReplyTo == item.Id) ); } #endregion }
会話を抽出するメソッド以外は簡単だと思います。
リストで最新の1000件を持つだけです。
会話を抽出するためにInReplyToというプロパティをModelからViewModelまで引っ張って来ています。
また、Search APIの結果とかと混ざらないように、Typeを識別できるようにしています。
(Search APIにはReply元ID情報が含まれないので、会話をたどれません)
あとは、1つ親をたどるメソッド(一番下のメソッド)と、
それを利用してずっと上まで辿るメソッド(下から2番目のメソッド)です。
あとはMainWindowViewModelでvm.AllItemにアイテムが追加されるごとに
キャッシュに入れるようにしています。
vm.AllItems.CollectionChanged += (sender, e) => { if (e.NewItems != null && e.NewItems.Count > 0) { foreach (var item in e.NewItems) { TimelineItemVMCache.Instance.Add(item as TimelineItemViewModel); } } };
※今はまだ関係ありませんが、のちのちはAllItemsはprivateメンバにしたいところ。
これで会話を辿れるようになったので、あとは最新を下に表示するようにするだけです。
ここは意見がわかれるところかもしれませんが、
リストの一番下のアイテムにフォーカスを移動するとか、
リストの昇順/降順を変更するとかの処理は、
ViewよりもViewModelで管理した方が便利かと思います。
すでにTimelineView.xamlで使っているCollectionViewSourceが
ビューへの抽象的なアクセス手段を提供してくれます。
というわけで、これをViewModelに移動させます。
private CollectionViewSource _viewSource; public TimelineViewModel(string displayName, IEnumerable<TimelineItemViewModel> initialItems, ListSortDirection sortDirection) : base(displayName) { _allItems = new ObservableCollection<TimelineItemViewModel>(initialItems); _viewSource = new CollectionViewSource(); _viewSource.Source = _allItems; _viewSource.SortDescriptions.Add(new SortDescription("CreatedAt", sortDirection));
特に難しいことはないと思います。コンストラクタでソート方向を渡すので、
ViewModel生成時に昇順/降順を選べます。
次に、ViewからこのCollectionViewSource をバインドするように変更します。
ここで意外と引っかかりましたが、バインディングは以下のようになります。
<ListBox ItemsSource="{Binding Path=ViewSource.View}"
CollectionViewSource のインスタンス自身ではなく、
CollectionViewSource インスタンスのViewプロパティにバインドします。
最後にTimelineItemViewModelの返答コマンドをちょこっと変更します。
キャッシュから会話を拾い出してコンストラクタへ渡すのと、
TimelineViewModelのコンストラクタにSort方向を渡すようにしました。
private void OnReplyCommandRequested(object parameter) { var references = TimelineItemVMCache.Instance.GetSequenceOfConversation(this).ToList<TimelineItemViewModel>(); var vm = new SubmitPanelViewModel(_userInfo, this.Item, new TimelineViewModel("", references, ListSortDirection.Ascending)); vm.SetTextToReply(); Utility.ViewConnector.Instance.Show(vm); }
今回のソースコード
http://wtwitter.codeplex.com/SourceControl/changeset/view/42422
0 件のコメント:
コメントを投稿