Lambda表达式

时间:2017-09-15 09:00:57

标签: c# wpf xaml lambda

我有标题所描述的内容:一个Lambda表达式,里面有一个Messagebox,并没有很好地工作。

我的项目是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,它是一个包含String和RelayCommand的类型的ObservableCollection。

当填充所述列表时,它会运行如下事件:

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)。

它工作正常,上下文菜单按照我期望的列表填充。您可以点击每个项目。

现在对错误的情况:如果我只是一个只显示&#34;你点击的消息框&#34; (没有添加任务名称),它每次都有效。

当我在字符串中添加Mission Name时,它仅在第一次工作。

我原本认为这与变量捕获或循环变量闭合有关,这就是为什么我的任务m2 = m;&#39;线。这确实有效,因为我现在在消息框中得到正确的字符串,但它只运行一次。我可以打开该菜单一百次并点击,并且只在我第一次得到任何东西以前,当它只给我列表中的最后一个字符串时,它也只运行一次(尽管至少现在显示的是正确的字符串)。

我在该事件处理程序中放置了一个断点,以查看它何时触发。在第一种情况下(只是你点击了&#39;),每次点击其中一个菜单项时都会触发。当我添加额外的任务名称时,它只会触发一次。

我希望能够提供足够的信息。谢谢你的阅读。

编辑:我正在使用Galasoft MVVMLight,以防万一。

编辑2:我还尝试了最常见的建议,即将MissionName复制到循环中的单独字符串,然后使用MessageBox.Show方法。这没有任何改变 - 我提到我已经通过复制Mission对象来解释循环闭包问题(以及我所知道的)&#39; m&#39;到临时物体&#39; m2&#39;。即便如此,我仍然尝试过复制字符串的建议,但没有任何区别。

编辑3:我想把MessageBox从这种情况中解脱出来,所以我在类中创建了一个包含所有这些东西的List(我的View Model类)并尝试添加到Mission ID。在我这样做之前,我确实考虑了闭包并创建了一个临时的int,然后我将它添加到List中时使用了它。和以前一样,我告诉它一个要添加的数字(让我们说&#39; 0&#39;)每次点击一个菜单项时它都会运行。如果我在循环中使用了某些东西(甚至是一个闭包安全副本),它只运行一次。

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

那么开心呢?好吧,关于上下文菜单,它按照我的预期填充 - 四个带有任务名称的条目。命令是另一回事。和以前一样,如果我删除&#39; sName&#39;从Messagebox.Show,我可以点击任何菜单项(和我喜欢的次数),我会得到一个结果。我会看到消息框,一切都很好。 但是在我确实包含sName的情况下(与上面的代码一样)之前存在差异。这次我甚至得不到一个结果(即点击菜单项什么都不做)。在我得到一个之前,之后什么都没有。这次我什么都没得到。并且用于DisplayName的sName工作正常 - 上下文菜单项是预期的。

编辑6:我这次只重复所有相同的步骤,其中包含名为&#39; Item&#39;而不是&#39; Mission&#39;。我装了一些物品&#39;从数据库中的文件,然后在我的视图模型中添加一个事件处理程序,以填充菜单项的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>

然后我做了相同的步骤来填充它 - 循环遍历数据库中的所有项目,并为每个项目添加一个项目,在本例中为DraggableNodeAddItemDirectorsList。通过在临时字符串中保存Item的名称,我也采取了相同的循环闭包预防措施。事实上,这几乎是一个复制粘贴,但是有了&#39; Item&#39;而不是&#39; Mission&#39;。问题完全相同(或许应该是预期的)。

然后我按照编辑五(5)中的步骤分离出事件处理程序并访问数据库,并使用了一些测试项。它的行为完全一样。

编辑7:取得了一些成功!我从头开始尝试使用最简单的项目重新创建问题。我确实能够重现这个问题。然后,由于我的项目不会被破坏,我玩了几件事。你看,我借用的第一个创建这个ContextMenu的代码是在我的另一个项目中。它在该项目中工作得很好,这就是为什么我认为它在这里工作正常。那有什么区别?它工作的项目是使用 MicroMvvm ,该项目使用 MvvmLight (Galasoft)。所以我将MicroMvvm引用导入到我的简单测试项目中,然后制作了MicroMvvm类型的所有RelayCommand项而不是MvvmLight类型。 它有效!

所以,目前,解决方案是将MicroMvvm导入我的主项目,以便使用工作中的RelayCommand。

这似乎有点奇怪,这在MvvmLight中不起作用,但......对我来说这绝对是不同的。至于我如何获得MvvmLight以及它是如何更新的,我使用Visual Studio 2010中的NuGet管理器。我今天创建的测试项目使用了最新版本。

1 个答案:

答案 0 :(得分:2)

RelayCommand的MVVM Light实现使用对处理函数的WeakAction引用。这种情况很有用,其中操作映射到viewmodel函数,viewmodel生命周期定义命令/处理程序生存期。此实现不适用于动态创建的Action处理程序,其中该命令应该保存对该操作的唯一引用。即使该命令适用于可以转换为静态函数的lambda,我实际上建议不要在这个RelayCommand实现中使用lambdas。

可能的解决方案:

  • 支持强大操作引用的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
    }