我已经为媒体元素设置了一个 BindableProperty,效果很好。但我想从我使用自定义类的页面访问事件。我有各种 bools 和 string 工作以及主要的方法。我只需要让活动正常进行。这是一些示例代码。
MediaControl.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView
x:Class="NerdNewsNavigator2.Controls.MediaControl"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:item="clr-namespace:NerdNewsNavigator2.Controls"
xmlns:page="clr-namespace:NerdNewsNavigator2.View"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:vm="clr-namespace:NerdNewsNavigator2.ViewModel"
Unloaded="ContentView_Unloaded">
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=MovedCommand}" />
<SwipeGestureRecognizer Direction="Up" Swiped="SwipeGestureRecognizer_Swiped" />
<SwipeGestureRecognizer Direction="Down" Swiped="SwipeGestureRecognizer_Swiped" />
</Grid.GestureRecognizers>
<toolkit:MediaElement
x:Name="mediaElement"
ShouldAutoPlay="True"
ShouldKeepScreenOn="True"
ShouldShowPlaybackControls="False" />
<Frame
BackgroundColor="Black"
IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=SetFullScreen}"
IsVisible="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=SetFullScreen}"
Opacity="0.5" />
<Grid IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=SetFullScreen}" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=SetFullScreen}">
<ImageButton
x:Name="btnFullScreen"
Margin="10"
BackgroundColor="Transparent"
Clicked="BtnFullScreen_Clicked"
HeightRequest="40"
HorizontalOptions="End"
Source="whitefs.png"
VerticalOptions="Start"
WidthRequest="40" />
<VerticalStackLayout VerticalOptions="End">
<HorizontalStackLayout HorizontalOptions="Center">
<ImageButton
x:Name="BtnRewind"
Margin="10"
BackgroundColor="Transparent"
BindingContext="{x:Reference mediaElement}"
Clicked="BtnRewind_Clicked"
HeightRequest="40"
HorizontalOptions="Center"
Source="rewind.png"
VerticalOptions="End"
WidthRequest="40" />
<ImageButton
x:Name="BtnPLay"
Margin="10"
BackgroundColor="Transparent"
BindingContext="{x:Reference mediaElement}"
Clicked="BtnPlay_Clicked"
HeightRequest="40"
HorizontalOptions="Center"
Source="pause.png"
VerticalOptions="End"
WidthRequest="40" />
<ImageButton
x:Name="BtnForward"
Margin="10"
BackgroundColor="Transparent"
BindingContext="{x:Reference mediaElement}"
Clicked="BtnForward_Clicked"
HeightRequest="40"
HorizontalOptions="Center"
Source="fastforward.png"
VerticalOptions="End"
WidthRequest="40" />
<ImageButton
x:Name="ImageButtonMute"
Margin="10"
BackgroundColor="Transparent"
Clicked="OnMuteClicked"
HeightRequest="40"
Source="muted.png"
WidthRequest="40">
<ImageButton.Triggers>
<DataTrigger
Binding="{Binding ShouldMute, Source={x:Reference mediaElement}}"
TargetType="ImageButton"
Value="True" />
<DataTrigger
Binding="{Binding ShouldMute, Source={x:Reference mediaElement}}"
TargetType="ImageButton"
Value="False" />
</ImageButton.Triggers>
</ImageButton>
</HorizontalStackLayout>
<HorizontalStackLayout HorizontalOptions="Start" VerticalOptions="End">
<Label
Margin="5"
FontSize="12"
HorizontalOptions="Center"
Text="{Binding Source={RelativeSource AncestorType={x:Type item:MediaControl}}, Path=PlayPosition}"
TextColor="White" />
</HorizontalStackLayout>
<Slider
x:Name="PositionSlider"
Margin="10"
DragCompleted="Slider_DragCompleted"
DragStarted="Slider_DragStarted"
MaximumTrackColor="LightGray"
MinimumTrackColor="Red" />
</VerticalStackLayout>
</Grid>
</Grid>
</ContentView>
显示属性和可绑定属性的缩写代码。 MediaControl.xaml.cs:
using Application = Microsoft.Maui.Controls.Application;
using Platform = Microsoft.Maui.ApplicationModel.Platform;
#if ANDROID
using Views = AndroidX.Core.View;
#endif
#if WINDOWS
using Microsoft.UI;
using Microsoft.UI.Windowing;
using WinRT;
using Microsoft.Maui.Controls;
using CommunityToolkit.Maui.Core.Primitives;
#endif
namespace NerdNewsNavigator2.Controls;
public partial class MediaControl : ContentView
{
#region Properties and Bindable Properties
/// <summary>
/// Initilizes a new instance of the <see cref="Position"/> class
/// </summary>
private Position Pos { get; set; } = new();
public string PlayPosition { get; set; }
public Page CurrentPage { get; set; }
private bool _fullScreen = false;
#if WINDOWS
private static MauiWinUIWindow CurrentWindow { get; set; }
#endif
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Name), typeof(MediaElement), typeof(MediaControl), propertyChanged: (bindable, oldValue, newValue) =>
{
var control = (MediaControl)bindable;
control.mediaElement.ShouldAutoPlay = (bool)newValue;
control.mediaElement.ShouldKeepScreenOn = (bool)newValue;
control.mediaElement.Source = newValue as MediaSource;
control.mediaElement.ShouldShowPlaybackControls = (bool)newValue;
control.mediaElement.StateChanged += (System.EventHandler<MediaStateChangedEventArgs>)newValue;
control.mediaElement.MediaOpened += (System.EventHandler)newValue;
});
public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(MediaSource), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.Source = newValue as MediaSource;
});
public TimeSpan Position
{
get => (TimeSpan)GetValue(TitleProperty);
}
public MediaSource Source
{
get => GetValue(SourceProperty) as MediaSource;
set => SetValue(SourceProperty, value);
}
public MediaElement Name
{
get => GetValue(TitleProperty) as MediaElement;
set => SetValue(TitleProperty, value);
}
public bool ShouldShowPlaybackControls
{
get => (bool)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public bool ShouldAutoPlay
{
get => (bool)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public bool ShouldKeepScreenOn
{
get => (bool)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
#endregion
public MediaControl()
{
InitializeComponent();
PlayPosition = string.Empty;
mediaElement.PropertyChanged += MediaElement_PropertyChanged;
mediaElement.PositionChanged += ChangedPosition;
CurrentPage = Shell.Current.CurrentPage;
}
public void SeekTo(TimeSpan position)
{
mediaElement.SeekTo(position);
}
public void Stop()
{
mediaElement.Stop();
}
#region Events
#nullable enable
private void MediaElement_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == MediaElement.DurationProperty.PropertyName)
{
PositionSlider.Maximum = mediaElement.Duration.TotalSeconds;
}
}
private void OnPositionChanged(object? sender, MediaPositionChangedEventArgs e)
{
PositionSlider.Value = e.Position.TotalSeconds;
}
private void SwipeGestureRecognizer_Swiped(object sender, SwipedEventArgs e)
{
#if WINDOWS
CurrentWindow = BaseViewModel.CurrentWindow;
#endif
if (e.Direction == SwipeDirection.Up)
{
SetFullScreen();
}
if (e.Direction == SwipeDirection.Down)
{
RestoreScreen();
}
}
private void Slider_DragCompleted(object? sender, EventArgs e)
{
ArgumentNullException.ThrowIfNull(sender);
var newValue = ((Slider)sender).Value;
mediaElement.SeekTo(TimeSpan.FromSeconds(newValue));
mediaElement.Play();
}
#nullable disable
private void Slider_DragStarted(object sender, EventArgs e)
{
mediaElement.Pause();
}
private void ChangedPosition(object sender, EventArgs e)
{
var playDuration = BaseViewModel.TimeConverter(mediaElement.Duration);
var position = BaseViewModel.TimeConverter(mediaElement.Position);
PlayPosition = $"{position}/{playDuration}";
OnPropertyChanged(nameof(PlayPosition));
}
#endregion
#region Buttons
private void BtnRewind_Clicked(object sender, EventArgs e)
{
var time = mediaElement.Position - TimeSpan.FromSeconds(15);
mediaElement.Pause();
mediaElement.SeekTo(time);
mediaElement.Play();
}
private void BtnForward_Clicked(object sender, EventArgs e)
{
var time = mediaElement.Position + TimeSpan.FromSeconds(15);
mediaElement.Pause();
mediaElement.SeekTo(time);
mediaElement.Play();
}
这是我想从内容页面调用的事件触发方法的示例 TabletPlayPodcastPage.xaml:
/// <summary>
/// Manages IOS seeking for <see cref="mediaElement"/> with <see cref="Pos"/> at start of playback.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public async void SeekIOS(object? sender, MediaStateChangedEventArgs e)
{
if (sender == null)
{
return;
}
Pos.Title = Preferences.Default.Get("New_Url", string.Empty);
Pos.SavedPosition = TimeSpan.Zero;
var positionList = await App.PositionData.GetAllPositions();
foreach (var item in positionList)
{
if (Pos.Title == item.Title)
{
Pos.SavedPosition = item.SavedPosition;
Debug.WriteLine($"Retrieved Saved position from database is: {item.Title} - {item.SavedPosition}");
}
}
if (e.NewState == MediaElementState.Playing)
{
mediaElement.SeekTo(Pos.SavedPosition);
mediaElement.ShouldKeepScreenOn = true;
Debug.WriteLine("Media playback started. ShouldKeepScreenOn is set to true.");
}
}
这里是我想使用的示例调用函数:
/// <summary>
/// A method that starts <see cref="MediaElement"/> event for <see cref="TabletPlayPodcastPage"/>
/// </summary>
public void Load()
{
#if WINDOWS || ANDROID
mediaElement.MediaOpened += Seek;
#endif
#if IOS || MACCATALYST
mediaElement.StateChanged += SeekIOS;
#endif
}
尝试调用 mediaElement.StateChanged += SeekIOS 给出错误 CS1061 Error Message Link
如果我使用 Visual studio 快速操作,我会得到这个消除错误但不允许我访问事件的功能。
public Action<object, EventArgs> MediaOpened { get; internal set; }
我不再收到错误消息,但事件也没有触发。我正在寻找一种从另一个使用 MediaControl 的页面调用事件的方法。我创建所有这些是为了减少代码以及我想要工作的所有功能和行为,这是关于减少代码并使其更整洁。
好吧,结果我自己弄明白了。我查看了媒体元素代码本身并通过反复试验实现了这个解决方案
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Name), typeof(MediaElement), typeof(MediaControl), propertyChanged: (bindable, oldValue, newValue) =>
{
var control = (MediaControl)bindable;
control.mediaElement.ShouldAutoPlay = (bool)newValue;
control.mediaElement.ShouldKeepScreenOn = (bool)newValue;
control.mediaElement.Source = newValue as MediaSource;
control.mediaElement.ShouldShowPlaybackControls = (bool)newValue;
control.mediaElement.PositionChanged += (EventHandler<MediaPositionChangedEventArgs>)newValue;
control.mediaElement.StateChanged += (EventHandler<MediaStateChangedEventArgs>)newValue;
control.mediaElement.MediaOpened += (EventHandler)newValue;
});
public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(MediaSource), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.Source = newValue as MediaSource;
});
public static readonly BindableProperty StateChangedProperty = BindableProperty.Create(nameof(StateChanged), typeof(EventHandler<MediaStateChangedEventArgs>), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.StateChanged += (EventHandler<MediaStateChangedEventArgs>)newValue;
});
public static readonly BindableProperty MediaOpenedProperty = BindableProperty.Create(nameof(MediaOpened), typeof(EventHandler), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.MediaOpened += (EventHandler)newValue;
});
public static readonly BindableProperty ShouldKeepScreenOnProperty = BindableProperty.Create(nameof(ShouldKeepScreenOn), typeof(bool), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.ShouldKeepScreenOn = (bool)newValue;
});
public static readonly BindableProperty PositionProperty = BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(MediaElement), TimeSpan.Zero);
public static readonly BindableProperty ShouldAutoPlayProperty = BindableProperty.Create(nameof(ShouldAutoPlay), typeof(bool), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.ShouldAutoPlay = (bool)newValue;
});
public static readonly BindableProperty ShouldShowPlaybackControlsProperty = BindableProperty.Create(nameof(ShouldShowPlaybackControls), typeof(bool), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.ShouldShowPlaybackControls = (bool)newValue;
});
public static readonly BindableProperty PositionChangedProperty = BindableProperty.Create(nameof(PositionChanged), typeof(EventHandler<MediaPositionChangedEventArgs>), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.PositionChanged += (EventHandler<MediaPositionChangedEventArgs>)newValue;
});
public EventHandler MediaOpened
{
get => GetValue(MediaOpenedProperty) as EventHandler;
set => SetValue(MediaOpenedProperty, value);
}
public EventHandler<MediaStateChangedEventArgs> StateChanged
{
get => GetValue(StateChangedProperty) as EventHandler<MediaStateChangedEventArgs>;
set => SetValue(StateChangedProperty, value);
}
public EventHandler<MediaPositionChangedEventArgs> PositionChanged
{
get => GetValue(PositionChangedProperty) as EventHandler<MediaPositionChangedEventArgs>;
set => SetValue(PositionChangedProperty, value);
}
public MediaSource Source
{
get => GetValue(SourceProperty) as MediaSource;
set => SetValue(SourceProperty, value);
}
public TimeSpan Position => mediaElement.Position;
public MediaElement Name
{
get => GetValue(TitleProperty) as MediaElement;
set => SetValue(TitleProperty, value);
}
public bool ShouldShowPlaybackControls
{
get => (bool)GetValue(ShouldShowPlaybackControlsProperty);
set => SetValue(ShouldShowPlaybackControlsProperty, value);
}
public bool ShouldAutoPlay
{
get => (bool)GetValue(ShouldAutoPlayProperty);
set => SetValue(ShouldAutoPlayProperty, value);
}
public bool ShouldKeepScreenOn
{
get => (bool)GetValue(ShouldKeepScreenOnProperty);
set => SetValue(ShouldKeepScreenOnProperty, value);
}
这让我可以使用我需要的媒体元素的所有类型的属性。这不是他们的全部。但是我能够将所有页面特定代码移回实际页面,并且我的 ContentView 类不再与其他页面的代码混在一起。