Подтвердить что ты не робот

Нарушение шаблона MVVM: MediaElement.Play()

Я понимаю, что ViewModel не должен иметь никаких знаний о представлении, но как я могу вызвать метод MediaElement.Play() из ViewModel, кроме ссылки на View (или непосредственно на MediaElement) в ViewModel?
Другой (связанный) вопрос: как я могу управлять View control visibility из ViewModel без нарушения шаблона MVVM?

4b9b3361

Ответ 1

1) Не вызывайте Play() из модели представления. Вместо этого создайте событие в модели представления (например, PlayRequested) и прослушайте это событие в представлении:

просмотреть модель:

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

Вид:

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

2) Вы можете выставить в модели представления публичное логическое свойство и привязать свойство Visibility к вашим элементам управления к этому свойству. Поскольку Visibility имеет тип Visibility, а не bool, вам придется использовать конвертер.

Вы можете найти базовую реализацию такого конвертера здесь. Этот вопрос может помочь вам.

Ответ 2

Для всех поздних,

Существует много способов достижения одного и того же результата, и это действительно зависит от того, как вы хотели бы реализовать свои, до тех пор, пока ваш код не сложно поддерживать, я действительно считаю, что в некоторых случаях можно нарушить шаблон MVVM.

Но, сказав это, я также считаю, что всегда есть способ сделать это в рамках шаблона, и следующим является один из них на всякий случай, если кто-то хочет знать, какие другие альтернативы доступны.

Задачи:

  • мы не хотим иметь прямую ссылку из ViewModel на любые элементы пользовательского интерфейса, то есть на MediaElement и сам вид.
  • мы хотим использовать команду для создания волшебства здесь

Решение:

Короче говоря, мы собираемся ввести интерфейс между View и ViewModel, чтобы разбить dependecy, и View будет реализовывать интерфейс и нести ответственность за непосредственное управление MediaElement, оставив ViewModel, говорящий только с интерфейс, который может быть заменен другой реализацией для целей тестирования, если это необходимо, и вот длинная версия:

  • Ввести интерфейс IMediaService, как показано ниже:

    public interface IMediaService
    {
        void Play();
        void Pause();
        void Stop();
        void Rewind();
        void FastForward();
    }
    
  • Реализовать IMediaService в представлении:

    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();
        }
    }
    
  • Затем мы делаем несколько вещей в DemoView.XAML:

    • Дайте MediaElement имя, чтобы доступный ему код мог получить доступ к нему, как указано выше:
       <MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>
    
    • Дайте виду имя, чтобы мы могли передать его как параметр, и
    • импортировать пространство имен интерактивности для последующего использования (некоторые пространства имен по умолчанию опущены для простоты):
       <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">
    
    • Подключить загруженное событие через триггер, чтобы передать представление в модель представления с помощью команды
       <ia:Interaction.Triggers>
             <ia:EventTrigger EventName="Loaded">
                 <ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction>
             </ia:EventTrigger>
         </ia:Interaction.Triggers>
    
    • последнее, но не менее важное: нам нужно подключить средства управления мультимедиа с помощью команд:
       <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> 
    
  • Теперь мы можем поймать все в ViewModel (я использую призма DelegateCommand здесь):

    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
        .
    }
    

Боковое примечание. Я использую функцию Prism Auto Wiring для соединения View и ViewModel. Таким образом, в файле View code за файлом отсутствует код назначения DataContext, и я предпочитаю сохранить его таким образом, и поэтому я решил использовать чисто команды для достижения этого результата.

Ответ 3

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

Все, что вам нужно делать при изменении источника (и это зависит от вас), заключается в том, чтобы установить свойство source равным null (вот почему свойство Source должно быть Uri, а не string, MediaElement, естественно, будет генерировать исключение, NotSupportedException, я думаю), затем установите его на любой URI, который вы хотите.

Возможно, самым важным аспектом этого совета является то, что вы должны установить свойство MediaElement LoadedBehaviour для воспроизведения в XAML вашего представления. Надеемся, что для достижения того, чего вы хотите достичь, не требуется никакого кода.

Трюк чрезвычайно прост, поэтому я не буду публиковать полный пример. Функция воспроизведения модели просмотра должна выглядеть следующим образом:

    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);
    }

Вот свойство Source, ничего особенного:

    #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

Что касается видимости, и так далее, вы можете использовать преобразователи (например, от bool до видимости, которые вы можете найти в CodePlex для WPF, SL, WP7,8) и привязать свойство управления к типу модели представления ( например IsVisible). Таким образом, вы контролируете части вашего зрения. Или вы можете просто получить свойство Visibility, набрав System.Windows.Visibility в вашей модели просмотра (я не вижу здесь никакого нарушения шаблона). Действительно, это не так уж редко.

Удачи,

Андрей

P.S. Я должен упомянуть, что .NET 4.5 - это версия, в которой я тестировал это, но я думаю, что он должен работать и с другими версиями.