【问题标题】:Send a message from View to ViewModel : what is an appropriate token?从 View 向 ViewModel 发送消息:什么是合适的令牌?
【发布时间】:2014-01-04 14:24:41
【问题描述】:

让我们定义:

  • viewModel:TabViewModel
  • 一个视图:TabView

我有 nTabView 类的实例,以及 nTabViewModel 的实例。 当TabView 类的一个实例发送一条消息时,我希望它被它自己的视图模型接收,只有这个

据我了解 mvvm light 工具包的 Messenger,我应该使用类似的东西:

// in the view
Messenger.Default.Send(new RefreshMessage(/*...*/), oneToken);

// in the viewmodel 
Messenger.Default.Register<RefreshMessage>(this, oneToken, MyViewModelMethod);

oneToken 应该使用什么?

我的第一个想法是使用 ViewModel 实例作为令牌:

// in the view
Messenger.Default.Send(new RefreshMessage(/*...*/), this.DataContext);

// in the viewmodel 
Messenger.Default.Register<RefreshMessage>(this, **this**, MyViewModelMethod);

这对我来说似乎是“mvvm-friendly”,因为视图不知道 DataContext 是什么。 但是有了这个解决方案,我担心内存泄漏:在 mvvm light 中,收件人是弱引用的,但令牌不是(正如您将在 WeakActionAndToken struct of the Messenger class 中看到的那样。

我可以使用什么作为令牌?视图模型实例是不是一个不错的选择,如果我使用它如何防止内存泄漏?


编辑:可能的解决方案

选项 1(基于 ethicallogics 答案):

  1. 在视图和视图模型上定义一个 Token 属性(例如字符串或 GUID 类型)
  2. 定义其中一个的值(唯一值,例如在视图模型的构造函数中设置)
  3. 在 XAML 中将它们绑定在一起
  4. 在 Messenger 通话中使用它们

选项 2(我采用的那个):

使用 viewmodel 实例作为 Token。

为了防止内存泄漏,我们必须将它封装在一个弱引用中。为了与比较 2 个令牌的 Messenger 一起工作,weakReference 应该实现 Equals 方法(这不是 WeakReference 类的默认 .Net 实现的情况)。

所以我们有:

// in the view
Messenger.Default.Send(new RefreshMessage(), new EquatableWeakReference(this.DataContext));

// in the viewmodel 
Messenger.Default.Register<RefreshMessage>(this, new EquatableWeakReference(this), ApplyRefreshMessage);

我实现了EquatableWeakReference 类如下:

/// <summary>
/// A weak reference which can be compared with another one, based on the target comparison.
/// </summary>
public class EquatableWeakReference : IEquatable<EquatableWeakReference>
{
    private WeakReference reference;
    private int targetHashcode;

    public EquatableWeakReference(object target)
    {
        if (target == null)
            throw new ArgumentNullException("target");
        reference = new WeakReference(target);
        targetHashcode = target.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as EquatableWeakReference);
    }

    /// <summary>
    /// As Equals is overriden, we must provide an override for GetHashCode.
    /// </summary>
    /// <returns></returns>
    public override int GetHashCode()
    {
        return targetHashcode;
    }

    public bool Equals(EquatableWeakReference other)
    {
        if (other == null
            || !reference.IsAlive
            || !other.reference.IsAlive)
            return false; // we assume that if both references are not alive, the result is inconclusive : let's say false.
        return this.reference.Target.Equals(other.reference.Target);
    }
}

Advantage 是视图和视图模型上的轻量级代码,没有内存泄漏。测试成功。 如果您有更好的解决方案,请随时发表评论。

【问题讨论】:

    标签: c# wpf mvvm mvvm-light messenger


    【解决方案1】:

    Token 是 View 传递给 ViewModel 的对象唯一值,它们都使用相同的 Token。喜欢

    查看

    public partial class MainWindow : Window
    {
        readonly string Token;
        public MainWindow()
        {
            Token = Guid.NewGuid().ToString();
            InitializeComponent();
            DataContext = new MainViewModel(Token);
        }
    }
    

    视图模型

    public class MainViewModel 
    {
        readonly string Token;
    
        public MainViewModel(string token)
        {
            Token = token;
        }
    }
    

    实际上 Token 背后的逻辑是,当我们向 Messenger 注册委托时,它确实有内部字典,并且这个 Token 充当该字典中的键。 View 和它的 ViewModel 必须具有相同的 Token,这样才能在 Send 方法上触发与该键对应的确切委托。

    【讨论】:

    • 感谢您的回答,这是有道理的。但是,在 Mvvm Light Toolkit 中,ViewModel 是通过 ViewModelLocator 注入到视图中的,所以我不能真正做到DataContext = new MainViewModel(Token)
    • 我将您的答案标记为已接受,因为它可以为那些不使用工具包的 viewmodelLocator 的人提供解决方案。我在问题中添加了自己的解决方案。谢谢你的回答!
    【解决方案2】:

    如果您使用的是 MVVMLight,请使用命令。这可以保证转到正确的虚拟机。

    在虚拟机中:

        this.DeletePreSaleCommand = new RelayCommand(() => this.DeletePreSale(), () => this.CanDeletePreSale());
    

    这会创建一个 RelayCommand,它是 VM 上的一个属性;当视图调用命令时,它将调用 VM 上的方法 DeletePreSale(),但如果 VM 方法 CanDeletePreSale() 不返回 true,则不允许调用该命令并将禁用该命令绑定的小部件到。

    在视图中:

            <telerik:RadButton Grid.Row="3" Width="200" Command="{Binding DeletePreSaleCommand}"/>
    

    干杯-

    【讨论】:

    • 我在其他任何地方都使用命令,但在这里我想对 SizeChanged 事件做出反应。此事件在视图中进行管理以模拟“EndResize”事件,如here 所述。所以我既不能使用 RelayCommand,也不能使用 EventToCommand。还是谢谢你的回复。
    • 有道理。是的,我会使用 VM/DataContext 作为令牌。我按照你的链接重新:内存泄漏,但它没有用。你能检查一下吗?我在我的应用程序上经常使用 MVVMLight 消息,但没有看到 gthis 行为(但我没有将 DataContext 作为参数发送)...
    • 如果是 DataContext,您也可以使用 GetHashCode(),这将等于目标 VM 实例的 this.GetHashCode()。发送一个 int 应该可以防止泄漏。
    • 链接已更新(空间问题)。 HashCode 的问题是有可能另一个 viewModel 有相同的。
    • 这是一个非常低的值,只有当你覆盖 GetHashCode 方法时才会发生,确定吗?
    猜你喜欢
    • 1970-01-01
    • 2015-05-19
    • 1970-01-01
    • 2019-01-13
    • 2018-08-11
    • 1970-01-01
    • 2018-11-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多