把动画播放控制改成媒体控制按钮,增加了步进、快放,支持反向播放

This commit is contained in:
tian 2025-09-13 13:34:22 +08:00
parent 9024eb2672
commit 89c98f1556
9 changed files with 1208 additions and 140 deletions

View File

@ -133,7 +133,8 @@
"Read(//c/Users/Tellme/apps/OpenSource/AStar-master/Roy-T.AStar/**)",
"Read(//c/Users/Tellme/apps/OpenSource/AStar-master/**)",
"Bash(\"./compile.bat\")",
"Bash(xcopy:*)"
"Bash(xcopy:*)",
"Read(//c/ProgramData/Autodesk/Navisworks Manage 2026/NavisworksTransport/logs/**)"
],
"deny": [],
"additionalDirectories": [

View File

@ -201,6 +201,9 @@
<Compile Include="src\UI\WPF\Views\PathAnalysisDialog.xaml.cs">
<DependentUpon>PathAnalysisDialog.xaml</DependentUpon>
</Compile>
<Compile Include="src\UI\WPF\Views\MediaControlBar.xaml.cs">
<DependentUpon>MediaControlBar.xaml</DependentUpon>
</Compile>
<!-- UI - WPF ViewModels -->
<Compile Include="src\UI\WPF\ViewModels\ViewModelBase.cs" />
@ -305,11 +308,19 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="src\UI\WPF\Views\MediaControlBar.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<!-- Shared Resource Dictionary -->
<Page Include="src\UI\WPF\Resources\NavisworksStyles.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="src\UI\WPF\Resources\MediaControlIcons.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>

View File

@ -365,4 +365,17 @@
│ 3. 改造播放控制为步进式 │
│ 4. 实现帧导航方法 │
│ 5. 更新UI添加步进控制 │
│ 6. 测试各种播放模式
│ 6. 测试各种播放模式
🎮 媒体控制功能:
- 快退10帧 (Shift+←) - 快速后退功能
- 上一帧 (←) - 精确单帧后退
- 反向播放 (R键) - 连续反向播放动画
- 暂停 (Space) - 居中暂停控制
- 正向播放 - 标准前进播放
- 下一帧 (→) - 精确单帧前进
- 快进10帧 (Shift+→) - 快速前进功能
- 停止 (Esc) - 停止并重置动画

View File

@ -66,6 +66,11 @@ namespace NavisworksTransport.Core.Animation
private double _movementSpeed = 1.0; // 运动速度默认1米/秒)
private double _detectionGap = 0.05; // 检测间隙默认0.05米)
// === 双向播放和步进控制 ===
private int _playbackDirection = 1; // 播放方向1=正向,-1=反向
private double _playbackSpeed = 1.0; // 播放速度倍率
private bool _isStepMode = false; // 是否处于步进模式
// === 性能监控 ===
private int _fpsFrameCount = 0;
private DateTime _fpsCounterStart = DateTime.Now;
@ -316,7 +321,7 @@ namespace NavisworksTransport.Core.Animation
LogManager.Info($"潜在碰撞对象数: {potentialColliders.Count}");
// 预计算每一帧
for (int i = 0; i <= totalFrames; i++)
for (int i = 0; i < totalFrames; i++)
{
double progress = (double)i / totalFrames;
var framePosition = InterpolatePosition(progress);
@ -640,6 +645,14 @@ namespace NavisworksTransport.Core.Animation
SetState(AnimationState.Stopped);
_pausedProgress = 0.0; // 重置暂停进度
_currentFrameIndex = 0; // 重置帧索引
// 将物体移回起点位置
if (_pathPoints != null && _pathPoints.Count > 0 && _animatedObject != null)
{
UpdateObjectPosition(_pathPoints[0]);
LogManager.Info($"物体已移回起点位置: ({_pathPoints[0].X:F2},{_pathPoints[0].Y:F2},{_pathPoints[0].Z:F2})");
}
LogManager.Info("动画已停止");
// 动画停止时不创建碰撞测试汇总,由动画完成事件统一处理
@ -1315,6 +1328,245 @@ namespace NavisworksTransport.Core.Animation
/// </summary>
public TimeLinerIntegrationManager TimeLinerManager => _timeLinerManager;
#region
/// <summary>
/// 获取当前帧索引
/// </summary>
public int CurrentFrame => _currentFrameIndex;
/// <summary>
/// 获取总帧数
/// </summary>
public int TotalFrames => _animationFrames?.Count ?? 0;
/// <summary>
/// 获取播放方向1=正向,-1=反向)
/// </summary>
public int PlaybackDirection => _playbackDirection;
/// <summary>
/// 获取播放速度倍率
/// </summary>
public double PlaybackSpeed => _playbackSpeed;
/// <summary>
/// 获取是否正在反向播放
/// </summary>
public bool IsPlayingReverse => _playbackDirection == -1;
/// <summary>
/// 设置播放方向
/// </summary>
/// <param name="reverse">true=反向播放false=正向播放</param>
public void SetPlaybackDirection(bool reverse)
{
_playbackDirection = reverse ? -1 : 1;
LogManager.Info($"播放方向设置为: {(reverse ? "" : "")}");
}
/// <summary>
/// 设置播放速度
/// </summary>
/// <param name="speed">播放速度倍率,负值表示反向</param>
public void SetPlaybackSpeed(double speed)
{
_playbackSpeed = Math.Abs(speed);
_playbackDirection = speed >= 0 ? 1 : -1;
// 更新帧间隔
_frameInterval = 1000.0 / (_animationFrameRate * _playbackSpeed);
LogManager.Info($"播放速度设置为: {speed}x方向: {(speed >= 0 ? "" : "")}");
}
/// <summary>
/// 跳转到指定帧
/// </summary>
/// <param name="frameIndex">目标帧索引</param>
public void SeekToFrame(int frameIndex)
{
try
{
if (_animationFrames == null || _animationFrames.Count == 0)
{
LogManager.Warning("无动画帧数据,无法跳转");
return;
}
// 限制帧索引范围
frameIndex = Math.Max(0, Math.Min(frameIndex, _animationFrames.Count - 1));
if (frameIndex == _currentFrameIndex)
{
LogManager.Debug($"已处于目标帧 {frameIndex},无需跳转");
return;
}
_currentFrameIndex = frameIndex;
// 更新对象位置
var frameData = _animationFrames[_currentFrameIndex];
UpdateObjectPosition(frameData.Position);
// 更新碰撞高亮
UpdateCollisionHighlightFromFrame();
// 计算并触发进度事件
double progress = TotalFrames > 1 ? (double)_currentFrameIndex / (TotalFrames - 1) * 100 : 0;
ProgressChanged?.Invoke(this, progress);
LogManager.Info($"跳转到帧 {frameIndex}/{TotalFrames - 1} (进度: {progress:F1}%)");
}
catch (Exception ex)
{
LogManager.Error($"跳转到帧失败: {ex.Message}");
}
}
/// <summary>
/// 跳转到指定进度
/// </summary>
/// <param name="progress">进度值0.0-1.0</param>
public void SeekToProgress(double progress)
{
try
{
progress = Math.Max(0.0, Math.Min(1.0, progress));
int targetFrame = TotalFrames > 1 ? (int)(progress * (TotalFrames - 1)) : 0;
SeekToFrame(targetFrame);
}
catch (Exception ex)
{
LogManager.Error($"跳转到进度失败: {ex.Message}");
}
}
/// <summary>
/// 单帧前进
/// </summary>
public void StepForward()
{
StepForward(1);
}
/// <summary>
/// 前进指定帧数
/// </summary>
/// <param name="frames">帧数</param>
public void StepForward(int frames)
{
try
{
if (frames <= 0) return;
int targetFrame = _currentFrameIndex + frames;
targetFrame = Math.Min(targetFrame, TotalFrames - 1);
SeekToFrame(targetFrame);
LogManager.Debug($"前进 {frames} 帧,当前帧: {_currentFrameIndex}");
}
catch (Exception ex)
{
LogManager.Error($"前进步进失败: {ex.Message}");
}
}
/// <summary>
/// 单帧后退
/// </summary>
public void StepBackward()
{
StepBackward(1);
}
/// <summary>
/// 后退指定帧数
/// </summary>
/// <param name="frames">帧数</param>
public void StepBackward(int frames)
{
try
{
if (frames <= 0) return;
int targetFrame = _currentFrameIndex - frames;
targetFrame = Math.Max(targetFrame, 0);
SeekToFrame(targetFrame);
LogManager.Debug($"后退 {frames} 帧,当前帧: {_currentFrameIndex}");
}
catch (Exception ex)
{
LogManager.Error($"后退步进失败: {ex.Message}");
}
}
/// <summary>
/// 快进10帧
/// </summary>
public void FastForward()
{
StepForward(10);
}
/// <summary>
/// 快退10帧
/// </summary>
public void FastBackward()
{
StepBackward(10);
}
/// <summary>
/// 开始正向播放
/// </summary>
public void PlayForward()
{
try
{
SetPlaybackDirection(false);
if (_currentState == AnimationState.Paused)
{
ResumeAnimation(); // 从暂停位置继续
}
else if (_currentState != AnimationState.Playing)
{
StartAnimation(); // 只有非播放和非暂停状态才从头开始
}
LogManager.Info("开始正向播放");
}
catch (Exception ex)
{
LogManager.Error($"正向播放失败: {ex.Message}");
}
}
/// <summary>
/// 开始反向播放
/// </summary>
public void PlayReverse()
{
try
{
SetPlaybackDirection(true);
if (_currentState == AnimationState.Paused)
{
ResumeAnimation(); // 从暂停位置继续
}
else if (_currentState != AnimationState.Playing)
{
StartAnimation(); // 只有非播放和非暂停状态才从头开始
}
LogManager.Info("开始反向播放");
}
catch (Exception ex)
{
LogManager.Error($"反向播放失败: {ex.Message}");
}
}
#endregion
/// <summary>
/// 清理对象引用
/// </summary>
@ -1713,18 +1965,38 @@ namespace NavisworksTransport.Core.Animation
return; // 还没到下一帧时间跳过本次Idle
}
// 计算动画进度
var totalElapsed = (now - _animationStartTime).TotalSeconds;
var progress = Math.Min(totalElapsed / _animationDuration, 1.0);
// 根据进度计算当前帧索引
int targetFrameIndex = (int)(progress * (_animationFrames.Count - 1));
targetFrameIndex = Math.Min(targetFrameIndex, _animationFrames.Count - 1);
// 计算下一帧索引(考虑播放方向和速度)
int nextFrameIndex = _currentFrameIndex;
// 如果帧索引发生变化,更新位置和碰撞高亮
if (targetFrameIndex != _currentFrameIndex || _currentFrameIndex == 0)
if (_playbackDirection == 1) // 正向播放
{
_currentFrameIndex = targetFrameIndex;
nextFrameIndex = _currentFrameIndex + 1;
if (nextFrameIndex >= _animationFrames.Count)
{
// 正向播放到达终点
nextFrameIndex = _animationFrames.Count - 1;
NavisApplication.Idle -= OnApplicationIdle;
FinishAnimation();
return;
}
}
else // 反向播放
{
nextFrameIndex = _currentFrameIndex - 1;
if (nextFrameIndex < 0)
{
// 反向播放到达起点
nextFrameIndex = 0;
NavisApplication.Idle -= OnApplicationIdle;
FinishAnimation();
return;
}
}
// 更新帧索引和位置
if (nextFrameIndex != _currentFrameIndex)
{
_currentFrameIndex = nextFrameIndex;
// 使用预计算的帧位置
if (_currentFrameIndex < _animationFrames.Count)
@ -1735,34 +2007,29 @@ namespace NavisworksTransport.Core.Animation
// 更新碰撞高亮(基于预计算结果)
UpdateCollisionHighlightFromFrame();
LogManager.Debug($"帧 {_currentFrameIndex}/{_animationFrames.Count-1}, 进度: {progress*100:F1}%, 位置: ({frameData.Position.X:F2},{frameData.Position.Y:F2},{frameData.Position.Z:F2})");
// 计算当前进度(基于帧索引)
double progress = _animationFrames.Count > 1 ? (double)_currentFrameIndex / (_animationFrames.Count - 1) : 0;
LogManager.Debug($"帧 {_currentFrameIndex}/{_animationFrames.Count-1}, 进度: {progress*100:F1}%, 方向: {(_playbackDirection == 1 ? "" : "")}, 位置: ({frameData.Position.X:F2},{frameData.Position.Y:F2},{frameData.Position.Z:F2})");
// 触发进度事件
var progressPercent = progress * 100;
ProgressChanged?.Invoke(this, progressPercent);
// 同步进度到 TimeLiner如果可用
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState);
}
}
}
// 触发进度事件
var progressPercent = progress * 100;
ProgressChanged?.Invoke(this, progressPercent);
// 同步进度到 TimeLiner如果可用
if (_timeLinerManager != null && !string.IsNullOrEmpty(_currentTaskId))
{
_timeLinerManager.UpdateTaskProgress(_currentTaskId, progress, _currentState);
}
// 记录帧时间
_lastFrameTime = now;
_animationFrameCount++;
// 性能监控
UpdateFPSCounter();
// 检查动画完成
if (progress >= 1.0)
{
// 先注销事件,避免重复调用
NavisApplication.Idle -= OnApplicationIdle;
FinishAnimation();
}
}
catch (Exception ex)
{

View File

@ -0,0 +1,240 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- 媒体控制图标定义 -->
<!-- 快退10帧图标 << -->
<Geometry x:Key="FastBackwardIcon">
M11,6L2,12L11,18V6M20,6L11,12L20,18V6Z
</Geometry>
<!-- 上一帧图标 <| -->
<Geometry x:Key="StepBackwardIcon">
M6,6V18H8V6H6M9.5,12L18,6V18L9.5,12Z
</Geometry>
<!-- 反向播放图标 ◀ -->
<Geometry x:Key="ReversePlayIcon">
M8,6V18L19,12L8,6Z
</Geometry>
<!-- 暂停图标 || -->
<Geometry x:Key="PauseIcon">
M14,18H18V6H14M6,18H10V6H6V18Z
</Geometry>
<!-- 正向播放图标 ▶ -->
<Geometry x:Key="PlayIcon">
M8,6V18L19,12L8,6Z
</Geometry>
<!-- 下一帧图标 |> -->
<Geometry x:Key="StepForwardIcon">
M16,18H18V6H16M6,18L14.5,12L6,6V18Z
</Geometry>
<!-- 快进10帧图标 >> -->
<Geometry x:Key="FastForwardIcon">
M13,6V18L22,12M4,18L12.5,12L4,6V18Z
</Geometry>
<!-- 停止图标 ■ -->
<Geometry x:Key="StopIcon">
M18,18H6V6H18V18Z
</Geometry>
<!-- 媒体控制按钮基础样式 -->
<Style x:Key="MediaControlButtonStyle" TargetType="Button">
<Setter Property="Width" Value="32"/>
<Setter Property="Height" Value="32"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="Background" Value="#FF404040"/>
<Setter Property="BorderBrush" Value="#FF606060"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="3">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#FF5A5A5A"/>
<Setter TargetName="border" Property="BorderBrush" Value="#FF4472C4"/>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="1.05" ScaleY="1.05"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="#FF2B579A"/>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="border" Property="Background" Value="#FF2A2A2A"/>
<Setter TargetName="border" Property="BorderBrush" Value="#FF3A3A3A"/>
<Setter Property="Foreground" Value="#FF666666"/>
<Setter Property="Opacity" Value="0.6"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 停止按钮样式(红色主题) -->
<Style x:Key="StopButtonStyle" TargetType="Button" BasedOn="{StaticResource MediaControlButtonStyle}">
<Setter Property="Background" Value="#FF8B0000"/>
<Setter Property="BorderBrush" Value="#FFAA0000"/>
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="3">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#FFAA0000"/>
<Setter TargetName="border" Property="BorderBrush" Value="#FFCC0000"/>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="1.05" ScaleY="1.05"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="#FF660000"/>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="border" Property="Background" Value="#FF2A2A2A"/>
<Setter TargetName="border" Property="BorderBrush" Value="#FF3A3A3A"/>
<Setter Property="Foreground" Value="#FF666666"/>
<Setter Property="Opacity" Value="0.6"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 播放状态指示样式 -->
<Style x:Key="ActivePlayButtonStyle" TargetType="Button" BasedOn="{StaticResource MediaControlButtonStyle}">
<Setter Property="Background" Value="#FF4472C4"/>
<Setter Property="BorderBrush" Value="#FF5A82D4"/>
</Style>
<!-- 滑块按钮样式必须在TimelineSliderStyle之前定义 -->
<Style x:Key="SliderButtonStyle" TargetType="RepeatButton">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RepeatButton">
<Border BorderThickness="1"
BorderBrush="#FF606060"
Background="#FF404040"
Height="6"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 滑块拇指样式必须在TimelineSliderStyle之前定义 -->
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Height" Value="14"/>
<Setter Property="Width" Value="14"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Ellipse x:Name="Ellipse"
Fill="#FF4472C4"
Stroke="#FF2B579A"
StrokeThickness="1"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Ellipse" Property="Fill" Value="#FF5A82D4"/>
</Trigger>
<Trigger Property="IsDragging" Value="True">
<Setter TargetName="Ellipse" Property="Fill" Value="#FF2B579A"/>
<Setter TargetName="Ellipse" Property="StrokeThickness" Value="2"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 时间轴滑块样式 -->
<Style x:Key="TimelineSliderStyle" TargetType="Slider">
<Setter Property="Height" Value="20"/>
<Setter Property="Background" Value="#FF404040"/>
<Setter Property="Foreground" Value="#FF4472C4"/>
<Setter Property="BorderBrush" Value="#FF606060"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Slider">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="{TemplateBinding MinHeight}"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Track Grid.Row="1" x:Name="PART_Track">
<Track.DecreaseRepeatButton>
<RepeatButton Style="{StaticResource SliderButtonStyle}"
Command="Slider.DecreaseLarge"/>
</Track.DecreaseRepeatButton>
<Track.Thumb>
<Thumb Style="{StaticResource SliderThumbStyle}"/>
</Track.Thumb>
<Track.IncreaseRepeatButton>
<RepeatButton Style="{StaticResource SliderButtonStyle}"
Command="Slider.IncreaseLarge"/>
</Track.IncreaseRepeatButton>
</Track>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 时间显示文本样式 -->
<Style x:Key="TimeDisplayStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Consolas, Courier New"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Margin" Value="8,0"/>
</Style>
</ResourceDictionary>

View File

@ -77,12 +77,9 @@ namespace NavisworksTransport.UI.WPF.ViewModels
private ObservableCollection<int> _availableFrameRates;
private int _selectedFrameRate = 30;
private double _animationDuration = 10.0;
private double _currentAnimationTime = 0.0;
private bool _canStartAnimation = true;
private bool _canPauseAnimation = false;
private bool _canStopAnimation = false;
private double _animationProgress = 0.0;
private string _startAnimationButtonText = "开始动画";
// 碰撞检测相关字段
private bool _hasCollisionResults = false;
@ -142,15 +139,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
/// <summary>
/// 当前动画时间
/// </summary>
public double CurrentAnimationTime
{
get => _currentAnimationTime;
set => SetProperty(ref _currentAnimationTime, value);
}
/// <summary>
/// 是否可以开始动画
/// </summary>
@ -179,25 +167,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
/// <summary>
/// 动画进度
/// </summary>
public double AnimationProgress
{
get => _animationProgress;
set => SetProperty(ref _animationProgress, value);
}
/// <summary>
/// 开始动画按钮文本
/// </summary>
public string StartAnimationButtonText
{
get => _startAnimationButtonText;
set => SetProperty(ref _startAnimationButtonText, value);
}
/// <summary>
/// 是否有碰撞检测结果
/// </summary>
@ -359,11 +328,80 @@ namespace NavisworksTransport.UI.WPF.ViewModels
set => SetProperty(ref _canGenerateAnimation, value);
}
#region
/// <summary>
/// 时间轴位置0-总帧数)
/// </summary>
public double TimelinePosition
{
get => _pathAnimationManager?.CurrentFrame ?? 0;
set
{
if (_pathAnimationManager != null)
{
_pathAnimationManager.SeekToFrame((int)Math.Round(value));
}
OnPropertyChanged();
}
}
/// <summary>
/// 时间轴持续时间(总帧数)
/// </summary>
public double TimelineDuration => _pathAnimationManager?.TotalFrames - 1 ?? 0;
/// <summary>
/// 当前时间显示(格式化)
/// </summary>
public string CurrentTimeDisplay
{
get
{
if (_pathAnimationManager == null) return "00:00";
var currentSeconds = (_pathAnimationManager.CurrentFrame / (double)(_pathAnimationManager.TotalFrames - 1)) * _animationDuration;
return FormatTime(currentSeconds);
}
}
/// <summary>
/// 总时长显示(格式化)
/// </summary>
public string TotalTimeDisplay => FormatTime(_animationDuration);
/// <summary>
/// 帧信息显示
/// </summary>
public string FrameInfoDisplay
{
get
{
if (_pathAnimationManager == null) return "0/0";
return $"{_pathAnimationManager.CurrentFrame + 1}/{_pathAnimationManager.TotalFrames}";
}
}
/// <summary>
/// 是否正在正向播放
/// </summary>
public bool IsPlayingForward => _pathAnimationManager?.PlaybackDirection == 1 &&
_pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing;
/// <summary>
/// 是否正在反向播放
/// </summary>
public bool IsPlayingReverse => _pathAnimationManager?.PlaybackDirection == -1 &&
_pathAnimationManager?.CurrentState == NavisworksTransport.Core.Animation.AnimationState.Playing;
#endregion
#endregion
#region
// 原有命令
public ICommand StartAnimationCommand { get; private set; }
public ICommand PauseAnimationCommand { get; private set; }
public ICommand StopAnimationCommand { get; private set; }
@ -372,6 +410,16 @@ namespace NavisworksTransport.UI.WPF.ViewModels
public ICommand ClearAnimatedObjectCommand { get; private set; }
public ICommand GenerateAnimationCommand { get; private set; }
// 媒体控制命令
public ICommand PlayForwardCommand { get; private set; }
public ICommand PlayReverseCommand { get; private set; }
public ICommand StepForwardCommand { get; private set; }
public ICommand StepBackwardCommand { get; private set; }
public ICommand FastForwardCommand { get; private set; }
public ICommand FastBackwardCommand { get; private set; }
public ICommand SeekToStartCommand { get; private set; }
public ICommand SeekToEndCommand { get; private set; }
#endregion
#region
@ -484,13 +532,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 设置默认动画参数
AnimationDuration = 10.0;
CurrentAnimationTime = 0.0;
// 设置初始状态 - 修改: 默认状态应该是未激活,等待有效动画
CanPauseAnimation = false;
CanStopAnimation = false;
StartAnimationButtonText = "开始动画";
AnimationProgress = 0;
// 初始化碰撞检测状态
HasCollisionResults = false;
@ -517,6 +562,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
/// </summary>
private void InitializeCommands()
{
// 原有命令
StartAnimationCommand = new RelayCommand(async () => await ExecuteStartAnimationAsync(), () => CanStartAnimation);
PauseAnimationCommand = new RelayCommand(async () => await ExecutePauseAnimationAsync(), () => CanPauseAnimation);
StopAnimationCommand = new RelayCommand(async () => await ExecuteStopAnimationAsync(), () => CanStopAnimation);
@ -525,6 +571,16 @@ namespace NavisworksTransport.UI.WPF.ViewModels
ClearAnimatedObjectCommand = new RelayCommand(ExecuteClearAnimatedObject, () => HasSelectedAnimatedObject);
GenerateAnimationCommand = new RelayCommand(ExecuteGenerateAnimation, () => CanGenerateAnimation);
// 媒体控制命令
PlayForwardCommand = new RelayCommand(ExecutePlayForward, CanExecuteMediaCommands);
PlayReverseCommand = new RelayCommand(ExecutePlayReverse, CanExecuteMediaCommands);
StepForwardCommand = new RelayCommand(ExecuteStepForward, CanExecuteMediaCommands);
StepBackwardCommand = new RelayCommand(ExecuteStepBackward, CanExecuteMediaCommands);
FastForwardCommand = new RelayCommand(ExecuteFastForward, CanExecuteMediaCommands);
FastBackwardCommand = new RelayCommand(ExecuteFastBackward, CanExecuteMediaCommands);
SeekToStartCommand = new RelayCommand(ExecuteSeekToStart, CanExecuteMediaCommands);
SeekToEndCommand = new RelayCommand(ExecuteSeekToEnd, CanExecuteMediaCommands);
LogManager.Info("动画控制命令初始化完成");
}
@ -592,11 +648,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
LogManager.Info("已清理UI层碰撞检测缓存");
// 首先重置进度(开始全新动画时)
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
AnimationProgress = 0;
CurrentAnimationTime = 0.0;
});
_pathAnimationManager.StartAnimation();
@ -652,8 +703,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
await _uiStateManager.ExecuteUIUpdateAsync(() =>
{
UpdateMainStatus("动画已停止");
AnimationProgress = 0;
CurrentAnimationTime = 0.0;
CanStartAnimation = true;
CanPauseAnimation = false;
@ -738,6 +787,181 @@ namespace NavisworksTransport.UI.WPF.ViewModels
}
}
#region
/// <summary>
/// 检查媒体控制命令是否可执行
/// </summary>
private bool CanExecuteMediaCommands()
{
return _pathAnimationManager != null &&
_pathAnimationManager.CurrentState != NavisworksTransport.Core.Animation.AnimationState.Idle &&
_pathAnimationManager.TotalFrames > 0;
}
/// <summary>
/// 执行正向播放命令
/// </summary>
private void ExecutePlayForward()
{
try
{
_pathAnimationManager?.PlayForward();
UpdateMediaControlProperties();
LogManager.Info("执行正向播放命令");
}
catch (Exception ex)
{
LogManager.Error($"正向播放命令执行失败: {ex.Message}");
UpdateMainStatus("正向播放失败");
}
}
/// <summary>
/// 执行反向播放命令
/// </summary>
private void ExecutePlayReverse()
{
try
{
_pathAnimationManager?.PlayReverse();
UpdateMediaControlProperties();
LogManager.Info("执行反向播放命令");
}
catch (Exception ex)
{
LogManager.Error($"反向播放命令执行失败: {ex.Message}");
UpdateMainStatus("反向播放失败");
}
}
/// <summary>
/// 执行单帧前进命令
/// </summary>
private void ExecuteStepForward()
{
try
{
_pathAnimationManager?.StepForward();
UpdateMediaControlProperties();
LogManager.Debug("执行单帧前进命令");
}
catch (Exception ex)
{
LogManager.Error($"单帧前进命令执行失败: {ex.Message}");
}
}
/// <summary>
/// 执行单帧后退命令
/// </summary>
private void ExecuteStepBackward()
{
try
{
_pathAnimationManager?.StepBackward();
UpdateMediaControlProperties();
LogManager.Debug("执行单帧后退命令");
}
catch (Exception ex)
{
LogManager.Error($"单帧后退命令执行失败: {ex.Message}");
}
}
/// <summary>
/// 执行快进10帧命令
/// </summary>
private void ExecuteFastForward()
{
try
{
_pathAnimationManager?.FastForward();
UpdateMediaControlProperties();
LogManager.Debug("执行快进10帧命令");
}
catch (Exception ex)
{
LogManager.Error($"快进命令执行失败: {ex.Message}");
}
}
/// <summary>
/// 执行快退10帧命令
/// </summary>
private void ExecuteFastBackward()
{
try
{
_pathAnimationManager?.FastBackward();
UpdateMediaControlProperties();
LogManager.Debug("执行快退10帧命令");
}
catch (Exception ex)
{
LogManager.Error($"快退命令执行失败: {ex.Message}");
}
}
/// <summary>
/// 执行跳转到开头命令
/// </summary>
private void ExecuteSeekToStart()
{
try
{
_pathAnimationManager?.SeekToFrame(0);
UpdateMediaControlProperties();
LogManager.Info("跳转到动画开头");
}
catch (Exception ex)
{
LogManager.Error($"跳转到开头失败: {ex.Message}");
}
}
/// <summary>
/// 执行跳转到结尾命令
/// </summary>
private void ExecuteSeekToEnd()
{
try
{
int lastFrame = (_pathAnimationManager?.TotalFrames ?? 1) - 1;
_pathAnimationManager?.SeekToFrame(lastFrame);
UpdateMediaControlProperties();
LogManager.Info("跳转到动画结尾");
}
catch (Exception ex)
{
LogManager.Error($"跳转到结尾失败: {ex.Message}");
}
}
/// <summary>
/// 更新媒体控制相关属性
/// </summary>
private void UpdateMediaControlProperties()
{
OnPropertyChanged(nameof(TimelinePosition));
OnPropertyChanged(nameof(TimelineDuration));
OnPropertyChanged(nameof(CurrentTimeDisplay));
OnPropertyChanged(nameof(FrameInfoDisplay));
OnPropertyChanged(nameof(IsPlayingForward));
OnPropertyChanged(nameof(IsPlayingReverse));
}
/// <summary>
/// 格式化时间显示
/// </summary>
private string FormatTime(double seconds)
{
var timeSpan = TimeSpan.FromSeconds(Math.Max(0, seconds));
return $"{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}";
}
#endregion
#endregion
#region
@ -794,9 +1018,10 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 直接使用Dispatcher.BeginInvoke进行UI更新避免UIStateManager的5秒超时导致积压
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
// 直接使用传入的百分比数值
AnimationProgress = progressPercent;
CurrentAnimationTime = (AnimationProgress / 100.0) * AnimationDuration;
// 更新时间轴相关属性
OnPropertyChanged(nameof(TimelinePosition));
OnPropertyChanged(nameof(CurrentTimeDisplay));
OnPropertyChanged(nameof(FrameInfoDisplay));
}));
}
@ -815,35 +1040,35 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CanStartAnimation = false;
CanPauseAnimation = true;
CanStopAnimation = true;
StartAnimationButtonText = "开始动画"; // 播放中时按钮显示为灰色的"开始动画"
UpdateMainStatus("动画播放中");
UpdateMediaControlProperties(); // 更新媒体控制属性
break;
case NavisworksTransport.Core.Animation.AnimationState.Paused:
CanStartAnimation = true; // 暂停状态下可以继续
CanPauseAnimation = false;
CanStopAnimation = true;
StartAnimationButtonText = "继续播放"; // 暂停状态下显示"继续播放"
UpdateMainStatus("动画已暂停");
UpdateMediaControlProperties(); // 更新媒体控制属性
break;
case NavisworksTransport.Core.Animation.AnimationState.Finished:
CanStartAnimation = true;
CanPauseAnimation = false;
CanStopAnimation = false;
StartAnimationButtonText = "开始动画"; // 完成后恢复"开始动画"
UpdateMainStatus("动画已完成");
// 动画完成后更新碰撞检测状态
UpdateCollisionStatusAfterAnimation();
UpdateMediaControlProperties(); // 更新媒体控制属性
break;
case NavisworksTransport.Core.Animation.AnimationState.Stopped:
CanStartAnimation = true;
CanPauseAnimation = false;
CanStopAnimation = false;
StartAnimationButtonText = "开始动画"; // 停止后恢复"开始动画"
UpdateMainStatus("动画已停止");
UpdateMediaControlProperties(); // 更新媒体控制属性
break;
default:
StartAnimationButtonText = "开始动画";
UpdateMainStatus($"动画状态: {state}");
UpdateMediaControlProperties(); // 更新媒体控制属性
break;
}
}));
@ -987,11 +1212,7 @@ namespace NavisworksTransport.UI.WPF.ViewModels
// 清理选择状态
SelectedAnimatedObject = null;
// 重置动画进度和状态
AnimationProgress = 0;
CurrentAnimationTime = 0.0;
// 清理碰撞检测结果
HasCollisionResults = false;
UpdateMainStatus("碰撞检测重置");
@ -1269,16 +1490,14 @@ namespace NavisworksTransport.UI.WPF.ViewModels
CanStartAnimation = hasValidAnimation && isAnimationReady;
CanPauseAnimation = false;
CanStopAnimation = true;
StartAnimationButtonText = "继续播放";
UpdateMainStatus("动画已暂停");
break;
default:
// 停止或其他状态:需要同时满足有效动画条件和动画已生成条件
CanStartAnimation = hasValidAnimation && isAnimationReady;
CanPauseAnimation = false;
CanStopAnimation = false;
StartAnimationButtonText = "开始动画";
// 更新状态文本
if (!hasValidPath && !hasValidAnimatedObject)
@ -1698,11 +1917,6 @@ namespace NavisworksTransport.UI.WPF.ViewModels
SelectedAnimatedObjectName = string.Empty;
CurrentPathRoute = null;
// 重置动画参数
AnimationProgress = 0;
CurrentAnimationTime = 0;
StartAnimationButtonText = "开始";
// 禁用按钮
CanStartAnimation = false;
CanPauseAnimation = false;

