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

如何在.NET MAUI的ViewModel中调用MediaElement的方法?

在.NET MAUI的MVVM结构中从ViewModel调用MediaElement的Play/Stop方法

问题描述

我正在学习.NET MAUI并开发一款网络电台应用,使用CommunityToolkit包中的MediaElement控件,该控件拥有Play()、Stop()等方法。目前这些方法仅能在XAML设置x:Name后通过代码后台调用,我希望在ViewModel中调用这些方法,同时保留MVVM结构,该如何改写代码?

以下是现有代码:

NowPlayingViewModel.cs

namespace Rad.io.Client.MAUI.ViewModels
{
    [QueryProperty(nameof(CurrentStation), "CurrentStation")]
    public class NowPlayingViewModel : INotifyPropertyChanged
    {
        ...

        public StationInfo CurrentStation
        {
            get => currentStation;
            set
            {
                currentStation = value;
                RaisePropertyChanged();
            }
        }

        public NowPlayingViewModel(IRadioBrowserClient radioBrowserClient)
        {
            this.radioBrowserClient = radioBrowserClient;
        }

        public ICommand OnStopPressed => new Command(() => {
            mediaElement.Stop(); // <- 这里无法直接调用MediaElement的Stop()
            
        });

        public ICommand OnPlay => new Command(() =>
        {
            mediaElement.Play(); // <- 这里无法直接调用MediaElement的Play()
        });

        ...
    }
}

NowPlayingView.xaml.cs

namespace Rad.io.Client.MAUI.Views;

public partial class NowPlayingPanel : ContentView
{
    public NowPlayingPanel()
    {
        InitializeComponent();
    }

    void playStopButton_Clicked(System.Object sender, System.EventArgs e)
    {
        if (mediaElement.CurrentState == CommunityToolkit.Maui.Core.Primitives.MediaElementState.Playing)
        {
            mediaElement.Pause();
        } else if (mediaElement.CurrentState == CommunityToolkit.Maui.Core.Primitives.MediaElementState.Paused)
        {
            mediaElement.Play();
        }
        
    }
}

NowPlayingView.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewmodel="clr-namespace:Rad.io.Client.MAUI.ViewModels"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             x:DataType="viewmodel:NowPlayingViewModel"
             x:Class="Rad.io.Client.MAUI.Views.NowPlayingPanel">
    ...
            <toolkit:MediaElement x:Name="mediaElement" Grid.Column="1"
                ShouldShowPlaybackControls="False"
                BackgroundColor="DarkCyan"
                ShouldAutoPlay="True"
                Source="{Binding CurrentStation.Url}"/>
            ...
            <Button x:Name="playStopButton" Text="Play/Stop" Clicked="playStopButton_Clicked"/>

                </HorizontalStackLayout>
                    
            </FlexLayout>

        </Grid>
</ContentView>

解决方案

下面提供三种符合MVVM原则的实现方式,可根据项目复杂度选择:


方式一:使用Behavior封装MediaElement操作

通过自定义行为,将ViewModel的命令与MediaElement的方法绑定,完全解耦View和ViewModel:

  1. 创建MediaElementCommandBehavior类
using CommunityToolkit.Maui.Core.Primitives;
using Microsoft.Maui.Controls;
using System.Windows.Input;

namespace Rad.io.Client.MAUI.Behaviors
{
    public class MediaElementCommandBehavior : Behavior<MediaElement>
    {
        public static readonly BindableProperty PlayCommandProperty =
            BindableProperty.Create(nameof(PlayCommand), typeof(ICommand), typeof(MediaElementCommandBehavior));

        public static readonly BindableProperty StopCommandProperty =
            BindableProperty.Create(nameof(StopCommand), typeof(ICommand), typeof(MediaElementCommandBehavior));

        public ICommand PlayCommand
        {
            get => (ICommand)GetValue(PlayCommandProperty);
            set => SetValue(PlayCommandProperty, value);
        }

        public ICommand StopCommand
        {
            get => (ICommand)GetValue(StopCommandProperty);
            set => SetValue(StopCommandProperty, value);
        }

        protected override void OnAttachedTo(MediaElement bindable)
        {
            base.OnAttachedTo(bindable);
            if (PlayCommand != null)
                PlayCommand.CanExecuteChanged += PlayCommand_CanExecuteChanged;
            if (StopCommand != null)
                StopCommand.CanExecuteChanged += StopCommand_CanExecuteChanged;
        }

        protected override void OnDetachingFrom(MediaElement bindable)
        {
            base.OnDetachingFrom(bindable);
            if (PlayCommand != null)
                PlayCommand.CanExecuteChanged -= PlayCommand_CanExecuteChanged;
            if (StopCommand != null)
                StopCommand.CanExecuteChanged -= StopCommand_CanExecuteChanged;
        }

        private void PlayCommand_CanExecuteChanged(object sender, EventArgs e)
        {
            if (PlayCommand?.CanExecute(null) == true)
                AssociatedObject.Play();
        }

        private void StopCommand_CanExecuteChanged(object sender, EventArgs e)
        {
            if (StopCommand?.CanExecute(null) == true)
                AssociatedObject.Stop();
        }
    }
}
  1. 修改ViewModel,移除对MediaElement的直接引用
