From a9f903e0fbbe0cb1be0eafa6e5ce6025ceb1162f Mon Sep 17 00:00:00 2001 From: iG0R <11407417+iG8R@users.noreply.github.com> Date: Sun, 5 Oct 2025 01:35:11 +0300 Subject: [PATCH 1/4] Fix for Portable mode when Exception Info: System.UnauthorizedAccessException: Access to the path is denied --- .gitignore | 2 + ClsSavedWindows.cs | 385 --------------------------------------------- WinSize4.csproj | 43 ----- 3 files changed, 2 insertions(+), 428 deletions(-) delete mode 100644 ClsSavedWindows.cs delete mode 100644 WinSize4.csproj diff --git a/.gitignore b/.gitignore index 6f15a6c..f21adc0 100644 --- a/.gitignore +++ b/.gitignore @@ -365,3 +365,5 @@ FodyWeavers.xsd # Temp folders [0-9]/ Temp/ +/WinSize4.csproj +/ClsSavedWindows.cs diff --git a/ClsSavedWindows.cs b/ClsSavedWindows.cs deleted file mode 100644 index 5850da4..0000000 --- a/ClsSavedWindows.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.Json; -using System.Text.RegularExpressions; -using System.Windows.Forms; -using WinSize4; -using static System.Net.Mime.MediaTypeNames; - -public class ClsSavedWindows -{ - public List Props = new List(); - private int _nextTag = 0; - - //********************************************** - /// Adds a new window - /// - /// Tag - //********************************************** - public int AddWindow(ClsWindowProps Props) - { - this.Props.Add(Props); - this.Props[this.Props.Count - 1].Tag = _nextTag++; - if (this.Props[this.Props.Count - 1].Exe == "explorer") - this.Props[this.Props.Count - 1].IgnoreChildWindows = false; - return _nextTag; - } - - //********************************************** - /// Duplicates a window - /// - /// Tag - //********************************************** - public void DuplicateWindow(int Index) - { - this.Props.Add(this.Props[Index]); - this.Props[this.Props.Count - 1].Tag = _nextTag++; - } - - //********************************************** - /// Updates properties for the supplied window properties - /// - /// True if a window is updated, false if window is not found - //********************************************** - - public bool UpdateWindowProperties(ClsWindowProps Props, List CurrentScreenList, int screenCurrentIndex) - { - bool Found = false; - int Index = GetIndexCurrentScreen(Props, CurrentScreenList, screenCurrentIndex); - if (Index > -1) - { - Found = true; - this.Props[Index].Left = Props.Left; - this.Props[Index].Top = Props.Top; - this.Props[Index].Width = Props.Width; - this.Props[Index].Height = Props.Height; - //this.Props[Index].MaxWidth = false; - //this.Props[Index].MaxHeight = false; - //this.Props[Index].FullScreen = false; - } - return Found; - } - - //********************************************** - /// Finds the window with the supplied data in the current screen, or -1 - /// - /// Index - //********************************************** - //public int GetIndexCurrentScreen(ClsWindowProps CurrentWindowProps) - public int GetIndexCurrentScreen(ClsWindowProps CurrentWindowProps, List CurrentScreenList, int screenCurrentIndex) - { - int result = -1; - - // Get list of matching saved windows - List matchingSavedWindows = new List(); - for (int i = 0; i < this.Props.Count; i++) - { - bool FoundTitle = true; - if (this.Props[i].SearchTitleInclude) - { - switch (this.Props[i].SearchTypeInclude) - { - case ClsWindowProps.Full: - FoundTitle = (CurrentWindowProps.TitleInclude == this.Props[i].TitleInclude); - break; - case ClsWindowProps.Contains: - FoundTitle = CurrentWindowProps.TitleInclude.Contains(this.Props[i].TitleInclude); - break; - case ClsWindowProps.StartsWith: - FoundTitle = CurrentWindowProps.TitleInclude.StartsWith(this.Props[i].TitleInclude); - break; - } - } - bool FoundExe = true; - if (this.Props[i].SearchExe) - FoundExe = (CurrentWindowProps.Exe == this.Props[i].Exe); - bool FoundWindowClass = true; - if (this.Props[i].ConsiderWindowClass) - FoundWindowClass = (CurrentWindowProps.WindowClass == this.Props[i].WindowClass); - - if (FoundTitle && FoundExe && FoundWindowClass) - { - matchingSavedWindows.Add(this.Props[i]); - } - } - for (int i = 0; i < matchingSavedWindows.Count; i++) - { - if (matchingSavedWindows[i].MonitorBoundsWidth == CurrentScreenList[screenCurrentIndex].BoundsWidth && - matchingSavedWindows[i].MonitorBoundsHeight == CurrentScreenList[screenCurrentIndex].BoundsHeight && - matchingSavedWindows[i].Primary == CurrentScreenList[screenCurrentIndex].Primary) - { - result = GetWindowIndexByTag(matchingSavedWindows[i].Tag); - break; - } - } - return result; - } - - //********************************************** - /// Finds the first window with the supplied data, or -1 - /// - /// Index - //********************************************** - //public int GetIndexAllScreens(ClsWindowProps CurrentWindowProps) - public int GetIndexAllScreens(ClsWindowProps WindowProps, List ScreenList, int ScreenIndex) - { - int result = -1; - - ClsDebug.AddText("GetIndexAllScreens: " + WindowProps.Title); - // Get list of matching saved windows - List matchingSavedWindows = new List(); - for (int i = 0; i < this.Props.Count; i++) - { - bool FoundTitleInclude = true; - if (this.Props[i].SearchTitleInclude) - { - FoundTitleInclude = false; - switch (this.Props[i].SearchTypeInclude) - { - case ClsWindowProps.Full: - FoundTitleInclude = (WindowProps.Title == this.Props[i].TitleInclude); - break; - case ClsWindowProps.Contains: - if (WindowProps.Title.Contains("Loading")) - Console.WriteLine(WindowProps.Title); - foreach (string titleInclude in (this.Props[i].TitleInclude).Split('|')) - { - if (WindowProps.Title.Contains(titleInclude)) - { - FoundTitleInclude = true; - break; - } - } - break; - case ClsWindowProps.StartsWith: - foreach (string titleInclude in (this.Props[i].TitleInclude).Split('|')) - { - if (WindowProps.Title.StartsWith(titleInclude)) { - FoundTitleInclude = true; - break; - } - } - break; - } - } - bool FoundTitleExclude = false; - if (this.Props[i].SearchTitleExclude) - { - switch (this.Props[i].SearchTypeExclude) - { - case ClsWindowProps.Full: - FoundTitleExclude = (WindowProps.Title == this.Props[i].TitleExclude); - break; - case ClsWindowProps.Contains: - //FoundTitleExclude = WindowProps.Title.Contains(this.Props[i].TitleExclude); - foreach (string titleExclude in (this.Props[i].TitleExclude).Split('|')) - { - if (WindowProps.Title.Contains(titleExclude)) { - FoundTitleExclude = true; - break; - } - } - break; - case ClsWindowProps.StartsWith: - //FoundTitleExclude = WindowProps.Title.StartsWith(this.Props[i].TitleExclude); - foreach (string titleExclude in (this.Props[i].TitleExclude).Split('|')) - { - if (WindowProps.Title.StartsWith(titleExclude)) { - FoundTitleExclude = true; - break; - } - } - break; - } - } - bool FoundExe = true; - if (this.Props[i].SearchExe) - FoundExe = (WindowProps.Exe == this.Props[i].Exe); - bool FoundWindowClass = true; - if (this.Props[i].ConsiderWindowClass) - FoundWindowClass = (WindowProps.WindowClass == this.Props[i].WindowClass); - - if (FoundTitleInclude && !FoundTitleExclude && FoundExe && FoundWindowClass) - { - matchingSavedWindows.Add(this.Props[i]); - } - } - - for (int i = 0; i < matchingSavedWindows.Count; i++) - { - for (int j = 0; j < ScreenList.Count; j++) - { - // Another screen than current and not primary - if (matchingSavedWindows[i].MonitorBoundsWidth == ScreenList[j].BoundsWidth && - matchingSavedWindows[i].MonitorBoundsHeight == ScreenList[j].BoundsHeight && - matchingSavedWindows[i].Primary == ScreenList[j].Primary && - j != ScreenIndex && - ScreenList[j].Primary == false && - ScreenList[j].Present) - { - ClsDebug.AddText(" Another screen than current and not primary (" + j + ")"); - result = GetWindowIndexByTag(matchingSavedWindows[i].Tag); - break; - } - } - } - for (int i = 0; i < matchingSavedWindows.Count; i++) - { - for (int j = 0; j < ScreenList.Count; j++) - { - // Another screen than current and primary - if (matchingSavedWindows[i].MonitorBoundsWidth == ScreenList[j].BoundsWidth && - matchingSavedWindows[i].MonitorBoundsHeight == ScreenList[j].BoundsHeight && - matchingSavedWindows[i].Primary == ScreenList[j].Primary && - j != ScreenIndex && - ScreenList[j].Primary == true && - ScreenList[j].Present) - { - ClsDebug.AddText(" Another screen than current and primary (" + j + ")"); - result = GetWindowIndexByTag(matchingSavedWindows[i].Tag); - break; - } - } - } - for (int i = 0; i < matchingSavedWindows.Count; i++) - { - for (int j = 0; j < ScreenList.Count; j++) - { - // This screen matches - if (matchingSavedWindows[i].MonitorBoundsWidth == ScreenList[j].BoundsWidth && - matchingSavedWindows[i].MonitorBoundsHeight == ScreenList[j].BoundsHeight && - matchingSavedWindows[i].Primary == ScreenList[j].Primary && - ScreenList[j].Present && - j == ScreenIndex) - { - ClsDebug.AddText(" This screen matches (" + j + ")"); - result = GetWindowIndexByTag(matchingSavedWindows[i].Tag); - break; - } - } - } - ClsDebug.AddText(" Saved window index chosen: " + result); - return result; - } - - //********************************************** - /// Get the index of the window with the supplied Tag - /// - /// i - //********************************************** - public int GetWindowIndexByTag(int Tag) - { - for (int i = 0; i < Props.Count; i++) - { - if (Props[i].Tag == Tag) - return i; - } - return -1; - } - - //********************************************** - /// Saves data to the specified path - //********************************************** - public void Save(string activePath) - { - var options = new JsonSerializerOptions() { WriteIndented = true }; - var saveProps = new List>(); - - Directory.CreateDirectory(activePath); - - // Clean out only old window position files from the target directory - var fileEntries = Directory.GetFiles(activePath, "*.json"); - foreach (string fileName in fileEntries) - { - if (Regex.IsMatch(Path.GetFileName(fileName), @"^\d{3,5}x\d{3,5}P?\.json$")) - { - File.Delete(fileName); - } - } - - // Group window properties by screen configuration - foreach (var prop in this.Props) - { - var group = saveProps.FirstOrDefault(g => - g[0].MonitorBoundsWidth == prop.MonitorBoundsWidth && - g[0].MonitorBoundsHeight == prop.MonitorBoundsHeight && - g[0].Primary == prop.Primary); - - if (group != null) - { - group.Add(prop); - } - else - { - saveProps.Add(new List { prop }); - } - } - - // Write the new files - foreach (var group in saveProps) - { - string fileNameWindows = $"{group[0].MonitorBoundsWidth}x{group[0].MonitorBoundsHeight}"; - fileNameWindows += group[0].Primary ? "P.json" : ".json"; - string fullPath = Path.Combine(activePath, fileNameWindows); - using (var writer = new StreamWriter(fullPath)) - { - String json = JsonSerializer.Serialize(group, options); - writer.Write(json); - } - } - } - - //********************************************** - /// Loads data from the specified path - //********************************************** - public void Load(string activePath) - { - this.Props.Clear(); - _nextTag = 0; // Reset tag counter on load - - Directory.CreateDirectory(activePath); - var fileEntries = Directory.GetFiles(activePath, "*.json"); - - foreach (string fileName in fileEntries) - { - if (Regex.IsMatch(Path.GetFileName(fileName), @"^\d{3,5}x\d{3,5}P?\.json$")) - { - using (StreamReader r = new StreamReader(fileName)) - { - String json = r.ReadToEnd(); - if (json.Length > 0) - { - var loadedProps = JsonSerializer.Deserialize>(json); - foreach (var item in loadedProps) - { - // AddWindow will assign the correct, unique tag - AddWindow(item); - } - } - } - } - } - this.Props.Sort((o1, o2) => o1.Name.CompareTo(o2.Name)); - } - - //********************************************** - // API calls - //********************************************** - - [DllImport("USER32.DLL")] - private static extern int GetWindowTextLength(IntPtr hWnd); - - [DllImport("USER32.DLL")] - private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); - - [DllImport("user32.dll")] - public static extern bool GetWindowRect(IntPtr hwnd, ref Rectangle rectangle); - - [DllImport("user32.dll")] - static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); -} diff --git a/WinSize4.csproj b/WinSize4.csproj deleted file mode 100644 index e66d367..0000000 --- a/WinSize4.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - WinExe - net6.0-windows10.0.17763.0 - enable - true - enable - 001-direction.ico - 1.1.4.0 - 1.1.4.0 - - - - - - - - - - - - - - - - - - - True - True - Settings.settings - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - \ No newline at end of file From 2873b5840cd6cd7a8df7d838520dd2b513e381c9 Mon Sep 17 00:00:00 2001 From: iG0R <11407417+iG8R@users.noreply.github.com> Date: Sun, 5 Oct 2025 01:44:57 +0300 Subject: [PATCH 2/4] Revert "Fix for Portable mode when Exception Info: System.UnauthorizedAccessException: Access to the path is denied" This reverts commit a9f903e0fbbe0cb1be0eafa6e5ce6025ceb1162f. --- .gitignore | 2 - ClsSavedWindows.cs | 385 +++++++++++++++++++++++++++++++++++++++++++++ WinSize4.csproj | 43 +++++ 3 files changed, 428 insertions(+), 2 deletions(-) create mode 100644 ClsSavedWindows.cs create mode 100644 WinSize4.csproj diff --git a/.gitignore b/.gitignore index f21adc0..6f15a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -365,5 +365,3 @@ FodyWeavers.xsd # Temp folders [0-9]/ Temp/ -/WinSize4.csproj -/ClsSavedWindows.cs diff --git a/ClsSavedWindows.cs b/ClsSavedWindows.cs new file mode 100644 index 0000000..5850da4 --- /dev/null +++ b/ClsSavedWindows.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Windows.Forms; +using WinSize4; +using static System.Net.Mime.MediaTypeNames; + +public class ClsSavedWindows +{ + public List Props = new List(); + private int _nextTag = 0; + + //********************************************** + /// Adds a new window + /// + /// Tag + //********************************************** + public int AddWindow(ClsWindowProps Props) + { + this.Props.Add(Props); + this.Props[this.Props.Count - 1].Tag = _nextTag++; + if (this.Props[this.Props.Count - 1].Exe == "explorer") + this.Props[this.Props.Count - 1].IgnoreChildWindows = false; + return _nextTag; + } + + //********************************************** + /// Duplicates a window + /// + /// Tag + //********************************************** + public void DuplicateWindow(int Index) + { + this.Props.Add(this.Props[Index]); + this.Props[this.Props.Count - 1].Tag = _nextTag++; + } + + //********************************************** + /// Updates properties for the supplied window properties + /// + /// True if a window is updated, false if window is not found + //********************************************** + + public bool UpdateWindowProperties(ClsWindowProps Props, List CurrentScreenList, int screenCurrentIndex) + { + bool Found = false; + int Index = GetIndexCurrentScreen(Props, CurrentScreenList, screenCurrentIndex); + if (Index > -1) + { + Found = true; + this.Props[Index].Left = Props.Left; + this.Props[Index].Top = Props.Top; + this.Props[Index].Width = Props.Width; + this.Props[Index].Height = Props.Height; + //this.Props[Index].MaxWidth = false; + //this.Props[Index].MaxHeight = false; + //this.Props[Index].FullScreen = false; + } + return Found; + } + + //********************************************** + /// Finds the window with the supplied data in the current screen, or -1 + /// + /// Index + //********************************************** + //public int GetIndexCurrentScreen(ClsWindowProps CurrentWindowProps) + public int GetIndexCurrentScreen(ClsWindowProps CurrentWindowProps, List CurrentScreenList, int screenCurrentIndex) + { + int result = -1; + + // Get list of matching saved windows + List matchingSavedWindows = new List(); + for (int i = 0; i < this.Props.Count; i++) + { + bool FoundTitle = true; + if (this.Props[i].SearchTitleInclude) + { + switch (this.Props[i].SearchTypeInclude) + { + case ClsWindowProps.Full: + FoundTitle = (CurrentWindowProps.TitleInclude == this.Props[i].TitleInclude); + break; + case ClsWindowProps.Contains: + FoundTitle = CurrentWindowProps.TitleInclude.Contains(this.Props[i].TitleInclude); + break; + case ClsWindowProps.StartsWith: + FoundTitle = CurrentWindowProps.TitleInclude.StartsWith(this.Props[i].TitleInclude); + break; + } + } + bool FoundExe = true; + if (this.Props[i].SearchExe) + FoundExe = (CurrentWindowProps.Exe == this.Props[i].Exe); + bool FoundWindowClass = true; + if (this.Props[i].ConsiderWindowClass) + FoundWindowClass = (CurrentWindowProps.WindowClass == this.Props[i].WindowClass); + + if (FoundTitle && FoundExe && FoundWindowClass) + { + matchingSavedWindows.Add(this.Props[i]); + } + } + for (int i = 0; i < matchingSavedWindows.Count; i++) + { + if (matchingSavedWindows[i].MonitorBoundsWidth == CurrentScreenList[screenCurrentIndex].BoundsWidth && + matchingSavedWindows[i].MonitorBoundsHeight == CurrentScreenList[screenCurrentIndex].BoundsHeight && + matchingSavedWindows[i].Primary == CurrentScreenList[screenCurrentIndex].Primary) + { + result = GetWindowIndexByTag(matchingSavedWindows[i].Tag); + break; + } + } + return result; + } + + //********************************************** + /// Finds the first window with the supplied data, or -1 + /// + /// Index + //********************************************** + //public int GetIndexAllScreens(ClsWindowProps CurrentWindowProps) + public int GetIndexAllScreens(ClsWindowProps WindowProps, List ScreenList, int ScreenIndex) + { + int result = -1; + + ClsDebug.AddText("GetIndexAllScreens: " + WindowProps.Title); + // Get list of matching saved windows + List matchingSavedWindows = new List(); + for (int i = 0; i < this.Props.Count; i++) + { + bool FoundTitleInclude = true; + if (this.Props[i].SearchTitleInclude) + { + FoundTitleInclude = false; + switch (this.Props[i].SearchTypeInclude) + { + case ClsWindowProps.Full: + FoundTitleInclude = (WindowProps.Title == this.Props[i].TitleInclude); + break; + case ClsWindowProps.Contains: + if (WindowProps.Title.Contains("Loading")) + Console.WriteLine(WindowProps.Title); + foreach (string titleInclude in (this.Props[i].TitleInclude).Split('|')) + { + if (WindowProps.Title.Contains(titleInclude)) + { + FoundTitleInclude = true; + break; + } + } + break; + case ClsWindowProps.StartsWith: + foreach (string titleInclude in (this.Props[i].TitleInclude).Split('|')) + { + if (WindowProps.Title.StartsWith(titleInclude)) { + FoundTitleInclude = true; + break; + } + } + break; + } + } + bool FoundTitleExclude = false; + if (this.Props[i].SearchTitleExclude) + { + switch (this.Props[i].SearchTypeExclude) + { + case ClsWindowProps.Full: + FoundTitleExclude = (WindowProps.Title == this.Props[i].TitleExclude); + break; + case ClsWindowProps.Contains: + //FoundTitleExclude = WindowProps.Title.Contains(this.Props[i].TitleExclude); + foreach (string titleExclude in (this.Props[i].TitleExclude).Split('|')) + { + if (WindowProps.Title.Contains(titleExclude)) { + FoundTitleExclude = true; + break; + } + } + break; + case ClsWindowProps.StartsWith: + //FoundTitleExclude = WindowProps.Title.StartsWith(this.Props[i].TitleExclude); + foreach (string titleExclude in (this.Props[i].TitleExclude).Split('|')) + { + if (WindowProps.Title.StartsWith(titleExclude)) { + FoundTitleExclude = true; + break; + } + } + break; + } + } + bool FoundExe = true; + if (this.Props[i].SearchExe) + FoundExe = (WindowProps.Exe == this.Props[i].Exe); + bool FoundWindowClass = true; + if (this.Props[i].ConsiderWindowClass) + FoundWindowClass = (WindowProps.WindowClass == this.Props[i].WindowClass); + + if (FoundTitleInclude && !FoundTitleExclude && FoundExe && FoundWindowClass) + { + matchingSavedWindows.Add(this.Props[i]); + } + } + + for (int i = 0; i < matchingSavedWindows.Count; i++) + { + for (int j = 0; j < ScreenList.Count; j++) + { + // Another screen than current and not primary + if (matchingSavedWindows[i].MonitorBoundsWidth == ScreenList[j].BoundsWidth && + matchingSavedWindows[i].MonitorBoundsHeight == ScreenList[j].BoundsHeight && + matchingSavedWindows[i].Primary == ScreenList[j].Primary && + j != ScreenIndex && + ScreenList[j].Primary == false && + ScreenList[j].Present) + { + ClsDebug.AddText(" Another screen than current and not primary (" + j + ")"); + result = GetWindowIndexByTag(matchingSavedWindows[i].Tag); + break; + } + } + } + for (int i = 0; i < matchingSavedWindows.Count; i++) + { + for (int j = 0; j < ScreenList.Count; j++) + { + // Another screen than current and primary + if (matchingSavedWindows[i].MonitorBoundsWidth == ScreenList[j].BoundsWidth && + matchingSavedWindows[i].MonitorBoundsHeight == ScreenList[j].BoundsHeight && + matchingSavedWindows[i].Primary == ScreenList[j].Primary && + j != ScreenIndex && + ScreenList[j].Primary == true && + ScreenList[j].Present) + { + ClsDebug.AddText(" Another screen than current and primary (" + j + ")"); + result = GetWindowIndexByTag(matchingSavedWindows[i].Tag); + break; + } + } + } + for (int i = 0; i < matchingSavedWindows.Count; i++) + { + for (int j = 0; j < ScreenList.Count; j++) + { + // This screen matches + if (matchingSavedWindows[i].MonitorBoundsWidth == ScreenList[j].BoundsWidth && + matchingSavedWindows[i].MonitorBoundsHeight == ScreenList[j].BoundsHeight && + matchingSavedWindows[i].Primary == ScreenList[j].Primary && + ScreenList[j].Present && + j == ScreenIndex) + { + ClsDebug.AddText(" This screen matches (" + j + ")"); + result = GetWindowIndexByTag(matchingSavedWindows[i].Tag); + break; + } + } + } + ClsDebug.AddText(" Saved window index chosen: " + result); + return result; + } + + //********************************************** + /// Get the index of the window with the supplied Tag + /// + /// i + //********************************************** + public int GetWindowIndexByTag(int Tag) + { + for (int i = 0; i < Props.Count; i++) + { + if (Props[i].Tag == Tag) + return i; + } + return -1; + } + + //********************************************** + /// Saves data to the specified path + //********************************************** + public void Save(string activePath) + { + var options = new JsonSerializerOptions() { WriteIndented = true }; + var saveProps = new List>(); + + Directory.CreateDirectory(activePath); + + // Clean out only old window position files from the target directory + var fileEntries = Directory.GetFiles(activePath, "*.json"); + foreach (string fileName in fileEntries) + { + if (Regex.IsMatch(Path.GetFileName(fileName), @"^\d{3,5}x\d{3,5}P?\.json$")) + { + File.Delete(fileName); + } + } + + // Group window properties by screen configuration + foreach (var prop in this.Props) + { + var group = saveProps.FirstOrDefault(g => + g[0].MonitorBoundsWidth == prop.MonitorBoundsWidth && + g[0].MonitorBoundsHeight == prop.MonitorBoundsHeight && + g[0].Primary == prop.Primary); + + if (group != null) + { + group.Add(prop); + } + else + { + saveProps.Add(new List { prop }); + } + } + + // Write the new files + foreach (var group in saveProps) + { + string fileNameWindows = $"{group[0].MonitorBoundsWidth}x{group[0].MonitorBoundsHeight}"; + fileNameWindows += group[0].Primary ? "P.json" : ".json"; + string fullPath = Path.Combine(activePath, fileNameWindows); + using (var writer = new StreamWriter(fullPath)) + { + String json = JsonSerializer.Serialize(group, options); + writer.Write(json); + } + } + } + + //********************************************** + /// Loads data from the specified path + //********************************************** + public void Load(string activePath) + { + this.Props.Clear(); + _nextTag = 0; // Reset tag counter on load + + Directory.CreateDirectory(activePath); + var fileEntries = Directory.GetFiles(activePath, "*.json"); + + foreach (string fileName in fileEntries) + { + if (Regex.IsMatch(Path.GetFileName(fileName), @"^\d{3,5}x\d{3,5}P?\.json$")) + { + using (StreamReader r = new StreamReader(fileName)) + { + String json = r.ReadToEnd(); + if (json.Length > 0) + { + var loadedProps = JsonSerializer.Deserialize>(json); + foreach (var item in loadedProps) + { + // AddWindow will assign the correct, unique tag + AddWindow(item); + } + } + } + } + } + this.Props.Sort((o1, o2) => o1.Name.CompareTo(o2.Name)); + } + + //********************************************** + // API calls + //********************************************** + + [DllImport("USER32.DLL")] + private static extern int GetWindowTextLength(IntPtr hWnd); + + [DllImport("USER32.DLL")] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] + public static extern bool GetWindowRect(IntPtr hwnd, ref Rectangle rectangle); + + [DllImport("user32.dll")] + static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); +} diff --git a/WinSize4.csproj b/WinSize4.csproj new file mode 100644 index 0000000..e66d367 --- /dev/null +++ b/WinSize4.csproj @@ -0,0 +1,43 @@ + + + + WinExe + net6.0-windows10.0.17763.0 + enable + true + enable + 001-direction.ico + 1.1.4.0 + 1.1.4.0 + + + + + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + \ No newline at end of file From 17eb0d2a8d731002ac6dd92b0c380f281a6da85c Mon Sep 17 00:00:00 2001 From: iG0R <11407417+iG8R@users.noreply.github.com> Date: Sun, 5 Oct 2025 01:54:39 +0300 Subject: [PATCH 3/4] Fix for Portable mode when Exception Info: System.UnauthorizedAccessException: Access to the path is denied --- ClsSettings.cs | 121 ++++++++++++++++++++++++++++---- Properties/Settings.Designer.cs | 2 +- 2 files changed, 108 insertions(+), 15 deletions(-) diff --git a/ClsSettings.cs b/ClsSettings.cs index 207940f..ee31fdc 100644 --- a/ClsSettings.cs +++ b/ClsSettings.cs @@ -84,6 +84,37 @@ private void CopyAllFiles(string sourceDir, string destDir) } } + //********************************************** + /// + /// Safely checks for write permissions in a directory by attempting to create and delete a temporary file. + /// + /// The directory to check. + /// True if write access is granted, otherwise false. + //********************************************** + private bool HasWriteAccess(string path) + { + try + { + // Ensure the directory exists for an accurate test. + Directory.CreateDirectory(path); + // Use a unique file name to avoid collisions. + string tempFile = Path.Combine(path, Guid.NewGuid().ToString() + ".tmp"); + File.WriteAllText(tempFile, "test"); + File.Delete(tempFile); + return true; + } + catch (UnauthorizedAccessException) + { + // This is the specific exception we are looking for. + return false; + } + catch (Exception) + { + // Any other exception also indicates a problem. + return false; + } + } + //********************************************** /// /// Implements the "Portable is King" startup logic. @@ -95,9 +126,9 @@ public void InitializeAndLoad() { string portableConfigFile = Path.Combine(_portablePath, _fileNameSettings); string localConfigFile = Path.Combine(_localDataPath, _fileNameSettings); - string sourceOfTruthPath; + string sourceOfTruthPath = null; - // 1. ESTABLISH THE SOURCE OF TRUTH (The Golden Rule) + // 1. Establish the source of truth (Portable is King). if (File.Exists(portableConfigFile)) { sourceOfTruthPath = _portablePath; @@ -106,25 +137,87 @@ public void InitializeAndLoad() { sourceOfTruthPath = _localDataPath; } + + // 2. Load settings from the source if found, otherwise use defaults. + if (sourceOfTruthPath != null) + { + LoadSettingsFromSpecificPath(sourceOfTruthPath); + } else { - // First-ever run. Default to portable mode. - _activePath = _portablePath; - this.PortableMode = true; - return; // Nothing to load or copy. + // This is a first-ever run. 'this.PortableMode' is already 'true' by default. } - // 2. LOAD the settings from the determined source of truth. - LoadSettingsFromSpecificPath(sourceOfTruthPath); // This populates 'this.PortableMode' correctly. - - // 3. DETERMINE the correct active path based on the loaded setting. - _activePath = this.PortableMode ? _portablePath : _localDataPath; + // 3. Determine the INTENDED path based on settings. + string intendedPath = this.PortableMode ? _portablePath : _localDataPath; - // 4. SYNCHRONIZE: Copy all config files from the source of truth to the target active path if they are different. - if (sourceOfTruthPath != _activePath) + // 4. Test the INTENDED path for write access. + if (HasWriteAccess(intendedPath)) { - CopyAllFiles(sourceOfTruthPath, _activePath); + // --- SUCCESS: The intended path is writable. --- + _activePath = intendedPath; + // Synchronize files if the source is different from our active directory. + if (sourceOfTruthPath != null && sourceOfTruthPath != _activePath) + { + CopyAllFiles(sourceOfTruthPath, _activePath); + } } + else + { + // --- FAILURE: The intended path is NOT writable. We must try to fall back. --- + if (this.PortableMode) // The failed path was the portable directory. + { + // Inform the user we are attempting to fall back to LocalAppData. + MessageBox.Show( + "WinSize4 does not have permission to save settings in its current location.\n\n" + + "It will now attempt to use the safe 'LocalAppData' folder as a fallback.", + "Permission Issue Detected", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + + // Now, test the fallback path. + if (HasWriteAccess(_localDataPath)) + { + // --- FALLBACK SUCCESS --- + this.PortableMode = false; // Force non-portable mode. + _activePath = _localDataPath; + // Copy the original files to the new, writable location. + if (sourceOfTruthPath != null) + { + CopyAllFiles(sourceOfTruthPath, _activePath); + } + } + else + { + // --- FATAL ERROR: Both paths are unwritable. --- + ShowFatalErrorAndExit(); + } + } + else // The failed path was LocalAppData itself. + { + // --- FATAL ERROR: The primary non-portable path is unwritable. There is no other fallback. --- + ShowFatalErrorAndExit(); + } + } + } + + //********************************************** + /// + /// Displays a final error message and terminates the application when no writable path is found. + /// + //********************************************** + private void ShowFatalErrorAndExit() + { + MessageBox.Show( + "WinSize4 could not find a writable location to save its settings.\n\n" + + "It has checked both its application folder and the LocalAppData folder and was denied access to both.\n\n" + + "Please check your system's permissions or run the application from a user-writable folder.\nThe application will now close.", + "Fatal Error: Cannot Save Settings", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + Environment.Exit(1); // Force the application to terminate. } //********************************************** diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs index 069228e..0c021d2 100644 --- a/Properties/Settings.Designer.cs +++ b/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace WinSize4.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); From 4aed09047afb9af1e4e83d6ef72c5d1356d5e38e Mon Sep 17 00:00:00 2001 From: iG0R <11407417+iG8R@users.noreply.github.com> Date: Sun, 5 Oct 2025 02:28:04 +0300 Subject: [PATCH 4/4] Fix for Portable mode when Exception Info: System.UnauthorizedAccessException: Access to the path is denied Attempt#5 --- .gitignore | 6 +- ClsDebug.cs | 66 ++-- ClsSavedWindows.cs | 84 +++-- ClsScreens.cs | 32 +- ClsSettings.cs | 241 ++++++++++---- ClsWindows.cs | 2 + WinSize4.csproj | 6 + frmMain.Designer.cs | 399 ++++++++++++++---------- frmMain.cs | 603 +++++++++++++++++++++++++++++++----- frmMain.resx | 37 +++ frmScreens.Designer.cs | 403 ++++++++++++------------ frmScreens.resx | 54 ++-- green-checkmark-icon-16.png | Bin 0 -> 448 bytes red-x-icon-16.png | Bin 0 -> 448 bytes 14 files changed, 1328 insertions(+), 605 deletions(-) create mode 100644 green-checkmark-icon-16.png create mode 100644 red-x-icon-16.png diff --git a/.gitignore b/.gitignore index 9491a2f..6f15a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,8 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +# Temp folders +[0-9]/ +Temp/ diff --git a/ClsDebug.cs b/ClsDebug.cs index 69b6136..27fd002 100644 --- a/ClsDebug.cs +++ b/ClsDebug.cs @@ -1,6 +1,7 @@ using System.Diagnostics; -using System.Globalization; +using System.IO; using System.Windows.Forms; +using System.Globalization; namespace WinSize4 { @@ -8,15 +9,42 @@ public static class ClsDebug { public static bool Debug = false; public static string _text = ""; + private static string _activePath; // Will be set by frmMain at startup + + //********************************************** + /// + /// Sets the active directory for logging. Must be called once at startup. + /// + //********************************************** + public static void Initialize(string path) + { + _activePath = path; + } + + //********************************************** + /// + /// Checks if the path has been set. Prevents crashes. + /// + //********************************************** + private static bool IsPathInitialized() + { + if (string.IsNullOrEmpty(_activePath)) + { + // Fallback to prevent crashes if Initialize() is not called. + // In a properly functioning app, this should not be hit. + _activePath = Path.GetDirectoryName(Application.ExecutablePath); + } + return true; + } public static void ClearLog() { - string _path = Environment.GetEnvironmentVariable("LocalAppData") + "\\WinSize4"; - Directory.CreateDirectory(_path); - string _FileName = "Debug.txt"; - if (File.Exists(Path.Combine(_path, _FileName))) + if (!IsPathInitialized()) return; + Directory.CreateDirectory(_activePath); + string fullPath = Path.Combine(_activePath, "Debug.txt"); + if (File.Exists(fullPath)) { - File.Delete(Path.Combine(_path, _FileName)); + File.Delete(fullPath); } _text = ""; } @@ -29,32 +57,26 @@ public static void AddText(string Text) public static void LogText() { - string _path = Environment.GetEnvironmentVariable("LocalAppData") + "\\WinSize4"; + if (!Debug || !IsPathInitialized()) return; string dt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); - Directory.CreateDirectory(_path); - string _FileName = "Debug.txt"; - if (Debug) + Directory.CreateDirectory(_activePath); + string fullPath = Path.Combine(_activePath, "Debug.txt"); + using (var writer = new StreamWriter(fullPath, true)) { - using (var writer = new StreamWriter(_path + "\\" + _FileName, true)) - { - writer.WriteLine(dt + " " + _text); - } + writer.WriteLine(dt + " " + _text); } _text = ""; } public static void LogNow(string Text) { + if (!Debug || !IsPathInitialized()) return; string dt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); - string _path = Environment.GetEnvironmentVariable("LocalAppData") + "\\WinSize4"; - Directory.CreateDirectory(_path); - string _FileName = "Debug.txt"; - if (Debug) + Directory.CreateDirectory(_activePath); + string fullPath = Path.Combine(_activePath, "Debug.txt"); + using (var writer = new StreamWriter(fullPath, true)) { - using (var writer = new StreamWriter(_path + "\\" + _FileName, true)) - { - writer.WriteLine(dt + " " + Text); - } + writer.WriteLine(dt + " " + Text); } } diff --git a/ClsSavedWindows.cs b/ClsSavedWindows.cs index dda49bd..5850da4 100644 --- a/ClsSavedWindows.cs +++ b/ClsSavedWindows.cs @@ -14,7 +14,6 @@ public class ClsSavedWindows { public List Props = new List(); - private string _path = Environment.GetEnvironmentVariable("LocalAppData") + "\\WinSize4"; private int _nextTag = 0; //********************************************** @@ -284,86 +283,81 @@ public int GetWindowIndexByTag(int Tag) } //********************************************** - /// Saves data to disk + /// Saves data to the specified path //********************************************** - public void Save() + public void Save(string activePath) { - string fileNameWindows; - var options = new JsonSerializerOptions() - { - WriteIndented = true - }; - List> saveProps = new List>(); - bool found; - int j; + var options = new JsonSerializerOptions() { WriteIndented = true }; + var saveProps = new List>(); + + Directory.CreateDirectory(activePath); - // Remove all saved files - var fileEntries = Directory.GetFiles(_path, "*.json"); + // Clean out only old window position files from the target directory + var fileEntries = Directory.GetFiles(activePath, "*.json"); foreach (string fileName in fileEntries) { - if (Regex.Match(fileName, @"\d{3,5}x\d{3,5}P?\.json").Success) + if (Regex.IsMatch(Path.GetFileName(fileName), @"^\d{3,5}x\d{3,5}P?\.json$")) { File.Delete(fileName); } } - for (int i = 0; i < this.Props.Count; i++) + // Group window properties by screen configuration + foreach (var prop in this.Props) { - found = false; - for (j = 0; j < saveProps.Count; j++) + var group = saveProps.FirstOrDefault(g => + g[0].MonitorBoundsWidth == prop.MonitorBoundsWidth && + g[0].MonitorBoundsHeight == prop.MonitorBoundsHeight && + g[0].Primary == prop.Primary); + + if (group != null) { - if (this.Props[i].MonitorBoundsWidth == saveProps[j][0].MonitorBoundsWidth && - this.Props[i].MonitorBoundsHeight == saveProps[j][0].MonitorBoundsHeight && - this.Props[i].Primary == saveProps[j][0].Primary) - { - saveProps[j].Add(this.Props[i]); - found = true; - break; - } + group.Add(prop); } - if (!found) + else { - saveProps.Add(new List()); - saveProps[saveProps.Count - 1].Add(this.Props[i]); + saveProps.Add(new List { prop }); } } - Directory.CreateDirectory(_path); - for (int i = 0; i < saveProps.Count; i++) + + // Write the new files + foreach (var group in saveProps) { - fileNameWindows = saveProps[i][0].MonitorBoundsWidth + "x" + saveProps[i][0].MonitorBoundsHeight; - fileNameWindows += (saveProps[i][0].Primary) ? "P.json" : ".json"; - using (var writer = new StreamWriter(_path + "\\" + fileNameWindows)) + string fileNameWindows = $"{group[0].MonitorBoundsWidth}x{group[0].MonitorBoundsHeight}"; + fileNameWindows += group[0].Primary ? "P.json" : ".json"; + string fullPath = Path.Combine(activePath, fileNameWindows); + using (var writer = new StreamWriter(fullPath)) { - String json = JsonSerializer.Serialize(saveProps[i], options); + String json = JsonSerializer.Serialize(group, options); writer.Write(json); } } } //********************************************** - /// Loads data from disk + /// Loads data from the specified path //********************************************** - public void Load() + public void Load(string activePath) { this.Props.Clear(); - int Id = 0; - System.IO.Directory.CreateDirectory(_path); - List loadProps = new List(); - var fileEntries = Directory.GetFiles(_path, "*.json"); + _nextTag = 0; // Reset tag counter on load + + Directory.CreateDirectory(activePath); + var fileEntries = Directory.GetFiles(activePath, "*.json"); + foreach (string fileName in fileEntries) { - if (Regex.Match(fileName, @"\d{3,5}x\d{3,5}P?\.json").Success) + if (Regex.IsMatch(Path.GetFileName(fileName), @"^\d{3,5}x\d{3,5}P?\.json$")) { using (StreamReader r = new StreamReader(fileName)) { String json = r.ReadToEnd(); if (json.Length > 0) { - loadProps = JsonSerializer.Deserialize>(json); - foreach (var item in loadProps) + var loadedProps = JsonSerializer.Deserialize>(json); + foreach (var item in loadedProps) { - item.Tag = Id; - Id++; + // AddWindow will assign the correct, unique tag AddWindow(item); } } diff --git a/ClsScreens.cs b/ClsScreens.cs index d7c55d4..38ad7ad 100644 --- a/ClsScreens.cs +++ b/ClsScreens.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Windows.Forms; using System.Text.Json; using Windows.ApplicationModel.Activation; @@ -9,8 +10,7 @@ namespace WinSize4 public class ClsScreens { public List ScreenList = new List(); - private string _path = Environment.GetEnvironmentVariable("LocalAppData") + "\\WinSize4"; - private string _fileNameWindows = "Screens.json"; + private readonly string _fileNameScreens = "Screens.json"; //********************************************** /// Get all current screens @@ -154,16 +154,14 @@ public bool CleanScreenList() } //********************************************** - /// Saves data to disk + /// Saves data to the specified path //********************************************** - public void Save() + public void Save(string activePath) { - var options = new JsonSerializerOptions() - { - WriteIndented = true - }; - Directory.CreateDirectory(_path); - using (var writer = new StreamWriter(_path + "\\" + _fileNameWindows)) + var options = new JsonSerializerOptions() { WriteIndented = true }; + Directory.CreateDirectory(activePath); + string fullPath = Path.Combine(activePath, _fileNameScreens); + using (var writer = new StreamWriter(fullPath)) { String _json = JsonSerializer.Serialize(this.ScreenList, options); writer.Write(_json); @@ -171,16 +169,20 @@ public void Save() } //********************************************** - /// Loads data from disk + /// Loads data from the specified path //********************************************** - public void Load() + public void Load(string activePath) { - if (File.Exists(_path + "\\" + _fileNameWindows)) + string fullPath = Path.Combine(activePath, _fileNameScreens); + if (File.Exists(fullPath)) { - using (StreamReader r = new StreamReader(_path + "\\" + _fileNameWindows)) + using (StreamReader r = new StreamReader(fullPath)) { String json = r.ReadToEnd(); - this.ScreenList = JsonSerializer.Deserialize>(json); + if (!string.IsNullOrWhiteSpace(json)) + { + this.ScreenList = JsonSerializer.Deserialize>(json); + } } } } diff --git a/ClsSettings.cs b/ClsSettings.cs index 161b0be..ee31fdc 100644 --- a/ClsSettings.cs +++ b/ClsSettings.cs @@ -7,49 +7,67 @@ namespace WinSize4 { public class ClsSettings { - //public ClsSettingsList SettingsList = new ClsSettingsList(); - public string HotKeyLeft = "alt"; - public string HotKeyRight = "ctrl"; - public string HotKeyCharacter = "Z"; - public bool showAllWindows = true; - public bool resetIfNewScreen = false; - public bool runAtLogin = false; - private string _path = Environment.GetEnvironmentVariable("LocalAppData") + "\\WinSize4"; - private string _fileNameWindows = "Settings.json"; - public bool Debug = false; - public int Interval = 500; - public bool isPaused = false; + private readonly string _portablePath = Path.GetDirectoryName(Application.ExecutablePath); + private readonly string _localDataPath = Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "WinSize4"); + private readonly string _fileNameSettings = "Settings.json"; + + private string _activePath; // This will be set at startup and used by the app + public string ActivePath => _activePath; // It provides controlled, read-only access to the path from outside the class. + + public bool PortableMode { get; set; } = true; + public string HotKeyLeft { get; set; } = "alt"; + public string HotKeyRight { get; set; } = "ctrl"; + public string HotKeyCharacter { get; set; } = "Z"; + public bool showAllWindows { get; set; } = true; + public bool resetIfNewScreen { get; set; } = false; + public bool runAtLogin { get; set; } = false; + public bool Debug { get; set; } = false; + public int Interval { get; set; } = 500; + public bool isPaused { get; set; } = false; + public bool ResetOnMinimize { get; set; } = false; + public Dictionary ListViewColumnWidths { get; set; } //********************************************** - /// Saves data to disk + /// + /// Backs up existing .json files in a directory to a timestamped subfolder. + /// + /// The folder containing files to be backed up. //********************************************** - public void SaveToFile() + private void BackupExistingFiles(string directoryToBackup) { - ClsSettingsList saveList = new ClsSettingsList(); - saveList.HotKeyCharacter = this.HotKeyCharacter; - saveList.HotKeyLeft = this.HotKeyLeft; - saveList.HotKeyRight = this.HotKeyRight; - saveList.showAllWindows = this.showAllWindows; - saveList.resetIfNewScreen = this.resetIfNewScreen; - saveList.runAtLogin = this.runAtLogin; - saveList.Debug = ClsDebug.Debug; - saveList.Interval = this.Interval; - saveList.isPaused = this.isPaused; - - var options = new JsonSerializerOptions() + try { - WriteIndented = true - }; - Directory.CreateDirectory(_path); - using (var writer = new StreamWriter(_path + "\\" + _fileNameWindows)) + if (!Directory.Exists(directoryToBackup)) return; + + var filesToBackup = Directory.GetFiles(directoryToBackup, "*.json"); + + // If there are no config files, there's nothing to do. + if (filesToBackup.Length == 0) return; + + // Create a unique, timestamped backup folder. + string timestamp = DateTime.Now.ToString("yyyy-MM-dd_HHmmss"); + string backupSubfolder = Path.Combine(directoryToBackup, $"_backup_{timestamp}"); + Directory.CreateDirectory(backupSubfolder); + + // Move each config file into the new backup folder. + foreach (var sourceFile in filesToBackup) + { + string fileName = Path.GetFileName(sourceFile); + string backupDestFile = Path.Combine(backupSubfolder, fileName); + File.Move(sourceFile, backupDestFile); + } + } + catch (Exception ex) { - String _json = JsonSerializer.Serialize(saveList, options); - writer.Write(_json); + // Inform the user if the backup failed, but don't crash the app. + MessageBox.Show($"Could not create a backup of the old settings.\nError: {ex.Message}", "WinSize4 Backup Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } //********************************************** - /// Loads data from disk + /// + /// Copies all .json files from a source to a destination, overwriting them. + /// //********************************************** private void CopyAllFiles(string sourceDir, string destDir) { @@ -110,7 +128,7 @@ public void InitializeAndLoad() string localConfigFile = Path.Combine(_localDataPath, _fileNameSettings); string sourceOfTruthPath = null; - // 1. ESTABLISH THE SOURCE OF TRUTH (The Golden Rule) + // 1. Establish the source of truth (Portable is King). if (File.Exists(portableConfigFile)) { sourceOfTruthPath = _portablePath; @@ -119,27 +137,89 @@ public void InitializeAndLoad() { sourceOfTruthPath = _localDataPath; } + + // 2. Load settings from the source if found, otherwise use defaults. + if (sourceOfTruthPath != null) + { + LoadSettingsFromSpecificPath(sourceOfTruthPath); + } else { - // First-ever run. Default to portable mode. - _activePath = _portablePath; - this.PortableMode = true; - return; // Nothing to load or copy. + // This is a first-ever run. 'this.PortableMode' is already 'true' by default. } - // 2. LOAD the settings from the determined source of truth. - LoadSettingsFromSpecificPath(sourceOfTruthPath); // This populates 'this.PortableMode' correctly. - - // 3. DETERMINE the correct active path based on the loaded setting. - _activePath = this.PortableMode ? _portablePath : _localDataPath; + // 3. Determine the INTENDED path based on settings. + string intendedPath = this.PortableMode ? _portablePath : _localDataPath; - // 4. SYNCHRONIZE: Copy all config files from the source of truth to the target active path if they are different. - if (sourceOfTruthPath != _activePath) + // 4. Test the INTENDED path for write access. + if (HasWriteAccess(intendedPath)) { - CopyAllFiles(sourceOfTruthPath, _activePath); + // --- SUCCESS: The intended path is writable. --- + _activePath = intendedPath; + // Synchronize files if the source is different from our active directory. + if (sourceOfTruthPath != null && sourceOfTruthPath != _activePath) + { + CopyAllFiles(sourceOfTruthPath, _activePath); + } + } + else + { + // --- FAILURE: The intended path is NOT writable. We must try to fall back. --- + if (this.PortableMode) // The failed path was the portable directory. + { + // Inform the user we are attempting to fall back to LocalAppData. + MessageBox.Show( + "WinSize4 does not have permission to save settings in its current location.\n\n" + + "It will now attempt to use the safe 'LocalAppData' folder as a fallback.", + "Permission Issue Detected", + MessageBoxButtons.OK, + MessageBoxIcon.Warning + ); + + // Now, test the fallback path. + if (HasWriteAccess(_localDataPath)) + { + // --- FALLBACK SUCCESS --- + this.PortableMode = false; // Force non-portable mode. + _activePath = _localDataPath; + // Copy the original files to the new, writable location. + if (sourceOfTruthPath != null) + { + CopyAllFiles(sourceOfTruthPath, _activePath); + } + } + else + { + // --- FATAL ERROR: Both paths are unwritable. --- + ShowFatalErrorAndExit(); + } + } + else // The failed path was LocalAppData itself. + { + // --- FATAL ERROR: The primary non-portable path is unwritable. There is no other fallback. --- + ShowFatalErrorAndExit(); + } } } + //********************************************** + /// + /// Displays a final error message and terminates the application when no writable path is found. + /// + //********************************************** + private void ShowFatalErrorAndExit() + { + MessageBox.Show( + "WinSize4 could not find a writable location to save its settings.\n\n" + + "It has checked both its application folder and the LocalAppData folder and was denied access to both.\n\n" + + "Please check your system's permissions or run the application from a user-writable folder.\nThe application will now close.", + "Fatal Error: Cannot Save Settings", + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + Environment.Exit(1); // Force the application to terminate. + } + //********************************************** /// /// This is called by the UI when the checkbox is changed by the user. @@ -196,37 +276,66 @@ private void LoadSettingsFromSpecificPath(string settingsDirPath) } catch (Exception e) { - MessageBox.Show(e.Message, "WinSize4", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show($"Error loading settings from {settingsDirPath}:\n{e.Message}", "WinSize4", MessageBoxButtons.OK, MessageBoxIcon.Error); } } + //********************************************** + /// Saves data to disk + //********************************************** + public void SaveToFile() + { + // Ensure the active path is set if it hasn't been already (for first run). + if (string.IsNullOrEmpty(_activePath)) + { + _activePath = this.PortableMode ? _portablePath : _localDataPath; + } + + ClsSettingsList saveList = new ClsSettingsList(); + saveList.PortableMode = this.PortableMode; + saveList.HotKeyCharacter = this.HotKeyCharacter; + saveList.HotKeyLeft = this.HotKeyLeft; + saveList.HotKeyRight = this.HotKeyRight; + saveList.showAllWindows = this.showAllWindows; + saveList.resetIfNewScreen = this.resetIfNewScreen; + saveList.runAtLogin = this.runAtLogin; + saveList.Debug = ClsDebug.Debug; + saveList.Interval = this.Interval; + saveList.isPaused = this.isPaused; + saveList.ResetOnMinimize = this.ResetOnMinimize; + saveList.ListViewColumnWidths = this.ListViewColumnWidths; + + var options = new JsonSerializerOptions() + { + WriteIndented = true + }; + // Use the active path + Directory.CreateDirectory(_activePath); + using (var writer = new StreamWriter(Path.Combine(_activePath, _fileNameSettings))) + { + String _json = JsonSerializer.Serialize(saveList, options); + writer.Write(_json); + } + } public int GetHotKeyNumber(string HotKey) { switch (HotKey) { - case "Alt": - return 1; - case "Ctrl": - return 2; - case "Shift": - return 4; - default: - return 0; + case "Alt": return 1; + case "Ctrl": return 2; + case "Shift": return 4; + default: return 0; } } public string GetHotKeyText(int HotKey) { switch (HotKey) { - case 1: - return "Alt"; - case 2: - return "Ctrl"; - case 4: - return "Shift"; - default: - return "None"; + case 1: return "Alt"; + case 2: return "Ctrl"; + case 4: return "Shift"; + default: return "None"; } } } @@ -251,5 +360,11 @@ public int Interval { set; get; } public bool isPaused { set; get; } + public bool ResetOnMinimize + { get; set; } + public Dictionary ListViewColumnWidths + { get; set; } + public bool PortableMode + { get; set; } = true; } } diff --git a/ClsWindows.cs b/ClsWindows.cs index a07d5dc..c7a3da0 100644 --- a/ClsWindows.cs +++ b/ClsWindows.cs @@ -64,6 +64,8 @@ public bool AlwaysMove { set; get; } = false; public bool CanResize { set; get; } = true; + public bool Disabled + { get; set; } = false; public const int Full = 0; public const int Contains = 1; diff --git a/WinSize4.csproj b/WinSize4.csproj index 25d9d1b..e66d367 100644 --- a/WinSize4.csproj +++ b/WinSize4.csproj @@ -11,6 +11,12 @@ 1.1.4.0 + + + + + + diff --git a/frmMain.Designer.cs b/frmMain.Designer.cs index 16d9f67..4230720 100644 --- a/frmMain.Designer.cs +++ b/frmMain.Designer.cs @@ -87,11 +87,15 @@ private void InitializeComponent() columnHeader2 = new ColumnHeader(); columnHeader3 = new ColumnHeader(); columnHeader4 = new ColumnHeader(); + stateImageList = new ImageList(components); cbShowAllWindows = new CheckBox(); cbRunAtLogin = new CheckBox(); contextMenuStrip1 = new ContextMenuStrip(components); txtVersion = new TextBox(); cbIsPaused = new CheckBox(); + chkResetOnMinimize = new CheckBox(); + btnResetColumns = new Button(); + chkPortableMode = new CheckBox(); groupBox1.SuspendLayout(); groupBox2.SuspendLayout(); groupBox4.SuspendLayout(); @@ -107,27 +111,27 @@ private void InitializeComponent() // label2 // label2.AutoSize = true; - label2.Location = new Point(14, 287); - label2.Margin = new Padding(4, 0, 4, 0); + label2.Location = new Point(24, 438); + label2.Margin = new Padding(6, 0, 6, 0); label2.Name = "label2"; - label2.Size = new Size(63, 15); + label2.Size = new Size(95, 25); label2.TabIndex = 4; label2.Text = "Executable"; // // tbExe // - tbExe.Location = new Point(168, 278); - tbExe.Margin = new Padding(4, 3, 4, 3); + tbExe.Location = new Point(240, 435); + tbExe.Margin = new Padding(6, 5, 6, 5); tbExe.Name = "tbExe"; - tbExe.Size = new Size(305, 23); + tbExe.Size = new Size(434, 31); tbExe.TabIndex = 3; // // butOK // - butOK.Location = new Point(540, 633); - butOK.Margin = new Padding(4, 3, 4, 3); + butOK.Location = new Point(774, 1050); + butOK.Margin = new Padding(6, 5, 6, 5); butOK.Name = "butOK"; - butOK.Size = new Size(124, 27); + butOK.Size = new Size(177, 45); butOK.TabIndex = 5; butOK.Text = "Apply and Hide"; butOK.UseVisualStyleBackColor = true; @@ -135,10 +139,10 @@ private void InitializeComponent() // // butApply // - butApply.Location = new Point(765, 633); - butApply.Margin = new Padding(4, 3, 4, 3); + butApply.Location = new Point(1092, 1050); + butApply.Margin = new Padding(6, 5, 6, 5); butApply.Name = "butApply"; - butApply.Size = new Size(88, 27); + butApply.Size = new Size(126, 45); butApply.TabIndex = 6; butApply.Text = "Apply"; butApply.UseVisualStyleBackColor = true; @@ -146,10 +150,10 @@ private void InitializeComponent() // // butExit // - butExit.Location = new Point(859, 633); - butExit.Margin = new Padding(4, 3, 4, 3); + butExit.Location = new Point(1226, 1050); + butExit.Margin = new Padding(6, 5, 6, 5); butExit.Name = "butExit"; - butExit.Size = new Size(88, 27); + butExit.Size = new Size(126, 45); butExit.TabIndex = 7; butExit.Text = "Exit"; butExit.UseVisualStyleBackColor = true; @@ -157,10 +161,10 @@ private void InitializeComponent() // // butRemove // - butRemove.Location = new Point(12, 428); - butRemove.Margin = new Padding(4, 3, 4, 3); + butRemove.Location = new Point(17, 685); + butRemove.Margin = new Padding(6, 5, 6, 5); butRemove.Name = "butRemove"; - butRemove.Size = new Size(136, 27); + butRemove.Size = new Size(194, 45); butRemove.TabIndex = 8; butRemove.Text = "Remove"; butRemove.UseVisualStyleBackColor = true; @@ -169,86 +173,86 @@ private void InitializeComponent() // label3 // label3.AutoSize = true; - label3.Location = new Point(14, 312); - label3.Margin = new Padding(4, 0, 4, 0); + label3.Location = new Point(24, 488); + label3.Margin = new Padding(6, 0, 6, 0); label3.Name = "label3"; - label3.Size = new Size(27, 15); + label3.Size = new Size(41, 25); label3.TabIndex = 10; label3.Text = "Left"; // // tbLeft // - tbLeft.Location = new Point(168, 308); - tbLeft.Margin = new Padding(4, 3, 4, 3); + tbLeft.Location = new Point(240, 485); + tbLeft.Margin = new Padding(6, 5, 6, 5); tbLeft.Name = "tbLeft"; - tbLeft.Size = new Size(131, 23); + tbLeft.Size = new Size(185, 31); tbLeft.TabIndex = 9; tbLeft.TextChanged += tbLeft_TextChanged; // // label4 // label4.AutoSize = true; - label4.Location = new Point(14, 342); - label4.Margin = new Padding(4, 0, 4, 0); + label4.Location = new Point(25, 538); + label4.Margin = new Padding(6, 0, 6, 0); label4.Name = "label4"; - label4.Size = new Size(27, 15); + label4.Size = new Size(41, 25); label4.TabIndex = 12; label4.Text = "Top"; // // tbTop // - tbTop.Location = new Point(168, 338); - tbTop.Margin = new Padding(4, 3, 4, 3); + tbTop.Location = new Point(240, 535); + tbTop.Margin = new Padding(6, 5, 6, 5); tbTop.Name = "tbTop"; - tbTop.Size = new Size(131, 23); + tbTop.Size = new Size(185, 31); tbTop.TabIndex = 11; tbTop.TextChanged += tbTop_TextChanged; // // label5 // label5.AutoSize = true; - label5.Location = new Point(14, 377); - label5.Margin = new Padding(4, 0, 4, 0); + label5.Location = new Point(25, 590); + label5.Margin = new Padding(6, 0, 6, 0); label5.Name = "label5"; - label5.Size = new Size(39, 15); + label5.Size = new Size(60, 25); label5.TabIndex = 14; label5.Text = "Width"; // // tbWidth // - tbWidth.Location = new Point(168, 368); - tbWidth.Margin = new Padding(4, 3, 4, 3); + tbWidth.Location = new Point(240, 585); + tbWidth.Margin = new Padding(6, 5, 6, 5); tbWidth.Name = "tbWidth"; - tbWidth.Size = new Size(131, 23); + tbWidth.Size = new Size(185, 31); tbWidth.TabIndex = 13; tbWidth.TextChanged += tbWidth_TextChanged; // // label6 // label6.AutoSize = true; - label6.Location = new Point(14, 407); - label6.Margin = new Padding(4, 0, 4, 0); + label6.Location = new Point(24, 638); + label6.Margin = new Padding(6, 0, 6, 0); label6.Name = "label6"; - label6.Size = new Size(43, 15); + label6.Size = new Size(65, 25); label6.TabIndex = 16; label6.Text = "Height"; // // tbHeight // - tbHeight.Location = new Point(168, 398); - tbHeight.Margin = new Padding(4, 3, 4, 3); + tbHeight.Location = new Point(240, 635); + tbHeight.Margin = new Padding(6, 5, 6, 5); tbHeight.Name = "tbHeight"; - tbHeight.Size = new Size(131, 23); + tbHeight.Size = new Size(185, 31); tbHeight.TabIndex = 15; tbHeight.TextChanged += tbHeight_TextChanged; // // cbCustomWidth // cbCustomWidth.AutoSize = true; - cbCustomWidth.Location = new Point(330, 370); - cbCustomWidth.Margin = new Padding(4, 3, 4, 3); + cbCustomWidth.Location = new Point(471, 589); + cbCustomWidth.Margin = new Padding(6, 5, 6, 5); cbCustomWidth.Name = "cbCustomWidth"; - cbCustomWidth.Size = new Size(68, 19); + cbCustomWidth.Size = new Size(100, 29); cbCustomWidth.TabIndex = 17; cbCustomWidth.Text = "Custom"; cbCustomWidth.UseVisualStyleBackColor = true; @@ -258,10 +262,10 @@ private void InitializeComponent() // cbCustomHeight // cbCustomHeight.AutoSize = true; - cbCustomHeight.Location = new Point(330, 400); - cbCustomHeight.Margin = new Padding(4, 3, 4, 3); + cbCustomHeight.Location = new Point(471, 637); + cbCustomHeight.Margin = new Padding(6, 5, 6, 5); cbCustomHeight.Name = "cbCustomHeight"; - cbCustomHeight.Size = new Size(68, 19); + cbCustomHeight.Size = new Size(100, 29); cbCustomHeight.TabIndex = 18; cbCustomHeight.Text = "Custom"; cbCustomHeight.UseVisualStyleBackColor = true; @@ -271,10 +275,10 @@ private void InitializeComponent() // cbFullScreen // cbFullScreen.AutoSize = true; - cbFullScreen.Location = new Point(17, 461); - cbFullScreen.Margin = new Padding(4, 3, 4, 3); + cbFullScreen.Location = new Point(24, 740); + cbFullScreen.Margin = new Padding(6, 5, 6, 5); cbFullScreen.Name = "cbFullScreen"; - cbFullScreen.Size = new Size(82, 19); + cbFullScreen.Size = new Size(120, 29); cbFullScreen.TabIndex = 19; cbFullScreen.Text = "Full screen"; cbFullScreen.UseVisualStyleBackColor = true; @@ -284,10 +288,10 @@ private void InitializeComponent() // butCancel // butCancel.DialogResult = DialogResult.Cancel; - butCancel.Location = new Point(670, 633); - butCancel.Margin = new Padding(4, 3, 4, 3); + butCancel.Location = new Point(958, 1050); + butCancel.Margin = new Padding(6, 5, 6, 5); butCancel.Name = "butCancel"; - butCancel.Size = new Size(88, 27); + butCancel.Size = new Size(126, 45); butCancel.TabIndex = 20; butCancel.Text = "Hide"; butCancel.UseVisualStyleBackColor = true; @@ -295,10 +299,10 @@ private void InitializeComponent() // // butEditScreens // - butEditScreens.Location = new Point(811, 563); - butEditScreens.Margin = new Padding(4, 3, 4, 3); + butEditScreens.Location = new Point(1159, 933); + butEditScreens.Margin = new Padding(6, 5, 6, 5); butEditScreens.Name = "butEditScreens"; - butEditScreens.Size = new Size(136, 27); + butEditScreens.Size = new Size(194, 45); butEditScreens.TabIndex = 33; butEditScreens.Text = "Edit screens"; butEditScreens.UseVisualStyleBackColor = true; @@ -307,10 +311,10 @@ private void InitializeComponent() // cbSearchExe // cbSearchExe.AutoSize = true; - cbSearchExe.Location = new Point(131, 288); - cbSearchExe.Margin = new Padding(4, 3, 4, 3); + cbSearchExe.Location = new Point(187, 441); + cbSearchExe.Margin = new Padding(6, 5, 6, 5); cbSearchExe.Name = "cbSearchExe"; - cbSearchExe.Size = new Size(15, 14); + cbSearchExe.Size = new Size(22, 21); cbSearchExe.TabIndex = 36; cbSearchExe.UseVisualStyleBackColor = true; cbSearchExe.CheckedChanged += cbSearchExe_CheckedChanged; @@ -320,11 +324,11 @@ private void InitializeComponent() groupBox1.Controls.Add(tbHotKeyCharacter); groupBox1.Controls.Add(cbHotKeyRight); groupBox1.Controls.Add(cbHotKeyLeft); - groupBox1.Location = new Point(463, 541); - groupBox1.Margin = new Padding(4, 3, 4, 3); + groupBox1.Location = new Point(661, 897); + groupBox1.Margin = new Padding(6, 5, 6, 5); groupBox1.Name = "groupBox1"; - groupBox1.Padding = new Padding(4, 3, 4, 3); - groupBox1.Size = new Size(298, 61); + groupBox1.Padding = new Padding(6, 5, 6, 5); + groupBox1.Size = new Size(426, 102); groupBox1.TabIndex = 38; groupBox1.TabStop = false; groupBox1.Text = "Hotkey"; @@ -332,11 +336,11 @@ private void InitializeComponent() // tbHotKeyCharacter // tbHotKeyCharacter.CharacterCasing = CharacterCasing.Upper; - tbHotKeyCharacter.Location = new Point(231, 22); - tbHotKeyCharacter.Margin = new Padding(4, 3, 4, 3); + tbHotKeyCharacter.Location = new Point(330, 37); + tbHotKeyCharacter.Margin = new Padding(6, 5, 6, 5); tbHotKeyCharacter.MaxLength = 1; tbHotKeyCharacter.Name = "tbHotKeyCharacter"; - tbHotKeyCharacter.Size = new Size(53, 23); + tbHotKeyCharacter.Size = new Size(74, 31); tbHotKeyCharacter.TabIndex = 2; tbHotKeyCharacter.TextAlign = HorizontalAlignment.Center; tbHotKeyCharacter.TextChanged += tbHotKeyCharacter_TextChanged; @@ -345,10 +349,10 @@ private void InitializeComponent() // cbHotKeyRight.FormattingEnabled = true; cbHotKeyRight.Items.AddRange(new object[] { "None" }); - cbHotKeyRight.Location = new Point(120, 22); - cbHotKeyRight.Margin = new Padding(4, 3, 4, 3); + cbHotKeyRight.Location = new Point(171, 37); + cbHotKeyRight.Margin = new Padding(6, 5, 6, 5); cbHotKeyRight.Name = "cbHotKeyRight"; - cbHotKeyRight.Size = new Size(103, 23); + cbHotKeyRight.Size = new Size(145, 33); cbHotKeyRight.TabIndex = 1; cbHotKeyRight.SelectedIndexChanged += cbHotKeyRight_SelectedIndexChanged; // @@ -356,10 +360,10 @@ private void InitializeComponent() // cbHotKeyLeft.FormattingEnabled = true; cbHotKeyLeft.Items.AddRange(new object[] { "Alt", "Ctrl", "Shift" }); - cbHotKeyLeft.Location = new Point(9, 22); - cbHotKeyLeft.Margin = new Padding(4, 3, 4, 3); + cbHotKeyLeft.Location = new Point(13, 37); + cbHotKeyLeft.Margin = new Padding(6, 5, 6, 5); cbHotKeyLeft.Name = "cbHotKeyLeft"; - cbHotKeyLeft.Size = new Size(103, 23); + cbHotKeyLeft.Size = new Size(145, 33); cbHotKeyLeft.TabIndex = 0; cbHotKeyLeft.SelectedIndexChanged += cbHotKeyLeft_SelectedIndexChanged; // @@ -374,30 +378,30 @@ private void InitializeComponent() // label7 // label7.AutoSize = true; - label7.Location = new Point(10, 30); - label7.Margin = new Padding(4, 0, 4, 0); + label7.Location = new Point(24, 40); + label7.Margin = new Padding(6, 0, 6, 0); label7.Name = "label7"; - label7.Size = new Size(39, 15); + label7.Size = new Size(59, 25); label7.TabIndex = 40; label7.Text = "Name"; // // tbName // - tbName.Location = new Point(164, 22); - tbName.Margin = new Padding(4, 3, 4, 3); + tbName.Location = new Point(234, 37); + tbName.Margin = new Padding(6, 5, 6, 5); tbName.Name = "tbName"; - tbName.Size = new Size(305, 23); + tbName.Size = new Size(434, 31); tbName.TabIndex = 39; // // cbResetIfNewScreen // cbResetIfNewScreen.AutoSize = true; - cbResetIfNewScreen.Location = new Point(480, 516); - cbResetIfNewScreen.Margin = new Padding(4, 3, 4, 3); + cbResetIfNewScreen.Location = new Point(685, 820); + cbResetIfNewScreen.Margin = new Padding(6, 5, 6, 5); cbResetIfNewScreen.Name = "cbResetIfNewScreen"; - cbResetIfNewScreen.Size = new Size(226, 19); + cbResetIfNewScreen.Size = new Size(360, 29); cbResetIfNewScreen.TabIndex = 41; - cbResetIfNewScreen.Text = "Reset moved if new screen is detected"; + cbResetIfNewScreen.Text = "Reset 'Movable' if new screen is detected"; cbResetIfNewScreen.UseVisualStyleBackColor = true; cbResetIfNewScreen.CheckedChanged += cbResetIfNewScreen_CheckedChanged; cbResetIfNewScreen.MouseHover += checkBox_MouseHover; @@ -431,23 +435,22 @@ private void InitializeComponent() groupBox2.Controls.Add(cbCustomWidth); groupBox2.Controls.Add(cbFullScreen); groupBox2.Controls.Add(cbCustomHeight); - groupBox2.Location = new Point(463, 14); - groupBox2.Margin = new Padding(4, 3, 4, 3); + groupBox2.Location = new Point(661, 23); + groupBox2.Margin = new Padding(6, 5, 6, 5); groupBox2.Name = "groupBox2"; - groupBox2.Padding = new Padding(4, 3, 4, 3); - groupBox2.Size = new Size(484, 492); + groupBox2.Padding = new Padding(6, 5, 6, 5); + groupBox2.Size = new Size(691, 784); groupBox2.TabIndex = 42; groupBox2.TabStop = false; groupBox2.Text = "Selected window"; - groupBox2.Enter += groupBox2_Enter; // // cbCanResize // cbCanResize.AutoSize = true; - cbCanResize.Location = new Point(131, 387); - cbCanResize.Margin = new Padding(4, 3, 4, 3); + cbCanResize.Location = new Point(187, 617); + cbCanResize.Margin = new Padding(6, 5, 6, 5); cbCanResize.Name = "cbCanResize"; - cbCanResize.Size = new Size(15, 14); + cbCanResize.Size = new Size(22, 21); cbCanResize.TabIndex = 59; cbCanResize.UseVisualStyleBackColor = true; cbCanResize.CheckedChanged += cbCanResize_CheckedChanged; @@ -455,19 +458,20 @@ private void InitializeComponent() // cbIgnoreChildWindows // cbIgnoreChildWindows.AutoSize = true; - cbIgnoreChildWindows.Location = new Point(152, 461); + cbIgnoreChildWindows.Location = new Point(217, 740); + cbIgnoreChildWindows.Margin = new Padding(4, 5, 4, 5); cbIgnoreChildWindows.Name = "cbIgnoreChildWindows"; - cbIgnoreChildWindows.Size = new Size(143, 19); + cbIgnoreChildWindows.Size = new Size(214, 29); cbIgnoreChildWindows.TabIndex = 58; cbIgnoreChildWindows.Text = "Ignore Child Windows"; cbIgnoreChildWindows.UseVisualStyleBackColor = true; // // butDuplicate // - butDuplicate.Location = new Point(164, 428); - butDuplicate.Margin = new Padding(4, 3, 4, 3); + butDuplicate.Location = new Point(234, 685); + butDuplicate.Margin = new Padding(6, 5, 6, 5); butDuplicate.Name = "butDuplicate"; - butDuplicate.Size = new Size(136, 27); + butDuplicate.Size = new Size(194, 45); butDuplicate.TabIndex = 57; butDuplicate.Text = "Duplicate"; butDuplicate.UseVisualStyleBackColor = true; @@ -480,9 +484,11 @@ private void InitializeComponent() groupBox4.Controls.Add(radioFullInclude); groupBox4.Controls.Add(tbTitleInclude); groupBox4.Controls.Add(cbSearchTitleInclude); - groupBox4.Location = new Point(10, 84); + groupBox4.Location = new Point(17, 136); + groupBox4.Margin = new Padding(4, 5, 4, 5); groupBox4.Name = "groupBox4"; - groupBox4.Size = new Size(467, 92); + groupBox4.Padding = new Padding(4, 5, 4, 5); + groupBox4.Size = new Size(667, 138); groupBox4.TabIndex = 56; groupBox4.TabStop = false; groupBox4.Text = "Window Title Include"; @@ -490,10 +496,10 @@ private void InitializeComponent() // radioStartsWithInclude // radioStartsWithInclude.AutoSize = true; - radioStartsWithInclude.Location = new Point(162, 60); - radioStartsWithInclude.Margin = new Padding(4, 3, 4, 3); + radioStartsWithInclude.Location = new Point(231, 100); + radioStartsWithInclude.Margin = new Padding(6, 5, 6, 5); radioStartsWithInclude.Name = "radioStartsWithInclude"; - radioStartsWithInclude.Size = new Size(80, 19); + radioStartsWithInclude.Size = new Size(119, 29); radioStartsWithInclude.TabIndex = 49; radioStartsWithInclude.Text = "Starts with"; radioStartsWithInclude.UseVisualStyleBackColor = true; @@ -501,10 +507,10 @@ private void InitializeComponent() // radioContainsInclude // radioContainsInclude.AutoSize = true; - radioContainsInclude.Location = new Point(82, 60); - radioContainsInclude.Margin = new Padding(4, 3, 4, 3); + radioContainsInclude.Location = new Point(117, 100); + radioContainsInclude.Margin = new Padding(6, 5, 6, 5); radioContainsInclude.Name = "radioContainsInclude"; - radioContainsInclude.Size = new Size(72, 19); + radioContainsInclude.Size = new Size(106, 29); radioContainsInclude.TabIndex = 48; radioContainsInclude.Text = "Contains"; radioContainsInclude.UseVisualStyleBackColor = true; @@ -512,30 +518,30 @@ private void InitializeComponent() // radioFullInclude // radioFullInclude.AutoSize = true; - radioFullInclude.Location = new Point(30, 60); - radioFullInclude.Margin = new Padding(4, 3, 4, 3); + radioFullInclude.Location = new Point(43, 100); + radioFullInclude.Margin = new Padding(6, 5, 6, 5); radioFullInclude.Name = "radioFullInclude"; - radioFullInclude.Size = new Size(44, 19); + radioFullInclude.Size = new Size(64, 29); radioFullInclude.TabIndex = 47; radioFullInclude.Text = "Full"; radioFullInclude.UseVisualStyleBackColor = true; // // tbTitleInclude // - tbTitleInclude.Location = new Point(30, 31); - tbTitleInclude.Margin = new Padding(4, 3, 4, 3); + tbTitleInclude.Location = new Point(43, 52); + tbTitleInclude.Margin = new Padding(6, 5, 6, 5); tbTitleInclude.Name = "tbTitleInclude"; - tbTitleInclude.Size = new Size(433, 23); + tbTitleInclude.Size = new Size(617, 31); tbTitleInclude.TabIndex = 44; tbTitleInclude.TextChanged += tbTitleInclude_TextChanged; // // cbSearchTitleInclude // cbSearchTitleInclude.AutoSize = true; - cbSearchTitleInclude.Location = new Point(7, 35); - cbSearchTitleInclude.Margin = new Padding(4, 3, 4, 3); + cbSearchTitleInclude.Location = new Point(10, 58); + cbSearchTitleInclude.Margin = new Padding(6, 5, 6, 5); cbSearchTitleInclude.Name = "cbSearchTitleInclude"; - cbSearchTitleInclude.Size = new Size(15, 14); + cbSearchTitleInclude.Size = new Size(22, 21); cbSearchTitleInclude.TabIndex = 46; cbSearchTitleInclude.UseVisualStyleBackColor = true; cbSearchTitleInclude.CheckedChanged += cbSearchTitleInclude_CheckedChanged; @@ -547,9 +553,11 @@ private void InitializeComponent() groupBox3.Controls.Add(radioFullExclude); groupBox3.Controls.Add(tbTitleExclude); groupBox3.Controls.Add(cbSearchTitleExclude); - groupBox3.Location = new Point(10, 182); + groupBox3.Location = new Point(14, 284); + groupBox3.Margin = new Padding(4, 5, 4, 5); groupBox3.Name = "groupBox3"; - groupBox3.Size = new Size(467, 90); + groupBox3.Padding = new Padding(4, 5, 4, 5); + groupBox3.Size = new Size(667, 138); groupBox3.TabIndex = 55; groupBox3.TabStop = false; groupBox3.Text = "Window Title Exclude"; @@ -557,10 +565,10 @@ private void InitializeComponent() // radioStartsWithExclude // radioStartsWithExclude.AutoSize = true; - radioStartsWithExclude.Location = new Point(162, 56); - radioStartsWithExclude.Margin = new Padding(4, 3, 4, 3); + radioStartsWithExclude.Location = new Point(231, 93); + radioStartsWithExclude.Margin = new Padding(6, 5, 6, 5); radioStartsWithExclude.Name = "radioStartsWithExclude"; - radioStartsWithExclude.Size = new Size(80, 19); + radioStartsWithExclude.Size = new Size(119, 29); radioStartsWithExclude.TabIndex = 60; radioStartsWithExclude.Text = "Starts with"; radioStartsWithExclude.UseVisualStyleBackColor = true; @@ -568,10 +576,10 @@ private void InitializeComponent() // radioContainsExclude // radioContainsExclude.AutoSize = true; - radioContainsExclude.Location = new Point(82, 56); - radioContainsExclude.Margin = new Padding(4, 3, 4, 3); + radioContainsExclude.Location = new Point(117, 93); + radioContainsExclude.Margin = new Padding(6, 5, 6, 5); radioContainsExclude.Name = "radioContainsExclude"; - radioContainsExclude.Size = new Size(72, 19); + radioContainsExclude.Size = new Size(106, 29); radioContainsExclude.TabIndex = 59; radioContainsExclude.Text = "Contains"; radioContainsExclude.UseVisualStyleBackColor = true; @@ -579,30 +587,30 @@ private void InitializeComponent() // radioFullExclude // radioFullExclude.AutoSize = true; - radioFullExclude.Location = new Point(30, 56); - radioFullExclude.Margin = new Padding(4, 3, 4, 3); + radioFullExclude.Location = new Point(43, 93); + radioFullExclude.Margin = new Padding(6, 5, 6, 5); radioFullExclude.Name = "radioFullExclude"; - radioFullExclude.Size = new Size(44, 19); + radioFullExclude.Size = new Size(64, 29); radioFullExclude.TabIndex = 58; radioFullExclude.Text = "Full"; radioFullExclude.UseVisualStyleBackColor = true; // // tbTitleExclude // - tbTitleExclude.Location = new Point(30, 27); - tbTitleExclude.Margin = new Padding(4, 3, 4, 3); + tbTitleExclude.Location = new Point(43, 45); + tbTitleExclude.Margin = new Padding(6, 5, 6, 5); tbTitleExclude.Name = "tbTitleExclude"; - tbTitleExclude.Size = new Size(433, 23); + tbTitleExclude.Size = new Size(617, 31); tbTitleExclude.TabIndex = 55; tbTitleExclude.TextChanged += tbTitleExclude_TextChanged; // // cbSearchTitleExclude // cbSearchTitleExclude.AutoSize = true; - cbSearchTitleExclude.Location = new Point(7, 31); - cbSearchTitleExclude.Margin = new Padding(4, 3, 4, 3); + cbSearchTitleExclude.Location = new Point(10, 52); + cbSearchTitleExclude.Margin = new Padding(6, 5, 6, 5); cbSearchTitleExclude.Name = "cbSearchTitleExclude"; - cbSearchTitleExclude.Size = new Size(15, 14); + cbSearchTitleExclude.Size = new Size(22, 21); cbSearchTitleExclude.TabIndex = 57; cbSearchTitleExclude.UseVisualStyleBackColor = true; cbSearchTitleExclude.CheckedChanged += cbSearchTitleExclude_CheckedChanged; @@ -610,38 +618,38 @@ private void InitializeComponent() // tbSavedWindowIndex // tbSavedWindowIndex.AutoSize = true; - tbSavedWindowIndex.Location = new Point(120, 30); - tbSavedWindowIndex.Margin = new Padding(4, 0, 4, 0); + tbSavedWindowIndex.Location = new Point(183, 40); + tbSavedWindowIndex.Margin = new Padding(6, 0, 6, 0); tbSavedWindowIndex.Name = "tbSavedWindowIndex"; - tbSavedWindowIndex.Size = new Size(13, 15); + tbSavedWindowIndex.Size = new Size(22, 25); tbSavedWindowIndex.TabIndex = 48; tbSavedWindowIndex.Text = "0"; // // tbWindowClass // - tbWindowClass.Location = new Point(164, 51); - tbWindowClass.Margin = new Padding(4, 3, 4, 3); + tbWindowClass.Location = new Point(234, 85); + tbWindowClass.Margin = new Padding(6, 5, 6, 5); tbWindowClass.Name = "tbWindowClass"; - tbWindowClass.Size = new Size(305, 23); + tbWindowClass.Size = new Size(434, 31); tbWindowClass.TabIndex = 45; // // label8 // label8.AutoSize = true; - label8.Location = new Point(10, 59); - label8.Margin = new Padding(4, 0, 4, 0); + label8.Location = new Point(24, 87); + label8.Margin = new Padding(6, 0, 6, 0); label8.Name = "label8"; - label8.Size = new Size(81, 15); + label8.Size = new Size(123, 25); label8.TabIndex = 46; label8.Text = "Window Class"; // // cbWindowClass // cbWindowClass.AutoSize = true; - cbWindowClass.Location = new Point(128, 60); - cbWindowClass.Margin = new Padding(4, 3, 4, 3); + cbWindowClass.Location = new Point(183, 90); + cbWindowClass.Margin = new Padding(6, 5, 6, 5); cbWindowClass.Name = "cbWindowClass"; - cbWindowClass.Size = new Size(15, 14); + cbWindowClass.Size = new Size(22, 21); cbWindowClass.TabIndex = 47; cbWindowClass.UseVisualStyleBackColor = true; cbWindowClass.CheckedChanged += cbWindowClass_CheckedChanged; @@ -649,20 +657,20 @@ private void InitializeComponent() // cbAlwaysMove // cbAlwaysMove.AutoSize = true; - cbAlwaysMove.Location = new Point(330, 461); - cbAlwaysMove.Margin = new Padding(4, 3, 4, 3); + cbAlwaysMove.Location = new Point(471, 740); + cbAlwaysMove.Margin = new Padding(6, 5, 6, 5); cbAlwaysMove.Name = "cbAlwaysMove"; - cbAlwaysMove.Size = new Size(96, 19); + cbAlwaysMove.Size = new Size(143, 29); cbAlwaysMove.TabIndex = 44; - cbAlwaysMove.Text = "Always move"; + cbAlwaysMove.Text = "Always Move"; cbAlwaysMove.UseVisualStyleBackColor = true; // // butResetMoved // - butResetMoved.Location = new Point(811, 516); - butResetMoved.Margin = new Padding(4, 3, 4, 3); + butResetMoved.Location = new Point(1159, 831); + butResetMoved.Margin = new Padding(6, 5, 6, 5); butResetMoved.Name = "butResetMoved"; - butResetMoved.Size = new Size(136, 27); + butResetMoved.Size = new Size(194, 45); butResetMoved.TabIndex = 43; butResetMoved.Text = "Reset moved"; butResetMoved.UseVisualStyleBackColor = true; @@ -671,18 +679,23 @@ private void InitializeComponent() // listView1 // listView1.Columns.AddRange(new ColumnHeader[] { columnHeader1, columnHeader2, columnHeader3, columnHeader4 }); - listView1.Location = new Point(14, 14); - listView1.Margin = new Padding(4, 3, 4, 3); + listView1.FullRowSelect = true; + listView1.Location = new Point(20, 23); + listView1.Margin = new Padding(6, 5, 6, 5); listView1.MultiSelect = false; listView1.Name = "listView1"; - listView1.Size = new Size(432, 613); + listView1.Size = new Size(615, 1019); listView1.Sorting = SortOrder.Ascending; + listView1.StateImageList = stateImageList; listView1.TabIndex = 44; listView1.UseCompatibleStateImageBehavior = false; listView1.View = View.Details; + listView1.ColumnWidthChanged += listView1_ColumnWidthChanged; + listView1.ColumnWidthChanging += listView1_ColumnWidthChanging; listView1.SelectedIndexChanged += listView1_SelectedIndexChanged; listView1.KeyDown += listView1_KeyDown; listView1.MouseClick += listView1_MouseClick; + listView1.Resize += listView1_Resize; // // columnHeader1 // @@ -701,13 +714,21 @@ private void InitializeComponent() // columnHeader4.Text = "Primary"; // + // stateImageList + // + stateImageList.ColorDepth = ColorDepth.Depth16Bit; + stateImageList.ImageStream = (ImageListStreamer)resources.GetObject("stateImageList.ImageStream"); + stateImageList.TransparentColor = Color.Transparent; + stateImageList.Images.SetKeyName(0, "green-checkmark-icon-16.png"); + stateImageList.Images.SetKeyName(1, "red-x-icon-16.png"); + // // cbShowAllWindows // cbShowAllWindows.AutoSize = true; - cbShowAllWindows.Location = new Point(14, 641); - cbShowAllWindows.Margin = new Padding(4, 3, 4, 3); + cbShowAllWindows.Location = new Point(20, 1059); + cbShowAllWindows.Margin = new Padding(6, 5, 6, 5); cbShowAllWindows.Name = "cbShowAllWindows"; - cbShowAllWindows.Size = new Size(153, 19); + cbShowAllWindows.Size = new Size(230, 29); cbShowAllWindows.TabIndex = 45; cbShowAllWindows.Text = "Show all saved windows"; cbShowAllWindows.UseVisualStyleBackColor = true; @@ -716,10 +737,10 @@ private void InitializeComponent() // cbRunAtLogin // cbRunAtLogin.AutoSize = true; - cbRunAtLogin.Location = new Point(469, 608); - cbRunAtLogin.Margin = new Padding(4, 3, 4, 3); + cbRunAtLogin.Location = new Point(670, 1008); + cbRunAtLogin.Margin = new Padding(6, 5, 6, 5); cbRunAtLogin.Name = "cbRunAtLogin"; - cbRunAtLogin.Size = new Size(90, 19); + cbRunAtLogin.Size = new Size(134, 29); cbRunAtLogin.TabIndex = 46; cbRunAtLogin.Text = "Run at login"; cbRunAtLogin.UseVisualStyleBackColor = true; @@ -727,38 +748,76 @@ private void InitializeComponent() // // contextMenuStrip1 // + contextMenuStrip1.ImageScalingSize = new Size(24, 24); contextMenuStrip1.Name = "contextMenuStrip1"; - contextMenuStrip1.Size = new Size(181, 26); + contextMenuStrip1.Size = new Size(61, 4); // // txtVersion // txtVersion.BorderStyle = BorderStyle.None; txtVersion.ForeColor = Color.Black; - txtVersion.Location = new Point(346, 633); + txtVersion.Location = new Point(670, 1060); + txtVersion.Margin = new Padding(4, 5, 4, 5); txtVersion.Name = "txtVersion"; txtVersion.ReadOnly = true; - txtVersion.Size = new Size(100, 16); + txtVersion.Size = new Size(66, 24); txtVersion.TabIndex = 47; - txtVersion.TextAlign = HorizontalAlignment.Right; // // cbIsPaused // cbIsPaused.AutoSize = true; - cbIsPaused.Location = new Point(594, 608); + cbIsPaused.Location = new Point(849, 1008); + cbIsPaused.Margin = new Padding(4, 5, 4, 5); cbIsPaused.Name = "cbIsPaused"; - cbIsPaused.Size = new Size(57, 19); + cbIsPaused.Size = new Size(83, 29); cbIsPaused.TabIndex = 48; cbIsPaused.Text = "Pause"; cbIsPaused.UseVisualStyleBackColor = true; cbIsPaused.CheckedChanged += cbIsPaused_CheckedChanged; // + // chkResetOnMinimize + // + chkResetOnMinimize.AutoSize = true; + chkResetOnMinimize.Location = new Point(685, 859); + chkResetOnMinimize.Margin = new Padding(6, 5, 6, 5); + chkResetOnMinimize.Name = "chkResetOnMinimize"; + chkResetOnMinimize.Size = new Size(452, 29); + chkResetOnMinimize.TabIndex = 49; + chkResetOnMinimize.Text = "Reset 'Movable' if app is minimized or closed to tray"; + chkResetOnMinimize.UseVisualStyleBackColor = true; + chkResetOnMinimize.CheckedChanged += chkResetOnMinimize_CheckedChanged; + // + // btnResetColumns + // + btnResetColumns.Location = new Point(259, 1050); + btnResetColumns.Name = "btnResetColumns"; + btnResetColumns.Size = new Size(138, 45); + btnResetColumns.TabIndex = 50; + btnResetColumns.Text = "Reset Columns"; + btnResetColumns.UseVisualStyleBackColor = true; + btnResetColumns.Click += btnResetColumns_Click; + // + // chkPortableMode + // + chkPortableMode.AutoSize = true; + chkPortableMode.Location = new Point(484, 1059); + chkPortableMode.Margin = new Padding(4, 5, 4, 5); + chkPortableMode.Name = "chkPortableMode"; + chkPortableMode.Size = new Size(155, 29); + chkPortableMode.TabIndex = 51; + chkPortableMode.Text = "Portable mode"; + chkPortableMode.UseVisualStyleBackColor = true; + // // frmMain // AcceptButton = butOK; - AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleDimensions = new SizeF(10F, 25F); AutoScaleMode = AutoScaleMode.Font; CancelButton = butCancel; - ClientSize = new Size(962, 674); + ClientSize = new Size(1374, 1118); + Controls.Add(chkPortableMode); + Controls.Add(btnResetColumns); + Controls.Add(chkResetOnMinimize); Controls.Add(cbIsPaused); Controls.Add(txtVersion); Controls.Add(cbRunAtLogin); @@ -775,13 +834,15 @@ private void InitializeComponent() Controls.Add(butOK); FormBorderStyle = FormBorderStyle.Fixed3D; Icon = (Icon)resources.GetObject("$this.Icon"); - Margin = new Padding(4, 3, 4, 3); + Margin = new Padding(6, 5, 6, 5); MaximizeBox = false; MinimizeBox = false; Name = "frmMain"; + StartPosition = FormStartPosition.CenterScreen; Text = "WinSize4"; FormClosing += frmMain_FormClosing; Load += Form1_Load; + Shown += frmMain_Shown; groupBox1.ResumeLayout(false); groupBox1.PerformLayout(); groupBox2.ResumeLayout(false); @@ -858,6 +919,10 @@ private void InitializeComponent() public NotifyIcon notifyIcon1; private ContextMenuStrip contextMenuStrip1; private CheckBox cbIsPaused; + private CheckBox chkResetOnMinimize; + private Button btnResetColumns; + private ImageList stateImageList; + private CheckBox chkPortableMode; } } diff --git a/frmMain.cs b/frmMain.cs index d6e5590..cb94485 100644 --- a/frmMain.cs +++ b/frmMain.cs @@ -1,15 +1,49 @@ -using System.Diagnostics; +using Microsoft.Toolkit.Uwp.Notifications; +using Microsoft.Win32; +using System.Diagnostics; using System.Runtime.InteropServices; +using System.Text; using System.Threading.Tasks; using System.Windows.Forms; -using Microsoft.Toolkit.Uwp.Notifications; -using Microsoft.Win32; using Windows.UI.Notifications; +using System.Collections.Concurrent; namespace WinSize4 { public partial class frmMain : Form { + // Delegate for the hook callback function + private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); + + // Import the SetWinEventHook and UnhookWinEvent functions from user32.dll + [DllImport("user32.dll")] + private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); + + [DllImport("user32.dll")] + private static extern bool UnhookWinEvent(IntPtr hWinEventHook); + + [DllImport("user32.dll", SetLastError = true)] + static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + // Constants for the events we want to listen for. + private const uint EVENT_SYSTEM_MINIMIZESTART = 0x0016; + private const uint EVENT_OBJECT_HIDE = 0x8003; + private const uint WINEVENT_OUTOFCONTEXT = 0; + + // A handle to our event hook + private IntPtr _hhook; + + // Keep a reference to the delegate, otherwise it can be garbage collected + private WinEventDelegate _winEventDelegate; + + private ConcurrentBag _minimizedWindowHandles = new ConcurrentBag(); // Store handles of minimized windows + public ClsCurrentWindows _currentWindows = new ClsCurrentWindows(); // Windows open right now public ClsSavedWindows _savedWindows = new ClsSavedWindows(); // Windows saved on file public ClsScreens _screens = new ClsScreens(); @@ -24,6 +58,8 @@ public partial class frmMain : Form bool isPausedUpdating; string _lastTitle = ""; private bool allowVisible; // ContextMenu's Show command used + private bool _userHasTakenControlOfColumns = false; // This flag is true ONLY when the user has manually resized a column. + private bool _isUserDraggingColumn = false; // A temporary flag to confirm a user drag is in progress. //********************************************** // Form initialize @@ -31,12 +67,15 @@ public partial class frmMain : Form public frmMain() { InitializeComponent(); - _settings.LoadFromFile(); - _savedWindows.Load(); - _screens.Load(); + _winEventDelegate = new WinEventDelegate(WinEventProc); + _settings.InitializeAndLoad(); + string path = _settings.ActivePath; // Get the determined path + ClsDebug.Initialize(path); // Initialize the logger first + _savedWindows.Load(path); + _screens.Load(path); _screens.AddNewScreens(); _screens.SetPresent(); - _screens.Save(); + _screens.Save(path); //_savedWindows.Order(); PopulateListBox(); //txtVersion.Text = GetType().Assembly.GetName().Version.ToString(); @@ -70,7 +109,54 @@ public frmMain() cbShowAllWindows.Checked = _settings.showAllWindows; cbResetIfNewScreen.Checked = _settings.resetIfNewScreen; cbRunAtLogin.Checked = _settings.runAtLogin; + chkPortableMode.Checked = _settings.PortableMode; cbIsPaused.Checked = _settings.isPaused; + chkResetOnMinimize.Checked = _settings.ResetOnMinimize; + + #region ListView Column Initialization + // Unsubscribe from events to prevent them from firing during the setup. + this.listView1.Resize -= new System.EventHandler(this.listView1_Resize); + this.listView1.ColumnWidthChanging -= new System.Windows.Forms.ColumnWidthChangingEventHandler(this.listView1_ColumnWidthChanging); + this.listView1.ColumnWidthChanged -= new System.Windows.Forms.ColumnWidthChangedEventHandler(this.listView1_ColumnWidthChanged); + + // ClsDebug.LogNow("[ColumnLayout] Starting ListView initialization in constructor."); + // Part 1: Always set the desired visual order first. This is static. + try + { + int totalColumns = listView1.Columns.Count; + if (totalColumns > 3) // Ensure there are enough columns + { + // Find the columns by their text or name + ColumnHeader nameCol = listView1.Columns.Cast().FirstOrDefault(c => c.Text == "Name"); + ColumnHeader widthCol = listView1.Columns.Cast().FirstOrDefault(c => c.Text == "Width"); + ColumnHeader heightCol = listView1.Columns.Cast().FirstOrDefault(c => c.Text == "Height"); + ColumnHeader primaryCol = listView1.Columns.Cast().FirstOrDefault(c => c.Text == "Primary"); + + // The final visual order + if (nameCol != null) nameCol.DisplayIndex = 0; + if (widthCol != null) widthCol.DisplayIndex = 1; + if (heightCol != null) heightCol.DisplayIndex = 2; + if (primaryCol != null) primaryCol.DisplayIndex = 3; + } + } + catch (Exception ex) + { + // ClsDebug.LogNow($"[ColumnLayout] ERROR setting display order: {ex.Message}"); + } + // Part 2: Load settings OR apply the default auto-expanding layout. + if (_settings.ListViewColumnWidths != null && _settings.ListViewColumnWidths.Count > 0) + { + _userHasTakenControlOfColumns = true; // Since using the user's saved layout, mark that they are in control. + } + else + { + // ClsDebug.LogNow("[ColumnLayout] No saved widths found. The app will be in control."); + // --- SCENARIO B: FIRST RUN (NO SAVED SETTINGS) --- + _userHasTakenControlOfColumns = false; // The application is in control of the layout. + } + // ClsDebug.LogNow($"[ColumnLayout] Constructor finished. _userHasTakenControlOfColumns = {_userHasTakenControlOfColumns}"); + #endregion ListView Column Initialization + ClsDebug.AddText("\nStarting"); ClsDebug.LogText(); timer1.Interval = _settings.Interval; @@ -78,12 +164,160 @@ public frmMain() //this.AddHandler(KeyDownEvent, new KeyEventHandler(KeyDown), true); } + //********************************************** + // Set default widths for fixed-size columns + //********************************************** + private void SetDefaultFixedColumnWidths() + { + // The FIX for the oversized with Autosize columns: Calculate widths manually. + foreach (ColumnHeader col in listView1.Columns) + { + if (col.Text == "Width") + { + col.Width = TextRenderer.MeasureText("Width", listView1.Font).Width + 5; // Text width + padding + // ClsDebug.LogNow($"[ColumnLayout] -> Set '{col.Text}' column width to {col.Width}px."); + } + else if (col.Text == "Height") + { + col.Width = TextRenderer.MeasureText("Height", listView1.Font).Width + 5; + // ClsDebug.LogNow($"[ColumnLayout] -> Set '{col.Text}' column width to {col.Width}px."); + } + else if (col.Text == "Primary") + { + col.Width = TextRenderer.MeasureText("Primary", listView1.Font).Width; + // ClsDebug.LogNow($"[ColumnLayout] -> Set '{col.Text}' column width to {col.Width}px."); + } + } + } + + //********************************************** + // Auto-resize the 'Name' column to fill remaining space + //********************************************** + private void ResizeNameColumn() + { + // ClsDebug.LogNow("[ColumnLayout] ResizeNameColumn() called."); + + ColumnHeader nameCol = listView1.Columns.Cast().FirstOrDefault(c => c.Text == "Name"); + if (nameCol == null) + { + // ClsDebug.LogNow("[ColumnLayout] -> Aborting resize because 'Name' column was not found."); + return; + } + + int allOtherColumnsWidth = 0; + foreach (ColumnHeader col in listView1.Columns) + { + if (col != nameCol) allOtherColumnsWidth += col.Width; + } + // ClsDebug.LogNow($"[ColumnLayout] -> Calculated allOtherColumnsWidth: {allOtherColumnsWidth}px."); + + int availableWidth = listView1.ClientSize.Width - allOtherColumnsWidth; + // ClsDebug.LogNow($"[ColumnLayout] -> listView.ClientSize.Width is {listView1.ClientSize.Width}px. Calculated availableWidth for 'Name': {availableWidth}px."); + + // Apply the new width, ensuring it's a reasonable value. + if (availableWidth > 50) // e.g., don't shrink it to nothing + { + nameCol.Width = availableWidth; + // ClsDebug.LogNow($"[ColumnLayout] -> SUCCESS: Set 'Name' column width to {availableWidth}px."); + } + // else + // { + // ClsDebug.LogNow($"[ColumnLayout] -> SKIPPED setting width because availableWidth ({availableWidth}px) was too small."); + // } + } + //********************************************** + // Saving column layout if user has changed it + //********************************************** + private void SaveColumnLayout() + { + // ClsDebug.LogNow("[ColumnLayout] SaveColumnLayout() called."); + // + // ClsDebug.LogNow("[ColumnLayout] -> Proceeding to save layout to settings object."); + if (_settings.ListViewColumnWidths == null) + { + _settings.ListViewColumnWidths = new Dictionary(); + } + else + { + _settings.ListViewColumnWidths.Clear(); + } + + foreach (ColumnHeader col in listView1.Columns) + { + _settings.ListViewColumnWidths[col.Text] = col.Width; + // ClsDebug.LogNow($"[ColumnLayout] -> Saving '{col.Text}' with width {col.Width}px."); + } + + _settings.SaveToFile(); + } + //********************************************** // Form load //********************************************** private void Form1_Load(object sender, EventArgs e) { notifyIcon1.Visible = true; + // Start listening for window minimize and hide events across all applications + _hhook = SetWinEventHook(EVENT_SYSTEM_MINIMIZESTART, EVENT_OBJECT_HIDE, IntPtr.Zero, _winEventDelegate, 0, 0, WINEVENT_OUTOFCONTEXT); + } + + //********************************************** + // Detecting when a managed window is minimized or sent to the tray and reseting its Moved flag + //********************************************** + private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + { + // 1. Check if a checkbox 'chkResetOnMinimize' is ticked and only care about events for top-level windows. + if (!chkResetOnMinimize.Checked || idObject != 0 || idChild != 0) + { + return; + } + + try + { + // Determine if this is a window that should be managed. + GetWindowThreadProcessId(hwnd, out uint processId); + if (processId == 0) return; + + var process = System.Diagnostics.Process.GetProcessById((int)processId); + if (process == null) return; + + // --- Start Debug Logging --- + // ClsDebug.LogNow($"[DEBUG] WinEventProc: Event fired for process '{process.ProcessName}' (hwnd: {hwnd})."); + + // Gather more info for the search, just like in the timer. + StringBuilder titleBuilder = new StringBuilder(256); + GetWindowText(hwnd, titleBuilder, titleBuilder.Capacity); + + StringBuilder classBuilder = new StringBuilder(256); + GetClassName(hwnd, classBuilder, classBuilder.Capacity); + + // Create a temporary object just for searching saved rules. + var searchProps = new ClsWindowProps + { + Exe = process.ProcessName, + Title = titleBuilder.ToString(), + WindowClass = classBuilder.ToString() + }; + + // ClsDebug.LogNow($"[DEBUG] WinEventProc: Searching for Title='{searchProps.Title}', Exe='{searchProps.Exe}'"); + + // If a rule exists for this application, to reset its Moved flag. + int savedIndex = _savedWindows.GetIndexAllScreens(searchProps, _screens.ScreenList, 0); + + // ClsDebug.LogNow($"[DEBUG] WinEventProc: GetIndexAllScreens returned index: {savedIndex}."); + + // If a configuration exists for this window, add its handle to our list for processing later. + if (savedIndex > -1) + { + _minimizedWindowHandles.Add(hwnd); + // ClsDebug.LogNow($"[DEBUG] WinEventProc: SUCCESS! Added hwnd {hwnd} to the list."); + } + } + catch (Exception ex) + { + // Ignore errors, e.g., if the process closes. + // ClsDebug.LogNow($"[DEBUG] WinEventProc: An error occurred: {ex.Message}"); + } } //********************************************** @@ -139,7 +373,8 @@ protected override void WndProc(ref Message m) { _toastClicked = true; allowVisible = true; - this.Invoke((MethodInvoker)delegate { + this.Invoke((MethodInvoker)delegate + { Show(); }); //ToastArguments args = ToastArguments.Parse(toastArgs.Argument); @@ -173,6 +408,18 @@ private void timer1_Tick(object sender, EventArgs e) ClsDebug.AddText("Timer1_Tick starting"); timer1.Stop(); long hWnd = (long)GetForegroundWindow(); + + // Process any windows that were minimized since the last tick. + while (_minimizedWindowHandles.TryTake(out IntPtr hwndToReset)) + { + // Find the window with this handle + var windowToUpdate = _currentWindows.Windows.FirstOrDefault(w => w.hWnd == (long)hwndToReset); + if (windowToUpdate != null) + { + windowToUpdate.Moved = false; + } + } + if (_settings.isPaused) { ClsDebug.AddText("WinSize4 is paused"); @@ -202,7 +449,8 @@ private void timer1_Tick(object sender, EventArgs e) if (targetSavedWindowsIndex > -1 && currentWindowProps.Title != "" && currentWindowProps.Title != _lastTitle && - (_currentWindows.Windows[currentWindowsIndex].Moved == false || _savedWindows.Props[targetSavedWindowsIndex].AlwaysMove == true)) + (_currentWindows.Windows[currentWindowsIndex].Moved == false || _savedWindows.Props[targetSavedWindowsIndex].AlwaysMove == true) + && !_savedWindows.Props[targetSavedWindowsIndex].Disabled) { int targetScreenIndex = _screens.GetScreenIndexForWindow(_savedWindows.Props[targetSavedWindowsIndex]); //bool targetWindowHasParent = ((int)GetParent((IntPtr)hWnd) > 0); @@ -210,19 +458,43 @@ private void timer1_Tick(object sender, EventArgs e) // Only continue if window is not a child window to a current window, or child windows should be considered if (!_savedWindows.Props[targetSavedWindowsIndex].IgnoreChildWindows || !targetWindowHasParent) { - ClsDebug.AddText("Moving window: " + Text); - - _currentWindows.MoveCurrentWindow(currentWindowsIndex, - _savedWindows.Props[targetSavedWindowsIndex], - targetSavedWindowsIndex, - _screens.ScreenList[targetScreenIndex], - new ClsScreenList()); - _currentWindows.UpdateWindowProperties(currentWindowsIndex); - _currentWindows.Windows[currentWindowsIndex].Moved = true; - //Debug.WriteLine($"Execution Time: {watch.ElapsedMilliseconds} ms"); - ClsDebug.AddText($"Execution Time: {watch.ElapsedMilliseconds} ms"); - watch.Stop(); - ClsDebug.LogText(); + // For clarity, get the target properties from your saved settings. + var targetProps = _savedWindows.Props[targetSavedWindowsIndex]; + //ClsDebug.LogNow($"[ColumnLayout] -> Set '{col.Text}' column width to {col.Width}px."); + ClsDebug.AddText($"[Compare window's parameters]: currentWindowProps.Left:'{currentWindowProps.Left}' - targetProps.Left:'{targetProps.Left}'"); + ClsDebug.AddText($"[Compare window's parameters]: currentWindowProps.Left:'{currentWindowProps.Top}' - targetProps.Left:'{targetProps.Top}'"); + ClsDebug.AddText($"[Compare window's parameters]: currentWindowProps.Left:'{currentWindowProps.Width}' - targetProps.Left:'{targetProps.Width}'"); + ClsDebug.AddText($"[Compare window's parameters]: currentWindowProps.Left:'{currentWindowProps.Height}' - targetProps.Left:'{targetProps.Height}'"); + // Compare the current window's geometry with the saved target geometry. + // Also ensure that the 'AlwaysMove' flag will bypass this check. + if (currentWindowProps.Left == targetProps.Left && + currentWindowProps.Top == targetProps.Top && + currentWindowProps.Width == targetProps.Width && + currentWindowProps.Height == targetProps.Height && + !targetProps.AlwaysMove) + { + // The window is already in the correct position. + // Skip the move, but MUST set the 'Moved' flag to true + // to prevent the app from checking it again on the next tick. + ClsDebug.LogNow("Window is already in the correct position. Skipping move."); + _currentWindows.Windows[currentWindowsIndex].Moved = true; + } + else + { + // The window is not in the correct position, so proceed with the move. + ClsDebug.AddText("Moving window: " + Text); + + _currentWindows.MoveCurrentWindow(currentWindowsIndex, + targetProps, + targetSavedWindowsIndex, + _screens.ScreenList[targetScreenIndex], + new ClsScreenList()); + _currentWindows.UpdateWindowProperties(currentWindowsIndex); + _currentWindows.Windows[currentWindowsIndex].Moved = true; + ClsDebug.AddText($"Execution Time: {watch.ElapsedMilliseconds} ms"); + watch.Stop(); + ClsDebug.LogText(); + } } } _lastTitle = currentWindowProps.Title; @@ -245,34 +517,45 @@ private void timer1_Tick(object sender, EventArgs e) //********************************************** private void PopulateListBox() { + ClsDebug.LogNow("[DrawDebug] PopulateListBox: Starting."); try { - int lastSelected = 0; + int lastSelectedTag = -1; if (listView1.SelectedItems.Count > 0) { - lastSelected = listView1.SelectedItems[0].Index; + lastSelectedTag = listView1.SelectedItems[0].Tag as int? ?? -1; } + listView1.Items.Clear(); + ClsDebug.LogNow("[DrawDebug] PopulateListBox: Items cleared."); string[] row; for (int i = 0; i < _savedWindows.Props.Count; i++) { - string Primary = _savedWindows.Props[i].Primary ? "Yes" : "No"; - row = new string[] { _savedWindows.Props[i].Name, _savedWindows.Props[i].MonitorBoundsWidth.ToString(), _savedWindows.Props[i].MonitorBoundsHeight.ToString(), Primary }; + var prop = _savedWindows.Props[i]; + string Primary = prop.Primary ? "Yes" : "No"; + + row = new string[] { prop.Name, prop.MonitorBoundsWidth.ToString(), prop.MonitorBoundsHeight.ToString(), Primary }; var listViewItem = new ListViewItem(row); - listViewItem.Tag = _savedWindows.Props[i].Tag; - if (_screens.GetScreenIndexForWindow(_savedWindows.Props[i]) == -1) + listViewItem.Tag = prop.Tag; + + // Set the checkbox state from the saved property + listViewItem.StateImageIndex = prop.Disabled ? 1 : 0; + + int screenIndex = _screens.GetScreenIndexForWindow(prop); + if (screenIndex == -1) { listView1.Items.Add(listViewItem); } - if (!_screens.ScreenList[_screens.GetScreenIndexForWindow(_savedWindows.Props[i])].Present) + else if (!_screens.ScreenList[screenIndex].Present) { if (_settings.showAllWindows) { listViewItem.ForeColor = Color.Silver; - listViewItem.SubItems[1].ForeColor = Color.Silver; - listViewItem.SubItems[2].ForeColor = Color.Silver; - listViewItem.SubItems[3].ForeColor = Color.Silver; listViewItem.UseItemStyleForSubItems = false; + for (int j = 1; j < listViewItem.SubItems.Count; j++) + { + listViewItem.SubItems[j].ForeColor = Color.Silver; + } listView1.Items.Add(listViewItem); } } @@ -280,20 +563,23 @@ private void PopulateListBox() { listView1.Items.Add(listViewItem); } - } - if (listView1.Items.Count > 0 && lastSelected < listView1.Items.Count) + ClsDebug.LogNow($"[DrawDebug] PopulateListBox: Finished. Total items in list: {listView1.Items.Count}."); + + if (lastSelectedTag != -1) { - listView1.Items[lastSelected].Selected = true; + int indexToSelect = GetListViewIndexByTag(lastSelectedTag); + if (indexToSelect > -1) + { + listView1.Items[indexToSelect].Selected = true; + listView1.Items[indexToSelect].Focused = true; + listView1.EnsureVisible(indexToSelect); + } } - listView1.Select(); - notifyIcon1.BalloonTipTitle = "WinSize4"; - notifyIcon1.BalloonTipText = _savedWindows.Props.Count.ToString() + " controlled windows"; } - catch - (Exception ex) + catch (Exception ex) { - ClsDebug.LogToEvent(ex, EventLogEntryType.Error, ""); + ClsDebug.LogNow($"[DrawDebug] PopulateListBox: CRITICAL ERROR! {ex.Message}"); } } //********************************************** @@ -395,6 +681,10 @@ private void listView1_SelectedIndexChanged(object sender, EventArgs e) radioStartsWithExclude.Checked = true; break; } + + // This forces the WindowClass textbox to update its enabled state + // based on the initial value of the checkbox. + cbWindowClass_CheckedChanged(this, EventArgs.Empty); } else // No item selected @@ -445,6 +735,7 @@ private void SaveValuesForIndex(int index) { _savedWindows.Props[index].Name = tbName.Text; _savedWindows.Props[index].ConsiderWindowClass = cbWindowClass.Checked; + _savedWindows.Props[index].WindowClass = tbWindowClass.Text; _savedWindows.Props[index].TitleInclude = tbTitleInclude.Text; _savedWindows.Props[index].TitleExclude = tbTitleExclude.Text; _savedWindows.Props[index].SearchTitleInclude = cbSearchTitleInclude.Checked; @@ -456,20 +747,24 @@ private void SaveValuesForIndex(int index) _savedWindows.Props[index].CanResize = cbCanResize.Checked; if (!cbCustomWidth.Checked) { - _savedWindows.Props[index].Width = int.Parse(tbWidth.Text); - _savedWindows.Props[index].Left = int.Parse(tbLeft.Text); + int.TryParse(tbWidth.Text, out int width); + _savedWindows.Props[index].Width = width; + + int.TryParse(tbLeft.Text, out int left); + _savedWindows.Props[index].Left = left; } if (!cbCustomHeight.Checked) { - _savedWindows.Props[index].Height = int.Parse(tbHeight.Text); - _savedWindows.Props[index].Top = int.Parse(tbTop.Text); + int.TryParse(tbHeight.Text, out int height); + _savedWindows.Props[index].Height = height; + + int.TryParse(tbTop.Text, out int top); + _savedWindows.Props[index].Top = top; } _savedWindows.Props[index].MaxWidth = cbCustomWidth.Checked; _savedWindows.Props[index].MaxHeight = cbCustomHeight.Checked; _savedWindows.Props[index].FullScreen = cbFullScreen.Checked; - _savedWindows.Props[index].IgnoreChildWindows = cbIgnoreChildWindows.Checked; - _savedWindows.Props[index].AlwaysMove = cbAlwaysMove.Checked; - _savedWindows.Props[index].CanResize = cbCanResize.Checked; + if (radioFullInclude.Checked) _savedWindows.Props[index].SearchTypeInclude = ClsWindowProps.Full; if (radioContainsInclude.Checked) @@ -562,7 +857,7 @@ private void butEditScreens_Click(object sender, EventArgs e) if (result == DialogResult.OK) { _screens.ScreenList = Scr.ReturnScreenList; - _screens.Save(); + _screens.Save(_settings.ActivePath); } } @@ -575,8 +870,18 @@ private void butOK_Click(object sender, EventArgs e) { SaveValuesForIndex(_savedWindows.GetWindowIndexByTag((int)listView1.SelectedItems[0].Tag)); } - _savedWindows.Save(); + + bool newMode = chkPortableMode.Checked; + if (newMode != _settings.PortableMode) + { + _settings.PortableMode = newMode; + _settings.UpdateSettingsLocation(); + } + + _savedWindows.Save(_settings.ActivePath); + _screens.Save(_settings.ActivePath); _settings.SaveToFile(); + RegisterListener(); _currentWindows.ResetMoved(); this.Hide(); @@ -604,18 +909,25 @@ private void butCancel_Click(object sender, EventArgs e) //********************************************** private void butApply_Click(object sender, EventArgs e) { - if (listView1.Items.Count > 0) + if (listView1.Items.Count > 0 && listView1.SelectedItems.Count > 0) { - if (listView1.SelectedItems.Count > 0) - { - SaveValuesForIndex(_savedWindows.GetWindowIndexByTag((int)listView1.SelectedItems[0].Tag)); - } - PopulateListBox(); + SaveValuesForIndex(_savedWindows.GetWindowIndexByTag((int)listView1.SelectedItems[0].Tag)); } - _savedWindows.Save(); + + bool newMode = chkPortableMode.Checked; + if (newMode != _settings.PortableMode) + { + _settings.PortableMode = newMode; + _settings.UpdateSettingsLocation(); + } + + _savedWindows.Save(_settings.ActivePath); + _screens.Save(_settings.ActivePath); _settings.SaveToFile(); + RegisterListener(); _currentWindows.ResetMoved(); + PopulateListBox(); // Repopulate to reflect any changes } //********************************************** @@ -649,18 +961,18 @@ private void cbIsPaused_CheckedChanged(object sender, EventArgs e) { isPausedUpdating = true; if (((ToolStripMenuItem)notifyIcon1.ContextMenuStrip.Items[1]).Checked) - { - _settings.isPaused = false; - ((ToolStripMenuItem)notifyIcon1.ContextMenuStrip.Items[1]).Checked = false; - cbIsPaused.Checked = false; - } - else - { - _settings.isPaused = true; - ((ToolStripMenuItem)notifyIcon1.ContextMenuStrip.Items[1]).Checked = true; - cbIsPaused.Checked = true; - } - _dirty = true; + { + _settings.isPaused = false; + ((ToolStripMenuItem)notifyIcon1.ContextMenuStrip.Items[1]).Checked = false; + cbIsPaused.Checked = false; + } + else + { + _settings.isPaused = true; + ((ToolStripMenuItem)notifyIcon1.ContextMenuStrip.Items[1]).Checked = true; + cbIsPaused.Checked = true; + } + _dirty = true; isPausedUpdating = false; } } @@ -835,7 +1147,17 @@ private void butResetMoved_Click(object sender, EventArgs e) private void butShow_Click(object sender, EventArgs e) { allowVisible = true; - Show(); + + // 1. Ensure the form is visible and not minimized. + this.Show(); + this.WindowState = FormWindowState.Normal; + + // 2. The "TopMost" trick to steal focus. + this.TopMost = true; // Temporarily bring it to the very top. + this.TopMost = false; // Immediately set it back to a normal window. + + // 3. Explicitly activate and focus the form. + this.Activate(); } private void butRemove_Click(object sender, EventArgs e) @@ -918,9 +1240,16 @@ private void tbHotKeyCharacter_TextChanged(object sender, EventArgs e) private void notifyIcon1_DoubleClick(object sender, EventArgs e) { - Show(); + // 1. Ensure the form is visible and not minimized. + this.Show(); this.WindowState = FormWindowState.Normal; - //notifyIcon1.Visible = false; + + // 2. The "TopMost" trick to steal focus. + this.TopMost = true; // Temporarily bring it to the very top. + this.TopMost = false; // Immediately set it back to a normal window. + + // 3. Explicitly activate and focus the form. + this.Activate(); } private void cbShowAllWindows_CheckedChanged(object sender, EventArgs e) @@ -974,6 +1303,15 @@ private void frmMain_FormClosing(object sender, FormClosingEventArgs e) e.Cancel = true; this.Hide(); } + else + { + // Stop listening when the application is closing + UnhookWinEvent(_hhook); + + _savedWindows.Save(_settings.ActivePath); + _screens.Save(_settings.ActivePath); + _settings.SaveToFile(); + } } private void cbRunAtLogin_CheckedChanged(object sender, EventArgs e) @@ -1012,6 +1350,37 @@ private void listView1_MouseClick(object sender, MouseEventArgs e) contextMenuStrip1.Show(Cursor.Position); } } + + // Get detailed hit-test information about where the user clicked. + var hitTestInfo = listView1.HitTest(e.X, e.Y); + + // Check if the click was specifically on the state image area. + if (hitTestInfo.Location == ListViewHitTestLocations.StateImage) + { + // Knowing for sure the user clicked the icon, so we can get the item. + var lvi = hitTestInfo.Item; + + try + { + int tag = (int)lvi.Tag; + int savedIndex = _savedWindows.GetWindowIndexByTag(tag); + + if (savedIndex != -1) + { + // 1. Toggle the "Disabled" property in the data source. + _savedWindows.Props[savedIndex].Disabled = !_savedWindows.Props[savedIndex].Disabled; + + // 2. Update the image to reflect the new state. + lvi.StateImageIndex = _savedWindows.Props[savedIndex].Disabled ? 1 : 0; + + _dirty = true; // Mark that there are unsaved changes. + } + } + catch (Exception ex) + { + ClsDebug.LogToEvent(ex, EventLogEntryType.Error, "Error in listView1_MouseClick"); + } + } } //********************************************** @@ -1042,9 +1411,97 @@ private void listView1_MouseClick(object sender, MouseEventArgs e) [DllImport("user32.dll")] static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); - private void groupBox2_Enter(object sender, EventArgs e) + // This event fires after any resize, but it has a way to know if it was a user. + private void listView1_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e) { + // Only act if the "authenticator" flag is true. This ignores all programmatic changes. + if (_isUserDraggingColumn) + { + _userHasTakenControlOfColumns = true; // 1. The user is now permanently in control. + SaveColumnLayout(); // 2. Immediately save the new layout. + _isUserDraggingColumn = false; // 3. Reset the authenticator flag, ready for the next drag. + } + } + + private void chkResetOnMinimize_CheckedChanged(object sender, EventArgs e) + { + _settings.ResetOnMinimize = chkResetOnMinimize.Checked; + } + + private void listView1_Resize(object sender, EventArgs e) + { + // Only auto-resize if the user has not yet taken control. + if (!_userHasTakenControlOfColumns) + { + // Schedule the expansion of the 'Name' column to happen AFTER the resize is finished. + // This gives the control time to settle and avoids the rendering bug. + ResizeNameColumn(); + } + } + + // This is the "Authenticator" event. It ONLY fires when a user is dragging. + private void listView1_ColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e) + { + // This event ONLY fires from a user drag. Set the flag to true. + _isUserDraggingColumn = true; + } + + private void btnResetColumns_Click(object sender, EventArgs e) + { + // 1. Unsubscribe from events to make the operation atomic --- + // This prevents the ListView from entering a chaotic state while we work. + this.listView1.Resize -= new System.EventHandler(this.listView1_Resize); + this.listView1.ColumnWidthChanged -= new System.Windows.Forms.ColumnWidthChangedEventHandler(this.listView1_ColumnWidthChanged); + + // 2. Reset the saved settings data. + // Check if the settings exist, clear the dictionary, and immediately save the file. + // This ensures that on the next launch, the app will perform the "first run" setup. + if (_settings.ListViewColumnWidths != null && _settings.ListViewColumnWidths.Count > 0) + { + _settings.ListViewColumnWidths.Clear(); + _settings.SaveToFile(); + } + + // 3. Immediately re-apply the initial "first run" visual layout. + // This gives the user instant visual feedback. + SetDefaultFixedColumnWidths(); + ResizeNameColumn(); + + // 4. Reset the application's state flag. + // This is crucial to re-enable the auto-resizing of the 'Name' column. + _userHasTakenControlOfColumns = false; + + // Re-subscribe here, after everything is stable. + this.listView1.Resize += new System.EventHandler(this.listView1_Resize); + this.listView1.ColumnWidthChanged += new System.Windows.Forms.ColumnWidthChangedEventHandler(this.listView1_ColumnWidthChanged); + } + + private void frmMain_Shown(object sender, EventArgs e) + { + // This event fires after the form is fully stable and visible. + + // --- Perform the full, stable layout process --- + + if (_userHasTakenControlOfColumns) + { + // If the user has saved preferences, apply them now. + foreach (ColumnHeader col in listView1.Columns) + { + if (_settings.ListViewColumnWidths.ContainsKey(col.Text)) + col.Width = _settings.ListViewColumnWidths[col.Text]; + } + } + else + { + // If this is a first run, apply the default layout. + SetDefaultFixedColumnWidths(); + ResizeNameColumn(); + } + // --- Finally, re-subscribe to events now that the initial layout is complete --- + this.listView1.Resize += new System.EventHandler(this.listView1_Resize); + this.listView1.ColumnWidthChanging += new System.Windows.Forms.ColumnWidthChangingEventHandler(this.listView1_ColumnWidthChanging); + this.listView1.ColumnWidthChanged += new System.Windows.Forms.ColumnWidthChangedEventHandler(this.listView1_ColumnWidthChanged); } } } diff --git a/frmMain.resx b/frmMain.resx index a788317..4f4ccaf 100644 --- a/frmMain.resx +++ b/frmMain.resx @@ -203,6 +203,43 @@ 217, 17 + + 530, 17 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs + LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu + SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAVgYAAAJNU0Z0AUkBTAIBAQIB + AAFAAQABQAEAARABAAEQAQAE/wERAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAARADAAEBAQABEAYAAQga + AAHeAXsBdwFfATEBRwENATsBDQE7ATEBRwF3AV8B3gF7EAAB3gF/AdYBegGtAX0BKQF9ASkBfQGtAX0B + 1gF6Ad4Bf0wAAf8BfwF3AV8BDQE3AQ4BPwF2AVsBmQFrAZkBawF2AVsBDgE/AQ0BNwF3AV8B/wF/CAAB + /wF/AbUBegEIAX0BSgF9AZQBegEYAXsBGAF7AZQBegFKAX0BCAF9AbUBegH/AX9GAAH/AX8CUwHtATYB + mQFrAf8BfwgAAf8BfwGZAWsB7QE2AlMB/wF/BAAB/wF/ATEBegEIAX0BGAF7Af8BfwgAAf8BfwEYAXsB + CAF9ATEBegH/AX9EAAF3AV8B7QE2Ad0BdxAAAd0BdwHtATYBdwFfBAABtQF6AQgBfQGcAXsQAAGcAXsB + CAF9AbUBekIAAd4BewENATcBmQFrFAABmQFrAQ0BNwHeAXsB3gF/AQgBfQEYAXsEAAH/AX8B/wF/BAAB + /wF/Af8BfwQAARgBewEIAX0B3gF/QAABdwFfAQ4BPwH/AX8EAAH/AX8BdgFfAXYBWwH/AX8IAAH/AX8B + DgE/AXcBXwHWAXoBSgF9Af8BfwIAAf8BfwEpAX0BEAF6Af8BfwH/AX8BEAF6ASkBfQH/AX8CAAH/AX8B + SgF9AdYBekAAATEBRwF2AVsEAAH/AX8BMwFPAewBNgHsATYBdQFbAf8BfwgAAXYBWwExAUcBrQF9AZQB + egQAAf8BfwExAXoBCAF9ARABegEQAXoBCAF9ATEBegH/AX8EAAGUAXoBrQF9QAABDQE7AZkBawQAAXYB + WwHsATYBVAFTATIBSwHsATYBdgFfAf8BfwYAAZkBawENATsBKQF9ARgBewYAAf8BfwExAXoBCAF9AQgB + fQExAXoB/wF/BgABGAF7ASkBfUAAAQ0BOwGZAWsEAAG8AXMBeAFjAf8BfwH/AX8BMQFHAewBNgF3AV8G + AAGZAWsBDQE7ASkBfQEYAXsGAAH/AX8BMQF6AQgBfQEIAX0BMQF6Af8BfwYAARgBewEpAX1AAAExAUcB + dgFbDAAB/wF/ATABRwHsATYBuwFzBAABdgFbATEBRwGtAX0BlAF6BAAB/wF/ATEBegEIAX0BEAF6ARAB + egEIAX0BMQF6Af8BfwQAAZQBegGtAX1AAAF3AV8BDgE/Af8BfwwAAf4BewF2AVsB3gF7AgAB/wF/AQ4B + PwF3AV8B1gF6AUoBfQH/AX8CAAH/AX8BKQF9ARABegH/AX8B/wF/ARABegEpAX0B/wF/AgAB/wF/AUoB + fQHWAXpAAAHeAXsBDQE3AZkBaxQAAZkBawENATcB3gF7Ad4BfwEIAX0BGAF7BAAB/wF/Af8BfwQAAf8B + fwH/AX8EAAEYAXsBCAF9Ad4Bf0IAAXcBXwHtATYB3QF3EAAB3QF3Ae0BNgF3AV8EAAG1AXoBCAF9AZwB + exAAAZwBewEIAX0BtQF6RAAB/wF/AlMB7QE2AZkBawH/AX8IAAH/AX8BmQFrAe0BNgJTAf8BfwQAAf8B + fwExAXoBCAF9ARgBewH/AX8IAAH/AX8BGAF7AQgBfQExAXoB/wF/RgAB/wF/AXcBXwENATcBDgE/AXYB + WwGZAWsBmQFrAXYBWwEOAT8BDQE3AXcBXwH/AX8IAAH/AX8BtQF6AQgBfQFKAX0BlAF6ARgBewEYAXsB + lAF6AUoBfQEIAX0BtQF6Af8Bf0wAAd4BewF3AV8BMQFHAQ0BOwENATsBMQFHAXcBXwHeAXsQAAHeAX8B + 1gF6Aa0BfQEpAX0BKQF9Aa0BfQHWAXoB3gF/SAABQgFNAT4HAAE+AwABKAMAAUADAAEQAwABAQEAAQEF + AAGAFwAD/wEAAfABDwHwAQ8EAAHAAQMBwAEDBAABgwHBAYMBwQQAAY8B8QGPAfEEAAEfAfgBGQGYBAAB + GAF4ARABCAQAATABPAEwAQwEAAEwARwBOAEcBAABMAEcATgBHAQAAT8BDAEwAQwEAAEfAYgBEAEIBAAB + HwH4ARkBmAQAAY8B8QGPAfEEAAGDAcEBgwHBBAABwAEDAcABAwQAAfABDwHwAQ8EAAs= + + 314, 17 diff --git a/frmScreens.Designer.cs b/frmScreens.Designer.cs index 5838263..d418de4 100644 --- a/frmScreens.Designer.cs +++ b/frmScreens.Designer.cs @@ -29,270 +29,289 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmScreens)); - this.tbFullHeight = new System.Windows.Forms.TextBox(); - this.tbFullWidth = new System.Windows.Forms.TextBox(); - this.tbCustomTop = new System.Windows.Forms.TextBox(); - this.tbCustomLeft = new System.Windows.Forms.TextBox(); - this.label10 = new System.Windows.Forms.Label(); - this.butCancel = new System.Windows.Forms.Button(); - this.butOK = new System.Windows.Forms.Button(); - this.label1 = new System.Windows.Forms.Label(); - this.tbWorkingAreaHeight = new System.Windows.Forms.TextBox(); - this.tbWorkingAreaWidth = new System.Windows.Forms.TextBox(); - this.label2 = new System.Windows.Forms.Label(); - this.label3 = new System.Windows.Forms.Label(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.label6 = new System.Windows.Forms.Label(); - this.label7 = new System.Windows.Forms.Label(); - this.tbCustomHeight = new System.Windows.Forms.TextBox(); - this.tbCustomWidth = new System.Windows.Forms.TextBox(); - this.label4 = new System.Windows.Forms.Label(); - this.label5 = new System.Windows.Forms.Label(); - this.listView1 = new System.Windows.Forms.ListView(); - this.width = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.height = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.primary = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.groupBox1.SuspendLayout(); - this.SuspendLayout(); + tbFullHeight = new TextBox(); + tbFullWidth = new TextBox(); + tbCustomTop = new TextBox(); + tbCustomLeft = new TextBox(); + label10 = new Label(); + butCancel = new Button(); + butOK = new Button(); + label1 = new Label(); + tbWorkingAreaHeight = new TextBox(); + tbWorkingAreaWidth = new TextBox(); + label2 = new Label(); + label3 = new Label(); + groupBox1 = new GroupBox(); + label6 = new Label(); + label7 = new Label(); + tbCustomHeight = new TextBox(); + tbCustomWidth = new TextBox(); + label4 = new Label(); + label5 = new Label(); + listView1 = new ListView(); + width = new ColumnHeader(); + height = new ColumnHeader(); + primary = new ColumnHeader(); + groupBox1.SuspendLayout(); + SuspendLayout(); // // tbFullHeight // - this.tbFullHeight.Enabled = false; - this.tbFullHeight.Location = new System.Drawing.Point(391, 30); - this.tbFullHeight.Name = "tbFullHeight"; - this.tbFullHeight.Size = new System.Drawing.Size(113, 20); - this.tbFullHeight.TabIndex = 34; + tbFullHeight.Enabled = false; + tbFullHeight.Location = new Point(652, 58); + tbFullHeight.Margin = new Padding(5, 6, 5, 6); + tbFullHeight.Name = "tbFullHeight"; + tbFullHeight.Size = new Size(186, 31); + tbFullHeight.TabIndex = 34; // // tbFullWidth // - this.tbFullWidth.Enabled = false; - this.tbFullWidth.Location = new System.Drawing.Point(272, 30); - this.tbFullWidth.Name = "tbFullWidth"; - this.tbFullWidth.Size = new System.Drawing.Size(113, 20); - this.tbFullWidth.TabIndex = 32; + tbFullWidth.Enabled = false; + tbFullWidth.Location = new Point(453, 58); + tbFullWidth.Margin = new Padding(5, 6, 5, 6); + tbFullWidth.Name = "tbFullWidth"; + tbFullWidth.Size = new Size(186, 31); + tbFullWidth.TabIndex = 32; // // tbCustomTop // - this.tbCustomTop.Location = new System.Drawing.Point(198, 32); - this.tbCustomTop.Name = "tbCustomTop"; - this.tbCustomTop.Size = new System.Drawing.Size(113, 20); - this.tbCustomTop.TabIndex = 38; - this.tbCustomTop.Visible = false; - this.tbCustomTop.TextChanged += new System.EventHandler(this.tbCustomTop_TextChanged); + tbCustomTop.Location = new Point(330, 62); + tbCustomTop.Margin = new Padding(5, 6, 5, 6); + tbCustomTop.Name = "tbCustomTop"; + tbCustomTop.Size = new Size(186, 31); + tbCustomTop.TabIndex = 38; + tbCustomTop.Visible = false; + tbCustomTop.TextChanged += tbCustomTop_TextChanged; // // tbCustomLeft // - this.tbCustomLeft.Location = new System.Drawing.Point(79, 32); - this.tbCustomLeft.Name = "tbCustomLeft"; - this.tbCustomLeft.Size = new System.Drawing.Size(113, 20); - this.tbCustomLeft.TabIndex = 37; - this.tbCustomLeft.Visible = false; - this.tbCustomLeft.TextChanged += new System.EventHandler(this.tbCustomLeft_TextChanged); + tbCustomLeft.Location = new Point(132, 62); + tbCustomLeft.Margin = new Padding(5, 6, 5, 6); + tbCustomLeft.Name = "tbCustomLeft"; + tbCustomLeft.Size = new Size(186, 31); + tbCustomLeft.TabIndex = 37; + tbCustomLeft.Visible = false; + tbCustomLeft.TextChanged += tbCustomLeft_TextChanged; // // label10 // - this.label10.AutoSize = true; - this.label10.Location = new System.Drawing.Point(190, 33); - this.label10.Name = "label10"; - this.label10.Size = new System.Drawing.Size(47, 13); - this.label10.TabIndex = 36; - this.label10.Text = "Full area"; + label10.AutoSize = true; + label10.Location = new Point(317, 63); + label10.Margin = new Padding(5, 0, 5, 0); + label10.Name = "label10"; + label10.Size = new Size(77, 25); + label10.TabIndex = 36; + label10.Text = "Full area"; // // butCancel // - this.butCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.butCancel.Location = new System.Drawing.Point(439, 191); - this.butCancel.Name = "butCancel"; - this.butCancel.Size = new System.Drawing.Size(75, 23); - this.butCancel.TabIndex = 41; - this.butCancel.Text = "Cancel"; - this.butCancel.UseVisualStyleBackColor = true; + butCancel.DialogResult = DialogResult.Cancel; + butCancel.Location = new Point(732, 367); + butCancel.Margin = new Padding(5, 6, 5, 6); + butCancel.Name = "butCancel"; + butCancel.Size = new Size(125, 44); + butCancel.TabIndex = 41; + butCancel.Text = "Cancel"; + butCancel.UseVisualStyleBackColor = true; // // butOK // - this.butOK.Location = new System.Drawing.Point(358, 191); - this.butOK.Name = "butOK"; - this.butOK.Size = new System.Drawing.Size(75, 23); - this.butOK.TabIndex = 40; - this.butOK.Text = "OK"; - this.butOK.UseVisualStyleBackColor = true; - this.butOK.Click += new System.EventHandler(this.butOK_Click); + butOK.Location = new Point(597, 367); + butOK.Margin = new Padding(5, 6, 5, 6); + butOK.Name = "butOK"; + butOK.Size = new Size(125, 44); + butOK.TabIndex = 40; + butOK.Text = "OK"; + butOK.UseVisualStyleBackColor = true; + butOK.Click += butOK_Click; // // label1 // - this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(190, 59); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(71, 13); - this.label1.TabIndex = 44; - this.label1.Text = "Working area"; + label1.AutoSize = true; + label1.Location = new Point(317, 113); + label1.Margin = new Padding(5, 0, 5, 0); + label1.Name = "label1"; + label1.Size = new Size(117, 25); + label1.TabIndex = 44; + label1.Text = "Working area"; // // tbWorkingAreaHeight // - this.tbWorkingAreaHeight.Enabled = false; - this.tbWorkingAreaHeight.Location = new System.Drawing.Point(391, 56); - this.tbWorkingAreaHeight.Name = "tbWorkingAreaHeight"; - this.tbWorkingAreaHeight.Size = new System.Drawing.Size(113, 20); - this.tbWorkingAreaHeight.TabIndex = 43; + tbWorkingAreaHeight.Enabled = false; + tbWorkingAreaHeight.Location = new Point(652, 108); + tbWorkingAreaHeight.Margin = new Padding(5, 6, 5, 6); + tbWorkingAreaHeight.Name = "tbWorkingAreaHeight"; + tbWorkingAreaHeight.Size = new Size(186, 31); + tbWorkingAreaHeight.TabIndex = 43; // // tbWorkingAreaWidth // - this.tbWorkingAreaWidth.Enabled = false; - this.tbWorkingAreaWidth.Location = new System.Drawing.Point(272, 56); - this.tbWorkingAreaWidth.Name = "tbWorkingAreaWidth"; - this.tbWorkingAreaWidth.Size = new System.Drawing.Size(113, 20); - this.tbWorkingAreaWidth.TabIndex = 42; + tbWorkingAreaWidth.Enabled = false; + tbWorkingAreaWidth.Location = new Point(453, 108); + tbWorkingAreaWidth.Margin = new Padding(5, 6, 5, 6); + tbWorkingAreaWidth.Name = "tbWorkingAreaWidth"; + tbWorkingAreaWidth.Size = new Size(186, 31); + tbWorkingAreaWidth.TabIndex = 42; // // label2 // - this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(269, 9); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(35, 13); - this.label2.TabIndex = 45; - this.label2.Text = "Width"; + label2.AutoSize = true; + label2.Location = new Point(448, 17); + label2.Margin = new Padding(5, 0, 5, 0); + label2.Name = "label2"; + label2.Size = new Size(60, 25); + label2.TabIndex = 45; + label2.Text = "Width"; // // label3 // - this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(388, 9); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(38, 13); - this.label3.TabIndex = 46; - this.label3.Text = "Height"; + label3.AutoSize = true; + label3.Location = new Point(647, 17); + label3.Margin = new Padding(5, 0, 5, 0); + label3.Name = "label3"; + label3.Size = new Size(65, 25); + label3.TabIndex = 46; + label3.Text = "Height"; // // groupBox1 // - this.groupBox1.Controls.Add(this.label6); - this.groupBox1.Controls.Add(this.label7); - this.groupBox1.Controls.Add(this.tbCustomHeight); - this.groupBox1.Controls.Add(this.tbCustomWidth); - this.groupBox1.Controls.Add(this.label4); - this.groupBox1.Controls.Add(this.label5); - this.groupBox1.Controls.Add(this.tbCustomTop); - this.groupBox1.Controls.Add(this.tbCustomLeft); - this.groupBox1.Location = new System.Drawing.Point(193, 82); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(321, 103); - this.groupBox1.TabIndex = 47; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Custom working area"; + groupBox1.Controls.Add(label6); + groupBox1.Controls.Add(label7); + groupBox1.Controls.Add(tbCustomHeight); + groupBox1.Controls.Add(tbCustomWidth); + groupBox1.Controls.Add(label4); + groupBox1.Controls.Add(label5); + groupBox1.Controls.Add(tbCustomTop); + groupBox1.Controls.Add(tbCustomLeft); + groupBox1.Location = new Point(322, 158); + groupBox1.Margin = new Padding(5, 6, 5, 6); + groupBox1.Name = "groupBox1"; + groupBox1.Padding = new Padding(5, 6, 5, 6); + groupBox1.Size = new Size(535, 198); + groupBox1.TabIndex = 47; + groupBox1.TabStop = false; + groupBox1.Text = "Custom working area"; // // label6 // - this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(76, 55); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(35, 13); - this.label6.TabIndex = 52; - this.label6.Text = "Width"; + label6.AutoSize = true; + label6.Location = new Point(127, 106); + label6.Margin = new Padding(5, 0, 5, 0); + label6.Name = "label6"; + label6.Size = new Size(60, 25); + label6.TabIndex = 52; + label6.Text = "Width"; // // label7 // - this.label7.AutoSize = true; - this.label7.Location = new System.Drawing.Point(195, 55); - this.label7.Name = "label7"; - this.label7.Size = new System.Drawing.Size(38, 13); - this.label7.TabIndex = 53; - this.label7.Text = "Height"; + label7.AutoSize = true; + label7.Location = new Point(325, 106); + label7.Margin = new Padding(5, 0, 5, 0); + label7.Name = "label7"; + label7.Size = new Size(65, 25); + label7.TabIndex = 53; + label7.Text = "Height"; // // tbCustomHeight // - this.tbCustomHeight.Location = new System.Drawing.Point(198, 71); - this.tbCustomHeight.Name = "tbCustomHeight"; - this.tbCustomHeight.Size = new System.Drawing.Size(113, 20); - this.tbCustomHeight.TabIndex = 51; - this.tbCustomHeight.TextChanged += new System.EventHandler(this.tbCustomHeight_TextChanged_1); + tbCustomHeight.Location = new Point(330, 137); + tbCustomHeight.Margin = new Padding(5, 6, 5, 6); + tbCustomHeight.Name = "tbCustomHeight"; + tbCustomHeight.Size = new Size(186, 31); + tbCustomHeight.TabIndex = 51; + tbCustomHeight.TextChanged += tbCustomHeight_TextChanged_1; // // tbCustomWidth // - this.tbCustomWidth.Location = new System.Drawing.Point(79, 71); - this.tbCustomWidth.Name = "tbCustomWidth"; - this.tbCustomWidth.Size = new System.Drawing.Size(113, 20); - this.tbCustomWidth.TabIndex = 50; - this.tbCustomWidth.TextChanged += new System.EventHandler(this.tbCustomWidth_TextChanged_1); + tbCustomWidth.Location = new Point(132, 137); + tbCustomWidth.Margin = new Padding(5, 6, 5, 6); + tbCustomWidth.Name = "tbCustomWidth"; + tbCustomWidth.Size = new Size(186, 31); + tbCustomWidth.TabIndex = 50; + tbCustomWidth.TextChanged += tbCustomWidth_TextChanged_1; // // label4 // - this.label4.AutoSize = true; - this.label4.Location = new System.Drawing.Point(76, 16); - this.label4.Name = "label4"; - this.label4.Size = new System.Drawing.Size(25, 13); - this.label4.TabIndex = 48; - this.label4.Text = "Left"; - this.label4.Visible = false; + label4.AutoSize = true; + label4.Location = new Point(127, 31); + label4.Margin = new Padding(5, 0, 5, 0); + label4.Name = "label4"; + label4.Size = new Size(41, 25); + label4.TabIndex = 48; + label4.Text = "Left"; + label4.Visible = false; // // label5 // - this.label5.AutoSize = true; - this.label5.Location = new System.Drawing.Point(195, 16); - this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(26, 13); - this.label5.TabIndex = 49; - this.label5.Text = "Top"; - this.label5.Visible = false; + label5.AutoSize = true; + label5.Location = new Point(325, 31); + label5.Margin = new Padding(5, 0, 5, 0); + label5.Name = "label5"; + label5.Size = new Size(41, 25); + label5.TabIndex = 49; + label5.Text = "Top"; + label5.Visible = false; // // listView1 // - this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.width, - this.height, - this.primary}); - this.listView1.HideSelection = false; - this.listView1.Location = new System.Drawing.Point(12, 12); - this.listView1.MultiSelect = false; - this.listView1.Name = "listView1"; - this.listView1.Size = new System.Drawing.Size(175, 202); - this.listView1.TabIndex = 48; - this.listView1.UseCompatibleStateImageBehavior = false; - this.listView1.View = System.Windows.Forms.View.Details; - this.listView1.SelectedIndexChanged += new System.EventHandler(this.listView1_SelectedIndexChanged); + listView1.Columns.AddRange(new ColumnHeader[] { width, height, primary }); + listView1.Location = new Point(20, 23); + listView1.Margin = new Padding(5, 6, 5, 6); + listView1.MultiSelect = false; + listView1.Name = "listView1"; + listView1.Size = new Size(289, 385); + listView1.TabIndex = 48; + listView1.UseCompatibleStateImageBehavior = false; + listView1.View = View.Details; + listView1.SelectedIndexChanged += listView1_SelectedIndexChanged; // // width // - this.width.Text = "Width"; - this.width.Width = 50; + width.Text = "Width"; + width.Width = 50; // // height // - this.height.Text = "Height"; - this.height.Width = 50; + height.Text = "Height"; + height.Width = 50; // // primary // - this.primary.Text = "Primary"; - this.primary.Width = 50; + primary.Text = "Primary"; + primary.Width = 50; // // frmScreens // - this.AcceptButton = this.butOK; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.CancelButton = this.butCancel; - this.ClientSize = new System.Drawing.Size(526, 225); - this.Controls.Add(this.listView1); - this.Controls.Add(this.groupBox1); - this.Controls.Add(this.label3); - this.Controls.Add(this.label2); - this.Controls.Add(this.label1); - this.Controls.Add(this.tbWorkingAreaHeight); - this.Controls.Add(this.tbWorkingAreaWidth); - this.Controls.Add(this.butCancel); - this.Controls.Add(this.butOK); - this.Controls.Add(this.tbFullHeight); - this.Controls.Add(this.tbFullWidth); - this.Controls.Add(this.label10); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "frmScreens"; - this.Text = "Screens"; - this.Load += new System.EventHandler(this.Screens_Load); - this.groupBox1.ResumeLayout(false); - this.groupBox1.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); + AcceptButton = butOK; + AutoScaleDimensions = new SizeF(10F, 25F); + AutoScaleMode = AutoScaleMode.Font; + CancelButton = butCancel; + ClientSize = new Size(877, 433); + Controls.Add(listView1); + Controls.Add(groupBox1); + Controls.Add(label3); + Controls.Add(label2); + Controls.Add(label1); + Controls.Add(tbWorkingAreaHeight); + Controls.Add(tbWorkingAreaWidth); + Controls.Add(butCancel); + Controls.Add(butOK); + Controls.Add(tbFullHeight); + Controls.Add(tbFullWidth); + Controls.Add(label10); + FormBorderStyle = FormBorderStyle.Fixed3D; + Icon = (Icon)resources.GetObject("$this.Icon"); + Margin = new Padding(5, 6, 5, 6); + MaximizeBox = false; + MinimizeBox = false; + Name = "frmScreens"; + StartPosition = FormStartPosition.CenterScreen; + Text = "Screens"; + Load += Screens_Load; + groupBox1.ResumeLayout(false); + groupBox1.PerformLayout(); + ResumeLayout(false); + PerformLayout(); } diff --git a/frmScreens.resx b/frmScreens.resx index be49357..fbed691 100644 --- a/frmScreens.resx +++ b/frmScreens.resx @@ -1,17 +1,17 @@  - diff --git a/green-checkmark-icon-16.png b/green-checkmark-icon-16.png new file mode 100644 index 0000000000000000000000000000000000000000..adc3414e103753048240613796128b2074de8812 GIT binary patch literal 448 zcmV;x0YCnUP)T)3Iw5K@`XF&mCK(u&_=c7UDG`C}FHEemsc^w3NGa1BkW@mLpo^p_gdf*cJLU-nBg#A53q`Jm|_$|n*SM);m^2ass?D(>z3D$mr37%*|GR{NhczBAuMQv#One=nEr`W9Oa_tOgv3*UG#%T=cU}542 qm$6&b<=;km#Q*wColXDroqq#&l!ntzr_Ejf0000D(;;t^K@bMuXTKmw#f3BoWC`kA6HHUXt>Q@9KY^<{ zbCx!MJ%c*YguhTx!J+mrmop@|Dnb>CG~`4D5Ek*xSDFh+Lnj%rJMZkw`_62#W;N$+ z9AJR0xIV`m(^Sf*>-cmnaRYDg94pLl7U4dI=;19!sg%pB2O{}BcJLaL4zN~Wj2Bqq z=@q8td|Y$>S##c7r}Phsdx1TM6r4ZeWh&)WfF9r?mGUFbcd?mD`5Na#9N}>jf~_!# z;3mG{L(REYbMB$wbItjFG%|^tgC^9Tb$n_YScD$$!gsK!e22i!Fle-+>eSYh@(cUg zvF(P>u^+L{&alqY8B97UTaHRr>Q&RXI| qEYL|{M?v{7z->R%TBZN`&VK;b=A%5;FEn8Q0000