check point

This commit is contained in:
meelstorm
2025-07-14 12:03:59 +02:00
commit d3cb790bd9
431 changed files with 44078 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
using Inspectron.Camera.Emulation;
using Ninject;
namespace Inspectron.Camera
{
public static class CameraExtensions
{
public static IKernel UseEmulationCamera(this IKernel self,string[] folders)
{
self.Rebind<ICameraProvider>().ToConstant(new EmulationCameraProvider(folders)).InSingletonScope();
return self;
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Inspectron.Camera
{
/// <summary>
/// Standard implementation of ICameraParameter interface
/// </summary>
public class CameraParameter : ICameraParameter, INotifyPropertyChanged
{
public CameraParameter(string name, Func<object> get, Action<object> set,
IEnumerable<object> values = null)
{
Name = name;
this.setter = set;
this.getter = get;
Values = new ReadOnlyCollection<object>(values != null ?
values.ToList() : new List<object>());
}
public string Name { get; private set; }
public object Value
{
get
{
return getter();
}
set
{
if (setter == null)
throw new NotSupportedException("This is a read-only parameter");
setter(value);
RaisePropertyChanged("Value");
}
}
public bool IsReadOnly { get { return setter == null; } }
public ReadOnlyCollection<object> Values { get; private set; }
Action<object> setter;
Func<object> getter;
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

View File

@@ -0,0 +1,167 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using Inspectron.Camera.Image;
namespace Inspectron.Camera.Emulation
{
public class EmulationCamera : ICamera
{
private string[] _selectedFiles = new string[0];
private int _index = 0;
private string _currentFolder;
public void Reset()
{
_index = 0;
}
public EmulationCamera(ICameraProvider provider)
{
Provider = provider;
}
public bool SingleRun { get; set; }=false;
public void SetFolder(string imagesFolder)
{
try
{
_currentFolder = imagesFolder;
var path = Path.Combine(@"..\Data\Emulation", imagesFolder);
_selectedFiles = Directory.GetFiles(path, "*.bmp")
.Concat(Directory.GetFiles(path, "*.png"))
.Concat(Directory.GetFiles(path, "*.jpg"))
.Where(x => !Path.GetFileNameWithoutExtension(x).Contains("_analysis"))
.ToArray();
}
catch
{
}
}
public void SetFolderAbsolute(string imagesFolder)
{
try
{
_currentFolder = imagesFolder;
var path = imagesFolder;
_selectedFiles = Directory.GetFiles(path, "*.bmp")
.Concat(Directory.GetFiles(path, "*.png"))
.Concat(Directory.GetFiles(path, "*.jpg"))
.Where(x => !Path.GetFileNameWithoutExtension(x).Contains("_analysis"))
.ToArray();
}
catch
{
}
}
public string Name { get; set; } = "Emulation";
public void Open()
{
IsOpen = true;
}
public bool IsOpen { get; private set; }
public void Close()
{
IsOpen = false;
}
public bool IsGrabbingContinuous { get; }
public void StartGrabContinuous()
{
throw new NotImplementedException();
}
public void StopGrabContinuous()
{
throw new NotImplementedException();
}
public void LoadParameters(string parametersFile)
{
}
public void SaveParameters(string parametersFile)
{
throw new NotImplementedException();
}
public void SaveParametersToDevice()
{
throw new NotImplementedException();
}
public int CycleDelay { get; set; } = 1;
public int PerImageDelay { get; set; } = 5000;
public event Action OnLoopOver=delegate{};
public IImage GrabSingle()
{
if (_selectedFiles.Length == 0) return null;
var id = (_index) % _selectedFiles.Length;
if (id == 0)
{
if(_index>0)
OnLoopOver();
if (SingleRun&&_index>0)
{
// infinite sleep
Thread.Sleep(Timeout.Infinite);
}
Console.WriteLine("starting new emulation cycle");
Thread.Sleep(CycleDelay);
}
else
{
Thread.Sleep(PerImageDelay);
}
var path = _selectedFiles[id];
_index++;
var bmp = new Bitmap(path);
Console.WriteLine($"Getting image {path}");
return new BitmapImage(bmp);
}
public event ImageGrabbedHandler ImageGrabbed;
public ICameraCapabilities Capabilities { get; } = new EmulationCameraCapabilities();
public IEnumerable<ICameraParameter> Parameters => new List<ICameraParameter>()
{
new CameraParameter("Folder", () => this.CurrentFolder, x => CurrentFolder = (string) x)
};
public ICameraProvider Provider { get; }
public string CurrentFolder
{
get => _currentFolder;
set => _currentFolder = value;
}
public int Index
{
get => _index;
set => _index = value;
}
public IEnumerable<(Bitmap bmp, string path)> GetImages()
{
foreach (string selectedFile in _selectedFiles)
{
yield return (new Bitmap(selectedFile), selectedFile);
}
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Inspectron.Camera.Emulation
{
public class EmulationCameraCapabilities:ICameraCapabilities
{
public bool CanGrabSingle { get; } = true;
public bool CanGrabContinuous { get; } = false;
public bool CanSaveParametersToFile { get; } = false;
public bool CanSaveParametersToDevice { get; } = false;
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
namespace Inspectron.Camera.Emulation
{
public class EmulationCameraProvider:ICameraProvider
{
private EmulationCamera _emulationCamera;
public EmulationCameraProvider(params string[] emulationFolders)
{
List<ICamera> cameras = new List<ICamera>();
foreach (string s in emulationFolders)
{
_emulationCamera = new EmulationCamera(this);
_emulationCamera.Name = "Emulation "+Path.GetFileNameWithoutExtension(s);
_emulationCamera.SetFolder(s);
cameras.Add(_emulationCamera);
}
Cameras=new ReadOnlyCollection<ICamera>(cameras);
}
public string Name { get; } = "Emulation camera provider";
public ReadOnlyCollection<ICamera> Discover()
{
return Cameras;
}
public ReadOnlyCollection<ICamera> Cameras { get; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Inspectron.Camera.Enums
{
public enum EStandardRotation
{
Rot0,
Rot90,
Rot180,
Rot270
}
}

View File

@@ -0,0 +1,91 @@
using System.Collections.Generic;
using Inspectron.Camera.Image;
namespace Inspectron.Camera
{
public interface ICamera
{
/// <summary>
/// Returns the name of the camera
/// </summary>
string Name { get; }
/// <summary>
/// Opens the camera
/// </summary>
void Open();
/// <summary>
/// Returns true if the camera is opened
/// </summary>
bool IsOpen { get; }
/// <summary>
/// Closes the camera
/// </summary>
void Close();
/// <summary>
/// Returns true if the camera is live
/// </summary>
bool IsGrabbingContinuous { get; }
/// <summary>
/// Stats live asynchronous acquisition. Images are transmitted the ImageGrabbed Event
/// </summary>
void StartGrabContinuous();
/// <summary>
/// Stops the asynchronous acquisition.
/// </summary>
void StopGrabContinuous();
/// <summary>
/// Loads the parameters from a filename
/// </summary>
/// <param name="parameters_file"></param>
void LoadParameters(string parametersFile);
/// <summary>
/// Saves the camera parameters to the given filename
/// </summary>
/// <param name="parameters_file"></param>
void SaveParameters(string parametersFile);
/// <summary>
/// Saves the parameters to the camera memory
/// </summary>
void SaveParametersToDevice();
/// <summary>
/// Synchronously grab an image
/// </summary>
/// <returns></returns>
IImage GrabSingle();
/// <summary>
/// Event occuring when a new frame is acquired
/// </summary>
event ImageGrabbedHandler ImageGrabbed;
/// <summary>
/// Returns the capabilities of the camera
/// </summary>
ICameraCapabilities Capabilities { get; }
/// <summary>
/// Returns a Ienumerable containing all of the camera
/// </summary>
IEnumerable<ICameraParameter> Parameters { get; }
/// <summary>
/// Reference to Parent Camera Provider
/// </summary>
ICameraProvider Provider { get; }
}
/// <summary>
/// Handler called when an image has been grabbed
/// </summary>
/// <param name="image">the grabbed image</param>
public delegate void ImageGrabbedHandler(ICamera sender, IImage image);
}

View File

@@ -0,0 +1,25 @@
namespace Inspectron.Camera
{
/// <summary>
/// Interface specifying the capabilities of the camera
/// </summary>
public interface ICameraCapabilities
{
/// <summary>
/// Indicates that handle synchronous acquisition. (see ICamera Grab method)
/// </summary>
bool CanGrabSingle { get; }
/// <summary>
/// Indicates that handle live asynchronous acquisition. Images are transmitted the ImageGrabbed Event. (see ICamera StartLive / StopLive methods)
/// </summary>
bool CanGrabContinuous { get; }
/// <summary>
/// Indicates that the camera can save and load the current parameter set to a file
/// </summary>
bool CanSaveParametersToFile { get; }
/// <summary>
/// Indicates that the camera can save the current parameter set to memory
/// </summary>
bool CanSaveParametersToDevice { get; }
}
}

View File

@@ -0,0 +1,27 @@
using System.Collections.ObjectModel;
namespace Inspectron.Camera
{
/// <summary>
/// ICameraParameter interface, standard interface to list and change camera parameters
/// </summary>
public interface ICameraParameter
{
/// <summary>
/// User friendly name
/// </summary>
string Name { get; }
/// <summary>
/// Getter and setter to values, null setter if readonly
/// </summary>
object Value { get; set; }
/// <summary>
/// Indicates that the parameter is readonly, Value setter is null
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// Values that can be chosen in case of fixed parameters set.
/// </summary>
ReadOnlyCollection<object> Values { get; }
}
}

View File

@@ -0,0 +1,22 @@
using System.Collections.ObjectModel;
namespace Inspectron.Camera
{
public interface ICameraProvider
{
/// <summary>
///
/// </summary>
string Name { get; }
/// <summary>
///
/// </summary>
/// <returns></returns>
ReadOnlyCollection<ICamera> Discover();
/// <summary>
///
/// </summary>
ReadOnlyCollection<ICamera> Cameras { get; }
}
}

View File

@@ -0,0 +1,35 @@
using System.Drawing;
using System.Drawing.Imaging;
namespace Inspectron.Camera.Image
{
public class BitmapImage : IImage
{
private readonly Bitmap _bitmap;
public BitmapImage(Bitmap bitmap)
{
_bitmap = bitmap;
}
public int Width => _bitmap.Width;
public int Height => _bitmap.Height;
public int Channels => throw new System.NotImplementedException();
public Bitmap Bitmap => (Bitmap)_bitmap.Clone();
public void Dispose()
{
}
public void Save(string path)
{
_bitmap.Save(path,ImageFormat.Bmp);
}
}
}

View File

@@ -0,0 +1,131 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace Inspectron.Camera.Image
{
public class ByteImage : IImage
{
Byte[] data;
int step;
/// <summary>
/// Initializes a newinstance with the given image dimensions and pixel-data
/// </summary>
/// <param name="width">the image width in pixel</param>
/// <param name="height">the image height in pixel</param>
/// <param name="channels">the number of color channels (1-4)</param>
/// <param name="channelDepth">the bit-depth per channel (8 or 16)</param>
/// <param name="data">pointer to the pixel data in unmanaged memory</param>
/// <param name="step">the number of bytes per image row</param>
public ByteImage(int width, int height, int channels,
Byte[] data, int step)
{
Width = width;
Height = height;
Channels = channels;
this.data = data;
this.step = step;
ProcessBitmapCache();
}
private void ProcessBitmapCache()
{
var image = new Bitmap(Width, Height,
Channels == 1 ? PixelFormat.Format8bppIndexed : PixelFormat.Format24bppRgb);
var lockedDst = image.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.WriteOnly,
image.PixelFormat);
Marshal.Copy(data, 0, lockedDst.Scan0, data.Length);
image.UnlockBits(lockedDst);
if (Channels == 1)
{
image.Palette = _monoPalette;
}
_cachedBMP = image;
}
static ColorPalette GetGrayScalePalette()
{
Bitmap bmp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed);
ColorPalette monoPalette = bmp.Palette;
Color[] entries = monoPalette.Entries;
for (int i = 0; i < 256; i++)
{
entries[i] = Color.FromArgb(i, i, i);
}
return monoPalette;
}
private static ColorPalette _monoPalette = GetGrayScalePalette();
public int Width { get; private set; }
public int Height { get; private set; }
public int Channels { get; private set; }
public IImageLock Lock { get { return new ImageLock(this); } }
private Bitmap _cachedBMP = null;
public Bitmap Bitmap
{
get
{
return (Bitmap)_cachedBMP.Clone();
}
}
public byte[] Data
{
get => data;
set => data = value;
}
public int Step
{
get => step;
set => step = value;
}
class ImageLock : IImageLock
{
ByteImage parent;
GCHandle pinnedArray;
internal ImageLock(ByteImage parent) { this.parent = parent; }
public int Step { get { return parent.step; } }
public IntPtr PixelData
{
get
{
pinnedArray = GCHandle.Alloc(parent.data, GCHandleType.Pinned);
IntPtr pointer = pinnedArray.AddrOfPinnedObject();
return pointer;
}
}
public void Dispose()
{
pinnedArray.Free();
}
}
public void Save(string path)
{
(new Bitmap(new MemoryStream(data))).Save(path,ImageFormat.Bmp);
}
public void Dispose()
{
data = null;
}
}
}

View File

@@ -0,0 +1,18 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace Inspectron.Camera.Image
{
public static class Extensions
{
public static ByteImage ToByteImage(this Bitmap self,int channels)
{
MemoryStream ms = new MemoryStream();
var bmp = self;
bmp.Save(ms, ImageFormat.Bmp);
ByteImage bi = new ByteImage(bmp.Width, bmp.Height, channels, ms.ToArray(), bmp.Width);
return bi;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Drawing.Imaging;
namespace Inspectron.Camera.Image
{
public interface IImage:IDisposable
{
int Width { get; }
int Height { get; }
int Channels { get; }
void Save(string path);
System.Drawing.Bitmap Bitmap { get; }
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Inspectron.Camera.Image
{
public interface IImageLock:IDisposable
{
int Step { get; }
IntPtr PixelData { get; }
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ninject" Version="3.3.4" />
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
<PackageReference Include="Serilog" Version="4.2.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="NoImage\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Threading;
using Inspectron.Camera.Image;
namespace Inspectron.Camera.NoImage
{
public class NoImageCamera:ICamera
{
public NoImageCamera(ICameraProvider provider)
{
Provider = provider;
}
private int _acquisitionDelay = 0;
public string Name { get; }
public void Open()
{
}
public bool IsOpen { get; }
public void Close()
{
}
public bool IsGrabbingContinuous { get; private set; }
public void StartGrabContinuous()
{
IsGrabbingContinuous = true;
}
public void StopGrabContinuous()
{
IsGrabbingContinuous = false;
}
public void LoadParameters(string parametersFile)
{
throw new System.NotImplementedException();
}
public void SaveParameters(string parametersFile)
{
throw new System.NotImplementedException();
}
public void SaveParametersToDevice()
{
throw new System.NotImplementedException();
}
public int AcquisitionDelay
{
get => _acquisitionDelay;
set => _acquisitionDelay = value;
}
public IImage GrabSingle()
{
Thread.Sleep(AcquisitionDelay);
return null;
}
public event ImageGrabbedHandler ImageGrabbed=delegate{};
public ICameraCapabilities Capabilities =>new NoImageCameraCapabilities();
public IEnumerable<ICameraParameter> Parameters =>new List<ICameraParameter>()
{
new CameraParameter("AcquisitionDelay",() => this.AcquisitionDelay,x=>AcquisitionDelay=(int)x)
};
public ICameraProvider Provider { get; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Inspectron.Camera.NoImage
{
public class NoImageCameraCapabilities:ICameraCapabilities
{
public bool CanGrabSingle { get; }=true;
public bool CanGrabContinuous { get; } = true;
public bool CanSaveParametersToFile { get; } = false;
public bool CanSaveParametersToDevice { get; } = false;
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace Inspectron.Camera.NoImage
{
public class NoImageCameraProvider : ICameraProvider
{
public string Name { get; } = "No image camera provider";
public int CamerasToDiscover { get; set; } = 1;
public ReadOnlyCollection<ICamera> Discover()
{
return Enumerable.Range(0, CamerasToDiscover).Select(x => (ICamera)new NoImageCamera(this)).ToList().AsReadOnly();
}
public ReadOnlyCollection<ICamera> Cameras { get; }
}
}

View File

@@ -0,0 +1,82 @@
using Inspectron.Fastbuffer.Interfaces;
using System.Collections.Concurrent;
namespace Inspectron.Fastbuffer.Filesystems;
public class AsyncFilesystem : IFilesystem
{
private readonly DirectFilesystem _directFilesystem = new();
private readonly ConcurrentDictionary<string, byte[]> _buffer = new();
private readonly BlockingCollection<(string path, byte[] data)> _writeQueue = new();
private readonly Thread _backgroundThread;
private readonly object _lock = new();
public AsyncFilesystem()
{
_backgroundThread = new Thread(ProcessWriteQueue) { IsBackground = true };
_backgroundThread.Name= "AsyncFilesystem";
_backgroundThread.Start();
}
public void Write(string path, byte[] data)
{
lock (_lock)
{
_buffer[path] = data;
_writeQueue.Add((path, data));
}
}
public byte[] Read(string path)
{
lock (_lock)
{
if (_buffer.TryGetValue(path, out var data))
{
return data;
}
}
return _directFilesystem.Read(path);
}
public void Delete(string path)
{
lock (_lock)
{
// pats is in buffer, so it's not written to disk yet
if (_buffer.TryRemove(path, out _))
{
return;
}
_directFilesystem.Delete(path);
}
}
private void ProcessWriteQueue()
{
foreach (var (path, data) in _writeQueue.GetConsumingEnumerable())
{
_directFilesystem.Write(path, data);
lock (_lock)
_buffer.TryRemove(path, out _);
}
}
public void Dispose()
{
_writeQueue.CompleteAdding();
_backgroundThread.Join();
}
public void Clear()
{
lock (_lock)
{
while (_writeQueue.TryTake(out _)) { }
}
}
}

View File

@@ -0,0 +1,31 @@
using Inspectron.Fastbuffer.Interfaces;
using Serilog;
namespace Inspectron.Fastbuffer.Filesystems;
public class DirectFilesystem:IFilesystem
{
public void Write(string path, byte[] data)
{
// ensure path
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllBytes(path, data);
}
public byte[] Read(string path)
{
return File.ReadAllBytes(path);
}
public void Delete(string path)
{
Log.Debug($"Deleting {path}");
if (File.Exists(path))
File.Delete(path);
}
public void Dispose()
{
}
}

View File

@@ -0,0 +1,150 @@
using System.Text;
using Inspectron.Fastbuffer.Interfaces;
using Serilog;
namespace Inspectron.Fastbuffer;
public class IndexFile
{
private readonly string _indexFilePath;
private readonly int _bufferSize;
private readonly IFilesystem _filesystem;
private readonly string? _bufferPath;
private const int RecordSize = 512;
private const int IndexSize = 4;
private const int HeaderSize = 4;
public IndexFile(string path, int bufferSize, IFilesystem filesystem)
{
_indexFilePath = path;
_bufferSize = bufferSize;
_filesystem = filesystem;
ValidateFile();
_bufferPath = Path.Combine(Path.GetDirectoryName(_indexFilePath), "..");
}
private void ValidateFile()
{
if (!File.Exists(_indexFilePath))
{
CreateFile();
}
// check file size
var fileInfo = new FileInfo(_indexFilePath);
if (fileInfo.Length != _bufferSize * RecordSize + HeaderSize)
{
UpdateFile();
}
}
private void UpdateFile()
{
using var fileStream = File.Open(_indexFilePath, FileMode.Open);
var actualSize = fileStream.Length;
if (actualSize < _bufferSize+ HeaderSize)
{
fileStream.Seek(0, SeekOrigin.End);
var buffer = new byte[_bufferSize * RecordSize - actualSize];
fileStream.Write(buffer, HeaderSize, buffer.Length);
}
}
private void CreateFile()
{
using var fileStream = File.Create(_indexFilePath);
var buffer = new byte[_bufferSize* RecordSize + HeaderSize];
fileStream.Write(buffer, 0, buffer.Length);
fileStream.Seek(0, SeekOrigin.Begin);
var bytes = BitConverter.GetBytes(-1);
fileStream.Write(bytes, 0, bytes.Length);
}
public int GetCurrentArtifactId()
{
using var fileStream = File.Open(_indexFilePath, FileMode.Open);
using var reader = new BinaryReader(fileStream);
return reader.ReadInt32();
}
public void SetCurrentArtifactId(int id)
{
using var fileStream = File.Open(_indexFilePath, FileMode.Open);
using var writer = new BinaryWriter(fileStream);
writer.Write(id);
}
public string GetArtifactPath(int id)
{
using var fileStream = File.Open(_indexFilePath, FileMode.Open);
fileStream.Seek(id * RecordSize+HeaderSize, SeekOrigin.Begin);
byte[] bytes = new byte[RecordSize];
fileStream.Read(bytes, 0, RecordSize);
return Encoding.UTF8.GetString(bytes).TrimEnd('\0');
}
public int GetNextId()
{
var currentId = GetCurrentArtifactId();
if (currentId + 1 >= _bufferSize)
{
currentId = 0;
}
else
{
currentId++;
}
return currentId;
}
public void DeleteArtifact(int id)
{
var relPath = GetArtifactPath(id);
var filePath = Path.Combine(_bufferPath, relPath);
try
{
_filesystem.Delete(filePath);
}
catch (Exception e)
{
Log.Error("Error deleting artifact {id}: {message}",id,e.Message);
}
}
public void WriteNextArtifact(byte[] data, string fileName)
{
var id = GetNextId();
Log.Debug("Writing artifact {id}",id);
var existingPath = GetArtifactPath(id);
Log.Debug("Existing path: {existingPath}", existingPath);
DeleteArtifact(id);
var filePath = Path.Combine(_bufferPath, fileName);
_filesystem.Write(filePath, data);
SetCurrentArtifactId(id);
using var fileStream = File.Open(_indexFilePath, FileMode.Open);
fileStream.Seek(id * RecordSize+ HeaderSize, SeekOrigin.Begin);
var bytes = Encoding.UTF8.GetBytes(fileName);
fileStream.Write(bytes, 0, bytes.Length);
var padding = new byte[RecordSize - bytes.Length];
fileStream.Write(padding, 0, padding.Length);
}
public byte[] ReadArtifact(int id)
{
using var fileStream = File.Open(_indexFilePath, FileMode.Open);
fileStream.Seek(id * RecordSize+ HeaderSize, SeekOrigin.Begin);
var bytes = new byte[RecordSize];
fileStream.Read(bytes, 0, RecordSize);
var fileName = Encoding.UTF8.GetString(bytes).TrimEnd('\0');
var filePath = Path.Combine(_bufferPath, fileName);
return _filesystem.Read(filePath);
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Interfaces\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="4.2.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.34928.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Inspectron.Fastbuffer", "Inspectron.Fastbuffer.csproj", "{697FB408-0899-423C-8D67-C4ABDBED0C09}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Inspectron.Fastbuffer.Tests", "..\Inspectron.Fastbuffer.Tests\Inspectron.Fastbuffer.Tests.csproj", "{5D1F5AAE-20DC-4C4A-B361-D1D42230BDF7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{697FB408-0899-423C-8D67-C4ABDBED0C09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{697FB408-0899-423C-8D67-C4ABDBED0C09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{697FB408-0899-423C-8D67-C4ABDBED0C09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{697FB408-0899-423C-8D67-C4ABDBED0C09}.Release|Any CPU.Build.0 = Release|Any CPU
{5D1F5AAE-20DC-4C4A-B361-D1D42230BDF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D1F5AAE-20DC-4C4A-B361-D1D42230BDF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D1F5AAE-20DC-4C4A-B361-D1D42230BDF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D1F5AAE-20DC-4C4A-B361-D1D42230BDF7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F3769CF4-894E-4BEE-9366-89E7505F27BF}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,8 @@
namespace Inspectron.Fastbuffer.Interfaces;
public interface IFilesystem:IDisposable
{
public void Write(string path, byte[] data);
public byte[] Read(string path);
public void Delete(string path);
}

View File

@@ -0,0 +1,38 @@
using Inspectron.Fastbuffer.Interfaces;
namespace Inspectron.Fastbuffer;
public class IsolatedRingbuffer: IDisposable
{
private readonly string _path;
private readonly int _bufferSize;
private readonly IFilesystem _filesystem;
private readonly string _indexPath;
private readonly IndexFile _index;
public const string BufferDirectory = ".rb";
public const string IndexFile = "index.idx";
public IsolatedRingbuffer(string path, int bufferSize, IFilesystem filesystem)
{
_path = path;
_bufferSize = bufferSize;
_filesystem = filesystem;
_indexPath = Path.Combine(_path, BufferDirectory,IndexFile );
Directory.CreateDirectory(Path.Combine(_path, BufferDirectory));
_index = new IndexFile(_indexPath, _bufferSize, _filesystem);
}
public void Write(byte[] data, string fileName)
{
_index.WriteNextArtifact(data, fileName);
}
public void Dispose()
{
_filesystem.Dispose();
}
}

View File

@@ -0,0 +1,52 @@
using Inspectron.Fastbuffer.Interfaces;
namespace Inspectron.Fastbuffer
{
public class RingbufferRepository
{
private readonly string _path;
private readonly string _goodIndexPath;
private readonly string _badIndexPath;
private readonly IndexFile _goodIndex;
private readonly IndexFile _badIndex;
public const string BufferDirectory = ".rb";
public const string GoodIndexFile = "good.idx";
public const string BadIndexFile = "bad.idx";
public RingbufferRepository(string path, int goodBufferSize, int badBufferSize, IFilesystem filesystem)
{
_path = path;
_goodIndexPath = Path.Combine(_path, BufferDirectory, GoodIndexFile);
_badIndexPath = Path.Combine(_path, BufferDirectory, BadIndexFile);
Directory.CreateDirectory(Path.Combine(_path,BufferDirectory));
_goodIndex = new IndexFile(_goodIndexPath, goodBufferSize, filesystem);
_badIndex = new IndexFile(_badIndexPath, badBufferSize, filesystem);
var id= _goodIndex.GetCurrentArtifactId();
Console.WriteLine(@"RB size: "+id);
for (int i = 0; i < 3; i++)
{
var artifact = _goodIndex.GetArtifactPath(i);
Console.WriteLine(@"Artifact: "+artifact);
}
}
public IndexFile GoodIndex => _goodIndex;
public IndexFile BadIndex => _badIndex;
public void WriteGood(byte[] data, string fileName)
{
_goodIndex.WriteNextArtifact(data, fileName);
}
public void WriteBad(byte[] data, string fileName)
{
_badIndex.WriteNextArtifact(data, fileName);
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Inspectron.Ringbuffer.Interfaces;
namespace Inspectron.Ringbuffer
{
public class GroupIndex:IIndex
{
private readonly int _groupPathPart;
private readonly int _goodBadPathPart;
public GroupIndex(int groupPathPart,int goodBadPathPart)
{
_groupPathPart = groupPathPart;
_goodBadPathPart = goodBadPathPart;
}
private readonly object _lock = new object();
private readonly Dictionary<string, ProductResults> _groups = new Dictionary<string, ProductResults>();
public void Track(string filePath)
{
var parts = Path.GetDirectoryName(filePath).Split(Path.DirectorySeparatorChar);
string groupKey;
try
{
groupKey = Path.Combine(Enumerable.Range(0, _groupPathPart + 1).Select(x => parts[x]).ToArray());
}
catch (Exception e)
{
Console.WriteLine($"Tracking {filePath}");
Console.WriteLine(e);
return;
}
string badGoodPart;
if (_goodBadPathPart == -1) badGoodPart = RingbufferPath.GOOD_DIR;
else
badGoodPart = parts[_goodBadPathPart];
lock (_lock)
{
if (!_groups.ContainsKey(groupKey))
{
_groups[groupKey] = new ProductResults();
}
if (badGoodPart == RingbufferPath.GOOD_DIR)
{
_groups[groupKey].Good.Insert(0,filePath);
}
else
{
_groups[groupKey].Bad.Insert(0, filePath);
}
}
}
public List<string> GetFilesToDelete(int allowedAmountGood, int allowedAmountBad)
{
lock (_lock)
{
var filesToDelete = new List<string>();
foreach (var p in _groups)
{
var goodToDelete = p.Value.Good.Skip(allowedAmountGood).ToList();
var badToDelete = p.Value.Bad.Skip(allowedAmountBad).ToList();
goodToDelete.ForEach(x => p.Value.Good.Remove(x));
badToDelete.ForEach(x => p.Value.Bad.Remove(x));
filesToDelete.AddRange(goodToDelete);
filesToDelete.AddRange(badToDelete);
}
return filesToDelete;
}
}
private class ProductResults
{
public List<string> Good { get; set; }=new List<string>();
public List<string> Bad { get; set; }=new List<string>();
}
}
}

View File

@@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Inspectron.Ringbuffer.Interfaces;
namespace Inspectron.Ringbuffer
{
/// <summary>
/// Ringbuffer\Product\Good\File
/// </summary>
public class ProductIndex : IIndex
{
private readonly object _lock = new object();
private readonly Dictionary<string, ProductResults> _products = new Dictionary<string, ProductResults>();
public void Track(string filePath)
{
var product = Path.GetDirectoryName(Path.GetDirectoryName(filePath)).Split(Path.DirectorySeparatorChar)
.Last();
var result = Path.GetDirectoryName(filePath).Split(Path.DirectorySeparatorChar).Last();
lock (_lock)
{
if (!_products.ContainsKey(product))
_products[product] = new ProductResults
{
Good = new List<string>(),
Bad = new List<string>()
};
if (result == RingbufferFolder.GOOD_DIR)
{
if (!_products[product].Good.Contains(filePath))
_products[product].Good.Insert(0, filePath);
}
else
{
if (!_products[product].Bad.Contains(filePath))
_products[product].Bad.Insert(0, filePath);
}
}
}
public List<string> GetFilesToDelete(int allowedAmountGood, int allowedAmountBad)
{
lock (_lock)
{
var filesToDelete = new List<string>();
foreach (var p in _products)
{
var goodToDelete = p.Value.Good.Skip(allowedAmountGood).ToList();
var badToDelete = p.Value.Bad.Skip(allowedAmountGood).ToList();
goodToDelete.ForEach(x => p.Value.Good.Remove(x));
badToDelete.ForEach(x => p.Value.Bad.Remove(x));
filesToDelete.AddRange(goodToDelete);
filesToDelete.AddRange(badToDelete);
}
return filesToDelete;
}
}
private class ProductResults
{
public List<string> Good { get; set; }
public List<string> Bad { get; set; }
}
}
}

View File

@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Inspectron.Ringbuffer.Interfaces;
namespace Inspectron.Ringbuffer
{
/// <summary>
/// Ringbuffer\Good\Product\File
/// </summary>
public class RootIndex : IIndex
{
private readonly List<string> _trackedGoodFiles = new List<string>();
private readonly List<string> _trackedBadFiles = new List<string>();
private readonly object _lock = new object();
public void Track(string filePath)
{
lock (_lock)
{
if (IsGood(filePath))
{
if (!_trackedGoodFiles.Contains(filePath))
_trackedGoodFiles.Insert(0, filePath);
}
else
{
if (!_trackedBadFiles.Contains(filePath))
_trackedBadFiles.Insert(0, filePath);
}
}
}
public List<string> GetFilesToDelete(int allowedAmountGood, int allowedAmountBad)
{
lock (_lock)
{
var resGood = _trackedGoodFiles.Skip(allowedAmountGood).ToList();
var resBad = _trackedBadFiles.Skip(allowedAmountBad).ToList();
resGood.ForEach(x => { _trackedGoodFiles.Remove(x); });
resBad.ForEach(x => { _trackedBadFiles.Remove(x); });
return resGood.Concat(resBad).ToList();
}
}
private bool IsGood(string filePath)
{
var folder = Path.GetDirectoryName(Path.GetDirectoryName(filePath)).Split(Path.DirectorySeparatorChar)
.Last();
return folder == RingbufferFolder.GOOD_DIR;
}
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Configurations>Debug;Release;CPU</Configurations>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Factories\**" />
<EmbeddedResource Remove="Factories\**" />
<None Remove="Factories\**" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
namespace Inspectron.Ringbuffer.Interfaces
{
public interface IFileIdentity
{
string WriteTo(string directoryPath);
}
}

View File

@@ -0,0 +1,8 @@
namespace Inspectron.Ringbuffer.Interfaces
{
public interface IFileIdentityFactory<T>
{
IFileIdentity CreateFrom(T image);
IFileVerifier CreateVerifier();
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Inspectron.Ringbuffer.Interfaces
{
public interface IFileVerifier
{
bool IsMainFile(string filePath);
List<string> LinkedFiles(string filePath);
}
}

View File

@@ -0,0 +1,8 @@
namespace Inspectron.Ringbuffer.Interfaces
{
public interface IFolderWritter
{
void WriteGood(IFileIdentity fileIdentity, string product);
void WriteBad(IFileIdentity fileIdentity, string product);
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Inspectron.Ringbuffer.Interfaces
{
public interface IIndex
{
void Track(string filePath);
List<string> GetFilesToDelete(int allowedAmountGood,int allowedAmountBad);
}
}

View File

@@ -0,0 +1,25 @@
namespace Inspectron.Ringbuffer
{
public class RingbufferConfiguration
{
/// <summary>
/// How many images should be present in Good folder
/// After reaching this amount older images will be deleted
/// </summary>
public int MaxAmountOfGoodImages { get; set; }
/// <summary>
/// How many images should be present in Bad folder
/// After reaching this amount older images will be deleted
/// </summary>
public int MaxAmountOfBadImages { get; set; }
/// <summary>
/// If enabled folder structure will be like "Good\MyProduct\001.bmp
/// If disabled folder structure will be like "MyProduct\Good\001.bmp
/// </summary>
public bool ProductFirst { get; set; }
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Inspectron.Ringbuffer.Interfaces;
namespace Inspectron.Ringbuffer
{
[Obsolete]
public class RingbufferFolder:IFolderWritter
{
private string _ringbufferFolder;
private RingbufferConfiguration _configuration;
private IFileVerifier _fileVerifier;
private IIndex _index;
public RingbufferFolder(string ringbufferFolder, RingbufferConfiguration configuration, IFileVerifier fileVerifier)
{
_ringbufferFolder = ringbufferFolder;
_configuration = configuration;
_fileVerifier = fileVerifier;
EnsureDirectory(_ringbufferFolder);
InitIndex();
var th1 = new Thread(RemoveLoop);
th1.Priority = ThreadPriority.Normal;
th1.Start();
var th2 = new Thread(SaveLoop);
th2.Priority = ThreadPriority.Normal;
th2.Start();
}
private void RemoveLoop()
{
while (true)
{
var filesToDelete=_index.GetFilesToDelete(_configuration.MaxAmountOfGoodImages, _configuration.MaxAmountOfBadImages);
filesToDelete.ForEach(x=>
{
DeleteFile(x);
_fileVerifier.LinkedFiles(x).ForEach(DeleteFile);
});
Thread.Sleep(1000);
}
}
ConcurrentQueue<SaveCandidate> _saveQueue = new ConcurrentQueue<SaveCandidate>();
object _lock=new object();
private void EnsureDirectory(string directory)
{
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
}
private void SaveLoop()
{
while (true)
{
if (_saveQueue.TryDequeue(out var candidate))
{
lock (_lock)
{
EnsureDirectory(candidate.TargetDirectory);
var written=candidate.FileIdentity.WriteTo(candidate.TargetDirectory);
_index.Track(written);
}
}
else
{
Thread.Sleep(1);
}
}
}
private void InitIndex()
{
if (_configuration.ProductFirst)
{
_index=new ProductIndex();
}
else
{
_index=new RootIndex();
}
IndexFiles();
}
public void DeleteFile(string fileToDelete)
{
var fi = new System.IO.FileInfo(fileToDelete);
if (fi.Exists)
{
lock (_lock)
{
fi.Delete();
}
fi.Refresh();
while (fi.Exists)
{
System.Threading.Thread.Sleep(10);
fi.Refresh();
}
}
}
private void IndexFiles()
{
var allFiles=Directory.GetFiles(_ringbufferFolder, "*.*", SearchOption.AllDirectories);
var verifiedFiles = allFiles.Where(x => _fileVerifier.IsMainFile(x)).ToList();
var orderedFiles = verifiedFiles.OrderByDescending(x => new FileInfo(x).CreationTimeUtc).ToList();
orderedFiles.ForEach(_index.Track);
}
public const string GOOD_DIR = "Good";
public const string BAD_DIR = "Bad";
public void WriteGood(IFileIdentity fileIdentity,string product)
{
var path = _configuration.ProductFirst
? Path.Combine(_ringbufferFolder, product, GOOD_DIR)
: Path.Combine(_ringbufferFolder, GOOD_DIR, product);
_saveQueue.Enqueue(new SaveCandidate(fileIdentity, path));
}
public void WriteBad(IFileIdentity fileIdentity, string product)
{
var path = _configuration.ProductFirst
? Path.Combine(_ringbufferFolder, product, BAD_DIR)
: Path.Combine(_ringbufferFolder, BAD_DIR, product);
_saveQueue.Enqueue(new SaveCandidate(fileIdentity, path));
}
}
}

View File

@@ -0,0 +1,182 @@
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using Inspectron.Ringbuffer.Interfaces;
namespace Inspectron.Ringbuffer
{
public class RingbufferPath : IFolderWritter,IDisposable
{
private readonly string _pathFormat;
private readonly RingbufferPathConfiguration _configuration;
private readonly IFileVerifier _fileVerifier;
public const string PRODUCT = "$PRODUCT$";
public const string GOODBAD = "$GOODBAD$";
public const string DATE = "$date$";
public const string MONTH = "$month$";
public const string YEAR = "$year$";
private IIndex _index;
ConcurrentQueue<SaveCandidate> _saveQueue = new ConcurrentQueue<SaveCandidate>();
object _lock = new object();
private string _ringbufferFolder;
private int _productPartId;
private int _goodBadPartId;
private int _subPartId;
public RingbufferPath(RingbufferPathConfiguration configuration, IFileVerifier fileVerifier)
{
_pathFormat = configuration.PathFormat;
_configuration = configuration;
_fileVerifier = fileVerifier;
var pathFormat = configuration.PathFormat;
var pathParts = Path.Combine(configuration.RootDirectory,pathFormat).Split(new char[]{ Path.DirectorySeparatorChar },StringSplitOptions.RemoveEmptyEntries);
_ringbufferFolder = configuration.RootDirectory;
_productPartId = -1;
_goodBadPartId = -1;
for (int i = 1; i < pathParts.Length; i++)
{
if (pathParts[i] == PRODUCT) _productPartId = i;
if (pathParts[i] == GOODBAD) _goodBadPartId = i;
}
//if(_productPartId==-1||_goodBadPartId==-1)throw new Exception("one of the markers is missing");
EnsureDirectory(_ringbufferFolder);
InitIndex();
var th1 = new Thread(RemoveLoop);
th1.IsBackground = true;
th1.Priority = ThreadPriority.Normal;
th1.Start();
var th2 = new Thread(SaveLoop);
th2.IsBackground = true;
th2.Priority = ThreadPriority.Normal;
th2.Start();
}
private void InitIndex()
{
_index = new GroupIndex(_configuration.GroupPartId,_goodBadPartId);
IndexFiles();
}
private void IndexFiles()
{
var allFiles = Directory.GetFiles(_ringbufferFolder, "*.*", SearchOption.AllDirectories);
var verifiedFiles = allFiles.Where(x => _fileVerifier.IsMainFile(x)).ToList();
var orderedFiles = verifiedFiles.OrderByDescending(x => new FileInfo(x).CreationTimeUtc).ToList();
orderedFiles.ForEach(_index.Track);
}
private void RemoveLoop()
{
while (!_stop)
{
var filesToDelete = _index.GetFilesToDelete(_configuration.MaxAmountOfGoodImages, _configuration.MaxAmountOfBadImages);
filesToDelete.ForEach(x =>
{
DeleteFile(x);
_fileVerifier.LinkedFiles(x).ForEach(DeleteFile);
});
Thread.Sleep(1000);
}
}
public void DeleteFile(string fileToDelete)
{
var fi = new System.IO.FileInfo(fileToDelete);
if (fi.Exists)
{
lock (_lock)
{
fi.Delete();
}
fi.Refresh();
while (fi.Exists)
{
System.Threading.Thread.Sleep(10);
fi.Refresh();
}
}
}
private bool _stop = false;
private void SaveLoop()
{
while (!_stop)
{
if (_saveQueue.TryDequeue(out var candidate))
{
lock (_lock)
{
EnsureDirectory(candidate.TargetDirectory);
var written = candidate.FileIdentity.WriteTo(candidate.TargetDirectory);
_index.Track(written);
}
}
else
{
Thread.Sleep(1);
}
}
}
private void EnsureDirectory(string directory)
{
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
}
public static string GOOD_DIR = "Good";
public static string BAD_DIR = "Bad";
string TransformPath(string originalPath,bool isGood,string product)
{
var time = DateTime.Now;
var res= originalPath.Replace(PRODUCT, product)
.Replace(GOODBAD, isGood ? GOOD_DIR : BAD_DIR)
.Replace(MONTH,time.ToString("MMMM"))
.Replace(YEAR,time.Year.ToString())
.Replace(DATE,time.ToString("dd.MM.yyyy"));
return Path.Combine(_configuration.RootDirectory, res);
}
public void WriteGood(IFileIdentity fileIdentity, string product)
{
var path = TransformPath(_configuration.PathFormat, true, product);
_saveQueue.Enqueue(new SaveCandidate(fileIdentity, path));
}
public void WriteBad(IFileIdentity fileIdentity, string product)
{
var path = TransformPath(_configuration.PathFormat, false, product);
_saveQueue.Enqueue(new SaveCandidate(fileIdentity, path));
}
public void Dispose()
{
_stop = true;
}
}
}

View File

@@ -0,0 +1,35 @@
namespace Inspectron.Ringbuffer
{
public class RingbufferPathConfiguration
{
/// <summary>
/// How many images should be present in Good folder
/// After reaching this amount older images will be deleted
/// </summary>
public int MaxAmountOfGoodImages { get; set; }
/// <summary>
/// How many images should be present in Bad folder
/// After reaching this amount older images will be deleted
/// </summary>
public int MaxAmountOfBadImages { get; set; }
/// <summary>
/// Where to store images
/// Supports $PRODUCT$ mark to put product name into its place
/// Supports $GOODBAD$ mark to put product quality into its place
/// </summary>
public string PathFormat { get; set; }
/// <summary>
/// Specifies which part of the path should be checked for maximum files
/// </summary>
public int GroupPartId { get; set; }
/// <summary>
/// Ringbuffer's root directory
/// </summary>
public string RootDirectory { get; set; } = "./";
}
}

View File

@@ -0,0 +1,16 @@
using Inspectron.Ringbuffer.Interfaces;
namespace Inspectron.Ringbuffer
{
public class SaveCandidate
{
public SaveCandidate(IFileIdentity fileIdentity, string targetDirectory)
{
FileIdentity = fileIdentity;
TargetDirectory = targetDirectory;
}
public IFileIdentity FileIdentity { get; set; }
public string TargetDirectory { get; set; }
}
}

View File

@@ -0,0 +1,187 @@
using System;
using System.Reflection;
using System.Windows.Forms;
using Inspectron.Settings.Attributes;
using Microsoft.WindowsAPICodePack.Dialogs;
namespace Inspectron.Settings.Windows.Configuration;
public class DefaultControlFactory : IControlFactory
{
public virtual Control CreateControl(string name, string description, PropertyInfo propertyInfo,
object initialValue, Action<object> valueChangedCallback, OptionsWindow optionsWindow)
{
var type = propertyInfo.PropertyType;
// Check for SettingDescriptionAttribute
var settingDescriptionAttribute = propertyInfo.GetCustomAttribute<SettingDescriptionAttribute>();
string settingDescription = settingDescriptionAttribute?.Description;
// Check for SettingPreviewAttribute
var previewAttribute = propertyInfo.GetCustomAttribute<SettingPreviewAttribute>();
Label previewLabel = null;
Func<object, string> getPreview = null;
if (previewAttribute != null)
{
var method = previewAttribute.PreviewClass.GetMethod(previewAttribute.PreviewFunction, BindingFlags.Public | BindingFlags.Static);
getPreview = value => (string)method.Invoke(null, new[] { value });
previewLabel = new Label
{
AutoSize = true,
Padding = new Padding(15, 0, 0, 0),
Font = new System.Drawing.Font("Segoe UI", 10, System.Drawing.FontStyle.Italic),
Text = getPreview(initialValue)
};
}
if (type == typeof(string) && name.EndsWith("Path", StringComparison.OrdinalIgnoreCase))
{
return CreatePathControl(name, description, initialValue, valueChangedCallback, optionsWindow, settingDescription, previewLabel, getPreview);
}
else if (type == typeof(string))
{
return CreateStringControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
}
else if (type == typeof(int))
{
return CreateIntControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
}
else if (type == typeof(bool))
{
return CreateBoolControl(description, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
}
else if (type.IsEnum)
{
return CreateEnumControl(description, type, initialValue, valueChangedCallback, settingDescription, previewLabel, getPreview);
}
else
{
return new Label { Text = $"Unsupported type: {type.Name}", AutoSize = true };
}
}
private Control CreatePathControl(string name, string description, object initialValue, Action<object> valueChangedCallback,
OptionsWindow optionsWindow, string settingDescription, Label previewLabel, Func<object, string> getPreview)
{
var textBox = new TextBox { Text = initialValue as string, Width = 400 };
var label = new Label { Text = description, AutoSize = true, Padding = new Padding(0, 5, 0, 0) };
var browseButton = new Button { Text = "Browse", AutoSize = true };
browseButton.Click += (s, e) =>
{
using (var dialog = new CommonOpenFileDialog { IsFolderPicker = true })
{
if (dialog.ShowDialog(optionsWindow.Handle) == CommonFileDialogResult.Ok)
{
textBox.Text = dialog.FileName;
valueChangedCallback(dialog.FileName);
if (previewLabel != null && getPreview != null)
previewLabel.Text = getPreview(dialog.FileName);
}
}
};
textBox.TextChanged += (s, e) =>
{
valueChangedCallback(textBox.Text);
if (previewLabel != null && getPreview != null)
previewLabel.Text = getPreview(textBox.Text);
};
return CreateOuterPanel(settingDescription, previewLabel, label, textBox, browseButton);
}
private Control CreateStringControl(string description, object initialValue, Action<object> valueChangedCallback,
string settingDescription, Label previewLabel, Func<object, string> getPreview)
{
var textBox = new TextBox { Text = initialValue as string, Width = 400 };
var label = new Label { Text = description, AutoSize = true, Padding = new Padding(0, 5, 0, 0) };
textBox.TextChanged += (s, e) =>
{
valueChangedCallback(textBox.Text);
if (previewLabel != null && getPreview != null)
previewLabel.Text = getPreview(textBox.Text);
};
return CreateOuterPanel(settingDescription, previewLabel, label, textBox);
}
private Control CreateIntControl(string description, object initialValue, Action<object> valueChangedCallback,
string settingDescription, Label previewLabel, Func<object, string> getPreview)
{
var label = new Label { Text = description, AutoSize = true, Padding = new Padding(0, 5, 0, 0) };
var numericUpDown = new NumericUpDown { Minimum = 0, Maximum = 99999999999, Value = Convert.ToDecimal(initialValue), Width = 200 };
numericUpDown.ValueChanged += (s, e) =>
{
valueChangedCallback(Convert.ToInt32(numericUpDown.Value));
if (previewLabel != null && getPreview != null)
previewLabel.Text = getPreview(Convert.ToInt32(numericUpDown.Value));
};
return CreateOuterPanel(settingDescription, previewLabel, label, numericUpDown);
}
private Control CreateBoolControl(string description, object initialValue, Action<object> valueChangedCallback,
string settingDescription, Label previewLabel, Func<object, string> getPreview)
{
var checkBox = new CheckBox { Text = description, Checked = (bool)initialValue, AutoSize = true };
checkBox.CheckedChanged += (s, e) =>
{
valueChangedCallback(checkBox.Checked);
if (previewLabel != null && getPreview != null)
previewLabel.Text = getPreview(checkBox.Checked);
};
return CreateOuterPanel(settingDescription, previewLabel, checkBox);
}
private Control CreateEnumControl(string description, Type enumType, object initialValue, Action<object> valueChangedCallback,
string settingDescription, Label previewLabel, Func<object, string> getPreview)
{
var label = new Label { Text = description, AutoSize = true, Padding = new Padding(0, 5, 0, 0) };
var comboBox = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 200 };
// Populate ComboBox with enum values
foreach (var value in Enum.GetValues(enumType))
{
comboBox.Items.Add(value);
}
// Set initial value
comboBox.SelectedItem = initialValue;
comboBox.SelectedIndexChanged += (s, e) =>
{
valueChangedCallback(comboBox.SelectedItem);
if (previewLabel != null && getPreview != null)
previewLabel.Text = getPreview(comboBox.SelectedItem);
};
return CreateOuterPanel(settingDescription, previewLabel, label, comboBox);
}
private Control CreateOuterPanel(string settingDescription, Label previewLabel, params Control[] controls)
{
var outerPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.TopDown, AutoSize = true, Margin = new Padding(0, 25, 0, 0) };
if (!string.IsNullOrEmpty(settingDescription))
{
var descriptionLabel = new Label { Text = settingDescription, AutoSize = true, Padding = new Padding(0, 5, 0, 0) };
outerPanel.Controls.Add(descriptionLabel);
}
foreach (var control in controls)
{
outerPanel.Controls.Add(control);
}
if (previewLabel != null)
outerPanel.Controls.Add(previewLabel);
return outerPanel;
}
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Reflection;
using System.Windows.Forms;
namespace Inspectron.Settings.Windows.Configuration;
public interface IControlFactory
{
Control CreateControl(string name, string description, PropertyInfo propertyInfo, object initialValue,
Action<object> valueChangedCallback, OptionsWindow optionsWindow);
}

View File

@@ -0,0 +1,12 @@
using System.Reflection;
namespace Inspectron.Settings.Windows.Configuration;
internal class OptionSetting
{
public object Owner { get; set; }
public PropertyInfo Property { get; set; }
public string Description { get; set; }
public string TreePath { get; set; }
public object Value { get; set; } // Temporary value until OK is pressed
}

View File

@@ -0,0 +1,69 @@
using System.Drawing;
using System.Windows.Forms;
namespace Inspectron.Settings.Windows.Configuration;
public partial class OptionsWindow
{
private TreeView treeViewCategories;
private FlowLayoutPanel flowLayoutPanelSettings;
private Button buttonOK;
private Button buttonCancel;
private void InitializeComponent()
{
treeViewCategories = new TreeView();
flowLayoutPanelSettings = new FlowLayoutPanel();
buttonOK = new Button();
buttonCancel = new Button();
SuspendLayout();
//
// treeViewCategories
//
treeViewCategories.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left;
treeViewCategories.Location = new Point(12, 12);
treeViewCategories.Name = "treeViewCategories";
treeViewCategories.Size = new Size(200, 582);
treeViewCategories.TabIndex = 0;
//
// flowLayoutPanelSettings
//
flowLayoutPanelSettings.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
flowLayoutPanelSettings.AutoScroll = true;
flowLayoutPanelSettings.FlowDirection = FlowDirection.TopDown;
flowLayoutPanelSettings.Location = new Point(220, 12);
flowLayoutPanelSettings.Name = "flowLayoutPanelSettings";
flowLayoutPanelSettings.Size = new Size(852, 582);
flowLayoutPanelSettings.TabIndex = 1;
flowLayoutPanelSettings.WrapContents = false;
//
// buttonOK
//
buttonOK.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
buttonOK.Location = new Point(912, 612);
buttonOK.Name = "buttonOK";
buttonOK.Size = new Size(75, 23);
buttonOK.TabIndex = 2;
buttonOK.Text = "OK";
//
// buttonCancel
//
buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
buttonCancel.Location = new Point(1002, 612);
buttonCancel.Name = "buttonCancel";
buttonCancel.Size = new Size(75, 23);
buttonCancel.TabIndex = 3;
buttonCancel.Text = "Cancel";
//
// OptionsWindow
//
ClientSize = new Size(1089, 649);
Controls.Add(treeViewCategories);
Controls.Add(flowLayoutPanelSettings);
Controls.Add(buttonOK);
Controls.Add(buttonCancel);
Name = "OptionsWindow";
Text = "Options";
ResumeLayout(false);
}
}

View File

@@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
namespace Inspectron.Settings.Windows.Configuration;
public partial class OptionsWindow : Form
{
private Dictionary<string, List<OptionSetting>> settingsByCategory;
private IControlFactory controlFactory;
public OptionsWindow(IControlFactory controlFactory)
{
this.controlFactory = controlFactory;
settingsByCategory = new Dictionary<string, List<OptionSetting>>();
InitializeComponent();
this.treeViewCategories.AfterSelect += new TreeViewEventHandler(this.treeViewCategories_AfterSelect);
this.buttonOK.Click += new EventHandler(this.buttonOK_Click);
this.buttonCancel.Click += new EventHandler(this.buttonCancel_Click);
}
public void LoadFromSettings(InspectronSettings inspectronSettings)
{
// UserSettings is a Tree<object> with folders (string) and UserSettingsInfo nodes.
// Traverse the tree to find all UserSettingsInfo nodes and their PropertyDescriptors.
void Traverse(object nodeObj, string path)
{
var node = nodeObj as dynamic; // Tree<object>
object value = node.Value;
if (value is string folderName)
{
// Folder node, append to path and traverse children
string newPath = string.IsNullOrEmpty(path) ? folderName : $"{path}/{folderName}";
foreach (var child in node.Children)
Traverse(child, newPath);
}
else if (value is Inspectron.Settings.InspectronSettings.UserSettingsInfo info)
{
// Leaf node: user settings group
string groupName = info.Name;
string groupPath = string.IsNullOrEmpty(path) ? groupName : $"{path}/{groupName}";
foreach (var pd in info.Settings)
{
// Try to get owner from BoundPropertyDescriptor, else skip
object owner = null;
string description = pd.Description;
if (pd is Inspectron.Settings.BoundPropertyDescriptor bpd)
{
owner = bpd.Owner;
if (string.IsNullOrEmpty(description))
description = bpd.DisplayName;
}
else
{
// If not a BoundPropertyDescriptor, cannot get owner, skip
continue;
}
RegisterUserSetting(owner, bpd.PropertyInfo.Name, groupPath, description);
}
}
}
Traverse(inspectronSettings.UserSettings.Root, "");
}
public void RegisterUserSetting(object owner, string propertyName, string treePath, string description = null)
{
// Get the PropertyInfo from owner and propertyName
var property = owner.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
if (property == null)
throw new ArgumentException($"Property '{propertyName}' not found on owner type.");
if (string.IsNullOrEmpty(description))
description = propertyName;
// Create an OptionSetting instance
var setting = new OptionSetting
{
Owner = owner,
Property = property,
Description = description,
TreePath = treePath,
Value = property.GetValue(owner)
};
// Add to settingsByCategory
if (!settingsByCategory.TryGetValue(treePath, out var settingsList))
{
settingsList = new List<OptionSetting>();
settingsByCategory[treePath] = settingsList;
}
settingsList.Add(setting);
// Add nodes to treeViewCategories
AddTreeNodes(treeViewCategories, treePath);
}
private void AddTreeNodes(TreeView treeView, string treePath)
{
var parts = treePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
TreeNodeCollection nodes = treeView.Nodes;
foreach (var part in parts)
{
var node = Enumerable.Cast<TreeNode>(nodes).FirstOrDefault(n => n.Text == part);
if (node == null)
{
node = new TreeNode(part);
nodes.Add(node);
}
nodes = node.Nodes;
}
}
private void treeViewCategories_AfterSelect(object sender, TreeViewEventArgs e)
{
// Display the settings for the selected category
string selectedPath = GetFullPath(e.Node);
DisplaySettings(selectedPath);
}
private string GetFullPath(TreeNode node)
{
if (node.Parent == null)
return node.Text;
else
return GetFullPath(node.Parent) + "/" + node.Text;
}
private void DisplaySettings(string treePath)
{
flowLayoutPanelSettings.Controls.Clear();
if (settingsByCategory.TryGetValue(treePath, out var settingsList))
{
foreach (var setting in settingsList)
{
var control = controlFactory.CreateControl(
setting.Property.Name,
setting.Description,
setting.Property,
setting.Value,
(newValue) => setting.Value = newValue,
this);
flowLayoutPanelSettings.Controls.Add(control);
}
}
}
private void buttonOK_Click(object sender, EventArgs e)
{
// Update the owner properties with the values
foreach (var settingsList in settingsByCategory.Values)
{
foreach (var setting in settingsList)
{
setting.Property.SetValue(setting.Owner, setting.Value);
}
}
this.DialogResult = DialogResult.OK;
this.Close();
}
private void buttonCancel_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Inspectron.Settings\Inspectron.Settings.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using System.Linq;
using System.Reflection;
namespace Inspectron.Settings.Windows
{
public static class PropHelper
{
public static PropertyInfo GetPrp(object obj,string name)
{
return obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
.First(x => x.Name == name);
}
public static string SplitCamelCase(string input)
{
return System.Text.RegularExpressions.Regex.Replace(input, "([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim();
}
}
}

View File

@@ -0,0 +1,61 @@
namespace Inspectron.Settings.WindowsWizard.Controls
{
partial class CheckEditor
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.checkBox1 = new System.Windows.Forms.CheckBox();
this.SuspendLayout();
//
// checkBox1
//
this.checkBox1.AutoSize = true;
this.checkBox1.Location = new System.Drawing.Point(3, 3);
this.checkBox1.Name = "checkBox1";
this.checkBox1.Size = new System.Drawing.Size(98, 21);
this.checkBox1.TabIndex = 0;
this.checkBox1.Text = "checkBox1";
this.checkBox1.UseVisualStyleBackColor = true;
this.checkBox1.CheckedChanged += new System.EventHandler(this.checkBox1_CheckedChanged);
//
// CheckEditor
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.checkBox1);
this.Name = "CheckEditor";
this.Size = new System.Drawing.Size(141, 29);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.CheckBox checkBox1;
}
}

View File

@@ -0,0 +1,28 @@
using System.Reflection;
using Inspectron.Settings.WindowsWizard.Interfaces;
namespace Inspectron.Settings.WindowsWizard.Controls
{
public partial class CheckEditor : UserControl, ISetupItem
{
private PropertyInfo _prp;
private object _obj;
public CheckEditor()
{
InitializeComponent();
}
public void SetProperty(object obj, string propertyName)
{
checkBox1.Text = PropHelper.SplitCamelCase(propertyName);
_prp = PropHelper.GetPrp(obj, propertyName);
_obj = obj;
checkBox1.Checked = (bool)_prp.GetValue(obj);
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
_prp.SetValue(_obj, checkBox1.Checked);
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,46 @@
namespace Inspectron.Settings.WindowsWizard.Controls
{
partial class EnumEditor
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.SuspendLayout();
//
// EnumEditor
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.Name = "EnumEditor";
this.Size = new System.Drawing.Size(112, 51);
this.ResumeLayout(false);
}
#endregion
}
}

View File

@@ -0,0 +1,50 @@
using System.Reflection;
using Inspectron.Settings.WindowsWizard.Interfaces;
namespace Inspectron.Settings.WindowsWizard.Controls
{
public partial class EnumEditor : UserControl,ISetupItem
{
private PropertyInfo _prp;
private object _obj;
public EnumEditor()
{
InitializeComponent();
}
public void SetProperty(object obj, string propertyName)
{
GroupBox gb = new GroupBox();
gb.Text = PropHelper.SplitCamelCase(propertyName);
this.Controls.Add(gb);
gb.Dock = DockStyle.Fill;
FlowLayoutPanel flp = new FlowLayoutPanel();
gb.Controls.Add(flp);
flp.Dock = DockStyle.Fill;
_prp = PropHelper.GetPrp(obj, propertyName);
_obj = obj;
var names = Enum.GetNames(_prp.PropertyType);
var values = Enum.GetValues(_prp.PropertyType);
var curValue = _prp.GetValue(_obj);
int cnt = 0;
foreach (string name in names)
{
var rb = new RadioButton() {Text = name};
var cnt1 = cnt;
rb.CheckedChanged += (sender, args) =>
{
if (rb.Checked) _prp.SetValue(_obj, values.GetValue(cnt1));
};
flp.Controls.Add(rb);
if ((int)curValue == (int)values.GetValue(cnt1))
{
rb.Checked = true;
}
cnt++;
}
this.Controls.Add(gb);
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,73 @@
namespace Inspectron.Settings.WindowsWizard.Controls
{
partial class NumberEditor
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.numericUpDown1 = new System.Windows.Forms.NumericUpDown();
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(2, 5);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 0;
this.label1.Text = "label1";
//
// numericUpDown1
//
this.numericUpDown1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.numericUpDown1.Location = new System.Drawing.Point(109, 3);
this.numericUpDown1.Name = "numericUpDown1";
this.numericUpDown1.Size = new System.Drawing.Size(147, 20);
this.numericUpDown1.TabIndex = 1;
this.numericUpDown1.ValueChanged += new System.EventHandler(this.numericUpDown1_ValueChanged);
//
// NumberEditor
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.numericUpDown1);
this.Controls.Add(this.label1);
this.Name = "NumberEditor";
this.Size = new System.Drawing.Size(259, 25);
((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.NumericUpDown numericUpDown1;
}
}

View File

@@ -0,0 +1,56 @@
using System.Reflection;
using Inspectron.Settings.WindowsWizard.Interfaces;
namespace Inspectron.Settings.WindowsWizard.Controls
{
public partial class NumberEditor : UserControl, ISetupItem
{
private PropertyInfo _prp;
private object _obj;
private int _number;
public NumberEditor()
{
InitializeComponent();
}
public int Number
{
get { return _number; }
set
{
if (_obj == null) return;
_number = value;
_prp.SetValue(_obj, value);
}
}
public int Min
{
get => (int)numericUpDown1.Minimum;
set => numericUpDown1.Minimum = value;
}
public int Max
{
get => (int) numericUpDown1.Maximum;
set => numericUpDown1.Maximum = value;
}
public void SetProperty(object obj, string propertyName)
{
label1.Text = PropHelper.SplitCamelCase(propertyName);
_prp = PropHelper.GetPrp(obj, propertyName);
_obj = obj;
numericUpDown1.Value =_number = ((int)_prp.GetValue(obj)<Min?Min: (int)_prp.GetValue(obj));
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
_number = (int)numericUpDown1.Value;
if(_obj==null)return;
_prp.SetValue(_obj, _number);
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,90 @@
namespace Inspectron.Settings.WindowsWizard.Controls
{
partial class PathEditor
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(150, 2);
this.textBox1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.textBox1.Name = "textBox1";
this.textBox1.ReadOnly = true;
this.textBox1.Size = new System.Drawing.Size(274, 20);
this.textBox1.TabIndex = 1;
//
// button1
//
this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.button1.Location = new System.Drawing.Point(428, 2);
this.button1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(29, 19);
this.button1.TabIndex = 2;
this.button1.Text = "...";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(2, 5);
this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 3;
this.label1.Text = "label1";
//
// PathEditor
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.label1);
this.Controls.Add(this.button1);
this.Controls.Add(this.textBox1);
this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.Name = "PathEditor";
this.Size = new System.Drawing.Size(460, 25);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label label1;
}
}

View File

@@ -0,0 +1,57 @@
using System.Reflection;
using Inspectron.Settings.WindowsWizard.Interfaces;
using Microsoft.WindowsAPICodePack.Dialogs;
namespace Inspectron.Settings.WindowsWizard.Controls
{
public partial class PathEditor : UserControl,ISetupItem
{
private PropertyInfo _prp;
private object _obj;
public PathEditor()
{
InitializeComponent();
}
public bool FolderPick { get; set; }
public string SelectedFolderPath
{
get => textBox1.Text;
set
{
if (_obj == null) return;
textBox1.Text = value;
_prp.SetValue(_obj,value);
}
}
private void button1_Click(object sender, EventArgs e)
{
var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = FolderPick;
dialog.InitialDirectory = SelectedFolderPath;
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
SelectedFolderPath = dialog.FileName;
FileChanged();
}
}
public event Action FileChanged = delegate { };
public void SetProperty(object obj, string propertyName)
{
label1.Text = PropHelper.SplitCamelCase(propertyName);
_prp = PropHelper.GetPrp(obj, propertyName);
_obj = obj;
SelectedFolderPath =(string)_prp.GetValue(obj);
if(SelectedFolderPath!=null) FileChanged();
}
public void HidePath()
{
textBox1.Visible = false;
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,72 @@
namespace Inspectron.Settings.WindowsWizard.Controls
{
partial class StringEditor
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(3, 6);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 0;
this.label1.Text = "label1";
//
// textBox1
//
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(150, 3);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(176, 20);
this.textBox1.TabIndex = 1;
this.textBox1.TextChanged += new System.EventHandler(this.textBox1_TextChanged);
//
// StringEditor
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.textBox1);
this.Controls.Add(this.label1);
this.Name = "StringEditor";
this.Size = new System.Drawing.Size(329, 28);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox textBox1;
}
}

View File

@@ -0,0 +1,41 @@
using System.Reflection;
using Inspectron.Settings.WindowsWizard.Interfaces;
namespace Inspectron.Settings.WindowsWizard.Controls
{
public partial class StringEditor : UserControl, ISetupItem
{
private PropertyInfo _prp;
private object _obj;
private bool _password;
public StringEditor()
{
InitializeComponent();
}
public bool Password
{
get { return _password; }
set
{
if (value) textBox1.PasswordChar = '*';
_password = value;
}
}
public void SetProperty(object obj, string propertyName)
{
_prp = PropHelper.GetPrp(obj, propertyName);
_obj = obj;
textBox1.Text = (string) _prp.GetValue(obj);
label1.Text = PropHelper.SplitCamelCase(propertyName);
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (_obj == null) return;
_prp.SetValue(_obj, textBox1.Text);
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,9 @@
namespace Inspectron.Settings.WindowsWizard.Enums
{
public enum ESetupResult
{
Cancel,
Back,
Next
}
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Configurations>Debug;Release;CPU</Configurations>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="WindowsAPICodePack-Shell" Version="1.1.1" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resource1.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource1.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resource1.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource1.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
namespace Inspectron.Settings.WindowsWizard.Interfaces
{
public interface ISetupItem
{
void SetProperty(object obj,string propertyName);
}
}

View File

@@ -0,0 +1,17 @@
using System.Reflection;
namespace Inspectron.Settings.WindowsWizard
{
public static class PropHelper
{
public static PropertyInfo GetPrp(object obj,string name)
{
return obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
.First(x => x.Name == name);
}
public static string SplitCamelCase(string input)
{
return System.Text.RegularExpressions.Regex.Replace(input, "([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim();
}
}
}

View File

@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Inspectron.Settings.WindowsWizard {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resource1 {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resource1() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Inspectron.Settings.WindowsWizard.Resource1", typeof(Resource1).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap setup {
get {
object obj = ResourceManager.GetObject("setup", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="setup" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\setup.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -0,0 +1,139 @@
namespace Inspectron.Settings.WindowsWizard
{
partial class SetupPage
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.panel1 = new System.Windows.Forms.Panel();
this.checkBox1 = new System.Windows.Forms.CheckBox();
this.button1 = new System.Windows.Forms.Button();
this.panel2 = new System.Windows.Forms.Panel();
this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.tabControl1 = new System.Windows.Forms.TabControl();
this.panel1.SuspendLayout();
this.panel2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
this.SuspendLayout();
//
// panel1
//
this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panel1.Controls.Add(this.checkBox1);
this.panel1.Controls.Add(this.button1);
this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
this.panel1.Location = new System.Drawing.Point(0, 433);
this.panel1.Margin = new System.Windows.Forms.Padding(2);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(814, 34);
this.panel1.TabIndex = 0;
//
// checkBox1
//
this.checkBox1.AutoSize = true;
this.checkBox1.Location = new System.Drawing.Point(4, 5);
this.checkBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.checkBox1.Name = "checkBox1";
this.checkBox1.Size = new System.Drawing.Size(93, 19);
this.checkBox1.TabIndex = 1;
this.checkBox1.Text = "Expert mode";
this.checkBox1.UseVisualStyleBackColor = true;
this.checkBox1.CheckedChanged += new System.EventHandler(this.checkBox1_CheckedChanged);
//
// button1
//
this.button1.Location = new System.Drawing.Point(737, 2);
this.button1.Margin = new System.Windows.Forms.Padding(2);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(65, 22);
this.button1.TabIndex = 0;
this.button1.Text = "Done";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// panel2
//
this.panel2.Controls.Add(this.pictureBox1);
this.panel2.Dock = System.Windows.Forms.DockStyle.Left;
this.panel2.Location = new System.Drawing.Point(0, 0);
this.panel2.Margin = new System.Windows.Forms.Padding(2);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(175, 433);
this.panel2.TabIndex = 1;
//
// pictureBox1
//
this.pictureBox1.Dock = System.Windows.Forms.DockStyle.Fill;
this.pictureBox1.Image = global::Inspectron.Settings.WindowsWizard.Resource1.setup;
this.pictureBox1.Location = new System.Drawing.Point(0, 0);
this.pictureBox1.Margin = new System.Windows.Forms.Padding(2);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(175, 433);
this.pictureBox1.TabIndex = 0;
this.pictureBox1.TabStop = false;
//
// tabControl1
//
this.tabControl1.Location = new System.Drawing.Point(183, 14);
this.tabControl1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(617, 413);
this.tabControl1.TabIndex = 3;
//
// SetupPage
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(814, 467);
this.ControlBox = false;
this.Controls.Add(this.tabControl1);
this.Controls.Add(this.panel2);
this.Controls.Add(this.panel1);
this.Margin = new System.Windows.Forms.Padding(2);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "SetupPage";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Setup";
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Panel panel2;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.CheckBox checkBox1;
}
}

View File

@@ -0,0 +1,156 @@
using Inspectron.Settings.WindowsWizard.Enums;
using Inspectron.Settings.WindowsWizard.Interfaces;
namespace Inspectron.Settings.WindowsWizard
{
public partial class SetupPage : Form
{
public SetupPage()
{
InitializeComponent();
}
public SetupPage PreviousPage { get; set; }
public SetupPage NextPage { get; set; }
public FlowLayoutPanel CurrentLayout { get; set; }
public static SetupPage Begin(string tabName,string title=null,Func<bool> expertModeCheck=null)
{
var sp = new SetupPage();
if (title != null)
{
sp.Text = title;
}
sp.ExpertModeCheck = expertModeCheck;
if (sp.ExpertModeCheck == null)
{
sp.checkBox1.Visible = false;
}
TabPage tp = new TabPage();
tp.Text = tabName;
FlowLayoutPanel panel = new FlowLayoutPanel();
panel.Dock = DockStyle.Fill;
panel.FlowDirection = FlowDirection.TopDown;
tp.Controls.Add(panel);
sp.tabControl1.TabPages.Add(tp);
sp.CurrentLayout = panel;
return sp;
}
public Func<bool> ExpertModeCheck { get; set; }
List<TabPage> _expertPages=new List<TabPage>();
public SetupPage Next(string tabName,bool forExpert=false)
{
TabPage tp = new TabPage();
tp.Text = tabName;
FlowLayoutPanel panel = new FlowLayoutPanel();
panel.Dock = DockStyle.Fill;
panel.FlowDirection = FlowDirection.TopDown;
tp.Controls.Add(panel);
if (forExpert)
{
_expertPages.Add(tp);
}
else
{
this.tabControl1.TabPages.Add(tp);
}
this.CurrentLayout = panel;
return this;
}
public static string SplitCamelCase(string input)
{
return System.Text.RegularExpressions.Regex.Replace(input, "([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim();
}
public ESetupResult SetupResult { get; private set; } = ESetupResult.Cancel;
public void ShowSetup()
{
SetupPage start = this;
while (start.PreviousPage!=null)
{
start = start.PreviousPage;
}
start.Show();
}
public void ShowSetupDialog()
{
SetupPage start = this;
while (start.PreviousPage != null)
{
start = start.PreviousPage;
}
start.ShowDialog();
}
public SetupPage FinishWith(Action setupFinished)
{
this.SetupFinished = setupFinished;
return this;
}
public Action SetupFinished { get; set; }
public SetupPage AddItem(ISetupItem item)
{
CurrentLayout.Controls.Add(item as UserControl);
return this;
}
public SetupPage AddItem<T>(object obj, string prp,Action<T> itemAction=null) where T:ISetupItem,new()
{
T pe = new T();
itemAction?.Invoke(pe);
pe.SetProperty(obj, prp);
(pe as UserControl).Width = 500;
this.AddItem(pe);
return this;
}
private void button1_Click(object sender, EventArgs e)
{
SetupFinished?.Invoke();
this.Close();
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
if (checkBox1.Checked)
{
if ((ExpertModeCheck?.Invoke() ?? true))
{
foreach (TabPage page in _expertPages)
{
this.tabControl1.TabPages.Add(page);
}
}
else
{
checkBox1.Checked = false;
}
}
else
{
foreach (TabPage page in _expertPages)
{
this.tabControl1.TabPages.Remove(page);
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
namespace Inspectron.Settings
{
public class AdaptablePath<T> : Path<T>, IAdaptable, IDecoratable
{
/// <summary>
/// Constructor</summary>
/// <param name="last">Single object making up the path</param>
public AdaptablePath(T last)
: base(last)
{
}
/// <summary>
/// Constructor</summary>
/// <param name="path">Path as sequence of objects</param>
public AdaptablePath(IEnumerable<T> path)
: base(path)
{
}
/// <summary>
/// Constructor</summary>
/// <param name="path">Path as collection of objects</param>
public AdaptablePath(ICollection<T> path)
: base(path)
{
}
#region IAdaptable, IDecoratable, and Related Methods
/// <summary>
/// Gets an adapter of the specified type or null</summary>
/// <param name="type">Adapter type</param>
/// <returns>Adapter of the specified type or null</returns>
public object GetAdapter(Type type)
{
object adapter = Last.As(type);
if (adapter != null)
return adapter;
if (type.IsAssignableFrom(GetType()))
return this;
return null;
}
/// <summary>
/// Gets all decorators of the specified type</summary>
/// <param name="type">Decorator type</param>
/// <returns>Enumeration of non-null decorators that are of the specified type. The enumeration may be empty.</returns>
public IEnumerable<object> GetDecorators(Type type)
{
foreach (object obj in Last.AsAll(type))
yield return obj;
}
// implement the following members as a convenience when extension methods aren't available
/// <summary>
/// Converts a reference to the given type by first trying a CLR cast, and then
/// trying to get an adapter</summary>
/// <typeparam name="U">Desired type, must be ref type</typeparam>
/// <returns>Converted reference for the given object or null</returns>
public U As<U>()
where U : class
{
return Adapters.As<U>(this);
}
/// <summary>
/// Converts a reference to the given type by first trying a CLR cast, and then
/// trying to get an adapter; if none is available, throws an AdaptationException</summary>
/// <typeparam name="U">Desired type, must be ref type</typeparam>
/// <returns>Converted reference for the given object</returns>
public U Cast<U>() where U : class
{
return Adapters.Cast<U>(this);
}
/// <summary>
/// Returns whether the given reference can be converted to one of
/// the desired type</summary>
/// <typeparam name="U">Adapter type, must be ref type</typeparam>
/// <returns>True iff the given object can be converted</returns>
public bool Is<U>()
where U : class
{
return Adapters.Is<U>(this);
}
/// <summary>
/// Returns an enumeration of all decorators that can convert a reference to the given type</summary>
/// <typeparam name="U">Decorator type, must be ref type</typeparam>
/// <returns>Enumerable returning all decorators of the given type</returns>
public IEnumerable<U> AsAll<U>()
where U : class
{
return Adapters.AsAll<U>(this);
}
#endregion
/// <summary>
/// Concatenates object with path</summary>
/// <param name="lhs">Prefix object</param>
/// <param name="rhs">Optional path</param>
/// <returns>Concatenated path, with lhs as first object</returns>
public static AdaptablePath<T> operator +(T lhs, AdaptablePath<T> rhs)
{
if (rhs == null)
return new AdaptablePath<T>(lhs);
T[] path = new T[1 + rhs.Count];
path[0] = lhs;
rhs.CopyTo(path, 1);
return new AdaptablePath<T>(path);
}
/// <summary>
/// Concatenates path with object</summary>
/// <param name="lhs">Optional path</param>
/// <param name="rhs">Suffix object</param>
/// <returns>Concatenated path, with rhs as last object</returns>
public static AdaptablePath<T> operator +(AdaptablePath<T> lhs, T rhs)
{
if (lhs == null)
return new AdaptablePath<T>(rhs);
T[] path = new T[lhs.Count + 1];
lhs.CopyTo(path, 0);
path[lhs.Count] = rhs;
return new AdaptablePath<T>(path);
}
/// <summary>
/// Concatenates 2 paths</summary>
/// <param name="lhs">First path. Can be null.</param>
/// <param name="rhs">Second path. Can be null.</param>
/// <returns>Concatenated path, with rhs as prefix and lhs as suffix. Is null if both lhs and rhs are null.</returns>
public static AdaptablePath<T> operator +(AdaptablePath<T> lhs, AdaptablePath<T> rhs)
{
if (lhs == null)
return rhs;
if (rhs == null)
return lhs;
T[] path = new T[lhs.Count + rhs.Count];
lhs.CopyTo(path, 0);
rhs.CopyTo(path, lhs.Count);
return new AdaptablePath<T>(path);
}
/// <summary>
/// Converts from the path type to another type</summary>
/// <typeparam name="U">Desired type</typeparam>
/// <param name="item">Item to convert</param>
/// <returns>Item converted to given type or null</returns>
protected override U Convert<U>(T item)
{
U u = item.As<U>();
return u;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Inspectron.Settings
{
public class AdaptationException : Exception
{
/// <summary>
/// Constructor</summary>
/// <param name="message">Message explaining why this object couldn't be adapted</param>
public AdaptationException(string message)
: base(message)
{
}
/// <summary>
/// Constructor</summary>
/// <param name="message">Message explaining why this object couldn't be adapted</param>
/// <param name="innerException">The exception that prevented adaptation. Will become the
/// InnerException property.</param>
public AdaptationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -0,0 +1,336 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Inspectron.Settings
{
public static class Adapters
{
/// <summary>
/// Converts a reference to the given type by first trying a CLR cast, and then
/// trying to get an adapter</summary>
/// <param name="reference">Reference to convert</param>
/// <param name="type">Desired type, should be ref type</param>
/// <returns>Converted reference for the given object or null</returns>
public static object As(this object reference, Type type)
{
if (reference == null)
return null;
if (type == null)
throw new ArgumentNullException("type");
// is the adapted object compatible?
if (type.IsAssignableFrom(reference.GetType()))
return reference;
// try to get an adapter
var adaptable = reference as IAdaptable;
if (adaptable != null)
{
object adapter = adaptable.GetAdapter(type);
if (adapter != null)
return adapter;
}
return null;
}
/// <summary>
/// Converts a reference to the given type by first trying a CLR cast, and then
/// trying to get an adapter</summary>
/// <typeparam name="T">Desired type, must be ref type</typeparam>
/// <param name="reference">Reference to convert</param>
/// <returns>Converted reference for the given object or null</returns>
public static T As<T>(this object reference)
where T : class
{
if (reference == null)
return null;
// try a normal cast
var converted = reference as T;
// if that fails, try to get an adapter
if (converted == null)
{
var adaptable = reference as IAdaptable;
if (adaptable != null)
converted = adaptable.GetAdapter(typeof(T)) as T;
}
return converted;
}
/// <summary>
/// Converts a reference to the given type by first trying a CLR cast, and then
/// trying to get an adapter</summary>
/// <typeparam name="T">Desired type, must be ref type</typeparam>
/// <param name="adaptable">Adaptable object</param>
/// <returns>Converted reference for the given object or null</returns>
public static T As<T>(this IAdaptable adaptable)
where T : class
{
if (adaptable == null)
return null;
// try a normal cast
var converted = adaptable as T;
// if that fails, try to get an adapter
if (converted == null)
converted = adaptable.GetAdapter(typeof(T)) as T;
return converted;
}
/// <summary>
/// Converts a reference to the given type by first trying a CLR cast, and then
/// trying to get an adapter; if none is available, throws an AdaptationException</summary>
/// <param name="reference">Reference to convert</param>
/// <param name="type">Desired type, should be ref type</param>
/// <returns>Converted reference for the given object</returns>
public static object Cast(this object reference, Type type)
{
object converted = As(reference, type);
if (converted == null)
throw new AdaptationException(type.Name + " adapter required");
return converted;
}
/// <summary>
/// Converts a reference to the given type by first trying a CLR cast, and then
/// trying to get an adapter; if none is available, throws an AdaptationException</summary>
/// <typeparam name="T">Desired type, must be ref type</typeparam>
/// <param name="reference">Reference to convert</param>
/// <returns>Converted reference for the given object</returns>
public static T Cast<T>(this object reference)
where T : class
{
T converted = As<T>(reference);
if (converted == null)
throw new AdaptationException(typeof(T).Name + " adapter required");
return converted;
}
/// <summary>
/// Converts a reference to the given type by first trying a CLR cast, and then
/// trying to get an adapter; if none is available, throws an AdaptationException</summary>
/// <typeparam name="T">Desired type, must be ref type</typeparam>
/// <param name="adaptable">Adaptable object</param>
/// <returns>Converted reference for the given object</returns>
public static T Cast<T>(this IAdaptable adaptable)
where T : class
{
T converted = As<T>(adaptable);
if (converted == null)
throw new AdaptationException(typeof(T).Name + " adapter required");
return converted;
}
/// <summary>
/// Returns a value indicating if the given reference can be converted to one of
/// the desired type</summary>
/// <param name="reference">Reference to test</param>
/// <param name="type">Desired type, should be ref type</param>
/// <returns>True iff the given object can be converted</returns>
public static bool Is(this object reference, Type type)
{
return As(reference, type) != null;
}
/// <summary>
/// Returns a value indicating if the given reference can be converted to one of
/// the desired type</summary>
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
/// <param name="reference">Reference to test</param>
/// <returns>True iff the given object can be converted</returns>
public static bool Is<T>(this object reference)
where T : class
{
return As<T>(reference) != null;
}
/// <summary>
/// Returns a value indicating if the given reference can be converted to one of
/// the desired type</summary>
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
/// <param name="adaptable">Adaptable object</param>
/// <returns>True iff the given object can be converted</returns>
public static bool Is<T>(this IAdaptable adaptable)
where T : class
{
return As<T>(adaptable) != null;
}
/// <summary>
/// Gets all decorators that can convert a reference to the given type</summary>
/// <param name="reference">Reference to convert</param>
/// <param name="type">Decorator type, should be ref type</param>
/// <returns>Enumerable returning all decorators of the given type.
/// Decorators are never null. The enumeration may be empty.</returns>
public static IEnumerable<object> AsAll(this object reference, Type type)
{
if (reference != null)
{
// if IDecoratable, use that to get decorators
var decoratable = reference as IDecoratable;
if (decoratable != null)
return decoratable.GetDecorators(type);
// is the decorated object compatible?
if (type.IsAssignableFrom(reference.GetType()))
return new object[] { reference };
}
return new List<object>();
}
/// <summary>
/// Gets all decorators that can convert a reference to the given type</summary>
/// <typeparam name="T">Decorator type, must be ref type</typeparam>
/// <param name="reference">Reference to convert</param>
/// <returns>Enumerable returning all decorators of the given type.
/// Decorators are never null. The enumeration may be empty.</returns>
public static IEnumerable<T> AsAll<T>(this object reference)
where T : class
{
if (reference != null)
{
// if IDecoratable, use that to get decorators
var decoratable = reference as IDecoratable;
if (decoratable != null)
return AsAll<T>(decoratable);
// otherwise, cast
var t = reference as T;
if (t != null)
return new T[] { t };
}
return new List<T>();
}
/// <summary>
/// Gets an enumeration of all decorators that can convert a reference to the given type</summary>
/// <typeparam name="T">Decorator type, must be ref type</typeparam>
/// <param name="decoratable">Decoratable object</param>
/// <returns>Enumerable returning all decorators of the given type</returns>
public static IEnumerable<T> AsAll<T>(this IDecoratable decoratable)
where T : class
{
if (decoratable != null)
{
foreach (object decorator in decoratable.GetDecorators(typeof(T)))
yield return decorator as T;
}
}
/// <summary>
/// Gets an adapter that converts an enumerable to an enumerable of another type</summary>
/// <param name="enumerable">Enumerable to adapt</param>
/// <param name="type">Adapter type, should be ref type</param>
/// <returns>Enumerable returning adapted items</returns>
public static IEnumerable<object> AsIEnumerable(this IEnumerable enumerable, Type type)
{
if (enumerable != null)
{
foreach (object item in enumerable)
{
object adapter = As(item, type);
if (adapter != null)
yield return adapter;
}
}
}
/// <summary>
/// Returns an enumeration for an adapter that converts an enumerable to an enumerable of another type</summary>
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
/// <param name="enumerable">Enumerable to adapt</param>
/// <returns>Enumerable returning adapted items</returns>
public static IEnumerable<T> AsIEnumerable<T>(this IEnumerable enumerable)
where T : class
{
if (enumerable != null)
{
foreach (object item in enumerable)
{
T adapter = As<T>(item);
if (adapter != null)
yield return adapter;
}
}
}
/// <summary>
/// Returns a value indicating if any of the items in the enumerable are
/// adaptable to the given type</summary>
/// <param name="enumerable">Enumerable to adapt</param>
/// <param name="type">Adapter type, should be ref type</param>
/// <returns>True iff any of the items in the enumerable are adaptable to
/// the given type</returns>
public static bool Any(this IEnumerable enumerable, Type type)
{
if (enumerable != null)
{
foreach (object item in enumerable)
{
object adapter = As(item, type);
if (adapter != null)
return true;
}
}
return false;
}
/// <summary>
/// Returns a value indicating if any of the items in the enumerable are
/// adaptable to the given type</summary>
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
/// <param name="enumerable">Enumerable to adapt</param>
/// <returns>True iff any of the items in the enumerable are adaptable to
/// the given type</returns>
public static bool Any<T>(this IEnumerable enumerable)
where T : class
{
return Any(enumerable, typeof(T));
}
/// <summary>
/// Returns a value indicating if all of the items in the enumerable are
/// adaptable to the given type</summary>
/// <param name="enumerable">Enumerable to adapt</param>
/// <param name="type">Adapter type, should be ref type</param>
/// <returns>True iff all of the items in the enumerable are adaptable to
/// the given type</returns>
public static bool All(this IEnumerable enumerable, Type type)
{
if (enumerable != null)
{
foreach (object item in enumerable)
{
object adapter = As(item, type);
if (adapter == null)
return false;
}
}
return true;
}
/// <summary>
/// Returns a value indicating if all of the items in the enumerable are
/// adaptable to the given type</summary>
/// <typeparam name="T">Adapter type, must be ref type</typeparam>
/// <param name="enumerable">Enumerable to adapt</param>
/// <returns>True iff all of the items in the enumerable are adaptable to
/// the given type</returns>
public static bool All<T>(this IEnumerable enumerable)
where T : class
{
return All(enumerable, typeof(T));
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace Inspectron.Settings.Attributes
{
public class SettingDescriptionAttribute: Attribute
{
public string Description { get; }
public SettingDescriptionAttribute(string description)
{
Description = description;
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace Inspectron.Settings.Attributes
{
public class SettingPreviewAttribute: System.Attribute
{
public Type PreviewClass { get; }
public string PreviewFunction { get; }
public SettingPreviewAttribute(Type previewClass, string previewFunction)
{
PreviewClass = previewClass;
PreviewFunction = previewFunction;
}
}
}

View File

@@ -0,0 +1,494 @@
using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
namespace Inspectron.Settings
{
/// <summary>
/// A specialization of System.ComponentModel.PropertyDescriptor that is bound
/// to a specific property of an object or type. If the property's setter is private,
/// this BoundPropertyDescriptor's IsReadOnly property is true.</summary>
/// <remarks>Use this class to expose an object or type's property for property editing.</remarks>
public class BoundPropertyDescriptor : PropertyDescriptor
{
public delegate object OnClickDelegate(object sender, EventArgs e);
public OnClickDelegate OnClick { get; set; }
/// <summary>
/// Constructor for instance properties</summary>
/// <param name="owner">Property owner</param>
/// <param name="expression">Lambda expression that accesses the property;
/// e.g., () => myObject.MyProperty</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
public BoundPropertyDescriptor(
object owner,
Expression<Func<object>> expression,
string displayName,
string category,
string description)
: this(displayName, category, description)
{
PropertyInfo propertyInfo = GetPropertyInfo(expression);
Init(owner, null, null, propertyInfo, null, null);
}
/// <summary>
/// Constructor for static properties</summary>
/// <param name="ownerType">Type holding static property</param>
/// <param name="expression">Lambda expression that accesses the property;
/// e.g., () => MyClass.MyProperty</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
public BoundPropertyDescriptor(
Type ownerType,
Expression<Func<object>> expression,
string displayName,
string category,
string description)
: this(displayName, category, description)
{
PropertyInfo propertyInfo = GetPropertyInfo(expression);
Init(null, ownerType, null, propertyInfo, null, null);
}
/// <summary>
/// Constructor for instance properties</summary>
/// <param name="owner">Property owner</param>
/// <param name="expression">Lambda expression that accesses the property;
/// e.g., () => myObject.MyProperty</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
/// <param name="editor">Editor for property</param>
/// <param name="converter">TypeConverter for property</param>
public BoundPropertyDescriptor(
object owner,
Expression<Func<object>> expression,
string displayName,
string category,
string description,
object editor,
TypeConverter converter)
: this(displayName, category, description)
{
PropertyInfo propertyInfo = GetPropertyInfo(expression);
Init(owner, null, null, propertyInfo, editor, converter);
}
/// <summary>
/// Constructor for static properties</summary>
/// <param name="ownerType">Type holding static property</param>
/// <param name="expression">Lambda expression that accesses the property;
/// e.g., () => MyClass.MyProperty</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
/// <param name="editor">Editor for property</param>
/// <param name="converter">TypeConverter for property</param>
public BoundPropertyDescriptor(
Type ownerType,
Expression<Func<object>> expression,
string displayName,
string category,
string description,
object editor,
TypeConverter converter)
: this(displayName, category, description)
{
PropertyInfo propertyInfo = GetPropertyInfo(expression);
Init(null, ownerType, null, propertyInfo, editor, converter);
}
/// <summary>
/// Constructor for instance properties</summary>
/// <param name="owner">Property owner</param>
/// <param name="name">Property name</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
public BoundPropertyDescriptor(
object owner,
string name,
string displayName,
string category,
string description)
: this(displayName, category, description)
{
Init(owner, null, name, null, null, null);
}
/// <summary>
/// Constructor for instance properties</summary>
/// <param name="owner">Property owner</param>
/// <param name="name">Property name</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
/// <param name="editor">Editor for property</param>
public BoundPropertyDescriptor(
object owner,
string name,
string displayName,
string category,
string description,
object editor)
: this(displayName, category, description)
{
Init(owner, null, name, null, editor, null);
}
/// <summary>
/// Constructor for instance properties</summary>
/// <param name="owner">Property owner</param>
/// <param name="name">Property name</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
/// <param name="editor">Editor for property</param>
/// <param name="converter">TypeConverter for property</param>
public BoundPropertyDescriptor(
object owner,
string name,
string displayName,
string category,
string description,
object editor,
TypeConverter converter)
: this(displayName, category, description)
{
Init(owner, null, name, null, editor, converter);
}
/// <summary>
/// Constructor for static properties</summary>
/// <param name="ownerType">Type holding static property</param>
/// <param name="name">Property name</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
public BoundPropertyDescriptor(
Type ownerType,
string name,
string displayName,
string category,
string description)
: this(displayName, category, description)
{
Init(null, ownerType, name, null, null, null);
}
/// <summary>
/// Constructor for static properties</summary>
/// <param name="ownerType">Type holding static property</param>
/// <param name="name">Property name</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
/// <param name="editor">Editor for property</param>
public BoundPropertyDescriptor(
Type ownerType,
string name,
string displayName,
string category,
string description,
object editor)
: this(displayName, category, description)
{
Init(null, ownerType, name, null, editor, null);
}
/// <summary>
/// Constructor for static properties</summary>
/// <param name="ownerType">Type holding static property</param>
/// <param name="name">Property name</param>
/// <param name="displayName">Property display name</param>
/// <param name="category">Property category</param>
/// <param name="description">Property description</param>
/// <param name="editor">Editor for property</param>
/// <param name="converter">TypeConverter for property</param>
public BoundPropertyDescriptor(
Type ownerType,
string name,
string displayName,
string category,
string description,
object editor,
TypeConverter converter)
: this(displayName, category, description)
{
Init(null, ownerType, name, null, editor, converter);
}
private void Init(
object owner,
Type ownerType,
string name,
PropertyInfo propertyInfo,
object editor,
TypeConverter converter)
{
m_owner = owner;
// if given the property owner, ignore the ownerType parameter
if (owner != null)
ownerType = owner.GetType();
m_ownerType = ownerType;
if (string.IsNullOrEmpty(name))
{
if (propertyInfo == null)
throw new ArgumentException("either 'name' or 'propertyInfo' must be non-null");
name = propertyInfo.Name;
}
// if given the property info, don't use reflection to find it
_propertyInfo = propertyInfo;
if (_propertyInfo == null)
{
_propertyInfo = m_ownerType.GetProperty(
name,
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static);
if (_propertyInfo == null)
throw new ArgumentException(name + ": Property doesn't exist");
}
// look at "set_" method to determine if property is read-only.
// (PropertyInfo.CanWrite will return true if there is a set
// accessor, even if that accessor is made inaccessible using
// asymmetric accessor accessibility.)
MethodInfo setInfo = m_ownerType.GetMethod("set_" + name,
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static);
m_readOnly = (setInfo == null);
m_editor = editor;
m_typeConverter = converter;
}
private BoundPropertyDescriptor(
string displayName,
string category,
string description)
: base(displayName,
new Attribute[] { new CategoryAttribute(category),
new DescriptionAttribute(description), })
{
}
/// <summary>
/// When overridden in a derived class, returns whether resetting an object changes its value</summary>
/// <param name="component">The component to test for reset capability</param>
/// <returns>True iff resetting the component changes its value</returns>
public override bool CanResetValue(object component)
{
object defaultValue;
return GetDefaultValue(out defaultValue) && !Object.Equals(GetValue(null), defaultValue);
}
/// <summary>
/// Gets the component this property is bound to. Is null, if bound to a static class's property.</summary>
public object Owner
{
get { return m_owner; }
}
/// <summary>
/// Gets the type of the component this property is bound to</summary>
public override Type ComponentType
{
get { return m_ownerType; }
}
/// <summary>
/// Gets the type of the property</summary>
public override Type PropertyType
{
get { return _propertyInfo.PropertyType; }
}
/// <summary>
/// Gets whether this property is read-only</summary>
public override bool IsReadOnly
{
get { return m_readOnly; }
}
/// <summary>
/// Resets the value for this property of the component to the default value</summary>
/// <param name="component">The component with the property value that is to be reset to the default value</param>
public override void ResetValue(object component)
{
object defaultValue;
GetDefaultValue(out defaultValue);
SetValue(component, defaultValue);
}
/// <summary>
/// Determines whether the value of this property needs to be persisted</summary>
/// <param name="component">The component with the property to be examined for persistence</param>
/// <returns>True iff the property should be persisted</returns>
public override bool ShouldSerializeValue(object component)
{
object val = GetValue(component);
object defaultValue;
if (!GetDefaultValue(out defaultValue) && val == null)
return false;
else
return !val.Equals(defaultValue);
}
/// <summary>
/// Returns the Owner's value (if the Owner is not null) or the component's value of the property</summary>
/// <param name="component">Component to examine</param>
/// <returns>The value of a property</returns>
public override object GetValue(object component)
{
if (m_owner != null)
component = m_owner;
return _propertyInfo.GetValue(component, null);
}
/// <summary>
/// Sets the value of the Owner (if not null) or component</summary>
/// <param name="component">Component</param>
/// <param name="value">The new value</param>
public override void SetValue(object component, object value)
{
if (m_owner != null)
component = m_owner;
_propertyInfo.SetValue(component, value, null);
}
/// <summary>
/// Gets the default value</summary>
/// <param name="result">Is set to the default value or null, if it couldn't be determined</param>
/// <returns>Whether or not the default value was determined</returns>
/// <remarks>Uses reflection to look for a DefaultValueAttribute</remarks>
public virtual bool GetDefaultValue(out object result)
{
bool foundDefault = false;
result = null;
object[] attributes = _propertyInfo.GetCustomAttributes(typeof(DefaultValueAttribute), false);
if (attributes.Length > 0)
{
foundDefault = true;
result = (attributes[0] as DefaultValueAttribute).Value;
if (result != null && result.GetType() != _propertyInfo.PropertyType)
{
// Default value type is not the same as the property type; convert it.
// This can happen if the property's type is not CLS-compliant (e.g. UInt32).
TypeConverter converter = TypeDescriptor.GetConverter(result);
if (converter.CanConvertTo(_propertyInfo.PropertyType))
{
result = converter.ConvertTo(result, _propertyInfo.PropertyType);
}
else
{
// Try using the converter associated with the source instead of the target
// (Not sure if this is useful for not, but can it hurt?)
converter = TypeDescriptor.GetConverter(_propertyInfo.PropertyType);
if (converter.CanConvertFrom(result.GetType()))
{
result = converter.ConvertFrom(result);
}
}
}
}
return foundDefault;
}
/// <summary>
/// Returns an editor of the specified type</summary>
/// <param name="editorBaseType">Base type of editor, which is used to differentiate between multiple
/// editors that a property supports</param>
/// <returns>An instance of the requested editor type, or null if an editor cannot be found</returns>
public override object GetEditor(Type editorBaseType)
{
if (m_editor != null &&
editorBaseType.IsInstanceOfType(m_editor))
{
return m_editor;
}
if (editorBaseType.IsEnum)
{
}
return base.GetEditor(editorBaseType);
}
/// <summary>
/// Gets the type converter for this property</summary>
public override TypeConverter Converter
{
get
{
if (m_typeConverter != null)
return m_typeConverter;
return base.Converter;
}
}
public PropertyInfo PropertyInfo => _propertyInfo;
// Does not return null. Will throw an exception if 'expression' is poorly formed.
private static PropertyInfo GetPropertyInfo(Expression<Func<object>> expression)
{
PropertyInfo propertyInfo = null;
MemberExpression memberExpression = expression.Body as MemberExpression;
if (memberExpression != null)
{
// this is the usual case for when a property has a public getter and setter
propertyInfo = memberExpression.Member as PropertyInfo;
}
else
{
// if the setter is private, the expression is a UnaryExpression type for some reason.
UnaryExpression unaryExpression = expression.Body as UnaryExpression;
if (unaryExpression != null)
{
memberExpression = unaryExpression.Operand as MemberExpression;
if (memberExpression != null)
propertyInfo = memberExpression.Member as PropertyInfo;
}
}
if (propertyInfo == null)
throw new ArgumentException(
"lambda expression was not properly formed." +
" Should be \" => myObject.MyProperty\" or" +
" \" => MyClass.MyProperty\"");
return propertyInfo;
}
object m_owner;
Type m_ownerType;
PropertyInfo _propertyInfo;
bool m_readOnly;
private object m_editor;
private TypeConverter m_typeConverter;
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.ComponentModel;
namespace Inspectron.Settings
{
public static class Event
{
public static void Raise(this EventHandler handler, object sender, EventArgs e)
{
if (handler != null) handler(sender, e);
}
public static void Raise<T>(this EventHandler<T> handler, object sender, T e) where T : EventArgs
{
if (handler != null) handler(sender, e);
}
public static bool RaiseCancellable(this CancelEventHandler handler, object sender, CancelEventArgs e)
{
if (handler != null)
foreach (CancelEventHandler h in handler.GetInvocationList())
{
h(sender, e);
if (e.Cancel) break;
}
return e.Cancel;
}
public static bool RaiseCancellable<T>(this EventHandler<T> handler, object sender, T e)
where T : CancelEventArgs
{
if (handler != null)
foreach (EventHandler<T> h in handler.GetInvocationList())
{
h(sender, e);
if (e.Cancel) break;
}
return e.Cancel;
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace Inspectron.Settings
{
public interface IAdaptable
{
/// <summary>
/// Gets an adapter of the specified type or null</summary>
/// <param name="type">Adapter type</param>
/// <returns>Adapter of the specified type or null if no adapter available</returns>
object GetAdapter(Type type);
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
namespace Inspectron.Settings
{
public interface IDecoratable
{
/// <summary>
/// Gets all decorators of the specified type</summary>
/// <param name="type">Decorator type</param>
/// <returns>Enumeration of non-null decorators that are of the specified type. The enumeration may be empty.</returns>
IEnumerable<object> GetDecorators(Type type);
}
}

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace Inspectron.Settings
{
public interface ITreeView
{
/// <summary>
/// Gets the root object of the tree view</summary>
object Root
{
get;
}
/// <summary>
/// Obtains enumeration of the children of the given parent object</summary>
/// <param name="parent">Parent object</param>
/// <returns>Enumeration of children of the parent object</returns>
IEnumerable<object> GetChildren(object parent);
}
}

View File

@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Configurations>Debug;Release;CPU</Configurations>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,845 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Xml;
namespace Inspectron.Settings
{
public class InspectronSettings
{
public InspectronSettings(string path=null)
{
Assembly assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly();
AssemblyName assemblyName = assembly.GetName();
_applicationName = assemblyName.Name;
Version version = assemblyName.Version;
_versionString = version.Major + "." + version.Minor;
string startupPath = Path.GetDirectoryName(new Uri(assemblyName.CodeBase).LocalPath);
_defaultSettingsPath = Path.Combine(startupPath, "DefaultSettings.xml");
if (path == null)
{
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
_settingsPath = string.Format("{0}\\{1}\\{2}\\AppSettings.xml", appDataPath, _applicationName, _versionString);
}
else
{
_settingsPath = Path.Combine(path,"AppSettings.xml");
}
}
internal Path<object> GetSettingsPath(string pathName)
{
string[] pathSegments = pathName.Split('/', 16);
object[] path = new object[pathSegments.Length + 1];
// first node is the settings tree root
Tree<object> node = (Tree<object>)_userSettings;
path[0] = _userSettings;
// middle nodes are folders
for (int i = 1; i < path.Length - 1; i++)
{
node = GetOrCreateFolder(pathSegments[i - 1], node);
path[i] = node;
}
// leaf node is user settings object
foreach (Tree<object> leaf in node.Children)
{
UserSettingsInfo info = leaf.Value as UserSettingsInfo;
if (info != null && info.Name == pathSegments[pathSegments.Length - 1])
{
path[path.Length - 1] = leaf;
break;
}
}
return new Path<object>(path);
}
/// <summary>
/// Gets or sets the current state of all properties (Memento pattern)</summary>
public object State
{
get
{
MemoryStream stream = new MemoryStream();
Serialize(stream);
return stream;
}
set
{
MemoryStream stream = value as MemoryStream;
if (stream == null)
throw new ArgumentException("Not a valid memento");
stream.Position = 0;
Deserialize(stream);
}
}
private IEnumerable<PropertyDescriptor> UserPropertyDescriptors
{
get
{
var userSettings = UserSettings as Tree<object>;
if (userSettings == null)
throw new InvalidOperationException("userSettings");
var all = userSettings.LevelOrder.Where(x => x.Value is UserSettingsInfo);
foreach (Tree<object> node in all)
{
UserSettingsInfo info = node.Value as UserSettingsInfo;
if (info != null)
{
foreach (PropertyDescriptor property in info.Settings)
yield return property;
}
}
}
}
private static readonly object _unusedComponent = new object();
public void SetDefaults()
{
foreach (SettingsInfo info in _settings.Values)
{
foreach (SettingsInfo.Setting setting in info.Settings.Values)
{
if (setting.PropertyDescriptor != null &&
setting.PropertyDescriptor.CanResetValue(_unusedComponent))
{
setting.PropertyDescriptor.ResetValue(_unusedComponent);
}
}
}
// Load default settings if exist.
if (File.Exists(_defaultSettingsPath))
{
using (Stream stream = File.OpenRead(_defaultSettingsPath))
Deserialize(stream);
}
}
internal List<PropertyDescriptor> GetProperties(Tree<object> tree)
{
var info = tree.Value as UserSettingsInfo;
if (info != null)
return info.Settings;
return null;
}
protected class SettingsInfo
{
/// <summary>
/// Constructor with name</summary>
/// <param name="name">Name associated with this group of settings</param>
public SettingsInfo(string name)
{
Name = name;
}
/// <summary>
/// Add a setting to group of settings as a name and value, replacing the previous value if present</summary>
/// <param name="name">Setting name</param>
/// <param name="valueString">Setting value</param>
public void Add(string name, string valueString)
{
Setting setting;
if (Settings.TryGetValue(name, out setting))
{
setting.Set(name, valueString);
}
else
{
Settings.Add(name, new Setting(name, valueString));
}
}
/// <summary>
/// Add a setting to group of settings as a PropertyDescriptor, replacing the previous value if present</summary>
/// <param name="descriptor">PropertyDescriptor describing setting</param>
public void Add(PropertyDescriptor descriptor)
{
Setting setting;
if (Settings.TryGetValue(descriptor.Name, out setting))
{
setting.Set(descriptor);
}
else
{
Settings.Add(descriptor.Name, new Setting(descriptor));
}
}
/// <summary>
/// Name associated with this group of settings</summary>
public readonly string Name;
/// <summary>
/// Dictionary for names and values of settings in group</summary>
public readonly SortedDictionary<string, Setting> Settings = new SortedDictionary<string, Setting>();
/// <summary>
/// Class for handling individual setting information</summary>
public class Setting
{
/// <summary>
/// Constructor with PropertyDescriptor</summary>
/// <param name="descriptor">PropertyDescriptor describing setting</param>
public Setting(PropertyDescriptor descriptor)
{
PropertyDescriptor = descriptor;
}
/// <summary>
/// Constructor with name and value of setting</summary>
/// <param name="name">Setting name</param>
/// <param name="valueString">Setting value</param>
public Setting(string name, string valueString)
{
Name = name;
ValueString = valueString;
}
/// <summary>
/// Set a setting's value with name and value of setting</summary>
/// <param name="name">Setting name</param>
/// <param name="valueString">Setting value</param>
public void Set(string name, string valueString)
{
Name = name;
ValueString = valueString;
if (PropertyDescriptor != null)
{
SetValue();
}
}
/// <summary>
/// Set a setting's value with PropertyDescriptor</summary>
/// <param name="descriptor">PropertyDescriptor describing setting</param>
public void Set(PropertyDescriptor descriptor)
{
PropertyDescriptor = descriptor;
if (Name != null && ValueString != null)
{
SetValue();
}
}
private void SetValue()
{
if (!CanMakeChanges)
return;
object value = GetValue(PropertyDescriptor.PropertyType, ValueString);
PropertyDescriptor.SetValue(null, value);
}
/// <summary>
/// Setting name</summary>
public string Name;
/// <summary>
/// Setting value</summary>
public string ValueString;
/// <summary>
/// Setting PropertyDescriptor</summary>
public PropertyDescriptor PropertyDescriptor;
}
/// <summary>
/// Set whether property descriptors are allowed to make changes</summary>
/// <remarks>Used when persisted settings are being loaded from disk
/// to prevent property descriptors from being set multiple times as
/// in the case where DefaultSettings.xml and AppSettings.xml both exist</remarks>
public static bool CanMakeChanges { private get; set; }
}
private static object GetValue(Type type, string valueString)
{
object value = null;
try
{
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (CanConvertToAndFromString(converter))
{
value = converter.ConvertFromInvariantString(valueString);
}
else
{
// deserialize
byte[] data = Convert.FromBase64String(valueString);
using (MemoryStream stream = new MemoryStream(data))
{
BinaryFormatter formatter = new BinaryFormatter();
value = formatter.Deserialize(stream);
}
}
}
catch
{
value = null;
}
return value;
}
private static bool CanConvertToAndFromString(TypeConverter converter)
{
return converter.CanConvertFrom(typeof(string)) &&
converter.CanConvertTo(typeof(string));
}
/// <summary>
/// Class for group of user settings</summary>
public class UserSettingsInfo
{
/// <summary>
/// Constructor with name and PropertyDescriptors</summary>
/// <param name="name">Name of group of user settings</param>
/// <param name="settings">PropertyDescriptors with settings</param>
public UserSettingsInfo(string name, PropertyDescriptor[] settings)
{
Name = name;
Settings = new List<PropertyDescriptor>(settings);
}
/// <summary>
/// Name of group of user settings</summary>
public readonly string Name;
/// <summary>
/// PropertyDescriptors with settings</summary>
public readonly List<PropertyDescriptor> Settings;
}
private readonly SortedDictionary<string, SettingsInfo> _settings = new SortedDictionary<string, SettingsInfo>();
private ITreeView _userSettings = new TreeView(string.Empty);
public void RegisterSettings(string uid, params PropertyDescriptor[] settings)
{
SettingsInfo settingsInfo;
if (!_settings.TryGetValue(uid, out settingsInfo))
{
settingsInfo = new SettingsInfo(uid);
_settings.Add(uid, settingsInfo);
}
foreach (PropertyDescriptor descriptor in settings)
settingsInfo.Add(descriptor);
}
public ITreeView UserSettings
{
get { return _userSettings; }
}
public void RegisterSimple(object owner,Expression<Func<object>> expression,string pathName,string name,string category="General",string description="")
{
var descriptor = new BoundPropertyDescriptor(owner,expression, name, category,
description);
RegisterSettings(pathName,descriptor);
RegisterUserSettings(pathName,descriptor);
}
public void RegisterUserSettings(string pathName, params PropertyDescriptor[] settings)
{
if (string.IsNullOrEmpty(pathName))
throw new ArgumentException("pathName");
string[] path = pathName.Split('/', 16);
// get root folder
Tree<object> folder = UserSettings as Tree<object>;
if (folder == null)
throw new InvalidOperationException("userSettings");
// for each subsequent segment of the path, get folder
for (int i = 0; i < path.Length - 1; i++)
folder = GetOrCreateFolder(path[i], folder);
// get the node that should hold the settings, if it already exists
string name = path[path.Length - 1];
UserSettingsInfo existing = null;
int index = 0;
foreach (Tree<object> node in folder.Children)
{
UserSettingsInfo info = node.Value as UserSettingsInfo;
if (info != null)
{
if (info.Name == name)
{
existing = info;
break;
}
if (info.Name.CompareTo(name) < 0)
index++;
}
}
// add the settings, either by merging with the existing node or creating a new one
if (existing != null)
{
foreach (PropertyDescriptor pd in settings)
existing.Settings.Add(pd);
}
else
{
Tree<object> node = new Tree<object>(new UserSettingsInfo(name, settings));
folder.Children.Insert(index, node);
}
}
private static string GetMutexName(string pathName)
{
string safeName = pathName;
//255 characters will break IpcChannel constructor. 250 works.
if (safeName.Length > 250)
safeName = safeName.Substring(safeName.Length - 250);
// The Mutex constructor will crash if given '\', unless it's part of a valid path.
// NextInstanceMonitor.ActivateApplication() will crash if there are '/' characters.
safeName = safeName.Replace('/', '-');
safeName = safeName.Replace('\\', '-');
return safeName;
}
protected void Serialize(Stream stream)
{
Saving.Raise(this, EventArgs.Empty);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "utf-8", "yes"));
XmlElement root = xmlDoc.CreateElement("settings");
xmlDoc.AppendChild(root);
// add application name and version to the root element.
root.SetAttribute("appName", _applicationName);
root.SetAttribute("appVersion", _versionString);
foreach (SettingsInfo info in _settings.Values)
{
XmlElement block = xmlDoc.CreateElement("block");
block.SetAttribute("id", info.Name);
foreach (SettingsInfo.Setting setting in info.Settings.Values)
{
PropertyDescriptor descriptor = setting.PropertyDescriptor;
if (descriptor != null)
{
object value = descriptor.GetValue(null);
if (CanWriteValue(value))
WriteValue(descriptor.Name, value, block);
}
else
{
WriteValue(setting.Name, setting.ValueString, block);
}
}
// skip empty block
if (block.ChildNodes.Count > 0)
root.AppendChild(block);
}
XmlWriterSettings settings = new XmlWriterSettings();
settings.CloseOutput = false;
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(stream, settings))
{
xmlDoc.WriteTo(writer);
}
}
private bool CanWriteValue(object value)
{
if (value == null)
return false;
TypeConverter converter = TypeDescriptor.GetConverter(value.GetType());
return CanConvertToAndFromString(converter) || value.GetType().IsSerializable;
}
private void WriteValue(string name, object value, XmlElement block)
{
if (value == null)
return;
// skip persisting if any exception occurs
string valueString = null;
Type type = value.GetType();
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (CanConvertToAndFromString(converter))
{
valueString = converter.ConvertToInvariantString(value);
}
else if (type.IsSerializable)
{
// serialize
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream())
{
formatter.Serialize(stream, value);
valueString = Convert.ToBase64String(stream.GetBuffer());
}
}
if (string.IsNullOrEmpty(valueString))
return;
XmlDocument xmlDoc = block.OwnerDocument;
XmlElement elmValue = xmlDoc.CreateElement("value");
elmValue.SetAttribute("name", name);
elmValue.SetAttribute("type", type.Name);
// if the valueString is xmlDoc then
XmlDocument temp = StringToXmlDoc(valueString);
if (temp != null)
{
// remove xml declaration if exists
XmlDeclaration decl = temp.FirstChild as XmlDeclaration;
if (decl != null)
temp.RemoveChild(decl);
elmValue.InnerXml = temp.DocumentElement.OuterXml;
}
else
{
elmValue.InnerText = valueString;
}
block.AppendChild(elmValue);
}
private XmlDocument StringToXmlDoc(string strXml)
{
XmlDocument xmlDoc = null;
try
{
int len = (strXml.Length > 20) ? 20 : strXml.Length;
string test = RemoveAllWhiteSpace(strXml.Substring(0, len)).ToLower();
if (test.Contains("<?xmlversion="))
{
xmlDoc = new XmlDocument();
xmlDoc.LoadXml(strXml);
}
}
catch
{
xmlDoc = null;
}
return xmlDoc;
}
public static string RemoveAllWhiteSpace(string s)
{
if (string.IsNullOrEmpty(s))
return s;
StringBuilder result = new StringBuilder(s.Length);
for (int i = 0; i < s.Length; i++)
{
char c = s[i];
if (!char.IsWhiteSpace(c))
result.Append(c);
}
return result.ToString();
}
public void SaveSettings()
{
string tempNew = string.Empty;
string mutexName = GetMutexName(_settingsPath);
using (Mutex saveMutex = new Mutex(false, mutexName))
{
try
{
saveMutex.WaitOne();
// Create zero-size file.
tempNew = Path.GetTempFileName();
using (Stream stream = File.Create(tempNew))
Serialize(stream);
// Make sure the settings directory exists. Do nothing if it already exists.
string settingsDir = Path.GetDirectoryName(_settingsPath);
Directory.CreateDirectory(settingsDir);
// Erase old backup (if any) and move current settings file (if any).
string tempBackup = Path.Combine(settingsDir, "~Settings.xml");
if (File.Exists(tempBackup)) // seems unnecessary, but Dan put this check in the WPF version --Ron
File.Delete(tempBackup);
if (File.Exists(_settingsPath))
File.Move(_settingsPath, tempBackup);
// Move temporary file to be the new settings file, then delete backup.
File.Move(tempNew, _settingsPath);
File.Delete(tempBackup);
}
catch (TargetInvocationException)
{
// Catch and ignore TargetInvocationException happening if Windows
// is shut down with the application still running.
// TO DO: Find a way to successfully save settings and exit on shutdown.
}
catch (Exception ex)
{
}
finally
{
// Attempt clean-up. No exception is thrown if file doesn't exist.
File.Delete(tempNew);
saveMutex.ReleaseMutex();
}
}
}
private Tree<object> GetOrCreateFolder(string name, Tree<object> tree)
{
// search for folder
Tree<object> result = null;
int index = 0;
foreach (Tree<object> child in tree.Children)
{
string folderName = child.Value as string;
if (folderName != null)
{
if (folderName == name)
{
result = child;
break;
}
if (folderName.CompareTo(name) < 0)
index++;
}
else // child is UserSettingsInfo
{
index++; // folders should follow settings nodes
}
}
// if not found, create it
if (result == null)
{
result = new Tree<object>(name);
tree.Children.Insert(index, result);
}
return result;
}
public event EventHandler Saving;
public event EventHandler Loading;
public event EventHandler Reloaded;
private class TreeView : Tree<object>, ITreeView
{
public TreeView(object root)
: base(root)
{
}
#region ITreeView Members
public object Root
{
get { return this; }
}
public IEnumerable<object> GetChildren(object parent)
{
foreach (object child in ((Tree<object>)parent).Children)
yield return child;
}
#endregion
#region IItemView Members
/// <summary>
/// Gets item's display information</summary>
/// <param name="item">Item being displayed</param>
/// <param name="info">Item info, to fill out</param>
public void GetInfo(object item, ItemInfo info)
{
object value = ((Tree<object>)item).Value;
if (value is string)
{
info.Label = (string)value;
//info.ImageIndex = info.GetImageList().Images.IndexOfKey(Resources.FolderImage);
info.AllowSelect = false;
}
else
{
UserSettingsInfo settingsInfo = value as UserSettingsInfo;
info.Label = settingsInfo.Name;
info.AllowLabelEdit = false;
//info.ImageIndex = info.GetImageList().Images.IndexOfKey(Resources.PreferencesImage);
info.IsLeaf = true;
}
}
#endregion
}
private string _settingsPath;
private string _defaultSettingsPath;
private string _applicationName;
private string _versionString;
private string _propertyViewState;
public void LoadSettings()
{
Loading.Raise(this, EventArgs.Empty);
try
{
bool defaultSettingsExists = File.Exists(_defaultSettingsPath);
bool appSettingsExists = File.Exists(_settingsPath);
// only update property descriptors during the DefaultSettings.xml pass
// if DefaultSettings.xml exists and AppSettings.xml does not exist
SettingsInfo.CanMakeChanges = defaultSettingsExists && !appSettingsExists;
// first, load default settings, if they exist:
if (defaultSettingsExists)
{
using (Stream stream = File.OpenRead(_defaultSettingsPath))
Deserialize(stream);
}
// restore for AppSettings.xml pass
SettingsInfo.CanMakeChanges = true;
// now load user settings, overriding defaults:
if (appSettingsExists)
{
using (Stream stream = File.OpenRead(_settingsPath))
Deserialize(stream);
}
}
catch (Exception ex)
{
}
finally
{
// restore value
SettingsInfo.CanMakeChanges = true;
}
}
protected void ApplyStoredSettings()
{
foreach (SettingsInfo info in _settings.Values)
{
foreach (SettingsInfo.Setting setting in info.Settings.Values)
{
if (setting.PropertyDescriptor is BoundPropertyDescriptor pd)
{
if (setting.ValueString == null)
{
continue;
}
object v;
if (setting.PropertyDescriptor.PropertyType.IsEnum)
{
v = Enum.Parse(setting.PropertyDescriptor.PropertyType, setting.ValueString);
}
else
{
v = Convert.ChangeType(setting.ValueString, setting.PropertyDescriptor.PropertyType);
}
pd.SetValue(pd.Owner, v);
}
}
}
}
protected bool Deserialize(Stream stream)
{
// create XML DOM from stream
// if failed, display message box and return
try
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(stream);
XmlElement root = xmlDoc.DocumentElement;
// get all the blocks
XmlNodeList blocks = root.SelectNodes("block");
if (blocks == null || blocks.Count == 0)
throw new Exception("The setting file is empty");
foreach (XmlElement block in blocks)
{
try
{
string id = block.GetAttribute("id");
SettingsInfo info;
if (!_settings.TryGetValue(id, out info))
{
info = new SettingsInfo(id);
_settings.Add(id, info);
}
// get a list of value element in each block
XmlNodeList valueNodes = block.SelectNodes("value");
// skip over empty block
if (valueNodes == null || valueNodes.Count == 0)
continue;
foreach (XmlElement xmlElement in valueNodes)
{
string name = xmlElement.GetAttribute("name");
string valueString = GetElementValueString(xmlElement);
info.Add(name, valueString);
}
}
catch (Exception ex)
{
}
}
ApplyStoredSettings();
Reloaded.Raise(this, EventArgs.Empty);
}
catch (Exception ex)
{
return false;
}
return true;
}
private static string GetElementValueString(XmlElement element)
{
string valueString = element.InnerText;
if (string.IsNullOrEmpty(valueString))
valueString = element.InnerXml;
return valueString;
}
}
}

View File

@@ -0,0 +1,148 @@
namespace Inspectron.Settings
{
public abstract class ItemInfo
{
/// <summary>
/// Constructor for items with no associated images to be drawn</summary>
public ItemInfo()
{
CheckBoxEnabled = true;
}
/// <summary>
/// Gets or sets item's label</summary>
/// <remarks>Default is empty string if item has no label</remarks>
public string Label
{
get { return m_label; }
set { m_label = value; }
}
/// <summary>
/// Gets or sets item's description</summary>
/// <remarks>Default is empty string if item has no description</remarks>
public string Description
{
get { return m_description; }
set { m_description = value; }
}
/// <summary>
/// Gets or sets whether item should have a check box control</summary>
/// <remarks>Default is false</remarks>
public bool HasCheck
{
get { return m_hasCheck; }
set { m_hasCheck = value; }
}
/// <summary>
/// Gets or sets whether check box is enabled</summary>
/// <remarks>Default is true.
/// This property makes sense only if HasCheck is true</remarks>
public bool CheckBoxEnabled
{
get;
set;
}
/// <summary>
/// Gets or sets whether item is checked</summary>
/// <remarks>Default is false</remarks>
public abstract bool Checked
{
get;
set;
}
/// <summary>
/// Gets or sets whether item is a leaf (has no sub-items)</summary>
/// <remarks>Used by tree controls to inhibit drawing the node expander; default
/// is false</remarks>
public bool IsLeaf
{
get { return m_isLeaf; }
set { m_isLeaf = value; }
}
/// <summary>
/// Gets or sets a value indicating whether the label is editable</summary>
/// <remarks>Used by tree controls to inhibit editing the node label; default
/// is true</remarks>
public bool AllowLabelEdit
{
get { return m_allowLabelEdit; }
set { m_allowLabelEdit = value; }
}
/// <summary>
/// Gets or sets whether the item is selectable</summary>
/// <remarks>Used by tree controls to inhibit selecting the node; default
/// is true</remarks>
public bool AllowSelect
{
get { return m_allowSelect; }
set { m_allowSelect = value; }
}
/// <summary>
/// Gets or sets whether this item is expanded in the view. Set by
/// Control adapters - client code shouldn't set this value.</summary>
public bool IsExpandedInView
{
get { return m_isExpandedInView; }
set { m_isExpandedInView = value; }
}
/// <summary>
/// Gets or sets index of item's image in image list</summary>
/// <remarks>Default is -1 if item has no image.
/// DAN: This is not required by WPF so could be moved to WinFormsItemInfo</remarks>
public int ImageIndex
{
get { return m_imageIndex; }
set { m_imageIndex = value; }
}
/// <summary>
/// Gets or sets index of item's "State" image in image list</summary>
/// <remarks>Default is -1 if item has no "State" image.
/// DAN: This is not required by WPF so could be moved to WinFormsItemInfo</remarks>
public int StateImageIndex
{
get { return m_stateImageIndex; }
set { m_stateImageIndex = value; }
}
/// <summary>
/// Gets or sets item's properties for lists and tree lists</summary>
/// <remarks>Default is an empty array if item has no properties</remarks>
public object[] Properties
{
get { return m_properties; }
set { m_properties = value; }
}
/// <summary>
/// Gets or sets item's mouse hover over text</summary>
public string HoverText
{
get { return m_hoverText; }
set { m_hoverText = value; }
}
private string m_label = string.Empty;
private string m_description = string.Empty;
private int m_imageIndex = -1;
private int m_stateImageIndex = -1;
private object[] m_properties = new object[0];
private bool m_hasCheck;
private bool m_isLeaf;
private bool m_allowLabelEdit = true;
private bool m_allowSelect = true;
private bool m_isExpandedInView;
private string m_hoverText = string.Empty;
}
}

View File

@@ -0,0 +1,405 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Inspectron.Settings
{
public class Path<T> : IList<T>, IEquatable<Path<T>>
{
/// <summary>
/// Constructor using single object</summary>
/// <param name="last">Single object making up the path</param>
public Path(T last)
{
m_path = new T[1];
m_path[0] = last;
}
/// <summary>
/// Constructor using sequence of objects</summary>
/// <param name="path">Path, as sequence of objects</param>
public Path(IEnumerable<T> path)
{
m_path = path.ToArray();
}
/// <summary>
/// Constructor using collection of objects</summary>
/// <param name="path">Path, as collection of objects</param>
public Path(ICollection<T> path)
{
m_path = new T[path.Count];
path.CopyTo(m_path, 0);
}
/// <summary>
/// Gets or sets the first object in the path</summary>
public T First
{
get { return m_path[0]; }
set { m_path[0] = value; }
}
/// <summary>
/// Gets or sets the last object in the path</summary>
public T Last
{
get { return m_path[m_path.Length - 1]; }
set { m_path[m_path.Length - 1] = value; }
}
/// <summary>
/// Obtains a prefix with the specified length</summary>
/// <param name="length">Prefix length</param>
/// <returns>Prefix with the specified length</returns>
public Path<T> Prefix(int length)
{
CheckSubPathLength(length);
T[] path = new T[length];
Array.Copy(m_path, 0, path, 0, length);
return new Path<T>(path);
}
/// <summary>
/// Obtains a suffix with the specified length</summary>
/// <param name="length">Suffix length</param>
/// <returns>Suffix with the specified length</returns>
public Path<T> Suffix(int length)
{
CheckSubPathLength(length);
T[] path = new T[length];
int offset = m_path.Length - length;
Array.Copy(m_path, offset, path, 0, length);
return new Path<T>(path);
}
/// <summary>
/// Converts path to a path of another type</summary>
/// <typeparam name="U">Path type to convert to</typeparam>
/// <returns>Path of new type</returns>
public Path<U> Convert<U>()
where U : class
{
U[] converted = new U[m_path.Length];
for (int i = 0; i < m_path.Length; i++)
converted[i] = Convert<U>(m_path[i]);
return new Path<U>(converted);
}
/// <summary>
/// Converts from the path type to another type</summary>
/// <typeparam name="U">Desired type</typeparam>
/// <param name="item">Item to convert</param>
/// <returns>Item, converted to given type, or null</returns>
protected virtual U Convert<U>(T item)
where U : class
{
U u = item as U;
return u;
}
/// <summary>
/// Tests path for equality</summary>
/// <param name="other">Other path</param>
/// <returns>True iff this path is equivalent to other</returns>
public bool Equals(Path<T> other)
{
if (object.Equals(other, null))
return false;
if (m_path.Length != other.m_path.Length)
return false;
for (int i = 0; i < m_path.Length; i++)
if (!m_path[i].Equals(other.m_path[i]))
return false;
return true;
}
/// <summary>
/// Tests object for equality</summary>
/// <param name="obj">Other object</param>
/// <returns>True iff this path is equivalent to other object</returns>
public override bool Equals(object obj)
{
Path<T> path = obj as Path<T>;
if (path != null)
return Equals(path);
return false;
}
/// <summary>
/// Obtains hash code</summary>
/// <returns>Hash code</returns>
public override int GetHashCode()
{
int hash = 0;
foreach (T obj in m_path)
hash ^= obj.GetHashCode();
return hash;
}
/// <summary>
/// Tests paths for equality</summary>
/// <param name="o1">First path</param>
/// <param name="o2">Second path</param>
/// <returns>True iff paths are equivalent</returns>
public static bool operator ==(Path<T> o1, Path<T> o2)
{
if (object.Equals(o1, null))
return object.Equals(o2, null);
else
return o1.Equals(o2);
}
/// <summary>
/// Tests paths for inequality</summary>
/// <param name="o1">First path</param>
/// <param name="o2">Second path</param>
/// <returns>True iff paths are not equivalent</returns>
public static bool operator !=(Path<T> o1, Path<T> o2)
{
if (object.Equals(o1, null))
return !object.Equals(o2, null);
else
return !o1.Equals(o2);
}
/// <summary>
/// Concatenates object with path</summary>
/// <param name="lhs">Prefix object</param>
/// <param name="rhs">Optional path, may be null</param>
/// <returns>Concatenated path, with lhs as first object</returns>
public static Path<T> operator +(T lhs, Path<T> rhs)
{
if (rhs == null)
return new Path<T>(lhs);
T[] path = new T[1 + rhs.Count];
path[0] = lhs;
Array.Copy(rhs.m_path, 0, path, 1, rhs.Count);
return new Path<T>(path);
}
/// <summary>
/// Concatenates path with object</summary>
/// <param name="lhs">Optional path, may be null</param>
/// <param name="rhs">Suffix object</param>
/// <returns>Concatenated path, with rhs as last object</returns>
public static Path<T> operator +(Path<T> lhs, T rhs)
{
if (lhs == null)
return new Path<T>(rhs);
T[] path = new T[lhs.Count + 1];
Array.Copy(lhs.m_path, 0, path, 0, lhs.Count);
path[path.Length - 1] = rhs;
return new Path<T>(path);
}
/// <summary>
/// Concatenates two paths</summary>
/// <param name="lhs">First path. Can be null.</param>
/// <param name="rhs">Second path. Can be null.</param>
/// <returns>Concatenated path, with rhs as prefix and lhs as suffix. Is null if both lhs and rhs are null.</returns>
public static Path<T> operator +(Path<T> lhs, Path<T> rhs)
{
if (lhs == null)
return rhs;
if (rhs == null)
return lhs;
T[] path = new T[lhs.Count + rhs.Count];
Array.Copy(lhs.m_path, 0, path, 0, lhs.Count);
Array.Copy(rhs.m_path, 0, path, lhs.Count, rhs.Count);
return new Path<T>(path);
}
/// <summary>
/// Gets the enumeration of each path's last item; i.e., the property 'Last'</summary>
/// <param name="paths">Enumeration of Path objects, whose Last property is returned</param>
/// <returns>Last property of each Path in 'paths', in the same order as 'paths'</returns>
public static IEnumerable<T> GetLastItems(IEnumerable<Path<T>> paths)
{
foreach (Path<T> path in paths)
yield return path.Last;
}
#region IList<T> Members
/// <summary>
/// Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"></see></summary>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"></see></param>
/// <returns>
/// The index of item if found in the list; otherwise -1
/// </returns>
public int IndexOf(T item)
{
for (int i = 0; i < m_path.Length; i++)
if (m_path[i].Equals(item))
return i;
return -1;
}
/// <summary>
/// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"></see> at the specified index</summary>
/// <param name="index">The zero-based index at which item should be inserted</param>
/// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"></see></param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"></see> is read-only</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see></exception>
public void Insert(int index, T item)
{
throw new NotSupportedException();
}
/// <summary>
/// Removes the <see cref="T:System.Collections.Generic.IList`1"></see> item at the specified index</summary>
/// <param name="index">The zero-based index of the item to remove</param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"></see> is read-only</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">index is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"></see></exception>
public void RemoveAt(int index)
{
throw new NotSupportedException();
}
/// <summary>
/// Gets or sets the item at the specified index</summary>
/// <value>Index at which to set value</value>
public T this[int index]
{
get { return m_path[index]; }
set { m_path[index] = value; }
}
#endregion
#region ICollection<T> Members
/// <summary>
/// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"></see></summary>
/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"></see></param>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see>
/// is read-only</exception>
public void Add(T item)
{
throw new NotSupportedException();
}
/// <summary>
/// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"></see></summary>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see>
/// is read-only</exception>
public void Clear()
{
throw new NotSupportedException();
}
/// <summary>
/// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> contains a specific value</summary>
/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"></see></param>
/// <returns>
/// True iff item is found in the <see cref="T:System.Collections.Generic.ICollection`1"></see>
/// </returns>
public bool Contains(T item)
{
foreach (T obj in m_path)
if (obj.Equals(item))
return true;
return false;
}
/// <summary>
/// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"></see> to an <see cref="T:System.Array"></see>,
/// starting at a particular <see cref="T:System.Array"></see> index</summary>
/// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements
/// copied from <see cref="T:System.Collections.Generic.ICollection`1"></see>.
/// The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
/// <param name="arrayIndex">The zero-based index in array at which copying begins</param>
/// <exception cref="T:System.ArgumentOutOfRangeException">ArrayIndex is less than 0</exception>
/// <exception cref="T:System.ArgumentNullException">Array is null</exception>
/// <exception cref="T:System.ArgumentException">Array is multidimensional.-or-
/// arrayIndex is equal to or greater than the length of array.-or-
/// The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"></see> is greater than
/// the available space from arrayIndex to the end of the destination array.-or-
/// Type T cannot be cast automatically to the type of the destination array.</exception>
public void CopyTo(T[] array, int arrayIndex)
{
m_path.CopyTo(array, arrayIndex);
}
/// <summary>
/// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"></see></summary>
public int Count
{
get { return m_path.Length; }
}
/// <summary>
/// Gets whether the <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only</summary>
public bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"></see></summary>
/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"></see></param>
/// <returns>
/// True iff item was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"></see>.
/// This method also returns false if item is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"></see>.
/// </returns>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"></see> is read-only</exception>
public bool Remove(T item)
{
throw new NotSupportedException();
}
#endregion
#region IEnumerable<T> Members
/// <summary>
/// Returns an enumerator that iterates through the collection</summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection
/// </returns>
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)m_path).GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return m_path.GetEnumerator();
}
#endregion
/// <summary>
/// Private constructor</summary>
private Path(T[] path)
{
m_path = path;
}
private void CheckSubPathLength(int length)
{
if (length < 1)
throw new InvalidOperationException("Length must be > 0");
if (length > m_path.Length)
throw new InvalidOperationException("Length greater than path length");
}
private readonly T[] m_path;
}
}

View File

@@ -0,0 +1,336 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
namespace Inspectron.Settings
{
[Serializable]
public class Tree<T>
{
/// <summary>
/// Constructor of empty tree</summary>
public Tree()
: this(default(T))
{
}
/// <summary>
/// Constructor for tree with one node</summary>
/// <param name="value">Value associated with this tree</param>
public Tree(T value)
{
m_value = value;
m_children = new ChildCollection(this);
}
/// <summary>
/// Gets or sets tree's parent. Is null if this is a root node.</summary>
public Tree<T> Parent
{
get { return m_parent; }
set
{
if (m_parent != value)
{
if (m_parent != null)
m_parent.Children.Remove(this);
m_parent = value;
if (m_parent != null)
m_parent.Children.Add(this);
}
}
}
/// <summary>
/// Gets the list of children nodes. Is the same as 'this' because this Tree implements IList.</summary>
public IList<Tree<T>> Children
{
get { return m_children; }
}
/// <summary>
/// Gets or sets the value associated with the tree</summary>
public T Value
{
get { return m_value; }
set { m_value = value; }
}
/// <summary>
/// Tests for equality</summary>
/// <param name="obj">Other object</param>
/// <returns>True iff object is a tree with the same structure and values as this tree</returns>
public override bool Equals(object obj)
{
if (obj == null)
return false;
Tree<T> other = obj as Tree<T>;
if (other == null)
return false;
if (!m_value.Equals(other.m_value))
return false;
if (m_children.Count != other.m_children.Count)
return false;
for (int i = 0; i < m_children.Count; i++)
if (!(m_children[i]).Equals(other.m_children[i]))
return false;
return true;
}
/// <summary>
/// Tests for similarity</summary>
/// <param name="other">Other tree</param>
/// <returns>True iff other has the same structure as this tree</returns>
/// <remarks>Same structure means same node structure</remarks>
public bool Similar(Tree<T> other)
{
if (this == other)
return true;
if (other == null)
return false;
if (m_children.Count != other.m_children.Count)
return false;
for (int i = 0; i < m_children.Count; i++)
if (!(m_children[i]).Similar(other.m_children[i]))
return false;
return true;
}
/// <summary>
/// Returns hash code for tree</summary>
/// <returns>Hash code</returns>
public override int GetHashCode()
{
int result = 0;
foreach (Tree<T> tree in PreOrder)
result ^= tree.Value.GetHashCode();
return result;
}
/// <summary>
/// Converts tree to string of the form "(Value(Child1),...,(ChildN))"</summary>
/// <returns>String representation of tree</returns>
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append('(');
Stringify(builder);
builder.Append(')');
return builder.ToString();
}
private void Stringify(StringBuilder builder)
{
builder.Append(m_value.ToString());
if (!IsLeaf)
{
builder.Append('(');
bool firstTime = true;
foreach (Tree<T> t in m_children)
{
if (firstTime)
firstTime = false;
else
builder.Append(',');
t.Stringify(builder);
}
builder.Append(')');
}
}
/// <summary>
/// Tests if this tree is a descendant of another</summary>
/// <param name="ancestor">Possible ancestor</param>
/// <returns>True iff this tree is a descendant of the other</returns>
/// <remarks>A tree is considered a descendant of itself</remarks>
public bool IsDescendantOf(Tree<T> ancestor)
{
Tree<T> descendant = this;
while (descendant != null)
{
if (ancestor == descendant)
return true;
descendant = descendant.Parent;
}
return false;
}
/// <summary>
/// Gets whether a tree is a leaf (no children)</summary>
public bool IsLeaf
{
get { return m_children.Count == 0; }
}
/// <summary>
/// Gets level, or depth, in tree</summary>
public int Level
{
get
{
int result = 0;
Tree<T> ancestor = m_parent;
while (ancestor != null)
{
result++;
ancestor = ancestor.Parent;
}
return result;
}
}
/// <summary>
/// Gets number of descendants, including the tree itself</summary>
public int DescendantCount
{
get
{
int n = 0;
foreach (Tree<T> tree in PreOrder)
n++;
return n;
}
}
/// <summary>
/// Gets an enumeration of all the nodes of the tree in pre-order (depth first).
/// For example, the root node is first, followed by its first child (and its
/// children and so on) and then the second child of the root (and its children
/// and so on) etc.</summary>
public IEnumerable<Tree<T>> PreOrder
{
get
{
Stack<Tree<T>> nodes = new Stack<Tree<T>>();
nodes.Push(this);
while (nodes.Count > 0)
{
Tree<T> node = nodes.Pop();
yield return node;
// push children in reverse order
for (int i = node.m_children.Count - 1; i >= 0; i--)
nodes.Push(node.m_children[i]);
}
}
}
/// <summary>
/// Gets an enumeration of all the nodes of the tree in post-order. This means that for
/// each node, the children are visited first (starting with the first child) and then
/// the parent node is enumerated.</summary>
public IEnumerable<Tree<T>> PostOrder
{
get
{
// push each non-leaf node twice to represent nodes whose children haven't been visited
// rather than storing a bit on each node.
Stack<Tree<T>> nodes = new Stack<Tree<T>>();
nodes.Push(this);
if (!IsLeaf)
nodes.Push(this);
while (nodes.Count > 1)
{
Tree<T> node = nodes.Pop();
if (node != nodes.Peek())
{
yield return node;
}
else
{
// push children in reverse order
for (int i = node.m_children.Count - 1; i >= 0; i--)
{
Tree<T> child = node.m_children[i];
nodes.Push(child);
if (!child.IsLeaf)
nodes.Push(child);
}
}
}
yield return nodes.Pop();
}
}
/// <summary>
/// Gets an enumeration of all the nodes in a breadth-first order. This means that the
/// root is enumerated first (level 0), followed by all of its children (level 1),
/// followed by all of their children (level 2), and so on.</summary>
public IEnumerable<Tree<T>> LevelOrder
{
get
{
Queue<Tree<T>> nodes = new Queue<Tree<T>>();
nodes.Enqueue(this);
while (nodes.Count > 0)
{
Tree<T> node = nodes.Dequeue();
yield return node;
// queue children
foreach (Tree<T> child in node.m_children)
nodes.Enqueue(child);
}
}
}
private class ChildCollection : Collection<Tree<T>>
{
public ChildCollection(Tree<T> parent)
{
m_parent = parent;
}
protected override void InsertItem(int index, Tree<T> item)
{
item.m_parent = m_parent;
base.InsertItem(index, item);
}
protected override void RemoveItem(int index)
{
Items[index].m_parent = null;
base.RemoveItem(index);
}
protected override void SetItem(int index, Tree<T> item)
{
Items[index].m_parent = null;
item.m_parent = m_parent;
base.SetItem(index, item);
}
protected override void ClearItems()
{
foreach (Tree<T> subTree in Items)
subTree.m_parent = null;
base.ClearItems();
}
private readonly Tree<T> m_parent;
}
private T m_value;
private Tree<T> m_parent;
private readonly ChildCollection m_children;
}
}

View File

@@ -0,0 +1,48 @@
using System.Globalization;
using CsvHelper;
namespace Inspectron.Statistics;
public class CsvStatistics
{
private readonly string _filePath;
private readonly StreamWriter _stream;
private readonly FileStream _fileStream;
private readonly CsvWriter _csv;
public CsvStatistics(string filePath, string[] headers)
{
_filePath = filePath;
var newFile = !File.Exists(filePath);
_fileStream = File.Open(_filePath, FileMode.Append);
_stream = new StreamWriter(_fileStream);
_csv = new CsvWriter(_stream,CultureInfo.InvariantCulture);
if (newFile)
{
foreach (var header in headers)
{
_csv.WriteField(header);
}
_csv.NextRecord();
_csv.Flush();
}
}
public void WriteLine(string[] values)
{
for (int i = 0; i < values.Length; i++)
{
_csv.WriteField(values[i]);
}
_csv.NextRecord();
_csv.Flush();
}
public void Dispose()
{
_csv.Dispose();
_stream.Dispose();
_fileStream.Dispose();
}
}

View File

@@ -0,0 +1,6 @@
namespace Inspectron.Statistics;
public interface IStatistics:IDisposable
{
void WriteLine(StatisticsRecord record);
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" Version="27.2.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,92 @@
using System.Globalization;
using CsvHelper;
namespace Inspectron.Statistics;
public class LockedCsvStatistics : IStatistics
{
private readonly string _filePath;
private readonly string _lockFilePath;
private readonly string[] _headers;
public LockedCsvStatistics(string filePath, string[] headers)
{
_filePath = filePath;
_lockFilePath = filePath + ".lock";
_headers = headers;
var directoryPath = Path.GetDirectoryName(filePath);
if (directoryPath != null && !Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
}
public void WriteLine(StatisticsRecord record)
{
AcquireLock();
try
{
bool writeHeaders = !File.Exists(_filePath) || new FileInfo(_filePath).Length == 0;
using var fileStream = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.None);
using var streamWriter = new StreamWriter(fileStream);
using var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture);
if (writeHeaders)
{
foreach (var header in _headers)
{
csvWriter.WriteField(header);
}
csvWriter.NextRecord();
csvWriter.Flush();
}
foreach (var value in record.Values)
{
csvWriter.WriteField(value);
}
csvWriter.NextRecord();
csvWriter.Flush();
}
finally
{
ReleaseLock();
}
}
public void Dispose()
{
// No resources to dispose
}
private void AcquireLock()
{
while (true)
{
try
{
using (File.Open(_lockFilePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None))
{
// Lock acquired
break;
}
}
catch (IOException)
{
// Lock file already exists, wait and retry
Thread.Sleep(10);
}
}
}
private void ReleaseLock()
{
if (File.Exists(_lockFilePath))
{
File.Delete(_lockFilePath);
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Inspectron.Statistics;
public class StatisticsRecord
{
public string[] Values { get; set; }
}

View File

@@ -0,0 +1,64 @@
using System.Collections.Concurrent;
namespace Inspectron.Statistics;
public class StatisticsWritter:IStatistics
{
private readonly Func<IStatistics> _statisticsBuilder;
private bool _running = true;
public StatisticsWritter(Func<IStatistics> statisticsBuilder)
{
_statisticsBuilder = statisticsBuilder;
Thread th = new Thread(WritingLoop);
th.IsBackground = false;
th.Start();
}
private void WritingLoop()
{
while (_running)
{
try
{
using (var statistics = _statisticsBuilder())
{
while (_recordQueue.TryDequeue(out var res))
{
var error = false;
do
{
try
{
statistics.WriteLine(res);
}
catch (Exception e)
{
error = true;
Thread.Sleep(1000);
}
} while (error);
}
}
}
catch
{
}
Thread.Sleep(1000);
}
}
public void Dispose()
{
_running = false;
}
private ConcurrentQueue<StatisticsRecord> _recordQueue = new ConcurrentQueue<StatisticsRecord>();
public void WriteLine(StatisticsRecord record)
{
_recordQueue.Enqueue(record);
}
}

View File

@@ -0,0 +1,12 @@
namespace MaterialSkin.Animations
{
enum AnimationDirection
{
In, //In. Stops if finished.
Out, //Out. Stops if finished.
InOutIn, //Same as In, but changes to InOutOut if finished.
InOutOut, //Same as Out.
InOutRepeatingIn, // Same as In, but changes to InOutRepeatingOut if finished.
InOutRepeatingOut // Same as Out, but changes to InOutRepeatingIn if finished.
}
}

View File

@@ -0,0 +1,363 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace MaterialSkin.Animations
{
class AnimationManager
{
public bool InterruptAnimation { get; set; }
public double Increment { get; set; }
public double SecondaryIncrement { get; set; }
public AnimationType AnimationType { get; set; }
public bool Singular { get; set; }
public delegate void AnimationFinished(object sender);
public event AnimationFinished OnAnimationFinished;
public delegate void AnimationProgress(object sender);
public event AnimationProgress OnAnimationProgress;
private readonly List<double> _animationProgresses;
private readonly List<Point> _animationSources;
private readonly List<AnimationDirection> _animationDirections;
private readonly List<object[]> _animationDatas;
private const double MIN_VALUE = 0.00;
private const double MAX_VALUE = 1.00;
private readonly Timer _animationTimer = new Timer { Interval = 5, Enabled = false };
/// <summary>
/// Constructor
/// </summary>
/// <param name="singular">If true, only one animation is supported. The current animation will be replaced with the new one. If false, a new animation is added to the list.</param>
public AnimationManager(bool singular = true)
{
_animationProgresses = new List<double>();
_animationSources = new List<Point>();
_animationDirections = new List<AnimationDirection>();
_animationDatas = new List<object[]>();
Increment = 0.03;
SecondaryIncrement = 0.03;
AnimationType = AnimationType.Linear;
InterruptAnimation = true;
Singular = singular;
if (Singular)
{
_animationProgresses.Add(0);
_animationSources.Add(new Point(0, 0));
_animationDirections.Add(AnimationDirection.In);
}
_animationTimer.Tick += AnimationTimerOnTick;
}
private void AnimationTimerOnTick(object sender, EventArgs eventArgs)
{
for (var i = 0; i < _animationProgresses.Count; i++)
{
UpdateProgress(i);
if (!Singular)
{
if ((_animationDirections[i] == AnimationDirection.InOutIn && _animationProgresses[i] == MAX_VALUE))
{
_animationDirections[i] = AnimationDirection.InOutOut;
}
else if ((_animationDirections[i] == AnimationDirection.InOutRepeatingIn && _animationProgresses[i] == MIN_VALUE))
{
_animationDirections[i] = AnimationDirection.InOutRepeatingOut;
}
else if ((_animationDirections[i] == AnimationDirection.InOutRepeatingOut && _animationProgresses[i] == MIN_VALUE))
{
_animationDirections[i] = AnimationDirection.InOutRepeatingIn;
}
else if (
(_animationDirections[i] == AnimationDirection.In && _animationProgresses[i] == MAX_VALUE) ||
(_animationDirections[i] == AnimationDirection.Out && _animationProgresses[i] == MIN_VALUE) ||
(_animationDirections[i] == AnimationDirection.InOutOut && _animationProgresses[i] == MIN_VALUE))
{
_animationProgresses.RemoveAt(i);
_animationSources.RemoveAt(i);
_animationDirections.RemoveAt(i);
_animationDatas.RemoveAt(i);
}
}
else
{
if ((_animationDirections[i] == AnimationDirection.InOutIn && _animationProgresses[i] == MAX_VALUE))
{
_animationDirections[i] = AnimationDirection.InOutOut;
}
else if ((_animationDirections[i] == AnimationDirection.InOutRepeatingIn && _animationProgresses[i] == MAX_VALUE))
{
_animationDirections[i] = AnimationDirection.InOutRepeatingOut;
}
else if ((_animationDirections[i] == AnimationDirection.InOutRepeatingOut && _animationProgresses[i] == MIN_VALUE))
{
_animationDirections[i] = AnimationDirection.InOutRepeatingIn;
}
}
}
OnAnimationProgress?.Invoke(this);
}
public bool IsAnimating()
{
return _animationTimer.Enabled;
}
public void StartNewAnimation(AnimationDirection animationDirection, object[] data = null)
{
StartNewAnimation(animationDirection, new Point(0, 0), data);
}
public void StartNewAnimation(AnimationDirection animationDirection, Point animationSource, object[] data = null)
{
if (!IsAnimating() || InterruptAnimation)
{
if (Singular && _animationDirections.Count > 0)
{
_animationDirections[0] = animationDirection;
}
else
{
_animationDirections.Add(animationDirection);
}
if (Singular && _animationSources.Count > 0)
{
_animationSources[0] = animationSource;
}
else
{
_animationSources.Add(animationSource);
}
if (!(Singular && _animationProgresses.Count > 0))
{
switch (_animationDirections[_animationDirections.Count - 1])
{
case AnimationDirection.InOutRepeatingIn:
case AnimationDirection.InOutIn:
case AnimationDirection.In:
_animationProgresses.Add(MIN_VALUE);
break;
case AnimationDirection.InOutRepeatingOut:
case AnimationDirection.InOutOut:
case AnimationDirection.Out:
_animationProgresses.Add(MAX_VALUE);
break;
default:
throw new Exception("Invalid AnimationDirection");
}
}
if (Singular && _animationDatas.Count > 0)
{
_animationDatas[0] = data ?? new object[] { };
}
else
{
_animationDatas.Add(data ?? new object[] { });
}
}
_animationTimer.Start();
}
public void UpdateProgress(int index)
{
switch (_animationDirections[index])
{
case AnimationDirection.InOutRepeatingIn:
case AnimationDirection.InOutIn:
case AnimationDirection.In:
IncrementProgress(index);
break;
case AnimationDirection.InOutRepeatingOut:
case AnimationDirection.InOutOut:
case AnimationDirection.Out:
DecrementProgress(index);
break;
default:
throw new Exception("No AnimationDirection has been set");
}
}
private void IncrementProgress(int index)
{
_animationProgresses[index] += Increment;
if (_animationProgresses[index] > MAX_VALUE)
{
_animationProgresses[index] = MAX_VALUE;
for (int i = 0; i < GetAnimationCount(); i++)
{
if (_animationDirections[i] == AnimationDirection.InOutIn) return;
if (_animationDirections[i] == AnimationDirection.InOutRepeatingIn) return;
if (_animationDirections[i] == AnimationDirection.InOutRepeatingOut) return;
if (_animationDirections[i] == AnimationDirection.InOutOut && _animationProgresses[i] != MAX_VALUE) return;
if (_animationDirections[i] == AnimationDirection.In && _animationProgresses[i] != MAX_VALUE) return;
}
_animationTimer.Stop();
OnAnimationFinished?.Invoke(this);
}
}
private void DecrementProgress(int index)
{
_animationProgresses[index] -= (_animationDirections[index] == AnimationDirection.InOutOut || _animationDirections[index] == AnimationDirection.InOutRepeatingOut) ? SecondaryIncrement : Increment;
if (_animationProgresses[index] < MIN_VALUE)
{
_animationProgresses[index] = MIN_VALUE;
for (var i = 0; i < GetAnimationCount(); i++)
{
if (_animationDirections[i] == AnimationDirection.InOutIn) return;
if (_animationDirections[i] == AnimationDirection.InOutRepeatingIn) return;
if (_animationDirections[i] == AnimationDirection.InOutRepeatingOut) return;
if (_animationDirections[i] == AnimationDirection.InOutOut && _animationProgresses[i] != MIN_VALUE) return;
if (_animationDirections[i] == AnimationDirection.Out && _animationProgresses[i] != MIN_VALUE) return;
}
_animationTimer.Stop();
OnAnimationFinished?.Invoke(this);
}
}
public double GetProgress()
{
if (!Singular)
throw new Exception("Animation is not set to Singular.");
if (_animationProgresses.Count == 0)
throw new Exception("Invalid animation");
return GetProgress(0);
}
public double GetProgress(int index)
{
if (!(index < GetAnimationCount()))
throw new IndexOutOfRangeException("Invalid animation index");
switch (AnimationType)
{
case AnimationType.Linear:
return AnimationLinear.CalculateProgress(_animationProgresses[index]);
case AnimationType.EaseInOut:
return AnimationEaseInOut.CalculateProgress(_animationProgresses[index]);
case AnimationType.EaseOut:
return AnimationEaseOut.CalculateProgress(_animationProgresses[index]);
case AnimationType.CustomQuadratic:
return AnimationCustomQuadratic.CalculateProgress(_animationProgresses[index]);
default:
throw new NotImplementedException("The given AnimationType is not implemented");
}
}
public Point GetSource(int index)
{
if (!(index < GetAnimationCount()))
throw new IndexOutOfRangeException("Invalid animation index");
return _animationSources[index];
}
public Point GetSource()
{
if (!Singular)
throw new Exception("Animation is not set to Singular.");
if (_animationSources.Count == 0)
throw new Exception("Invalid animation");
return _animationSources[0];
}
public AnimationDirection GetDirection()
{
if (!Singular)
throw new Exception("Animation is not set to Singular.");
if (_animationDirections.Count == 0)
throw new Exception("Invalid animation");
return _animationDirections[0];
}
public AnimationDirection GetDirection(int index)
{
if (!(index < _animationDirections.Count))
throw new IndexOutOfRangeException("Invalid animation index");
return _animationDirections[index];
}
public object[] GetData()
{
if (!Singular)
throw new Exception("Animation is not set to Singular.");
if (_animationDatas.Count == 0)
throw new Exception("Invalid animation");
return _animationDatas[0];
}
public object[] GetData(int index)
{
if (!(index < _animationDatas.Count))
throw new IndexOutOfRangeException("Invalid animation index");
return _animationDatas[index];
}
public int GetAnimationCount()
{
return _animationProgresses.Count;
}
public void SetProgress(double progress)
{
if (!Singular)
throw new Exception("Animation is not set to Singular.");
if (_animationProgresses.Count == 0)
throw new Exception("Invalid animation");
_animationProgresses[0] = progress;
}
public void SetDirection(AnimationDirection direction)
{
if (!Singular)
throw new Exception("Animation is not set to Singular.");
if (_animationProgresses.Count == 0)
throw new Exception("Invalid animation");
_animationDirections[0] = direction;
}
public void SetData(object[] data)
{
if (!Singular)
throw new Exception("Animation is not set to Singular.");
if (_animationDatas.Count == 0)
throw new Exception("Invalid animation");
_animationDatas[0] = data;
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
namespace MaterialSkin.Animations
{
enum AnimationType
{
Linear,
EaseInOut,
EaseOut,
CustomQuadratic
}
static class AnimationLinear
{
public static double CalculateProgress(double progress)
{
return progress;
}
}
static class AnimationEaseInOut
{
public static double PI = Math.PI;
public static double PI_HALF = Math.PI / 2;
public static double CalculateProgress(double progress)
{
return EaseInOut(progress);
}
private static double EaseInOut(double s)
{
return s - Math.Sin(s * 2 * PI) / (2 * PI);
}
}
public static class AnimationEaseOut
{
public static double CalculateProgress(double progress)
{
return -1 * progress * (progress - 2);
}
}
public static class AnimationCustomQuadratic
{
public static double CalculateProgress(double progress)
{
var kickoff = 0.6;
return 1 - Math.Cos((Math.Max(progress, kickoff) - kickoff) * Math.PI / (2 - (2 * kickoff)));
}
}
}

View File

@@ -0,0 +1,371 @@
using System.Drawing;
namespace MaterialSkin
{
public class ColorScheme
{
public readonly Color PrimaryColor, DarkPrimaryColor, LightPrimaryColor, AccentColor, TextColor;
public readonly Pen PrimaryPen, DarkPrimaryPen, LightPrimaryPen, AccentPen, TextPen;
public readonly Brush PrimaryBrush, DarkPrimaryBrush, LightPrimaryBrush, AccentBrush, TextBrush;
/// <summary>
/// Defines the Color Scheme to be used for all forms.
/// </summary>
/// <param name="primary">The primary color, a -500 color is suggested here.</param>
/// <param name="darkPrimary">A darker version of the primary color, a -700 color is suggested here.</param>
/// <param name="lightPrimary">A lighter version of the primary color, a -100 color is suggested here.</param>
/// <param name="accent">The accent color, a -200 color is suggested here.</param>
/// <param name="textShade">The text color, the one with the highest contrast is suggested.</param>
public ColorScheme(Primary primary, Primary darkPrimary, Primary lightPrimary, Accent accent, TextShade textShade)
{
//Color
PrimaryColor = ((int)primary).ToColor();
DarkPrimaryColor = ((int)darkPrimary).ToColor();
LightPrimaryColor = ((int)lightPrimary).ToColor();
AccentColor = ((int)accent).ToColor();
TextColor = ((int)textShade).ToColor();
//Pen
PrimaryPen = new Pen(PrimaryColor);
DarkPrimaryPen = new Pen(DarkPrimaryColor);
LightPrimaryPen = new Pen(LightPrimaryColor);
AccentPen = new Pen(AccentColor);
TextPen = new Pen(TextColor);
//Brush
PrimaryBrush = new SolidBrush(PrimaryColor);
DarkPrimaryBrush = new SolidBrush(DarkPrimaryColor);
LightPrimaryBrush = new SolidBrush(LightPrimaryColor);
AccentBrush = new SolidBrush(AccentColor);
TextBrush = new SolidBrush(TextColor);
}
public ColorScheme(int primary, int darkPrimary, int lightPrimary, Accent accent, TextShade textShade)
{
//Color
PrimaryColor = ((int)primary).ToColor();
DarkPrimaryColor = ((int)darkPrimary).ToColor();
LightPrimaryColor = ((int)lightPrimary).ToColor();
AccentColor = ((int)accent).ToColor();
TextColor = ((int)textShade).ToColor();
//Pen
PrimaryPen = new Pen(PrimaryColor);
DarkPrimaryPen = new Pen(DarkPrimaryColor);
LightPrimaryPen = new Pen(LightPrimaryColor);
AccentPen = new Pen(AccentColor);
TextPen = new Pen(TextColor);
//Brush
PrimaryBrush = new SolidBrush(PrimaryColor);
DarkPrimaryBrush = new SolidBrush(DarkPrimaryColor);
LightPrimaryBrush = new SolidBrush(LightPrimaryColor);
AccentBrush = new SolidBrush(AccentColor);
TextBrush = new SolidBrush(TextColor);
}
}
public static class ColorExtension
{
/// <summary>
/// Convert an integer number to a Color.
/// </summary>
/// <returns></returns>
public static Color ToColor(this int argb)
{
return Color.FromArgb(
(argb & 0xff0000) >> 16,
(argb & 0xff00) >> 8,
argb & 0xff);
}
/// <summary>
/// Removes the alpha component of a color.
/// </summary>
/// <param name="color"></param>
/// <returns></returns>
public static Color RemoveAlpha(this Color color)
{
return Color.FromArgb(color.R, color.G, color.B);
}
/// <summary>
/// Converts a 0-100 integer to a 0-255 color component.
/// </summary>
/// <param name="percentage"></param>
/// <returns></returns>
public static int PercentageToColorComponent(this int percentage)
{
return (int)((percentage / 100d) * 255d);
}
}
//Color constantes
public enum TextShade
{
WHITE = 0xFFFFFF,
BLACK = 0x212121
}
public enum Primary
{
Red50 = 0xFFEBEE,
Red100 = 0xFFCDD2,
Red200 = 0xEF9A9A,
Red300 = 0xE57373,
Red400 = 0xEF5350,
Red500 = 0xF44336,
Red600 = 0xE53935,
Red700 = 0xD32F2F,
Red800 = 0xC62828,
Red900 = 0xB71C1C,
Pink50 = 0xFCE4EC,
Pink100 = 0xF8BBD0,
Pink200 = 0xF48FB1,
Pink300 = 0xF06292,
Pink400 = 0xEC407A,
Pink500 = 0xE91E63,
Pink600 = 0xD81B60,
Pink700 = 0xC2185B,
Pink800 = 0xAD1457,
Pink900 = 0x880E4F,
Purple50 = 0xF3E5F5,
Purple100 = 0xE1BEE7,
Purple200 = 0xCE93D8,
Purple300 = 0xBA68C8,
Purple400 = 0xAB47BC,
Purple500 = 0x9C27B0,
Purple600 = 0x8E24AA,
Purple700 = 0x7B1FA2,
Purple800 = 0x6A1B9A,
Purple900 = 0x4A148C,
DeepPurple50 = 0xEDE7F6,
DeepPurple100 = 0xD1C4E9,
DeepPurple200 = 0xB39DDB,
DeepPurple300 = 0x9575CD,
DeepPurple400 = 0x7E57C2,
DeepPurple500 = 0x673AB7,
DeepPurple600 = 0x5E35B1,
DeepPurple700 = 0x512DA8,
DeepPurple800 = 0x4527A0,
DeepPurple900 = 0x311B92,
Indigo50 = 0xE8EAF6,
Indigo100 = 0xC5CAE9,
Indigo200 = 0x9FA8DA,
Indigo300 = 0x7986CB,
Indigo400 = 0x5C6BC0,
Indigo500 = 0x3F51B5,
Indigo600 = 0x3949AB,
Indigo700 = 0x303F9F,
Indigo800 = 0x283593,
Indigo900 = 0x1A237E,
Blue50 = 0xE3F2FD,
Blue100 = 0xBBDEFB,
Blue200 = 0x90CAF9,
Blue300 = 0x64B5F6,
Blue400 = 0x42A5F5,
Blue500 = 0x2196F3,
Blue600 = 0x1E88E5,
Blue700 = 0x1976D2,
Blue800 = 0x1565C0,
Blue900 = 0x0D47A1,
LightBlue50 = 0xE1F5FE,
LightBlue100 = 0xB3E5FC,
LightBlue200 = 0x81D4FA,
LightBlue300 = 0x4FC3F7,
LightBlue400 = 0x29B6F6,
LightBlue500 = 0x03A9F4,
LightBlue600 = 0x039BE5,
LightBlue700 = 0x0288D1,
LightBlue800 = 0x0277BD,
LightBlue900 = 0x01579B,
Cyan50 = 0xE0F7FA,
Cyan100 = 0xB2EBF2,
Cyan200 = 0x80DEEA,
Cyan300 = 0x4DD0E1,
Cyan400 = 0x26C6DA,
Cyan500 = 0x00BCD4,
Cyan600 = 0x00ACC1,
Cyan700 = 0x0097A7,
Cyan800 = 0x00838F,
Cyan900 = 0x006064,
Teal50 = 0xE0F2F1,
Teal100 = 0xB2DFDB,
Teal200 = 0x80CBC4,
Teal300 = 0x4DB6AC,
Teal400 = 0x26A69A,
Teal500 = 0x009688,
Teal600 = 0x00897B,
Teal700 = 0x00796B,
Teal800 = 0x00695C,
Teal900 = 0x004D40,
Green50 = 0xE8F5E9,
Green100 = 0xC8E6C9,
Green200 = 0xA5D6A7,
Green300 = 0x81C784,
Green400 = 0x66BB6A,
Green500 = 0x4CAF50,
Green600 = 0x43A047,
Green700 = 0x388E3C,
Green800 = 0x2E7D32,
Green900 = 0x1B5E20,
LightGreen50 = 0xF1F8E9,
LightGreen100 = 0xDCEDC8,
LightGreen200 = 0xC5E1A5,
LightGreen300 = 0xAED581,
LightGreen400 = 0x9CCC65,
LightGreen500 = 0x8BC34A,
LightGreen600 = 0x7CB342,
LightGreen700 = 0x689F38,
LightGreen800 = 0x558B2F,
LightGreen900 = 0x33691E,
Lime50 = 0xF9FBE7,
Lime100 = 0xF0F4C3,
Lime200 = 0xE6EE9C,
Lime300 = 0xDCE775,
Lime400 = 0xD4E157,
Lime500 = 0xCDDC39,
Lime600 = 0xC0CA33,
Lime700 = 0xAFB42B,
Lime800 = 0x9E9D24,
Lime900 = 0x827717,
Yellow50 = 0xFFFDE7,
Yellow100 = 0xFFF9C4,
Yellow200 = 0xFFF59D,
Yellow300 = 0xFFF176,
Yellow400 = 0xFFEE58,
Yellow500 = 0xFFEB3B,
Yellow600 = 0xFDD835,
Yellow700 = 0xFBC02D,
Yellow800 = 0xF9A825,
Yellow900 = 0xF57F17,
Amber50 = 0xFFF8E1,
Amber100 = 0xFFECB3,
Amber200 = 0xFFE082,
Amber300 = 0xFFD54F,
Amber400 = 0xFFCA28,
Amber500 = 0xFFC107,
Amber600 = 0xFFB300,
Amber700 = 0xFFA000,
Amber800 = 0xFF8F00,
Amber900 = 0xFF6F00,
Orange50 = 0xFFF3E0,
Orange100 = 0xFFE0B2,
Orange200 = 0xFFCC80,
Orange300 = 0xFFB74D,
Orange400 = 0xFFA726,
Orange500 = 0xFF9800,
Orange600 = 0xFB8C00,
Orange700 = 0xF57C00,
Orange800 = 0xEF6C00,
Orange900 = 0xE65100,
DeepOrange50 = 0xFBE9E7,
DeepOrange100 = 0xFFCCBC,
DeepOrange200 = 0xFFAB91,
DeepOrange300 = 0xFF8A65,
DeepOrange400 = 0xFF7043,
DeepOrange500 = 0xFF5722,
DeepOrange600 = 0xF4511E,
DeepOrange700 = 0xE64A19,
DeepOrange800 = 0xD84315,
DeepOrange900 = 0xBF360C,
Brown50 = 0xEFEBE9,
Brown100 = 0xD7CCC8,
Brown200 = 0xBCAAA4,
Brown300 = 0xA1887F,
Brown400 = 0x8D6E63,
Brown500 = 0x795548,
Brown600 = 0x6D4C41,
Brown700 = 0x5D4037,
Brown800 = 0x4E342E,
Brown900 = 0x3E2723,
Grey50 = 0xFAFAFA,
Grey100 = 0xF5F5F5,
Grey200 = 0xEEEEEE,
Grey300 = 0xE0E0E0,
Grey400 = 0xBDBDBD,
Grey500 = 0x9E9E9E,
Grey600 = 0x757575,
Grey700 = 0x616161,
Grey800 = 0x424242,
Grey900 = 0x212121,
BlueGrey50 = 0xECEFF1,
BlueGrey100 = 0xCFD8DC,
BlueGrey200 = 0xB0BEC5,
BlueGrey300 = 0x90A4AE,
BlueGrey400 = 0x78909C,
BlueGrey500 = 0x607D8B,
BlueGrey600 = 0x546E7A,
BlueGrey700 = 0x455A64,
BlueGrey800 = 0x37474F,
BlueGrey900 = 0x263238
}
public enum Accent
{
Red100 = 0xFF8A80,
Red200 = 0xFF5252,
Red400 = 0xFF1744,
Red700 = 0xD50000,
Pink100 = 0xFF80AB,
Pink200 = 0xFF4081,
Pink400 = 0xF50057,
Pink700 = 0xC51162,
Purple100 = 0xEA80FC,
Purple200 = 0xE040FB,
Purple400 = 0xD500F9,
Purple700 = 0xAA00FF,
DeepPurple100 = 0xB388FF,
DeepPurple200 = 0x7C4DFF,
DeepPurple400 = 0x651FFF,
DeepPurple700 = 0x6200EA,
Indigo100 = 0x8C9EFF,
Indigo200 = 0x536DFE,
Indigo400 = 0x3D5AFE,
Indigo700 = 0x304FFE,
Blue100 = 0x82B1FF,
Blue200 = 0x448AFF,
Blue400 = 0x2979FF,
Blue700 = 0x2962FF,
LightBlue100 = 0x80D8FF,
LightBlue200 = 0x40C4FF,
LightBlue400 = 0x00B0FF,
LightBlue700 = 0x0091EA,
Cyan100 = 0x84FFFF,
Cyan200 = 0x18FFFF,
Cyan400 = 0x00E5FF,
Cyan700 = 0x00B8D4,
Teal100 = 0xA7FFEB,
Teal200 = 0x64FFDA,
Teal400 = 0x1DE9B6,
Teal700 = 0x00BFA5,
Green100 = 0xB9F6CA,
Green200 = 0x69F0AE,
Green400 = 0x00E676,
Green700 = 0x00C853,
LightGreen100 = 0xCCFF90,
LightGreen200 = 0xB2FF59,
LightGreen400 = 0x76FF03,
LightGreen700 = 0x64DD17,
Lime100 = 0xF4FF81,
Lime200 = 0xEEFF41,
Lime400 = 0xC6FF00,
Lime700 = 0xAEEA00,
Yellow100 = 0xFFFF8D,
Yellow200 = 0xFFFF00,
Yellow400 = 0xFFEA00,
Yellow700 = 0xFFD600,
Amber100 = 0xFFE57F,
Amber200 = 0xFFD740,
Amber400 = 0xFFC400,
Amber700 = 0xFFAB00,
Orange100 = 0xFFD180,
Orange200 = 0xFFAB40,
Orange400 = 0xFF9100,
Orange700 = 0xFF6D00,
DeepOrange100 = 0xFF9E80,
DeepOrange200 = 0xFF6E40,
DeepOrange400 = 0xFF3D00,
DeepOrange700 = 0xDD2C00
}
}

View File

@@ -0,0 +1,250 @@
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Windows.Forms;
using MaterialSkin.Animations;
namespace MaterialSkin.Controls
{
public class MaterialCheckBox : CheckBox, IMaterialControl
{
[Browsable(false)]
public int Depth { get; set; }
[Browsable(false)]
public MaterialSkinManager SkinManager => MaterialSkinManager.Instance;
[Browsable(false)]
public MouseState MouseState { get; set; }
[Browsable(false)]
public Point MouseLocation { get; set; }
private bool _ripple;
[Category("Behavior")]
public bool Ripple
{
get { return _ripple; }
set
{
_ripple = value;
AutoSize = AutoSize; //Make AutoSize directly set the bounds.
if (value)
{
Margin = new Padding(0);
}
Invalidate();
}
}
private readonly AnimationManager _animationManager;
private readonly AnimationManager _rippleAnimationManager;
private const int CHECKBOX_SIZE = 18;
private const int CHECKBOX_SIZE_HALF = CHECKBOX_SIZE / 2;
private const int CHECKBOX_INNER_BOX_SIZE = CHECKBOX_SIZE - 4;
private int _boxOffset;
private Rectangle _boxRectangle;
public MaterialCheckBox()
{
_animationManager = new AnimationManager
{
AnimationType = AnimationType.EaseInOut,
Increment = 0.05
};
_rippleAnimationManager = new AnimationManager(false)
{
AnimationType = AnimationType.Linear,
Increment = 0.10,
SecondaryIncrement = 0.08
};
_animationManager.OnAnimationProgress += sender => Invalidate();
_rippleAnimationManager.OnAnimationProgress += sender => Invalidate();
CheckedChanged += (sender, args) =>
{
_animationManager.StartNewAnimation(Checked ? AnimationDirection.In : AnimationDirection.Out);
};
Ripple = true;
MouseLocation = new Point(-1, -1);
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
_boxOffset = Height / 2 - 9;
_boxRectangle = new Rectangle(_boxOffset, _boxOffset, CHECKBOX_SIZE - 1, CHECKBOX_SIZE - 1);
}
public override Size GetPreferredSize(Size proposedSize)
{
var w = _boxOffset + CHECKBOX_SIZE + 2 + (int)CreateGraphics().MeasureString(Text, SkinManager.ROBOTO_MEDIUM_10).Width;
return Ripple ? new Size(w, 30) : new Size(w, 20);
}
private static readonly Point[] CheckmarkLine = { new Point(3, 8), new Point(7, 12), new Point(14, 5) };
private const int TEXT_OFFSET = 22;
protected override void OnPaint(PaintEventArgs pevent)
{
var g = pevent.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = TextRenderingHint.AntiAlias;
// clear the control
g.Clear(Parent.BackColor);
var CHECKBOX_CENTER = _boxOffset + CHECKBOX_SIZE_HALF - 1;
var animationProgress = _animationManager.GetProgress();
var colorAlpha = Enabled ? (int)(animationProgress * 255.0) : SkinManager.GetCheckBoxOffDisabledColor().A;
var backgroundAlpha = Enabled ? (int)(SkinManager.GetCheckboxOffColor().A * (1.0 - animationProgress)) : SkinManager.GetCheckBoxOffDisabledColor().A;
var brush = new SolidBrush(Color.FromArgb(colorAlpha, Enabled ? SkinManager.ColorScheme.AccentColor : SkinManager.GetCheckBoxOffDisabledColor()));
var brush3 = new SolidBrush(Enabled ? SkinManager.ColorScheme.AccentColor : SkinManager.GetCheckBoxOffDisabledColor());
var pen = new Pen(brush.Color);
// draw ripple animation
if (Ripple && _rippleAnimationManager.IsAnimating())
{
for (var i = 0; i < _rippleAnimationManager.GetAnimationCount(); i++)
{
var animationValue = _rippleAnimationManager.GetProgress(i);
var animationSource = new Point(CHECKBOX_CENTER, CHECKBOX_CENTER);
var rippleBrush = new SolidBrush(Color.FromArgb((int)((animationValue * 40)), ((bool)_rippleAnimationManager.GetData(i)[0]) ? Color.Black : brush.Color));
var rippleHeight = (Height % 2 == 0) ? Height - 3 : Height - 2;
var rippleSize = (_rippleAnimationManager.GetDirection(i) == AnimationDirection.InOutIn) ? (int)(rippleHeight * (0.8d + (0.2d * animationValue))) : rippleHeight;
using (var path = DrawHelper.CreateRoundRect(animationSource.X - rippleSize / 2, animationSource.Y - rippleSize / 2, rippleSize, rippleSize, rippleSize / 2))
{
g.FillPath(rippleBrush, path);
}
rippleBrush.Dispose();
}
}
brush3.Dispose();
var checkMarkLineFill = new Rectangle(_boxOffset, _boxOffset, (int)((int)(FontHeight * 1.1) * animationProgress), (int)(FontHeight * 1.1));
using (var checkmarkPath = DrawHelper.CreateRoundRect(_boxOffset, _boxOffset, (int)(FontHeight*1.1), (int)(FontHeight * 1.1), 1f))
{
var brush2 = new SolidBrush(DrawHelper.BlendColor(Parent.BackColor, Enabled ? SkinManager.GetCheckboxOffColor() : SkinManager.GetCheckBoxOffDisabledColor(), backgroundAlpha));
var pen2 = new Pen(brush2.Color);
g.FillPath(brush2, checkmarkPath);
g.DrawPath(pen2, checkmarkPath);
g.FillRectangle(new SolidBrush(Parent.BackColor), _boxOffset + 2, _boxOffset + 2, (int)(FontHeight * 1.1) -4, (int)(FontHeight * 1.1) -4);
g.DrawRectangle(new Pen(Parent.BackColor), _boxOffset + 2, _boxOffset + 2, (int)(FontHeight * 1.1) -4, (int)(FontHeight * 1.1) - 4);
brush2.Dispose();
pen2.Dispose();
if (Enabled)
{
g.FillPath(brush, checkmarkPath);
g.DrawPath(pen, checkmarkPath);
}
else if (Checked)
{
g.SmoothingMode = SmoothingMode.None;
g.FillRectangle(brush, _boxOffset + 2, _boxOffset + 2, CHECKBOX_INNER_BOX_SIZE, CHECKBOX_INNER_BOX_SIZE);
g.SmoothingMode = SmoothingMode.AntiAlias;
}
g.DrawImageUnscaledAndClipped(DrawCheckMarkBitmap(), checkMarkLineFill);
}
// draw checkbox text
SizeF stringSize = g.MeasureString(Text, Font);
g.DrawString(
Text,
Font,
Enabled ? SkinManager.GetPrimaryTextBrush() : SkinManager.GetDisabledOrHintBrush(),
_boxOffset + TEXT_OFFSET, Height / 2 - stringSize.Height / 2);
// dispose used paint objects
pen.Dispose();
brush.Dispose();
}
private Bitmap DrawCheckMarkBitmap()
{
var checkMark = new Bitmap(CHECKBOX_SIZE, CHECKBOX_SIZE);
var g = Graphics.FromImage(checkMark);
// clear everything, transparent
g.Clear(Color.Transparent);
// draw the checkmark lines
using (var pen = new Pen(Parent.BackColor, 2))
{
g.DrawLines(pen, CheckmarkLine);
}
return checkMark;
}
public override bool AutoSize
{
get { return base.AutoSize; }
set
{
base.AutoSize = value;
if (value)
{
Size = new Size(10, 10);
}
}
}
private bool IsMouseInCheckArea()
{
return _boxRectangle.Contains(MouseLocation);
}
protected override void OnCreateControl()
{
base.OnCreateControl();
Font = SkinManager.ROBOTO_MEDIUM_10;
if (DesignMode) return;
MouseState = MouseState.OUT;
MouseEnter += (sender, args) =>
{
MouseState = MouseState.HOVER;
};
MouseLeave += (sender, args) =>
{
MouseLocation = new Point(-1, -1);
MouseState = MouseState.OUT;
};
MouseDown += (sender, args) =>
{
MouseState = MouseState.DOWN;
if (Ripple && args.Button == MouseButtons.Left && IsMouseInCheckArea())
{
_rippleAnimationManager.SecondaryIncrement = 0;
_rippleAnimationManager.StartNewAnimation(AnimationDirection.InOutIn, new object[] { Checked });
}
};
MouseUp += (sender, args) =>
{
MouseState = MouseState.HOVER;
_rippleAnimationManager.SecondaryIncrement = 0.08;
};
MouseMove += (sender, args) =>
{
MouseLocation = args.Location;
Cursor = IsMouseInCheckArea() ? Cursors.Hand : Cursors.Default;
};
}
}
}

Some files were not shown because too many files have changed in this diff Show More