我知道 ViewModel 不应该对 View 有任何了解,但是除了在 ViewModel 中引用 View(或直接指向 MediaElement)之外,如何从 ViewModel 调用 MediaElement.Play() 方法?
其他(链接)问题:如何在不违反 MVVM 模式的情况下从 ViewModel 管理 View 的控件可见性?
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
控件的属性绑定到该属性。作为Visibility
typeVisibility
而不是bool
,您必须使用转换器。
对于所有迟到的人,
有很多方法可以达到相同的结果,这实际上取决于您希望如何实现您的代码,只要您的代码不难维护,我相信在某些情况下打破 MVVM 模式是可以的。
但话虽如此,我也相信在模式中总有办法做到这一点,以下是其中之一,以防万一有人想知道还有哪些其他替代方案可用。
任务:
- 我们不想从 ViewModel 直接引用任何 UI 元素,即 MediaElement 和 View 本身。
- 我们想在这里使用命令来做魔术
解决方案:
简而言之,我们将在 View 和 ViewModel 之间引入一个接口来打破依赖关系,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">
- 通过触发器连接 Loaded 事件以通过命令将视图本身传递给视图模型
<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 中的所有内容(我在这里使用 prism 的 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 的代码隐藏文件中没有 DataContext 分配代码,我更喜欢保持这种方式,因此我选择使用纯粹的命令来实现这个结果。
每当应用程序中发生事件时,我都会使用媒体元素在 UI 中播放声音。处理此问题的视图模型是使用 Uri 类型的 Source 属性创建的(通知属性已更改,但您已经知道需要通知 UI)。
每当源更改时(这取决于您),您所要做的就是将源属性设置为 null(这就是为什么 Source 属性应该是 Uri 而不是字符串,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 for WPF、SL、WP7,8 上找到它)并将控件的属性绑定到视图模型的属性(例如 IsVisible) . 这样,您可以控制视图方面的部分内容。或者您可以在您的视图模型上仅使用 System.Windows.Visibility 类型的 Visibility 属性(我在这里看不到任何模式违规)。真的,这并不少见。
祝你好运,
安德烈
PS我不得不提到.NET 4.5是我测试过的版本,但我认为它也应该适用于其他版本。