namespace Rad.io.Client.MAUI.ViewModels
{
    [QueryProperty(nameof(CurrentStation), "CurrentStation")]
    public class NowPlayingViewModel : INotifyPropertyChanged
    {
        private StationInfo currentStation;
        private readonly IRadioBrowserClient radioBrowserClient;

        public StationInfo CurrentStation
        {
            get => currentStation;
            set
            {
                currentStation = value;
                RaisePropertyChanged();
            }
        }

        // 命令仅负责触发逻辑,无需直接操作控件
        public ICommand OnStopPressed => new Command(() => {
            // 可在此添加播放停止后的业务逻辑,比如更新UI状态
        });

        public ICommand OnPlay => new Command(() => {
            // 可在此添加播放开始后的业务逻辑
        });

        public NowPlayingViewModel(IRadioBrowserClient radioBrowserClient)
        {
            this.radioBrowserClient = radioBrowserClient;
        }

        // INotifyPropertyChanged实现
        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
  1. 修改XAML,添加行为并绑定命令
    引入行为命名空间,给MediaElement附加行为,同时修改按钮绑定命令:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewmodel="clr-namespace:Rad.io.Client.MAUI.ViewModels"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:behaviors="clr-namespace:Rad.io.Client.MAUI.Behaviors"
             x:DataType="viewmodel:NowPlayingViewModel"
             x:Class="Rad.io.Client.MAUI.Views.NowPlayingPanel">
    ...
    <toolkit:MediaElement Grid.Column="1"
        ShouldShowPlaybackControls="False"
        BackgroundColor="DarkCyan"
        ShouldAutoPlay="True"
        Source="{Binding CurrentStation.Url}">
        <toolkit:MediaElement.Behaviors>
            <behaviors:MediaElementCommandBehavior 
                PlayCommand="{Binding OnPlay}"
                StopCommand="{Binding OnStopPressed}"/>
        </toolkit:MediaElement.Behaviors>
    </toolkit:MediaElement>
    ...
    <!-- 替换原来的Clicked事件为命令绑定 -->
    <Button Text="Play" Command="{Binding OnPlay}"/>
    <Button Text="Stop" Command="{Binding OnStopPressed}"/>
    ...
</ContentView>

方式二:代码后台转发命令(轻量实现)

如果不想编写自定义行为,可以在View的代码后台直接绑定ViewModel命令的执行逻辑,实现简单快速:

  1. 修改ViewModel,保留空命令(或添加业务逻辑)
public ICommand OnStopPressed => new Command(() => {
    // 可选:添加停止后的业务逻辑
});

public ICommand OnPlay => new Command(() => {
    // 可选:添加播放后的业务逻辑
});
  1. 修改View的代码后台,绑定命令执行逻辑
namespace Rad.io.Client.MAUI.Views;

public partial class NowPlayingPanel : ContentView
{
    private NowPlayingViewModel ViewModel => (NowPlayingViewModel)BindingContext;

    public NowPlayingPanel()
    {
        InitializeComponent();
        // 重新赋值命令,将控件操作逻辑注入
        ViewModel.OnStopPressed = new Command(() => mediaElement.Stop());
        ViewModel.OnPlay = new Command(() => mediaElement.Play());
    }
}
  1. 修改XAML按钮绑定命令
<Button Text="Play" Command="{Binding OnPlay}"/>
<Button Text="Stop" Command="{Binding OnStopPressed}"/>

方式三:使用WeakReferenceMessenger(完全解耦)

借助CommunityToolkit.Mvvm的消息机制,让ViewModel发送指令消息,View接收后执行控件操作,完全分离两者的依赖:

  1. 定义消息类
namespace Rad.io.Client.MAUI.Messages
{
    public record MediaCommandMessage(MediaAction Action);

    public enum MediaAction
    {
        Play,
        Stop,
        Pause
    }
}
  1. 修改ViewModel,发送消息
    确保已安装CommunityToolkit.Mvvm NuGet包:
using CommunityToolkit.Mvvm.Messaging;

public ICommand OnStopPressed => new Command(() => {
    WeakReferenceMessenger.Default.Send(new MediaCommandMessage(MediaAction.Stop));
});

public ICommand OnPlay => new Command(() => {
    WeakReferenceMessenger.Default.Send(new MediaCommandMessage(MediaAction.Play));
});
  1. 修改View,注册消息并执行操作
namespace Rad.io.Client.MAUI.Views;

public partial class NowPlayingPanel : ContentView
{
    public NowPlayingPanel()
    {
        InitializeComponent();
        // 注册消息监听
        WeakReferenceMessenger.Default.Register<MediaCommandMessage>(this, (recipient, message) =>
        {
            switch (message.Action)
            {
                case MediaAction.Play:
                    mediaElement.Play();
                    break;
                case MediaAction.Stop:
                    mediaElement.Stop();
                    break;
            }
        });
    }

    // 页面销毁时注销消息,避免内存泄漏
    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        WeakReferenceMessenger.Default.Unregister<MediaCommandMessage>(this);
    }
}

最后注意事项

  • 移除原来的playStopButton_Clicked事件处理,完全使用命令绑定符合MVVM原则
  • 如果需要处理播放/暂停切换,可以在ViewModel中添加一个TogglePlayPauseCommand,并对应修改行为或消息逻辑
  • 方式一和方式三更适合大型项目,方式二适合小型快速开发场景

内容的提问来源于stack exchange,提问作者Sisimośki

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 23:57:10