.Net 4 绑定到 ElementHost (MVVM) 中的 WPF 控件

发布于 2024-10-31 23:49:15 字数 7003 浏览 3 评论 0原文

我在 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

妥活 2024-11-07 23:49:15

有很多代码需要解析,而且我已经有一段时间没有用 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文