WindowHelper.cs

using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Extensions.Configuration.UserSecrets;
using System.Collections.Generic;
 
namespace GenXdev.Helpers
{
    public enum DeviceCap
    {
        VERTRES = 10,
        DESKTOPVERTRES = 117,
 
        // http://pinvoke.net/default.aspx/gdi32/GetDeviceCaps.html
    }
 
    public static class DesktopInfo
    {
        [DllImport("gdi32.dll")]
        public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
 
        [PreserveSig()]
        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateDC(
            string lpszDriver,
            string lpszDevice,
            IntPtr lpszOutput,
            IntPtr lpInitData
        );
 
        public static float getScalingFactor(int monitor)
        {
            Graphics g = Graphics.FromHwnd(IntPtr.Zero);
            var AllScreens = (from q in WpfScreenHelper.Screen.AllScreens select q).ToArray();
            IntPtr desktop = CreateDC(
                AllScreens[monitor].DeviceName,
                AllScreens[monitor].DeviceName,
                IntPtr.Zero,
                IntPtr.Zero
            );
            int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES);
            int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES);
 
            float ScreenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight;
 
            var a = new Microsoft.Extensions.Configuration.UserSecrets.PathHelper();
            if (a.ToString() == "need this dep") { System.Threading.Thread.Sleep(1); }
 
