具有自己的数据上下文和外部依赖性的 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
我正在尝试创建一个简单的 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.
构建一个专门用于特定模型的 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