我一直找不到一个干净、简单的例子,来说明如何进行 正确 用WPF实现一个用户控件,该控件具有以下功能 DependencyProperty
在MVVM框架内。我的下面的代码失败了,每当我给用户控制分配一个 DataContext
.
我正在尝试。
DependencyProperty
从调用的ItemsControl中获取,并且DependencyProperty
的ViewModel中可用。我还有很多东西要学,衷心感谢任何帮助。
这是 ItemsControl
的最上层用户控件中,调用的是 InkStringView
用户控制 DependencyProperty
TextInControl
另一个问题的例子)。
<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
用户控制 DependencyProperty
.
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>
</UserControl>
后台代码文件。
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));
}
}
这也是你永远不应该设置DependencyProperty的众多原因之一。DataContext
直接从 UserControl
本身。
当你这样做的时候,你不能再使用任何其他的 DataContext
因为UserControl的 DataContext
被硬编码到一个实例上,而这个实例只有 UserControl
的访问权限,这有点违背了WPF拥有独立UI和数据层的最大优势之一。
有两种主要的使用方法 UserControls
在WPF中
一个独立的 UserControl
可以在任何地方使用,不需要特定的 DataContext
被要求。
这种类型的 UserControl
通常暴露 DependencyProperties
的任何值,并会像这样使用。
<v:InkStringView TextInControl="{Binding SomeValue}" />
我能想到的典型例子是任何通用的东西,比如日历控件或弹出式控件。
A UserControl
是为了与特定的 Model
或 ViewModel
只有。
这些 UserControls
对我来说更为常见,也可能是你要找的东西。我如何使用这样的UserControl的一个例子是这样的。
<v:InkStringView DataContext="{Binding MyInkStringViewModelProperty}" />
或者更常见的是,它将与一个隐式的 DataTemplate
. 隐性 DataTemplate
是一个 DataTemplate
附带 DataType
而不 Key
WPF会在任何时候自动使用这个模板来渲染指定类型的对象。
<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
或 ItemsControl.ItemTemplate
在使用这种方法时是需要的。
不要把这两种方法混为一谈,这样做不好:)。)
但不管怎么说,再详细解释一下你的具体问题吧
当你像这样创建你的UserControl时
<v:InkStringView TextInControl="{Binding text}" />
你基本上是说
var vw = new InkStringView()
vw.TextInControl = vw.DataContext.text;
vw.DataContext
在XAML中没有任何地方指定,所以它将从父项中继承,这将导致
vw.DataContext = Strings[x];
所以你的绑定,设置 TextInControl = vw.DataContext.text
是有效的,并且在运行时可以很好地解析。
然而,当你在你的UserControl构造函数中运行这个函数时
this.DataContext = new InkStringViewModel();
的 DataContext
被设置为一个值,所以不再自动从父代继承。
所以现在被运行的代码是这样的。
var vw = new InkStringView()
vw.DataContext = new InkStringViewModel();
vw.TextInControl = vw.DataContext.text;
自然而然地, InkStringViewModel
没有一个叫做 text
所以在运行时绑定失败。
似乎你把父视图的模型和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
你需要在这里做两件事 (我假设Strings是一个...)。ObservableCollection<string>
).
1) 移除 this.DataContext = new InkStringViewModel();
的InkStringView构造函数。DataContext将是Strings ObservableCollection的一个元素。
2) 改变
<v:InkStringView TextInControl="{Binding text, ElementName=self}" />
到
<v:InkStringView TextInControl="{Binding }" />
你的xaml是在ItemsControl上寻找一个 "Text "属性来绑定TextInControl的值。我放的xaml使用DataContext(正好是一个字符串)来绑定TextInControl。如果Strings实际上是一个ObservableCollection,有一个字符串属性的SomeProperty,你想绑定到它,那就把它改成这个。
<v:InkStringView TextInControl="{Binding SomeProperty}" />
你就快成功了。 问题是你正在为你的UserControl创建一个ViewModel。 这是一个气味。
UserControls的外观和行为应该和其他控件一样,从外部来看。你正确地在控件上暴露了属性,并将内部控件绑定到这些属性上。 这都是正确的。
你失败的地方是试图为所有的东西创建一个ViewModel。 所以抛弃那个愚蠢的InkStringViewModel,让使用控件的人将他们的视图模型绑定到它上面。
如果你很想问 "视图模型中的逻辑怎么办? 如果我把它去掉,我就得把代码放在codebehind里!" 我回答:"是业务逻辑吗? 那无论如何都不应该嵌入到你的UserControl中。 而MVVM !=没有codebehind。 用codebehind来处理你的UI逻辑。 这是属于它的地方。"