【问题标题】:How to Reference the page (view) the viewmodel is for?如何引用视图模型的页面(视图)?
【发布时间】:2026-01-28 10:55:01
【问题描述】:

我之前已经有人帮忙了,但是现在我将所有内容都重写到 MVVM 中,我失去了这样做的能力......再次!

预MVVM重写,我的页面后面的代码中有这行代码:

var window = MahApps.Metro.Controls.TreeHelper.TryFindParent<MetroWindow>(this);

最后,“this”表示页面。现在,这段代码位于我的视图模型中,带有一条弯曲的红线,我不知道用什么替换它。我想我不能说:

SideBar sb = new SideBar();
var window = MahApps.Metro.Controls.TreeHelper.TryFindParent<MetroWindow>(sb);

因为这会创建一个新的侧边栏实例...不是吗?顺便说一句,此页面作为主页中框架的默认源加载,而不是通过代码启动。

【问题讨论】:

  • 永远不要尝试像这样 1:1 重写代码...您的视图模型根本不应该调用这样的任何方法!重新考虑你的方法,因为 MVVM 不是take my code from codebehind and put it into a separate class
  • 大部分我都没有这样做...但是我完全不熟悉这行中发生的事情...我想达到同样的效果。你知道我能做些什么吗?
  • 它显然试图找到对象的父对象,在这种情况下是页面的父对象,这将是窗口......不知道你以后如何在代码中使用它,但是您的 ViewModel 不应该知道任何关于窗口或页面的信息。如果您甚至需要它,您需要将该逻辑放在其他地方......
  • 下一行代码是:await DialogManager.ShowMessageAsync(window, "MESSAGE HERE");据我所知,在不知道窗口的情况下无法调用它......而且我不知道这段代码还能去哪里?
  • 它应该通知其他类来显示消息,但它不应该直接负责处理这样的事情。据它所知,它只会触发显示一条消息,但这取决于其他人。 ViewModel 不应该关心它是 WPF 消息框,还是网站 javascript 弹出窗口,甚至是一些控制台输出。这就是 MVVM 的意义所在——分离层和关注点以使代码可测试、可管理和可重用。尽可能避免紧密耦合。如何准确地实现这一点是一个长期讨论的问题,并且没有明确的答案。

标签: c# wpf mvvm mahapps.metro


【解决方案1】:

您可以将窗口作为某些操作的参数传递给您的视图模型。

<Button Content="Test"
    Command="{Binding ShowPopupCommand}"
    CommandParameter="{Binding RelativeSource=
        {RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" />

然后在您的视图模型命令中获取参数

ShowPopupCommand = new RelayCommand(o =>
{
    var wnd = o as Window;
});

RelayCommand 的代码

public class RelayCommand<T> : ICommand
    where T : class
{
    private Action<T> execute;
    private Func<T, bool> canExecute;

    public RelayCommand(Action<T> execute, Func<T, bool> canExecute = null)
    {
        this.execute = execute;
        this.canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public bool CanExecute(object parameter)
    {
        return this.canExecute == null || this.canExecute(parameter as T);
    }

    public void Execute(object parameter)
    {
        this.execute(parameter as T);
    }
}

public class RelayCommand : RelayCommand<object>
{
    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
        : base(execute, canExecute)
    {
    }
}

查看模型:

public class MainViewModel
{
    public MainViewModel()
    {
        ShowPopupCommand = new RelayCommand(o =>
        {
            var wnd = o as Window;
            var putBreakPointhere = 1;
        });
    }

    public ICommand ShowPopupCommand { get; set; }
}

【讨论】:

  • 你会如何测试这样的虚拟机?
  • 我无法让参数通过...这就是我的 ATM,我应该把参数放在哪里? statsRefresh = new RelayCommand(param => this.StatsRefresh_Click(), null);然后,我的方法:public void StatsRefresh_Click(MetroWindow window)
  • 感谢您的编辑 - 我仍然很困惑,抱歉!我已经有一个中继命令,但它有点不同 - 我可以粘贴该中继命令并使其超载吗?然后...通过运行 ShowPopupCommand = new RelayCommand(... 那会运行一个单独的方法吗?我当前的命令将中继命令传递给我想要运行的方法...但我在你的中看不到它?对不起! :(
  • @BrunoLM:这个有效的 MVVM 模式在哪个世界?
  • @Tseng 说你想引用窗口来关闭它,不引用窗口你怎么实现呢?
【解决方案2】:

您没有将新的 Sidebar 实例添加到可视化树中,所以当然没有 MetroWindow 父级,因为它根本没有父级。

我建议让自己更熟悉 MVVM 模式。视图模型应该没有实际 UI 的概念。我写了一个基于 MahApps.Metro 和 Autofac 的small MVVM library,也许它可以成为你的起点。

【讨论】: