hot reload
This commit is contained in:
8
.claude/settings.local.json
Normal file
8
.claude/settings.local.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(find:*)",
|
||||
"Bash(ls:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
94
CLAUDE.md
Normal file
94
CLAUDE.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
VisionBuilder.UI is a modular .NET 8.0 WinForms application for industrial computer vision. It processes images through configurable operation pipelines, supports AI inference (YOLO/ONNX), and integrates with HawkEye camera hardware. The solution uses Ninject for DI and follows MVVM patterns.
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
dotnet build VisionBuilder.UI.sln -c Debug # GPU build (YoloV8.Gpu + OnnxRuntime.Gpu)
|
||||
dotnet build VisionBuilder.UI.sln -c CPU # CPU-only build (YoloV8 + OnnxRuntime)
|
||||
dotnet build VisionBuilder.UI.sln -c Release # Production build
|
||||
```
|
||||
|
||||
The `Debug` and `Release` configurations pull GPU NuGet packages; `CPU` configuration pulls CPU-only packages. This is controlled by conditional `<PackageReference>` blocks in `Hawkeye.VisionBuilder.Workflow.csproj`.
|
||||
|
||||
## Testing
|
||||
|
||||
Test framework: **MSTest** (Microsoft.NET.Test.Sdk 17.12.0, MSTest 3.6.4)
|
||||
|
||||
```bash
|
||||
dotnet test VisionBuilder.UI.sln # Run all tests
|
||||
dotnet test VisionBuilder.UI.Tests/ # Primary unit tests only
|
||||
dotnet test --filter "FullyQualifiedName~ClassName.Method" # Single test
|
||||
```
|
||||
|
||||
Test projects: `VisionBuilder.UI.Tests`, `VisionBuilder.UI.Recipes.Scripted.Tests`, `IDSTest`
|
||||
|
||||
## Solution Architecture
|
||||
|
||||
The solution is organized into logical groups:
|
||||
|
||||
**Starters** (entry points):
|
||||
- `Hawkeye.VisionBuilder` — Main WinExe. `Program.cs` bootstraps the Ninject kernel, creates the `WorkflowList` and `OperationDiscoveryService`, then runs `MainWindow`.
|
||||
|
||||
**Core Modules**:
|
||||
- `Hawkeye.VisionBuilder.Workflow` — Image processing pipeline engine. Contains `BaseOperation`, `Context`, `WorkflowList`, `OperationDiscoveryService`, and all operation implementations. See its own `CLAUDE.md` for detailed operation/recipe documentation.
|
||||
- `VisionBuilder.UI.Common` — Shared contracts: `IPlugin`, `IVisionBuilderModule`, `IRecognitionControl`, `IImageSource`, service interfaces, MVVM ViewModels, command/event system.
|
||||
- `VisionBuilder.UI.Windows` — WinForms UI controls (PreviewWindow, ErrorPreview, Stats, SingleCameraControl).
|
||||
|
||||
**Framework** (reusable libraries under `framework/`):
|
||||
- `Inspectron.Camera`, `Inspectron.Settings`, `Inspectron.HawkEye`, `Inspectron.Fastbuffer`, `Inspectron.Ringbuffer`, `MaterialSkin.Core` — Hardware abstraction, settings management, and UI theming. Some target `netstandard2.1`.
|
||||
|
||||
**Sources** (image input providers):
|
||||
- `Sources.Emulation` — File-based emulated camera for development.
|
||||
- `Sources.Hawkeye` — Real HawkEye hardware source.
|
||||
- `Sources.IDS` — IDS industrial camera integration.
|
||||
|
||||
**Plugins** (extend functionality via `IPlugin` interface):
|
||||
- `B24SiemensPlugin`, `CandyboxPlugin`, `PralinenPLC`, `PackstrasseBarcodeReader`, etc.
|
||||
- Plugins register services into the Ninject kernel at global and per-camera scope.
|
||||
|
||||
**Recipes** (workflow serialization):
|
||||
- `Recipes.HawkeyeRecipe` — Binary `.hrcp` and XML `.xhrcp` recipe formats.
|
||||
- `Recipes.Scripted` — Script-based recipe execution.
|
||||
|
||||
## Key Architectural Patterns
|
||||
|
||||
### Operation Pipeline
|
||||
Images flow through a chain of `BaseOperation` subclasses. Each operation receives a `Context` (holding `ActiveImage`, `Memory` dictionary, `GraphicsElements`) and implements `InterpretInternal(Context)`. Operations are discovered at runtime via reflection by `OperationDiscoveryService`. Categories: `AI/`, `Simple/`, `Filters/`, `Morphology/`, `Image/`, `Basic/`.
|
||||
|
||||
### Plugin System
|
||||
Plugins implement `IPlugin` with two registration points:
|
||||
- `RegisterGlobalModules(IKernel)` — app-wide services
|
||||
- `RegisterCameraModules(IKernel, string cameraName)` — per-camera services
|
||||
|
||||
Modules implement `IVisionBuilderModule.InitializeModule()` for deferred initialization.
|
||||
|
||||
### DI Container
|
||||
Ninject `StandardKernel` is the root container. Child kernels (`Ninject.Extensions.ChildKernel`) scope per-camera services. All service resolution flows through the kernel — avoid `new` for services.
|
||||
|
||||
### Event/Command System
|
||||
`IEvent` / `IEventHandler<TEvent>` in `VisionBuilder.UI.Common/Commands/`. Key events: `SessionStartedEvent`, `SessionEndedEvent`, `ImageProcessedEvent`, `ErrorsInSequenceEvent`. `IRecognitionControl` surfaces these as `Action<T>` events.
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- **OpenCvSharp4** — Core image processing
|
||||
- **YoloV8 / YoloV8.Gpu** — AI object detection (conditional on build config)
|
||||
- **IronPython 3.4.0** — Python scripting integration
|
||||
- **Ninject** — Dependency injection
|
||||
- **CommunityToolkit.Mvvm** — MVVM support (ObservableObject, RelayCommand)
|
||||
- **Serilog** — Structured logging
|
||||
- **Scintilla.NET** — Code editor component in UI
|
||||
- **MaterialSkin.Core** — Material Design WinForms theming
|
||||
|
||||
## Conventions
|
||||
|
||||
- Target platform is **x64** Windows.
|
||||
- Culture is forced to `en-US` at startup.
|
||||
- Nullable reference types are enabled across most projects.
|
||||
- Operation attributes: `[Category("name")]` for UI grouping, `[NotForTool]` to exclude properties from serialization, `[IgnoreOperation]` to hide from discovery.
|
||||
- Recipe serialization supports both binary (`.hrcp`) and XML (`.xhrcp`) formats. Prefer XML for new work. Override `SaveXML`/`LoadXML`/`GetOperationElementName` for custom serialization.
|
||||
@@ -1,6 +1,7 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.Configuration;
|
||||
using Hawkeye.VisionBuilder.Workflow.Links;
|
||||
using OpenCvSharp;
|
||||
using Serilog;
|
||||
using System.ComponentModel.Design.Serialization;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
@@ -472,4 +473,77 @@ public class WorkflowList
|
||||
return workflowList;
|
||||
}
|
||||
|
||||
public void HotReloadParameters(string fileName)
|
||||
{
|
||||
string jsonString = File.ReadAllText(fileName);
|
||||
var workflowData = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString);
|
||||
|
||||
if (workflowData == null) return;
|
||||
|
||||
// Update Configuration
|
||||
if (workflowData.ContainsKey("Configuration"))
|
||||
{
|
||||
var configElement = (JsonElement)workflowData["Configuration"];
|
||||
var configData = JsonSerializer.Deserialize<Dictionary<string, object>>(configElement.GetRawText());
|
||||
|
||||
if (configData != null)
|
||||
{
|
||||
var convertedConfigData = ConvertJsonElementsToNatives(configData);
|
||||
|
||||
if (convertedConfigData.ContainsKey("RuntimeCameraType"))
|
||||
Configuration.RuntimeCameraType = Enum.Parse<ECameraType>(convertedConfigData["RuntimeCameraType"].ToString());
|
||||
if (convertedConfigData.ContainsKey("DevelopmentCameraType"))
|
||||
Configuration.DevelopmentCameraType = Enum.Parse<ECameraType>(convertedConfigData["DevelopmentCameraType"].ToString());
|
||||
if (convertedConfigData.ContainsKey("Outputs"))
|
||||
Configuration.Outputs = Enum.Parse<EOutputs>(convertedConfigData["Outputs"].ToString());
|
||||
if (convertedConfigData.ContainsKey("EmulationPath"))
|
||||
Configuration.EmulationPath = convertedConfigData["EmulationPath"].ToString();
|
||||
if (convertedConfigData.ContainsKey("PythonPath"))
|
||||
Configuration.PythonPath = convertedConfigData["PythonPath"].ToString();
|
||||
if (convertedConfigData.ContainsKey("ResultPin"))
|
||||
Configuration.ResultPin = (int)convertedConfigData["ResultPin"];
|
||||
if (convertedConfigData.ContainsKey("SerialPort"))
|
||||
Configuration.SerialPort = convertedConfigData["SerialPort"].ToString();
|
||||
if (convertedConfigData.ContainsKey("Delay"))
|
||||
Configuration.Delay = (int)convertedConfigData["Delay"];
|
||||
}
|
||||
}
|
||||
|
||||
// Update operation parameters by matching Id — do NOT recreate the Operations list
|
||||
if (workflowData.ContainsKey("Operations"))
|
||||
{
|
||||
var operationsElement = (JsonElement)workflowData["Operations"];
|
||||
var operationsArray = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(operationsElement.GetRawText());
|
||||
|
||||
if (operationsArray != null)
|
||||
{
|
||||
foreach (var operationData in operationsArray)
|
||||
{
|
||||
if (!operationData.ContainsKey("Data")) continue;
|
||||
|
||||
var dataElement = (JsonElement)operationData["Data"];
|
||||
var operationDict = JsonSerializer.Deserialize<Dictionary<string, object>>(dataElement.GetRawText());
|
||||
|
||||
if (operationDict == null) continue;
|
||||
|
||||
var convertedDict = ConvertJsonElementsToNatives(operationDict);
|
||||
|
||||
if (!convertedDict.ContainsKey("Id")) continue;
|
||||
|
||||
var id = new Guid(convertedDict["Id"].ToString());
|
||||
var existingOp = Operations.FirstOrDefault(x => x.Id == id);
|
||||
|
||||
if (existingOp != null)
|
||||
{
|
||||
existingOp.Load(convertedDict);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning("HotReload: no matching operation for Id {Id}", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -142,6 +142,7 @@ public abstract class BaseRecognitionControl: IRecognitionControl
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
_loopTask = null;
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
SessionEnded?.Invoke(new SessionEndedEvent
|
||||
@@ -241,4 +242,9 @@ public abstract class BaseRecognitionControl: IRecognitionControl
|
||||
public event Action<SessionStartedEvent> SessionStarted = delegate { };
|
||||
public event Action<SessionEndedEvent> SessionEnded = delegate { };
|
||||
public event Action<ErrorsInSequenceEvent> ErrorsInSequenceAlarm = delegate { };
|
||||
|
||||
public event Action RecipeFileChanged = delegate { };
|
||||
public virtual void HotReload() { }
|
||||
protected void RaiseRecipeFileChanged() => RecipeFileChanged?.Invoke();
|
||||
protected virtual void Cleanup() { }
|
||||
}
|
||||
@@ -17,4 +17,6 @@ public interface IRecognitionControl
|
||||
event Action<SessionEndedEvent> SessionEnded;
|
||||
event Action<ErrorsInSequenceEvent> ErrorsInSequenceAlarm;
|
||||
|
||||
void HotReload();
|
||||
event Action RecipeFileChanged;
|
||||
}
|
||||
@@ -32,9 +32,11 @@ namespace VisionBuilder.UI.Common
|
||||
[NotifyPropertyChangedFor(nameof(CanStop))]
|
||||
[NotifyPropertyChangedFor(nameof(CanPause))]
|
||||
[NotifyPropertyChangedFor(nameof(CanResume))]
|
||||
[NotifyPropertyChangedFor(nameof(CanHotReload))]
|
||||
[NotifyCanExecuteChangedFor(nameof(SelectRecipeCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(StartCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(StopCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(HotReloadCommand))]
|
||||
private bool _isRunning;
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -55,6 +57,13 @@ namespace VisionBuilder.UI.Common
|
||||
|
||||
public bool IsResumed => !IsPaused;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanHotReload))]
|
||||
[NotifyCanExecuteChangedFor(nameof(HotReloadCommand))]
|
||||
private bool _isHotReloadAvailable;
|
||||
|
||||
public bool CanHotReload => IsRunning && IsHotReloadAvailable;
|
||||
|
||||
public bool CanStop => IsRunning;
|
||||
public bool CanStart => !IsRunning && SelectedRecipe!=null;
|
||||
public bool CanPause => (IsRunning && !IsPaused)&&_uiConfiguration.ShowPauseButton;
|
||||
@@ -100,6 +109,9 @@ namespace VisionBuilder.UI.Common
|
||||
recognitionControl.SessionStarted += StatisticsVm.Handle;
|
||||
recognitionControl.SessionStarted += ErrorsVm.Handle;
|
||||
|
||||
recognitionControl.RecipeFileChanged += () =>
|
||||
SynchronizationContext?.Post(_ => IsHotReloadAvailable = true, null);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -153,7 +165,14 @@ namespace VisionBuilder.UI.Common
|
||||
});
|
||||
IsPaused = false;
|
||||
IsRunning = false;
|
||||
IsHotReloadAvailable = false;
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanHotReload))]
|
||||
public void HotReload()
|
||||
{
|
||||
_recognitionControl.HotReload();
|
||||
IsHotReloadAvailable = false;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -22,6 +22,11 @@ namespace VisionBuilder.UI.Recipes.HawkeyeRecipe
|
||||
private CancellationTokenSource _cts;
|
||||
private Task _loopTask;
|
||||
|
||||
private FileSystemWatcher? _fileWatcher;
|
||||
private System.Timers.Timer? _debounceTimer;
|
||||
private string? _pendingHotReloadFile;
|
||||
private string? _currentRecipeFilePath;
|
||||
|
||||
public HawkeyeRecognitionControl(IImageSource imageSource, HawkeyeRecognitionSettings recognitionConfiguration, ILoadingService loadingService): base(recognitionConfiguration,loadingService)
|
||||
{
|
||||
_imageSource = imageSource;
|
||||
@@ -62,7 +67,10 @@ namespace VisionBuilder.UI.Recipes.HawkeyeRecipe
|
||||
|
||||
protected override void Initialize(RecipeData currentRecipe)
|
||||
{
|
||||
_workflow = WorkflowList.LoadJSONFromFile("..\\Data\\Recipes\\" + currentRecipe.RecipeName + ".jhrcp");
|
||||
var path = "..\\Data\\Recipes\\" + currentRecipe.RecipeName + ".jhrcp";
|
||||
_currentRecipeFilePath = Path.GetFullPath(path);
|
||||
_workflow = WorkflowList.LoadJSONFromFile(path);
|
||||
StartFileWatcher(_currentRecipeFilePath);
|
||||
}
|
||||
|
||||
protected override void WarmUp()
|
||||
@@ -75,6 +83,9 @@ namespace VisionBuilder.UI.Recipes.HawkeyeRecipe
|
||||
|
||||
protected override (Mat originalImage, Mat analysisImage,TimeSpan processingTime, TimeSpan acquisitionTime, string[] errorNames)? ProcessImage(CancellationToken token)
|
||||
{
|
||||
var pendingFile = Interlocked.Exchange(ref _pendingHotReloadFile, null);
|
||||
if (pendingFile != null)
|
||||
_workflow.HotReloadParameters(pendingFile);
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
_workflow.Context.CancellationToken = token;
|
||||
@@ -113,5 +124,52 @@ namespace VisionBuilder.UI.Recipes.HawkeyeRecipe
|
||||
return (originalImage, analysisImage, sw.Elapsed, cameraTime, errorNames);
|
||||
}
|
||||
|
||||
private void StartFileWatcher(string filePath)
|
||||
{
|
||||
StopFileWatcher();
|
||||
|
||||
var directory = Path.GetDirectoryName(filePath)!;
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
|
||||
_debounceTimer = new System.Timers.Timer(500) { AutoReset = false };
|
||||
_debounceTimer.Elapsed += (_, _) => RaiseRecipeFileChanged();
|
||||
|
||||
_fileWatcher = new FileSystemWatcher(directory, fileName)
|
||||
{
|
||||
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
_fileWatcher.Changed += (_, _) =>
|
||||
{
|
||||
_debounceTimer.Stop();
|
||||
_debounceTimer.Start();
|
||||
};
|
||||
}
|
||||
|
||||
private void StopFileWatcher()
|
||||
{
|
||||
if (_fileWatcher != null)
|
||||
{
|
||||
_fileWatcher.EnableRaisingEvents = false;
|
||||
_fileWatcher.Dispose();
|
||||
_fileWatcher = null;
|
||||
}
|
||||
if (_debounceTimer != null)
|
||||
{
|
||||
_debounceTimer.Stop();
|
||||
_debounceTimer.Dispose();
|
||||
_debounceTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void HotReload()
|
||||
{
|
||||
Interlocked.Exchange(ref _pendingHotReloadFile, _currentRecipeFilePath);
|
||||
}
|
||||
|
||||
protected override void Cleanup()
|
||||
{
|
||||
StopFileWatcher();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,9 @@
|
||||
btnSelectRecipe = new MaterialSkin.Controls.MaterialRaisedButton();
|
||||
btnStop = new MaterialSkin.Controls.MaterialRaisedButton();
|
||||
btnPause = new MaterialSkin.Controls.MaterialRaisedButton();
|
||||
materialDivider2 = new MaterialSkin.Controls.MaterialDivider();
|
||||
btnResume = new MaterialSkin.Controls.MaterialRaisedButton();
|
||||
btnHotReload = new MaterialSkin.Controls.MaterialRaisedButton();
|
||||
materialDivider2 = new MaterialSkin.Controls.MaterialDivider();
|
||||
((System.ComponentModel.ISupportInitialize)previewWindow1).BeginInit();
|
||||
flowLayoutPanel1.SuspendLayout();
|
||||
flowLayoutPanel2.SuspendLayout();
|
||||
@@ -51,9 +52,9 @@
|
||||
//
|
||||
lblCameraName.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
lblCameraName.Font = new Font("Segoe UI Semibold", 12F, FontStyle.Bold, GraphicsUnit.Point, 204);
|
||||
lblCameraName.Location = new Point(8, 8);
|
||||
lblCameraName.Location = new Point(9, 11);
|
||||
lblCameraName.Name = "lblCameraName";
|
||||
lblCameraName.Size = new Size(744, 21);
|
||||
lblCameraName.Size = new Size(850, 28);
|
||||
lblCameraName.TabIndex = 2;
|
||||
lblCameraName.Text = "CameraName";
|
||||
lblCameraName.TextAlign = ContentAlignment.TopCenter;
|
||||
@@ -62,9 +63,9 @@
|
||||
//
|
||||
label1.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
label1.Font = new Font("Segoe UI", 12F);
|
||||
label1.Location = new Point(784, 0);
|
||||
label1.Location = new Point(896, 0);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new Size(408, 21);
|
||||
label1.Size = new Size(466, 28);
|
||||
label1.TabIndex = 4;
|
||||
label1.Text = "Errors";
|
||||
label1.TextAlign = ContentAlignment.TopCenter;
|
||||
@@ -72,9 +73,10 @@
|
||||
// previewWindow1
|
||||
//
|
||||
previewWindow1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
previewWindow1.Location = new Point(0, 32);
|
||||
previewWindow1.Location = new Point(0, 43);
|
||||
previewWindow1.Margin = new Padding(3, 4, 3, 4);
|
||||
previewWindow1.Name = "previewWindow1";
|
||||
previewWindow1.Size = new Size(752, 456);
|
||||
previewWindow1.Size = new Size(859, 608);
|
||||
previewWindow1.SizeMode = PictureBoxSizeMode.Zoom;
|
||||
previewWindow1.TabIndex = 5;
|
||||
previewWindow1.TabStop = false;
|
||||
@@ -84,28 +86,31 @@
|
||||
materialDivider1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
materialDivider1.BackColor = Color.FromArgb(55, 71, 79);
|
||||
materialDivider1.Depth = 0;
|
||||
materialDivider1.Location = new Point(760, 24);
|
||||
materialDivider1.Location = new Point(869, 32);
|
||||
materialDivider1.Margin = new Padding(3, 4, 3, 4);
|
||||
materialDivider1.MouseState = MaterialSkin.MouseState.HOVER;
|
||||
materialDivider1.Name = "materialDivider1";
|
||||
materialDivider1.Size = new Size(1, 456);
|
||||
materialDivider1.Size = new Size(1, 608);
|
||||
materialDivider1.TabIndex = 6;
|
||||
materialDivider1.Text = "materialDivider1";
|
||||
//
|
||||
// errorPreview1
|
||||
//
|
||||
errorPreview1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
errorPreview1.Location = new Point(776, 24);
|
||||
errorPreview1.Location = new Point(887, 32);
|
||||
errorPreview1.Margin = new Padding(3, 5, 3, 5);
|
||||
errorPreview1.Name = "errorPreview1";
|
||||
errorPreview1.Size = new Size(416, 456);
|
||||
errorPreview1.Size = new Size(475, 608);
|
||||
errorPreview1.TabIndex = 7;
|
||||
//
|
||||
// stats1
|
||||
//
|
||||
stats1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
stats1.BackColor = Color.White;
|
||||
stats1.Location = new Point(1200, 32);
|
||||
stats1.Location = new Point(1371, 43);
|
||||
stats1.Margin = new Padding(3, 5, 3, 5);
|
||||
stats1.Name = "stats1";
|
||||
stats1.Size = new Size(232, 456);
|
||||
stats1.Size = new Size(265, 608);
|
||||
stats1.TabIndex = 8;
|
||||
//
|
||||
// btnStart
|
||||
@@ -114,13 +119,14 @@
|
||||
btnStart.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
btnStart.Depth = 0;
|
||||
btnStart.DrawBorder = false;
|
||||
btnStart.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
|
||||
btnStart.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
btnStart.Icon = null;
|
||||
btnStart.Location = new Point(21, 73);
|
||||
btnStart.Location = new Point(25, 97);
|
||||
btnStart.Margin = new Padding(3, 4, 3, 4);
|
||||
btnStart.MouseState = MaterialSkin.MouseState.HOVER;
|
||||
btnStart.Name = "btnStart";
|
||||
btnStart.Primary = true;
|
||||
btnStart.Size = new Size(176, 64);
|
||||
btnStart.Size = new Size(201, 85);
|
||||
btnStart.TabIndex = 9;
|
||||
btnStart.Text = "Start";
|
||||
btnStart.UseVisualStyleBackColor = true;
|
||||
@@ -130,11 +136,12 @@
|
||||
flowLayoutPanel1.Controls.Add(flowLayoutPanel2);
|
||||
flowLayoutPanel1.Dock = DockStyle.Right;
|
||||
flowLayoutPanel1.FlowDirection = FlowDirection.TopDown;
|
||||
flowLayoutPanel1.Location = new Point(1448, 0);
|
||||
flowLayoutPanel1.Location = new Point(1655, 0);
|
||||
flowLayoutPanel1.Margin = new Padding(3, 4, 3, 4);
|
||||
flowLayoutPanel1.Name = "flowLayoutPanel1";
|
||||
flowLayoutPanel1.Padding = new Padding(8, 24, 8, 8);
|
||||
flowLayoutPanel1.Padding = new Padding(9, 32, 9, 11);
|
||||
flowLayoutPanel1.RightToLeft = RightToLeft.Yes;
|
||||
flowLayoutPanel1.Size = new Size(240, 498);
|
||||
flowLayoutPanel1.Size = new Size(274, 664);
|
||||
flowLayoutPanel1.TabIndex = 10;
|
||||
//
|
||||
// flowLayoutPanel2
|
||||
@@ -144,9 +151,11 @@
|
||||
flowLayoutPanel2.Controls.Add(btnStop);
|
||||
flowLayoutPanel2.Controls.Add(btnPause);
|
||||
flowLayoutPanel2.Controls.Add(btnResume);
|
||||
flowLayoutPanel2.Location = new Point(21, 27);
|
||||
flowLayoutPanel2.Controls.Add(btnHotReload);
|
||||
flowLayoutPanel2.Location = new Point(24, 36);
|
||||
flowLayoutPanel2.Margin = new Padding(3, 4, 3, 4);
|
||||
flowLayoutPanel2.Name = "flowLayoutPanel2";
|
||||
flowLayoutPanel2.Size = new Size(200, 365);
|
||||
flowLayoutPanel2.Size = new Size(229, 578);
|
||||
flowLayoutPanel2.TabIndex = 12;
|
||||
//
|
||||
// btnSelectRecipe
|
||||
@@ -157,11 +166,12 @@
|
||||
btnSelectRecipe.DrawBorder = true;
|
||||
btnSelectRecipe.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
|
||||
btnSelectRecipe.Icon = null;
|
||||
btnSelectRecipe.Location = new Point(21, 3);
|
||||
btnSelectRecipe.Location = new Point(25, 4);
|
||||
btnSelectRecipe.Margin = new Padding(3, 4, 3, 4);
|
||||
btnSelectRecipe.MouseState = MaterialSkin.MouseState.HOVER;
|
||||
btnSelectRecipe.Name = "btnSelectRecipe";
|
||||
btnSelectRecipe.Primary = false;
|
||||
btnSelectRecipe.Size = new Size(176, 64);
|
||||
btnSelectRecipe.Size = new Size(201, 85);
|
||||
btnSelectRecipe.TabIndex = 11;
|
||||
btnSelectRecipe.Text = "Select recipe";
|
||||
btnSelectRecipe.UseVisualStyleBackColor = true;
|
||||
@@ -174,11 +184,12 @@
|
||||
btnStop.DrawBorder = false;
|
||||
btnStop.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
|
||||
btnStop.Icon = null;
|
||||
btnStop.Location = new Point(21, 143);
|
||||
btnStop.Location = new Point(25, 190);
|
||||
btnStop.Margin = new Padding(3, 4, 3, 4);
|
||||
btnStop.MouseState = MaterialSkin.MouseState.HOVER;
|
||||
btnStop.Name = "btnStop";
|
||||
btnStop.Primary = true;
|
||||
btnStop.Size = new Size(176, 64);
|
||||
btnStop.Size = new Size(201, 85);
|
||||
btnStop.TabIndex = 10;
|
||||
btnStop.Text = "Stop";
|
||||
btnStop.UseVisualStyleBackColor = true;
|
||||
@@ -191,27 +202,16 @@
|
||||
btnPause.DrawBorder = true;
|
||||
btnPause.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
|
||||
btnPause.Icon = null;
|
||||
btnPause.Location = new Point(21, 213);
|
||||
btnPause.Location = new Point(25, 283);
|
||||
btnPause.Margin = new Padding(3, 4, 3, 4);
|
||||
btnPause.MouseState = MaterialSkin.MouseState.HOVER;
|
||||
btnPause.Name = "btnPause";
|
||||
btnPause.Primary = false;
|
||||
btnPause.Size = new Size(176, 64);
|
||||
btnPause.Size = new Size(201, 85);
|
||||
btnPause.TabIndex = 12;
|
||||
btnPause.Text = "Pause";
|
||||
btnPause.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// materialDivider2
|
||||
//
|
||||
materialDivider2.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
materialDivider2.BackColor = Color.FromArgb(55, 71, 79);
|
||||
materialDivider2.Depth = 0;
|
||||
materialDivider2.Location = new Point(1192, 24);
|
||||
materialDivider2.MouseState = MaterialSkin.MouseState.HOVER;
|
||||
materialDivider2.Name = "materialDivider2";
|
||||
materialDivider2.Size = new Size(1, 456);
|
||||
materialDivider2.TabIndex = 11;
|
||||
materialDivider2.Text = "materialDivider2";
|
||||
//
|
||||
// btnResume
|
||||
//
|
||||
btnResume.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
@@ -220,18 +220,51 @@
|
||||
btnResume.DrawBorder = true;
|
||||
btnResume.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
|
||||
btnResume.Icon = null;
|
||||
btnResume.Location = new Point(21, 283);
|
||||
btnResume.Location = new Point(25, 376);
|
||||
btnResume.Margin = new Padding(3, 4, 3, 4);
|
||||
btnResume.MouseState = MaterialSkin.MouseState.HOVER;
|
||||
btnResume.Name = "btnResume";
|
||||
btnResume.Primary = false;
|
||||
btnResume.Size = new Size(176, 64);
|
||||
btnResume.Size = new Size(201, 85);
|
||||
btnResume.TabIndex = 13;
|
||||
btnResume.Text = "Resume";
|
||||
btnResume.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnHotReload
|
||||
//
|
||||
btnHotReload.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
btnHotReload.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
||||
btnHotReload.Depth = 0;
|
||||
btnHotReload.DrawBorder = true;
|
||||
btnHotReload.Font = new Font("Segoe UI", 9F, FontStyle.Bold);
|
||||
btnHotReload.Icon = null;
|
||||
btnHotReload.Location = new Point(25, 469);
|
||||
btnHotReload.Margin = new Padding(3, 4, 3, 4);
|
||||
btnHotReload.MouseState = MaterialSkin.MouseState.HOVER;
|
||||
btnHotReload.Name = "btnHotReload";
|
||||
btnHotReload.Primary = false;
|
||||
btnHotReload.Size = new Size(201, 85);
|
||||
btnHotReload.TabIndex = 14;
|
||||
btnHotReload.Text = "Hot Reload";
|
||||
btnHotReload.UseVisualStyleBackColor = true;
|
||||
btnHotReload.Visible = false;
|
||||
//
|
||||
// materialDivider2
|
||||
//
|
||||
materialDivider2.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
materialDivider2.BackColor = Color.FromArgb(55, 71, 79);
|
||||
materialDivider2.Depth = 0;
|
||||
materialDivider2.Location = new Point(1362, 32);
|
||||
materialDivider2.Margin = new Padding(3, 4, 3, 4);
|
||||
materialDivider2.MouseState = MaterialSkin.MouseState.HOVER;
|
||||
materialDivider2.Name = "materialDivider2";
|
||||
materialDivider2.Size = new Size(1, 608);
|
||||
materialDivider2.TabIndex = 11;
|
||||
materialDivider2.Text = "materialDivider2";
|
||||
//
|
||||
// SingleCameraControl
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
BackColor = Color.White;
|
||||
Controls.Add(materialDivider2);
|
||||
@@ -242,8 +275,9 @@
|
||||
Controls.Add(previewWindow1);
|
||||
Controls.Add(label1);
|
||||
Controls.Add(lblCameraName);
|
||||
Margin = new Padding(3, 4, 3, 4);
|
||||
Name = "SingleCameraControl";
|
||||
Size = new Size(1688, 498);
|
||||
Size = new Size(1929, 664);
|
||||
((System.ComponentModel.ISupportInitialize)previewWindow1).EndInit();
|
||||
flowLayoutPanel1.ResumeLayout(false);
|
||||
flowLayoutPanel2.ResumeLayout(false);
|
||||
@@ -266,5 +300,6 @@
|
||||
private FlowLayoutPanel flowLayoutPanel2;
|
||||
private MaterialSkin.Controls.MaterialRaisedButton btnPause;
|
||||
private MaterialSkin.Controls.MaterialRaisedButton btnResume;
|
||||
private MaterialSkin.Controls.MaterialRaisedButton btnHotReload;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace VisionBuilder.UI.Windows.Components
|
||||
btnPause.DataBindings.Add(nameof(btnPause.Visible), _singleCameraVm, nameof(_singleCameraVm.CanPause), true, DataSourceUpdateMode.OnPropertyChanged);
|
||||
btnResume.DataBindings.Add(nameof(btnResume.Visible), _singleCameraVm, nameof(_singleCameraVm.CanResume), true, DataSourceUpdateMode.OnPropertyChanged);
|
||||
|
||||
btnHotReload.Command = _singleCameraVm.HotReloadCommand;
|
||||
btnHotReload.DataBindings.Add(nameof(btnHotReload.Visible), _singleCameraVm, nameof(_singleCameraVm.CanHotReload), true, DataSourceUpdateMode.OnPropertyChanged);
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user