            return ScreenScalingFactor; // 1.25 = 125%
        }
    }
 
    public class WindowObj
    {
 
        ///<summary>Windows handle to identify the current windows</summary>
        public IntPtr Handle { get; private set; }
        ///<summary>Name of the windows</summary>
        public string Title { get; private set; }
        ///<summary>Get the children of a window</summary>
        public ICollection<WindowObj> Children { get; private set; }
 
        /// <summary>
        /// Get a custom representation of the window class base on https://docs.microsoft.com/en-us/windows/win32/winmsg/about-window-classes documentation
        /// </summary>
        public string WindowType { get; private set; }
 
        /// <summary>
        /// Get the name of the class that represents the windows
        /// </summary>
        public string WindowClassName { get; private set; }
 
        private const UInt32 WM_CLOSE = 0x0010;
        public static int Amount = 0;
 
        // Constants for window styling
        private const int GWL_STYLE = -16;
        private const int GWL_EXSTYLE = -20;
        private const uint WS_CAPTION = 0x00C00000;
        private const uint WS_THICKFRAME = 0x00040000;
        private const uint WS_MINIMIZEBOX = 0x00020000;
        private const uint WS_MAXIMIZEBOX = 0x00010000;
        private const uint WS_SYSMENU = 0x00080000;
        private const uint WS_EX_TOPMOST = 0x00000008;
        private const uint WS_EX_LAYERED = 0x00080000;
        private const uint LWA_ALPHA = 0x00000002;
 
        // For window transparency
        [DllImport("user32.dll")]
        private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
 
        [DllImport("user32.dll")]
        private static extern int SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
 
        [DllImport("user32.dll")]
        private static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
 
        /// <summary>Creates a window object with a handle and a window title</summary>
        /// <param name="handle"></param>
        /// <param name="title"></param>
        public WindowObj(IntPtr handle, string title)
        {
            Handle = handle;
            Title = title;
 
            StringBuilder stringBuilder = new StringBuilder(256);
            GetClassName(handle, stringBuilder, stringBuilder.Capacity);
            WindowType = GetWindowClass(stringBuilder.ToString());
            WindowClassName = stringBuilder.ToString();
 
            Children = new List<WindowObj>();
            ArrayList handles = new ArrayList();
            EnumedWindow childProc = GetWindowHandle;
 
            EnumChildWindows(handle, childProc, handles);
            foreach (IntPtr item in handles)
            {
                int capacityChild = GetWindowTextLength(handle) * 2;
 
                StringBuilder stringBuilderChild = new StringBuilder(capacityChild);
                GetWindowText(handle, stringBuilder, stringBuilderChild.Capacity);
 
                StringBuilder stringBuilderChild2 = new StringBuilder(256);
                GetClassName(handle, stringBuilderChild2, stringBuilderChild2.Capacity);
 
                WindowObj win = new WindowObj(item, stringBuilder.ToString());
                win.WindowClassName = stringBuilderChild2.ToString();
                win.WindowType = GetWindowClass(stringBuilderChild2.ToString());
                Children.Add(win);
            }
        }
 
        [DllImport("user32.dll")]
        public static extern void mouse_event(Int32 dwFlags, Int32 dx, Int32 dy, Int32 dwData, UIntPtr dwExtraInfo);
 
        private const int MOUSEEVENTF_MOVE = 0x0001;
        private const int WM_SYSCOMMAND = 0x0112;
        private const int SC_MONITORPOWER = 0xF170;
        private const int MonitorTurnOn = -1;
        private const int MonitorShutoff = 2;
 
 
        public static void WakeMonitor()
        {
            //Turn them on
            PostMessage((IntPtr)0xffff, WM_SYSCOMMAND, SC_MONITORPOWER, MonitorTurnOn);
            System.Threading.Thread.Sleep(150);
            mouse_event(MOUSEEVENTF_MOVE, 0, 1, 0, UIntPtr.Zero);
            System.Threading.Thread.Sleep(40);
            mouse_event(MOUSEEVENTF_MOVE, 0, -1, 0, UIntPtr.Zero);
        }
        public static void SleepMonitor()
        {
 
            //Turn them off
            PostMessage((IntPtr)0xffff, WM_SYSCOMMAND, SC_MONITORPOWER, MonitorShutoff);
        }
        [DllImport("user32.dll")]
        public static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
 
        [DllImport("kernel32.dll")]
        public static extern IntPtr GetConsoleWindow();
 
 
        public static bool GetWindowHandle(IntPtr windowHandle, ArrayList windowHandles)
        {
            windowHandles.Add(windowHandle);
            return true;
        }
 
        ///<summary>A class to have better manipulation of windows sizes</summary>
        public struct RectStruct
        {
            public int Left { get; set; }
            public int Top { get; set; }
            public int Right { get; set; }
            public int Bottom { get; set; }
            public int Width { get; set; }
            public int Height { get; set; }
 
        }
 
        /// <summary>Open a new Process with the given path and return a window object if the process have a user interface, else return null</summary>
        /// <param name="filePath">Path of the file to look for</param>
        /// <param name="timeToWait">Time to wait until the proccess execute, *Only apply for process with a window interface*</param>
        public static WindowObj Open(string filePath, int timeToWait = -1)
        {
            if (!File.Exists(filePath))
                throw new Exception(string.Format("The filePath {0} is not valid", filePath));
 
            Process newProcess = Process.Start(filePath);
 
            if (timeToWait == -1)
                newProcess.WaitForInputIdle();
            else
                newProcess.WaitForInputIdle(timeToWait * 1000);
 
            if (newProcess != null && newProcess.MainWindowHandle != IntPtr.Zero)
                return new WindowObj(newProcess.MainWindowHandle, newProcess.MainWindowTitle);
 
            return null;
        }
 
        /// <summary>Look for the existence of a process with the given name an return the first occurrence of the process as a Window object</summary>
        /// <param name="name">Name of the process</param>
        /// <param name="attempts">Amount of tries that it will look for the window</param>
        /// <param name="waitInterval">Amount ot time it will stop the thread while waiting for the windows in each attemp</param>
        public static WindowObj GetWindow(string name, int attempts = 1, int waitInterval = 1000)
        {
            IEnumerable<Process> currentProcesses = Process.GetProcesses();
            int counter = 0;
            while (counter < attempts)
            {
                foreach (Process p in currentProcesses)
                    if (p.MainWindowHandle != IntPtr.Zero && p.MainWindowTitle == name)
                        return new WindowObj(p.MainWindowHandle, p.MainWindowTitle);
 
                System.Threading.Thread.Sleep(waitInterval);
                currentProcesses = Process.GetProcesses();
                counter++;
            }
            return null;
        }
 
        public static WindowObj GetFocusedWindow()
        {
            var sb = new StringBuilder();
            var handle = GetForegroundWindow();
 
            return new WindowObj(handle, GetWindowTitle(handle));
        }
 
        /// <summary>Look for the existence of processes with the given name an return all occurrences of the process as Windows objects, *case sensitive*</summary>
        /// <param name="name">Name of the process</param>
        /// <param name="attempts">Amount of tries that it will look for the window</param>
        /// <param name="waitInterval">Amount ot time it will stop the thread while waiting for the windows in each attemp</param>
        public static IEnumerable<WindowObj> GetWindows(string name, int attempts = 1, int waitInterval = 1000)
        {
            IEnumerable<Process> currentProcesses = Process.GetProcesses();
            ICollection<WindowObj> windows = new List<WindowObj>();
            int counter = 0;
            while (counter < attempts)
            {
                foreach (Process p in currentProcesses)
                    if (p.MainWindowHandle != IntPtr.Zero && p.ProcessName == name)
                        windows.Add(new WindowObj(p.MainWindowHandle, p.MainWindowTitle));
 
                if (windows.Count > 0)
                    break;
 
                System.Threading.Thread.Sleep(waitInterval);
                currentProcesses = Process.GetProcesses();
                counter++;
            }
            return windows;
        }
 
        public static IEnumerable<WindowObj> GetMainWindow(Process p, int attempts = 1, int waitInterval = 1000)
        {
            IEnumerable<Process> currentProcesses = Process.GetProcesses();
            ICollection<WindowObj> windows = new List<WindowObj>();
            int counter = 0;
            while (counter < attempts)
            {
                if (p.MainWindowHandle != IntPtr.Zero)
                {
                    try
                    {
                        windows.Add(new WindowObj(p.MainWindowHandle, p.MainWindowTitle));
                    }
                    catch
                    {
                        break;
                    }
                }
 
                if (windows.Count > 0)
                    break;
 
                System.Threading.Thread.Sleep(waitInterval);
                currentProcesses = Process.GetProcesses();
                counter++;
            }
 
            return windows;
        }
 
        public static IEnumerable<WindowObj> GetMainWindow(IntPtr windowHandle, int attempts = 1, int waitInterval = 1000)
        {
            IEnumerable<Process> currentProcesses = Process.GetProcesses();
            ICollection<WindowObj> windows = new List<WindowObj>();
            if (windowHandle != IntPtr.Zero)
            {
                try
                {
                    windows.Add(new WindowObj(windowHandle, ""));
                }
                catch { }
            }
 
            return windows;
        }
        /// <summary>Look for the existense of a process in all processes an return the first ocurrence of a process that contains the given name as a Window object</summary>
        /// <param name="name">Name of the process</param>
        /// <param name="attempts">Amount of tries that it will look for the window</param>
        /// <param name="waitInterval">Amount ot time it will stop the thread while waiting for the windows in each attemp</param>
        public static WindowObj GetWindowWithPartialName(string name, int attempts = 1, int waitInterval = 1000)
        {
            IEnumerable<Process> currentProcesses = Process.GetProcesses();
            int counter = 0;
            while (counter < attempts)
            {
                foreach (Process p in currentProcesses)
                    if (p.MainWindowHandle != IntPtr.Zero && p.MainWindowTitle.ToLower().Contains(name.ToLower()))
                        return new WindowObj(p.MainWindowHandle, p.MainWindowTitle);
 
                System.Threading.Thread.Sleep(waitInterval);
                currentProcesses = Process.GetProcesses();
                counter++;
            }
            return null;
        }
 
        /// <summary>Look for the existense of a process in all processes an return the processes that contains the given name as Windows objects</summary>
        /// <param name="name">Name of the process</param>
        /// <param name="attempts">Amount of tries that it will look for at least one window</param>
        /// <param name="waitInterval">Amount ot time it will stop the thread while waiting for the windows in each attemp</param>
        public static IEnumerable<WindowObj> GetWindowsWithPartialName(string name, int attempts = 1, int waitInterval = 1000)
        {
            IEnumerable<Process> currentProcesses = Process.GetProcesses();
            ICollection<WindowObj> windows = new List<WindowObj>();
            int counter = 0;
            while (counter < attempts)
            {
                foreach (Process p in currentProcesses)
                    if (p.MainWindowHandle != IntPtr.Zero && p.MainWindowTitle.ToLower().Contains(name.ToLower()))
                        windows.Add(new WindowObj(p.MainWindowHandle, p.MainWindowTitle));
 
                if (windows.Count > 0)
                    break;
 
                System.Threading.Thread.Sleep(waitInterval);
                currentProcesses = Process.GetProcesses();
                counter++;
            }
            return windows;
        }
 
        /// <summary>
        /// Get the active windows if possible.
        /// </summary>
        /// <returns></returns>
        public static WindowObj GetActive()
        {
            IntPtr handle = GetActiveWindow();
            if (handle != IntPtr.Zero)
            {
                foreach (Process p in Process.GetProcesses())
                    if (p.MainWindowHandle == handle)
                        return new WindowObj(p.MainWindowHandle, p.MainWindowTitle);
            }
            return null;
        }
 
        /// <summary>Focus the current window</summary>
        public void Focus()
        {
            AllowSetForegroundWindow(-1);
            SetForegroundWindow(this.Handle);
        }
 
        public void SetForeground()
        {
            ShowWindowAsync(this.Handle, (int)ShowWindowCommands.Show);
            SetForegroundWindow(this.Handle);
        }
 
        /// <summary>Maximize the current window</summary>
        public bool Maximize()
        {
            return ShowWindowAsync(this.Handle, (int)ShowWindowCommands.Maximize);
        }
 
        /// <summary>Minimize the current window</summary>
        public bool Minimize()
        {
            return ShowWindowAsync(this.Handle, (int)ShowWindowCommands.Minimize);
        }
 
        /// <summary>Return the current windows at its first state when the windows was created</summary>
        public bool Restore()
        {
            return ShowWindowAsync(this.Handle, (int)ShowWindowCommands.Restore);
        }
 
        /// <summary>Return the current windows at its default state</summary>
        public bool DefaultState()
        {
            return ShowWindowAsync(this.Handle, (int)ShowWindowCommands.ShowDefault);
        }
 
        public static string GetWindowTitle(IntPtr hWnd)
        {
            var length = GetWindowTextLength(hWnd) + 1;
            var title = new StringBuilder(length);
            GetWindowText(hWnd, title, length);
            return title.ToString();
        }
 
        /// <summary>Hide the current window
        /// *If the application close with a hide process, this will not be close unless close method
        /// calls or manually kill the process*</summary>
        public bool Hide()
        {
            return ShowWindowAsync(this.Handle, (int)ShowWindowCommands.Hide);
        }
 
        /// <summary>Shows the current windows if it was hide</summary>
        public bool Show()
        {
            return ShowWindowAsync(this.Handle, (int)ShowWindowCommands.Show);
        }
 
        /// <summary>Close the current windows</summary>
        public bool Close()
        {
            return SendMessage(this.Handle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero) == IntPtr.Zero;
        }
        public bool SendMessage(UInt32 Msg, IntPtr wParam, IntPtr lParam)
        {
            return SendMessage(this.Handle, Msg, wParam, lParam) == IntPtr.Zero;
        }
 
        /// <summary>Resize the current window with the given params</summary>
        /// <param name="width">New width of the current windows</param>
        /// <param name="height">New height of the current windows</param>
        public bool Resize(int width, int height)
        {
            return MoveWindow(this.Handle, 0, 0, width, height, true);
        }
 
        /// <summary>Resize the current window with the given params</summary>
        /// <param name="pixels">this will use as new width and new height of the windows</param>
        public bool Resize(int pixels)
        {
            return MoveWindow(this.Handle, 0, 0, pixels, pixels, true);
        }
 
        /// <summary>Move the current window with the given params</summary>
        /// <param name="X">New X coordinate of the current windows</param>
        /// <param name="Y">New Y coordinate of the current windows</param>
        public bool Move(int X, int Y)
        {
            RectStruct rect = new RectStruct();
            GetWindowRect(this.Handle, ref rect);
 
            rect.Width = rect.Right - rect.Left + Amount;
            rect.Height = rect.Bottom - rect.Top + Amount;
            return MoveWindow(this.Handle, X, Y, rect.Width, rect.Height, true);
        }
        public bool Move(int X, int Y, int W, int H)
        {
            return MoveWindow(this.Handle, X, Y, W, H, true);
        }
 
        /// <summary>Return the position of the windows as X, Y coordinates</summary>
        public Point Position()
        {
            RectStruct rect = new RectStruct();
            GetWindowRect(this.Handle, ref rect);
 
            return new Point(rect.Left, rect.Top);
        }
 
        public int Left
        {
 
            get
            {
                return Position().X;
            }
 
            set
            {
                Move(value, Position().Y);
            }
        }
        public int Top
        {
 
            get
            {
                return Position().Y;
            }
 
            set
            {
                Move(Position().X, value);
            }
        }
 
        public int Width
        {
 
            get
            {
                return Size().Width;
            }
 
            set
            {
                Resize(value, Size().Height);
            }
        }
        public int Height
        {
 
            get
            {
                return Size().Height;
            }
 
            set
            {
                Resize(Size().Width, value);
            }
        }
 
        /// <summary>Return the Size of the windows as width, height coordinates</summary>
        public Size Size()
        {
            RectStruct rect = new RectStruct();
            GetWindowRect(this.Handle, ref rect);
 
            rect.Width = rect.Right - rect.Left + Amount;
            rect.Height = rect.Bottom - rect.Top + Amount;
            return new Size(rect.Width, rect.Height);
        }
 
        /// <summary>Return the position and size of the windows as X, Y, with, height coordinates</summary>
        /*
        public Rect Area()
        {
            RectStruct rect = new RectStruct();
            GetWindowRect(this.Handle, ref rect);
 
            rect.Width = rect.Right - rect.Left + Amount;
            rect.Height = rect.Bottom - rect.Top + Amount;
            return new System.Windows.(rect.Left, rect.Top, rect.Width, rect.Height);
        }
        */
 
        /// <summary>Check if the current windows is visible</summary>
        public bool IsVisible()
        {
            return IsWindowVisible(this.Handle);
        }
        [DllImport("user32.dll")]
        public static extern bool AllowSetForegroundWindow(int dwProcessId);
 
        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
 
        [DllImport("user32.dll")]
        public static extern IntPtr GetActiveWindow();
 
        [DllImport("user32.dll")]
        public static extern bool IsWindowVisible(IntPtr hWnd);
 
        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();
 
        [DllImport("user32.dll")]
        public static extern bool SetForegroundWindow(IntPtr hWnd);
 
        [DllImport("user32.dll")]
        public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
 
        [DllImport("user32.dll")]
        public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
 
        [DllImport("user32.dll")]
        public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
 
        [DllImport("user32.dll")]
        public static extern bool GetWindowRect(IntPtr hWnd, ref RectStruct rectangle);
 
        [DllImport("kernel32.dll")]
        public static extern int GetProcessId(IntPtr handle);
 
        [DllImport("user32.dll")]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
 
        [DllImport("user32.dll", SetLastError = true)]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
 
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
 
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr SetActiveWindow(IntPtr hWnd);
 
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr SetFocus(IntPtr hWnd);
 
        [DllImport("user32.dll", SetLastError = true)]
        public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
 
        [DllImport("user32.dll")]
        public static extern IntPtr GetFocus();
 
        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
 
        public delegate bool EnumedWindow(IntPtr handleWindow, ArrayList handles);
 
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool EnumChildWindows(IntPtr window, EnumedWindow callback, ArrayList lParam);
 
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int GetWindowTextLength(IntPtr hWnd);
 
        private enum ShowWindowCommands
        {
            /// <summary>
            /// Hides the window and activates another window.
            /// </summary>
            Hide = 0,
            /// <summary>
            /// Activates and displays a window. If the window is minimized or
            /// maximized, the system restores it to its original size and position.
            /// An application should specify this flag when displaying the window
            /// for the first time.
            /// </summary>
            Normal = 1,
            /// <summary>
            /// Activates the window and displays it as a minimized window.
            /// </summary>
            ShowMinimized = 2,
            /// <summary>
            /// Maximizes the specified window.
            /// </summary>
            Maximize = 3, // is this the right value?
            /// <summary>
            /// Activates the window and displays it as a maximized window.
            /// </summary>
            ShowMaximized = 3,
            /// <summary>
            /// Displays a window in its most recent size and position. This value
            /// is similar to <see cref="Win32.ShowWindowCommand.Normal"/>, except
            /// the window is not activated.
            /// </summary>
            ShowNoActivate = 4,
            /// <summary>
            /// Activates the window and displays it in its current size and position.
            /// </summary>
            Show = 5,
            /// <summary>
            /// Minimizes the specified window and activates the next top-level
            /// window in the Z order.
            /// </summary>
            Minimize = 6,
            /// <summary>
            /// Displays the window as a minimized window. This value is similar to
            /// <see cref="Win32.ShowWindowCommand.ShowMinimized"/>, except the
            /// window is not activated.
            /// </summary>
            ShowMinNoActive = 7,
            /// <summary>
            /// Displays the window in its current size and position. This value is
            /// similar to <see cref="Win32.ShowWindowCommand.Show"/>, except the
            /// window is not activated.
            /// </summary>
            ShowNA = 8,
            /// <summary>
            /// Activates and displays the window. If the window is minimized or
            /// maximized, the system restores it to its original size and position.
            /// An application should specify this flag when restoring a minimized window.
            /// </summary>
            Restore = 9,
            /// <summary>
            /// Sets the show state based on the SW_* value specified in the
            /// STARTUPINFO structure passed to the CreateProcess function by the
            /// program that started the application.
            /// </summary>
            ShowDefault = 10,
            /// <summary>
            /// <b>Windows 2000/XP:</b> Minimizes a window, even if the thread
            /// that owns the window is not responding. This flag should only be
            /// used when minimizing windows from a different thread.
            /// </summary>
            ForceMinimize = 11
        }
        private string GetWindowClass(string windowClass)
        {
            List<string> values = new List<string>(){
                "ComboLBox","DDEMLEvent","Message","#32768",
                "#32769","#32770","#32771","#32772","Button","Edit","ListBox","MDIClient",
                "ScrollBar","Static",""
            };
 
            if (windowClass == "#32768")
                return "Menu";
            else if (windowClass == "#32769")
                return "DektopWindow";
            else if (windowClass == "#32770")
                return "DialogBox";
            else if (windowClass == "#32771")
                return "TaskSwitchWindowClass";
            else if (windowClass == "#32772")
                return "IconTitlesClass";
            return values.SingleOrDefault(s => s == windowClass);
        }
 
        /// <summary>
        /// Sets or removes the "Always On Top" state of the window
        /// </summary>
        /// <param name="alwaysOnTop">True to set the window always on top, false otherwise</param>
        /// <returns>True if successful</returns>
        public bool SetAlwaysOnTop(bool alwaysOnTop)
        {
            uint exStyle = GetWindowLong(Handle, GWL_EXSTYLE);
 
            if (alwaysOnTop)
                exStyle |= WS_EX_TOPMOST;
            else
                exStyle &= ~WS_EX_TOPMOST;
 
            return SetWindowLong(Handle, GWL_EXSTYLE, exStyle) != 0;
        }
 
        /// <summary>
        /// Sets the opacity/transparency level of the window
        /// </summary>
        /// <param name="opacity">Opacity level from 0 (transparent) to 255 (opaque)</param>
        /// <returns>True if successful</returns>
        public bool SetOpacity(byte opacity)
        {
            uint exStyle = GetWindowLong(Handle, GWL_EXSTYLE);
            exStyle |= WS_EX_LAYERED;
            SetWindowLong(Handle, GWL_EXSTYLE, exStyle);
            return SetLayeredWindowAttributes(Handle, 0, opacity, LWA_ALPHA);
        }
 
        // Struct to save window position and state
        [Serializable]
        public class WindowState
        {
            public int X { get; set; }
            public int Y { get; set; }
            public int Width { get; set; }
            public int Height { get; set; }
            public bool IsMaximized { get; set; }
            public bool IsMinimized { get; set; }
            public bool IsAlwaysOnTop { get; set; }
            public byte Opacity { get; set; }
            public string Title { get; set; }
        }
 
        /// <summary>
        /// Captures the current state of the window
        /// </summary>
        /// <returns>A WindowState object containing the window's current state</returns>
        public WindowState CaptureState()
        {
            RectStruct rect = new RectStruct();
            GetWindowRect(Handle, ref rect);
 
            return new WindowState
            {
                X = rect.Left,
                Y = rect.Top,
                Width = rect.Right - rect.Left,
                Height = rect.Bottom - rect.Top,
                IsMaximized = IsMaximized(),
                IsMinimized = IsMinimized(),
                IsAlwaysOnTop = IsAlwaysOnTop(),
                Opacity = GetOpacity(),
                Title = Title
            };
        }
 
        /// <summary>
        /// Restores a previously captured window state
        /// </summary>
        /// <param name="state">The WindowState to restore</param>
        public void RestoreState(WindowState state)
        {
            if (state.IsMaximized)
                Maximize();
            else if (state.IsMinimized)
                Minimize();
            else
                Move(state.X, state.Y, state.Width, state.Height);
 
            SetAlwaysOnTop(state.IsAlwaysOnTop);
            SetOpacity(state.Opacity);
        }
 
        /// <summary>
        /// Checks if the window is currently maximized
        /// </summary>
        public bool IsMaximized()
        {
            return IsZoomed(Handle);
        }
 
        /// <summary>
        /// Checks if the window is currently minimized
        /// </summary>
        public bool IsMinimized()
        {
            return IsIconic(Handle);
        }
 
        /// <summary>
        /// Checks if the window is set to always be on top
        /// </summary>
        public bool IsAlwaysOnTop()
        {
            uint exStyle = GetWindowLong(Handle, GWL_EXSTYLE);
            return (exStyle & WS_EX_TOPMOST) != 0;
        }
 
        /// <summary>
        /// Gets the current opacity of the window
        /// </summary>
        public byte GetOpacity()
        {
            uint exStyle = GetWindowLong(Handle, GWL_EXSTYLE);
            if ((exStyle & WS_EX_LAYERED) == 0)
                return 255;
 
            byte opacity = 255;
            uint flags = 0;
            uint key = 0;
            GetLayeredWindowAttributes(Handle, ref key, ref opacity, ref flags);
            return opacity;
        }
 
        /// <summary>
        /// Fades the window in or out with animation
        /// </summary>
        /// <param name="fadeIn">True to fade in, false to fade out</param>
        /// <param name="duration">Duration of the animation in milliseconds</param>
        public void FadeWindow(bool fadeIn, int duration = 200)
        {
            byte startOpacity = fadeIn ? (byte)0 : (byte)255;
            byte endOpacity = fadeIn ? (byte)255 : (byte)0;
            int steps = 10;
            int delay = duration / steps;
 
            SetOpacity(startOpacity);
            if (fadeIn) Show();
 
            for (int i = 1; i <= steps; i++)
            {
                byte currentOpacity = (byte)(startOpacity + ((endOpacity - startOpacity) * i / steps));
                SetOpacity(currentOpacity);
                System.Threading.Thread.Sleep(delay);
            }
 
            if (!fadeIn) Hide();
        }
 
        /// <summary>
        /// Snaps the window to the nearest screen edge
        /// </summary>
        public void SnapToEdge()
        {
            var position = Position();
            var size = Size();
            var screen = System.Windows.Forms.Screen.FromPoint(new System.Drawing.Point(position.X, position.Y));
 
            int snapDistance = 20; // pixels
            var workArea = screen.WorkingArea;
 
            int x = position.X;
            int y = position.Y;
 
            // Snap to left edge
            if (Math.Abs(position.X - workArea.Left) < snapDistance)
                x = workArea.Left;
 
            // Snap to right edge
            if (Math.Abs((position.X + size.Width) - workArea.Right) < snapDistance)
                x = workArea.Right - size.Width;
 
            // Snap to top edge
            if (Math.Abs(position.Y - workArea.Top) < snapDistance)
                y = workArea.Top;
 
            // Snap to bottom edge
            if (Math.Abs((position.Y + size.Height) - workArea.Bottom) < snapDistance)
                y = workArea.Bottom - size.Height;
 
            Move(x, y);
        }
 
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool IsZoomed(IntPtr hWnd);
 
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool IsIconic(IntPtr hWnd);
 
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetLayeredWindowAttributes(IntPtr hwnd, ref uint crKey, ref byte bAlpha, ref uint dwFlags);
 
        public void RemoveBorder()
        {
            uint style = GetWindowLong(this.Handle, GWL_STYLE);
            style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU);
            SetWindowLong(this.Handle, GWL_STYLE, style);
        }
 
        /// <summary>
        /// Positions the window on the left half of the screen
        /// </summary>
        public bool PositionLeft()
        {
            try
            {
                // First restore the window if it's maximized or minimized
                if (IsMaximized() || IsMinimized())
                {
                    Restore();
                    System.Threading.Thread.Sleep(50); // Give OS time to complete operation
                }
 
                var screen = System.Windows.Forms.Screen.FromHandle(Handle);
                int width = screen.WorkingArea.Width / 2;
                int height = screen.WorkingArea.Height;
 
                // Special handling for known problematic window classes or Electron apps
                bool isElectronApp = IsElectronApp();
                if (isElectronApp)
                {
                    // For Electron apps like VS Code, do an extra step to ensure it works
                    // First set a different size to force a refresh
                    MoveWindow(Handle, screen.WorkingArea.X, screen.WorkingArea.Y, width - 1, height, true);
                    System.Threading.Thread.Sleep(10);
                }
 
                return MoveWindow(Handle, screen.WorkingArea.X, screen.WorkingArea.Y, width, height, true);
            }
            catch
            {
                // Fallback to basic implementation if anything goes wrong
                try
                {
                    var screen = System.Windows.Forms.Screen.FromHandle(Handle);
                    int width = screen.WorkingArea.Width / 2;
                    int height = screen.WorkingArea.Height;
                    return MoveWindow(Handle, screen.WorkingArea.X, screen.WorkingArea.Y, width, height, true);
                }
                catch
                {
                    return false;
                }
            }
        }
 
        /// <summary>
        /// Positions the window on the right half of the screen
        /// </summary>
        public bool PositionRight()
        {
            try
            {
                // First restore the window if it's maximized or minimized
                if (IsMaximized() || IsMinimized())
                {
                    Restore();
                    System.Threading.Thread.Sleep(50); // Give OS time to complete operation
                }
 
                var screen = System.Windows.Forms.Screen.FromHandle(Handle);
                int width = screen.WorkingArea.Width / 2;
                int height = screen.WorkingArea.Height;
                int x = screen.WorkingArea.X + screen.WorkingArea.Width - width;
 
                // Special handling for known problematic window classes or Electron apps
                bool isElectronApp = IsElectronApp();
                if (isElectronApp)
                {
                    // For Electron apps like VS Code, do an extra step to ensure it works
                    // First set a different size to force a refresh
                    MoveWindow(Handle, x, screen.WorkingArea.Y, width - 1, height, true);
                    System.Threading.Thread.Sleep(10);
                }
 
                return MoveWindow(Handle, x, screen.WorkingArea.Y, width, height, true);
            }
            catch
            {
                // Fallback to basic implementation if anything goes wrong
                try
                {
                    var screen = System.Windows.Forms.Screen.FromHandle(Handle);
                    int width = screen.WorkingArea.Width / 2;
                    int height = screen.WorkingArea.Height;
                    int x = screen.WorkingArea.X + screen.WorkingArea.Width - width;
                    return MoveWindow(Handle, x, screen.WorkingArea.Y, width, height, true);
                }
                catch
                {
                    return false;
                }
            }
        }
 
        /// <summary>
        /// Positions the window on the top half of the screen
        /// </summary>
        public bool PositionTop()
        {
            var screen = System.Windows.Forms.Screen.FromHandle(Handle);
            int width = screen.WorkingArea.Width;
            int height = screen.WorkingArea.Height / 2;
            return MoveWindow(Handle, screen.WorkingArea.X, screen.WorkingArea.Y, width, height, true);
        }
 
        /// <summary>
        /// Positions the window on the bottom half of the screen
        /// </summary>
        public bool PositionBottom()
        {
            var screen = System.Windows.Forms.Screen.FromHandle(Handle);
            int width = screen.WorkingArea.Width;
            int height = screen.WorkingArea.Height / 2;
            int y = screen.WorkingArea.Y + screen.WorkingArea.Height - height;
            return MoveWindow(Handle, screen.WorkingArea.X, y, width, height, true);
        }
 
        /// <summary>
        /// Positions the window in the top-left corner of the screen (quarter screen)
        /// </summary>
        public bool PositionTopLeft()
        {
            var screen = System.Windows.Forms.Screen.FromHandle(Handle);
            int width = screen.WorkingArea.Width / 2;
            int height = screen.WorkingArea.Height / 2;
            return MoveWindow(Handle, screen.WorkingArea.X, screen.WorkingArea.Y, width, height, true);
        }
 
        /// <summary>
        /// Positions the window in the top-right corner of the screen (quarter screen)
        /// </summary>
        public bool PositionTopRight()
        {
            var screen = System.Windows.Forms.Screen.FromHandle(Handle);
            int width = screen.WorkingArea.Width / 2;
            int height = screen.WorkingArea.Height / 2;
            int x = screen.WorkingArea.X + screen.WorkingArea.Width - width;
            return MoveWindow(Handle, x, screen.WorkingArea.Y, width, height, true);
        }
 
        /// <summary>
        /// Positions the window in the bottom-left corner of the screen (quarter screen)
        /// </summary>
        public bool PositionBottomLeft()
        {
            var screen = System.Windows.Forms.Screen.FromHandle(Handle);
            int width = screen.WorkingArea.Width / 2;
            int height = screen.WorkingArea.Height / 2;
            int y = screen.WorkingArea.Y + screen.WorkingArea.Height - height;
            return MoveWindow(Handle, screen.WorkingArea.X, y, width, height, true);
        }
 
        /// <summary>
        /// Positions the window in the bottom-right corner of the screen (quarter screen)
        /// </summary>
        public bool PositionBottomRight()
        {
            var screen = System.Windows.Forms.Screen.FromHandle(Handle);
            int width = screen.WorkingArea.Width / 2;
            int height = screen.WorkingArea.Height / 2;
            int x = screen.WorkingArea.X + screen.WorkingArea.Width - width;
            int y = screen.WorkingArea.Y + screen.WorkingArea.Height - height;
            return MoveWindow(Handle, x, y, width, height, true);
        }
 
        /// <summary>
        /// Positions the window in the center of the screen
        /// </summary>
        public bool PositionCentered()
        {
            var screen = System.Windows.Forms.Screen.FromHandle(Handle);
            int width = (int)(screen.WorkingArea.Width * 0.8);
            int height = (int)(screen.WorkingArea.Height * 0.8);
            int x = screen.WorkingArea.X + (screen.WorkingArea.Width - width) / 2;
            int y = screen.WorkingArea.Y + (screen.WorkingArea.Height - height) / 2;
            return MoveWindow(Handle, x, y, width, height, true);
        }
 
        /// <summary>
        /// Positions the window to fill the entire screen (without maximizing)
        /// </summary>
        public bool PositionFullscreen()
        {
            var screen = System.Windows.Forms.Screen.FromHandle(Handle);
            return MoveWindow(Handle, screen.WorkingArea.X, screen.WorkingArea.Y,
                             screen.WorkingArea.Width, screen.WorkingArea.Height, true);
        }
 
        /// <summary>
        /// Moves the window to a specified monitor
        /// </summary>
        /// <param name="monitorIndex">Monitor index: 0=primary, 1+=specific monitor (1-based index), -2=secondary monitor</param>
        /// <param name="preserveState">If true, preserves maximized/minimized state after moving</param>
        /// <returns>True if successful</returns>
        public bool MoveToMonitor(int monitorIndex, bool preserveState = true)
        {
            // Save current window state
            bool wasMaximized = IsMaximized();
            bool wasMinimized = IsMinimized();
 
            // Need to restore window before moving if it's maximized/minimized
            if (wasMaximized || wasMinimized)
            {
                Restore();
                System.Threading.Thread.Sleep(100); // Give OS time to complete the operation
            }
 
            // Get current size
            var size = Size();
 
            // Get all available screens
            var allScreens = WpfScreenHelper.Screen.AllScreens.ToList();
            WpfScreenHelper.Screen targetScreen = null;
 
            // Determine target monitor
            if (monitorIndex == 0)
            {
                // Primary monitor
                targetScreen = WpfScreenHelper.Screen.PrimaryScreen;
            }
            else if (monitorIndex == -2)
            {
                // Try to get from global variable in PowerShell
                // Default to secondary monitor if available, or current monitor
                try
                {
                    // We can't access PowerShell variables directly, so we'll rely on the caller
                    // to translate the -2 value appropriately
                    if (allScreens.Count > 1)
                    {
                        // Use second monitor as default secondary
                        targetScreen = allScreens[1];
                    }
                    else
                    {
                        // Fall back to primary if only one monitor
                        targetScreen = WpfScreenHelper.Screen.PrimaryScreen;
                    }
                }
                catch
                {
                    // Default to primary if anything goes wrong
                    targetScreen = WpfScreenHelper.Screen.PrimaryScreen;
                }
            }
            else if (monitorIndex > 0 && monitorIndex <= allScreens.Count)
            {
                // Specific monitor (1-based index)
                targetScreen = allScreens[monitorIndex - 1];
            }
            else
            {
                // Invalid monitor index, use current
                targetScreen = WpfScreenHelper.Screen.FromHandle(Handle);
            }
 
            // Center window on target monitor's working area
            int newX = (int)targetScreen.WorkingArea.X + (int)((targetScreen.WorkingArea.Width - size.Width) / 2.0);
            int newY = (int)targetScreen.WorkingArea.Y + (int)((targetScreen.WorkingArea.Height - size.Height) / 2.0);
 
            // Move window to new position
            bool result = Move(newX, newY);
 
            // Restore previous state if requested
            if (preserveState)
            {
                if (wasMaximized)
                {
                    Maximize();
                }
                else if (wasMinimized)
                {
                    Minimize();
                }
            }
 
            return result;
        }
 
        /// <summary>
        /// Gets the index of the monitor that the window is currently on
        /// </summary>
        /// <returns>
        /// Monitor index where the window is located:
        /// 0 = primary monitor
        /// 1+ = specific monitor (1-based index)
        /// -1 = unable to determine
        /// </returns>
        public int GetCurrentMonitor()
        {
            try
            {
                // Get window position (center point of the window)
                var windowRect = new RectStruct();
                GetWindowRect(Handle, ref windowRect);
                int centerX = windowRect.Left + ((windowRect.Right - windowRect.Left) / 2);
                int centerY = windowRect.Top + ((windowRect.Bottom - windowRect.Top) / 2);
 
                // Get all screens
                var allScreens = WpfScreenHelper.Screen.AllScreens.ToList();
 
                // Find which screen contains this point
                for (int i = 0; i < allScreens.Count; i++)
                {
                    var screen = allScreens[i];
                    if (centerX >= screen.Bounds.Left && centerX <= screen.Bounds.Right &&
                        centerY >= screen.Bounds.Top && centerY <= screen.Bounds.Bottom)
                    {
                        // If this is the primary monitor, return 0
                        if (screen.Primary)
                            return 0;
 
                        // Otherwise return 1-based index
                        // Find 1-based index of this monitor
                        int nonPrimaryIndex = 1;
                        foreach (var s in allScreens)
                        {
                            if (s == screen)
                                return nonPrimaryIndex;
 
                            if (!s.Primary)
                                nonPrimaryIndex++;
                        }
                    }
                }
 
                // If we reach here, try a different approach using FromHandle
                var currentScreen = System.Windows.Forms.Screen.FromHandle(Handle);
                if (currentScreen.Primary)
                    return 0;
 
                // Count screens to find the index
                var screens = System.Windows.Forms.Screen.AllScreens;
                for (int i = 0; i < screens.Length; i++)
                {
                    if (screens[i].DeviceName == currentScreen.DeviceName)
                    {
                        return screens[i].Primary ? 0 : i + 1;
                    }
                }
 
                return -1; // Unable to determine
            }
            catch
            {
                return -1; // Error occurred
            }
        }
 
        // Add helper method to detect Electron apps like VS Code
        private bool IsElectronApp()
        {
            // Check window class name first
            if (WindowClassName.Contains("Chrome_WidgetWin") ||
                WindowClassName.Contains("Electron") ||
                Title.Contains("Visual Studio Code"))
            {
                return true;
            }
 
            // Try to get process name as additional check
            try
            {
                uint processId = 0;
                GetWindowThreadProcessId(Handle, out processId);
                if (processId != 0)
                {
                    var process = System.Diagnostics.Process.GetProcessById((int)processId);
                    string procName = process?.ProcessName?.ToLowerInvariant() ?? "";
                    return procName.Contains("code") || procName.Contains("electron");
                }
            }
            catch
            {
                // Ignore errors in process identification
            }
 
            return false;
        }
    }
}