Wpf UserControl со своим собственным контекстом данных и внешним свойством зависимости

Я пытаюсь создать простой элемент управления 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>

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

Мой вопрос заключается просто в следующем. Из кода видно, что в настоящее время воспроизводимый звук жестко запрограммирован. Я хотел бы знать, можно ли создать свойство зависимости для этого элемента управления (я предполагаю, что это будет строка типа, представляющая путь к звуковому файлу, но я не уверен), чтобы, когда элемент управления созданные в других элементах управления или окнах, их модели представления могут передавать ему свойство звука (если это имеет смысл!). Если возможно, где я должен создать его в отношении показанных фрагментов кода?

Большое спасибо


person Dom Sinclair    schedule 28.01.2015    source источник


Ответы (1)


Вы можете создать DP, но это не будет работать так, как ожидают пользователи.

Например, если бы пользователь написал

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

Затем WPF пытается установить Media = DataContext.SomeString

Но поскольку вы жестко запрограммировали DataContext = New AudioPlayerViewModel в конструкторе, привязка, скорее всего, завершится ошибкой, потому что пользователи будут ожидать, что их унаследованный DataContext будет использоваться UserControl, но вместо этого будет использоваться жестко запрограммированный DataContext.


Я всегда советую никогда жестко не задавать свойство DataContext внутри UserControl. Это ломает весь шаблон проектирования WPF с отдельными слоями для пользовательского интерфейса и данных.

Либо создайте UserControl специально для использования с определенной моделью или ViewModel, используемой в качестве DataContext, например:

<!-- 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
  • Привяжите или установите DataContext дочернего элемента верхнего уровня внутри вашего UserControl к вашему частному свойству (в вашем случае 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
person Rachel    schedule 28.01.2015