diff --git a/Clear_Builds.ps1 b/Clear_Builds.ps1 new file mode 100644 index 0000000..72f175e --- /dev/null +++ b/Clear_Builds.ps1 @@ -0,0 +1,56 @@ +<# +.SYNOPSIS + Removes temporary bin and obj folders. +.NOTES + Author: Evaldas Jocys + Modified: 2021-08-31 +.LINK + http://www.jocys.com +#> +# ---------------------------------------------------------------------------- +# Get current command path. +[string]$current = $MyInvocation.MyCommand.Path +# Get calling command path. +[string]$calling = @(Get-PSCallStack)[1].InvocationInfo.MyCommand.Path +# If executed directly then... +if ($calling -ne "") { + $current = $calling +} + +$file = Get-Item $current +# Working folder. +$wdir = $file.Directory.FullName; + +# ---------------------------------------------------------------------------- + +$global:removeCount = 0; +$global:skipCount = 0; + +Function RemoveDirectories +{ + # Parameters. + Param ($pattern) + # Function. + $items = Get-ChildItem $wdir -Filter $pattern -Recurse -Force | Where-Object {$_ -is [System.IO.DirectoryInfo]}; + foreach ($item in $items) + { + [System.IO.DirectoryInfo] $parent = $item.Parent; + $projects = $parent.GetFiles("*.*proj", [System.IO.SearchOption]::TopDirectoryOnly); + # If project file was found in parent folder then... + if ($projects.length -gt 0){ + Write-Output "Remove: $($item.FullName)"; + $global:removeCount += 1; + Remove-Item -LiteralPath $item.FullName -Force -Recurse + } + else + { + Write-Output "Skip: $($item.FullName)"; + $global:skipCount += 1; + } + } +} +# Remove 'obj' folders first, because it can contain 'bin' inside. +RemoveDirectories "obj" +RemoveDirectories "bin" +Write-Output "Skipped: $global:skipCount, Removed: $global:removeCount"; +pause \ No newline at end of file diff --git a/Clear_Cache.ps1 b/Clear_Cache.ps1 new file mode 100644 index 0000000..4d48c1a --- /dev/null +++ b/Clear_Cache.ps1 @@ -0,0 +1,96 @@ +<# +.SYNOPSIS + Kill and clear IIS Express. Removes temporary and user specific solution files. +.NOTES + Author: Evaldas Jocys + Modified: 2021-04-14 +.LINK + http://www.jocys.com +#> +# ---------------------------------------------------------------------------- +# Get current command path. +[string]$current = $MyInvocation.MyCommand.Path; +# Get calling command path. +[string]$calling = @(Get-PSCallStack)[1].InvocationInfo.MyCommand.Path; +# If executed directly then... +if ($calling -ne "") { + $current = $calling; +} +$file = Get-Item $current; +# Working folder. +$wdir = $file.Directory.FullName; +# ---------------------------------------------------------------------------- +Function RemoveDirectories +{ + # Parameters. + param($pattern); + # Function. + $items = Get-ChildItem $wdir -Filter $pattern -Recurse -Force | Where-Object {$_ -is [System.IO.DirectoryInfo]}; + foreach ($item in $items) + { + Write-Output $item.FullName; + Remove-Item -LiteralPath $item.FullName -Force -Recurse; + } +} +# ---------------------------------------------------------------------------- +Function RemoveSubDirectories +{ + # Parameters. + param($path); + # Function. + $dirs = Get-Item $path -ErrorAction SilentlyContinue; + foreach ($dir in $dirs) + { + Write-Output "Clear: $($dir.FullName)"; + $items = Get-ChildItem -LiteralPath $dir.FullName -Force | Where-Object {$_ -is [System.IO.DirectoryInfo]}; + foreach ($item in $items) + { + Write-Output " - $($item.Name)"; + Remove-Item -LiteralPath $item.FullName -Force -Recurse; + } + } +} +# ---------------------------------------------------------------------------- +Function RemoveFiles +{ + # Parameters. + param($pattern); + # Function. + $items = Get-ChildItem $wdir -Filter $pattern -Recurse -Force | Where-Object {$_ -is [System.IO.FileInfo]}; + foreach ($item in $items) + { + Write-Output $item.FullName; + Remove-Item -LiteralPath $item.FullName -Force; + } +} +# ---------------------------------------------------------------------------- +Function KillProcess +{ + # Parameters. + param($pattern); + # Function. + $procs = Get-Process; + foreach ($proc in $procs) + { + if ($proc.Path) + { + $item = Get-Item $proc.Path; + if ($item.Name -eq $pattern) + { + Write-Output "Stopping process: $($item.Name)"; + Stop-Process $proc; + } + } + } +} +# ---------------------------------------------------------------------------- +# Clear IIS Express configuration. +KillProcess "iisexpress.exe"; +KillProcess "iisexpresstray.exe"; +RemoveSubDirectories "$($env:USERPROFILE)\Documents\My Web Sites"; +# Clear directories and files. +RemoveDirectories ".vs"; +RemoveFiles "*.dbmdl"; +RemoveFiles "*.user"; +RemoveFiles "*.suo"; +pause; diff --git a/Clear_Cache_VS.ps1 b/Clear_Cache_VS.ps1 new file mode 100644 index 0000000..b03e78f --- /dev/null +++ b/Clear_Cache_VS.ps1 @@ -0,0 +1,77 @@ +<# +.SYNOPSIS + Clear Visual Studio cache files. +.NOTES + Author: Evaldas Jocys + Modified: 2021-04-14 +.LINK + http://www.jocys.com +#> +#---------------------------------------------------------------------------- +# Run as administrator. +if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) +{ + $arguments = "& '" + $myinvocation.mycommand.definition + "'"; + Start-Process powershell -Verb runAs -ArgumentList $arguments; + break; +} +#---------------------------------------------------------------------------- +# Get current command path. +[string]$current = $MyInvocation.MyCommand.Path; +# Get calling command path. +[string]$calling = @(Get-PSCallStack)[1].InvocationInfo.MyCommand.Path; +# If executed directly then... +if ($calling -ne "") { + $current = $calling; +} +$file = Get-Item $current; +# Working folder. +$wdir = $file.Directory.FullName; +# ---------------------------------------------------------------------------- +function RemoveSubFoldersAndFiles +{ + # Parameters. + param($path); + # Function. + $dirs = Get-Item $path -ErrorAction SilentlyContinue; + foreach ($dir in $dirs) + { + Write-Output "Clear: $($dir.FullName)"; + $items = Get-ChildItem -LiteralPath $dir.FullName -Force; + foreach ($item in $items) + { + Write-Output " - $($item.Name)"; + Remove-Item -LiteralPath $item.FullName -Force -Recurse; + } + } +} +# ---------------------------------------------------------------------------- +# Fix Visual Studio "Windows Form Designer: Could not load file or assembly" designer error by +# clearing temporary compiled assemblies inside dynamically created folders by Visual Studio. +# Visual studio must be closed for this batch script to succeed. +# +# Clear Visual Studio. +RemoveSubFoldersAndFiles "$($env:USERPROFILE)\AppData\Local\Microsoft\VisualStudio\12.*\ProjectAssemblies"; +RemoveSubFoldersAndFiles "$($env:USERPROFILE)\AppData\Local\Microsoft\VisualStudio\13.*\ProjectAssemblies"; +RemoveSubFoldersAndFiles "$($env:USERPROFILE)\AppData\Local\Microsoft\VisualStudio\14.*\ProjectAssemblies"; +RemoveSubFoldersAndFiles "$($env:USERPROFILE)\AppData\Local\Microsoft\VisualStudio\15.*\ProjectAssemblies"; +RemoveSubFoldersAndFiles "$($env:USERPROFILE)\AppData\Local\Microsoft\VisualStudio\16.*\ProjectAssemblies"; +# Clear IIS Express. +RemoveSubFoldersAndFiles "$($env:LOCALAPPDATA)\Temp\iisexpress"; +RemoveSubFoldersAndFiles "$($env:LOCALAPPDATA)\Temp\Temporary ASP.NET Files"; +# Clear Xamarin. +RemoveSubFoldersAndFiles "$($env:LOCALAPPDATA)\Temp\Xamarin"; +RemoveSubFoldersAndFiles "$($env:LOCALAPPDATA)\Xamarin\iOS\Provisioning"; +# Clear .NET Framework. +RemoveSubFoldersAndFiles "$($env:SystemRoot)\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files"; +RemoveSubFoldersAndFiles "$($env:SystemRoot)\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files"; +# +# Solution Explorer, right-click Solution +# Properties -> Common Properties -> Debug Source Files -> clean "Do not look for these source files" box. +# +# Tools -> Options -> Projects and Solutions -> Build and Run +# Set "On Run, when build or deployment errors occur:" Prompt to Launch +# +# .EditorConfig file. +# "charset=utf-8" option can trigger "The source file is different from when the module was built." warning when debugging. +pause; diff --git a/FocusLogger/App.ico b/FocusLogger/App.ico new file mode 100644 index 0000000..a78188d Binary files /dev/null and b/FocusLogger/App.ico differ diff --git a/FocusLogger/App.xaml b/FocusLogger/App.xaml new file mode 100644 index 0000000..f431dd6 --- /dev/null +++ b/FocusLogger/App.xaml @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/FocusLogger/App.xaml.cs b/FocusLogger/App.xaml.cs new file mode 100644 index 0000000..fc77b1d --- /dev/null +++ b/FocusLogger/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace JocysCom.FocusLogger +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/FocusLogger/AssemblyInfo.cs b/FocusLogger/AssemblyInfo.cs new file mode 100644 index 0000000..27503ab --- /dev/null +++ b/FocusLogger/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/FocusLogger/Common/DataItem.cs b/FocusLogger/Common/DataItem.cs new file mode 100644 index 0000000..238991d --- /dev/null +++ b/FocusLogger/Common/DataItem.cs @@ -0,0 +1,57 @@ +using JocysCom.ClassLibrary.Configuration; +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace JocysCom.FocusLogger +{ + public class DataItem : ISettingsItem, INotifyPropertyChanged + { + + public DateTime Date { get => _Date; set => SetProperty(ref _Date, value); } + DateTime _Date; + + public int ProcessId { get => _ProcessId; set => SetProperty(ref _ProcessId, value); } + int _ProcessId; + + public string ProcessName { get => _ProcessName; set => SetProperty(ref _ProcessName, value); } + string _ProcessName; + + public string ProcessPath { get => _ProcessPath; set => SetProperty(ref _ProcessPath, value); } + string _ProcessPath; + + public string WindowTitle { get => _WindowTitle; set => SetProperty(ref _WindowTitle, value); } + string _WindowTitle; + + public System.Windows.MessageBoxImage StatusCode { get => _StatusCode; set => SetProperty(ref _StatusCode, value); } + System.Windows.MessageBoxImage _StatusCode; + + #region ■ ISettingsItem + + bool ISettingsItem.Enabled { get => IsEnabled; set => IsEnabled = value; } + private bool IsEnabled; + + public bool IsEmpty => + string.IsNullOrEmpty(ProcessName); + + #endregion + + #region ■ INotifyPropertyChanged + + public event PropertyChangedEventHandler PropertyChanged; + + protected void SetProperty(ref T property, T value, [CallerMemberName] string propertyName = null) + { + property = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + #endregion + + } +} diff --git a/FocusLogger/Common/DataItemType.cs b/FocusLogger/Common/DataItemType.cs new file mode 100644 index 0000000..d508587 --- /dev/null +++ b/FocusLogger/Common/DataItemType.cs @@ -0,0 +1,7 @@ +namespace JocysCom.FocusLogger +{ + public enum DataItemType + { + None = 0, + } +} diff --git a/FocusLogger/Controls/DataListControl.xaml b/FocusLogger/Controls/DataListControl.xaml new file mode 100644 index 0000000..cede415 --- /dev/null +++ b/FocusLogger/Controls/DataListControl.xaml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FocusLogger/Controls/DataListControl.xaml.cs b/FocusLogger/Controls/DataListControl.xaml.cs new file mode 100644 index 0000000..e1d16c4 --- /dev/null +++ b/FocusLogger/Controls/DataListControl.xaml.cs @@ -0,0 +1,171 @@ +using JocysCom.ClassLibrary.ComponentModel; +using JocysCom.ClassLibrary.Controls; +using JocysCom.ClassLibrary.Controls.Themes; +using System; +using System.Data; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Controls; + +namespace JocysCom.FocusLogger.Controls +{ + /// + /// Interaction logic for DataListControl.xaml + /// + public partial class DataListControl : UserControl + { + public DataListControl() + { + InitializeComponent(); + if (ControlsHelper.IsDesignMode(this)) + return; + if (!IsElevated) + { + WindowTiteColumn.Visibility = Visibility.Hidden; + } + // Configure converter. + MainDataGrid.ItemsSource = DataItems; + var gridFormattingConverter = MainDataGrid.Resources.Values.OfType().First(); + gridFormattingConverter.ConvertFunction = _MainDataGridFormattingConverter_Convert; + } + + object _MainDataGridFormattingConverter_Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + var sender = (FrameworkElement)values[0]; + var template = (FrameworkElement)values[1]; + var cell = (DataGridCell)(template ?? sender).Parent; + var value = values[2]; + var item = (DataItem)cell.DataContext; + if (cell.Column == ProcessPathColumn) + { + } + return value; + } + + public SortableBindingList DataItems { get; set; } = new SortableBindingList(); + + private void UserControl_Loaded(object sender, RoutedEventArgs e) + { + if (ControlsHelper.IsDesignMode(this)) + return; + InitTimer(); + //Automation.AddAutomationFocusChangedEventHandler(OnFocusChangedHandler); + } + + private void ClearButton_Click(object sender, RoutedEventArgs e) + { + DataItems.Clear(); + } + + private void UserControl_Unloaded(object sender, RoutedEventArgs e) + { + if (ControlsHelper.IsDesignMode(this)) + return; + //Automation.RemoveAutomationFocusChangedEventHandler(OnFocusChangedHandler); + } + + object AddLock = new object(); + + public bool IsElevated + { + get + { + var id = System.Security.Principal.WindowsIdentity.GetCurrent(); + return id.Owner != id.User; + } + } + + int lastProcessId; + + private void OnFocusChangedHandler(object src, AutomationFocusChangedEventArgs args) + { + if (MainWindow.IsClosing) + return; + var element = src as AutomationElement; + if (element != null) + { + var name = element.Current.Name; + var id = element.Current.AutomationId; + var processId = element.Current.ProcessId; + AddProcess(processId); + } + } + + public void AddProcess(int processId) + { + lock (AddLock) + { + // Add only if process changed. + if (lastProcessId == processId) + return; + lastProcessId = processId; + } + using (var process = Process.GetProcessById(processId)) + { + var item = new DataItem(); + item.Date = DateTime.Now; + item.ProcessId = process.Id; + item.ProcessName = process.ProcessName; + if (processId > 0) + { + item.ProcessPath = process.MainModule?.FileName; + if (IsElevated) + { + try + { + item.WindowTitle = process.MainWindowTitle; + } + catch { } + } + } + ControlsHelper.BeginInvoke(() => + { + DataItems.Insert(0, item); + }); + } + } + + System.Timers.Timer _Timer; + + void InitTimer() + { + _Timer = new System.Timers.Timer(); + _Timer.Elapsed += _Timer_Elapsed; + _Timer.AutoReset = false; + _Timer.Interval = 1; + _Timer.Start(); + } + + private void _Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + if (MainWindow.IsClosing) + return; + _Timer.Start(); + var processId = GetActiveProcessId(); + AddProcess(processId); + } + + public static int GetActiveProcessId() + { + var activatedHandle = GetForegroundWindow(); + if (activatedHandle == IntPtr.Zero) + return 0; // No window is currently activated + int activeProcId; + GetWindowThreadProcessId(activatedHandle, out activeProcId); + return activeProcId; + } + + + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); + + } +} \ No newline at end of file diff --git a/FocusLogger/Documents/Images/JocysCom.FocusLogger.png b/FocusLogger/Documents/Images/JocysCom.FocusLogger.png new file mode 100644 index 0000000..f2a55fe Binary files /dev/null and b/FocusLogger/Documents/Images/JocysCom.FocusLogger.png differ diff --git a/FocusLogger/Documents/Step2_app_sign.bat b/FocusLogger/Documents/Step2_app_sign.bat new file mode 100644 index 0000000..ffe2c0a --- /dev/null +++ b/FocusLogger/Documents/Step2_app_sign.bat @@ -0,0 +1,39 @@ +@ECHO OFF +COPY /Y "..\bin\Debug\net5.0-windows\publish\JocysCom.FocusLogger.exe" "JocysCom.FocusLogger.exe" +CALL:SIG "JocysCom.FocusLogger.exe" +echo. +pause + +GOTO:EOF +::============================================================= +:SIG :: Sign and Timestamp Code +::------------------------------------------------------------- +:: SIGNTOOL.EXE Note: +:: Use the Windows 7 Platform SDK web installer that lets you +:: download just the components you need - so just choose the +:: ".NET developer \ Tools" and deselect everything else. +echo. +IF NOT EXIST "%~1" ( + ECHO "%~1" not exist. Skipping. + GOTO:EOF +) +SET sgt=Tools\signtool.exe +IF NOT EXIST "%sgt%" SET sgt=%ProgramFiles(x86)%\Windows Kits\10\App Certification Kit\signtool.exe +IF NOT EXIST "%sgt%" SET sgt=%ProgramFiles(x86)%\Windows Kits\10\bin\x86\signtool.exe +IF NOT EXIST "%sgt%" SET sgt=%ProgramFiles%\Windows Kits\10\bin\x86\signtool.exe +echo %sgt% +echo. +:: Other options. +set pfx=D:\_Backup\Configuration\SSL\Code Sign - Evaldas Jocys\2020\Evaldas_Jocys.pfx +set d=Jocys.com Focus Logger +set du=https://www.jocys.com +set vsg=http://timestamp.comodoca.com +if not exist "%sgt%" CALL:Error "%sgt%" +if not exist "%~1" CALL:Error "%~1" +if not exist "%pfx%" CALL:Error "%pfx%" +"%sgt%" sign /f "%pfx%" /d "%d%" /du "%du%" /fd sha256 /td sha256 /tr "%vsg%" /v "%~1" +GOTO:EOF + +:Error +echo File doesn't Exist: "%~1" +pause diff --git a/FocusLogger/JocysCom.FocusLogger.csproj b/FocusLogger/JocysCom.FocusLogger.csproj new file mode 100644 index 0000000..8fe656b --- /dev/null +++ b/FocusLogger/JocysCom.FocusLogger.csproj @@ -0,0 +1,42 @@ + + + + WinExe + net5.0-windows + true + true + Jocys.com + Jocys.com + Focus Logger + Find out which process or program is taking the window focus. In-game controls could temporary stop responding if other program steals the focus. + App.ico + 1.0.6 + https://github.com/JocysCom/FocusLogger + https://www.jocys.com + Copyright © Jocys.com 2021 + GNU General Public License v3.0 + + + + embedded + true + + + + + + + + + + + + + + + + + + + + diff --git a/FocusLogger/JocysCom/ComponentModel/BindingListInvoked.cs b/FocusLogger/JocysCom/ComponentModel/BindingListInvoked.cs new file mode 100644 index 0000000..834c03d --- /dev/null +++ b/FocusLogger/JocysCom/ComponentModel/BindingListInvoked.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; + +namespace JocysCom.ClassLibrary.ComponentModel +{ + public class BindingListInvoked : BindingList + { + public BindingListInvoked() : base() { } + + public BindingListInvoked(IList list) + : base(list) { } + + public BindingListInvoked(IEnumerable enumeration) + : base(new List(enumeration)) { } + + public void AddRange(IEnumerable list) + { + foreach (T item in list) + { Add(item); } + } + + #region ISynchronizeInvoker + + public TaskScheduler SynchronizingObject { get; set; } + + delegate void ItemDelegate(int index, T item); + + public bool AsynchronousInvoke { get; set; } + + void Invoke(Delegate method, params object[] args) + { + var so = SynchronizingObject; + if (so == null || !JocysCom.ClassLibrary.Controls.ControlsHelper.InvokeRequired) + { + DynamicInvoke(method, args); + } + else + { + // Note that Control.Invoke(...) is a synchronous action on the main GUI thread, + // and will wait for EnableBackControl() to return. + // so.Invoke(...) line could freeze if main GUI thread is busy and can't give + // attention to any .Invoke requests from background threads. + // + // Main GUI thread could be blocked because: + // a) Modal dialog is up (which means that it's not listening to new requests). + // b) It is checking something in a tight continuous loop. + // c) Main thread crashed because of exception. + // + // Try inserting a Application.DoEvents() in the loop, which will pause + // execution and force the main thread to process messages and any outstanding .Invoke requests. + if (AsynchronousInvoke) + Task.Factory.StartNew(() => + { + DynamicInvoke(method, args); + }, CancellationToken.None, TaskCreationOptions.None, so); + else + { + var task = new Task(() => + { + DynamicInvoke(method, args); + }); + task.RunSynchronously(so); + } + } + } + + object OneChangeAtTheTime = new object(); + + void DynamicInvoke(Delegate method, params object[] args) + { + try + { + lock (OneChangeAtTheTime) + { + method.DynamicInvoke(args); + } + } + catch (Exception ex) + { + // Add data to help with debugging. + var prefix = string.Format("{0}", nameof(BindingListInvoked)) + "."; + ex.Data.Add(prefix + "T", typeof(T).FullName); + ex.Data.Add(prefix + "SynchronizingObject", SynchronizingObject?.GetType().FullName); + ex.Data.Add(prefix + "AsynchronousInvoke", AsynchronousInvoke); + throw; + } + } + + + protected override void RemoveItem(int index) + { + Invoke((Action)base.RemoveItem, index); + } + + protected override void InsertItem(int index, T item) + { + Invoke((ItemDelegate)base.InsertItem, index, item); + } + + protected override void SetItem(int index, T item) + { + Invoke((ItemDelegate)base.SetItem, index, item); + } + + protected override void OnListChanged(ListChangedEventArgs e) + { + Invoke((Action)base.OnListChanged, e); + } + + protected override void OnAddingNew(AddingNewEventArgs e) + { + Invoke((Action)base.OnAddingNew, e); + } + + #endregion + } +} diff --git a/FocusLogger/JocysCom/ComponentModel/PropertyComparer.cs b/FocusLogger/JocysCom/ComponentModel/PropertyComparer.cs new file mode 100644 index 0000000..8b6b83f --- /dev/null +++ b/FocusLogger/JocysCom/ComponentModel/PropertyComparer.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace JocysCom.ClassLibrary.ComponentModel +{ + /// + /// Be.Timvw.Framework.ComponentModel + /// http://betimvwframework.codeplex.com/ + /// + public class PropertyComparer : IComparer + { + private readonly ListSortDescriptionCollection _SortCollection = null; + private readonly PropertyDescriptor _PropDesc = null; + private readonly ListSortDirection _Direction = ListSortDirection.Ascending; + + public PropertyComparer(PropertyDescriptor propDesc, ListSortDirection direction) + { + _PropDesc = propDesc; + _Direction = direction; + } + + public PropertyComparer(ListSortDescriptionCollection sortCollection) + { + _SortCollection = sortCollection; + } + + int IComparer.Compare(T x, T y) + { + return Compare(x, y); + } + + protected int Compare(T x, T y) + { + if (_PropDesc != null) + { + var xValue = _PropDesc.GetValue(x); + var yValue = _PropDesc.GetValue(y); + return CompareValues(xValue, yValue, _Direction); + } + else if (_SortCollection != null && _SortCollection.Count > 0) + return RecursiveCompareInternal(x, y, 0); + else + return 0; + } + + private int CompareValues(object xValue, object yValue, ListSortDirection direction) + { + int retValue; + if (xValue == null && yValue == null) + retValue = 0; + else if (xValue is IComparable) + retValue = ((IComparable)xValue).CompareTo(yValue); + else if (yValue is IComparable) + retValue = ((IComparable)yValue).CompareTo(xValue); + // not comparable, compare String representations + else if (!xValue.Equals(yValue)) + retValue = xValue.ToString().CompareTo(yValue.ToString()); + else + retValue = 0; + return (direction == ListSortDirection.Ascending ? 1 : -1) * retValue; + } + + private int RecursiveCompareInternal(T x, T y, int index) + { + if (index >= _SortCollection.Count) + return 0; + var listSortDesc = _SortCollection[index]; + var xValue = listSortDesc.PropertyDescriptor.GetValue(x); + var yValue = listSortDesc.PropertyDescriptor.GetValue(y); + var retValue = CompareValues(xValue, yValue, listSortDesc.SortDirection); + return (retValue == 0) + ? RecursiveCompareInternal(x, y, ++index) + : retValue; + } + } +} + diff --git a/FocusLogger/JocysCom/ComponentModel/SortableBindingList.cs b/FocusLogger/JocysCom/ComponentModel/SortableBindingList.cs new file mode 100644 index 0000000..86b566c --- /dev/null +++ b/FocusLogger/JocysCom/ComponentModel/SortableBindingList.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace JocysCom.ClassLibrary.ComponentModel +{ + /// + /// Be.Timvw.Framework.ComponentModel + /// http://betimvwframework.codeplex.com/ + /// + /// + [Serializable] + public class SortableBindingList : BindingListInvoked, IBindingListView, IRaiseItemChangedEvents + { + public SortableBindingList() : base() { } + + public SortableBindingList(IList list) + : base(list) { } + + public SortableBindingList(IEnumerable enumeration) + : base(new List(enumeration)) { } + + public static SortableBindingList From(IEnumerable list) + { + return new SortableBindingList(list); + } + + protected override bool SupportsSearchingCore => true; + protected override bool SupportsSortingCore => true; + protected override bool IsSortedCore => _Sorted; + protected override ListSortDirection SortDirectionCore => _SortDirection; + protected override PropertyDescriptor SortPropertyCore => _SortProperty; + + ListSortDescriptionCollection IBindingListView.SortDescriptions => SortDescriptions; + protected ListSortDescriptionCollection SortDescriptions => _SortDescriptions; + + bool IBindingListView.SupportsAdvancedSorting => SupportsAdvancedSorting; + protected bool SupportsAdvancedSorting => true; + + bool IBindingListView.SupportsFiltering => SupportsFiltering; + protected bool SupportsFiltering => true; + + bool IRaiseItemChangedEvents.RaisesItemChangedEvents => RaisesItemChangedEvents; + protected bool RaisesItemChangedEvents => true; + + private bool _Sorted = false; + private bool _Filtered = false; + private string _FilterString = null; + private ListSortDirection _SortDirection = ListSortDirection.Ascending; + + [NonSerialized] + private PropertyDescriptor _SortProperty = null; + + [NonSerialized] + private ListSortDescriptionCollection _SortDescriptions = new ListSortDescriptionCollection(); + private readonly List _OriginalCollection = new List(); + bool IBindingList.AllowNew => CheckReadOnly(); + bool IBindingList.AllowRemove => CheckReadOnly(); + private bool CheckReadOnly() { return !_Sorted && !_Filtered; } + + protected override int FindCore(PropertyDescriptor property, object key) + { + // Simple iteration: + for (var i = 0; i < Count; i++) + { + var item = this[i]; + if (property.GetValue(item).Equals(key)) + return i; + } + return -1; // Not found + } + + protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) + { + _SortDirection = direction; + _SortProperty = property; + var comparer = new PropertyComparer(property, direction); + ApplySortInternal(comparer); + } + + void IBindingListView.ApplySort(ListSortDescriptionCollection sorts) + { + ApplySort(sorts); + } + + protected void ApplySort(ListSortDescriptionCollection sorts) + { + _SortProperty = null; + _SortDescriptions = sorts; + var comparer = new PropertyComparer(sorts); + ApplySortInternal(comparer); + } + + private void ApplySortInternal(PropertyComparer comparer) + { + if (_OriginalCollection.Count == 0) + _OriginalCollection.AddRange(this); + var listRef = Items as List; + if (listRef == null) + return; + listRef.Sort(comparer); + _Sorted = true; + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + protected override void RemoveSortCore() + { + if (!_Sorted) + return; + Clear(); + foreach (var item in _OriginalCollection) + Add(item); + _OriginalCollection.Clear(); + _SortProperty = null; + _SortDescriptions = null; + _Sorted = false; + } + + string IBindingListView.Filter { get { return Filter; } set { Filter = value; } } + + protected string Filter + { + get { return _FilterString; } + set + { + _FilterString = value; + _Filtered = true; + UpdateFilter(); + } + } + + void IBindingListView.RemoveFilter() { RemoveFilter(); } + protected void RemoveFilter() + { + if (!_Filtered) + return; + _FilterString = null; + _Filtered = false; + _Sorted = false; + _SortDescriptions = null; + _SortProperty = null; + Clear(); + foreach (var item in _OriginalCollection) + Add(item); + _OriginalCollection.Clear(); + } + + protected virtual void UpdateFilter() + { + var equalsPos = _FilterString.IndexOf('='); + // Get property name + var propName = _FilterString.Substring(0, equalsPos).Trim(); + // Get filter criteria + var criteria = _FilterString.Substring(equalsPos + 1, + _FilterString.Length - equalsPos - 1).Trim(); + // Strip leading and trailing quotes + criteria = criteria.Trim('\'', '"'); + // Get a property descriptor for the filter property + var propDesc = TypeDescriptor.GetProperties(typeof(T))[propName]; + if (_OriginalCollection.Count == 0) + _OriginalCollection.AddRange(this); + var currentCollection = new List(this); + Clear(); + foreach (var item in currentCollection) + { + var value = propDesc.GetValue(item); + if (string.Format("{0}", value) == criteria) + Add(item); + } + } + + protected override void InsertItem(int index, T item) + { + foreach (PropertyDescriptor propDesc in TypeDescriptor.GetProperties(item)) + { + if (propDesc.SupportsChangeEvents) + propDesc.AddValueChanged(item, OnItemChanged); + } + base.InsertItem(index, item); + } + + protected override void RemoveItem(int index) + { + var item = Items[index]; + var propDescs = TypeDescriptor.GetProperties(item); + foreach (PropertyDescriptor propDesc in propDescs) + { + if (propDesc.SupportsChangeEvents) + propDesc.RemoveValueChanged(item, OnItemChanged); + } + base.RemoveItem(index); + } + + private void OnItemChanged(object sender, EventArgs args) + { + var index = Items.IndexOf((T)sender); + OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index)); + } + + } + +} diff --git a/FocusLogger/JocysCom/Configuration/AssemblyInfo.cs b/FocusLogger/JocysCom/Configuration/AssemblyInfo.cs new file mode 100644 index 0000000..173f3d2 --- /dev/null +++ b/FocusLogger/JocysCom/Configuration/AssemblyInfo.cs @@ -0,0 +1,421 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace JocysCom.ClassLibrary.Configuration +{ + public partial class AssemblyInfo + { + + public AssemblyInfo() + { + Assembly = + Assembly.GetEntryAssembly() ?? + FindEntryAssembly1() ?? + FindEntryAssembly2() ?? + Assembly.GetCallingAssembly() ?? + Assembly.GetExecutingAssembly(); + } + + public static AssemblyInfo _Entry; + public static object _EntryLock = new object(); + public static AssemblyInfo Entry + { + get + { + lock (_EntryLock) + { + if (_Entry == null) + _Entry = new AssemblyInfo(); + return _Entry; + } + } + } + + public static string ExpandPath(string path) + { + path = Environment.ExpandEnvironmentVariables(path); + path = JocysCom.ClassLibrary.Text.Helper.Replace(path, Entry, false); + return path; + } + + public AssemblyInfo(string strValFile) + { + Assembly = Assembly.LoadFile(strValFile); + } + + public AssemblyInfo(Assembly assembly) + { + Assembly = assembly; + } + + #region Entry assembly + + /// + /// Assembly.GetEntryAssembly() returns null in web applications. Mark assembly as the entry assembly + /// by adding this attribute inside Properties\AssemblyInfo.cs file: + /// [assembly: JocysCom.ClassLibrary.Configuration.AssemblyInfo.EntryAssembly] + /// + [AttributeUsage(AttributeTargets.Assembly)] + public sealed class EntryAssemblyAttribute : Attribute { } + + // Method 1 better works on multiple assemblies marked as entry. + Assembly FindEntryAssembly1() + { + var frames = new StackTrace().GetFrames(); + Array.Reverse(frames); + foreach (var frame in frames) + { + var declaringType = frame.GetMethod().DeclaringType; + var assembly = Assembly.GetAssembly(declaringType); + var attribute = assembly.GetCustomAttributes(typeof(EntryAssemblyAttribute), false); + if (attribute.Length > 0) + return assembly; + } + return null; + } + + + // Find on current domain. + Assembly FindEntryAssembly2() + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + var attribute = assembly.GetCustomAttributes(typeof(EntryAssemblyAttribute), false); + if (attribute.Length > 0) + return assembly; + } + return null; + } + + #endregion + + public Assembly Assembly { get; set; } + + DateTime? _BuildDateTime; + object BuildDateTimeLock = new object(); + + public DateTime BuildDateTime + { + get + { + lock (BuildDateTimeLock) + { + if (!_BuildDateTime.HasValue) + _BuildDateTime = GetBuildDateTime(Assembly); + return _BuildDateTime.Value; + } + } + } + + object FullTitleLock = new object(); + string _FullTitle; + public string FullTitle + { + get + { + lock (FullTitleLock) + { + if (string.IsNullOrEmpty(_FullTitle)) + _FullTitle = GetTitle(); + return _FullTitle; + } + } + } + + public string RunMode + { + get + { + if (_RunMode == null) + _RunMode = SettingsParser.Current.Parse("RunMode", ""); + return _RunMode; + } + } + public string _RunMode; + + public string GetTitle(bool showBuild = true, bool showRunMode = true, bool showBuildDate = true, bool showArchitecture = true, bool showDescription = true, int versionNumbers = 3) + { + var s = string.Format("{0} {1} {2}", Company, Product, this.Version.ToString(versionNumbers)); + if (showBuild) + { + // Version = major.minor.build.revision + switch (this.Version.Build) + { + case 0: s += " Alpha"; break; // Alpha Release (AR) + case 1: s += " Beta 1"; break; // Master Beta (MB) + case 2: s += " Beta 2"; break; // Feature Complete (FC) + case 3: s += " Beta 3"; break; // Technical Preview (TP) + case 4: s += " RC"; break; // Release Candidate (RC) + case 5: s += " RTM"; break; // Release to Manufacturing (RTM) + default: break; // General Availability (GA) - Gold + } + } + + var haveRunMode = !string.IsNullOrEmpty(RunMode); + // If run mode is not specified then assume live. + var nonLive = haveRunMode && string.Compare(RunMode, "LIVE", true) != 0; + if (showBuildDate || (showRunMode && nonLive)) + { + s += " ("; + if (showRunMode && nonLive) + { + s += string.Format("{0}", RunMode); + if (showBuildDate) s += " "; + } + if (showBuildDate) + { + s += string.Format("Build: {0:yyyy-MM-dd}", BuildDateTime); + } + s += ")"; + } + if (showArchitecture) + { + switch ((Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetName().ProcessorArchitecture) + { + case ProcessorArchitecture.Amd64: + case ProcessorArchitecture.IA64: + s += " 64-bit"; + break; + case ProcessorArchitecture.X86: + s += " 32-bit"; + break; + default: // Default is MSIL: Any CPU, show nothing/ + break; + } + } + if (showDescription && !string.IsNullOrEmpty(Description) && !s.Contains(Description)) + { + s += " - " + Description; + } + +#if NETSTANDARD // .NET Standard +#elif NETCOREAPP // .NET Core +#else // .NET Framework + + // Add elevated tag. + var identity = System.Security.Principal.WindowsIdentity.GetCurrent(); + var isElevated = identity.Owner != identity.User; + // Add running user. + string windowsDomain = GetWindowsDomainName(); + string windowsUser = GetWindowsUserName(); + string processDomain = Environment.UserDomainName; + string processUser = Environment.UserName; + if (string.Compare(windowsDomain, processDomain, true) != 0 || string.Compare(windowsUser, processUser, true) != 0) + s += string.Format(" ({0}\\{1})", processDomain, processUser); + else if (isElevated) + s += " (Administrator)"; + // if (WinAPI.IsVista && WinAPI.IsElevated() && WinAPI.IsInAdministratorRole) this.Text += " (Administrator)"; +#endif + return s.Trim(); + } + +#if NETSTANDARD // .NET Standard +#elif NETCOREAPP // .NET Core +#else // .NET Framework + + internal partial class NativeMethods + { + [DllImport("wtsapi32.dll")] + internal static extern bool WTSQuerySessionInformationW( + IntPtr hServer, + int SessionId, + int WTSInfoClass, + out IntPtr ppBuffer, + out IntPtr pBytesReturned + ); + + } + + public string GetWindowsDomainName() { return GetInformation(7); } + + public string GetWindowsUserName() { return GetInformation(5); } + + private static string GetInformation(int WTSInfoClass) + { + // Use current context. + var WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; + var p = System.Diagnostics.Process.GetCurrentProcess(); + IntPtr AnswerBytes; + IntPtr AnswerCount; + // Get domain name. + var success = NativeMethods.WTSQuerySessionInformationW( + WTS_CURRENT_SERVER_HANDLE, + p.SessionId, + WTSInfoClass, + out AnswerBytes, + out AnswerCount + ); + return Marshal.PtrToStringUni(AnswerBytes); + } + +#endif + + /// + /// Read build time from the file. This won't work with deterministic builds. + /// + /// + /// The C# compiler (Roslyn) supports deterministic builds since Visual Studio 2015. + /// This means that compiling assemblies under the same conditions (permalink) + /// would produce byte-for-byte equivalent binaries. + /// + public static DateTime GetBuildDateTime(string filePath, TimeZoneInfo tzi = null) + { + // Constants related to the Windows PE file format. + const int PE_HEADER_OFFSET = 60; // 0x3C + const int LINKER_TIMESTAMP_OFFSET = 8; + // Read header from file + byte[] b = new byte[2048]; + Stream s = null; + try + { + s = new FileStream(filePath, FileMode.Open, FileAccess.Read); + s.Read(b, 0, 2048); + } + finally + { + if (s != null) + s.Close(); + } + // Read the linker TimeStamp + var offset = BitConverter.ToInt32(b, PE_HEADER_OFFSET); + var secondsSince1970 = BitConverter.ToInt32(b, offset + LINKER_TIMESTAMP_OFFSET); + var dt = GetDateTime(secondsSince1970); + return dt; + } + + + /// + /// Read build time from the assembly. Workaround is required to work with deterministic builds. + /// + /// + /// You have two options: + /// + /// Option 1: Disable Deterministic build by adding + /// + /// >Deterministic<False>/Deterministic< inside a >PropertyGroup< section of .csproj + /// + /// Option 2: + /// + /// Create "Resources\BuildDate.txt" and set its "Build Action: Embedded Resource" + /// Add to pre-build event to work with latest .NET builds: + /// + /// PowerShell.exe -Command "New-Item -ItemType Directory -Force -Path \"$(ProjectDir)Resources\" | Out-Null" + /// PowerShell.exe -Command "(Get-Date).ToString(\"o\") | Out-File \"$(ProjectDir)Resources\BuildDate.txt\"" + /// + /// Note: + /// The C# compiler (Roslyn) supports deterministic builds since Visual Studio 2015. + /// This means that compiling assemblies under the same conditions (permalink) + /// would produce byte-for-byte equivalent binaries. + /// + public static DateTime GetBuildDateTime(Assembly assembly, TimeZoneInfo tzi = null) + { + if (assembly == null) + throw new ArgumentNullException(nameof(assembly)); + var names = assembly.GetManifestResourceNames(); + var dt = default(DateTime); + foreach (var name in names) + { + if (!name.EndsWith("BuildDate.txt")) + continue; + var stream = assembly.GetManifestResourceStream(name); + using (var reader = new StreamReader(stream)) + { + var date = reader.ReadToEnd(); + dt = DateTime.Parse(date); + dt = TimeZoneInfo.ConvertTime(dt, tzi ?? TimeZoneInfo.Local); + return dt; + } + } +#if NETSTANDARD // .NET Standard +#elif NETCOREAPP // .NET Core +#else // .NET Framework + + // Constants related to the Windows PE file format. + const int PE_HEADER_OFFSET = 60; + const int LINKER_TIMESTAMP_OFFSET = 8; + // Discover the base memory address where our assembly is loaded + var entryModule = assembly.ManifestModule; + var hMod = Marshal.GetHINSTANCE(entryModule); + if (hMod == IntPtr.Zero - 1) + throw new Exception("Failed to get HINSTANCE."); + // Read the linker TimeStamp + var offset = Marshal.ReadInt32(hMod, PE_HEADER_OFFSET); + var secondsSince1970 = Marshal.ReadInt32(hMod, offset + LINKER_TIMESTAMP_OFFSET); + dt = GetDateTime(secondsSince1970); +#endif + return dt; + } + + /// + /// Convert the TimeStamp to a DateTime + /// + static DateTime GetDateTime(int secondsSince1970, TimeZoneInfo tzi = null) + { + var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var linkTimeUtc = epoch.AddSeconds(secondsSince1970); + return TimeZoneInfo.ConvertTimeFromUtc(linkTimeUtc, tzi ?? TimeZoneInfo.Local); + } + + public string AssemblyPath + { + get + { + string codeBase = Assembly.Location; + UriBuilder uri = new UriBuilder(codeBase); + string path = Uri.UnescapeDataString(uri.Path); + return path; + } + } + + public string AssemblyFullName { get { return Assembly.GetName().FullName.ToString(); } } + public string AssemblyName { get { return Assembly.GetName().Name.ToString(); } } + public string CodeBase { get { return Assembly.Location; } } + + public string Company { get { return GetAttribute(a => a.Company); } } + public string Product { get { return GetAttribute(a => a.Product); } } + public string Copyright { get { return GetAttribute(a => a.Copyright); } } + public string Trademark { get { return GetAttribute(a => a.Trademark); } } + public string Title { get { return GetAttribute(a => a.Title); } } + public string Description { get { return GetAttribute(a => a.Description); } } + public string Configuration { get { return GetAttribute(a => a.Configuration); } } + public string FileVersion { get { return GetAttribute(a => a.Version); } } + public string ProductGuid { get { return GetAttribute(a => a.Value); } } + + public Version Version { get { return Assembly.GetName().Version; } } + + string GetAttribute(Func value) where T : Attribute + { + T attribute = (T)Attribute.GetCustomAttribute(Assembly, typeof(T)); + return attribute == null + ? "" + : value.Invoke(attribute); + } + + public string GetAppDataPath(bool userLevel, string format, params object[] args) + { + // Get writable application folder. + var specialFolder = userLevel + ? Environment.SpecialFolder.ApplicationData + : Environment.SpecialFolder.CommonApplicationData; + var folder = string.Format("{0}\\{1}\\{2}", + Environment.GetFolderPath(specialFolder), + Company, + Product); + // Get file name. + var file = string.Format(format, args); + var path = Path.Combine(folder, file); + return path; + } + + public FileInfo GetAppDataFile(bool userLevel, string format, params object[] args) + { + var path = GetAppDataPath(userLevel, format, args); + return new FileInfo(path); + } + + } +} diff --git a/FocusLogger/JocysCom/Configuration/ISettingsData.cs b/FocusLogger/JocysCom/Configuration/ISettingsData.cs new file mode 100644 index 0000000..f55f22c --- /dev/null +++ b/FocusLogger/JocysCom/Configuration/ISettingsData.cs @@ -0,0 +1,17 @@ +using System.Collections; +using System.IO; + +namespace JocysCom.ClassLibrary.Configuration +{ + public interface ISettingsData + { + bool ResetToDefault(); + void Save(); + void SaveAs(string fileName); + void Load(); + void LoadFrom(string fileName); + FileInfo XmlFile { get; } + IList Items { get; } + + } +} diff --git a/FocusLogger/JocysCom/Configuration/ISettingsItem.cs b/FocusLogger/JocysCom/Configuration/ISettingsItem.cs new file mode 100644 index 0000000..e68b12c --- /dev/null +++ b/FocusLogger/JocysCom/Configuration/ISettingsItem.cs @@ -0,0 +1,10 @@ +namespace JocysCom.ClassLibrary.Configuration +{ + public interface ISettingsItem + { + bool Enabled { get; set; } + + bool IsEmpty { get; } + + } +} diff --git a/FocusLogger/JocysCom/Configuration/SettingsData.cs b/FocusLogger/JocysCom/Configuration/SettingsData.cs new file mode 100644 index 0000000..7a960b6 --- /dev/null +++ b/FocusLogger/JocysCom/Configuration/SettingsData.cs @@ -0,0 +1,323 @@ +using JocysCom.ClassLibrary.ComponentModel; +using JocysCom.ClassLibrary.Runtime; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Text; +using System.Windows.Forms; +using System.Xml; +using System.Xml.Serialization; + +namespace JocysCom.ClassLibrary.Configuration +{ + [Serializable, XmlRoot("Data"), DataContract] + public class SettingsData : ISettingsData + { + + public SettingsData() + { + Initialize(); + } + + public SettingsData(string fileName, bool userLevel = false, string comment = null, Assembly assembly = null) + { + Initialize(fileName, userLevel, comment, assembly); + } + + /// + /// + /// + /// + /// + /// + /// Used to get company and product name. + void Initialize(string fileName = null, bool userLevel = false, string comment = null, Assembly assembly = null) + { + Items = new SortableBindingList(); + _Comment = comment; + var company = Application.CompanyName; + var product = Application.ProductName; + if (assembly != null) + { + company = ((AssemblyCompanyAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyCompanyAttribute))).Company; + product = ((AssemblyProductAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyProductAttribute))).Product; + } + // Get writable application folder. + var specialFolder = userLevel + ? Environment.SpecialFolder.ApplicationData + : Environment.SpecialFolder.CommonApplicationData; + var folder = string.Format("{0}\\{1}\\{2}", Environment.GetFolderPath(specialFolder), company, product); + // Get file name. + var file = string.IsNullOrEmpty(fileName) + ? string.Format("{0}.xml", typeof(T).Name) + : fileName; + var path = Path.Combine(folder, file); + _XmlFile = new FileInfo(path); + } + + [XmlIgnore] + public FileInfo XmlFile { get { return _XmlFile; } set { _XmlFile = value; } } + + [NonSerialized] + protected FileInfo _XmlFile; + + [NonSerialized] + protected string _Comment; + + [DataMember] + public SortableBindingList Items { get; set; } + + [NonSerialized] + private object _SyncRoot; + + // Synchronization root for this object. + public virtual object SyncRoot + { + get + { + if (_SyncRoot == null) + System.Threading.Interlocked.CompareExchange(ref _SyncRoot, new object(), null); + return _SyncRoot; + } + } + + public T[] ItemsToArraySyncronized() + { + lock (SyncRoot) + return Items.ToArray(); + } + + [XmlIgnore] + System.Collections.IList ISettingsData.Items { get { return Items; } } + + public delegate void ApplyOrderDelegate(SettingsData source); + + [XmlIgnore, NonSerialized] + public ApplyOrderDelegate ApplyOrder; + + /// + /// File Version. + /// + [XmlAttribute] + public int Version { get; set; } + + [XmlIgnore, NonSerialized] + object initialFileLock = new object(); + + [XmlIgnore, NonSerialized] + object saveReadFileLock = new object(); + + public event EventHandler Saving; + + public void SaveAs(string fileName) + { + var ev = Saving; + if (ev != null) + ev(this, new EventArgs()); + var items = ItemsToArraySyncronized(); + lock (saveReadFileLock) + { + var type = items.FirstOrDefault()?.GetType(); + if (type != null && type.Name.EndsWith("EntityObject")) + { + var pi = type.GetProperty("EntityKey"); + for (int i = 0; i < items.Length; i++) + pi.SetValue(items[i], null); + } + var fi = new FileInfo(fileName); + if (!fi.Directory.Exists) + fi.Directory.Create(); + byte[] bytes; + bytes = Serializer.SerializeToXmlBytes(this, Encoding.UTF8, true, _Comment); + if (fi.Name.EndsWith(".gz")) + bytes = SettingsHelper.Compress(bytes); + SettingsHelper.WriteIfDifferent(fi.FullName, bytes); + } + } + + public void Save() + { + SaveAs(_XmlFile.FullName); + } + + /// Remove with SyncRoot lock. + public void Remove(params T[] items) + { + lock (SyncRoot) + foreach (var item in items) + Items.Remove(item); + } + + /// Add with SyncRoot lock. + public void Add(params T[] items) + { + lock (SyncRoot) + foreach (var item in items) + Items.Add(item); + } + + public delegate IList ValidateDataDelegate(IList items); + + [XmlIgnore, NonSerialized] + public ValidateDataDelegate ValidateData; + + public void Load() + { + LoadFrom(_XmlFile.FullName); + } + + public void LoadFrom(string fileName) + { + var settingsLoaded = false; + var fi = new FileInfo(fileName); + // If configuration file exists then... + if (fi.Exists) + { + SettingsData data = null; + // Try to read file until success. + while (true) + { + // Deserialize and load data. + lock (saveReadFileLock) + { + try + { + var bytes = File.ReadAllBytes(fi.FullName); + data = DeserializeData(bytes, fi.Name.EndsWith(".gz")); + break; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + var backupFile = fi.FullName + ".bak"; + var sb = new StringBuilder(); + sb.AppendFormat("{0} file has become corrupted.\r\n\r\n" + + "Reason: " + ex.Message + "\r\n\r\n" + + "Program must reset {0} file in order to continue.\r\n\r\n" + + " Click [Yes] to reset and continue.\r\n" + + " Click [No] if you wish to attempt manual repair.\r\n\r\n" + + " File: {1}", fi.Name, fi.FullName); + sb.AppendLine(); + sb.Append('-', 64); + sb.AppendLine(); + sb.AppendLine(ex.ToString()); + var caption = string.Format("Corrupt {0} of {1}", fi.Name, Application.ProductName); + //var form = new MessageBox(); + //form.StartPosition = FormStartPosition.CenterParent; + var text = sb.ToString(); + var result = MessageBox.Show(text, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Error); + if (result == DialogResult.Yes) + { + if (File.Exists(backupFile)) + { + File.Copy(backupFile, fi.FullName, true); + fi.Refresh(); + } + else + { + File.Delete(fi.FullName); + break; + } + } + else + { + // Avoid the inevitable crash by killing application first. + Process.GetCurrentProcess().Kill(); + return; + } + } + } + } + // If data read was successful then... + if (data != null) + { + // Reorder data of order method exists. + var ao = ApplyOrder; + if (ao != null) + ao(data); + Version = data.Version; + LoadAndValidateData(data.Items); + settingsLoaded = true; + } + } + // If settings failed to load then... + if (!settingsLoaded) + { + ResetToDefault(); + Save(); + } + } + + void LoadAndValidateData(IList data) + { + // Clear original data. + Items.Clear(); + if (data == null) + data = new SortableBindingList(); + // Filter data if filter method exists. + var fl = ValidateData; + var items = (fl == null) + ? data + : fl(data); + for (int i = 0; i < items.Count; i++) + Items.Add(items[i]); + } + + public bool ResetToDefault() + { + // Clear original data. + Items.Clear(); + SettingsData data = null; + var assemblies = new List(); + var exasm = Assembly.GetExecutingAssembly(); + var enasm = Assembly.GetEntryAssembly(); + assemblies.Add(exasm); + if (enasm != null && exasm != enasm) + assemblies.Add(enasm); + var success = false; + for (int a = 0; a < assemblies.Count; a++) + { + var assembly = assemblies[a]; + var names = assembly.GetManifestResourceNames(); + // Get compressed resource name. + var name = names.FirstOrDefault(x => x.EndsWith(_XmlFile.Name + ".gz", StringComparison.OrdinalIgnoreCase)); + if (string.IsNullOrEmpty(name)) + { + // Get uncompressed resource name. + name = names.FirstOrDefault(x => x.EndsWith(_XmlFile.Name, StringComparison.OrdinalIgnoreCase)); + } + // If internal preset was found. + if (!string.IsNullOrEmpty(name)) + { + var resource = assembly.GetManifestResourceStream(name); + var sr = new StreamReader(resource); + byte[] bytes; + using (var memstream = new MemoryStream()) + { + sr.BaseStream.CopyTo(memstream); + bytes = memstream.ToArray(); + } + sr.Dispose(); + data = DeserializeData(bytes, name.EndsWith(".gz", StringComparison.OrdinalIgnoreCase)); + success = true; + break; + } + } + LoadAndValidateData(data == null ? null : data.Items); + return success; + } + + SettingsData DeserializeData(byte[] bytes, bool compressed) + { + if (compressed) + bytes = SettingsHelper.Decompress(bytes); + var data = Serializer.DeserializeFromXmlBytes>(bytes); + return data; + } + } +} diff --git a/FocusLogger/JocysCom/Configuration/SettingsHelper.cs b/FocusLogger/JocysCom/Configuration/SettingsHelper.cs new file mode 100644 index 0000000..1064bf2 --- /dev/null +++ b/FocusLogger/JocysCom/Configuration/SettingsHelper.cs @@ -0,0 +1,114 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; + +namespace JocysCom.ClassLibrary.Configuration +{ + public static partial class SettingsHelper + { + #region Compression + + public static byte[] Compress(byte[] bytes) + { + int numRead; + var srcStream = new MemoryStream(bytes); + var dstStream = new MemoryStream(); + srcStream.Position = 0; + var stream = new GZipStream(dstStream, CompressionMode.Compress); + byte[] buffer = new byte[0x1000]; + while (true) + { + numRead = srcStream.Read(buffer, 0, buffer.Length); + if (numRead == 0) break; + stream.Write(buffer, 0, numRead); + } + stream.Close(); + srcStream.Close(); + return dstStream.ToArray(); + } + + public static byte[] Decompress(byte[] bytes) + { + int numRead; + var srcStream = new MemoryStream(bytes); + var dstStream = new MemoryStream(); + srcStream.Position = 0; + var stream = new GZipStream(srcStream, CompressionMode.Decompress); + var buffer = new byte[0x1000]; + while (true) + { + numRead = stream.Read(buffer, 0, buffer.Length); + if (numRead == 0) break; + dstStream.Write(buffer, 0, numRead); + } + dstStream.Close(); + stream.Close(); + return dstStream.ToArray(); + } + + #endregion + + #region Writing + + public static bool IsDifferent(string name, byte[] bytes) + { + if (bytes == null) + throw new ArgumentNullException(nameof(bytes)); + var fi = new FileInfo(name); + var isDifferent = false; + // If file doesn't exists or file size is different then... + if (!fi.Exists || fi.Length != bytes.Length) + { + isDifferent = true; + } + else + { + // Compare checksums. + var algorithm = System.Security.Cryptography.SHA256.Create(); + var byteHash = algorithm.ComputeHash(bytes); + var fileBytes = File.ReadAllBytes(fi.FullName); + var fileHash = algorithm.ComputeHash(fileBytes); + isDifferent = !byteHash.SequenceEqual(fileHash); + } + return isDifferent; + } + + public static bool WriteIfDifferent(string name, byte[] bytes) + { + var isDifferent = IsDifferent(name, bytes); + if (isDifferent) + File.WriteAllBytes(name, bytes); + return isDifferent; + } + + public static string ReadFileContent(string name, out Encoding encoding) + { + using (var reader = new System.IO.StreamReader(name, true)) + { + encoding = reader.CurrentEncoding; + return reader.ReadToEnd(); + } + } + + /// + /// Get file content with encoding header. + /// + public static byte[] GetFileContentBytes(string content, Encoding encoding = null) + { + var ms = new MemoryStream(); + // Encoding header will be added to content. + var sw = new StreamWriter(ms, encoding); + sw.Write(content); + sw.Flush(); + var bytes = ms.ToArray(); + sw.Dispose(); + return bytes; + } + + #endregion + + + } +} diff --git a/FocusLogger/JocysCom/Configuration/SettingsParser.cs b/FocusLogger/JocysCom/Configuration/SettingsParser.cs new file mode 100644 index 0000000..8197af4 --- /dev/null +++ b/FocusLogger/JocysCom/Configuration/SettingsParser.cs @@ -0,0 +1,149 @@ +using System; +using System.Linq; +#if NETCOREAPP // .NET Core +#elif NETSTANDARD // .NET Standard +using System.Globalization; +using System.Net; +using System.Collections.Generic; +using System.IO; +#else // .NET Framework... +using System.Configuration; +#endif +#if __MOBILE__ + using Xamarin.Forms; +#endif + +namespace JocysCom.ClassLibrary.Configuration +{ + /// + /// Parse application setting values. + /// + public partial class SettingsParser + { + + public SettingsParser(string configPrefix = "") + { + ConfigPrefix = configPrefix; + } + + public string ConfigPrefix { get; set; } + public static SettingsParser Current { get; } = new SettingsParser(); + + /// Parse all IConvertible types, like value types, with one function. + public T Parse(string name, T defaultValue = default(T)) + { + if (_GetValue == null) + return defaultValue; + var v = _GetValue(ConfigPrefix + name); + return ParseValue(v, defaultValue); + } + + public static object ParseValue(Type t, string v, object defaultValue = null) + { + if (t == null) + throw new ArgumentNullException(nameof(t)); + if (v == null) + return defaultValue; + if (typeof(System.Drawing.Color).IsAssignableFrom(t)) + return System.Drawing.Color.FromName(v); + // Get Parse method with string parameter. + var m = t.GetMethod("Parse", new[] { typeof(string) }); + if (m != null) + return m.Invoke(null, new[] { v }); + //if (typeof(IPAddress).IsAssignableFrom(t)) + // return IPAddress.Parse(v); + //if (typeof(TimeSpan).IsAssignableFrom(t)) + // return TimeSpan.Parse(v, CultureInfo.InvariantCulture); + if (t.IsEnum) + return Enum.Parse(t, v, true); + // If type can be converted then convert. + if (typeof(IConvertible).IsAssignableFrom(t)) + return System.Convert.ChangeType(v, t); + return defaultValue; + } + + public static T TryParseValue(string v, T defaultValue = default(T)) + { + try + { + return (T)ParseValue(typeof(T), v, defaultValue); + } + catch (Exception) + { + return defaultValue; + } + } + + public static T ParseValue(string v, T defaultValue = default(T)) + { + return (T)ParseValue(typeof(T), v, defaultValue); + } + +#if NETSTANDARD // If .NET Standard (Xamarin) preprocessor directive is set then... + + string GetValue(string name) + { + if (_GetValue != null) + return _GetValue(ConfigPrefix + name); +#if __MOBILE__ + + var p = Application.Current.Properties; + var key = ConfigPrefix + name; + if (!p.Keys.Contains(key)) + { + if (EmbeddedAppSettings == null) + ReadEmbeddedSettings(); + if (EmbeddedAppSettings.Keys.Contains(key)) + return EmbeddedAppSettings[key]; + return null; + } + return (string)p[key]; +#else + return null; +#endif + } + +#if __MOBILE__ + + public static Dictionary EmbeddedAppSettings; + + public static void ReadEmbeddedSettings() + { + EmbeddedAppSettings = new Dictionary(); + var assembly = typeof(SettingsParser).Assembly; + var names = assembly.GetManifestResourceNames(); + var name = names.FirstOrDefault(x => x.EndsWith("App.config")); + if (string.IsNullOrEmpty(name)) + return; + var stream = assembly.GetManifestResourceStream(name); + using (var reader = new StreamReader(stream)) + { + var doc = XDocument.Parse(reader.ReadToEnd()); + var items = doc + .Element("configuration") + .Element("appSettings") + .Elements("add").ToList(); + foreach (var item in items) + { + var k = item.Attribute("key").Value; + var v = item.Attribute("value").Value; + EmbeddedAppSettings.Add(k, v); + } + } + } + +#endif + +#elif NETCOREAPP // if .NET Core preprocessor directive is set then... + + public static Func _GetValue; + +#else // NETFRAMEWORK - .NET Framework... + + public static Func _GetValue = (name) + => ConfigurationManager.AppSettings[name]; + +#endif + + } +} diff --git a/FocusLogger/JocysCom/Controls/BaseWithHeaderManager.cs b/FocusLogger/JocysCom/Controls/BaseWithHeaderManager.cs new file mode 100644 index 0000000..a77cd98 --- /dev/null +++ b/FocusLogger/JocysCom/Controls/BaseWithHeaderManager.cs @@ -0,0 +1,192 @@ +using JocysCom.ClassLibrary.Controls.Themes; +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace JocysCom.ClassLibrary.Controls +{ + public class BaseWithHeaderManager : IBaseWithHeaderControl, IDisposable + { + + public BaseWithHeaderManager(Label headLabel, TextBlock bodyLabel, ContentControl leftIcon, ContentControl rightIcon, ContentControl control) + { + _Control = control; + _HeadLabel = headLabel; + defaultHead = headLabel.Content as string; + _BodyLabel = bodyLabel; + defaultBody = bodyLabel.Text; + _LeftIcon = leftIcon; + _RightIcon = rightIcon; + _RightIconOriginalContent = _RightIcon.Content; + _RotateTransform = new RotateTransform(); + _RightIcon.RenderTransform = _RotateTransform; + _RightIcon.RenderTransformOrigin = new Point(0.5, 0.5); + RotateTimer = new System.Timers.Timer(); + RotateTimer.Interval = 25; + RotateTimer.Elapsed += RotateTimer_Elapsed; + } + + string defaultHead; + Label _HeadLabel; + string defaultBody; + TextBlock _BodyLabel; + ContentControl _LeftIcon; + ContentControl _RightIcon; + object _RightIconOriginalContent; + ContentControl _Control; + private readonly object TasksLock = new object(); + public readonly BindingList Tasks = new BindingList(); + + /// Activate busy spinner. + public void AddTask(T name) + { + lock (TasksLock) + { + if (!Tasks.Contains(name)) + Tasks.Add(name); + UpdateIcon(); + } + } + + /// Deactivate busy spinner if all tasks are gone. + public void RemoveTask(T name) + { + lock (TasksLock) + { + if (Tasks.Contains(name)) + Tasks.Remove(name); + UpdateIcon(); + } + } + + public void UpdateIcon() + { + if (Tasks.Count > 0) + { + _RightIcon.Content = Icons.Current[Icons.Icon_ProcessRight]; + _RightIcon.RenderTransform = _RotateTransform; + RotateTimer.Start(); + } + else + { + RotateTimer.Stop(); + _RightIcon.RenderTransform = null; + _RotateTransform.Angle = 0; + _RightIcon.Content = _RightIconOriginalContent; + } + } + + RotateTransform _RotateTransform; + System.Timers.Timer RotateTimer; + + private void RotateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { +#pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs + _RightIcon.Dispatcher.Invoke(() => +#pragma warning restore VSTHRD001 // Avoid legacy thread switching APIs + { + var angle = (_RotateTransform.Angle + 2) % 360; + _RotateTransform.Angle = angle; + }); + } + + public void SetHead(string format, params object[] args) + { + // Apply format. + if (format == null) + format = defaultHead; + else if (args.Length > 0) + format = string.Format(format, args); + if (_HeadLabel.Content as string != format) + _HeadLabel.Content = format; + } + + public void SetTitle(string format, params object[] args) + { + if (_Control is Window w) + { + w.Title = (args.Length == 0) + ? format + : string.Format(format, args); + } + } + + public void SetBodyError(string content, params object[] args) + { + // Apply format. + if (content == null) + content = defaultBody; + else if (args.Length > 0) + content = string.Format(content, args); + // Set info with time. + SetBody(MessageBoxImage.Error, "{0: yyyy-MM-dd HH:mm:ss}: {1}", DateTime.Now, content); + } + + public void SetBodyInfo(string content, params object[] args) + { + // Apply format. + if (content == null) + content = defaultBody; + else if (args.Length > 0) + content = string.Format(content, args); + // Set info with time. + SetBody(MessageBoxImage.Information, content); + } + + public void SetBody(MessageBoxImage image, string content = null, params object[] args) + { + if (content == null) + content = defaultBody; + else if (args.Length > 0) + content = string.Format(content, args); + _BodyLabel.Text = content; + // Update body colors. + switch (image) + { + case MessageBoxImage.Error: + _BodyLabel.Foreground = new SolidColorBrush(Colors.DarkRed); + _LeftIcon.Content = Icons.Current[Icons.Icon_Error]; + break; + case MessageBoxImage.Question: + _BodyLabel.Foreground = new SolidColorBrush(Colors.DarkBlue); + _LeftIcon.Content = Icons.Current[Icons.Icon_Question]; + break; + case MessageBoxImage.Warning: + _BodyLabel.Foreground = new SolidColorBrush(Colors.DarkOrange); + _LeftIcon.Content = Icons.Current[Icons.Icon_Warning]; + break; + default: + _BodyLabel.Foreground = SystemColors.ControlTextBrush; + _LeftIcon.Content = Icons.Current[Icons.Icon_Information]; + break; + } + } + + #region ■ IDisposable + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public bool IsDisposing; + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + IsDisposing = true; + // Free managed resources. + _Control = null; + _HeadLabel = null; + _BodyLabel = null; + _LeftIcon = null; + } + } + + #endregion + } +} diff --git a/FocusLogger/JocysCom/Controls/ControlsHelper.WPF.cs b/FocusLogger/JocysCom/Controls/ControlsHelper.WPF.cs new file mode 100644 index 0000000..e53730f --- /dev/null +++ b/FocusLogger/JocysCom/Controls/ControlsHelper.WPF.cs @@ -0,0 +1,781 @@ +using System.Threading.Tasks; +using System.Windows; +using System.Linq; +using System.ComponentModel; +using System.Windows.Controls; +using System; +using System.Windows.Media; +using System.IO; +using System.Windows.Documents; +using System.Collections.Generic; +using System.Xml; +using System.Data; +using System.Windows.Controls.Primitives; + +namespace JocysCom.ClassLibrary.Controls +{ + public partial class ControlsHelper + { + public static void EnableWithDelay(UIElement control) + { + Task.Run(async delegate { + await Task.Delay(500).ConfigureAwait(true); + control.Dispatcher.Invoke(() => control.IsEnabled = true); + }); + } + + /// + /// Set form TopMost if one of the application forms is top most. + /// + /// + public static void CheckTopMost(Window win) + { + // If this form is not set as TopMost but one of the application forms is on TopMost then... + // Make this dialog form TopMost too or user won't be able to access it. + if (!win.Topmost && System.Windows.Forms.Application.OpenForms.Cast().Any(x => x.TopMost)) + win.Topmost = true; + } + + public static void AutoSizeByOpenForms(Window win, int addSize = -64) + { + var form = System.Windows.Forms.Application.OpenForms.Cast().First(); + win.Width = form.Width + addSize; + win.Height = form.Height + addSize; + win.Top = form.Top - addSize / 2; + win.Left = form.Left - addSize / 2; + } + + private static bool? _IsDesignModeWPF; + + public static bool IsDesignMode(UIElement component) + { + if (!_IsDesignModeWPF.HasValue) + _IsDesignModeWPF = IsDesignMode1(component); + return _IsDesignModeWPF.Value; + } + + private static bool IsDesignMode1(UIElement component) + { + // Check 1. + if (DesignerProperties.GetIsInDesignMode(component)) + return true; + //If WPF hosted in WinForms. + var ea = System.Reflection.Assembly.GetEntryAssembly(); + if (ea != null && ea.Location.Contains("VisualStudio")) + return true; + //If WPF hosted in WinForms. + ea = System.Reflection.Assembly.GetExecutingAssembly(); + if (ea != null && ea.Location.Contains("VisualStudio")) + return true; + return false; + } + + public static T Clone(T o) + { + var sb = new System.Text.StringBuilder(); + var writer = XmlWriter.Create(sb, new XmlWriterSettings + { + Indent = true, + ConformanceLevel = ConformanceLevel.Fragment, + OmitXmlDeclaration = true, + NamespaceHandling = NamespaceHandling.OmitDuplicates, + }); + var manager = new System.Windows.Markup.XamlDesignerSerializationManager(writer); + manager.XamlWriterMode = System.Windows.Markup.XamlWriterMode.Expression; + System.Windows.Markup.XamlWriter.Save(o, manager); + var stringReader = new StringReader(sb.ToString()); + var xmlReader = XmlReader.Create(stringReader); + var item = System.Windows.Markup.XamlReader.Load(xmlReader); + if (item == null) + throw new ArgumentNullException("Could not be cloned."); + return (T)item; + } + + /// + /// Change value if it is different only. + /// This helps not to trigger control events when doing frequent events. + /// + public static void SetText(Label control, string format, params object[] args) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + var text = (args == null) + ? format + : string.Format(format, args); + if (control.Content as string != text) + control.Content = text; + } + + + /// + /// Change value if it is different only. + /// This helps not to trigger control events when doing frequent events. + /// + public static void SetText(GroupBox control, string format, params object[] args) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + var text = (args == null) + ? format + : string.Format(format, args); + if (control.Header as string != text) + control.Header = text; + } + + /// + /// Change value if it is different only. + /// This helps not to trigger control events when doing frequent events. + /// + public static void SetText(TextBox control, string format, params object[] args) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + var text = (args == null) + ? format ?? "" + : string.Format(format ?? "", args); + if (control.Text != text) + control.Text = text; + } + + /// + /// Change value if it is different only. + /// This helps not to trigger control events when doing frequent events. + /// + public static void SetText(TextBlock control, string format, params object[] args) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + var text = (args == null) + ? format ?? "" + : string.Format(format ?? "", args); + if (control.Text != text) + control.Text = text; + } + + public static void SetTextFromResource(RichTextBox box, byte[] rtf) + { + var ms = new MemoryStream(rtf); + var textRange = new TextRange(box.Document.ContentStart, box.Document.ContentEnd); + textRange.Load(ms, DataFormats.Rtf); + ms.Dispose(); + box.Document.PagePadding = new Thickness(8); + box.IsDocumentEnabled = true; + HookHyperlinks(box, null); + } + + /// + /// Change value if it is different only. + /// This helps not to trigger control events when doing frequent events. + /// + public static void SetChecked(System.Windows.Controls.Primitives.ToggleButton control, bool check) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + if (control.IsChecked != check) + control.IsChecked = check; + } + + /// + /// Change value if it is different only. + /// This helps not to trigger control events when doing frequent events. + public static void SetEnabled(UIElement control, bool enabled) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + if (control.IsEnabled != enabled) + control.IsEnabled = enabled; + } + + /// + /// Change value if it is different only. + /// This helps not to trigger control events when doing frequent events. + public static void SetVisible(UIElement control, bool enabled) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + var visibility = enabled ? Visibility.Visible : Visibility.Collapsed; + if (control.Visibility != visibility) + control.Visibility = visibility; + } + + /// + /// Convert Bitmap to image source. + /// + public static ImageSource GetImageSource(System.Drawing.Bitmap bitmap) + { + var bi = new System.Windows.Media.Imaging.BitmapImage(); + var ms = new MemoryStream(); + bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + bi.BeginInit(); + bi.CacheOption = System.Windows.Media.Imaging.BitmapCacheOption.OnLoad; + bi.StreamSource = ms; + bi.EndInit(); + ms.Dispose(); + return bi; + } + + private static void HookHyperlinks(object sender, TextChangedEventArgs e) + { + var doc = (sender as RichTextBox).Document; + for (var position = doc.ContentStart; + position != null && position.CompareTo(doc.ContentEnd) <= 0; + position = position.GetNextContextPosition(LogicalDirection.Forward)) + { + if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) + { + if (position.Parent is Hyperlink link) + link.RequestNavigate += link_RequestNavigate; + else if (position.Parent is Span span) + { + var range = new TextRange(span.ContentStart, span.ContentEnd); + if (Uri.TryCreate(range.Text, UriKind.Absolute, out var uriResult)) + { + if (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps) + { + var h = new Hyperlink(range.Start, range.End); + h.RequestNavigate += link_RequestNavigate; + h.NavigateUri = new Uri(range.Text); + h.Cursor = System.Windows.Input.Cursors.Hand; + } + } + } + } + } + } + private static void link_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) + { + var link = (Hyperlink)sender; + OpenUrl(link.NavigateUri.AbsoluteUri); + e.Handled = true; + } + + #region IsVisibleToUser + + public static Point[] GetPoints(Control control, bool relative = false) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + var pos = relative + ? new Point(0, 0) + // Get control position on the screen + : control.PointToScreen(new Point(0, 0)); + var pointsToCheck = + new Point[] + { + // Top-Left. + pos, + // Top-Right. + new Point(pos.X + control.ActualWidth - 1, pos.Y), + // Bottom-Left. + new Point(pos.X, pos.Y + control.ActualHeight - 1), + // Bottom-Right. + new Point(pos.X + control.ActualWidth - 1, pos.Y + control.ActualHeight - 1), + // Middle-Centre. + new Point(pos.X + control.ActualWidth/2, pos.Y + control.ActualHeight/2) + }; + return pointsToCheck; + } + + /* + public static bool IsControlVisibleToUser(Control control) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + var handle = (PresentationSource.FromVisual(control) as System.Windows.Interop.HwndSource)?.Handle; + if (!handle.HasValue) + return false; + var children = GetAll(control, true); + // Return true if any of the controls is visible. + var pointsToCheck = GetPoints(control, true); + foreach (var p in pointsToCheck) + { + //var hwnd = NativeMethods.WindowFromPoint(p); + //if (hwnd == IntPtr.Zero) + // continue; + var result = VisualTreeHelper.HitTest(control, p); + if (result == null) + continue; + if (children.Contains(result.VisualHit)) + return true; + //var other = Control.FromChildHandle(hwnd); + //if (other == null) + // continue; + //if (GetAll(control, null, true).Contains(other)) + } + return false; + } + */ + /// + /// Get parent control of specific type. + /// + public static T GetParent(DependencyObject control, bool includeTop = false) where T : class + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + var parent = control; + while (parent != null) + { + if (parent is T && (includeTop || parent != control)) + return (T)(object)parent; + var p = VisualTreeHelper.GetParent(parent); + if (p == null) + p = LogicalTreeHelper.GetParent(parent); + parent = p; + } + return null; + } + + + /// + /// Get all child controls. + /// + public static IEnumerable GetAll(DependencyObject control, Type type = null, bool includeTop = false) + { + if (control == null) + throw new ArgumentNullException(nameof(control)); + // Create new list. + var controls = new List(); + // Add top control if required. + if (includeTop) + controls.Add(control); + var visual = control as Visual; + if (visual != null) + { + // If control contains visual children then... + var childrenCount = VisualTreeHelper.GetChildrenCount(control); + for (int i = 0; i < childrenCount; i++) + { + var child = VisualTreeHelper.GetChild(control, i); + var children = GetAll(child, null, true); + controls.AddRange(children); + } + } + // Get logical children. + var logicalChildren = LogicalTreeHelper.GetChildren(control).OfType().ToList(); + for (int i = 0; i < logicalChildren.Count; i++) + { + var child = logicalChildren[i]; + var children = GetAll(child, null, true); + controls.AddRange(children); + } + // If type filter is not set then... + return (type == null) + ? controls + : controls.Where(x => type.IsInterface ? x.GetType().GetInterfaces().Contains(type) : type.IsAssignableFrom(x.GetType())); + } + + /// + /// Get all child controls. + /// + public static T[] GetAll(Control control, bool includeTop = false) + { + if (control == null) + return new T[0]; + return GetAll(control, typeof(T), includeTop).Cast().ToArray(); + } + + public static void GetActiveControl(FrameworkElement control, out FrameworkElement activeControl, out string activePath) + { + string _activePath = null; + Invoke(() => { + _activePath = string.Format("/{0}", control.Name); + }); + activePath = _activePath; + // Return current control by default. + activeControl = control; + // If control can contains active controls. + var container = control as DependencyObject; + while (container != null) + { + control = System.Windows.Input.FocusManager.GetFocusedElement(control) as FrameworkElement; + if (control == null) + break; + Invoke(() => { + _activePath = string.Format("/{0}", control.Name); + }); + + activePath += _activePath; + activeControl = control; + container = control; + } + } + + #endregion + + #region Apply Grid Border Style + + public static void ApplyBorderStyle(DataGrid grid, bool updateEnabledProperty = false) + { + if (grid == null) + throw new ArgumentNullException(nameof(grid)); + grid.Background = new SolidColorBrush(Color.FromRgb(255, 255, 255)); + //grid.BorderThickness = BorderStyle.None; + //grid.EnableHeadersVisualStyles = false; + //grid.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single; + //grid.ColumnHeadersDefaultCellStyle.BackColor = SystemColors.Control; + //grid.ColumnHeadersDefaultCellStyle.WrapMode = DataGridViewTriState.False; + //grid.RowHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single; + //grid.RowHeadersDefaultCellStyle.BackColor = SystemColors.Control; + //grid.BackColor = SystemColors.Window; + //grid.DefaultCellStyle.BackColor = SystemColors.Window; + //grid.CellPainting += Grid_CellPainting; + //grid.SelectionChanged += Grid_SelectionChanged; + //grid.CellFormatting += Grid_CellFormatting; + //if (updateEnabledProperty) + // grid.CellClick += Grid_CellClick; + } + + /* + private static void Grid_CellClick(object sender, DataGridViewCellEventArgs e) + { + if (e.RowIndex < 0 || e.ColumnIndex < 0) + return; + var grid = (DataGridView)sender; + // If add new record row. + if (grid.AllowUserToAddRows && e.RowIndex + 1 == grid.Rows.Count) + return; + var column = grid.Columns[e.ColumnIndex]; + var item = grid.Rows[e.RowIndex].DataBoundItem; + if (column.DataPropertyName == "Enabled" || column.DataPropertyName == "IsEnabled") + { + SetEnabled(item, !GetEnabled(item)); + grid.Invalidate(); + } + } + + private static void Grid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) + { + if (e.RowIndex < 0 || e.ColumnIndex < 0) + return; + var grid = (DataGridView)sender; + // If add new record row. + if (grid.AllowUserToAddRows && e.RowIndex + 1 == grid.Rows.Count) + return; + var row = grid.Rows[e.RowIndex]; + if (e.RowIndex > -1 && e.ColumnIndex > -1) + { + var item = row.DataBoundItem; + // If grid is virtual then... + if (item == null) + { + var list = grid.DataSource as IBindingList; + if (list != null) + item = list[e.RowIndex]; + } + var enabled = true; + if (item != null) + enabled = GetEnabled(item); + var fore = enabled ? grid.DefaultCellStyle.ForeColor : SystemColors.ControlDark; + var selectedBack = enabled ? grid.DefaultCellStyle.SelectionBackColor : SystemColors.ControlDark; + // Apply style to row header. + if (row.HeaderCell.Style.ForeColor != fore) + row.HeaderCell.Style.ForeColor = fore; + if (row.HeaderCell.Style.SelectionBackColor != selectedBack) + row.HeaderCell.Style.SelectionBackColor = selectedBack; + // Apply style to cell + var cell = grid.Rows[e.RowIndex].Cells[e.ColumnIndex]; + if (cell.Style.ForeColor != fore) + cell.Style.ForeColor = fore; + if (cell.Style.SelectionBackColor != selectedBack) + cell.Style.SelectionBackColor = selectedBack; + } + } + + private static void Grid_SelectionChanged(object sender, EventArgs e) + { + // Sort issue with paint artifcats. + var grid = (DataGridView)sender; + grid.Invalidate(); + } + + private static void SetEnabled(object item, bool enabled) + { + var enabledProperty = item.GetType().GetProperties().FirstOrDefault(x => x.Name == "Enabled" || x.Name == "IsEnabled"); + if (enabledProperty != null) + { + enabledProperty.SetValue(item, enabled, null); + } + } + + private static bool GetEnabled(object item) + { + var enabledProperty = item.GetType().GetProperties().FirstOrDefault(x => x.Name == "Enabled" || x.Name == "IsEnabled"); + var enabled = enabledProperty == null ? true : (bool)enabledProperty.GetValue(item, null); + return enabled; + } + + private static void Grid_CellPainting(object sender, DataGridViewCellPaintingEventArgs e) + { + // Header and cell borders must be set to "Single" style. + var grid = (DataGridView)sender; + var firstVisibleColumn = grid.Columns.Cast().Where(x => x.Displayed).Min(x => x.Index); + var lastVisibleColumn = grid.Columns.Cast().Where(x => x.Displayed).Max(x => x.Index); + var selected = e.RowIndex > -1 ? grid.Rows[e.RowIndex].Selected : false; + e.Paint(e.CellBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border); + var bounds = e.CellBounds; + var tl = new Point(bounds.X, bounds.Y); + var tr = new Point(bounds.X + bounds.Width - 1, bounds.Y); + var bl = new Point(bounds.X, bounds.Y + bounds.Height - 1); + var br = new Point(bounds.X + bounds.Width - 1, bounds.Y + bounds.Height - 1); + Color backColor; + // If top left corner and column header then... + if (e.RowIndex == -1) + { + backColor = selected + ? grid.ColumnHeadersDefaultCellStyle.SelectionBackColor + : grid.ColumnHeadersDefaultCellStyle.BackColor; + } + // If row header then... + else if (e.ColumnIndex == -1 && e.RowIndex > -1) + { + var row = grid.Rows[e.RowIndex]; + backColor = selected + ? row.HeaderCell.Style.SelectionBackColor + : grid.RowHeadersDefaultCellStyle.BackColor; + } + // If normal cell then... + else + { + var row = grid.Rows[e.RowIndex]; + var cell = row.Cells[e.ColumnIndex]; + backColor = selected + ? cell.InheritedStyle.SelectionBackColor + : cell.InheritedStyle.BackColor; + } + // Cell background colour. + var back = new Pen(backColor, 1); + // Border colour. + var border = new Pen(SystemColors.Control, 1); + // Do not draw borders for selected device. + Pen c; + // Top + e.Graphics.DrawLine(back, tl, tr); + // Left (only if not first) + c = !selected && e.ColumnIndex > firstVisibleColumn ? border : back; + e.Graphics.DrawLine(c, bl, tl); + // Right (always) + c = back; + e.Graphics.DrawLine(c, tr, br); + // Bottom (always) + c = border; + e.Graphics.DrawLine(c, bl, br); + back.Dispose(); + border.Dispose(); + e.Handled = true; + } + + */ + + #endregion + + #region Center Window + + public static void CenterWindowOnApplication(Window window) + { + // Get WFF window first. + var win = System.Windows.Application.Current?.MainWindow; + System.Drawing.Rectangle? r = null; + var isNormal = false; + if (win != null) + { + r = new System.Drawing.Rectangle((int)win.Left, (int)win.Top, (int)win.Width, (int)win.Height); + isNormal = win.WindowState == WindowState.Normal; + } + else + { + // Try to get top windows form. + var form = System.Windows.Forms.Application.OpenForms.Cast().FirstOrDefault(); + if (form != null) + { + double l; + double t; + double w; + double h; + TransformToUnits(form.Left, form.Top, out l, out t); + TransformToUnits(form.Width, form.Height, out w, out h); + r = new System.Drawing.Rectangle((int)l, (int)t, (int)w, (int)h); + isNormal = form.WindowState == System.Windows.Forms.FormWindowState.Normal; + } + } + if (r.HasValue) + { + if (isNormal) + { + window.Left = r.Value.X + ((r.Value.Width - window.ActualWidth) / 2); + window.Top = r.Value.Y + ((r.Value.Height - window.ActualHeight) / 2); + } + else + { + // Get the form screen. + var screen = System.Windows.Forms.Screen.FromRectangle(r.Value); + double screenWidth = screen.WorkingArea.Width; + double screenHeight = screen.WorkingArea.Height; + window.Left = (screenWidth / 2) - (window.Width / 2); + window.Top = (screenHeight / 2) - (window.Height / 2); + } + } + } + + /// + /// Transforms device independent units (1/96 of an inch) to pixels. + /// + private static void TransformToPixels(double unitX, double unitY, out int pixelX, out int pixelY) + { + using (var g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero)) + { + pixelX = (int)((g.DpiX / 96) * unitX); + pixelY = (int)((g.DpiY / 96) * unitY); + } + } + + /// + /// Transforms device pixels to independent units (1/96 of an inch). + /// + private static void TransformToUnits(int pixelX, int pixelY, out double unitX, out double unitY) + { + using (var g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero)) + { + unitX = (double)pixelX / (g.DpiX / 96); + unitY = (double)pixelY / (g.DpiX / 96); + } + } + + public static bool GetMainFormTopMost() + { + var win = System.Windows.Application.Current?.MainWindow; + if (win != null) + return win.Topmost; + var form = System.Windows.Forms.Application.OpenForms.Cast().FirstOrDefault(); + if (form != null) + return form.TopMost; + return false; + } + + #endregion + + #region Data Grid Functions + + /// + /// Get list of primary keys of items selected in the grid. + /// + /// Type of Primary key. + /// Grid for getting selection + /// Primary key name. + public static List GetSelection(DataGrid grid, string keyPropertyName = null) + { + if (grid == null) + throw new ArgumentNullException(nameof(grid)); + var list = new List(); + var items = grid.SelectedItems.Cast().ToArray(); + // If nothing selected then try to get rows from cells. + if (items.Length == 0) + items = grid.SelectedCells.Cast().Select(x => x.Item).Distinct().ToArray(); + // If nothing selected then return. + if (items.Length == 0) + return list; + var pi = GetPropertyInfo(keyPropertyName, items[0]); + for (var i = 0; i < items.Length; i++) + { + var value = GetValue(items[i], keyPropertyName, pi); + list.Add(value); + } + return list; + } + + public static void RestoreSelection(DataGrid grid, string keyPropertyName, List list, bool selectFirst = true) + { + if (grid == null) + throw new ArgumentNullException(nameof(grid)); + if (list == null) + throw new ArgumentNullException(nameof(list)); + var items = grid.Items.Cast().ToArray(); + // Return if grid is empty. + if (items.Length == 0) + return; + // If something to restore then... + if (list.Count > 0) + { + var selectedItems = new List(); + var pi = GetPropertyInfo(keyPropertyName, items[0]); + for (var i = 0; i < items.Length; i++) + { + var item = items[i]; + var val = GetValue(item, keyPropertyName, pi); + if (list.Contains(val)) + selectedItems.Add(item); + } + if (grid.SelectionMode == DataGridSelectionMode.Single) + { + grid.SelectedItem = selectedItems.FirstOrDefault(); + } + else + { + // Remove items which should not be selected. + var itemsToUnselect = grid.SelectedItems.Cast().Except(selectedItems); + foreach (var item in itemsToUnselect) + grid.SelectedItems.Remove(item); + var itemsToSelect = selectedItems.Except(grid.SelectedItems.Cast()); + foreach (var item in itemsToSelect) + grid.SelectedItems.Add(item); + } + } + // If must select first row and nothing is selected then... + if (selectFirst && grid.SelectedItems.Count == 0) + grid.SelectedItem = items[0]; + } + + #endregion + + #region TextBoxBase + + public static VerticalAlignment GetScrollVerticalAlignment(System.Windows.Controls.Primitives.TextBoxBase control) + { + // Vertical scroll position. + var offset = control.VerticalOffset; + // Vertical size of the scrollable content area. + var height = control.ViewportHeight; + // Vertical size of the visible content area. + var visibleView = control.ExtentHeight; + // Allow flexibility of 2 pixels. + var flex = 2; + if (offset + height - visibleView < flex) + return VerticalAlignment.Bottom; + if (offset < flex) + return VerticalAlignment.Top; + return VerticalAlignment.Center; + } + + private static void AutoScroll(TextBoxBase control) + { + var scrollPosition = GetScrollVerticalAlignment(control); + if (scrollPosition == VerticalAlignment.Bottom && control.IsVisible) + control.ScrollToEnd(); + } + + public static void EnableAutoScroll(TextBoxBase control, bool enable = true) + { + control.TextChanged -= TextBoxBase_TextChanged; + control.IsVisibleChanged -= TextBoxBase_IsVisibleChanged; + control.Unloaded -= TextBoxBase_Unloaded; + if (enable) + { + control.TextChanged += TextBoxBase_TextChanged; + control.IsVisibleChanged += TextBoxBase_IsVisibleChanged; + control.Unloaded += TextBoxBase_Unloaded; + } + } + + private static void TextBoxBase_Unloaded(object sender, RoutedEventArgs e) + => EnableAutoScroll((TextBox)sender, false); + + private static void TextBoxBase_TextChanged(object sender, TextChangedEventArgs e) + => AutoScroll((TextBox)sender); + + private static void TextBoxBase_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + => AutoScroll((TextBox)sender); + + + #endregion + + } +} diff --git a/FocusLogger/JocysCom/Controls/ControlsHelper.cs b/FocusLogger/JocysCom/Controls/ControlsHelper.cs new file mode 100644 index 0000000..5ba0a98 --- /dev/null +++ b/FocusLogger/JocysCom/Controls/ControlsHelper.cs @@ -0,0 +1,286 @@ +using System; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace JocysCom.ClassLibrary.Controls +{ + public static partial class ControlsHelper + { + #region Invoke and BeginInvoke + + /// + /// Call this method from main form constructor for BeginInvoke to work. + /// + public static void InitInvokeContext() + { + if (MainTaskScheduler != null) + return; + _MainThreadId = Thread.CurrentThread.ManagedThreadId; + // Create a TaskScheduler that wraps the SynchronizationContext returned from + // System.Threading.SynchronizationContext.Current + MainTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); + } + + /// + /// Object that handles the low-level work of queuing tasks onto main User Interface (GUI) thread. + /// + public static TaskScheduler MainTaskScheduler { get; private set; } + + public static int MainThreadId => _MainThreadId; + private static int _MainThreadId; + + public static bool InvokeRequired + => _MainThreadId != Thread.CurrentThread.ManagedThreadId; + + /* + + public static void TestTasks(TaskCreationOptions childOptions) + { + var i = 5000000; + Console.WriteLine("//"); + Console.WriteLine("TestTasks(TaskCreationOptions.{0});", childOptions); + Console.WriteLine("// Parent starting"); + var parent = Task.Factory.StartNew(() => + { + Console.WriteLine("// Parent started"); + Console.WriteLine("// Child starting"); + var child = Task.Factory.StartNew(() => + { + Console.WriteLine("// Child started"); + Thread.SpinWait(i); + Console.WriteLine("// Child completing"); + }, childOptions); + //child.Wait(); + //Console.WriteLine("// Child completed"); + Console.WriteLine("// Parent completing"); + }); + parent.Wait(); + Console.WriteLine("// Parent completed"); + Thread.SpinWait(i * 4); + } + + // Attached and Detached Child Tasks. + // + // TaskCreationOptions.AttachedToParent: + // + // - Parent task waits for child tasks to complete. + // - Parent task propagates exceptions thrown by child tasks. + // - Status of parent task depends on status of child task. + // + TestTasks(TaskCreationOptions.AttachedToParent); + // + // Parent starting + // Parent started + // Child starting + // Parent completing + // Child started + // Child completing + // Parent completed + // + TestTasks(TaskCreationOptions.None); + // + // Parent starting + // Parent started + // Child starting + // Parent completing + // Parent completed + // Child started + // Child completing + + */ + + /// Executes the specified action delegate asynchronously on main Graphical User Interface (GUI) Thread. + /// The action delegate to execute asynchronously. + /// The started System.Threading.Tasks.Task. + public static Task BeginInvoke(Action action, int? millisecondsDelay = null) + { + if (millisecondsDelay.HasValue) + { + return Task.Run(async () => + { + // Wait 1 second, which will allow to release the button. + await Task.Delay(millisecondsDelay.Value).ConfigureAwait(true); + await BeginInvoke(action); + }); + } + InitInvokeContext(); + return Task.Factory.StartNew(action, + CancellationToken.None, TaskCreationOptions.DenyChildAttach, MainTaskScheduler); + } + + /// Executes the specified action delegate asynchronously on main User Interface (UI) Thread. + /// The action delegate to execute asynchronously. + /// The started System.Threading.Tasks.Task. + public static Task BeginInvoke(Delegate method, params object[] args) + { + InitInvokeContext(); + return Task.Factory.StartNew(() => { method.DynamicInvoke(args); }, + CancellationToken.None, TaskCreationOptions.DenyChildAttach, MainTaskScheduler); + } + + /// Executes the specified action delegate synchronously on main Graphical User Interface (GUI) Thread. + /// The action delegate to execute synchronously. + public static void Invoke(Action action) + { + if (action == null) + throw new ArgumentNullException(nameof(action)); + InitInvokeContext(); + if (InvokeRequired) + { + var t = new Task(action); + t.RunSynchronously(MainTaskScheduler); + } + else + { + action.DynamicInvoke(); + } + } + + /// Executes the specified action delegate synchronously on main Graphical User Interface (GUI) Thread. + /// The delegate to execute synchronously. + public static object Invoke(Delegate method, params object[] args) + { + if (method == null) + throw new ArgumentNullException(nameof(method)); + // Run method on main Graphical User Interface thread. + if (InvokeRequired) + { + var t = new Task(() => method.DynamicInvoke(args)); + t.RunSynchronously(MainTaskScheduler); + return t.Result; + } + else + { + return method.DynamicInvoke(args); + } + } + + #endregion + + #region Open Path or URL + + public static void OpenUrl(string url) + { + try + { + System.Diagnostics.Process.Start(url); + } + catch (System.ComponentModel.Win32Exception winEx) + { + if (winEx.ErrorCode == -2147467259) + MessageBoxShow(winEx.Message); + } + catch (Exception ex) + { + MessageBoxShow(ex.Message); + } + } + + private static void MessageBoxShow(string message) + { +#if NETCOREAPP // .NET Core + System.Windows.Forms.MessageBox.Show(message); +#elif NETSTANDARD // .NET Standard +#elif NETFRAMEWORK // .NET Framework + // Requires: PresentationFramework.dll + System.Windows.MessageBox.Show(message); +#else + throw new NotImplementedException("MessageBox not available for this .NET type"); +#endif + } + + /// + /// Open file with associated program. + /// + /// file to open. + public static void OpenPath(string path, string arguments = null) + { + try + { + var fi = new System.IO.FileInfo(path); + // Brings up the "Windows cannot open this file" dialog if association not found. + var psi = new System.Diagnostics.ProcessStartInfo(path); + psi.UseShellExecute = true; + psi.WorkingDirectory = fi.Directory.FullName; + psi.ErrorDialog = true; + if (arguments != null) + psi.Arguments = arguments; + System.Diagnostics.Process.Start(psi); + } + catch (Exception) { } + } + + #endregion + + public static PropertyInfo GetPrimaryKeyPropertyInfo(object item) + { + if (item == null) + return null; + var t = item.GetType(); + PropertyInfo pi = null; +#if NETCOREAPP // .NET Core + // Try to find property by KeyAttribute. + pi = t.GetProperties() + .Where(x => Attribute.IsDefined(x, typeof(System.ComponentModel.DataAnnotations.KeyAttribute), true)) + .FirstOrDefault(); + if (pi != null) + return pi; +#else + // Try to find property by EntityFramework EdmScalarPropertyAttribute (System.Data.Entity.dll). + pi = t.GetProperties() + .Where(x => + x.GetCustomAttributes(typeof(System.Data.Objects.DataClasses.EdmScalarPropertyAttribute), true) + .Cast() + .Any(a => a.EntityKeyProperty)) + .FirstOrDefault(); + if (pi != null) + return pi; + +#endif + return null; + } + + /// + /// Get DataViewRow, DataRow or item property value. + /// + /// Return value type. + /// DataViewRow, DataRow or another type. + /// Data property or column name. + /// Optional property info cache. + /// + private static T GetValue(object item, string keyPropertyName, PropertyInfo pi = null) + { + // Return object value if property info supplied. + if (pi != null) + return (T)pi.GetValue(item, null); + // Get DataRow. + var row = item is System.Data.DataRowView rowView + ? rowView.Row + : (System.Data.DataRow)item; + // Return DataRow value. + return row.IsNull(keyPropertyName) + ? default + : (T)row[keyPropertyName]; + } + + /// + /// Get Property info + /// + /// + /// + private static PropertyInfo GetPropertyInfo(string keyPropertyName, object item) + { + // Get property info if not DataRowView or DataRow. + PropertyInfo pi = null; + if (!(item is DataRowView) && !(item is DataRow)) + pi = string.IsNullOrEmpty(keyPropertyName) + ? GetPrimaryKeyPropertyInfo(item) + : item.GetType().GetProperty(keyPropertyName); + return pi; + } + + } +} diff --git a/FocusLogger/JocysCom/Controls/IBaseWithHeaderControl.cs b/FocusLogger/JocysCom/Controls/IBaseWithHeaderControl.cs new file mode 100644 index 0000000..c48005f --- /dev/null +++ b/FocusLogger/JocysCom/Controls/IBaseWithHeaderControl.cs @@ -0,0 +1,15 @@ +using System.Windows; + +namespace JocysCom.ClassLibrary.Controls +{ + public interface IBaseWithHeaderControl + { + void SetTitle(string format, params object[] args); + void SetHead(string format, params object[] args); + void SetBody(MessageBoxImage image, string content = null, params object[] args); + void SetBodyError(string content, params object[] args); + void SetBodyInfo(string content, params object[] args); + void AddTask(T name); + void RemoveTask(T name); + } +} diff --git a/FocusLogger/JocysCom/Controls/ItemFormattingConverter.cs b/FocusLogger/JocysCom/Controls/ItemFormattingConverter.cs new file mode 100644 index 0000000..8cbdcf6 --- /dev/null +++ b/FocusLogger/JocysCom/Controls/ItemFormattingConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace JocysCom.ClassLibrary.Controls +{ + public class ItemFormattingConverter : IMultiValueConverter + { + + public Func ConvertFunction; + public Func ConvertBackFunction; + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + => ConvertFunction?.Invoke(values, targetType, parameter, culture); + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + => ConvertBackFunction?.Invoke(value, targetTypes, parameter, culture); + + } +} diff --git a/FocusLogger/JocysCom/Controls/TabIndexConverter.cs b/FocusLogger/JocysCom/Controls/TabIndexConverter.cs new file mode 100644 index 0000000..2dca5c7 --- /dev/null +++ b/FocusLogger/JocysCom/Controls/TabIndexConverter.cs @@ -0,0 +1,35 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; + +namespace JocysCom.ClassLibrary.Controls +{ + public class TabIndexConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var item = value as TabItem; + if (item == null) + return ""; + var container = ItemsControl.ItemsControlFromItemContainer(item).ItemContainerGenerator; + var items = container.Items.Cast().Where(x => x.Visibility == Visibility.Visible).ToList(); + var count = items.Count(); + var index = items.IndexOf(item); + var result = ""; + if (index == 0) + result += "First"; + if (item.IsSelected) + result += "Selected"; + return result; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return DependencyProperty.UnsetValue; + } + } + +} diff --git a/FocusLogger/JocysCom/Controls/Themes/Default.xaml b/FocusLogger/JocysCom/Controls/Themes/Default.xaml new file mode 100644 index 0000000..703c832 --- /dev/null +++ b/FocusLogger/JocysCom/Controls/Themes/Default.xaml @@ -0,0 +1,582 @@ + + + + + #ff000000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +