如何从视图中使用的组件-WPF PRISM制作ViewModel的方法



在我的视图中,我正在使用一个组件(自定义控件(,该组件提供了一些功能。当我的ViewModel收到订阅的事件时,我想调用其中一个。

我想尽可能干净地做到这一点,因为我可能会使用更多的功能。


我知道我可以创建一个诸如" InvokeFunctiona"之类的变量,绑定到此变量,并在视图中创建onchange方法,该方法将调用相应的函数。但是,仅仅调用一个功能,这是很多代码。还有一个额外的变量,这似乎也很不全面。

有更好的方法吗?例如,也许视图可以将某种处理程序功能传递给ViewModel,哪些可以完成工作?我做了很多研究,但还没有找到适合我问题的东西。也许我错过了明显的东西?


[编辑] Haukinger解决方案现在可以使用(这样做:https://blog.machinezoo.com/expose-wpf-control-to-view-model-iii(,但我不认为这是最干净的解决方案(而不是提供访问权限对于几个功能,我将整个控制介绍到ViewModel(。

在完美的mvvm-world中(当您要求使用干净的解决方案时(,ViewModel不会调用位于视图中的任何东西直接或间接(。我会处理这样的问题:

  1. 如果'组件'不是usercontrol,请尝试将其移至ViewModel并在视图中使用绑定或命令来操作您的"组件"。

  2. 如果'组件'是usercontrol,请给予"组件"一个依赖项属性,并通过与您的viewModel属性绑定填充它。在"元素"内部,您可以注册依赖关系属性的价值更改回调,以开始您的工作。<local:UserControlComponent MyDependencyProperty="{Binding PropertyInViewModel}" />

作为最后的手段:

  1. 您可以将C#事件添加到ViewModel,并在视图内的代码中处理它。

  2. 而不是事件,您可以使用iObservable模式(https://learn.microsoft.com/en-us/dotnet/api/system.iobservable-1?view=netframework-4.8,https:https:https:https://github.com/dotnet/reactive(

为了完整的清酒,一个无关的选择:Prism具有可用于松散通信的EventAggregator。我不得不从一个相当大的应用程序中删除EventAggregator的使用情况,因为它不再可维护。

在视图中公开依赖关系属性其类型是提供的接口,将其绑定到视图模型上的属性,然后从视图模型上调用视图模型属性的接口上的方法。

要澄清,我并不是要揭示组件本身,而是一个完全包含一种方法的接口。该视图必须具有将接口和路由实现实际组件的私有类,以及转换参数和结果,以便在接口中不必存在属于组件的类型。

,但我和sa在一起。他首先应避免整个情况。可能是不可能的,取决于使用的第三方组件。不会成为"干净"解决方案。但是它至少可以做到一半。您需要创建一个特殊的附件属性(或行为,但在这种情况下属性似乎是一个更好的选择(和VM中的iCommand属性,然后将AP绑定到属性,并使用OnewayTosource绑定并在VM中使用命令调用。仍然有很多代码,但是一旦完成,您只需要在VM中创建新属性。

下面是我编写的一些代码,将其视为起点,您可以添加对命令参数和转换器的支持。

public class MethodDelegation : DependencyObject
{
    public static readonly DependencyProperty CommandDelegatesProperty = 
        DependencyProperty.RegisterAttached("CommandDelegatesInternal", typeof(CommandDelegatesCollection), typeof(MethodDelegation), new PropertyMetadata(null));
    private MethodDelegation() { }
    public static CommandDelegatesCollection GetCommandDelegates(DependencyObject obj)
    {
        if (obj.GetValue(CommandDelegatesProperty) is null)
        {
            SetCommandDelegates(obj, new CommandDelegatesCollection(obj));
        }
        return (CommandDelegatesCollection)obj.GetValue(CommandDelegatesProperty);
    }
    public static void SetCommandDelegates(DependencyObject obj, CommandDelegatesCollection value)
    {
        obj.SetValue(CommandDelegatesProperty, value);
    }
}
public class CommandDelegatesCollection : FreezableCollection<CommandDelegate>
{
    public CommandDelegatesCollection()
    {
    }
    public CommandDelegatesCollection(DependencyObject targetObject)
    {
        TargetObject = targetObject;
        ((INotifyCollectionChanged)this).CollectionChanged += UpdateDelegatesTargetObjects;
    }
    public DependencyObject TargetObject { get; }
    protected override Freezable CreateInstanceCore()
    {
        return new CommandDelegatesCollection();
    }
    private void UpdateDelegatesTargetObjects(object sender, NotifyCollectionChangedEventArgs e)
    {
        foreach (CommandDelegate commandDelegate in e?.NewItems ?? Array.Empty<CommandDelegate>())
        {
            commandDelegate.TargetObject = TargetObject;
        }
    }
}
public class CommandDelegate : Freezable
{
    public static readonly DependencyProperty MethodNameProperty = 
        DependencyProperty.Register("MethodName", typeof(string), typeof(CommandDelegate), new PropertyMetadata(string.Empty, MethodName_Changed));
    public static readonly DependencyProperty CommandProperty = 
        DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandDelegate), new PropertyMetadata(null));
    public static readonly DependencyProperty TargetObjectProperty = 
        DependencyProperty.Register("TargetObject", typeof(DependencyObject), typeof(CommandDelegate), new PropertyMetadata(null, TargetObject_Changed));
    private MethodInfo _method;
    public string MethodName
    {
        get { return (string)GetValue(MethodNameProperty); }
        set { SetValue(MethodNameProperty, value); }
    }
    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }
    public DependencyObject TargetObject
    {
        get { return (DependencyObject)GetValue(TargetObjectProperty); }
        set { SetValue(TargetObjectProperty, value); }
    }
    protected override Freezable CreateInstanceCore()
    {
        return new CommandDelegate();
    }
    private static void MethodName_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var del = (CommandDelegate)d;
        del.UpdateMethod();
        del.UpdateCommand();
    }
    private static void TargetObject_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var del = (CommandDelegate)d;
        del.UpdateMethod();
        del.UpdateCommand();
    }
    private void UpdateMethod()
    {
        _method = TargetObject?.GetType()?.GetMethod(MethodName);
    }
    private void UpdateCommand()
    {
        Command = new RelayCommand(() => _method.Invoke(TargetObject, Array.Empty<object>()));
    }
}

XAML的使用如下:

<TextBox>
    <l:MethodDelegation.CommandDelegates>
        <l:CommandDelegate MethodName="Focus" 
                           Command="{Binding TestCommand, Mode=OneWayToSource}" />
    </l:MethodDelegation.CommandDelegates>
</TextBox> 

向上泡泡您的事件。让您的VM发布一些事件。您的V可以订阅(如果愿意(。

不利的一面是您需要CodeBehind,理想情况下,V应该尽可能地仅XAML。好处是您的VM仍然非常超然(即,它不取决于V所使用的任何特定控件(。它说"发生了一些值得注意的事情",但它并不认为(a(任何人都特别倾听,或者(b(它将其留给听众(在您的情况下为v(,以确定该怎么办采取行动(即如何更改UI(。

这是一个多年生问题 - VM如何导致V以某种方式更新,据我所知,它仍然是有争议的。

上面的机制,我有一个模糊的回忆,即棱镜本身可能包括类似的东西。我很确定它使用类似于InotifyPropertyChanged(即某些界面或其他(而不是"事件"的东西,因为我们可能只是从.NET的工作知识中理解它。您甚至可以使用此机制完全分配CodeBehind。首先使用棱镜的缺点是它的批量,但是如果您已经使用了它...

您要决定这有多干净。我认为比直接与UI的VM干预更可取。

最新更新