You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 12:34:54