Change Window's Sticky Notes (StikyNot.exe) location (multiple instances) - c#

It seems that all notes under StikyNot.exe are single exes instead of multiple. Also that means the coordinates of its location are always 0, 0, 0, 0. Is there a way to move it around? I tried using Win32's MoveWindow function without success.

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:
RECT Structure
FindWindowEx(W)
GetWindowRect
SetWindowPos
Edit: Added error checking based on #DavidHeffernan's comment. Also added clarification about how I found the Window class name.

Related

positioning the windows system date time clock window [duplicate]

This question already has answers here:
Opening process and changing window position
(3 answers)
Closed 5 years ago.
The event method below brings up the windows system date time clock window. My label is on the lower right side of my form and the system date time clock window appears on the upper left side of my form. Is there a way to position this date time clock window to be on the lower right side of my form when this event handler is clicked?
private void LabelDateTime_Click(object sender, System.EventArgs e)
{
// bring up the date & time dialog
System.Diagnostics.Process.Start("timedate.cpl");
}
Starting a process using System.Diagnostics.Process.Start() in this manner, is not effective, since the generated Process will exit immediately after the window is created. A .cpl applet is not a standard executable and needs then operating system shell and a launcher to start.
However, a stable process can be created using Rundll32.exe, which will generate some threads to host the applet controls and the GDI+ support.
Reaching the applet Window requires some P/Invoke(ing), though, since rundll is window-less and it doesn't reference the one it helps create, so the Process.MainWindowHandle = 0.
Doc Ref. MSDN EnumThreadWndProc() Callback, EnumThreadWindows(), GetWindowRect(), GetWindowText(), SetWindowPos()
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
ProcessStartInfo psInfo = new ProcessStartInfo() {
UseShellExecute = true,
FileName = "rundll32.exe",
Arguments = "shell32.dll, Control_RunDLL timedate.cpl,,0", //<- 0 = First Tab
WindowStyle = ProcessWindowStyle.Normal
};
Process sysClockProcess = new Process() {
SynchronizingObject = this,
EnableRaisingEvents = true,
StartInfo = psInfo
};
sysClockProcess.Start();
sysClockProcess.WaitForInputIdle();
//Insert the Window title. It's case SENSITIVE
//Window Title in HKEY_CURRENT_USER\Software\Classes\Local Settings\MuiCache\[COD]\[LANG]\
string windowTitle = "Date and Time";
int maxLenght = 256;
SetWindowPosFlags flags = SetWindowPosFlags.NoSize |
SetWindowPosFlags.AsyncWindowPos |
SetWindowPosFlags.ShowWindow;
//The first thread is the Main thread. All Dialog windows' handles are attached here.
//The second thread is for GDI+ Hook Window. Ignore it.
EnumThreadWindows((uint)sysClockProcess.Threads[0].Id, (hWnd, lParam) =>
{
StringBuilder lpString = new StringBuilder(maxLenght);
if (GetWindowText(hWnd, lpString, maxLenght) > 0)
if (lpString.ToString() == windowTitle)
{
GetWindowRect(hWnd, out RECT lpRect);
Size size = new Size(lpRect.Right - lpRect.Left, lpRect.Bottom - lpRect.Top);
//Caculate the position of the Clock Windows relative to the ref. Form Size
SetWindowPos(hWnd, (IntPtr)0, ((this.Width + this.Left) - size.Width),
((this.Height + this.Top) - size.Height), 0, 0, flags);
return false;
}
//Window not found: return true to continue the enumeration
return true;
}, ref windowTitle);
sysClockProcess.Exited += (s, ev) => {
Console.WriteLine($"The process has exited. Code: " +
$"{sysClockProcess.ExitCode} Time: {sysClockProcess.ExitTime}");
sysClockProcess.Dispose();
};
Win32 declarations:
// SetWindowPos() flags
[Flags]
public enum SetWindowPosFlags : uint
{
NoSize = 0x0001,
NoActivate = 0x0010,
ShowWindow = 0x0040,
DeferErase = 0x2000,
AsyncWindowPos = 0x4000
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
//Callback for `EnumThreadWindows()`.
public delegate bool EnumThreadWndProc([In] IntPtr hWnd, [In] IntPtr lParam);
[DllImport("user32.dll")]
static extern bool EnumThreadWindows([In] uint dwThreadId, [In] EnumThreadWndProc lpfn, [In] ref string lParam);
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, [Out] StringBuilder lpString, [In] int nMaxCount);
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll", SetLastError=true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);

The handle to window is not found just after showing MessageBox but after 1 second it is found

