diff --git a/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationCameraImageSource.cs b/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationCameraImageSource.cs
index 8a2cd72..e9d2cf7 100644
--- a/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationCameraImageSource.cs
+++ b/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationCameraImageSource.cs
@@ -1,11 +1,12 @@
using OpenCvSharp;
using System.Drawing;
using Ninject;
+using VisionBuilder.UI.Common;
using VisionBuilder.UI.Common.RecipeProcessing;
namespace Hawkeye.VisionBuilder.UI.Sources.Emulation
{
- public class EmulationCameraImageSource:IImageSource, IInitializable
+ public class EmulationCameraImageSource:IImageSource, IVisionBuilderModule
{
private readonly EmulationSettings _settings;
private readonly string _name;
@@ -91,7 +92,7 @@ namespace Hawkeye.VisionBuilder.UI.Sources.Emulation
private int _index = 0;
- public void Initialize()
+ public void InitializeModule()
{
if(string.IsNullOrEmpty(_settings.EmulationPath)) return;
SetFolderAbsolute(_settings.EmulationPath);
diff --git a/Hawkeye.VisionBuilder.UI.Sources.Emulation/Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj b/Hawkeye.VisionBuilder.UI.Sources.Emulation/Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj
index d475293..d890e47 100644
--- a/Hawkeye.VisionBuilder.UI.Sources.Emulation/Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj
+++ b/Hawkeye.VisionBuilder.UI.Sources.Emulation/Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj
@@ -6,6 +6,10 @@
enable
+
+
+
+
diff --git a/VisionBuilder.UI.Camera/ModuleExtensions.cs b/VisionBuilder.UI.Camera/ModuleExtensions.cs
new file mode 100644
index 0000000..d9181a6
--- /dev/null
+++ b/VisionBuilder.UI.Camera/ModuleExtensions.cs
@@ -0,0 +1,41 @@
+using Hawkeye.VisionBuilder.UI.Sources.Emulation;
+using Ninject;
+using Ninject.Extensions.ChildKernel;
+using VisionBuilder.UI.Common;
+using VisionBuilder.UI.Common.RecipeProcessing;
+
+namespace Lindt.Colorballs.Duo.Utils;
+
+public static class ModuleExtensions
+{
+ public static IChildKernel RegisterCamera(this IChildKernel self, string cameraName)
+ {
+ self.Bind().ToConstant(new CameraSettings(cameraName));
+ self.Bind().ToConstant(new EmulationSettings(cameraName));
+ return self;
+ }
+ private static void BindCameras(CameraSettings cameraSettings, IChildKernel kernel)
+ {
+ switch (cameraSettings.CameraSource)
+ {
+ case CameraSettings.EImageSource.Emulation:
+
+ kernel.Bind().To().InSingletonScope();
+ break;
+ case CameraSettings.EImageSource.Hawkeye:
+ break;
+ case CameraSettings.EImageSource.IDS:
+ break;
+ case CameraSettings.EImageSource.Basler:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ public static IChildKernel UseCamera(this IChildKernel self)
+ {
+ var cameraSettings = self.Get();
+ BindCameras(cameraSettings, self);
+ return self;
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Camera/VisionBuilder.UI.Camera.csproj b/VisionBuilder.UI.Camera/VisionBuilder.UI.Camera.csproj
new file mode 100644
index 0000000..d29c8c2
--- /dev/null
+++ b/VisionBuilder.UI.Camera/VisionBuilder.UI.Camera.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/VisionBuilder.UI.Common/CameraSettings.cs b/VisionBuilder.UI.Common/CameraSettings.cs
index c53e067..27eb927 100644
--- a/VisionBuilder.UI.Common/CameraSettings.cs
+++ b/VisionBuilder.UI.Common/CameraSettings.cs
@@ -20,12 +20,12 @@ public class CameraSettings : ISettings
}
- public EImageSource Camera1Source { get; set; }
- public string Camera1Label { get; set; }
+ public EImageSource CameraSource { get; set; }
+ public string CameraLabel { get; set; }
public void RegisterSettings(InspectronSettings settings)
{
- settings.RegisterSimple(this, () => this.Camera1Source, CameraName, "Image source");
- settings.RegisterSimple(this, () => this.Camera1Label, CameraName, "Label");
+ settings.RegisterSimple(this, () => this.CameraSource, CameraName, "Image source");
+ settings.RegisterSimple(this, () => this.CameraLabel, CameraName, "Label");
}
}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Common/Extensions.cs b/VisionBuilder.UI.Common/Extensions.cs
index d20e1ae..47c61b5 100644
--- a/VisionBuilder.UI.Common/Extensions.cs
+++ b/VisionBuilder.UI.Common/Extensions.cs
@@ -1,5 +1,6 @@
using Inspectron.Settings;
using Ninject;
+using Ninject.Extensions.ChildKernel;
namespace VisionBuilder.UI.Common;
@@ -14,6 +15,29 @@ public static class Extensions
{
settingsInstance.RegisterSettings(self.Get());
}
-
}
+
+ public static void RegisterModule(this IKernel self) where T : IVisionBuilderModule
+ {
+ self.Bind().To().InSingletonScope();
+ }
+
+ public static void InitializeModules(this IKernel self)
+ {
+
+ var serviceTypes = self.GetAll(typeof(IVisionBuilderModule)).Select(x => (IVisionBuilderModule)x).ToList();
+ foreach (var service in serviceTypes)
+ {
+ if (service != null)
+ {
+ service.InitializeModule();
+ }
+ }
+ }
+
+
+
+
+
+
}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Common/IVisionBuilderModule.cs b/VisionBuilder.UI.Common/IVisionBuilderModule.cs
new file mode 100644
index 0000000..df80da8
--- /dev/null
+++ b/VisionBuilder.UI.Common/IVisionBuilderModule.cs
@@ -0,0 +1,6 @@
+namespace VisionBuilder.UI.Common;
+
+public interface IVisionBuilderModule
+{
+ public void InitializeModule();
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Common/PathSettingsUtils.cs b/VisionBuilder.UI.Common/PathSettingsUtils.cs
index b846066..ebee8e0 100644
--- a/VisionBuilder.UI.Common/PathSettingsUtils.cs
+++ b/VisionBuilder.UI.Common/PathSettingsUtils.cs
@@ -1,7 +1,32 @@
-namespace VisionBuilder.UI.Common;
+namespace Lindt.Colorballs.Duo.Utils;
public static class PathSettingsUtils
{
+ public const string INSTUCTIONS = @"
+You can use the following system variables:
+
+$RECIPE_NAME - name of the current recipe
+$DEFECT_NAME - name of the current defect(for bad images)
+$CAMERA_NAME - name of the camera
+
+$HOUR - hour of the image
+$MINUTE - minute of the image
+$SECOND - second of the image
+$MS - milliseconds of the image
+
+$DAY - day of the image
+$MONTH_NUM - number of the month of the image
+$MONTH_NAME - name of the month of the image
+$YEAR - year of the image
+
+$SS_HOUR - hour of the session start
+$SS_MINUTE - minute of the session start
+$SS_SECOND - second of the session start
+
+$SS_DAY - day of the session start
+$SS_MONTH_NUM - number of the month of the session start
+$SS_MONTH_NAME - name of the month of the session start
+$SS_YEAR - year of the session start";
public static string ConvertVariables(
string text,
DateTime imageTime,
@@ -25,7 +50,8 @@ public static class PathSettingsUtils
["$MS"] = imageTime.Millisecond.ToString("D3"),
["$DAY"] = imageTime.Day.ToString("D2"),
- ["$MONTH"] = imageTime.Month.ToString("D2"),
+ ["$MONTH_NUM"] = imageTime.Month.ToString("D2"),
+ ["$MONTH_NAME"] = imageTime.ToString("MMMM"),
["$YEAR"] = imageTime.Year.ToString(),
["$SS_HOUR"] = sessionTime.Hour.ToString("D2"),
@@ -33,7 +59,8 @@ public static class PathSettingsUtils
["$SS_SECOND"] = sessionTime.Second.ToString("D2"),
["$SS_DAY"] = sessionTime.Day.ToString("D2"),
- ["$SS_MONTH"] = sessionTime.ToString("MMMM"),
+ ["$SS_MONTH_NUM"] = sessionTime.Month.ToString("D2"),
+ ["$SS_MONTH_NAME"] = sessionTime.ToString("MMMM"),
["$SS_YEAR"] = sessionTime.Year.ToString()
};
diff --git a/VisionBuilder.UI.Common/ViewModel/SingleCameraVM.cs b/VisionBuilder.UI.Common/ViewModel/SingleCameraVM.cs
index f4796e9..8da8a29 100644
--- a/VisionBuilder.UI.Common/ViewModel/SingleCameraVM.cs
+++ b/VisionBuilder.UI.Common/ViewModel/SingleCameraVM.cs
@@ -30,7 +30,7 @@ namespace VisionBuilder.UI.Common
[NotifyCanExecuteChangedFor(nameof(StopCommand))]
private bool _isRunning;
[ObservableProperty]
- private string _cameraName;
+ private string _cameraLabel;
public bool CanStop => IsRunning;
@@ -56,7 +56,7 @@ namespace VisionBuilder.UI.Common
StatisticsVm = new StatisticsVM();
PreviewVm = new PreviewVM();
- CameraName = cameraSettings.CameraName;
+ CameraLabel = cameraSettings.CameraLabel;
recognitionControl.ImageProcessed += Handle;
diff --git a/VisionBuilder.UI.Common/VisionBuilder.UI.Common.csproj b/VisionBuilder.UI.Common/VisionBuilder.UI.Common.csproj
index 9c400a2..b0a2cfb 100644
--- a/VisionBuilder.UI.Common/VisionBuilder.UI.Common.csproj
+++ b/VisionBuilder.UI.Common/VisionBuilder.UI.Common.csproj
@@ -9,6 +9,7 @@
+
diff --git a/VisionBuilder.UI.Common/VisionBuilder.cs b/VisionBuilder.UI.Common/VisionBuilder.cs
new file mode 100644
index 0000000..dc13ea2
--- /dev/null
+++ b/VisionBuilder.UI.Common/VisionBuilder.cs
@@ -0,0 +1,23 @@
+using Inspectron.Settings;
+using Ninject;
+using Ninject.Extensions.ChildKernel;
+using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
+
+namespace VisionBuilder.UI.Common;
+
+public static class VisionBuilder
+{
+ public static IKernel CreateMainKernel(InspectronSettings settings)
+ {
+ StandardKernel mainKernel = new StandardKernel();
+
+ // GLOBAL SETTINGS //
+ mainKernel.Bind().ToConstant(settings);
+ mainKernel.Bind().ToConstant(new UIConfiguration());
+ mainKernel.Bind().To().InSingletonScope();
+ // --- //
+
+ return mainKernel;
+ }
+
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Console/ModuleExtensions.cs b/VisionBuilder.UI.Console/ModuleExtensions.cs
new file mode 100644
index 0000000..8f21f53
--- /dev/null
+++ b/VisionBuilder.UI.Console/ModuleExtensions.cs
@@ -0,0 +1,15 @@
+using Ninject.Extensions.ChildKernel;
+using VisionBuilder.UI.Common;
+using VisionBuilder.UI.Console;
+
+namespace VisionBuilder.UI.Statistics;
+
+public static class ModuleExtensions
+{
+ public static IChildKernel UseConsole(this IChildKernel self)
+ {
+ self.Bind().ToConstant(new VisionBuilderConsoleSettings());
+ self.RegisterModule();
+ return self;
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Console/VisionBuilder.UI.Console.csproj b/VisionBuilder.UI.Console/VisionBuilder.UI.Console.csproj
new file mode 100644
index 0000000..951ee0f
--- /dev/null
+++ b/VisionBuilder.UI.Console/VisionBuilder.UI.Console.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VisionBuilder.UI.Console/VisionBuilderConsole.cs b/VisionBuilder.UI.Console/VisionBuilderConsole.cs
new file mode 100644
index 0000000..cda569f
--- /dev/null
+++ b/VisionBuilder.UI.Console/VisionBuilderConsole.cs
@@ -0,0 +1,36 @@
+using Ninject;
+using Serilog;
+using VisionBuilder.UI.Common;
+
+namespace VisionBuilder.UI.Console
+{
+ public class VisionBuilderConsole: IVisionBuilderModule
+ {
+ private readonly VisionBuilderConsoleSettings _settings;
+
+ public VisionBuilderConsole(VisionBuilderConsoleSettings settings)
+ {
+ _settings = settings;
+ }
+
+ public void InitializeModule()
+ {
+ if (_settings.CreateConsoleWindow)
+ {
+ WinConsole.Initialize();
+ }
+ // configure Serilog to use console
+ if (_settings.UseConsole)
+ {
+ Serilog.Log.Logger = new Serilog.LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .WriteTo.Console()
+ .CreateLogger();
+
+ Log.Information("Console initialized");
+ }
+
+
+ }
+ }
+}
diff --git a/VisionBuilder.UI.Console/VisionBuilderConsoleSettings.cs b/VisionBuilder.UI.Console/VisionBuilderConsoleSettings.cs
new file mode 100644
index 0000000..d4225e7
--- /dev/null
+++ b/VisionBuilder.UI.Console/VisionBuilderConsoleSettings.cs
@@ -0,0 +1,18 @@
+using Inspectron.Settings;
+using Inspectron.Settings.Attributes;
+using Serilog;
+using VisionBuilder.UI.Common;
+
+namespace VisionBuilder.UI.Console;
+
+public class VisionBuilderConsoleSettings:ISettings
+{
+ public bool UseConsole { get; set; } = false;
+ [SettingDescription("Create a console window for non-console application")]
+ public bool CreateConsoleWindow { get; set; }
+ public void RegisterSettings(InspectronSettings settings)
+ {
+ settings.RegisterSimple(this, () => this.UseConsole, "Console", nameof(UseConsole));
+ settings.RegisterSimple(this, () => this.CreateConsoleWindow, "Console", nameof(CreateConsoleWindow));
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Console/WinConsole.cs b/VisionBuilder.UI.Console/WinConsole.cs
new file mode 100644
index 0000000..bc05978
--- /dev/null
+++ b/VisionBuilder.UI.Console/WinConsole.cs
@@ -0,0 +1,118 @@
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+
+namespace VisionBuilder.UI.Console
+{
+ public static class WinConsole
+ {
+ public static void Initialize(bool alwaysCreateNewConsole = true, bool minimizeWindow = false)
+ {
+ bool consoleAttached = true;
+ if (alwaysCreateNewConsole
+ || (AttachConsole(ATTACH_PARRENT) == 0
+ && Marshal.GetLastWin32Error() != ERROR_ACCESS_DENIED))
+ {
+ consoleAttached = AllocConsole() != 0;
+ }
+
+ if (consoleAttached)
+ {
+ InitializeOutStream();
+ InitializeInStream();
+ }
+
+ if (minimizeWindow)
+ {
+ const int SW_MINIMIZE = 6;
+ var handle = GetConsoleWindow();
+ ShowWindow(handle, SW_MINIMIZE);
+ }
+ }
+
+ public static void MinimizeWindow()
+ {
+ const int SW_MINIMIZE = 6;
+ var handle = GetConsoleWindow();
+ ShowWindow(handle, SW_MINIMIZE);
+ }
+
+
+ [DllImport("user32.dll")]
+ private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
+ [DllImport("kernel32.dll")]
+ private static extern IntPtr GetConsoleWindow();
+ private static void InitializeOutStream()
+ {
+ var fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
+ if (fs != null)
+ {
+ var writer = new StreamWriter(fs) { AutoFlush = true };
+ System.Console.SetOut(writer);
+ System.Console.SetError(writer);
+ }
+ }
+
+ private static void InitializeInStream()
+ {
+ var fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
+ if (fs != null)
+ {
+ System.Console.SetIn(new StreamReader(fs));
+ }
+ }
+
+ private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
+ FileAccess dotNetFileAccess)
+ {
+ var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
+ if (!file.IsInvalid)
+ {
+ var fs = new FileStream(file, dotNetFileAccess);
+ return fs;
+ }
+ return null;
+ }
+
+ #region Win API Functions and Constants
+ [DllImport("kernel32.dll",
+ EntryPoint = "AllocConsole",
+ SetLastError = true,
+ CharSet = CharSet.Auto,
+ CallingConvention = CallingConvention.StdCall)]
+ private static extern int AllocConsole();
+
+ [DllImport("kernel32.dll",
+ EntryPoint = "AttachConsole",
+ SetLastError = true,
+ CharSet = CharSet.Auto,
+ CallingConvention = CallingConvention.StdCall)]
+ private static extern UInt32 AttachConsole(UInt32 dwProcessId);
+
+ [DllImport("kernel32.dll",
+ EntryPoint = "CreateFileW",
+ SetLastError = true,
+ CharSet = CharSet.Auto,
+ CallingConvention = CallingConvention.StdCall)]
+ private static extern IntPtr CreateFileW(
+ string lpFileName,
+ UInt32 dwDesiredAccess,
+ UInt32 dwShareMode,
+ IntPtr lpSecurityAttributes,
+ UInt32 dwCreationDisposition,
+ UInt32 dwFlagsAndAttributes,
+ IntPtr hTemplateFile
+ );
+
+ private const UInt32 GENERIC_WRITE = 0x40000000;
+ private const UInt32 GENERIC_READ = 0x80000000;
+ private const UInt32 FILE_SHARE_READ = 0x00000001;
+ private const UInt32 FILE_SHARE_WRITE = 0x00000002;
+ private const UInt32 OPEN_EXISTING = 0x00000003;
+ private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x80;
+ private const UInt32 ERROR_ACCESS_DENIED = 5;
+
+ private const UInt32 ATTACH_PARRENT = 0xFFFFFFFF;
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Recipes.HawkeyeRecipe/ModuleExtensions.cs b/VisionBuilder.UI.Recipes.HawkeyeRecipe/ModuleExtensions.cs
new file mode 100644
index 0000000..b5dd3f0
--- /dev/null
+++ b/VisionBuilder.UI.Recipes.HawkeyeRecipe/ModuleExtensions.cs
@@ -0,0 +1,15 @@
+using Ninject.Extensions.ChildKernel;
+using VisionBuilder.UI.Common;
+using VisionBuilder.UI.Common.RecipeProcessing;
+
+namespace VisionBuilder.UI.Recipes.HawkeyeRecipe;
+
+public static class ModuleExtensions
+{
+ public static IChildKernel UseHawkeyeRecipes(this IChildKernel self, string cameraName)
+ {
+ self.Bind().ToConstant(new HawkeyeRecognitionSettings(cameraName));
+ self.Bind().To().InSingletonScope();
+ return self;
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Ringbuffer/ModuleExtensions.cs b/VisionBuilder.UI.Ringbuffer/ModuleExtensions.cs
new file mode 100644
index 0000000..17cf0e8
--- /dev/null
+++ b/VisionBuilder.UI.Ringbuffer/ModuleExtensions.cs
@@ -0,0 +1,14 @@
+using Ninject.Extensions.ChildKernel;
+using VisionBuilder.UI.Common;
+
+namespace VisionBuilder.UI.Ringbuffer;
+
+public static class ModuleExtensions
+{
+ public static IChildKernel UseRingbuffer(this IChildKernel self, string cameraName)
+ {
+ self.Bind().ToConstant(new VisionBuilderRingbufferSettings(cameraName));
+ self.RegisterModule();
+ return self;
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbuffer.cs b/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbuffer.cs
index b7dfd89..f351bfc 100644
--- a/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbuffer.cs
+++ b/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbuffer.cs
@@ -1,14 +1,14 @@
using Inspectron.Fastbuffer;
using Inspectron.Fastbuffer.Filesystems;
-using System.Drawing;
-using System.Drawing.Imaging;
+using Lindt.Colorballs.Duo.Utils;
using OpenCvSharp;
+using VisionBuilder.UI.Common;
using VisionBuilder.UI.Common.Commands;
using VisionBuilder.UI.Common.RecipeProcessing;
-namespace Lindt.Colorballs.Duo.Utils;
+namespace VisionBuilder.UI.Ringbuffer;
-public class VisionBuilderRingbuffer
+public class VisionBuilderRingbuffer:IVisionBuilderModule
{
private readonly VisionBuilderRingbufferSettings _settings;
private readonly IRecognitionControl _recognitionControl;
@@ -17,10 +17,13 @@ public class VisionBuilderRingbuffer
{
_settings = settings;
_recognitionControl = recognitionControl;
+
+ }
+ public void InitializeModule()
+ {
_recognitionControl.SessionStarted += _recognitionControl_SessionStarted;
_recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed;
}
-
private void _recognitionControl_ImageProcessed(VisionBuilder.UI.Common.Commands.ImageProcessedEvent obj)
{
if (obj.HasError)
@@ -124,4 +127,6 @@ public class VisionBuilderRingbuffer
_badRingbuffers.Clear();
}
+
+
}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbufferSettings.cs b/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbufferSettings.cs
index d802275..686a40e 100644
--- a/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbufferSettings.cs
+++ b/VisionBuilder.UI.Ringbuffer/VisionBuilderRingbufferSettings.cs
@@ -1,10 +1,9 @@
using Inspectron.Settings;
using Inspectron.Settings.Attributes;
-using System.ComponentModel;
-using System.Security.Cryptography.X509Certificates;
+using Lindt.Colorballs.Duo.Utils;
using VisionBuilder.UI.Common;
-namespace Lindt.Colorballs.Duo.Utils;
+namespace VisionBuilder.UI.Ringbuffer;
public class VisionBuilderRingbufferSettings : ISettings
{
@@ -23,48 +22,25 @@ public class VisionBuilderRingbufferSettings : ISettings
[SettingDescription(
@"
Specifies directory, in which MaxBad and MaxGood are applied.
-You can use the following system variables:
-
-$RECIPE_NAME - name of the current recipe
-$DEFECT_NAME - name of the current defect(for bad images)
-$CAMERA_NAME - name of the camera
-
-$HOUR - hour of the image
-$MINUTE - minute of the image
-$SECOND - second of the image
-$MS - milliseconds of the image
-
-$DAY - day of the image
-$MONTH - month of the image
-$YEAR - year of the image
-
-$SS_HOUR - hour of the session start
-$SS_MINUTE - minute of the session start
-$SS_SECOND - second of the session start
-
-$SS_DAY - day of the session start
-$SS_MONTH - month of the session start
-$SS_YEAR - year of the session start
-
-"
+"+ PathSettingsUtils.INSTUCTIONS
)]
[SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))]
- public string Root { get; set; } = @"..\Data\Ringbuffer\$SS_MONTH $SS_YEAR";
+ public string Root { get; set; } = @"..\Data\Ringbuffer\$SS_MONTH_NAME $SS_YEAR";
- [SettingDescriptionAttribute("Specifies directory, relative to the Root")]
+ [SettingDescription("Specifies directory, relative to the Root")]
[SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))]
public string GoodImagesPathTemplate { get; set; } = @"\$RECIPE_NAME";
- [SettingDescriptionAttribute("Specifies directory, relative to the Root")]
+ [SettingDescription("Specifies directory, relative to the Root")]
[SettingPreview(typeof(VisionBuilderRingbufferSettings),nameof(ImagesPathTemplatePreview))]
public string BadImagesPathTemplate { get; set; } =
@"\$RECIPE_NAME\$DEFECT_NAME";
[SettingPreview(typeof(VisionBuilderRingbufferSettings), nameof(ImagesPathTemplatePreview))]
- public string FilenameTemplate { get; set; } = "$YEAR-$MONTH-$DAY $HOUR-$MINUTE-$SECOND $MS";
+ public string FilenameTemplate { get; set; } = "$YEAR-$MONTH_NUM-$DAY $HOUR-$MINUTE-$SECOND $MS";
public void RegisterSettings(InspectronSettings settings)
diff --git a/VisionBuilder.UI.Statistics/ModuleExtensions.cs b/VisionBuilder.UI.Statistics/ModuleExtensions.cs
new file mode 100644
index 0000000..a5b2146
--- /dev/null
+++ b/VisionBuilder.UI.Statistics/ModuleExtensions.cs
@@ -0,0 +1,14 @@
+using Ninject.Extensions.ChildKernel;
+using VisionBuilder.UI.Common;
+
+namespace VisionBuilder.UI.Statistics;
+
+public static class ModuleExtensions
+{
+ public static IChildKernel UseStatistics(this IChildKernel self, string cameraName)
+ {
+ self.Bind().ToConstant(new VisionBuilderStatisticsSettings(cameraName));
+ self.RegisterModule();
+ return self;
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs b/VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs
index 967ce90..3aa90b9 100644
--- a/VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs
+++ b/VisionBuilder.UI.Statistics/VisionBuilderStatistics.cs
@@ -13,9 +13,9 @@ using VisionBuilder.UI.Common.RecipeProcessing;
namespace VisionBuilder.UI.Statistics
{
- public class VisionBuilderStatistics:IInitializable
+ public class VisionBuilderStatistics:IVisionBuilderModule,IDisposable
{
- private CsvStatistics _csvStatistics;
+ private CsvStatistics? _csvStatistics;
private readonly VisionBuilderStatisticsSettings _settings;
private string _filePath;
private readonly IRecognitionControl _recognitionControl;
@@ -29,7 +29,7 @@ namespace VisionBuilder.UI.Statistics
private const string MONTHLY_FILE_NAME = "statistics_month.csv";
private const string EMPTY_DATE = "";
private const string EMPTY_TIME = "";
-
+ public bool DoNotDeleteTempFiles { get; set; } = false;
public VisionBuilderStatistics(VisionBuilderStatisticsSettings settings, IRecognitionControl recognitionControl, ILoadingService loadingService)
{
@@ -37,18 +37,20 @@ namespace VisionBuilder.UI.Statistics
_recognitionControl = recognitionControl;
_loadingService = loadingService;
- _recognitionControl.SessionStarted += _recognitionControl_SessionStarted;
- _recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed;
- _recognitionControl.SessionEnded += _recognitionControl_SessionEnded;
+
}
private void _recognitionControl_SessionEnded(SessionEndedEvent obj)
{
+ var endedAt = obj.SessionEnded;
+ _endTime = endedAt;
_loadingService.StartLoading("Writing statistics for " + _settings.CameraName);
try
{
- _csvStatistics.WriteLine([_startTime.ToString("d"), _startTime.ToString("T"), DateTime.Now.ToString("d"), DateTime.Now.ToString("T"), "", "", ""]);
+ _csvStatistics.WriteLine([_startTime.ToString("d"), _startTime.ToString("T"), endedAt.ToString("d"), endedAt.ToString("T"), "", "", ""]);
_csvStatistics.Dispose();
+ _csvStatistics = null;
+
WriteFinalStatistics();
}
finally
@@ -67,10 +69,10 @@ namespace VisionBuilder.UI.Statistics
private void _recognitionControl_SessionStarted(SessionStartedEvent obj)
{
- _startTime = DateTime.Now;
+ _startTime = obj.SessionStarted;
_currentRecipe = obj.RecipeName;
_fileName = "tmp_"+Guid.NewGuid().ToString()+".csv";
- _directoryPath = PathSettingsUtils.ConvertVariables(_settings.PathTemplate, DateTime.Now, DateTime.Now,
+ _directoryPath = PathSettingsUtils.ConvertVariables(_settings.PathTemplate, _startTime, _startTime,
obj.RecipeName, cameraName: _settings.CameraName);
_filePath = Path.Combine(_directoryPath, _fileName);
var headers = new[] { _settings.StartDateColumnName, _settings.StartTimeColumnName, _settings.EndDateColumnName, _settings.EndTimeColumnName, _settings.ErrorTimeColumnName, _settings.ProductColumnName, _settings.ErrorColumnName };
@@ -84,6 +86,8 @@ namespace VisionBuilder.UI.Statistics
}
ConcurrentDictionary _defects = new();
+ private DateTime _endTime;
+
private void RecordBadImage(string defectName)
{
@@ -96,50 +100,60 @@ namespace VisionBuilder.UI.Statistics
_defects.AddOrUpdate(defectName, 1, (key, oldValue) => oldValue + 1);
}
- private void WriteMonthStatistics(string filename)
+ private void WriteMonthStatistics()
{
- // read second line to get the start time
- var lines = File.ReadAllLines(filename);
- var lastLine = lines[^1];
- if (lines.Length < 2) return; // no data to write
- var startDate = DateTime.Parse(lastLine.Split(',')[0]);
- var startTime = DateTime.Parse(lastLine.Split(',')[1]);
- var endDate = DateTime.Parse(lastLine.Split(',')[2]);
- var endTime = DateTime.Parse(lastLine.Split(',')[3]);
- // all must be non-empty
- if (startDate == DateTime.MinValue || startTime == DateTime.MinValue || endDate == DateTime.MinValue || endTime == DateTime.MinValue)
+ var startDate = _startTime.Date;
+ var startTime = _startTime;
+ var endDate = _endTime.Date;
+ var endTime = _endTime;
+
+ var monthFilePath = Path.Combine(_directoryPath, MONTHLY_FILE_NAME);
+
+ var monthStatistics = new CsvStatistics(monthFilePath, [
+ "Date start","Time start","Date end", "Time end" ,"Product","Error type","Error count"
+ ]);
+
+ foreach (var defect in _defects)
{
- Log.Error("Invalid date or time in statistics file: " + filename);
- throw new InvalidOperationException("Invalid date or time in statistics file: " + filename);
+ monthStatistics.WriteLine(
+ [startDate.ToString("d"), startTime.ToString("T"), endDate.ToString("d"), endTime.ToString("T"), _currentRecipe, defect.Key, defect.Value.ToString()]
+ );
}
+
+ monthStatistics.Dispose();
+
+
}
private void WriteFinalStatistics()
{
try
{
- var tmpFiles = Directory.GetFiles(_directoryPath, "tmp_*");
- // order by creation time
- Array.Sort(tmpFiles, (x, y) => File.GetCreationTime(x).CompareTo(File.GetCreationTime(y)));
- foreach (var file in tmpFiles)
- {
- var finalFileName = Path.Combine(_directoryPath, BY_EVENTS_FILE_NAME);
- var lines = File.ReadAllLines(file);
- File.AppendAllLines(finalFileName, lines.Skip(1)); // skip header
- WriteMonthStatistics(file);
- File.Delete(file);
- }
+ Log.Debug("Writing month statistics");
+ WriteMonthStatistics();
}
catch (Exception e)
{
- Log.Error("Error writing statistics for " + _settings.CameraName+ e);
+ Log.Error("Error writing statistics for " + _settings.CameraName+ "\n"+ e);
}
+ _csvStatistics?.Dispose();
+ _csvStatistics = null;
}
- public void Initialize()
+ public void InitializeModule()
{
- WriteFinalStatistics();
+ _recognitionControl.SessionStarted += _recognitionControl_SessionStarted;
+ _recognitionControl.ImageProcessed += _recognitionControl_ImageProcessed;
+ _recognitionControl.SessionEnded += _recognitionControl_SessionEnded;
+ }
+
+ public void Dispose()
+ {
+ _csvStatistics?.Dispose();
+ _recognitionControl.SessionStarted -= _recognitionControl_SessionStarted;
+ _recognitionControl.ImageProcessed -= _recognitionControl_ImageProcessed;
+ _recognitionControl.SessionEnded -= _recognitionControl_SessionEnded;
}
}
}
diff --git a/VisionBuilder.UI.Statistics/VisionBuilderStatisticsSettings.cs b/VisionBuilder.UI.Statistics/VisionBuilderStatisticsSettings.cs
index 89a39c6..bf2725d 100644
--- a/VisionBuilder.UI.Statistics/VisionBuilderStatisticsSettings.cs
+++ b/VisionBuilder.UI.Statistics/VisionBuilderStatisticsSettings.cs
@@ -1,5 +1,6 @@
using Inspectron.Settings;
using Inspectron.Settings.Attributes;
+using Lindt.Colorballs.Duo.Utils;
using VisionBuilder.UI.Common;
namespace VisionBuilder.UI.Statistics;
@@ -16,31 +17,10 @@ public class VisionBuilderStatisticsSettings: ISettings
[SettingDescription(
@"
Specifies directory, in which MaxBad and MaxGood are applied.
-You can use the following system variables:
-
-$RECIPE_NAME - name of the current recipe
-$DEFECT_NAME - name of the current defect(for bad images)
-$CAMERA_NAME - name of the camera
-
-$HOUR - hour of the image
-$MINUTE - minute of the image
-$SECOND - second of the image
-$MS - milliseconds of the image
-
-$DAY - day of the image
-$MONTH - month of the image
-$YEAR - year of the image
-
-$SS_HOUR - hour of the session start
-$SS_MINUTE - minute of the session start
-$SS_SECOND - second of the session start
-
-$SS_DAY - day of the session start
-$SS_MONTH - month of the session start
-$SS_YEAR - year of the session start
-"
+"+PathSettingsUtils.INSTUCTIONS
)]
- public string PathTemplate { get; set; }= @$"..\Data\Statistics\$SS_MONTH $SS_YEAR\$RECIPE_NAME\";
+ [SettingPreview(typeof(VisionBuilderStatisticsSettings), nameof(PathTemplatePreview))]
+ public string PathTemplate { get; set; }= @$"..\Data\Statistics\$SS_MONTH_NUM $SS_YEAR\$RECIPE_NAME\";
public string StartDateColumnName { get; set; } = "Start Date";
public string StartTimeColumnName { get; set; } = "Start time";
@@ -61,4 +41,10 @@ $SS_YEAR - year of the session start
settings.RegisterSimple(this,()=>this.ProductColumnName,CameraName+"/"+"Statistics",nameof(ProductColumnName));
settings.RegisterSimple(this,()=>this.ErrorColumnName,CameraName+"/"+"Statistics",nameof(ErrorColumnName));
}
+
+ public static string PathTemplatePreview(object template)
+ {
+ return PathSettingsUtils.ConvertVariables((string)template, DateTime.Now, DateTime.Now, "my recipe",
+ "my defect", "my camera");
+ }
}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Tests/VisionBuilder.UI.Tests.csproj b/VisionBuilder.UI.Tests/VisionBuilder.UI.Tests.csproj
index d43e20c..3baad5c 100644
--- a/VisionBuilder.UI.Tests/VisionBuilder.UI.Tests.csproj
+++ b/VisionBuilder.UI.Tests/VisionBuilder.UI.Tests.csproj
@@ -14,6 +14,7 @@
+
diff --git a/VisionBuilder.UI.Tests/VisionBuilderStatisticsTests.cs b/VisionBuilder.UI.Tests/VisionBuilderStatisticsTests.cs
new file mode 100644
index 0000000..f92456e
--- /dev/null
+++ b/VisionBuilder.UI.Tests/VisionBuilderStatisticsTests.cs
@@ -0,0 +1,471 @@
+using System.Collections.Concurrent;
+using System.Reflection;
+using VisionBuilder.UI.Common;
+using VisionBuilder.UI.Common.Commands;
+using VisionBuilder.UI.Common.RecipeProcessing;
+using VisionBuilder.UI.Common.ViewModel.Classes;
+using VisionBuilder.UI.Statistics;
+
+namespace VisionBuilder.UI.Tests;
+
+[TestClass]
+public class VisionBuilderStatisticsTests
+{
+ private string _tempDirectory;
+ private VisionBuilderStatisticsSettings _settings;
+ private TestRecognitionControl _recognitionControl;
+ private TestLoadingService _loadingService;
+
+ [TestInitialize]
+ public void Setup()
+ {
+ _tempDirectory = Path.Combine(Path.GetTempPath(), "VisionBuilderStatisticsTests", Guid.NewGuid().ToString());
+ Directory.CreateDirectory(_tempDirectory);
+
+ _settings = new VisionBuilderStatisticsSettings("TestCamera")
+ {
+ PathTemplate = _tempDirectory,
+ StartDateColumnName = "Start Date",
+ StartTimeColumnName = "Start time",
+ EndDateColumnName = "End Date",
+ EndTimeColumnName = "End time",
+ ErrorTimeColumnName = "Error time",
+ ProductColumnName = "Product",
+ ErrorColumnName = "Error"
+ };
+
+ _recognitionControl = new TestRecognitionControl();
+ _loadingService = new TestLoadingService();
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ if (Directory.Exists(_tempDirectory))
+ {
+ Directory.Delete(_tempDirectory, true);
+ }
+ }
+
+ [TestMethod]
+ public void SessionStarted_CreatesTemporaryFile_WithCorrectHeaders()
+ {
+ // Arrange
+ var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
+ statistics.InitializeModule();
+ var sessionStartEvent = new SessionStartedEvent
+ {
+ RecipeName = "TestRecipe",
+ SessionStarted = DateTime.Now
+ };
+
+ // Act
+ _recognitionControl.TriggerSessionStarted(sessionStartEvent);
+ statistics.Dispose(); // Ensure file is written and closed
+
+ // Assert
+ var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
+ Assert.AreEqual(1, tmpFiles.Length, "Should create exactly one temporary file");
+
+ var lines = File.ReadAllLines(tmpFiles[0]);
+ Assert.IsTrue(lines.Length >= 2, "Should have header and at least one data line");
+
+ var expectedHeaders = new[] { "Start Date", "Start time", "End Date", "End time", "Error time", "Product", "Error" };
+ var actualHeaders = lines[0].Split(',');
+ CollectionAssert.AreEqual(expectedHeaders, actualHeaders, "Headers should match settings");
+
+ var firstDataLine = lines[1].Split(',');
+ Assert.IsTrue(DateTime.TryParse(firstDataLine[0], out _), "First column should be a valid date");
+ Assert.IsTrue(DateTime.TryParse(firstDataLine[1], out _), "Second column should be a valid time");
+ }
+
+ [TestMethod]
+ public void ImageProcessed_WithError_RecordsDefectInFile()
+ {
+ var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
+ statistics.InitializeModule();
+ // Arrange
+ var sessionStartEvent = new SessionStartedEvent
+ {
+ RecipeName = "TestRecipe",
+ SessionStarted = DateTime.Now
+ };
+
+ var imageProcessedEvent = new ImageProcessedEvent
+ {
+ HasError = true,
+ ErrorNames = new List { "TestDefect" },
+ RecipeName = "TestRecipe",
+ SessionStart = DateTime.Now
+ };
+
+ // Act
+ _recognitionControl.TriggerSessionStarted(sessionStartEvent);
+ _recognitionControl.TriggerImageProcessed(imageProcessedEvent);
+ statistics.Dispose(); // Ensure file is written and closed
+ // Assert
+ var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
+ var lines = File.ReadAllLines(tmpFiles[0]);
+
+ Assert.IsTrue(lines.Length >= 3, "Should have header, session start, and error record");
+
+ var errorLine = lines[2].Split(',');
+ Assert.AreEqual("TestDefect", errorLine[6], "Error name should be recorded in correct column");
+ Assert.AreEqual("TestRecipe", errorLine[5], "Recipe name should be recorded in correct column");
+ Assert.IsTrue(DateTime.TryParse(errorLine[4], out _), "Error time should be valid");
+
+ // Verify defect counting using reflection
+ var defectsField = typeof(VisionBuilderStatistics).GetField("_defects", BindingFlags.NonPublic | BindingFlags.Instance);
+ var defects = (ConcurrentDictionary)defectsField.GetValue(statistics);
+ Assert.AreEqual(1, defects["TestDefect"], "Defect count should be incremented");
+ }
+
+ [TestMethod]
+ public void ImageProcessed_WithoutError_DoesNotRecordDefect()
+ {
+ var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
+ statistics.InitializeModule();
+ // Arrange
+ var sessionStartEvent = new SessionStartedEvent
+ {
+ RecipeName = "TestRecipe",
+ SessionStarted = DateTime.Now
+ };
+
+ var imageProcessedEvent = new ImageProcessedEvent
+ {
+ HasError = false,
+ ErrorNames = new List(),
+ RecipeName = "TestRecipe",
+ SessionStart = DateTime.Now
+ };
+
+ // Act
+ _recognitionControl.TriggerSessionStarted(sessionStartEvent);
+ _recognitionControl.TriggerImageProcessed(imageProcessedEvent);
+ statistics.Dispose(); // Ensure file is written and closed
+ // Assert
+ var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
+ var lines = File.ReadAllLines(tmpFiles[0]);
+
+ Assert.AreEqual(2, lines.Length, "Should only have header and session start line");
+
+ // Verify defect counting using reflection
+ var defectsField = typeof(VisionBuilderStatistics).GetField("_defects", BindingFlags.NonPublic | BindingFlags.Instance);
+ var defects = (ConcurrentDictionary)defectsField.GetValue(statistics);
+ Assert.AreEqual(0, defects.Count, "No defects should be recorded");
+ }
+
+ [TestMethod]
+ public void SessionEnded_WritesEndTimeAndDisposesCsv()
+ {
+ var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService)
+ {
+ DoNotDeleteTempFiles = true
+ };
+ statistics.InitializeModule();
+ // Arrange
+ var sessionStartEvent = new SessionStartedEvent
+ {
+ RecipeName = "TestRecipe",
+ SessionStarted = DateTime.Now
+ };
+
+ var sessionEndEvent = new SessionEndedEvent
+ {
+ SessionEnded = DateTime.Now
+ };
+
+ // Act
+ _recognitionControl.TriggerSessionStarted(sessionStartEvent);
+ _recognitionControl.TriggerSessionEnded(sessionEndEvent);
+ statistics.Dispose(); // Ensure file is written and closed
+
+ // Assert
+ var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
+ var lines = File.ReadAllLines(tmpFiles[0]);
+
+ Assert.IsTrue(lines.Length >= 3, "Should have header, session start, and session end");
+
+ var endLine = lines[2].Split(',');
+ Assert.IsTrue(DateTime.TryParse(endLine[2], out _), "End date should be valid");
+ Assert.IsTrue(DateTime.TryParse(endLine[3], out _), "End time should be valid");
+
+ Assert.IsTrue(_loadingService.StartLoadingCalled, "Should start loading during session end");
+ Assert.IsTrue(_loadingService.StopLoadingCalled, "Should stop loading after session end");
+ }
+
+ [TestMethod]
+ public void WriteFinalStatistics_ConsolidatesTemporaryFiles()
+ {
+ var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
+ statistics.InitializeModule();
+ // Arrange
+ var sessionStartEvent = new SessionStartedEvent
+ {
+ RecipeName = "TestRecipe",
+ SessionStarted = DateTime.Now
+ };
+
+ var imageProcessedEvent = new ImageProcessedEvent
+ {
+ HasError = true,
+ ErrorNames = new List { "TestDefect" },
+ RecipeName = "TestRecipe",
+ SessionStart = DateTime.Now
+ };
+
+ var sessionEndEvent = new SessionEndedEvent
+ {
+ SessionEnded = DateTime.Now
+ };
+
+ // Act
+ _recognitionControl.TriggerSessionStarted(sessionStartEvent);
+ _recognitionControl.TriggerImageProcessed(imageProcessedEvent);
+ _recognitionControl.TriggerSessionEnded(sessionEndEvent);
+
+ statistics.Dispose(); // Ensure file is written and closed
+
+ // Assert
+ var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
+ Assert.AreEqual(0, tmpFiles.Length, "Temporary files should be deleted after consolidation");
+
+ var finalFile = Path.Combine(_tempDirectory, "statistics_by_events.csv");
+ Assert.IsTrue(File.Exists(finalFile), "Final statistics file should be created");
+
+ var finalLines = File.ReadAllLines(finalFile);
+ Assert.IsTrue(finalLines.Length >= 2, "Final file should contain session data");
+
+ // Verify the defect was recorded in the final file
+ var defectLine = finalLines.FirstOrDefault(l => l.Contains("TestDefect"));
+ Assert.IsNotNull(defectLine, "Defect should be recorded in final file");
+ }
+
+ [TestMethod]
+ public void MultipleDefects_CountedCorrectly()
+ {
+ var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
+ statistics.InitializeModule();
+ // Arrange
+ var sessionStartEvent = new SessionStartedEvent
+ {
+ RecipeName = "TestRecipe",
+ SessionStarted = DateTime.Now
+ };
+
+ // Act
+ _recognitionControl.TriggerSessionStarted(sessionStartEvent);
+
+ // Process multiple images with same defect
+ for (int i = 0; i < 3; i++)
+ {
+ _recognitionControl.TriggerImageProcessed(new ImageProcessedEvent
+ {
+ HasError = true,
+ ErrorNames = new List { "DefectA" },
+ RecipeName = "TestRecipe",
+ SessionStart = DateTime.Now
+ });
+ }
+
+ // Process images with different defect
+ for (int i = 0; i < 2; i++)
+ {
+ _recognitionControl.TriggerImageProcessed(new ImageProcessedEvent
+ {
+ HasError = true,
+ ErrorNames = new List { "DefectB" },
+ RecipeName = "TestRecipe",
+ SessionStart = DateTime.Now
+ });
+ }
+ statistics.Dispose(); // Ensure file is written and closed
+ // Assert
+ var defectsField = typeof(VisionBuilderStatistics).GetField("_defects", BindingFlags.NonPublic | BindingFlags.Instance);
+ var defects = (ConcurrentDictionary)defectsField.GetValue(statistics);
+
+ Assert.AreEqual(3, defects["DefectA"], "DefectA should be counted correctly");
+ Assert.AreEqual(2, defects["DefectB"], "DefectB should be counted correctly");
+ }
+
+ [TestMethod]
+ public void PathTemplate_WithVariables_ReplacedCorrectly()
+ {
+ // Arrange
+ var customSettings = new VisionBuilderStatisticsSettings("TestCamera")
+ {
+ PathTemplate = Path.Combine(_tempDirectory, "$RECIPE_NAME", "$SS_YEAR", "$SS_MONTH")
+ };
+
+ var customStatistics = new VisionBuilderStatistics(customSettings, _recognitionControl, _loadingService);
+ customStatistics.InitializeModule();
+ var testDate = new DateTime(2023, 5, 15, 10, 30, 0);
+ var sessionStartEvent = new SessionStartedEvent
+ {
+ RecipeName = "MyRecipe",
+ SessionStarted = new DateTime(2023, 5, 15, 10, 30, 0)
+ };
+
+ // Act
+ _recognitionControl.TriggerSessionStarted(sessionStartEvent);
+ customStatistics.Dispose(); // Ensure file is written and closed
+ // Assert
+ var expectedPath = Path.Combine(_tempDirectory, "MyRecipe", testDate.ToString("yyyy"), testDate.ToString("MMMM"));
+ Assert.IsTrue(Directory.Exists(expectedPath), "Directory should be created with expanded variables");
+
+ var tmpFiles = Directory.GetFiles(expectedPath, "tmp_*.csv");
+ Assert.AreEqual(1, tmpFiles.Length, "Temporary file should be created in correct directory");
+ }
+
+ [TestMethod]
+ public void CustomColumnNames_UsedInHeaders()
+ {
+ // Arrange
+ var customSettings = new VisionBuilderStatisticsSettings("TestCamera")
+ {
+ PathTemplate = _tempDirectory,
+ StartDateColumnName = "Custom Start Date",
+ StartTimeColumnName = "Custom Start Time",
+ EndDateColumnName = "Custom End Date",
+ EndTimeColumnName = "Custom End Time",
+ ErrorTimeColumnName = "Custom Error Time",
+ ProductColumnName = "Custom Product",
+ ErrorColumnName = "Custom Error"
+ };
+
+ var customStatistics = new VisionBuilderStatistics(customSettings, _recognitionControl, _loadingService);
+ customStatistics.InitializeModule();
+ var sessionStartEvent = new SessionStartedEvent
+ {
+ RecipeName = "TestRecipe",
+ SessionStarted = DateTime.Now
+ };
+
+ // Act
+ _recognitionControl.TriggerSessionStarted(sessionStartEvent);
+
+ customStatistics.Dispose();
+
+ // Assert
+ var tmpFiles = Directory.GetFiles(_tempDirectory, "tmp_*.csv");
+ var lines = File.ReadAllLines(tmpFiles[0]);
+
+ var expectedHeaders = new[] { "Custom Start Date", "Custom Start Time", "Custom End Date", "Custom End Time", "Custom Error Time", "Custom Product", "Custom Error" };
+ var actualHeaders = lines[0].Split(',');
+ CollectionAssert.AreEqual(expectedHeaders, actualHeaders, "Custom column names should be used in headers");
+ }
+
+ [TestMethod]
+ public void WriteMonthStatistics_CreatesMonthlyFileWithDefectCounts()
+ {
+ var statistics = new VisionBuilderStatistics(_settings, _recognitionControl, _loadingService);
+ statistics.InitializeModule();
+ // Arrange
+ var sessionStartEvent = new SessionStartedEvent
+ {
+ RecipeName = "TestRecipe",
+ SessionStarted = new DateTime(2023, 5, 15, 10, 30, 0)
+ };
+
+ var imageProcessedEvent1 = new ImageProcessedEvent
+ {
+ HasError = true,
+ ErrorNames = new List { "DefectA" },
+ RecipeName = "TestRecipe",
+ SessionStart = new DateTime(2023, 5, 15, 10, 30, 0)
+ };
+
+ var imageProcessedEvent2 = new ImageProcessedEvent
+ {
+ HasError = true,
+ ErrorNames = new List { "DefectB" },
+ RecipeName = "TestRecipe",
+ SessionStart = new DateTime(2023, 5, 15, 10, 30, 0)
+ };
+
+ var sessionEndEvent = new SessionEndedEvent
+ {
+ SessionEnded = new DateTime(2023, 5, 15, 11, 45, 0)
+ };
+
+ // Act
+ _recognitionControl.TriggerSessionStarted(sessionStartEvent);
+ _recognitionControl.TriggerImageProcessed(imageProcessedEvent1);
+ _recognitionControl.TriggerImageProcessed(imageProcessedEvent1); // Same defect again
+ _recognitionControl.TriggerImageProcessed(imageProcessedEvent2);
+ _recognitionControl.TriggerSessionEnded(sessionEndEvent);
+ statistics.Dispose();
+
+ // Assert
+ var monthlyFile = Path.Combine(_tempDirectory, "statistics_month.csv");
+ Assert.IsTrue(File.Exists(monthlyFile), "Monthly statistics file should be created");
+
+ var monthlyLines = File.ReadAllLines(monthlyFile);
+ Assert.IsTrue(monthlyLines.Length >= 3, "Should have header and defect records");
+
+ var expectedHeaders = new[] { "Date start", "Time start", "Date end", "Time end", "Product", "Error type", "Error count" };
+ var actualHeaders = monthlyLines[0].Split(',');
+ CollectionAssert.AreEqual(expectedHeaders, actualHeaders, "Monthly file should have correct headers");
+
+ // Verify defect counts
+ var defectALine = monthlyLines.FirstOrDefault(l => l.Contains("DefectA"));
+ var defectBLine = monthlyLines.FirstOrDefault(l => l.Contains("DefectB"));
+
+ Assert.IsNotNull(defectALine, "DefectA should be recorded in monthly file");
+ Assert.IsNotNull(defectBLine, "DefectB should be recorded in monthly file");
+
+ var defectAData = defectALine.Split(',');
+ var defectBData = defectBLine.Split(',');
+
+ Assert.AreEqual("2", defectAData[6], "DefectA count should be 2");
+ Assert.AreEqual("1", defectBData[6], "DefectB count should be 1");
+ Assert.AreEqual("TestRecipe", defectAData[4], "Product name should be correct");
+ Assert.AreEqual("TestRecipe", defectBData[4], "Product name should be correct");
+
+ // Verify dates and times
+ Assert.IsTrue(DateTime.TryParse(defectAData[0], out var startDate), "Start date should be valid");
+ Assert.IsTrue(DateTime.TryParse(defectAData[1], out var startTime), "Start time should be valid");
+ Assert.IsTrue(DateTime.TryParse(defectAData[2], out var endDate), "End date should be valid");
+ Assert.IsTrue(DateTime.TryParse(defectAData[3], out var endTime), "End time should be valid");
+ }
+
+
+}
+
+// Test implementation of IRecognitionControl using reflection approach
+public class TestRecognitionControl : IRecognitionControl
+{
+ public event Action ImageProcessed = delegate { };
+ public event Action SessionStarted = delegate { };
+ public event Action SessionEnded = delegate { };
+
+ public List GetRecipesData() => new List();
+ public void SetRecipe(RecipeData recipe) { }
+ public void Start() { }
+ public void Stop() { }
+
+ public void TriggerImageProcessed(ImageProcessedEvent eventArgs) => ImageProcessed(eventArgs);
+ public void TriggerSessionStarted(SessionStartedEvent eventArgs) => SessionStarted(eventArgs);
+ public void TriggerSessionEnded(SessionEndedEvent eventArgs) => SessionEnded(eventArgs);
+}
+
+// Test implementation of ILoadingService
+public class TestLoadingService : ILoadingService
+{
+ public bool StartLoadingCalled { get; private set; }
+ public bool StopLoadingCalled { get; private set; }
+ public string LastLoadingMessage { get; private set; }
+
+ public void StartLoading(string message)
+ {
+ StartLoadingCalled = true;
+ LastLoadingMessage = message;
+ }
+
+ public void StopLoading(string message)
+ {
+ StopLoadingCalled = true;
+ LastLoadingMessage = message;
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Windows.Test/Program.cs b/VisionBuilder.UI.Windows.Test/Program.cs
index 1123f29..b3321fa 100644
--- a/VisionBuilder.UI.Windows.Test/Program.cs
+++ b/VisionBuilder.UI.Windows.Test/Program.cs
@@ -11,14 +11,19 @@ using Inspectron.Settings;
using Inspectron.Settings.Windows.Configuration;
using Lindt.Colorballs.Duo.Utils;
using Ninject;
+using Ninject.Extensions.ChildKernel;
using OpenCvSharp;
using VisionBuilder.UI.Common.RecipeProcessing;
+using VisionBuilder.UI.Console;
using VisionBuilder.UI.Recipes.HawkeyeRecipe;
+using VisionBuilder.UI.Ringbuffer;
+using VisionBuilder.UI.Statistics;
namespace VisionBuilder.UI.Windows.Test
{
internal static class Program
{
+ private const string CAMERA1 = "Camera 1";
///
/// The main entry point for the application.
///
@@ -29,59 +34,43 @@ namespace VisionBuilder.UI.Windows.Test
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
+ InspectronSettings settings = new InspectronSettings("..\\Config\\AppSettings.xml");
- StandardKernel kernelCamera1 = new StandardKernel();
+ var mainKernel = Common.VisionBuilder.CreateMainKernel(settings);
+
+
+
+ // CAMERA 1 //
+ ChildKernel kernelCamera1 = new ChildKernel(mainKernel);
+ mainKernel.UseWindowsServices();
+
+ // MODULES //
+ kernelCamera1
+ .RegisterCamera(CAMERA1)
+ .UseStatistics(CAMERA1)
+ .UseRingbuffer(CAMERA1)
+ .UseHawkeyeRecipes(CAMERA1)
+ .UseConsole();
- // SETTINGS //
- InspectronSettings settings = new InspectronSettings("..\\Config\\AppSettings.xml");
- kernelCamera1.Bind().ToConstant(settings);
- kernelCamera1.Bind().ToConstant(new UIConfiguration());
- kernelCamera1.Bind().ToConstant(new CameraSettings("Camera 1"));
- kernelCamera1.Bind().ToConstant(new EmulationSettings("Camera 1"));
- kernelCamera1.Bind().ToConstant(new HawkeyeRecognitionSettings("Camera 1"));
- kernelCamera1.Bind().ToConstant(new VisionBuilderRingbufferSettings("Camera 1"));
-
+ // LOAD SETTINGS //
kernelCamera1.RegisterSettings();
settings.LoadSettings();
+
+ // Create camera and bind it
+ // Must be done after settings are loaded
+ kernelCamera1.UseCamera();
+
+
+
+ kernelCamera1.InitializeModules();
+
+ var mainWindowVm = mainKernel.Get();
+ mainWindowVm.SingleCameraVms = [
+ kernelCamera1.Get()
+ ];
- // --- //
-
- // SOURCES //
- var cameraSettings = kernelCamera1.Get();
- switch (cameraSettings.Camera1Source)
- {
- case CameraSettings.EImageSource.Emulation:
- kernelCamera1.Bind().To().InSingletonScope();
- break;
- case CameraSettings.EImageSource.Hawkeye:
- break;
- case CameraSettings.EImageSource.IDS:
- break;
- case CameraSettings.EImageSource.Basler:
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
- // --- //
-
- // SERVICES //
- kernelCamera1.Bind().To().InSingletonScope();
- kernelCamera1.Bind().To().InSingletonScope();
- kernelCamera1.Bind().ToSelf().InSingletonScope();
- kernelCamera1.Bind().To().InSingletonScope();
-
- kernelCamera1.Get(); // initialize the ringbuffer
-
- var mainWindowVm = new MainWindowVM(kernelCamera1.Get())
- {
- SingleCameraVms = new ObservableCollection()
- {
- new SingleCameraVM(kernelCamera1.Get(),kernelCamera1.Get(),new RecipeSelectionDialogService(),kernelCamera1.Get(), new ImagePreviewService(), new NoLearning())
- }
- };
-
settings.LoadSettings();
Application.Run(new Form1(mainWindowVm));
}
diff --git a/VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Test.csproj b/VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Test.csproj
index 90e6dab..1349041 100644
--- a/VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Test.csproj
+++ b/VisionBuilder.UI.Windows.Test/VisionBuilder.UI.Windows.Test.csproj
@@ -9,6 +9,7 @@
+
@@ -17,9 +18,12 @@
+
+
+
diff --git a/VisionBuilder.UI.Windows/Components/PreviewWindow.resx b/VisionBuilder.UI.Windows/Components/PreviewWindow.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/VisionBuilder.UI.Windows/Components/PreviewWindow.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/VisionBuilder.UI.Windows/Components/SingleCameraControl.cs b/VisionBuilder.UI.Windows/Components/SingleCameraControl.cs
index 977bed1..ed33986 100644
--- a/VisionBuilder.UI.Windows/Components/SingleCameraControl.cs
+++ b/VisionBuilder.UI.Windows/Components/SingleCameraControl.cs
@@ -31,7 +31,7 @@ namespace VisionBuilder.UI.Windows.Components
btnStop.Command=_singleCameraVm.StopCommand;
btnSelectRecipe.Command=_singleCameraVm.SelectRecipeCommand;
- lblCameraName.DataBindings.Add("Text", _singleCameraVm, nameof(_singleCameraVm.CameraName), true, DataSourceUpdateMode.OnPropertyChanged);
+ lblCameraName.DataBindings.Add("Text", _singleCameraVm, nameof(_singleCameraVm.CameraLabel), true, DataSourceUpdateMode.OnPropertyChanged);
btnSelectRecipe.DataBindings.Add("Text", _singleCameraVm, nameof(_singleCameraVm.CurrentRecipeName), true, DataSourceUpdateMode.OnPropertyChanged);
}
diff --git a/VisionBuilder.UI.Windows/ModuleExtensions.cs b/VisionBuilder.UI.Windows/ModuleExtensions.cs
new file mode 100644
index 0000000..f1fb4a8
--- /dev/null
+++ b/VisionBuilder.UI.Windows/ModuleExtensions.cs
@@ -0,0 +1,18 @@
+using Ninject;
+using VisionBuilder.UI.Common.RecipeProcessing;
+using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
+using VisionBuilder.UI.Windows.Settings;
+
+namespace VisionBuilder.UI.Windows;
+
+public static class ModuleExtensions
+{
+ public static IKernel UseWindowsServices(this IKernel self)
+ {
+ self.Bind().To().InSingletonScope();
+ self.Bind().To().InSingletonScope();
+ self.Bind().To().InSingletonScope();
+ self.Bind().To().InSingletonScope();
+ return self;
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Windows/Services/LoadingService.cs b/VisionBuilder.UI.Windows/Services/LoadingService.cs
deleted file mode 100644
index bfe3a76..0000000
--- a/VisionBuilder.UI.Windows/Services/LoadingService.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using MaterialSkin.Controls;
-using VisionBuilder.UI.Common.RecipeProcessing;
-
-namespace VisionBuilder.UI.Windows.Settings;
-
-public class LoadingService: ILoadingService
-{
- public void StartLoading(string title)
- {
- MaterialLoader.Instance.StartLoading(title);
- }
-
- public void StopLoading(string title)
- {
- MaterialLoader.Instance.FinishLoading(title);
- }
-}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Windows/Services/ImagePreviewService.cs b/VisionBuilder.UI.Windows/Services/WindowsImagePreviewService.cs
similarity index 80%
rename from VisionBuilder.UI.Windows/Services/ImagePreviewService.cs
rename to VisionBuilder.UI.Windows/Services/WindowsImagePreviewService.cs
index 14fe3e6..916095f 100644
--- a/VisionBuilder.UI.Windows/Services/ImagePreviewService.cs
+++ b/VisionBuilder.UI.Windows/Services/WindowsImagePreviewService.cs
@@ -5,10 +5,10 @@ using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
namespace VisionBuilder.UI.Windows.Settings;
-public class ImagePreviewService: IImagePreviewService
+public class WindowsImagePreviewService: IImagePreviewService
{
- public ImagePreviewService()
+ public WindowsImagePreviewService()
{
}
diff --git a/VisionBuilder.UI.Windows/Services/WindowsLoadingService.cs b/VisionBuilder.UI.Windows/Services/WindowsLoadingService.cs
new file mode 100644
index 0000000..b3e3670
--- /dev/null
+++ b/VisionBuilder.UI.Windows/Services/WindowsLoadingService.cs
@@ -0,0 +1,28 @@
+using MaterialSkin.Controls;
+using VisionBuilder.UI.Common.RecipeProcessing;
+
+namespace VisionBuilder.UI.Windows.Settings;
+
+public class WindowsLoadingService: ILoadingService
+{
+ public void StartLoading(string title)
+ {
+ lock (this)
+ {
+ MaterialLoader.Instance.StartLoading(title);
+ Application.DoEvents(); // Ensure the UI updates immediately
+ Thread.Sleep(10);
+ }
+
+ }
+
+ public void StopLoading(string title)
+ {
+ lock (this)
+ {
+ MaterialLoader.Instance.FinishLoading(title);
+ Application.DoEvents(); // Ensure the UI updates immediately
+ Thread.Sleep(10);
+ }
+ }
+}
\ No newline at end of file
diff --git a/VisionBuilder.UI.Windows/Services/RecipeSelectionDialogService.cs b/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs
similarity index 85%
rename from VisionBuilder.UI.Windows/Services/RecipeSelectionDialogService.cs
rename to VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs
index f177581..023ba2e 100644
--- a/VisionBuilder.UI.Windows/Services/RecipeSelectionDialogService.cs
+++ b/VisionBuilder.UI.Windows/Services/WindowsRecipeSelectionDialogService.cs
@@ -4,7 +4,7 @@ using VisionBuilder.UI.Windows.Dialogs;
namespace VisionBuilder.UI.Windows.Settings;
-public class RecipeSelectionDialogService: IRecipeSelectionDialogService
+public class WindowsRecipeSelectionDialogService: IRecipeSelectionDialogService
{
public bool SelectRecipe(RecipeSelectionVM recipeSelectionVm)
{
diff --git a/VisionBuilder.UI.Windows/Services/SettingsService.cs b/VisionBuilder.UI.Windows/Services/WindowsSettingsService.cs
similarity index 86%
rename from VisionBuilder.UI.Windows/Services/SettingsService.cs
rename to VisionBuilder.UI.Windows/Services/WindowsSettingsService.cs
index 2e1c78b..1b446b1 100644
--- a/VisionBuilder.UI.Windows/Services/SettingsService.cs
+++ b/VisionBuilder.UI.Windows/Services/WindowsSettingsService.cs
@@ -5,12 +5,12 @@ using VisionBuilder.UI.Common.ViewModel.Interfaces.UI;
namespace VisionBuilder.UI.Windows.Settings;
-public class SettingsService: ISettingsService
+public class WindowsSettingsService: ISettingsService
{
private readonly UIConfiguration _uiConfiguration;
private readonly InspectronSettings _settings;
- public SettingsService(UIConfiguration uiConfiguration, InspectronSettings settings)
+ public WindowsSettingsService(UIConfiguration uiConfiguration, InspectronSettings settings)
{
_uiConfiguration = uiConfiguration ?? throw new ArgumentNullException(nameof(uiConfiguration));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
diff --git a/VisionBuilder.UI.sln b/VisionBuilder.UI.sln
index 8f2d432..b3635c2 100644
--- a/VisionBuilder.UI.sln
+++ b/VisionBuilder.UI.sln
@@ -51,6 +51,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inspectron.Fastbuffer", "fr
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{C828783C-1CE1-4245-8731-4AE18A28F590}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Console", "VisionBuilder.UI.Console\VisionBuilder.UI.Console.csproj", "{4B1817EF-A3D1-48CC-BE22-4138740E450F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.Camera", "VisionBuilder.UI.Camera\VisionBuilder.UI.Camera.csproj", "{FBDCC120-B6B3-4A28-8845-DF1690F619C1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionBuilder.UI.IOCommander", "VisionBuilder.UI.IOCommander\VisionBuilder.UI.IOCommander.csproj", "{E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -137,6 +143,18 @@ Global
{0A23E56A-DF4C-5174-18DF-18A771957AF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A23E56A-DF4C-5174-18DF-18A771957AF4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4B1817EF-A3D1-48CC-BE22-4138740E450F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FBDCC120-B6B3-4A28-8845-DF1690F619C1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -155,6 +173,9 @@ Global
{51C2BC65-5E5D-E72A-988D-215E192DE889} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
{1F9BB3BD-65C3-4B96-A880-4908CF6D9754} = {C828783C-1CE1-4245-8731-4AE18A28F590}
{0A23E56A-DF4C-5174-18DF-18A771957AF4} = {2B8C63F7-B7FD-464C-9045-180ED8F595F1}
+ {4B1817EF-A3D1-48CC-BE22-4138740E450F} = {C828783C-1CE1-4245-8731-4AE18A28F590}
+ {FBDCC120-B6B3-4A28-8845-DF1690F619C1} = {C828783C-1CE1-4245-8731-4AE18A28F590}
+ {E0F75E78-F7FA-4B7D-BA5F-A8A91C0A9AD9} = {C828783C-1CE1-4245-8731-4AE18A28F590}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3CE42AE5-D79F-4E97-A246-AA8FD228B677}
diff --git a/framework/Inspectron.Settings.Windows/Configuration/DefaultControlFactory.cs b/framework/Inspectron.Settings.Windows/Configuration/DefaultControlFactory.cs
index b90bb2e..d06b2f5 100644
--- a/framework/Inspectron.Settings.Windows/Configuration/DefaultControlFactory.cs
+++ b/framework/Inspectron.Settings.Windows/Configuration/DefaultControlFactory.cs
@@ -28,7 +28,8 @@ public class DefaultControlFactory : IControlFactory
getPreview = value => (string)method.Invoke(null, new[] { value });
previewLabel = new Label
{
- AutoSize = true,
+ AutoSize = false,
+ Size = new System.Drawing.Size(600, 30),
Padding = new Padding(15, 0, 0, 0),
Font = new System.Drawing.Font("Segoe UI", 10, System.Drawing.FontStyle.Italic),
Text = getPreview(initialValue)
@@ -95,7 +96,7 @@ public class DefaultControlFactory : IControlFactory
private Control CreateStringControl(string description, object initialValue, Action