使用Rx进行上下文菜单操作

时间:2011-07-03 11:33:36

标签: c# contextmenu system.reactive

我以为我会尝试使用新的System.Reactive位来查看是否会简化执行操作以响应上下文菜单单击ListView中的项目。 到目前为止,所有设置位似乎都更容易,但我正在以正确的方式将两个事件流(项目选择和菜单点击)结合起来。

这是我到目前为止所拥有的 (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',然后右键单击一本书,但点击远离上下文菜单而不是它,那么下次我右键单击并实际点击菜单时,第一次调用该操作选择而不是第二个。

我尝试了各种形式的时间缓冲区和窗口以及最新的等等,但通常只有在菜单出现时才成功阻止,以便无法进行选择,或者如果菜单显示但是获得有关空序列的异常但是没有点击任何项目。

我确信必须有一种更简单的方法来做到这一点我不知道但是我不确定它是什么。

也许这个?

//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 };

1 个答案:

答案 0 :(得分:2)

解决这些问题的最简单方法是通过整个可观察序列携带cause元素(从ClickedOnBook获得的元素)。一种方法是每次有人点击书籍时创建一个单独的上下文菜单实例。像

这样的东西
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”负责处理。

编辑:这是另一个很好的方法。我懒得复制上下文菜单,所以我的“测试套件”是一个窗口,其中有三个按钮,名为“firstBn”,“secondBn”和“actionBn” - 这与此问题中的模式相同。这是我得到的:

    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));
    }