Lambda表达式内的消息框 [英] Messagebox inside Lambda expression

查看:60
本文介绍了Lambda表达式内的消息框的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好吧,正如标题所描述的那样:一个Lambda表达式,其中带有一个消息框,效果不佳.

Well I have as the title describes: a Lambda expression with a Messagebox inside it that doesn't work awfully well.

我的项目是WPF,在Visual Studio 2010中全部使用C#和MVVM.

My project is WPF, using C# and MVVM all in Visual Studio 2010.

这从上下文菜单开始,如下所示:

This starts with a context menu, which is as follows:

<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.

The detail is really about the second item, which gets its information from a list, called DraggableNodeAddMissionList, which is an ObservableCollection of a type which holds a String and a RelayCommand.

填充所述列表后,它将运行以下事件:

When said list is populated, it runs an event which is as follows:

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

因此,正如您所看到的,有一个字符串(DisplayName)和RelayCommand(ContextMenuCommand).

So, as you can see, there is a String (DisplayName) and the RelayCommand (ContextMenuCommand).

它工作正常,并且如我从列表中期望的那样填充了上下文菜单.您可以单击每个项目.

It works fine and the context menu is populated as I would expect from the list. You can click on each item.

由于错误的原因,如果我仅拥有一个仅显示您已单击"(不添加任务名称)的消息框,则每次都可以使用.

Now to the circumstances of the error: if I simply have a Messagebox which just shows "You Clicked" (without the addition of the Mission Name), it works every time.

当我将任务名称添加到字符串中时,它仅在第一次使用时有效.

When I add in the addition of the Mission Name to the string, it works the first time only.

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

I had thought this to be something to do with variable capture or 'loop variable closure', which is why I have the 'Mission m2 = m;' line. This does have an effect, in that I now get the correct string in the messagebox, but it only runs once. I can open that menu a hundred times and click, and only on the first time will I get anything. Before, when it was giving me only the final string from the list, it also only ran once (although at least now it is the correct string that is shown).

我在该事件处理程序中放置了一个断点,以查看何时触发.在第一种情况下(仅单击您单击"),每次我单击其中一个菜单项时都会触发.当我添加其他任务名称时,它只会触发一次.

I have put a break point within that event handler to see when it fires. In the first circumstance (just 'You Clicked'), it fires every time I click one of the menu items. When I add that additional Mission Name, it only fires once.

我希望提供足够的信息以供使用.感谢您的阅读.

I hope that confers enough information to be of use. Thanks for reading.

我正在使用Galasoft MVVMLight,以防万一.

I am using Galasoft MVVMLight, in case that makes a difference.

我也尝试了最常见的建议,即将MissionName复制到循环内的单独字符串中,然后将其与MessageBox.Show方法一起使用.这没有任何改变-我已经提到,通过将Mission对象'm'复制到临时对象'm2',我已经解决了(以及我所知道的)循环关闭问题.即便如此,我仍然尝试仅复制字符串的建议,但没有任何区别.

Edit 2: I have also tried the most common suggestion, which is to copy the MissionName to a separate string within the loop and then use that withing the MessageBox.Show method. This makes no change - I mentioned that I'd already accounted for (as well as I know) the loop closure problem by my copying of the Mission object 'm' to a temporary object 'm2'. Even so, I still tried the suggestion to copy just the string and it did not make a difference.

我想摆脱MessageBox的影响,所以我在包含所有这些内容的类(我的View Model类)中创建了一个List,并尝试将其添加到Mission中ID.在执行此操作之前,我确实考虑了闭包,并创建了一个临时int,然后在将其添加到列表时使用了它.和以前一样,它告诉我要添加一个数字(假设为"0"),该数字将在每次单击菜单项时运行.如果我在循环中使用了某些东西(甚至是一个封闭的安全副本),它也只会运行一次.

Edit 3: I wanted to take the MessageBox out of the situation, so I created a List within the class which contains all this stuff (my View Model class) and tried adding to that the Mission ID. I did take closure into account before I did this and created a temporary int which I then used when I added that to the List. Just as before, it I told it a number to add (let's say '0') it would run every time I clicked a menu item. If I used something from within the loop (even a closure safe copy), it ran just once.

该事件是由这些任务的数据库加载触发的.程序启动时,加载完成后以及事件触发时,将从文件中加载它们.有两个视图模型订阅了此事件.我曾尝试注释掉其他事件,但这没什么区别.文件中还有八(8)个任务,并且该任务循环运行八(8)次.

Edit 4: The event is triggered by a database load of these missions. They are loaded from a file when the program starts, and when that loading is complete, and event is fired. Two view models subscribe to this event. I have tried commenting out that other event, but it made no difference. Also there are eight (8) missions in the file and this loop of the missions runs eight (8) times.

我试图更改测试的一些条件.我取出了对数据库的访问权,并且还从事件处理程序中取出了全部内容.因此,现在,在有问题的视图模型的构造函数中,我具有以下代码:

Edit 5: I tried to change a few of the conditions for a test. I took out the access to the database, and I also took the entire thing out of the event handler. So now, in the constructor of the view model in question, I have the following code:

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可以正常工作-上下文菜单项符合预期.

So what happpens? Well, regarding the context menu, it populates as I would expect - four entries with the names of the missions. The commands are another matter. As before, if I remove 'sName' from the Messagebox.Show, I can click on any of the menu items (and as many times as I like) and I will get a result. I will see the messagebox and all is well. But there is a difference from before in the case where I do include sName (as in the code above). This time I can't even get one result (i.e. clicking on the menu items does nothing). Before I got one and then nothing after that. This time I just get none. And sName used for the DisplayName worked fine - the context menu items were as expected.

这次,我仅使用项目"(而不是任务")重复了所有相同的步骤.我从数据库中的文件加载了一些项目",然后在视图模型中添加了一个事件处理程序,以填充菜单项的ObservableCollection.

Edit 6: I repeated all the same steps only this time with something called 'Item' instead of 'Mission'. I loaded some 'items' from a file in the database, then added an event handler in my view model to populate the ObservableCollection for the menu item.

现在看起来像这样:

<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的名称保存在一个临时字符串中,我也采取了相同的预防循环闭合的措施.实际上,它几乎是一个复制粘贴,但是带有项目"而不是任务".问题是完全一样的(也许应该是预期的).

I then did the same steps to populate it - looping through all the Items within the database and, for each, adding an item to, in this case, DraggableNodeAddItemDirectorsList. I also took the same precautions for loop closure, by saving the name of the Item in a temporary string. It was pretty much a copy paste, in fact, but with 'Item' instead of 'Mission'. The problem was exactly the same (as perhaps should be expected).

然后,我按照编辑五(5)中的步骤进行操作,并分离出事件处理程序和对数据库的访问权限,并仅使用了一些测试项.它的行为完全相同.

Then I followed the steps in edit five (5) and separated out the event handler and access to the database and just used some test Items. It behaves exactly the same.

成功!我从头开始了一个新项目,试图用我能做的最简单的项目重现问题.我确实能够重现问题.然后,由于我的项目不会被破坏,所以我尝试了一些事情.您会看到,我最初用来创建ContextMenu东西的代码是在我的另一个项目中.它在该项目中运行正常,这就是为什么我认为在这里可以正常运行的原因.那有什么区别呢?它工作的项目使用的是 MicroMvvm ,而该项目使用的是 MvvmLight (Galasoft).因此,我将MicroMvvm引用导入到我的简单测试项目中,然后将所有RelayCommand项设置为MicroMvvm类型而不是MvvmLight类型.它奏效了!

Edit 7: some success! I started a new project from scratch attempting to recreate the problem with the simplest project I could. I was indeed able to recreate the problem. Then, since my project was not going to be ruined, I played around with a few things. You see, the code I borrowed to create this ContextMenu stuff in the first place was in another of my projects. It worked in that project just fine, which is why I assumed it would work fine here. So what was the difference? The project in which it worked was using MicroMvvm and this project used MvvmLight (Galasoft). So I imported the MicroMvvm reference into my simple test project and then made all the RelayCommand items of the MicroMvvm type rather than of the MvvmLight type. And it worked!

因此,目前,解决方案是将MicroMvvm导入到我的主项目中,以使用该源中有效的RelayCommand.

So, for now, the solution is to import MicroMvvm into my main project to use the working RelayCommand from that.

在MvvmLight中不起作用似乎有点不可思议,但是...这绝对是我的区别.关于我如何获得MvvmLight及其最新信息,我使用Visual Studio 2010中的NuGet管理器.我今天创建的测试项目使用了那里的最新版本.

It seems kinda weird that this doesn't work in MvvmLight but... that's definitely the difference for me. As to how I get MvvmLight and how up to date it is, I use the NuGet manager in Visual Studio 2010. My test project, which I created today, used the latest version on there.

推荐答案

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

The MVVM Light implementation of RelayCommand uses a WeakAction reference to the handler functions. This works nice in cases, where the action maps to a viewmodel function and the viewmodel lifetime defines the command / handler lifetime. This implementation doesn't really work with dynamically created Action handlers, where the command is supposed to hold the only reference to that action. Even though the command is working for lambdas that can be translated to a static function, I'd actually suggest not to use lambdas at all with this RelayCommand implementation.

可能的解决方案:

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

这篇关于Lambda表达式内的消息框的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