wpf mvvm visibility mediaelement

wpf - Violación del patrón MVVM: MediaElement.Play()



visibility (3)

Para todos los que llegan tarde,

Hay muchas formas de lograr el mismo resultado y realmente depende de cómo le gustaría implementar el suyo, siempre que su código no sea difícil de mantener, creo que está bien romper el patrón de MVVM en ciertos casos.

Pero dicho esto, también creo que siempre hay una manera de hacer esto dentro del patrón, y la siguiente es una de ellas en caso de que alguien quisiera saber qué otras alternativas están disponibles.

Las tareas:

  1. no queremos tener una referencia directa de ViewModel a ningún elemento de la interfaz de usuario, es decir, el elemento de medios y la vista en sí.
  2. Queremos usar el Comando para hacer la magia aquí.

La solución:

En resumen, vamos a introducir una interfaz entre View y ViewModel para romper la dependencia, y View implementará la interfaz y será responsable del control directo de MediaElement mientras dejamos ViewModel hablando solo a la interfaz, que se puede intercambiar con otra implementación para propósitos de prueba si es necesario, y aquí viene la versión larga:

  1. Introduzca una interfaz llamada IMediaService como a continuación:

    public interface IMediaService { void Play(); void Pause(); void Stop(); void Rewind(); void FastForward(); }

  2. Implementar el servicio IMedia en la vista:

    public partial class DemoView : UserControl, IMediaService { public DemoView() { InitializeComponent(); } void IMediaService.FastForward() { this.MediaPlayer.Position += TimeSpan.FromSeconds(10); } void IMediaService.Pause() { this.MediaPlayer.Pause(); } void IMediaService.Play() { this.MediaPlayer.Play(); } void IMediaService.Rewind() { this.MediaPlayer.Position -= TimeSpan.FromSeconds(10); } void IMediaService.Stop() { this.MediaPlayer.Stop(); } }

  3. Entonces hacemos algunas cosas en el DemoView.XAML:

    • Asigne un nombre a MediaElement para que el código subyacente pueda acceder a él como se muestra arriba:

    <MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>

    • Asígnele un nombre a la vista para que podamos pasarla como parámetro, y
    • importe el espacio de nombres de interactividad para su uso posterior (algunos espacios de nombres predeterminados se omiten por razones de simplicidad):

    <UserControl x:Class="Test.DemoView" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity" x:Name="MediaService">

    • Conecte el evento Loaded a través de Trigger para pasar la vista al modelo de vista a través de un comando

    <ia:Interaction.Triggers> <ia:EventTrigger EventName="Loaded"> <ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction> </ia:EventTrigger> </ia:Interaction.Triggers>

    • Por último, pero no menos importante, debemos conectar los controles de medios a través de Comandos:

    <Button Command="{Binding PlayCommand}" Content="Play"></Button> <Button Command="{Binding PauseCommand}" Content="Pause"></Button> <Button Command="{Binding StopCommand}" Content="Stop"></Button> <Button Command="{Binding RewindCommand}" Content="Rewind"></Button> <Button Command="{Binding FastForwardCommand}" Content="FastForward"></Button>

  4. Ahora podemos capturar todo en ViewModel (aquí estoy usando DelegateCommand de prism):

    public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest { public IMediaService {get; private set;} private DelegateCommand<IMediaService> loadedCommand; public DelegateCommand<IMediaService> LoadedCommand { get { if (this.loadedCommand == null) { this.loadedCommand = new DelegateCommand<IMediaService>((mediaService) => { this.MediaService = mediaService; }); } return loadedCommand; } } private DelegateCommand playCommand; public DelegateCommand PlayCommand { get { if (this.playCommand == null) { this.playCommand = new DelegateCommand(() => { this.MediaService.Play(); }); } return playCommand; } } . . // other commands are not listed, but you get the idea . }

Nota al margen: uso la función de cableado automático de Prism para vincular los modelos View y ViewModel. Entonces, en el código detrás del archivo de la Vista no hay un código de asignación de DataContext, y prefiero mantenerlo de esa manera, y por lo tanto, elegí usar solo Comandos para lograr este resultado.

Entiendo que ViewModel no debería tener ningún conocimiento de View, pero ¿cómo puedo llamar al método MediaElement.Play () desde ViewModel, aparte de tener una referencia a View (o directamente a MediaElement) en ViewModel?
Otra pregunta (vinculada): ¿Cómo puedo administrar la visibilidad de los controles de Vista desde ViewModel sin violar el patrón MVVM?


Utilizo el elemento multimedia para reproducir sonidos en la interfaz de usuario cuando se produce un evento en la aplicación. El modelo de vista que maneja esto, se creó con una propiedad de origen de tipo Uri (con la propiedad de notificación modificada, pero ya sabe que necesita notificar a la interfaz de usuario).

Todo lo que tiene que hacer siempre que la fuente cambie (y esto depende de usted), es establecer la propiedad de la fuente en nula (esta es la razón por la que la propiedad de la fuente debe ser Uri y no la cadena, el elemento Media naturalmente arrojará una excepción, no creo), entonces configúralo a cualquier URI que quieras.

Probablemente, el aspecto más importante de esta sugerencia es que tiene que configurar la propiedad LoadedBehaviour de MediaElement como Reproducir en XAML de su vista. Esperemos que no se necesite un código detrás para lo que quiere lograr.

El truco es extremadamente simple, así que no publicaré un ejemplo completo. La función de reproducción del modelo de vista debería verse así:

private void PlaySomething(string fileUri) { if (string.IsNullOrWhiteSpace(fileUri)) return; // HACK for MediaElement: to force it to play a new source, set source to null then put the real source URI. this.Source = null; this.Source = new Uri(fileUri); }

Aquí está la propiedad Source, nada especial al respecto:

#region Source property /// <summary> /// Stores Source value. /// </summary> private Uri _Source = null; /// <summary> /// Gets or sets file URI to play. /// </summary> public Uri Source { get { return this._Source; } private set { if (this._Source != value) { this._Source = value; this.RaisePropertyChanged("Source"); } } } #endregion Source property

En cuanto a la visibilidad y este tipo de cosas, puede usar convertidores (por ejemplo, de bool a visibilidad, que puede encontrar en CodePlex para WPF, SL, WP7,8) y vincular las propiedades de su control con las del modelo de vista (por ejemplo, IsVisible) . De esta manera, controlas partes del aspecto de tu vista. O simplemente puede tener la propiedad Visibilidad escrita System.Windows.Visibility en su modelo de vista (no veo ninguna brecha de patrón aquí). Realmente, no es tan raro.

Buena suerte,

Andrei

PD: Tengo que mencionar que .NET 4.5 es la versión donde probé esto, pero creo que también debería funcionar en otras versiones.


1) No llame a Play() desde el modelo de vista. En su lugar, PlayRequested un evento en el modelo de vista (por ejemplo, PlayRequested ) y escuche este evento en la vista:

ver modelo:

public event EventHandler PlayRequested; ... if (this.PlayRequested != null) { this.PlayRequested(this, EventArgs.Empty); }

ver:

ViewModel vm = new ViewModel(); this.DataContext = vm; vm.PlayRequested += (sender, e) => { this.myMediaElement.Play(); };

2) Puede exponer en el modelo de vista una propiedad booleana pública y vincular la propiedad Visibility de sus controles a esta propiedad. Como la Visibility es de tipo Visibility y no bool , tendrá que usar un convertidor.

Puede encontrar una implementación básica de un convertidor de here . Esta pregunta relacionada puede ayudarte también.