2011-07-03 3 views
3

新しいSystem.Reactiveビットを試して、リストビュー内の項目をコンテキストメニューのクリックに応じてアクションを実行することを簡略化するかどうかを考えました。 これまでの設定ビットはやや簡単ですが、私は2つのイベントストリーム(アイテムの選択とメニューのクリック)を正しい方法で組み合わせることに戸惑っています。コンテキストメニューの操作にRxを使用する

これは私がこれまで (bookListViewは、私の本が含まれており、メニューbookListContextMenuを表示)

private void frmBookList_Load(object sender, EventArgs e) 
{ 
    //filter click events to right clicks over a ListViewItem containing a actual book 
    var rightclicks = from click in Observable.FromEventPattern<MouseEventArgs>(bookListView, "MouseClick") 
         where click.EventArgs.Button == MouseButtons.Right && 
         ClickedOnBook((ListView)click.Sender, click.EventArgs) != null 
         select click; 

    //subscribe to clicks to display context menu at clicked location 
    rightclicks.Subscribe(click => bookListContextMenu.Show((Control)click.Sender, click.EventArgs.Location)); 
    //subscribe to clicks again to convert click into clicked book 
    var rightclickedbook = rightclicks.Select(click => ClickedOnBook((ListView)click.Sender, click.EventArgs)); 

    //capture context menu click, convert to action enum 
    var clickaction = Observable.FromEventPattern<ToolStripItemClickedEventArgs>(bookListContextMenu, "ItemClicked") 
          .Select(click => GetAction(click.EventArgs.ClickedItem)); 

    //combine the two event streams to get a pair of Book and Action 
    //can project to an anonymoue type as it will be consumed within this method 
    var bookaction = clickaction.CombineLatest(rightclickedbook, (action, book) => new { Action = action, Book = book }); 

    //subscribe to action and branch to action specific method 
    bookaction.Subscribe(doaction => 
    { 
     switch (doaction.Action) 
     { 
      case BookAction.Delete: 
       DeleteBookCommand(doaction.Book); 
       break; 
      case BookAction.Edit: 
       EditBookCommand(doaction.Book); 
       break; 
     } 
    }); 
} 

public enum BookAction 
{ 
    None = 0, 
    Edit = 1, 
    Delete = 2 
} 

private BookAction GetAction(ToolStripItem item) 
{ 
    if (item == deleteBookToolStripMenuItem) return BookAction.Delete; 
    else if (item == editBookToolStripMenuItem) return BookAction.Edit; 
    else return BookAction.None; 
} 

private Book ClickedOnBook(ListView lview, MouseEventArgs click) 
{ 
    ListViewItem lvitem = lview.GetItemAt(click.X, click.Y); 
    return lvitem != null ? lvitem.Tag as Book : null; 
} 

private void DeleteBookCommand(Book selectedbook) 
{ 
    //code to delete a book 
} 

private void EditBookCommand(Book selectedbook) 
{ 
    //code to edit a book 
} 

問題は結合関数である持っているものです。 「CombineLatest」を使用してコンテキストメニューを初めて使用した場合は、その後でそれぞれ を右クリックすると、新しい選択項目で前のアクションが再び呼び出されます。

私は「Zip」を使って本を右クリックしますが、コンテキストメニューをクリックするのではなく、次に右クリックして実際にメニューをクリックすると、アクションが呼び出されます第1の選択であり、第2の選択ではない。

私は、さまざまな形式のタイムバッファとウィンドウと最新のものなどを試しましたが、通常、メニューが表示されるとすぐにブロックして選択できないようにしたり、メニューが表示された場合は空のシーケンスについての例外を受け取りましたアイテムはクリックされていませんでした。

私は行方不明だがこれを行う簡単な方法が必要であると確信しているが、私はそれが何であるか分からない。

おそらくこれは?

//the menu may be closed with or without clicking any item 
var contextMenuClosed = Observable.FromEventPattern(bookListContextMenu, "Closed"); 
var contextMenuClicked = Observable.FromEventPattern<ToolStripItemClickedEventArgs>(bookListContextMenu, "ItemClicked"); 

//combine the two event streams to get a pair of Book and Action 
//which we can project to an anonymoue type as it will be consumed within this method 
var bookaction = from mouseclick in rightclicks 
       let book = ClickedOnBook((ListView)mouseclick.Sender, mouseclick.EventArgs) 
       from menuclick in contextMenuClicked.Take(1).TakeUntil(contextMenuClosed) 
       let action = GetAction(menuclick.EventArgs.ClickedItem) 
       select new { Action = action, Book = book }; 
+0

おそらく答えはSelectMany、Take、TakeUntilですが、もう5時間はまだ投稿できます。 – eddwo

+0

私の答えの2番目のオプションを見てみましょう。 –

答えて

2

それらを解決する最も簡単な方法は、全体の観察可能なシーケンスを引き起こし要素(あなたがClickedOnBookから得るもの)を運ぶことです。それを行う1つの方法は、誰かが本をクリックするたびにコンテキストメニューの別のインスタンスを作成することです。今

IObservable<BookAction> WhenBookAction() 
{ 
    return Observable.Defer(() => 
    { 
     var menu = ContextMenuFactory.CreateMenu; 
     return Observable 
     .FromEventPattern<ToolStripItemClickedEventArgs>(
      menu, "ItemClicked") 
     .Select(click => GetAction(click.EventArgs.ClickedItem)); 
    } 
} 

のようなものだろう「から/から」シンプル:

from book in rightclicks.Select(
    click => ClickedOnBook((ListView)click.Sender, click.EventArgs)) 
from action in WhenBookAction() 
select book, action; 

あなたはペアの本は、メニューを出現させていた正確に一つであることを保証しています - "AKA"からの/から "SelectMany" AKA "Monad"がそれを処理します。

編集:これを行うもう1つの良い方法があります。私はコンテキストメニューを複製するのが怠惰だったので、私の "テストスイート"は "firstBn"、 "secondBn"、 "actionBn"の3つのボタンを持つウィンドウです - これはこの質問と同じパターンです。ここに私が持っているものがあります:

public MainWindow() 
    { 
     InitializeComponent(); 
     var actions = Observable 
      .FromEventPattern<RoutedEventArgs>(actionBn, "Click"); 
     var firsts = Observable 
      .FromEventPattern<RoutedEventArgs>(firstBn, "Click") 
      .Select(x => firstBn); 
     var seconds = Observable 
      .FromEventPattern<RoutedEventArgs>(secondBn, "Click") 
      .Select(x => secondBn); 
     var buttons = firsts.Merge(seconds); 
     var buttonActions = buttons 
      .Select(x => actions.Select(_ => x)) 
      .Switch(); 
     buttonActions.Subscribe(x => Console.WriteLine(x.Name)); 
    } 
関連する問題