MediaElement.Position 與 NaturalDuration 不是依賴屬性,無法直接繫結。本文示範兩種實作播放進度條的方式:傳統 DispatcherTimer 輪詢,與封裝依賴屬性的優雅做法。
<MediaElement Name="player" Source="demo.wmv" Loaded="player_Loaded" MediaOpened="player_MediaOpened"/>
<Slider Name="slider" ValueChanged="slider_ValueChanged"/>
<TextBlock Name="tb1"/><TextBlock Name="tb2"/>
private void player_Loaded(object sender, RoutedEventArgs e)
{
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (s, e) => {
var ts = player.Position;
tb1.Text = $"{ts:hh\\:mm\\:ss}";
slider.Value = ts.TotalMilliseconds;
};
timer.Start();
}
private void player_MediaOpened(object sender, RoutedEventArgs e)
{
var ts = player.NaturalDuration.TimeSpan;
tb2.Text = $"{ts:hh\\:mm\\:ss}";
slider.Maximum = ts.TotalMilliseconds;
}
private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
player.Position = TimeSpan.FromMilliseconds(e.NewValue);
}
public class MediaElementExt : MediaElement, INotifyPropertyChanged
{
private DispatcherTimer timer;
private DateTime _lastChanged = DateTime.Now;
public double CurrentTime
{
get => Position.TotalMilliseconds;
set
{
if ((DateTime.Now - _lastChanged).TotalMilliseconds > 50) // 節流
{
Position = TimeSpan.FromMilliseconds(value);
_lastChanged = DateTime.Now;
}
}
}
public double DurationTime => NaturalDuration.HasTimeSpan ? NaturalDuration.TimeSpan.TotalMilliseconds : double.NaN;
public MediaElementExt()
{
timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (s, e) => RaisePropertyChanged(nameof(CurrentTime));
MediaOpened += (s, e) => { RaisePropertyChanged(nameof(DurationTime)); timer.Start(); };
MediaEnded += (s, e) => timer.Stop();
MediaFailed += (s, e) => timer.Stop();
}
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
}
<local:MediaElementExt x:Name="player" Source="demo.wmv"/>
<Slider Value="{Binding CurrentTime, ElementName=player, Mode=TwoWay}"
Maximum="{Binding DurationTime, ElementName=player, Mode=OneWay}"/>
<TextBlock Text="{Binding Value, ElementName=slider, Converter={StaticResource TimeSpanConverter}}"/>
<TextBlock Text="{Binding DurationTime, ElementName=player, Converter={StaticResource TimeSpanConverter}}"/>
public class TimeSpanConverter : IValueConverter
{
public object Convert(object v, Type t, object p, CultureInfo c)
=> TimeSpan.FromMilliseconds((double)v).ToString(@"hh\:mm\:ss");
public object ConvertBack(...) => throw new NotImplementedException();
}
| 面向 | Timer 輪詢 | 依賴屬性封裝 |
|---|---|---|
| 程式碼量 | 較少 | 較多(需建立類別) |
| MVVM 友善 | 否 | 是,支援雙向繫結 |
| 效能 | 固定間隔輪詢 | 同樣需 Timer,但可加入節流 |
| 維護性 | 分散在 Code-behind | 封裝於控制項,可重用 |
建議專案採用方法二,長期維護與擴充性較佳。