我一直无法找到一个干净,简单的示例,说明如何正确使用在MVVM框架中具有依赖项属性的WPF实现用户控件。每当我为usercontrol分配一个datacontext时,我的代码都会失败。
我想:
我还有很多东西需要学习,真诚地感谢任何帮助。
这是最顶层的usercontrol中的ItemsControl,它使用依赖项属性TextInControl调用InkStringView用户控件(来自另一个问题的示例)。
<ItemsControl
ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
以下是具有依赖项属性的InkStringView用户控件。
XAML:
<UserControl x:Class="Nova5.UI.Views.Ink.InkStringView"
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:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="mainInkStringView"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextInControl, ElementName=mainInkStringView}" />
<TextBlock Grid.Row="1" Text="I am row 1" />
</Grid>
Code-Behind file:
namespace Nova5.UI.Views.Ink
{
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
this.DataContext = new InkStringViewModel(); <--THIS PREVENTS CORRECT BINDING, WHAT
} --ELSE TO DO?????
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
}
答案 0 :(得分:13)
这是您不应直接从DataContext
本身设置UserControl
的众多原因之一。
执行此操作时,您无法再使用任何其他DataContext
,因为UserControl的DataContext
被硬编码到只有UserControl
有权访问的实例哪种方式击败了WPF在拥有单独的UI和数据层方面的最大优势之一。
在WPF中使用UserControls
有两种主要方法
可以在任何地方使用的独立UserControl
,无需特定的DataContext
。
这种类型的UserControl
通常会为DependencyProperties
提供所需的任何值,并且会像这样使用:
<v:InkStringView TextInControl="{Binding SomeValue}" />
我能想到的典型例子是任何通用的例如Calendar控件或Popup控件。
仅适用于特定UserControl
或Model
的{{1}}。
这些ViewModel
对我来说更为常见,而且很可能就是你在寻找的东西。我将如何使用这样一个UserControl的例子如下:
UserControls
或者更频繁地,它将与隐式<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
一起使用。隐式DataTemplate
是DataTemplate
,DataTemplate
而不是DataType
,WPF会在想要呈现指定类型的对象时自动使用此模板。
Key
使用此方法时不需要<Window.Resources>
<DataTemplate DataType="{x:Type m:InkStringViewModel}">
<v:InkStringView />
</DataTemplate>
<Window.Resources>
<!-- Binding to a single ViewModel -->
<ContentPresenter Content="{Binding MyInkStringViewModelProperty}" />
<!-- Binding to a collection of ViewModels -->
<ItemsControl ItemsSource="{Binding MyCollectionOfInkStringViewModels}" />
或ContentPresenter.ItemTemplate
。
不要混用这两种方法,它不顺利:))
但无论如何,要更详细地解释一下你的具体问题
当您像这样创建UserControl时
ItemsControl.ItemTemplate
你基本上是在说
<v:InkStringView TextInControl="{Binding text}" />
未在XAML中的任何位置指定 var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
,因此它从父项继承,从而导致
vw.DataContext
所以设置vw.DataContext = Strings[x];
的绑定有效,并且在运行时解析得很好。
但是,当您在UserControl构造函数中运行它时
TextInControl = vw.DataContext.text
this.DataContext = new InkStringViewModel();
设置为一个值,因此不再自动从父级继承。
所以现在运行的代码如下所示:
DataContext
当然,var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
没有名为InkStringViewModel
的属性,因此绑定在运行时失败。
答案 1 :(得分:3)
好像你正在将父视图的模型与UC的模型混合在一起。
以下是与您的代码匹配的示例:
MainViewModel:
using System.Collections.Generic;
namespace UCItemsControl
{
public class MyString
{
public string text { get; set; }
}
public class MainViewModel
{
public ObservableCollection<MyString> Strings { get; set; }
public MainViewModel()
{
Strings = new ObservableCollection<MyString>
{
new MyString{ text = "First" },
new MyString{ text = "Second" },
new MyString{ text = "Third" }
};
}
}
}
使用它的MainWindow:
<Window x:Class="UCItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:UCItemsControl"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<v:MainViewModel></v:MainViewModel>
</Window.DataContext>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" x:Name="self" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<Style TargetType="v:InkStringView">
<Setter Property="FontSize" Value="25"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</DataTemplate.Resources>
<v:InkStringView TextInControl="{Binding text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
您的UC(没有DataContext集合):
public partial class InkStringView : UserControl
{
public InkStringView()
{
InitializeComponent();
}
public String TextInControl
{
get { return (String)GetValue(TextInControlProperty); }
set { SetValue(TextInControlProperty, value); }
}
public static readonly DependencyProperty TextInControlProperty =
DependencyProperty.Register("TextInControl", typeof(String), typeof(InkStringView));
}
(您的XAML没问题)
有了这个我可以获得我猜的预期结果,一个值列表:
First
I am row 1
Second
I am row 1
Third
I am row 1
答案 2 :(得分:2)
你需要在这里做两件事(我假设字符串是ObservableCollection<string>
)。
1)从InkStringView构造函数中删除this.DataContext = new InkStringViewModel();
。 DataContext将是Strings ObservableCollection的一个元素。
2)改变
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
到
<v:InkStringView TextInControl="{Binding }" />
你拥有的xaml正在寻找一个&#34; Text&#34; ItemsControl上的属性将值TextInControl绑定到。我使用DataContext(恰好是一个字符串)来绑定TextInControl的xaml。如果Strings实际上是一个ObservableCollection,其中包含要绑定的字符串属性SomeProperty,则将其更改为此。
<v:InkStringView TextInControl="{Binding SomeProperty}" />
答案 3 :(得分:1)
你快到了。问题是您正在为UserControl创建ViewModel。这是一种气味。
从外部看,UserControls的外观和行为与任何其他控件一样。您正确地在控件上具有公开的属性,并将内部控件绑定到这些属性。这都是正确的。
失败的地方是尝试为所有内容创建ViewModel。所以抛弃了愚蠢的InkStringViewModel并让任何使用该控件的人将他们的视图模型绑定到它。
如果你很想问“视图模型中的逻辑怎么样?如果我摆脱它,我将不得不将代码置于代码隐藏中!”我回答说,“它是业务逻辑吗?不管怎么说都不应该嵌入你的UserControl中。而且MVVM!=没有代码隐藏。使用代码隐藏来实现你的UI逻辑。这就是它所属的地方。”