在 ViewModel 后面的视图代码中调用方法?

Posted

技术标签:

【中文标题】在 ViewModel 后面的视图代码中调用方法?【英文标题】:Calling a Method in View's CodeBehind from ViewModel? 【发布时间】:2012-01-29 07:39:13 【问题描述】:

我的视图后面的代码中有一个方法(这个方法对我的 UI 有一些作用)。

无论如何,我想从我的 ViewModel 中触发此方法。这怎么可能?

【问题讨论】:

【参考方案1】:

我(也许还有其他人?)MVVM 的困难在于理解一个简单的事情:View 了解 ViewModel。我正在使用绑定和命令,但它们在xaml 中很简单strings。由于在运行时安全解析(安全意味着您可以打错字,但软件不会崩溃),这使得视图与视图模型解耦(至少在编译时)。我一直在寻找解决方案来保持这种脱钩,例如行为。

事实是,您可以直接访问视图模型,这通常是窗口/用户控件的DataContext

var vm = (MyViewModel)this.DataContext;

知道,使用事件可能是从视图模型调用视图方法的最佳方式,因为视图模型不知道是否有订阅者,它只是触发该事件,并且该事件可以被视图或其他视图模型使用。

// define in the view model
public delegate void MyEventAction(string someParameter, ...);
public event MyEventAction MyEvent;

// rise event when you need to
MyEvent?.Invoke("123", ...);

// in the view
var vm = (MyViewModel)DataContext;
vm.MyEvent += (someParameter, ...) => ... // do something

【讨论】:

您的示例中的语法似乎不正确。我必须对其进行大量更改才能使其正常工作。 @Deantwo,3 年前我很少使用EventHandler<T>,也没有从EventArgs 继承参数...暂时修复明显的拼写错误,感谢报告。 这是一个很好的答案。我不得不使用: var vm = (MyViewModel)BindingContext;对于 xamarin 表单而不是 DataContext。非常感谢。【参考方案2】:

您可以在 View 中这样做(代码隐藏)。

它转换为一个由 ViewModel 实现的接口,这样您就不会受限于一种特定的 ViewModel 类型。

    // CONSTRUCTOR
    public SomeView()
    
        InitializeComponent();

        DataContextChanged += DataContextChangedHandler;
    

    void DataContextChangedHandler(object sender, DependencyPropertyChangedEventArgs e)
    
        var viewModel = e.NewValue as IInterfaceToBeImplementedByViewModel;

        if (viewModel != null)
        
            viewModel.SomeEvent += (sender, args) =>  someMethod(); 
        
    

【讨论】:

完美运行。我喜欢使用 DataContextChanged,因为它在稍后设置 viewModel 时起作用,而不是在视图创建期间。【参考方案3】:

根据MVVM patternViewModel 不知道View,所以这是不可接受的。与 ViewModel 交互可以触发命令,也可以使用绑定。此外,您不应将特定于 UI 的内容(例如 BusyIndi​​cator)移至 ViewModel 级别。

请提供有关您的具体用例的更多详细信息 - 您何时想要调用视图的方法以及该方法的作用。

【讨论】:

我的 UI 有一个“忙碌”指示器,它只能通过代码隐藏来启动,因为它派生自具有此功能的用户控件。所以我需要视图模型来设置这个繁忙的指标,因为它是处理从服务器获取数据的指标 您可以尝试在 XAML 中声明 BusyInduicator 并设置与 ViewModel 属性的绑定吗? ViewModel 应该如何设置这个指标?你能通过 ViewModel 显示 BudyIndi​​cator 初始化的代码吗?此外,您不应将特定于 UI 的内容(例如 BusyIndi​​cator)移至 ViewModel 级别【参考方案4】:

我看到你对上面答案的回复,你是说你想让你的 ViewModel 检索数据,然后告诉你的视图停止忙碌指示器。

我不确定我的解决方案是否是最好的解决方案,但是您可以尝试一下,如果我错了,也许有人可以纠正。

所以从您的角度来看,您会从 ViewModel 调用一个方法来开始读取数据集,对吗?在此方法中,您可以传递一个委托(指向您的视图中存在的方法),当您的 ViewModel 完成从服务器读取数据集时,触发链接到您的视图中的方法的委托(来自您的视图模型)可以停止忙碌指示器。

所以在你看来你有

void StopBusyIndicator()

    this.BusyIndicator.IsBusy = false;

当您调用 ViewModel 来读取数据集时,

这样称呼它:

ViewModel.ReadDataSet( ()= >StopBusyIndicator)

它将作为委托传递 StopBusyIndi​​cator 方法,您可以在 ReadDataSet 结束时调用该方法。

HTH

【讨论】:

【参考方案5】:

您可以编写一个接受数据传输对象的操作类。在 DTO 中,添加一个名为“View”的属性并将其分配给当前视图。从视图的代码隐藏中通过控制器调用操作,取消装箱 DTO,现在您可以完全控制操作类中的视图。

如果您真的想在模型中执行此操作,只需在模型中创建带有“视图”类型参数的方法并执行它,并传入当前视图。

【讨论】:

【参考方案6】:

假设您在我的登录视图后面的代码中有一个方法,如果登录失败,它会通过将焦点带到 PasswordEntry 来更新 UI,那么从您的 ViewModel 触发此方法的最简单和最通用的方法是使用 Action delegates .

正如您在this sample 中看到的那样,您需要添加的只是您的 ViewModel 中的两行代码和一个操作处理程序,您的服务确定登录失败并且您希望密码条目获得焦点在你的视野中。

ViewModel 代码:

事件声明:public Action<bool> OnLoginFailed get; set; & 然后在需要时简单地执行这个OnLoginFailed?.Invoke(true);

查看代码:

ViewModel.OnLoginFailed = ((obj) =>

    PasswordEntry.Focus();
);

【讨论】:

以上是关于在 ViewModel 后面的视图代码中调用方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ViewModel 类中调用 TranslateTo?

从 ViewModel 调用视图中的方法

从代码后面调用命令

SwiftUI:从嵌套视图调用方法

数据应该在哪里加载到 ViewModel

在 ViewModel (MVVM) 中使用视图方法