View File

@ -15,6 +15,7 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:converters="clr-namespace:NavisworksTransport.UI.WPF.Converters"
xmlns:views="clr-namespace:NavisworksTransport.UI.WPF.Views"
mc:Ignorable="d"
d:DesignHeight="700" d:DesignWidth="480">
@ -254,57 +255,14 @@ NavisworksTransport 检测动画页签视图 - 采用与类别设置和分层管
</StackPanel>
</Border>
<!-- 区域4: 播放控制 -->
<!-- 区域4: 专业级媒体播放控制 -->
<Border BorderBrush="#FFD4E7FF" BorderThickness="1" CornerRadius="0" Margin="0,0,0,15" Padding="12">
<StackPanel>
<Label Content="播放控制" Style="{StaticResource SectionHeaderStyle}"/>
<!-- 播放按钮组 -->
<StackPanel Orientation="Horizontal" Margin="0,10,0,15">
<Button Content="{Binding StartAnimationButtonText}"
Command="{Binding StartAnimationCommand}"
IsEnabled="{Binding CanStartAnimation}"
Style="{StaticResource ActionButtonStyle}"/>
<Button Content="暂停动画"
Command="{Binding PauseAnimationCommand}"
IsEnabled="{Binding CanPauseAnimation}"
Style="{StaticResource SecondaryButtonStyle}"/>
<Button Content="停止动画"
Command="{Binding StopAnimationCommand}"
IsEnabled="{Binding CanStopAnimation}"
Style="{StaticResource SecondaryButtonStyle}"/>
</StackPanel>
<!-- 专业级媒体控制条 -->
<views:MediaControlBar DataContext="{Binding}" Margin="0,10,0,10"/>
<!-- 进度条 -->
<Grid Margin="0,5,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="播放进度:" Width="80" VerticalAlignment="Center"/>
<ProgressBar Grid.Column="1"
Value="{Binding AnimationProgress}"
Style="{StaticResource ProgressBarStyle}"
Width="300"
Maximum="100"
Margin="5,0,10,0"/>
<TextBlock Grid.Column="2"
Text="{Binding AnimationProgress, StringFormat={}{0:F1}%}"
Width="80"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontWeight="SemiBold"
Foreground="#FF4472C4"
Margin="10,0,0,0"/>
</Grid>
<!-- 时间信息 -->
<TextBlock Text="{Binding CurrentAnimationTime, StringFormat='当前时间: {0:F1}s'}"
Style="{StaticResource StatusTextStyle}"
Margin="0,5,0,0"/>
</StackPanel>
</Border>

