具有自己的数据上下文和外部依赖性的 Wpf UserControl 属性

Wpf UserControl with its own data context and external dependency property

我正在尝试创建一个简单的 AudioPlayer 控件,以便在我正在处理的解决方案中多次重用。我在网络上的各种帖子和博客中看到了大量示例,并从中创建了一个带有四个按钮的小控件。

xaml 是这样定义的:

<UserControl x:Class="AudioPlayer"
         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="30" d:DesignWidth="150">
<StackPanel Orientation="Horizontal">
    <StackPanel.Resources>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Margin" Value="10,0,0,0" />
        </Style>
    </StackPanel.Resources>
     <MediaElement Name="media" Source="{Binding Source}" LoadedBehavior="{Binding LoadedBehavior}"/>
    <Button Width="24" Height="24" x:Name="Repeat" Background="Transparent" BorderBrush="Transparent">
        <Image Source="Images/button_blue_repeat.png" ToolTip="Repeat"/>
    </Button>
    <Button Width="24" Height="24" x:Name="Play" Background="Transparent" BorderBrush="Transparent">
        <Image Source="Images/button_blue_play.png" ToolTip="Play"/>
    </Button>
    <Button Width="24" Height="24" x:Name="Pause" Background="Transparent" BorderBrush="Transparent">
        <Image Source="Images/button_blue_pause.png" ToolTip="Pause"/>
    </Button>
    <Button Width="24" Height="24" x:Name="Stop" Background="Transparent" BorderBrush="Transparent">
        <Image Source="Images/button_blue_stop.png" ToolTip="Stop"/>
    </Button>
</StackPanel>

后台代码相当简单;

Public Class AudioPlayer

Public Sub New()

    InitializeComponent()
    DataContext = New AudioPlayerViewModel With {.MediaElement = media, .Source = "bag1.mp3", .LoadedBehavior = MediaState.Manual, .CanCommandExecute = True}

End Sub

End Class

    Public Class AudioPlayerViewModel
        Inherits DependencyObject

        Public Sub New()
            Me.MediaCommand = New MediaElementCommand(Me)
        End Sub
        Public Property MediaElement() As MediaElement
        Public Property Source() As String
        Public Property LoadedBehavior() As MediaState
        Public Property CanCommandExecute() As Boolean
        Public Property MediaCommand() As ICommand
    End Class

Public Class MediaElementCommand
    Implements ICommand

    Private vm As AudioPlayerViewModel
    Public Sub New(ByVal vm As AudioPlayerViewModel)
        Me.vm = vm
    End Sub
    Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
        Return vm.CanCommandExecute
    End Function
    Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
        AddHandler(ByVal value As EventHandler)
            AddHandler CommandManager.RequerySuggested, value
        End AddHandler
        RemoveHandler(ByVal value As EventHandler)
            RemoveHandler CommandManager.RequerySuggested, value
        End RemoveHandler
        RaiseEvent(ByVal sender As System.Object, ByVal e As System.EventArgs)
        End RaiseEvent
    End Event
    Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
        Dim action As String = DirectCast(parameter, String)
        Select Case action.ToLower()
            Case "play"
                vm.MediaElement.Position = TimeSpan.Zero
                vm.MediaElement.Play()
            Case "stop"
                vm.MediaElement.Stop()
            Case "pause"
                vm.MediaElement.Pause()
            Case "resume"
                vm.MediaElement.Play()
            Case Else
                Throw New NotSupportedException(String.Format("Unknown media action {0}", action))
        End Select
    End Sub
End Class

我的问题很简单就是这个。从代码中可以看出,目前正在播放的声音是硬编码的。我想知道是否可以为此控件创建依赖项 属性(我假设它是字符串类型来表示声音文件的路径,但我不确定)以便当在其他控件或 windows 中创建控件时,它们的视图模型可以将声音 属性 传递给它(如果这有意义!)。 如果可能的话,我应该根据显示的代码片段在哪里创建它?

非常感谢

您可以创建一个 DP,但它不会像用户期望的那样工作。

例如,如果用户要写

<local:AudioPlayer Media="{Binding SomeString}" />

然后 WPF 尝试设置 Media = DataContext.SomeString

但是由于您已经在构造函数中进行了硬编码 DataContext = New AudioPlayerViewModel,因此绑定很可能会失败,因为用户希望 UserControl 使用他们继承的 DataContext,但实际上将使用硬编码的 DataContext。


我始终建议 永远不要 对 UserControl 中的 DataContext 属性 进行硬编码。它打破了 UI 和 Data.

具有单独层的整个 WPF 设计模式

构建一个专门用于特定模型的 UserControl 或用作 DataContext 的 ViewModel,例如:

<!-- Draw anything of type AudioPlayerViewModel with control AudioPlayer -->
<!-- DataContext will automatically set to the AudioPlayerViewModel -->
<DataTemplate DataType="{x:Type local:AudioPlayerViewModel}}">
    <local:AudioPlayer /> 
</DataTemplate>

或者构建它时期望 DataContext 可以是任何东西,并且 DependencyProperites 将用于为控件提供它需要的数据:

<!-- DataContext property can be anything, as long as it as the property MyString -->
<local:AudioPlayer Media="{Binding MyString}" />

让代码正常工作的最简单方法可能是

  • 将 ViewModel 创建为私有 属性 而不是将其分配给 UserControl.DataContext
  • 将您的 UserControl 中顶级子项的 DataContext 绑定或设置为您的私有 属性(在您的情况下为 StackPanel)
  • 调整 MediaElement 的绑定以从自定义 DependencyProperty 而不是 StackPanel.DataContext
  • 读取

像这样:

<UserControl x:Name="MyAudioPlayer" ...>
    <StackPanel x:Name="AudioPlayerRoot">
        ...
        <MediaElement Source="{Binding ElementName=MyAudioPlayer, Path=MediaDependecyProperty}" ... />
        ...
    </StackPanel>
</UserControl>
Public Sub New()
    InitializeComponent()
    AudioPlayerRoot.DataContext = New AudioPlayerViewModel ...
End Sub