Here's an example of how to iterate through all the Sticky Note windows and move each of them. (Error checking has been removed for brevity. Also, be sure to read the note at the end for some comments on this implementation.)
First, we have to define the RECT
struct.
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public RECT(int l, int t, int r, int b)
{
Left = l;
Top = t;
Right = r;
Bottom = b;
}
public int Left;
public int Top;
public int Right;
public int Bottom;
}
Then some key p/Invokes. We'll need FindWindowExW
to locate the window with the correct window class for a sticky note. We also need GetWindowRect
, so we can figure out the size of the window, so we only move it, rather than a move and resize. Finally, we need SetWindowPos
which is pretty self-explanatory.
[DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndAfter,
string lpszClass, string lpszWindow);
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
int Y, int cx, int cy, uint uFlags);
Finally our algorithm.
IntPtr hWnd = FindWindowExW(IntPtr.Zero, IntPtr.Zero, "Sticky_Notes_Note_Window", null);
if (hWnd == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
if (error > 0) throw new Win32Exception(error);
else return;
}
IntPtr first = hWnd;
int currentX = 0;
while (hWnd != IntPtr.Zero)
{
RECT r;
bool result = GetWindowRect(hWnd, out r);
if (!result)
{
int error = Marshal.GetLastWin32Error();
if (error > 0) throw new Win32Exception(error);
else return;
}
result = SetWindowPos(hWnd,
IntPtr.Zero,
currentX,
0,
r.Right - r.Left,
r.Bottom - r.Top,
0);
if (!result)
{
int error = Marshal.GetLastWin32Error();
if (error > 0) throw new Win32Exception(error);
else return;
}
currentX += r.Right - r.Left;
hWnd = FindWindowExW(IntPtr.Zero, hWnd, "Sticky_Notes_Note_Window", null);
if (hWnd == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
if (error > 0) throw new Win32Exception(error);
else return;
}
if (hWnd == first) hWnd = IntPtr.Zero;
}
How does it work? First, using a tool like Spy++, I found the window class. From the window's property sheet, we can see that the window's class name is Sticky_Notes_Note_Window
.
With the information from Spy++, the first window handle is obtained using FindWindowExW
. This value is cached so that it can be determined when we've finished iterating all the windows. Inside the loop, we move the window, then use FindWindowEx
to again locate the next window with the same class, if none are found, hWnd
will be IntPtr.Zero
aka NULL
. We also have to check whether we are back to the start of our iteration. (If the notes are wider than the screen, they will spill off to the right. Wrapping the notes to another row is left as an exercise)
The issue with this implementation is that, if the first sticky note is closed, before we have iterated through all of them, then the program will never terminate. It would be better to keep track of all the windows that have been seen, and if any is seen again, then all have been enumerated.
An alternative method would be to use EnumWindows
and inside the callback call GetClassName
to see if it's a Sticky_Notes_Note_Window
, and then act appropriately. The method above required less work, so it's the method I chose.
References:
Edit: Added error checking based on @DavidHeffernan's comment. Also added clarification about how I found the Window class name.