簡體   English   中英

Lambda表達式內的消息框

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM