MVVM-Light Messenger:使用多个相同ViewModel实例

5
我对MVVM相对较新,遇到了一个问题。我们正在使用MVVM-Light框架编写WPF数据库应用程序。程序规格要求我们必须能够同时打开多个ClaimView实例。
为了打开新窗口,我们从ViewModel发送消息,由View接收并打开新窗口。我们使用枚举标记来标识正确的接收方以获取请求。
现在,如果我同时打开两个ClaimView实例,并调用Messanger,则会打开两个相同的窗口,因为两个视图都接收到该消息。
我们尝试在不同的线程上运行每个ViewModel实例,并通过输出ManagedThreadId进行验证,但是消息仍然被两个实例接收。
我们还注销了注册的消息,因此那也不是问题所在。
任何帮助将不胜感激。
1个回答

9

新答案

正如楼主(Daryl)所指出的,我的原始回答(请参见下文)并不完全正确,因此我提供了一个新的答案,以防后来有人遇到同样的问题:

如果您有两个实例注册了相同令牌的相同消息类型,那么两个实例都将接收该消息是很合理的。解决方案是为每个 View-ViewModel 组合提供一个唯一的令牌。

您可以像这样将枚举值放在类中,而不仅仅是使用普通的枚举值作为令牌:

public class UniqueToken
{
    public MessengerToken Token { get; private set; }

    public UniqueToken(MessengerToken token)
    {
        Token = token;
    }
}

然后在您的ViewModel中,添加一个新属性来存储这些唯一令牌之一:

// add a property to your ViewModel
public UniqueToken OpenWindowToken { get; private set; }

// place this in the constructor of your ViewModel
OpenWindowToken = new UniqueToken(MessengerToken.OpenWindow);

// in the appropriate method, send the message
Messenger.Send(message, OpenWindowToken);

最后,在您的视图中,您现在可以获取唯一标识符并使用它来注册OpenWindow消息:

var viewModel = (MyViewModel)DataContext;
var token = viewModel.OpenWindowToken;
Messenger.Register<TMessage>(this, token, message => OpenWindow(message));

需要 ViewModel 和 View 使用唯一标记的单个实例,因为只有接收者标记和发送者标记是完全相同的对象(而不仅仅是具有相同属性值的实例),信使才会发送消息。

原始答案(不太正确)

我认为你的问题可能有打字错误:你说要打开一个新窗口,你从ViewModel发送消息到View,但后来你又说两个ViewModel都接收到了消息。你是不是想说两个View都接收到了消息?

无论如何,如果您有两个注册了相同令牌的消息类型的实例,那么两个实例都将接收该消息。

要解决这个问题,首先需要每个ViewModel实例都有一个唯一的ID。可以使用 Guid 来实现这一点。例如:

// add a property to your ViewModel
public Guid Id { get; private set; }

// place this in the constructor of your ViewModel
Id = Guid.NewGuid();

那么你需要让你的令牌成为一个具有两个属性的对象:一个用于GUID,一个用于枚举值:

public class UniqueToken
{
    public Guid Id { get; private set; }
    public MessengerToken Token { get; private set; }

    public UniqueToken(Guid id, MessengerToken token)
    {
        Id = id;
        Token = token;
    }
}

那么当您在视图(View)中注册时(还是在ViewModel中?),您需要从ViewModel中获取Guid。可以像这样实现:

var viewModel = (MyViewModel)DataContext;
var id = viewModel.Id;
var token = new UniqueToken(id, MessengerToken.OpenWindow);
Messenger.Register<TMessage>(this, token, message => OpenWindow(message));

最后,在您的 ViewModel 中,您需要执行以下操作:
var token = new UniqueToken(Id, MessengerToken.OpenWindow);
Messenger.Send(message, token);

编辑

在输入所有内容后,我意识到 ViewModel 上并不真正需要一个 Id 属性。你可以直接使用 ViewModel 本身作为唯一标识符。所以,对于 UniqueToken,你可以用 public MyViewModel ViewModel 替换 public Guid Id,这样应该仍然可以工作。


谢谢回复。首先,我已经整理过这篇文章,现在应该更有意义了。其次,我正在尝试执行您在编辑中提到的操作,但我的消息没有被接收到。也许我犯了一个错误。在我的ViewModel中,我正在做这个: Messenger.Default.Send<int>(Number, new MessageToken(this, Enumerators.OpenWindow.MainComent)); 在我的视图中,我使用以下代码向vm注册: Messenger.Default.Register<int>(this, new MessageToken(vm, Enumerators.OpenWindow.MainComent), OpenCommentView); 消息正在发送,但是没有被接收到。 - Daryl Behrens
搞定了。问题是在视图和视图模型中都创建了一个新的MessageToken对象导致的。令牌中保存的数据相同,但实际的令牌对象不同。我解决它的方法是在我的ViewModel中将令牌创建为属性,然后在我的视图中从DataContext中获取该属性。这似乎有效,如果您有更好的方法,请告诉我。 - Daryl Behrens
啊,是的,那很有道理。令牌需要是完全相同的对象。 - devuxer
我会更进一步,只需创建一个空的UniqueToken类即可。MVVMLight框架将能够识别接收者,因为那些UniqueToken实例具有相同的引用。然后,MessengerToken.OpenWindow可以移动到消息而不是令牌中。 - Norbert Hüthmayr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接