Player Tool Bar
Layout
Player Tool BarStatus Text FieldStart ButtonPause ButtonRun-One-Line ButtonRun-One-Step ButtonStop ButtonReset Button
Behavior of Player Tool Bar
See the example code to:
- complete the behavior of the buttons and
Status Text Field. - The rapidly used buttons should has hotkey. At least the following buttons:
- Run One Line Button
- Run One Step Button
- Start/Continue
- Pause
- Both webservice and win-desktop applications use LocalProjectService events for monitoring PacePlayer status changes.
- In webservice applications, the
PlayerStatusServicesubscribes to these LocalProjectService events and broadcasts status changes viaPlayerStatusHubusing SignalR for real-time communication. - Win-desktop applications can directly subscribe to LocalProjectService events for status updates.
- Alter the background color of the
Status Text Fieldif the status changed.- Warning style color
- Running
- Secondary style color
- Paused
- No Project
- Success style color
- Finished
- Ready
- Warning style color
The action of Reset Button should be async for user experience.
Tip
Use icon instead of text to the tool bar button. Run One Line Button and Run One Step Button use the same icon, use the different color to resolve them.
- Run One Line Button > default color with green seasoned
- Run One Step Button > default color with blue seasoned
The other button use the default color is enough.
Source Code Path
See this page for git repository.
WPF Application Source Code Path
- Play/PlayerToolBar
Web Page Application Source Code Path
- wwwroot/player/player-tool-bar.js
- Players/PlayerController.cs
- Players/PlayerStatusHub.cs
- Players/PlayerStatusService.cs
SignalR Implementation (Webapi Only)
PlayerStatusHub provides real-time player status updates, with methods GetPlayerStatus() and event PlayerStatusUpdated. PlayerStatusService monitors LocalProjectService events (PacePlayer_IsRunningChangedEvent, PacePlayer_IsLockedChangedEvent, PacePlayer_IsFinishedChangedEvent, PacePlayer_ResetedEvent) and broadcasts changes via SignalR. The JavaScript component connects to /playerStatusHub and listens for status updates. API endpoints include /api/player/start, /api/player/pause, /api/player/resume, /api/player/run-line, /api/player/run-step, /api/player/stop, and /api/player/reset.
Razor Page Source Code
@using Hi.Common.PathUtils;
@using Hi.HiNcKits;
@using Hi.MachiningProcs
@using Hi.MillingProcs;
@using Hi.Numerical.FilePlayers;
@inject HiNcHost hiNcHost
@{
MachiningProject machiningProject = hiNcHost.MachiningProject;
bool disabledByMachiningProject = machiningProject == null;
}
<div class="btn-group" role="group">
<div class=" btn-group"
data-bs-toggle="collapse" data-bs-target="#player-@Tid">
<button class="btn btn-outline-info text-nowrap "
disabled="@disabledByMachiningProject"
data-bs-toggle="button">
<span class="me-1">@Loc["Player"]</span>
<div class="d-inline-block" style="width: 4rem">
@{
if (machiningProject == null) { }
else if (machiningProject.PacePlayer.IsRunning)
{
<span class="badge text-bg-warning">@Loc["Running"]</span>
}
else if (machiningProject.PacePlayer.IsLocked)
{
<span class="badge text-bg-secondary">@Loc["Pause"]</span>
}
else if (machiningProject.PacePlayer.IsFinished)
{
<span class="badge text-bg-success">@Loc["Finish"]</span>
}
else
{
<span class="badge text-bg-primary">@Loc["Unlocked"]</span>
}
}
</div>
</button>
</div>
<div id="player-@Tid" class="btn-group collapse collapse-horizontal show" role="group">
@if (machiningProject == null) { }
else if (!machiningProject.PacePlayer.IsLocked)
{
<button class="btn btn-primary text-nowrap" title="@Loc["Start"] (S)"
accesskey="s"
disabled="@(disabledByMachiningProject||machiningProject.PacePlayer.IsFinished)"
@onclick="StartOrContinue">
<span class="oi oi-media-play me-1"></span>
</button>
}
else
{
<button class="btn btn-primary text-nowrap" title="@Loc["Continue"] (S)"
accesskey="s"
@onclick="StartOrContinue"
disabled="@(disabledByMachiningProject||machiningProject.PacePlayer.IsFinished||machiningProject.PacePlayer.IsRunning)">
<span class="oi oi-media-play me-1"></span>
</button>
}
<button class="btn btn-primary text-nowrap" title="@Loc["Pause"] (P)"
accesskey="p"
@onclick="Pause"
disabled="@(disabledByMachiningProject||!machiningProject.PacePlayer.IsRunning)">
<span class="oi oi-media-pause me-1"></span>
</button>
<button class="btn btn-primary text-nowrap" title="@Loc["Run One Line"] (L)"
accesskey="l"
@onclick="RunToLineEnd"
disabled="@(disabledByMachiningProject||machiningProject.PacePlayer.IsFinished)">
<span class="oi oi-media-step-forward me-1"></span>
</button>
<button class="btn btn-primary text-nowrap" title="@Loc["Run One Step"] (K)"
accesskey="k"
@onclick="RunToNextPace"
disabled="@(disabledByMachiningProject||machiningProject.PacePlayer.IsFinished)">
<CommonRcl.Shared.CombinedIcon>
<IconA>
<span class="oi oi-media-step-forward me-1"></span>
</IconA>
<IconB>
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis">
step
</span>
</IconB>
</CommonRcl.Shared.CombinedIcon>
</button>
<button class="btn btn-primary text-nowrap" title="@Loc["Break"]"
@onclick="@Break"
disabled="@(disabledByMachiningProject||!(machiningProject.PacePlayer.IsLocked||machiningProject.PacePlayer.IsFinished))">
<span class="oi oi-media-stop me-1"></span>
</button>
<button class="btn btn-primary text-nowrap" title="@Loc["Reset"]"
disabled="@disabledByMachiningProject"
@onclick="Reset">
<span class="bi bi-backspace"></span>
</button>
</div>
</div>
using Hi.Common;
using Hi.MachiningProcs;
using Hi.Parallels;
using Microsoft.AspNetCore.Components;
namespace HiNcRcl.Areas.Player
{
public partial class PlayerButtonGroup : IAsyncDisposable
{
[Parameter]
public string Tid { set; get; } = System.Guid.NewGuid().ToString();
StringLocalizer Loc { get; } = new StringLocalizer(typeof(PlayerDiv));
SemaphoreSlim DisposeSemaphore { get; } = new SemaphoreSlim(1);
MachiningProject MachiningProject => hiNcHost.MachiningProject;
bool disposedValue = false;
/// <inheritdoc/>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
base.OnAfterRender(firstRender);
if (firstRender)
{
using var _ = await DisposeSemaphore.EmbraceAsync();
if (disposedValue) return;
var machiningProject = MachiningProject;
if (machiningProject != null)
{
machiningProject.PacePlayer.IsLockedChangedEvent
+= EnumerablePlayer_IsLockedEventHandler;
machiningProject.PacePlayer.IsRunningChangedEvent
+= EnumerablePlayer_IsLockedEventHandler;
machiningProject.PacePlayer.IsFinishedChangedEvent
+= EnumerablePlayer_IsLockedEventHandler;
}
}
}
/// <inheritdoc/>
public async ValueTask DisposeAsync()
{
using var _ = await DisposeSemaphore.EmbraceAsync();
var machiningProject = MachiningProject;
if (machiningProject != null)
{
machiningProject.PacePlayer.IsLockedChangedEvent
-= EnumerablePlayer_IsLockedEventHandler;
machiningProject.PacePlayer.IsRunningChangedEvent
-= EnumerablePlayer_IsLockedEventHandler;
machiningProject.PacePlayer.IsFinishedChangedEvent
-= EnumerablePlayer_IsLockedEventHandler;
}
disposedValue = true;
await ValueTask.CompletedTask;
}
private void EnumerablePlayer_IsLockedEventHandler(bool obj)
{
InvokeAsync(StateHasChanged).ConfigureAwait(false);
}
public async Task StartOrContinue()
{
await Task.Run(() =>
{
if (!MachiningProject.PacePlayer.IsLocked)
{
MachiningProject.PacePlayer.Start();
}
else if (!MachiningProject.PacePlayer.IsRunning
&& !MachiningProject.PacePlayer.IsFinished)
{
MachiningProject.PacePlayer.Resume();
}
}).ShowIfCatched(this);
}
public async Task Pause()
{
await Task.Run(() =>
{
MachiningProject?.PacePlayer.Pause();
}).ShowIfCatched(this);
}
public async Task RunToLineEnd()
{
await Task.Run(() =>
{
MachiningProject?.NcRunner.RunToLineEnd();
}).ShowIfCatched(this);
}
public async Task RunToNextPace()
{
await Task.Run(() =>
{
MachiningProject?.PacePlayer.RunToNextPace();
}).ShowIfCatched(this);
}
public async Task Break()
{
await Task.Run(() =>
{
MachiningProject?.PacePlayer.Terminate();
}).ShowIfCatched(this);
}
public async Task Reset()
{
await Task.Run(() =>
{
MachiningProject?.PacePlayer.Reset();
}).ShowIfCatched(this);
}
}
}