[英]Using Rx for context menu actions
我想我會嘗試使用新的 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
}
問題是結合 function。 如果我使用“CombineLatest”,那么在第一次使用上下文菜單之后,每個后續的右鍵單擊都會在新選擇上再次調用上一個操作。
如果我使用“Zip”然后右鍵單擊一本書,但單擊遠離上下文菜單而不是單擊它,那么下次我右鍵單擊並實際單擊菜單時,將在第一次選擇時調用該操作,而不是第二個。
我嘗試了各種 forms 的時間限制緩沖區和 windows 和最新等,但通常只在菜單出現時成功阻止,因此無法選擇,或者如果顯示菜單但沒有項目,則獲得關於空序列的異常被點擊。
我確信一定有一種更簡單的方法可以做到這一點,但我不確定它是什么。
也許這個?
//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 };
解決這些問題的最簡單方法是在整個可觀察序列中攜帶導致元素(從 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” 負責這一點。
編輯:這是另一種不錯的方法。 我懶得復制上下文菜單,所以我的“測試套件”是一個 window,帶有三個名為“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));
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.