gpt4 book ai didi

wpf - View-First-MVVM 中的用户控件和 View 模型

转载 作者:行者123 更新时间:2023-12-04 12:59:10 25 4
gpt4 key购买 nike

我被迫在 WPF 应用程序中使用 View First MVVM,我正在努力了解如何让它优雅地工作。

问题的根源在于嵌套 UserControls .在 MVVM 架构中,每个 UserControl需要将其 View 模型分配给其 DataContext ,这使绑定(bind)表达式保持简单,而且这也是 WPF 将通过 DataTemplate 生成的任何 View 实例化的方式。 .

但是如果一个 child UserControl具有父需要绑定(bind)到其自己的 View 模型的依赖属性,那么子 UserControl有它的DataContext设置为其自己的 View 模型意味着父 XAML 文件中的“隐式路径”绑定(bind)将解析为 subview 模型而不是父 View 模型。

为了解决这个问题,每个 UserControl 的每个 parent 在应用程序中,默认情况下需要对所有内容使用显式命名绑定(bind)(冗长、丑陋且容易出错),或者必须知道特定控件是否具有其 DataContext是否设置为自己的 View 模型并使用适当的绑定(bind)语法(这同样容易出错,并且严重违反了基本封装)。

经过几天的研究,我还没有找到一个半体面的解决方案来解决这个问题。我遇到的最接近解决方案的方法是设置 UserControl's viewmodel 到 UserControl 的内部元素(最上面的 Grid 或其他),这仍然让您在尝试绑定(bind) UserControl 的属性时遇到问题自己到自己的 View 模型! ( ElementName 绑定(bind)在这种情况下不起作用,因为绑定(bind)将在命名元素之前声明,并且 View 模型分配给它的 DataContext )。

我怀疑没有多少其他人遇到此问题的原因是他们要么使用不存在此问题的 viewmodel first MVVM,要么将 view first MVVM 与改善此问题的依赖注入(inject)实现结合使用。

请问有人有干净的解决方案吗?

更新:

根据要求提供示例代码。

<!-- 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>

namespace UiInteraction
{
// MainWindow viewmodel.
class MainWindowVm
{
public string MainWindowVmString1
{
get { return "MainWindowVm.String1"; }
}
}
}

<!-- 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>

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); }
}
}
}

namespace UiInteraction
{
// UserControl's viewmodel.
class UserControl6Vm
{
public string UserControlVmString1
{
get { return "UserControl6Vm.String1"; }
}
}
}

这导致:

System.Windows.Data Error: 40 : BindingExpression path error: 'MainWindowVmString1' property not found on 'object' ''UserControl6Vm' (HashCode=44204140)'. BindingExpression:Path=MainWindowVmString1; DataItem='UserControl6Vm' (HashCode=44204140); target element is 'UserControl6' (Name='_this'); target property is 'Text' (type 'String')



因为在 MainWindow.xaml声明 <local:UserControl6 Text="{Binding MainWindowVmString1}"/>正在尝试解决 MainWindowVmString1UserControl6Vm .

UserControl6.xaml注释掉 DataContext 的声明和第一个 TextBlock代码可以工作,但 UserControl需要一个 DataContext .在 MainWIndow1使用 ElementName代替隐式路径绑定(bind)也可以,但为了使用 ElementName绑定(bind)语法,您要么必须知道 UserControl将其 View 模型分配给其 DataContext (封装失败)或替代采用 ElementName的策略到处绑定(bind)。两者都没有吸引力。

最佳答案

一个直接的解决方案是使用 RelativeSource并将其设置为查找 DataContext parent 的UserControl :

<UserControl>
<UserControl.DataContext>
<local:ParentViewModel />
</UserControl.DataContext>
<Grid>
<local:ChildControl MyProperty="{Binding DataContext.PropertyInParentDataContext, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
</Grid>
</UserControl>

您还可以将 subview 模型视为父 View 模型的属性,并从父 View 模型传播它。这样,父 View 模型就知道 subview 模型,因此它可以更新它们的属性。 subview 模型也可能有 "Parent"拥有对父级的引用的属性,由父级itelf在创建时注入(inject),可以授予对父级的直接访问权限。
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>

编辑
另一种更复杂的方法是将 parent 的 DataContext提供给 child UserControl使用附加属性。我还没有完全实现它,但它包含一个附加属性来请求该功能(类似于 "HasAccessToParentDT" ),其中 DependencyPropertyChanged事件您将连接负载和 Unload ChildUserControl 的事件, 访问 Parent属性(在加载控件时可用)并绑定(bind)其 DataContext到第二个附加属性, "ParentDataContext" ,然后可以在 xaml 中使用。
        <local:ChildControl BindingHelper.AccessParentDataContext="True"
MyProperty="{Binding BindingHelper.ParentDataContext.TargetProperty}" />

关于wpf - View-First-MVVM 中的用户控件和 View 模型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14442418/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com