.Net 4 绑定到 ElementHost (MVVM) 中的 WPF 控件
我在 Winforms 应用程序中有一个 WPF ElementHost
。用户控件有一些文本和一个 TreeView ,它应该显示应用程序提供的可用命令树。
我是 WPF 新手(这是一个学习练习),因此在绑定数据时遇到一些问题。
我创建了一个 CommandTreeViewModel 类来充当我的视图模型。它有一个 FirstGeneration
属性,它是一个 IEnumerable(of CommandViewModel)
。 CommandViewModel
类又具有一些描述 Command
的简单属性,其中包括 Children
属性(同样是 IEnumerable(of CommandViewModel)
代码>)。
我已将 Public Property ViewModel As CommandTreeViewModel
添加到我的 WPF 用户控件,目前由我的 winforms 应用程序设置。
我不知道该怎么做,就是获取 ViewModel 属性中传递的数据并将其绑定到 TreeView。 (有没有什么方法可以强类型化我的 XAML 绑定的 ViewModel 类 A-la MVC?)
我已经包含了我认为下面的相关代码,以备需要时使用。
用户控制
Public Class WPFCommandTree
Implements INotifyPropertyChanged
Public Property ViewModel As CommandTreeViewModel
Get
Return DirectCast(GetValue(ViewModelProperty), CommandTreeViewModel)
End Get
Set(ByVal value As CommandTreeViewModel)
If Not value.Equals(DirectCast(GetValue(ViewModelProperty), CommandTreeViewModel)) Then
SetValue(ViewModelProperty, value)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ViewModelProperty"))
End If
End Set
End Property
Public Shared ReadOnly ViewModelProperty As DependencyProperty = _
DependencyProperty.Register("ViewModel",
GetType(CommandTreeViewModel), GetType(Window),
New FrameworkPropertyMetadata(Nothing))
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
XAML
<UserControl x:Class="WPFCommandTree"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock FontSize="30" HorizontalAlignment="Center" VerticalAlignment="Center">Test</TextBlock>
<TreeView ItemsSource="{Binding FirstGeneration}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Grid.Row="1"
DataContext="{Binding}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight"
Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</UserControl>
视图模型
Public Class CommandTreeViewModel
Public Property RootCommand As CommandViewModel
Public Property FirstGeneration As ReadOnlyCollection(Of CommandViewModel)
Public Sub New(ByVal RootCommand As Command)
_RootCommand = New CommandViewModel(RootCommand)
_FirstGeneration = New ReadOnlyCollection(Of CommandViewModel)(New CommandViewModel() {_RootCommand})
End Sub
Public Sub New(ByVal RootCommand As CommandViewModel)
Me.RootCommand = RootCommand
Me.FirstGeneration = New ReadOnlyCollection(Of CommandViewModel)(New CommandViewModel() {_RootCommand})
End Sub
Public Sub New(ByVal RootCommands As IEnumerable(Of CommandViewModel))
Me.RootCommand = RootCommands.First
Me.FirstGeneration = New ReadOnlyCollection(Of CommandViewModel)(RootCommands.ToList)
End Sub
End Class
Public Class CommandViewModel
Implements INotifyPropertyChanged
Public Property Command As Command
Public Property Children As ReadOnlyCollection(Of CommandViewModel)
Public Property Parent As CommandViewModel
Public Property Name As String
Private Property _IsSelected As Boolean
Public Property IsSelected() As Boolean
Get
Return _isSelected
End Get
Set(ByVal value As Boolean)
If value <> _isSelected Then
_isSelected = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("IsSelected"))
End If
End Set
End Property
Private Property _IsExpanded As Boolean
Public Property IsExpanded() As Boolean
Get
Return _IsExpanded
End Get
Set(ByVal value As Boolean)
If value <> IsExpanded Then
_IsExpanded = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("IsExpanded"))
If _IsExpanded And _Parent IsNot Nothing Then
_Parent.IsExpanded = True
End If
End If
End Set
End Property
Public Sub New(ByVal Command As Command)
Me.New(Command, Nothing)
End Sub
Private Sub New(ByVal Command As Command, ByVal Parent As CommandViewModel)
_Command = Command
_Parent = Parent
If Command.Children IsNot Nothing AndAlso Command.Children.Count > 0 Then
_Children = New ReadOnlyCollection(Of CommandViewModel)(
Command.Children.Select(Function(x) New CommandViewModel(x, Me)
).ToList)
End If
End Sub
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
正如你所看到的,我不太清楚我需要做什么,并且一直在尝试破解一些从各种教程中一起编写代码。大多数仅是 WPF。
运行上面的代码可以正确加载控件(我可以在表单上看到“测试”文本),但 TreeView
仍为空白。不会抛出任何错误。我假设这是因为我没有正确绑定数据。
最后,我也不清楚我的哪些财产需要成为DP?用户控件、ViewModel、模型子项?我只是不清楚绑定是如何工作的。
I've got a WPF ElementHost
in a Winforms app. The User Control has some text and a TreeView
which should show a tree of available commands provided by the app.
I'm new to WPF (this is a learning exercise) so am having some problems binding the data.
I've created a CommandTreeViewModel
class to act as my view model. It has a FirstGeneration
property which is an IEnumerable(of CommandViewModel)
. The CommandViewModel
class in turn has some simple properties describing the Command
including a Children
property (again an IEnumerable(of CommandViewModel)
).
I've added a Public Property ViewModel As CommandTreeViewModel
to my WPF User Control which is set by my winforms app for now.
What I don't know how to do is take that data I've been handed in the ViewModel property and bind it to the TreeView. (And is there any way to strongly type my XAML Binding's ViewModel Class a-la MVC?)
I've included what I believe is the pertinent code below in case it's needed.
User Control
Public Class WPFCommandTree
Implements INotifyPropertyChanged
Public Property ViewModel As CommandTreeViewModel
Get
Return DirectCast(GetValue(ViewModelProperty), CommandTreeViewModel)
End Get
Set(ByVal value As CommandTreeViewModel)
If Not value.Equals(DirectCast(GetValue(ViewModelProperty), CommandTreeViewModel)) Then
SetValue(ViewModelProperty, value)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ViewModelProperty"))
End If
End Set
End Property
Public Shared ReadOnly ViewModelProperty As DependencyProperty = _
DependencyProperty.Register("ViewModel",
GetType(CommandTreeViewModel), GetType(Window),
New FrameworkPropertyMetadata(Nothing))
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
XAML
<UserControl x:Class="WPFCommandTree"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock FontSize="30" HorizontalAlignment="Center" VerticalAlignment="Center">Test</TextBlock>
<TreeView ItemsSource="{Binding FirstGeneration}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Grid.Row="1"
DataContext="{Binding}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight"
Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</UserControl>
View Models
Public Class CommandTreeViewModel
Public Property RootCommand As CommandViewModel
Public Property FirstGeneration As ReadOnlyCollection(Of CommandViewModel)
Public Sub New(ByVal RootCommand As Command)
_RootCommand = New CommandViewModel(RootCommand)
_FirstGeneration = New ReadOnlyCollection(Of CommandViewModel)(New CommandViewModel() {_RootCommand})
End Sub
Public Sub New(ByVal RootCommand As CommandViewModel)
Me.RootCommand = RootCommand
Me.FirstGeneration = New ReadOnlyCollection(Of CommandViewModel)(New CommandViewModel() {_RootCommand})
End Sub
Public Sub New(ByVal RootCommands As IEnumerable(Of CommandViewModel))
Me.RootCommand = RootCommands.First
Me.FirstGeneration = New ReadOnlyCollection(Of CommandViewModel)(RootCommands.ToList)
End Sub
End Class
Public Class CommandViewModel
Implements INotifyPropertyChanged
Public Property Command As Command
Public Property Children As ReadOnlyCollection(Of CommandViewModel)
Public Property Parent As CommandViewModel
Public Property Name As String
Private Property _IsSelected As Boolean
Public Property IsSelected() As Boolean
Get
Return _isSelected
End Get
Set(ByVal value As Boolean)
If value <> _isSelected Then
_isSelected = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("IsSelected"))
End If
End Set
End Property
Private Property _IsExpanded As Boolean
Public Property IsExpanded() As Boolean
Get
Return _IsExpanded
End Get
Set(ByVal value As Boolean)
If value <> IsExpanded Then
_IsExpanded = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("IsExpanded"))
If _IsExpanded And _Parent IsNot Nothing Then
_Parent.IsExpanded = True
End If
End If
End Set
End Property
Public Sub New(ByVal Command As Command)
Me.New(Command, Nothing)
End Sub
Private Sub New(ByVal Command As Command, ByVal Parent As CommandViewModel)
_Command = Command
_Parent = Parent
If Command.Children IsNot Nothing AndAlso Command.Children.Count > 0 Then
_Children = New ReadOnlyCollection(Of CommandViewModel)(
Command.Children.Select(Function(x) New CommandViewModel(x, Me)
).ToList)
End If
End Sub
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
As you can see, I'm not really clear on what I need to do and have been trying to hack some code together from various tutorials. Most are WPF only.
Running the above loads the cotrol correctly (I can see the "Test" text on my form) but the TreeView
remains blank. No errors are thrown. I'm assuming this is because I haven't bound the data correctly.
Finally, I' also unclear on which of my properties need to be DPs? the User Control, ViewModel, Model Children? I'm just not clear on how the binding works.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
有很多代码需要解析,而且我已经有一段时间没有用 VB 编写代码了,但我至少看到了一些东西。
我建议你从更简单的开始。如果您确实刚刚开始,请首先尝试获取简单的绑定内容,而不是具有多个级别命令的树视图。在使用分层数据模板、添加样式(带有触发器!)等之前,尝试先绑定一个平面数据网格。恕我直言,您一次引入了太多概念。
我认为你根本不需要在这里使用依赖属性,除非我遗漏了一些东西。依赖属性主要用于当您想要将用户控件放入另一个控件/窗口并能够通过 XAML/数据绑定控制属性时。
看起来您正在尝试通过依赖项属性设置 viewModel。相反,只需在代码隐藏的构造函数中设置 DataContext 属性,例如 Me.DataContext = New CommandTreeViewModel (记住我的 VB 很生锈:))。实际上,这可能是绑定不起作用的主要问题,因为视图的 DataContext 未设置。
最后,VS 中的调试输出窗口应该包含所有数据绑定错误,这对于找出绑定失败的位置有很大帮助。
It's a lot of code to parse, and I haven't coded in VB for a while, but I see at least a few things.
I would recommend you start a little more simply. If you are really just getting started, start out by trying to get something simple to bind, not a treeview with multiple levels of commands. Try getting a flat datagrid to bind before going for a hierarchical data template, adding styles (with triggers!), etc. You are introducing too many concepts at once IMHO.
I don't think you need to be using Dependency Properties at all here, unless I am missing something. Dependency properties are mainly used when you want to drop a usercontrol into another control/window and have the ability to control the property via XAML/Databinding.
It looks like you are trying to set your viewModel via a dependency property. Instead, just set the DataContext property in the constructor on your codebehind--something like Me.DataContext = New CommandTreeViewModel (remember my VB is rusty :) ). This is probably your main problem with the binding not working, actually, as the View's DataContext is not getting set.
Finally, your debug output window in VS should contain any databinding errors, which is a big help in figuring out where binding is failing.