在我的视图中,我正在使用一个组件(自定义控件(,该组件提供了一些功能。当我的ViewModel收到订阅的事件时,我想调用其中一个。
我想尽可能干净地做到这一点,因为我可能会使用更多的功能。
我知道我可以创建一个诸如" InvokeFunctiona"之类的变量,绑定到此变量,并在视图中创建onchange方法,该方法将调用相应的函数。但是,仅仅调用一个功能,这是很多代码。还有一个额外的变量,这似乎也很不全面。
有更好的方法吗?例如,也许视图可以将某种处理程序功能传递给ViewModel,哪些可以完成工作?我做了很多研究,但还没有找到适合我问题的东西。也许我错过了明显的东西?
[编辑] Haukinger解决方案现在可以使用(这样做:https://blog.machinezoo.com/expose-wpf-control-to-view-model-iii(,但我不认为这是最干净的解决方案(而不是提供访问权限对于几个功能,我将整个控制介绍到ViewModel(。
在完美的mvvm-world中(当您要求使用干净的解决方案时(,ViewModel不会调用位于视图中的任何东西直接或间接(。我会处理这样的问题:
-
如果'组件'不是usercontrol,请尝试将其移至ViewModel并在视图中使用绑定或命令来操作您的"组件"。
-
如果'组件'是usercontrol,请给予"组件"一个依赖项属性,并通过与您的viewModel属性绑定填充它。在"元素"内部,您可以注册依赖关系属性的价值更改回调,以开始您的工作。
<local:UserControlComponent MyDependencyProperty="{Binding PropertyInViewModel}" />
作为最后的手段:
-
您可以将C#事件添加到ViewModel,并在视图内的代码中处理它。
-
而不是事件,您可以使用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干预更可取。