如何在.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:
- 创建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(); } } }
- 修改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)); } } }
- 修改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命令的执行逻辑,实现简单快速:
- 修改ViewModel,保留空命令(或添加业务逻辑)
public ICommand OnStopPressed => new Command(() => { // 可选:添加停止后的业务逻辑 }); public ICommand OnPlay => new Command(() => { // 可选:添加播放后的业务逻辑 });
- 修改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()); } }
- 修改XAML按钮绑定命令
<Button Text="Play" Command="{Binding OnPlay}"/> <Button Text="Stop" Command="{Binding OnStopPressed}"/>
方式三:使用WeakReferenceMessenger(完全解耦)
借助CommunityToolkit.Mvvm的消息机制,让ViewModel发送指令消息,View接收后执行控件操作,完全分离两者的依赖:
- 定义消息类
namespace Rad.io.Client.MAUI.Messages { public record MediaCommandMessage(MediaAction Action); public enum MediaAction { Play, Stop, Pause } }
- 修改ViewModel,发送消息
确保已安装CommunityToolkit.MvvmNuGet包:
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)); });
- 修改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
相关产品推荐
相关产品推荐