View File

@ -0,0 +1,211 @@
<UserControl x:Class="NavisworksTransport.UI.WPF.Views.MediaControlBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="600">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/NavisworksTransportPlugin;component/src/UI/WPF/Resources/MediaControlIcons.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<!-- 媒体控制条主容器 -->
<Border Background="#FF2D2D30"
BorderBrush="#FF3F3F46"
BorderThickness="1"
CornerRadius="4"
MouseEnter="OnMouseEnter"
GotFocus="OnGotFocus">
<Grid Margin="10,8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- 播放控制按钮行 -->
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,8">
<!-- 快退10帧 << -->
<Button x:Name="FastBackwardButton"
Command="{Binding FastBackwardCommand}"
Style="{StaticResource MediaControlButtonStyle}"
ToolTip="快退10帧 (Shift+←)">
<Path Data="{StaticResource FastBackwardIcon}"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
Width="14" Height="14"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button>
<!-- 上一帧 <| -->
<Button x:Name="StepBackwardButton"
Command="{Binding StepBackwardCommand}"
Style="{StaticResource MediaControlButtonStyle}"
ToolTip="上一帧 (←)">
<Path Data="{StaticResource StepBackwardIcon}"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
Width="12" Height="12"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button>
<!-- 反向播放 ◀ -->
<Button x:Name="ReversePlayButton"
Command="{Binding PlayReverseCommand}"
ToolTip="反向播放">
<Button.Style>
<Style BasedOn="{StaticResource MediaControlButtonStyle}" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding IsPlayingReverse}" Value="True">
<Setter Property="Background" Value="#FF4472C4"/>
<Setter Property="BorderBrush" Value="#FF5A82D4"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<Path Data="{StaticResource ReversePlayIcon}"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
Width="12" Height="12"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<ScaleTransform ScaleX="-1" ScaleY="1"/>
</Path.RenderTransform>
</Path>
</Button>
<!-- 暂停 || (居中位置) -->
<Button x:Name="PauseButton"
Command="{Binding PauseAnimationCommand}"
IsEnabled="{Binding CanPauseAnimation}"
Style="{StaticResource MediaControlButtonStyle}"
ToolTip="暂停 (Space)">
<Path Data="{StaticResource PauseIcon}"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
Width="12" Height="12"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button>
<!-- 正向播放 ▶ -->
<Button x:Name="PlayButton"
Command="{Binding PlayForwardCommand}"
ToolTip="播放">
<Button.Style>
<Style BasedOn="{StaticResource MediaControlButtonStyle}" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding IsPlayingForward}" Value="True">
<Setter Property="Background" Value="#FF4472C4"/>
<Setter Property="BorderBrush" Value="#FF5A82D4"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<Path Data="{StaticResource PlayIcon}"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
Width="12" Height="12"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button>
<!-- 下一帧 |> -->
<Button x:Name="StepForwardButton"
Command="{Binding StepForwardCommand}"
Style="{StaticResource MediaControlButtonStyle}"
ToolTip="下一帧 (→)">
<Path Data="{StaticResource StepForwardIcon}"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
Width="12" Height="12"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button>
<!-- 快进10帧 >> -->
<Button x:Name="FastForwardButton"
Command="{Binding FastForwardCommand}"
Style="{StaticResource MediaControlButtonStyle}"
ToolTip="快进10帧 (Shift+→)">
<Path Data="{StaticResource FastForwardIcon}"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
Width="14" Height="14"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button>
<!-- 停止 ■ (最右侧) -->
<Button x:Name="StopButton"
Command="{Binding StopAnimationCommand}"
IsEnabled="{Binding CanStopAnimation}"
Style="{StaticResource StopButtonStyle}"
ToolTip="停止"
Margin="12,2,2,2">
<Path Data="{StaticResource StopIcon}"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
Width="12" Height="12"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Button>
</StackPanel>
<!-- 时间轴和时间显示行 -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧信息区 -->
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,10,0">
<TextBlock Text="{Binding CurrentTimeDisplay}"
Style="{StaticResource TimeDisplayStyle}"
MinWidth="50"
TextAlignment="Right"/>
</StackPanel>
<!-- 中央时间轴区(居中) -->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
<Slider x:Name="TimelineSlider"
Value="{Binding TimelinePosition, Mode=TwoWay}"
Minimum="0"
Maximum="{Binding TimelineDuration}"
Style="{StaticResource TimelineSliderStyle}"
Width="300"
TickFrequency="1"
IsSnapToTickEnabled="True"
ToolTip="点击或拖动跳转到指定位置"/>
</StackPanel>
<!-- 右侧信息区 -->
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Left" Margin="10,0,0,0">
<TextBlock Text="{Binding TotalTimeDisplay}"
Style="{StaticResource TimeDisplayStyle}"
MinWidth="50"
Margin="0,0,15,0"/>
<TextBlock Text="{Binding FrameInfoDisplay}"
Style="{StaticResource TimeDisplayStyle}"
Foreground="#FF999999"
FontSize="10"
MinWidth="80"
TextAlignment="Left"/>
</StackPanel>
</Grid>
</Grid>
</Border>
</UserControl>

