.NET Maui中如何在不破坏MVVM的前提下控制MediaElement
如何在.NET Maui的MVVM模式下控制MediaElement播放/停止?
问题场景
我是.NET Maui新手,需要实现以下需求:页面包含一个隐藏的MediaElement(需自定义控制)和两个按钮,点击「播放」按钮时播放绑定的MP3文件,点击「停止」按钮时停止播放。功能本身能实现,但无法在MVVM模式下合规完成。
原始代码
View部分
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="LifeTrax.Mobile.MyPage" xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" Title="Main Player Page"> <VerticalStackLayout> <Button Text="Play Sound" Command="{Binding StartPlayingSoundCommand}"></Button> <Button Text="Stop Sound" Command="{Binding StopPlayingSoundCommand}"></Button> <toolkit:MediaElement Grid.Row="0" Grid.Column="0" ShouldShowPlaybackControls="False" ShouldAutoPlay="False" ShouldLoopPlayback="False" IsVisible="False" Volume="0.15" Source="filename.mp3"></toolkit:MediaElement> </VerticalStackLayout> </ContentPage>
ViewModel部分
public partial class MyViewModel : ObservableObject { [ObservableProperty] private String _buttonName = "A"; [ObservableProperty] private String _playerSource = "filename.mp3"; private readonly INavigationService navigationService; public MyViewModel(INavigationService navigationService) { this.navigationService = navigationService; } [RelayCommand] private Task NavigateToEditPage() => navigationService.GoToEdit(); [RelayCommand] private Task NavigateToAboutPage() => navigationService.GoToAbout(); [RelayCommand] private Task StartPlayingSound() { // Do whatever return Task.CompletedTask; } [RelayCommand] private Task StopPlayingSound() { // Do whatever return Task.CompletedTask; } }
尝试过的方案及问题
- 将MediaElement实例移到ViewModel中,通过按钮命令直接调用
MediaElement.Play():违反MVVM模式(ViewModel不应持有UI控件实例) - 在View的代码后置中编写按钮点击事件处理逻辑:业务逻辑耦合到View中,同样不符合MVVM规范
符合MVVM的解决方案
核心思路:MediaElement本身没有用于触发播放/停止的可绑定属性,因此通过继承MediaElement创建自定义控件,添加两个布尔类型的可绑定属性,通过属性变更事件来触发播放/停止操作,从而实现ViewModel与View的绑定通信。
1. 创建自定义EnhancedMediaElement控件
public partial class EnhancedMediaElement : MediaElement { #region Should Initiate Play public static readonly BindableProperty ShouldInitiatePlayProperty = BindableProperty.Create("ShouldInitiatePlay", typeof(Boolean), typeof(EnhancedMediaElement), false, propertyChanged: OnShouldInitiatePlayChanged); public Boolean ShouldInitiatePlay { get { return (Boolean)GetValue(ShouldInitiatePlayProperty); } set { SetValue(ShouldInitiatePlayProperty, value); } } static void OnShouldInitiatePlayChanged(BindableObject bindable, object oldValue, object newValue) { EnhancedMediaElement eme = (EnhancedMediaElement)bindable; if ((Boolean)newValue == true) { eme.Stop(); eme.Play(); eme.ShouldInitiatePlay = false; } } #endregion #region Should Initiate Stop public static readonly BindableProperty ShouldInitiateStopProperty = BindableProperty.Create("ShouldInitiateStop", typeof(Boolean), typeof(EnhancedMediaElement), false, propertyChanged: OnShouldInitiateStopChanged); public Boolean ShouldInitiateStop { get { return (Boolean)GetValue(ShouldInitiateStopProperty); } set { SetValue(ShouldInitiateStopProperty, value); } } static void OnShouldInitiateStopChanged(BindableObject bindable, object oldValue, object newValue) { EnhancedMediaElement eme = (EnhancedMediaElement)bindable; if ((Boolean)newValue == true) { eme.Stop(); eme.ShouldInitiateStop = false; } } #endregion }
2. 扩展ViewModel,添加绑定属性
在ViewModel中添加两个用于触发播放/停止的布尔属性,并修改命令逻辑:
[ObservableProperty] private Boolean _shouldPlayerInitiatePlay = false; [ObservableProperty] private Boolean _shouldPlayerInitiateStop = false; [RelayCommand] private Task StartPlayingSound() { ShouldPlayerInitiatePlay = true; return Task.CompletedTask; } [RelayCommand] private Task StopPlayingSound() { ShouldPlayerInitiateStop = true; return Task.CompletedTask; }
3. 更新View,使用自定义控件并配置绑定
首先在XAML页面的根节点添加自定义控件的命名空间:
xmlns:customcontrols="clr-namespace:MySolution.Mobile.Custom_Controls"
然后替换原有的MediaElement为自定义的EnhancedMediaElement,并绑定对应的属性:
<customcontrols:EnhancedMediaElement Grid.Row="0" Grid.Column="0" ShouldShowPlaybackControls="False" ShouldAutoPlay="False" ShouldLoopPlayback="False" IsVisible="False" Volume="0.5" x:Name="mediaPlayer" ShouldInitiatePlay="{Binding ShouldPlayerInitiatePlay, Mode=TwoWay}" Source="{Binding PlayerSource}" ShouldInitiateStop="{Binding ShouldPlayerInitiateStop, Mode=TwoWay}"></customcontrols:EnhancedMediaElement>
效果说明
通过这种方式,ViewModel仅通过属性值的变更来触发播放/停止操作,完全不依赖UI控件实例;View则通过自定义控件的属性变更事件处理具体的播放逻辑,严格遵循MVVM模式的职责分离原则。该方案还可以进一步扩展,添加更多自定义绑定属性来实现MediaElement的其他控制逻辑。
内容的提问来源于stack exchange,提问作者Skyraider
相关产品推荐
相关产品推荐

