Neu*_*ino 7 wpf xaml binding mvvm
我被迫在WPF应用程序中使用View First MVVM,我很难看到如何使它优雅地工作.
问题的根源在于嵌套UserControls.在MVVM架构中,每个都UserControl需要将其视图模型分配给它DataContext,这使得绑定表达式保持简单,而且这也是WPF实例化通过a生成的任何视图的方式DataTemplate.
但是,如果子项UserControl具有父项需要绑定到其自己的viewmodel的依赖项属性,则子项UserControl将其DataContext设置为其自己的viewmodel这一事实意味着父XAML文件中的"隐式路径"绑定将解析为子视图模型父母的.
要解决这个UserControl问题,应用程序中每个父项的每个父项都需要默认使用显式命名绑定(这是详细的,丑陋的和错误的),或者必须知道特定控件是否将其DataContext设置为自己的viewmodel是否使用适当的绑定语法(同样是errorprone,并且是对基本封装的重大违反).
经过几天的研究,我没有遇到过这个问题的一半解决方案.我遇到的最接近解决方案的是将UserControl'sviewmodel 设置为UserControl(最顶层Grid或其他)的内部元素,这仍然会让您在尝试将UserControl自身的属性绑定到自己的viewmodel时遇到问题!(ElementName绑定在这种情况下不起作用,因为绑定将在指定元素之前声明,并且viewmodel分配给它DataContext).
我怀疑没有其他人遇到这个问题的原因是他们要么使用viewmodel第一个没有这个问题的MVVM,要么他们使用视图第一个MVVM和一个依赖注入实现来解决这个问题.
有没有人为此提供干净的解决方案?
更新:
请求的示例代码.
<!-- MainWindow.xaml -->
<Window x:Class="UiInteraction.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:UiInteraction"
Title="MainWindow" Height="350" Width="525"
x:Name="_this">
<Window.DataContext>
<local:MainWindowVm/>
</Window.DataContext>
<StackPanel>
<local:UserControl6 Text="{Binding MainWindowVmString1}"/>
</StackPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)
namespace UiInteraction
{
// MainWindow viewmodel.
class MainWindowVm
{
public string MainWindowVmString1
{
get { return "MainWindowVm.String1"; }
}
}
}
Run Code Online (Sandbox Code Playgroud)
<!-- UserControl6.xaml -->
<UserControl x:Class="UiInteraction.UserControl6" x:Name="_this"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:UiInteraction">
<UserControl.DataContext>
<local:UserControl6Vm/>
</UserControl.DataContext>
<StackPanel>
<!-- Is bound to this UserControl's own viewmodel. -->
<TextBlock Text="{Binding UserControlVmString1}"/>
<!-- Has its value set by the UserControl's parent via dependency property. -->
<TextBlock Text="{Binding Text, ElementName=_this}"/>
</StackPanel>
</UserControl>
Run Code Online (Sandbox Code Playgroud)
namespace UiInteraction
{
using System.Windows;
using System.Windows.Controls;
// UserControl code behind declares DependencyProperty for parent to bind to.
public partial class UserControl6 : UserControl
{
public UserControl6()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof(string), typeof(UserControl6));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
}
Run Code Online (Sandbox Code Playgroud)
namespace UiInteraction
{
// UserControl's viewmodel.
class UserControl6Vm
{
public string UserControlVmString1
{
get { return "UserControl6Vm.String1"; }
}
}
}
Run Code Online (Sandbox Code Playgroud)
这导致:
System.Windows.Data错误:40:BindingExpression路径错误:'对象'''UserControl6Vm'(HashCode = 44204140)'上找不到'MainWindowVmString1'属性.BindingExpression:路径= MainWindowVmString1; DataItem ='UserControl6Vm'(HashCode = 44204140); target元素是'UserControl6'(Name ='_ this'); target属性是'Text'(类型'String')
因为在MainWindow.xaml申报<local:UserControl6 Text="{Binding MainWindowVmString1}"/>正试图解决MainWindowVmString1的UserControl6Vm.
在UserControl6.xaml评论声明DataContext和第一个TextBlock代码将工作,但UserControl需要一个DataContext.在MainWIndow1使用ElementName而不是implict路径时,绑定也会起作用,但是为了使用ElementName绑定语法,您要么必须知道UserControl将其viewmodel分配给它DataContext(封装失败),要么只是采用在任何地方使用ElementName绑定的策略.这两者都没有吸引力.
一个直接的解决方案是使用 aRelativeSource并将其设置为查找DataContext父级的UserControl:
<UserControl>
<UserControl.DataContext>
<local:ParentViewModel />
</UserControl.DataContext>
<Grid>
<local:ChildControl MyProperty="{Binding DataContext.PropertyInParentDataContext, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
</Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)
您还可以将子视图模型视为父视图模型的属性,并从父视图模型传播它。这样,父视图模型就知道子视图,因此可以更新它们的属性。子视图模型还可以具有一个"Parent"属性,该属性保存对父视图模型的引用,由父视图模型在创建时注入,这可以授予对父视图模型的直接访问权限。
public class ParentViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged values
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private ChildViewModel childViewModel;
public ChildViewModel ChildViewModel
{
get { return this.childViewModel; }
set
{
if (this.childViewModel != value)
{
this.childViewModel = value;
this.OnPropertyChanged("ChildViewModel");
}
}
}
}
<UserControl>
<UserControl.DataContext>
<local:ParentViewModel />
</UserControl.DataContext>
<Grid>
<local:ChildControl DataContext="{Binding ChildViewModel}"
MyProperty1="{Binding PropertyInTheChildControlledByParent}"
MyProperty2="{Binding Parent.PropertyWithDirectAccess}"/>
</Grid>
</UserControl>
Run Code Online (Sandbox Code Playgroud)
编辑
另一种更复杂的方法是使用附加属性使父项DataContext可供子项使用。UserControl我还没有完全实现它,但它会包含一个附加属性来请求该功能(类似于"HasAccessToParentDT"),在这种DependencyPropertyChanged情况下,您将连接 Load 和Unload的事件ChildUserControl,访问该Parent属性(如果加载了控件则可用)并且将其绑定DataContext到第二个附加属性 ,"ParentDataContext"然后可以在 xaml 中使用该属性。
<local:ChildControl BindingHelper.AccessParentDataContext="True"
MyProperty="{Binding BindingHelper.ParentDataContext.TargetProperty}" />
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
11392 次 |
| 最近记录: |