View File

@ -0,0 +1,153 @@
using System.Windows.Controls;
using System.Windows.Input;
using NavisworksTransport.Utils;
namespace NavisworksTransport.UI.WPF.Views
{
/// <summary>
/// MediaControlBar.xaml 的交互逻辑
/// 专业级媒体播放器风格的动画控制条
/// </summary>
public partial class MediaControlBar : UserControl
{
public MediaControlBar()
{
InitializeComponent();
// 启用键盘焦点以支持快捷键
this.Focusable = true;
this.KeyDown += OnKeyDown;
LogManager.Debug("MediaControlBar 初始化完成");
}
/// <summary>
/// 键盘快捷键处理
/// </summary>
private void OnKeyDown(object sender, KeyEventArgs e)
{
try
{
var viewModel = this.DataContext as NavisworksTransport.UI.WPF.ViewModels.AnimationControlViewModel;
if (viewModel == null) return;
// 处理键盘快捷键
switch (e.Key)
{
case Key.Space:
// 空格键:播放/暂停切换
if (viewModel.CanPauseAnimation)
{
viewModel.PauseAnimationCommand?.Execute(null);
}
else if (viewModel.CanStartAnimation)
{
viewModel.StartAnimationCommand?.Execute(null);
}
e.Handled = true;
break;
case Key.Left:
// 左箭头:单帧后退 或 Shift+左箭头快退10帧
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
viewModel.FastBackwardCommand?.Execute(null);
}
else
{
viewModel.StepBackwardCommand?.Execute(null);
}
e.Handled = true;
break;
case Key.Right:
// 右箭头:单帧前进 或 Shift+右箭头快进10帧
if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
{
viewModel.FastForwardCommand?.Execute(null);
}
else
{
viewModel.StepForwardCommand?.Execute(null);
}
e.Handled = true;
break;
case Key.R:
// R键切换播放方向
if (viewModel.IsPlayingReverse)
{
viewModel.PlayForwardCommand?.Execute(null);
}
else
{
viewModel.PlayReverseCommand?.Execute(null);
}
e.Handled = true;
break;
case Key.Home:
// Home键跳转到开头
viewModel.SeekToStartCommand?.Execute(null);
e.Handled = true;
break;
case Key.End:
// End键跳转到结尾
viewModel.SeekToEndCommand?.Execute(null);
e.Handled = true;
break;
case Key.Escape:
// Esc键停止播放
viewModel.StopAnimationCommand?.Execute(null);
e.Handled = true;
break;
}
if (e.Handled)
{
LogManager.Debug($"处理键盘快捷键: {e.Key} (修饰键: {Keyboard.Modifiers})");
}
}
catch (System.Exception ex)
{
LogManager.Error($"处理键盘快捷键时发生错误: {ex.Message}");
}
}
/// <summary>
/// 控件获得焦点时自动聚焦到时间轴滑块,以便支持键盘操作
/// </summary>
private void OnGotFocus(object sender, System.Windows.RoutedEventArgs e)
{
try
{
// 将焦点设置到时间轴滑块,以便键盘操作
TimelineSlider?.Focus();
}
catch (System.Exception ex)
{
LogManager.Debug($"设置焦点时出现异常: {ex.Message}");
}
}
/// <summary>
/// 鼠标进入控制条时获取焦点,以便支持键盘快捷键
/// </summary>
private void OnMouseEnter(object sender, MouseEventArgs e)
{
try
{
if (!this.IsFocused)
{
this.Focus();
}
}
catch (System.Exception ex)
{
LogManager.Debug($"鼠标进入时设置焦点异常: {ex.Message}");
}
}
}
}