I show MessageBox and close it after 1 second by finding it by FindWindow function and then sending const UInt32 WM_CLOSE = 0x0010; to this MessageBox. The closing is success, but before I close it I try to move it to the upperright corner of the screen, but just after creating MessageBox by MessageBox.Show() it cannot be found.
[DllImport("user32.dll", SetLastError = true, PreserveSig = true)]
static extern IntPtr FindWindow(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.Dll")]
static extern int PostMessage(IntPtr hWnd, UInt32 msg, int wParam, int lParam);
[DllImport("user32.Dll")]
static extern int MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
const UInt32 WM_CLOSE = 0x0010;
static Thread thread;
static void CloseMessageBox() {
IntPtr hWnd = FindWindow(IntPtr.Zero, "Success!");
if (hWnd != IntPtr.Zero) {//ALWAYS FALSE DOES NOT FIND THE MESSAGEBOX
MoveWindow(hWnd, 0, 0, 300, 300, true);
// PostMessage(hWnd, WM_CLOSE, 0, 0);
Debug.WriteLine("HERE 1");
}
}
static void ShowMessageBox() {
MessageBox.Show("Data has been loaded!", "Success!");
IntPtr hWnd = FindWindow(IntPtr.Zero, "Success!");
if (hWnd != IntPtr.Zero) {//ALWAYS FALSE DOES NOT FIND THE MESSAGEBOX
Debug.WriteLine("HERE 2");
MoveWindow(hWnd, 0, 0, 300, 300, true);
}
}
public static void Seed() {
thread = new Thread(ShowMessageBox);
new Thread(() => {
thread.Start();
Thread.Sleep(1000);
CloseMessageBox();
}).Start();
}
I doubt .NET world MessageBox.Show message box can be found or controlled by native API calls. Better try with MessageBox API itself. Also ensure that API import and string are both Unicode. I have had problems with FindWindow also when searching by caption!
I would suggest to give Spy++ tool a try on how windows are placed, and if they can be found. Z-order, parent/child relationship also matters.

Get form handle from point using pinvoke

I'm trying to get a window handle from point using p/invoke, where window is a form, and not any child control. I have a simple interface where X and Y are entered by user, and then Find button is used to call win32 and get necessary information. My problem is that window is not necessarily a form, it can also be a control. See below screenshot - at (100,100) happened to be Notepad's text area with "StackOverflow" written in it. As a result, Found window shows "StackOverflow".
Is there any way I can restrict window type to be a Form? Expected result is "Untitled - Notepad" for below test case. Alternatively, is there a way to ask another application's control to provide its form's handle? In short, I need to get form's title from (x,y) point. Button click handler code:
private void btn_Find_Click(object sender, EventArgs e)
{
int xPoint = Convert.ToInt32(txt_WindowX.Text);
int yPoint = Convert.ToInt32(txt_WindowY.Text);
IntPtr hWnd = Win32.GetWindowHandleFromPoint(xPoint, yPoint);
txt_FormTitle.Text = Win32.GetWindowTitle(hWnd);
}
Major portion of Win32 class comes from this answer:
Tergiver's answer to "C# - unable to read another application's caption"
Full Win32 class code is provided below:
public class Win32
{
/// <summary>
///
/// </summary>
/// <param name="hwnd"></param>
/// <remarks>https://stackoverflow.com/questions/4604023/unable-to-read-another-applications-caption</remarks>
public static string GetWindowTitle(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
throw new ArgumentNullException("hwnd");
int length = Win32.SendMessageGetTextLength(hwnd, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (length > 0 && length < int.MaxValue)
{
length++; // room for EOS terminator
StringBuilder sb = new StringBuilder(length);
Win32.SendMessageGetText(hwnd, WM_GETTEXT, (IntPtr)sb.Capacity, sb);
return sb.ToString();
}
return String.Empty;
}
public static IntPtr GetWindowHandleFromPoint(int x, int y)
{
var point = new Point(x, y);
return Win32.WindowFromPoint(point);
}
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(Point p);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, [Out] StringBuilder lParam);
}
You need to locate the top level window. Start from the window that GetWindowHandleFromPoint yielded. Then call GetParent repeatedly until you find a window with no parent. That window with no parent is the top level window that you are looking for.

GetWindowRect returning same value for left & right

I'm trying to capture browser screenshot and one of my Win32 api method is GetWindowRect. This is returning same left & right value. This is only happen when I'm running my application in a remote machine having Win7 as a OS.
Also my PrintWindow method failing in this machine. If anyone have faced this issue before please let me know.
Those above two methods works fine with Vista and XP as OS in remote machine.
Adding few of the methods of my application.
[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
private Image Capture(IntPtr hwnd)
{
Rectangle windowSize = this.GetWindowPosition(hwnd);
Bitmap bm = new Bitmap(windowSize.Width, windowSize.Height);
using (Graphics g = Graphics.FromImage(bm))
{
IntPtr hdc = g.GetHdc();
if (PrintWindow(hwnd, hdc, 0) == false)
{
throw new Exception("PrintWindow call failed");
}
g.ReleaseHdc(hdc);
g.Flush();
}
return bm;
}
private Rectangle GetWindowPosition(IntPtr hwnd)
{
Rect r = new Rect();
GetWindowRect(hwnd, ref r);
return new Rectangle(r.Left, r.Top, r.Width, r.Height);
}
You aren't checking your Win32 return codes. My guess is that GetWindowRect fails for some reason and so doesn't assign any values to the rect. Thus its values remain uninitialised.
Check the return value and if the call fails use Marshal.GetLastWin32Error() to find out why. You'll need to update your P/Invokes too:
[DllImport("user32.dll", SetLastError=true)]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll", SetLastError=true)]
public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);
...
if (!GetWindowRect(hwnd, ref r))
int ErrorCode = Marshal.GetLastWin32Error();

Why am I seeing multiple Systray Icons?

I've added a Notify Icon to my app, and quite often I see up to 3 copies of the notify icon in my systray. is there a reason for this?
is there a way to stop it from happening.
Often this persists after my app has closed, untill I mose over to the systray and the systray expands and collapses snd then they all disapear.
Is this while you are debugging your application? if so this is because the messages that remove the icon from the system tray are only sent when the application exits normally, if it terminates because of an exception or because you terminate it from Visual Studio the icon will remain until you mouse over it.
You can kill the icon using the parent Window's Closed event. This works in my WPF app, even when testing in Visual Studio (2010 in my case):
parentWindow.Closing += (object sender, CancelEventArgs e) =>
{
notifyIcon.Visible = false;
notifyIcon.Icon = null;
notifyIcon.Dispose();
};
What I did:
Create a class library that updates the system tray.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace SystrayUtil
{
internal enum MessageEnum
{
WM_MOUSEMOVE = 0x0200,
WM_CLOSE = 0x0010,
}
internal struct RECT
{
internal int Left;
internal int Top;
internal int Right;
internal int Bottom;
internal RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
}
public sealed class Systray
{
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, IntPtr lpszWindow);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, int message, uint wParam, long lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetClientRect(IntPtr hWnd, out RECT usrTray);
public static void Cleanup()
{
RECT sysTrayRect = new RECT();
IntPtr sysTrayHandle = FindWindow("Shell_TrayWnd", null);
if (sysTrayHandle != IntPtr.Zero)
{
IntPtr childHandle = FindWindowEx(sysTrayHandle, IntPtr.Zero, "TrayNotifyWnd", IntPtr.Zero);
if (childHandle != IntPtr.Zero)
{
childHandle = FindWindowEx(childHandle, IntPtr.Zero, "SysPager", IntPtr.Zero);
if (childHandle != IntPtr.Zero)
{
childHandle = FindWindowEx(childHandle, IntPtr.Zero, "ToolbarWindow32", IntPtr.Zero);
if (childHandle != IntPtr.Zero)
{
bool systrayWindowFound = GetClientRect(childHandle, out sysTrayRect);
if (systrayWindowFound)
{
for (int x = 0; x < sysTrayRect.Right; x += 5)
{
for (int y = 0; y < sysTrayRect.Bottom; y += 5)
{
SendMessage(childHandle, (int)MessageEnum.WM_MOUSEMOVE, 0, (y << 16) + x);
}
}
}
}
}
}
}
}
}
}
Copy the dll to "%ProgramFiles%\Microsoft Visual Studio x.x\Common7\IDE\PublicAssemblies\SystrayUtil.dll"
Where x.x is the version number of Visual Studio
Record a macro and save it
Edit the macro
Add a reference to the created dll.
Add Imports SystrayUtil to the list of imports at the top of Module EnvironmentEvents.
Remove any unwanted items and add the following code to the EnvironmentEvents module
Public Sub DebuggerEvents_OnEnterDesignMode(ByVal Reason As EnvDTE.dbgEventReason) Handles DebuggerEvents.OnEnterDesignMode
Systray.Cleanup()
MsgBox("Entered design mode!")
End Sub
If it works remove MsgBox("Entered design mode!") because it's annoying to have a message box popping up every time you return from a debugging session.
This should work when you normally close the application:
// in form's constructor
Application.ApplicationExit += new EventHandler(this.OnApplicationExit);
private void OnApplicationExit(object sender, EventArgs e)
{
try
{
if (notifyIcon1!= null)
{
notifyIcon1.Visible = false;
notifyIcon1.Icon = null;
notifyIcon1.Dispose();
notifyIcon1= null;
}
}
catch { }
}
When you stop the App from Visual Studio stop debug button - the process is killed and no dispose events are fired.

Categories

Resources