[英]Messagebox inside Lambda expression
好吧,正如標題所描述的那樣,我有一個Lambda表達式,其中帶有一個消息框,但效果不佳。
我的項目是WPF,在Visual Studio 2010中全部使用C#和MVVM。
首先從上下文菜單開始,如下所示:
<ContextMenu x:Key="ChatNodeMenu" >
<MenuItem Header="Remove ChatNode" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.Tag.DataContext.RemoveChatNodeCommand}" />
<Separator/>
<MenuItem Header="Add branching for mission complete:" ItemsSource="{Binding ChatNodeListViewModel.DraggableNodeAddMissionList, Source={StaticResource Locator}}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding DisplayName}"/>
<Setter Property="MenuItem.Command" Value="{Binding ContextMenuCommand}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
詳細信息實際上是關於第二項的,它從名為DraggableNodeAddMissionList的列表中獲取其信息,該列表是一個ObservableCollection,其類型包含一個String和一個RelayCommand。
填充所述列表后,它將運行以下事件:
DraggableNodeAddMissionList.Clear();
foreach(Mission m in Database.Instance.Missions)
{
Mission m2 = m;
DraggableNodeAddMissionList.Add(new ContextMenuVM()
{
DisplayName = m2.MissionName,
ContextMenuCommand = new RelayCommand(
() =>
{
MessageBox.Show("You clicked!" + m2.MissionName);
})
});
}
因此,如您所見,這里有一個String(DisplayName)和RelayCommand(ContextMenuCommand)。
它工作正常,並且如我從列表中期望的那樣填充了上下文菜單。 您可以單擊每個項目。
現在要解決錯誤的情況:如果我僅擁有一個僅顯示“您已單擊”的消息框(未添加任務名稱),則每次都可以使用。
當我在字符串中添加“任務名稱”時,它僅在第一次工作。
我曾經認為這與變量捕獲或“循環變量閉包”有關,這就是為什么我有“任務m2 = m;”的原因。 線。 這確實有效果,因為我現在在消息框中獲得了正確的字符串,但是只運行了一次。 我可以打開該菜單一百次,然后單擊,只有第一次我才能得到任何東西。 以前,當它只給我列表中的最后一個字符串時,它也只運行了一次(盡管至少現在顯示的是正確的字符串)。
我在該事件處理程序中放置了一個斷點,以查看何時觸發。 在第一種情況下(僅單擊“您單擊”),每次我單擊其中一個菜單項時都會觸發。 當我添加其他任務名稱時,它只會觸發一次。
我希望能提供足夠的信息以供使用。 謝謝閱讀。
編輯:我正在使用Galasoft MVVMLight,以防萬一。
編輯2:我也嘗試了最常見的建議,即將MissionName復制到循環內的單獨字符串中,然后將其與MessageBox.Show方法一起使用。 這沒有任何改變-我已經提到,通過將Mission對象'm'復制到臨時對象'm2',我已經解決了(眾所周知)循環關閉問題。 即使這樣,我仍然嘗試僅復制字符串的建議,但沒有任何區別。
編輯3:我想把MessageBox排除在外,所以我在包含所有這些東西的類(我的View Model類)中創建了一個List,然后嘗試將其添加到Mission ID中。 在執行此操作之前,我確實考慮了閉包,並創建了一個臨時int,然后在將其添加到列表時使用了它。 和以前一樣,我告訴它要添加一個數字(假設為“ 0”),該數字將在每次單擊菜單項時運行。 如果我在循環中使用了某些東西(甚至是一個封閉的安全副本),它只會運行一次。
編輯4:事件由這些任務的數據庫加載觸發。 程序啟動時,加載完成后會觸發事件,並從文件中加載事件。 有兩個視圖模型訂閱此事件。 我已嘗試注釋掉其他事件,但沒什么區別。 文件中也有八(8)個任務,並且該任務循環運行八(8)次。
編輯5:我嘗試更改一些測試條件。 我取出了對數據庫的訪問權,還從事件處理程序中取出了全部內容。 因此,現在,在有問題的視圖模型的構造函數中,我具有以下代碼:
Mission m1 = new Mission() { MissionID = 1001, MissionName = "Mission 1001", IsCompleted = false };
Mission m2 = new Mission() { MissionID = 1002, MissionName = "Mission 1002", IsCompleted = false };
Mission m3 = new Mission() { MissionID = 1003, MissionName = "Mission 1003", IsCompleted = false };
Mission m4 = new Mission() { MissionID = 1004, MissionName = "Mission 1004", IsCompleted = false };
TestMissions.Add(m1);
TestMissions.Add(m2);
TestMissions.Add(m3);
TestMissions.Add(m4);
foreach (Mission m in this.TestMissions)
{
String sName = m.MissionName;
DraggableNodeAddMissionList.Add(new ContextMenuVM()
{
DisplayName = sName,
ContextMenuCommand = new RelayCommand(
() =>
{
MessageBox.Show("You clicked!" + sName);
})
});
}
那么發生了什么? 好了,關於上下文菜單,它按我期望的那樣填充-帶有任務名稱的四個條目。 命令是另一回事。 和以前一樣,如果我從Messagebox.Show中刪除“ sName”,則可以單擊任何菜單項(並且可以根據需要多次單擊),然后將得到結果。 我將看到消息框,一切都很好。 但是在包含sName的情況下(與上面的代碼一樣) 與以前有所不同 。 這次,我什至無法獲得任何結果(即,單擊菜單項無濟於事)。 在我得到一個之前,然后什么也沒有。 這次我什么也沒得到。 用於DisplayName的sName可以正常工作-上下文菜單項符合預期。
編輯6:我這次只用“項目”而不是“任務”重復了所有相同的步驟。 我從數據庫中的文件加載了一些“項目”,然后在視圖模型中添加了一個事件處理程序,以填充菜單項的ObservableCollection。
現在看起來像這樣:
<ContextMenu x:Key="ChatNodeMenu" >
<MenuItem Header="Remove ChatNode" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.Tag.DataContext.RemoveChatNodeCommand}" />
<Separator/>
<MenuItem Header="Add branching for mission complete:" ItemsSource="{Binding ChatNodeListViewModel.DraggableNodeAddMissionList, Source={StaticResource Locator}}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding DisplayName}"/>
<Setter Property="MenuItem.Command" Value="{Binding ContextMenuCommand}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="Add direction for item:" ItemsSource="{Binding ChatNodeListViewModel.DraggableNodeAddItemDirectorsList, Source={StaticResource Locator}}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Header" Value="{Binding DisplayName}"/>
<Setter Property="MenuItem.Command" Value="{Binding ContextMenuCommand}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
然后,我執行相同的步驟來填充它-遍歷數據庫中的所有Item,並為每個實例添加一個項目,在本例中為DraggableNodeAddItemDirectorsList。 通過將Item的名稱保存在臨時字符串中,我也采取了相同的預防措施來關閉循環。 實際上,它幾乎是一個復制粘貼,但是帶有“項目”而不是“任務”。 問題是完全一樣的(也許應該可以預期)。
然后,我按照編輯五(5)中的步驟進行操作,並分離出事件處理程序和對數據庫的訪問權限,並僅使用了一些測試項目。 它的行為完全相同。
編輯7:取得一些成功! 我從頭開始了一個新項目,試圖用我能做的最簡單的項目重新創建問題。 我確實能夠重現問題。 然后,由於我的項目不會被破壞,所以我嘗試了一些事情。 您會看到,我最初用來創建ContextMenu東西的代碼是在我的另一個項目中。 它在該項目中運行正常,這就是為什么我認為在這里可以正常運行的原因。 那有什么區別呢? 它工作的項目使用MicroMvvm,而該項目使用MvvmLight (Galasoft)。 因此,我將MicroMvvm引用導入到我的簡單測試項目中,然后將所有RelayCommand項設置為MicroMvvm類型而不是MvvmLight類型。 而且有效!
因此,目前,解決方案是將MicroMvvm導入到我的主項目中,以從中使用有效的RelayCommand。
看來這在MvvmLight中不起作用有點奇怪,但是...這絕對是我的區別。 關於我如何獲得MvvmLight及其最新信息,我使用Visual Studio 2010中的NuGet管理器。我今天創建的測試項目使用了那里的最新版本。
RelayCommand
的MVVM Light實現使用對處理程序功能的WeakAction
引用。 在動作映射到viewmodel函數且viewmodel生存期定義命令/處理程序生存期的情況下,這很好用。 此實現實際上不適用於動態創建的Action
處理程序,在該處理程序中該命令應該保存對該動作的唯一引用。 即使該命令適用於可以轉換為靜態函數的lambda,但實際上我建議在此RelayCommand實現中完全不要使用lambda。
可能的解決方案:
RelayCommand
實現,支持強大的操作引用 參考列表示例:
ConditionalWeakTable<ContextMenuVM, Action> ActionHolder = new ConditionalWeakTable<ContextMenuVM, Action>();
// keep action references alive, ActionHolder needs to have some
// appropriate scope so it doesn't disappear as long as
// ContextMenuVM is alive
...
foreach(Mission m in Database.Instance.Missions)
{
var item = new ContextMenuVM()
{
DisplayName = m.MissionName,
};
Action a = () =>
{
MessageBox.Show("You clicked!" + item.DisplayName);
};
item.ContextMenuCommand = new RelayCommand(a);
DraggableNodeAddMissionList.Add(item);
ActionHolder.Add(item, a); // keep a strong action reference for the lifetime of item
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.