Нарушение шаблона MVVM: MediaElement.Играть()
Я понимаю, что ViewModel не должен иметь никаких знаний о View, но как я могу назвать MediaElement.Метод Play () от ViewModel, кроме ссылки на View (или непосредственно на MediaElement) в ViewModel?
Другой (связанный) вопрос: как я могу управлять видимостью элементов управления View из ViewModel без нарушения шаблона MVVM?
3 ответов
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
свойства элементов управления на это имущество. As Visibility
типа Visibility
, а не bool
, вам придется использовать конвертер.
вы можете найти основной реализация такого конвертера здесь. Это вопрос может и тебе поможет.
для всех опоздавших,
есть много способов достичь того же результата, и это действительно зависит от того, как вы хотели бы реализовать свой, пока ваш код не сложно поддерживать, я считаю, что это нормально, чтобы сломать шаблон MVVM в определенных случаях.
но сказав это, я также верю, что всегда есть способ сделать это в шаблон, и вот один из них на всякий случай, если кто-нибудь хотел бы знать, какие другие альтернативы доступный.
Задачи:
- мы не хотим иметь прямую ссылку от ViewModel на какие-либо элементы пользовательского интерфейса, т. е. MediaElement и само представление.
- мы хотим использовать команду, чтобы сделать волшебство здесь
Решение:
короче говоря, мы собираемся ввести интерфейс между представлением и ViewModel, чтобы сломать dependecy, и представление будет реализовывать интерфейс и нести ответственность за прямое управление 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 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 . }
боковое примечание: Я использую функцию автоматической проводки Prism, чтобы связать вид и ViewModel. Так код представления за файлом нет кода назначения DataContext, и я предпочитаю сохранить его таким образом, и поэтому я решил использовать чисто команды для достижения этого результата.
Я использую media element для воспроизведения звуков в UI всякий раз, когда в приложении происходит событие. Модель представления, обрабатывающая это, была создана с исходным свойством типа Uri (с измененным свойством notify, но вы уже знаете, что это нужно для уведомления пользовательского интерфейса).
все, что вам нужно сделать, когда источник изменяется (и это зависит от вас), это установить свойство 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.Окна.Видимость на вашей модели представления (я не вижу здесь никакого нарушения шаблона). Правда, дело не в этом. необычный.
удачи,
Андрей
P.S. Я должен упомянуть, что .NET 4.5-это версия, в которой я тестировал это, но я думаю, что она должна работать и на других версиях.