简体   繁体   English

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

[英]Wpf UserControl with its own data context and external dependency property

I'm trying to create a simple AudioPlayer control multiple reuse in a solution I'm working on. 我正在尝试创建一个简单的AudioPlayer控件,以便在我正在使用的解决方案中进行多次重用。 I have seen numerous example in various posts and blogs around the net and from those have created a small control with four buttons. 我在网上的各种帖子和博客中看到了无数示例,并从中创建了带有四个按钮的小控件。

The xaml is defined thus: 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>

With fairly simple code in the background; 在后台有相当简单的代码;

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

My question quite simply is this. 我的问题很简单是这个。 From the code you can see that at present the sound that is being played is hard coded. 从代码中可以看到,目前正在播放的声音已经过硬编码。 What I would like to know is wheteher it would be possible to create a dependency property for this control (I presume it would be of type string to represent a path to a sound file but I'm not sure) so that when the control is created in other controls or windows their viewmodels can pass a sound property to it (if that makes sense!). 我想知道的是,有可能为此控件创建一个依赖项属性(我想它应该是字符串类型,代表声音文件的路径,但我不确定),以便在控件处于在其他控件或窗口中创建的它们的视图模型可以向其传递声音属性(如果有道理!)。 If it is possible where should I create it in respect of the code snippets shown? 如果有可能,我应该根据所示的代码片段在何处创建它?

Many thanks 非常感谢

You could create a DP, but it would not work the way users would expect. 您可以创建一个DP,但是它不能像用户期望的那样工作。

For example, if the user were to write 例如,如果用户要写

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

Then WPF tries to set Media = DataContext.SomeString 然后WPF尝试设置Media = DataContext.SomeString

But since you have hardcoded DataContext = New AudioPlayerViewModel in the constructor, then the binding will most likely fail because users will be expecting their inherited DataContext to be used by the UserControl, but the hardcoded DataContext will be used instead. 但是,由于在构造函数中已对DataContext = New AudioPlayerViewModel进行了硬编码,因此绑定很可能会失败,因为用户希望其继承的DataContext被UserControl使用,但是将使用硬编码的DataContext。


It is always my advice to never hardcode the DataContext property inside of a UserControl. 始终建议不要在UserControl内部对DataContext属性进行硬编码。 It breaks the entire WPF design pattern of having separate layers for UI and Data. 它打破了将UI和数据具有单独的图层的整个WPF设计模式。

Either build a UserControl specifically for use with a specific Model or ViewModel being used as the DataContext , such as this : 构建专门用于与用作DataContext的特定Model或ViewModel一起使用的UserControl,例如:

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

Or build it with the expectation that the DataContext can be absolutely anything, and DependencyProperites will be used to give the control the data it needs : 或在期望DataContext绝对可以是任何东西的情况下进行构建,并且DependencyProperites将用于为控件提供所需的数据:

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

The easiest way to get your code to work would probably be 使代码正常工作的最简单方法可能是

  • Create the ViewModel as a private property instead of assiging it to the UserControl.DataContext 创建ViewModel作为私有属性,而不是将其辅助到UserControl.DataContext
  • Bind or set the DataContext of the top level child inside your UserControl to your private property (in your case, the StackPanel) 将UserControl中顶级子级的DataContext绑定或设置为私有属性(在您的情况下为StackPanel)
  • Adjust the binding for your MediaElement to read from a custom DependencyProperty instead of from StackPanel.DataContext 调整MediaElement的绑定以从自定义DependencyProperty而不是从StackPanel.DataContext读取

Something like this : 像这样的东西:

<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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM