I am writing a WPF application that will put an icon in the system tray and, as an exercise, I want to do this without depending on System.Windows.Forms and using its NotifyIcon or NativeWindow classes.
This is fairly easy - Shell_NotifyIcon isn't hard to call from C# - and, indeed, I have succeeded in my task.
As part of this effort, I have had to create a window handle for the sole purpose of receiving messages from the system-tray. I create the native window as follows:
// Create a 'Native' window
_hwndSource = new HwndSource(0, 0, 0, 0, 0, 0, 0, null, parentHandle);
_hwndSource.AddHook(WndProc);
The message loop is hooked in AddHook() and messages are processed in a function that looks like this:
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Handle windows messages in this...
}
And, finally, when it comes time to destroy the thing, I close the window by posting it a WM_CLOSE message and disposing the HwndSource.
if (null != _hwndSource)
{
UnsafeNativeMethods.PostMessage(_hwndSource.Handle, WindowMessage.WM_CLOSE, 0, 0);
_hwndSource.Dispose();
_hwndSource = null;
}
My question is this: the first three parameters to the constructor of HwndSource are the class style, style and extended style of the native Win32 window, respectively. For a non-visible window that will only be used as a target for window-messages, what should they be?
My defaults of zero, zero and ... er.. zero do work but I have used Spy++ to examine what Windows.Forms.NotifyIcon does and it seems that the NativeWindow it creates have the following:
Class Style: <zero>
Styles: WS_CAPTION, WS_CLIPSIBLINGS,
WS_OVERLAPPED
Extended Styles: WS_EX_LEFT, WS_EX_LTRREADING,
WS_EX_RIGHTSCROLLBAR, WS_EX_WINDOWEDGE
Are any of those important for a non-visible window? (I think not.)
Windows style flags date from 1986, back when Windows v1.0 was released. There have been lots of appcompat hacks in the past 29 years and 10 major versions, Windows silently overrides style flags when the app specifies wonky ones. Nothing terribly wonky about this however, note that the value of the WS_OVERLAPPED style flag is 0. Which asks for a plain window, you automatically get the appropriate style flags for such a window.
Your HwndSource window has the exact same style flags, maybe you haven't found the correct one back in Spy++. So you don't have a problem. And no, they don't matter when the window never becomes visible.
Note a bug in your code, the WM_CLOSE message you post is never actually processed since you destroy the window right after calling PostMessage(). Just delete it, there is no point in asking the window nicely, it isn't going to object. You do however have to call Shell_NotifyIcon() with NIM_DELETE to delete the tray icon. Failure to do so leaves a "ghost" icon that only disappears when you move the mouse over it.
And do note that NotifyIcon is not as trivial as you assume it is, it has a non-obvious bug workaround that you are likely to overlook. You'll notice when the context menu refuses to close.
Related
I'm trying to obtain process information for the current active application (or window), using .Net/C#.
Currently I'm using
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
To get the current active window.
I understand there's no native way to do this other than use this API function.
From that, I use:
[DllImport("user32")]
private static extern UInt32 GetWindowThreadProcessId(IntPtr hWnd, out Int32 lpdwProcessId);
To get the process name that belongs to that window and then I get further process information.
I also use
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
To get the current Window Text or caption.
Now, using the Process class, I can use
MainWindowTitle
to get the main Window title as well.
The thing is, MainWindowTitle and GetWindowText does not return the same information.
For example, let's say the main application opened is "Toad" with a connection and an editor open.
Then with GetWindowText I get:
"Toad for Oracle - myConnection - Somequery.sql".
and Process.MainWindowTitle returns
"myConnection".
So, the question is how do I get the exact same text as I get using GetWindowText, but using merely .Net classes?
Edit:
I found out that actually the reason is simply because both functions are not querying the same window handle.
The window handle returned in the GetForegroundWindow, is the number 198982.
And the MainWindowHandle property, which I suppose is the one used in the MainWindowTitle propery is the number 198954.
Using Spy++ I could find and confirm those windows handle captions are the one returned by their corresponding function.
So the "problem", if any, is that the Process class does not correctly identify the most foreground window as the Main Window.
GetForegroundWindow gives you the active window the user is working in and that might be a owned window or a modal dialog, not necessarily the applications main/root window.
MainWindow is a .NET concept, native win32 does not have such a thing and there can be 0, 1 or multiple "main windows" in an application.
Some Delphi/C++Builder applications have a HWND for the taskbar button and each form is a owned window belonging to this "invisible" window. Other UI frameworks may pull similar stunts that might confuse "main window" detection.
You can use UI Automation to inspect other applications if you don't want to use p-invoke. Start with the foreground window and walk up the tree of owned and child windows...
I'm creating a "desktop gadget" of sorts, I've disabled manual minimizing of the window, but now there is another problem: the system can still hide the window if the user presses Windows+D, for example.
When hidden that way, no usual minimize/resize/visibility events are fired.
I want to do something almost like TopMost, but without forcing the window order.
Maybe it's possible to install a global shortcut event using win32 API, and briefly set TopMost to true, but that sounds very hackish.
I found one solution, but it does not seem to work on Windows 10: Keeping window visible through "Show Desktop"/Win+D
The other common option, which would be writing an actual desktop gadget, is not possible on Windows 10, given their deprecation.
Are there any other methods to keep a window visible (but not on top of the screen) at all moments?
This function is working for me:
BOOL FixShowDesktop(HWND hWnd)
{
HWND hWndTmp = FindWindowEx(NULL, NULL, L"Progman", NULL);
if (hWndTmp)
{
hWndTmp = FindWindowEx(hWndTmp, NULL, L"SHELLDLL_DefView", NULL);
if (hWndTmp)
{
SetWindowLongPtr(hWnd, -8, (LONG_PTR)hWndTmp);
return TRUE;
}
}
return FALSE;
}
Note, this code is a bit better then from Keeping window visible through "Show Desktop"/Win+D because the window can be overflowed by other windows (like any other window). Using SetParent places window under all other windows.
What i have now: my app in C# is half-transparent, and does not catch winapi events - every click, drag etc is catch by underlaying window, which is separate app (like webbrower). I use this to overlay information on top of what browser shows. This is my code for this:
int exstyle = GetWindowLong(this.Handle, GWL_EXSTYLE);
exstyle |= WS_EX_TRANSPARENT;
SetWindowLong(this.Handle, GWL_EXSTYLE, exstyle);
IntPtr hwndf = this.Handle;
IntPtr hwndParent = GetDesktopWindow();
SetParent(hwndf, hwndParent);
But now, i would like to send all events to both my app window (which is half-transparent on top) and web browser (under my app). So for example if i click, the click works in both windows as if they were on top. I imagine that only way to do that is to catch all events and then forward them to lower window, but is there any way to do that?
I use winforms as window lib.
What i do now is not that important, because i want to normally consume events, then forward them to underlaying window. So this is something completly different from what i'm doing now with WS_EX_TRANSPARENT. The point of this is to drag content in both windows simultaneously. If there is any better way of doing it, i would be glad to hear it.
As least what i need is to transfer drag events to both windows, and all other events to underlaying window (not under my control). So, perhaps it will be easier to stay with my window as WS_EX_TRANSPARENT (makes events pass-thru to underlaying window) and simply install global hook to receive drag events? What do you think?
BTW i don't have experience with Winapi, so solution might be obvious.
You can capture Windows events sent your own form by overloading WndProc on the form, or alternatively by calling user32!GetMessage
You can send messages to other Windows forms via the user32!PostMessage or user32!SendMessage apis (read PostMessage function on msdn).
You could try forwarding the event after the underlying form has handled it. Something like
protected override void OnDragOver(DragEventArgs drgevent)
{
base.OnDragOver(drgevent);
MyControl.ForwardDragEvent(drgevent);
}
In MyControl:
public void ForwardDragEvent(DragEventArgs drgevent)
{
base.OnDragOver(drgevent);
//Or call your own method to handle the event
}
I have used this to forward scroll-events, in my case however, only one of the controls handled the event..
I work eg. in Firefox, and my C#.NET app brings its window to the front. That's ok, but when I use SendToBack() to hide the form, it doesn't activate Firefox's window, so altough Firefox is in the foreground, I have to click into the window to be able to scroll, etc. How can I activate the previously focused window in C#?
i have tried these:
[DllImport("User32")]
private static extern int SetForegroundWindow(IntPtr hwnd);
[DllImport("user32.dll")]
static extern bool AllowSetForegroundWindow(int dwProcessId);
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[...]
AllowSetForegroundWindow(System.Diagnostics.Process.GetCurrentProcess().Id);
SendToBack();
SetForegroundWindow(GetForegroundWindow());
I hoped that after sending my window back, the previous one will be returned by GetForegroundWindow, but it doesn't work.
Secondly, I've tried to override WndProc, and handle the WM_ACTIVATE message to get the previous window from lParam, but it doesn't work either if I use SetForegroundWindow() with this handle.
protected override void WndProc(ref Message msg) {
if (msg.Msg == 0x0006) {
prevWindow = msg.LParam;
}
base.WndProc(ref msg);
}
[...]
SetForegroundWindow(prevWindow);
did you try the SetActiveWindow function? Its separate from the SetForgroundWindow function, although it seems that you may need to use them both.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms646311%28v=vs.85%29.aspx
There is also a SetFocus function which sounds like it could work.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms646312%28v=vs.85%29.aspx
Update
To get the current Active Window, I would fire off the GetActiveWindow function before moving your application to the front of the stack, that way you have the handle for the window that was active before hand.
http://msdn.microsoft.com/en-us/library/windows/desktop/ms646292%28v=vs.85%29.aspx
Another Update
I did a bit more digging around on the site and came up with the following three links, which might work better. The keyboard input functions seem to be dependent on the Window you are trying to set being part of the calling threads message queue, which since we are dealing with two separate application threads, is likely to not be the case.
GetGUIThreadInfo Get the threads information, including active window
GUITHREADINFO The GUITHREADINFO structure
SwitchToThisWindow Another method of window changing
All of these are in the same method stack as the SetForegroundWindow method, which seems to make them more likely to do what you are attempting.
When you call SetFocus() to move your app forward, it returns the handle of the window that had focus before you.
I have an application that displays a keyboard and tests whether the keys have been pressed or not. The issue I'm having is that when certain keys are pressed like the arrow buttons/tab, the keyboard graphic loses focus and it starts accessing menu items/etc. I tried registering to the preview mouse down event in the MainWindow and setting e.handled = true. But this does not work all the time. It would also be nice if there was a way to disable the windows button as well.
I think you would need to get into the operating system code for your solution. The OS treats some keys different from normal, so you may not be able to peak at a key's value or even that it has been pressed before it takes control away from your application.
I saw this kind of thing back when I was writing machine code BIOS routines for CP/M. Windows is much more involved than that. I quit writing code to control hardware when I started to use Windows 3.1.
I used this class:
http://gist.github.com/471698
I replaced line 99 with this code:
return EnableKeyboard ? InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam) : (IntPtr) 1;
Where EnableKeyboard is set by the user.