I am trying to get an Outlook C# add-in to scroll the left navigation pane using SendMessage. It seems to have no effect. Does an add-in run under a lower privilege level than Outlook, therefore preventing SendMessage from working thanks to UIPI?
I iterate using EnumChildWindows (which is recursive) to find the left Navigation Pane child window's handle, as identified using Spy++, using GetWindowText to find it by class name. "workerBeeFunc" is my delegate called by EnumChildWindows. Spy++ tells me the navigation Pane window is the child of the second window having a class name "NUIDocumentWindow" (I've left all the WinAPI DLLImport statements out here):
private const int WM_VSCROLL = 277; // Vertical scroll
private const int SB_PAGEDOWN = 3; // Scrolls one page down
static StringBuilder childWindowNames = new StringBuilder();
private int NUIWindowCounter = 0;
private stopIterating as bool = false;
public delegate int EnumChildProc(IntPtr hWnd, IntPtr lParam); //Declare the delegate
public int workerBeeFunc(IntPtr hwndChild, IntPtr Param) //The function the delegate of EnumChildWindows goes to to get the work done.
{
int a;
a = WinAPIs.GetWindowText(hwndChild, childWindowNames, 100); //childWindowNames gets cleared on each iteration of this function
if (childWindowNames.ToString() == "NUIDocumentWindow")
{
NUIWindowCounter++;
if (NUIWindowCounter == 2) //The NEXT window is the Navigation Pane
{
stopIterating = true;
}
return 1; //Keep iterating
}
if (stopIterating == true)
{
SendMessage(hwndChild, WM_VSCROLL, (IntPtr)SB_PAGEDOWN, IntPtr.Zero);
return 0; //Stop iterating
}
}
Will SendMessage silently fail because of UIPI?
Thanks.
Addins run in the same security context as Outlook itself.
There is no requirement for any control to use the WM_VSCROLL message. Do you actually see it in Spy++?
Related
I need to launch a new search form from all the open windows of my application if the user presses Control + F.
Currently I am using windows user32.dll methods to register a hotkey that I want to show across my application like this:
// Registers a hot key with Windows.
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
// Unregisters the hot key with Windows.
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
The issue with this method is that the shortcut is launched even when a different application like a web browser is selected.
I have tried using MainForm_KeyDown with KeyPreview set to true but it only works for the main form. I also tried overriding ProcessCmdKey but again, it only works for the main form.
I was thinking that maybe the UI Automation library offers support to watch for input. Otherwise would it be possible to add a filter to the windows hotkey registration so that it only works for the process id of my application?
As I understand it, you have these requirements:
Control-F makes a new form
Regardless of which Form (or control) has the focus currently, the hotkey should still work.
The hotkey is local to the application.
Implementing IMessageFilter should be effective in achieving these objectives:
public partial class MainForm : Form, IMessageFilter
{
public MainForm()
{
InitializeComponent();
Application.AddMessageFilter(this);
Disposed += (sender, e) =>Application.RemoveMessageFilter(this);
}
const int WM_KEYDOWN = 0x0100;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_KEYDOWN:
switch((Keys)m.WParam | ModifierKeys)
{
case Keys.Control | Keys.F:
onNewForm();
break;
}
break;
}
return false;
}
int _count = 0;
private void onNewForm()
{
char c = (char)(_count + 'A');
int dim = ++_count * SystemInformation.CaptionHeight;
new TextBoxForm
{
Name = $"textBoxForm{c}",
Text = $"TextBoxForm {c}",
Size = this.Size,
StartPosition= FormStartPosition.Manual,
Location = new Point(Left + dim, Top + dim),
}.Show(this);
}
}
I have a window (subclass of System.Windows.Forms.Form) without a border in order to apply a custom style to it. Moving the window when the mouse button is down seems to be easy. Either by sending a WM_NCLBUTTONDOWN HT_CAPTION or a WM_SYSCOMMAND 0xF102 message the window can be dragged to a new location. As soon as the mouse button is up though, it seems to be impossible to move the window.
One could send WM_SYSCOMMAND SC_MOVE message but then the cursor moves at the top center of the window and awaits for the user to press any arrow key in order to hook the window for move -which is awkward at the least. I tried to fake a key press/release sequence but that of course didn't work as I called SendMessage with the current form Handle as argument but I guess the message should not be sent to the current form.
The desired behavior is: click a button (ie the mouse button is released) move the form where cursor goes, click again to release the form. Is that possible with winapi? Unfortunately I am not familiar with it.
Addendum
Sending a key input: I tried to use SendInput as SendMessage is supposed to be bad practice. Still it didn't hook the window. I tried to read and print the winapi error code with Marshal.GetLastWin32Error() and I got a 5 which is access denied. The curious thing was that I received the messages after the move sequence ended (ie I manually pressed a key or mouse button). No idea how to work around this.
Using the IMessageFilter (IVSoftware's answer): this is what I ended up doing but that has 2 issues: moving the window with its Location property has lag compared to the native way (no big deal for now) and also it doesn't receive mouse messages that occur outside the main form. That means it won't work a. for multiscreen environments b. if the cursor moves outside the forms of the application. I could create full screen transparent forms for every monitor that will serve as an "message canvas" but still... why not give the OS way a chance.
As I understand it, the desired behavior is to enable the "Click to Move" (one way or another) and then click anywhere on a multiscreen surface and have the borderless form follow the mouse to the new position. One solution that seems to work in my brief testing is to pinvoke the WinApi SetWindowsHookEx to install a global low level hook for WH_MOUSE_LL in order to intercept WM_LBUTTONDOWN.
*This answer has been modified in order to track updates to the question.
Low-level global mouse hook
public MainForm()
{
InitializeComponent();
using (var process = Process.GetCurrentProcess())
{
using (var module = process.MainModule!)
{
var mname = module.ModuleName!;
var handle = GetModuleHandle(mname);
_hook = SetWindowsHookEx(
HookType.WH_MOUSE_LL,
lpfn: callback,
GetModuleHandle(mname),
0);
}
}
// Unhook when this `Form` disposes.
Disposed += (sender, e) => UnhookWindowsHookEx(_hook);
// A little hack to keep window on top while Click-to-Move is enabled.
checkBoxEnableCTM.CheckedChanged += (sender, e) =>
{
TopMost = checkBoxEnableCTM.Checked;
};
// Compensate move offset with/without the title NC area.
var offset = RectangleToScreen(ClientRectangle);
CLIENT_RECT_OFFSET = offset.Y - Location.Y;
}
readonly int CLIENT_RECT_OFFSET;
IntPtr _hook;
private IntPtr callback(int code, IntPtr wParam, IntPtr lParam)
{
var next = IntPtr.Zero;
if (code >= 0)
{
switch ((int)wParam)
{
case WM_LBUTTONDOWN:
if (checkBoxEnableCTM.Checked)
{
_ = onClickToMove(MousePosition);
// This is a very narrow condition and the window is topmost anyway.
// So probably swallow this mouse click and skip other hooks in the chain.
return (IntPtr)1;
}
break;
}
}
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
}
Perform the move
private async Task onClickToMove(Point mousePosition)
{
// Exempt clicks that occur on the 'Enable Click to Move` button itself.
if (!checkBoxEnableCTM.ClientRectangle.Contains(checkBoxEnableCTM.PointToClient(mousePosition)))
{
// Try this. Offset the new `mousePosition` so that the cursor lands
// in the middle of the button when the move is over. This feels
// like a semi-intuitive motion perhaps. This means we have to
// subtract the relative position of the button from the new loc.
var clientNew = PointToClient(mousePosition);
var centerButton =
new Point(
checkBoxEnableCTM.Location.X + checkBoxEnableCTM.Width / 2,
checkBoxEnableCTM.Location.Y + checkBoxEnableCTM.Height / 2);
var offsetToNow = new Point(
mousePosition.X - centerButton.X,
mousePosition.Y - centerButton.Y - CLIENT_RECT_OFFSET);
// Allow the pending mouse messages to pump.
await Task.Delay(TimeSpan.FromMilliseconds(1));
WindowState = FormWindowState.Normal; // JIC window happens to be maximized.
Location = offsetToNow;
}
checkBoxEnableCTM.Checked = false; // Turn off after each move.
}
In the code I used to test this answer, it seemed intuitive to center the button where the click takes place (this offset is easy to change if it doesn't suit you). Here's the result of the multiscreen test:
WinApi
#region P I N V O K E
public enum HookType : int { WH_MOUSE = 7, WH_MOUSE_LL = 14 }
const int WM_LBUTTONDOWN = 0x0201;
delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
#endregion P I N V O K E
Here is a possible solution that I will go with after all. It's not that IVSoftware's answer doesn't work, it does, I tried it. It's that my solution has some set of advantages relevant to what I am trying to do. The main points are:
Utilizing the IMessageFilter (thanks to SwDevMan81's answer) which reminded me that the correct way to process messages "globally" is not to override WndProc)
Laying out a set of transparent windows on all screens in order to receive mouse move messages everywhere.
Pros
It works without having to make any P/Invokes
It allows more tricks to be done like for example leverage the transparent forms to implement a "move border instead of form" functionality (though I didn't test it, paint might be tricky)
Can be easily applied for resize as well.
Can work with mouse buttons other than the left/primary.
Cons
It has too many "moving parts". At least for my taste. Laying out transparent windows all over the place? Hm.
It has some corner cases. Pressing Alt+F4 while moving the form will close the "canvas form". That can be easily mitigated but there might be others as well.
There must be an OS way to do this...
The code (basic parts; full code on github)
public enum WindowMessage
{
WM_MOUSEMOVE = 0x200,
WM_LBUTTONDOWN = 0x201,
WM_LBUTTONUP = 0x202,
WM_RBUTTONDOWN = 0x204,
//etc. omitted for brevity
}
public class MouseMessageFilter : IMessageFilter
{
public event EventHandler MouseMoved;
public event EventHandler<MouseButtons> MouseDown;
public event EventHandler<MouseButtons> MouseUp;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case (int)WindowMessage.WM_MOUSEMOVE:
MouseMoved?.Invoke(this, EventArgs.Empty);
break;
case (int)WindowMessage.WM_LBUTTONDOWN:
MouseDown?.Invoke(this, MouseButtons.Left);
break;
//etc. omitted for brevity
}
return false;
}
}
public partial class CustomForm : Form
{
private MouseMessageFilter windowMoveHandler = new();
private Point originalLocation;
private Point offset;
private static List<Form> canvases = new(SystemInformation.MonitorCount);
public CustomForm()
{
InitializeComponent();
windowMoveHandler.MouseMoved += (_, _) =>
{
Point position = Cursor.Position;
position.Offset(offset);
Location = position;
};
windowMoveHandler.MouseDown += (_, button) =>
{
switch (button)
{
case MouseButtons.Left:
EndMove();
break;
case MouseButtons.Middle:
CancelMove();
break;
}
};
moveButton.MouseClick += (_, _) =>
{
BeginMove();
};
}
private void BeginMove()
{
Application.AddMessageFilter(windowMoveHandler);
originalLocation = Location;
offset = Invert(PointToClient(Cursor.Position));
ShowCanvases();
}
//Normally an extension method in another library of mine but I didn't want to
//add a dependency just for that
private static Point Invert(Point p) => new Point(-p.X, -p.Y);
private void ShowCanvases()
{
for (int i = 0; i < Screen.AllScreens.Length; i++)
{
Screen screen = Screen.AllScreens[i];
Form form = new TransparentForm
{
Bounds = screen.Bounds,
Owner = Owner
};
canvases.Add(form);
form.Show();
}
}
private void EndMove()
{
DisposeCanvases();
}
private void DisposeCanvases()
{
Application.RemoveMessageFilter(windowMoveHandler);
for (var i = 0; i < canvases.Count; i++)
{
canvases[i].Close();
}
canvases.Clear();
}
private void CancelMove()
{
EndMove();
Location = originalLocation;
}
//The form used as a "message canvas" for moving the form outside the client area.
//It practically helps extend the client area. Without it we won't be able to get
//the events from everywhere
private class TransparentForm : Form
{
public TransparentForm()
{
StartPosition = FormStartPosition.Manual;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = false;
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//Draws a white border mostly useful for debugging. For example that's
//how I realised I needed Screen.Bounds instead of WorkingArea.
ControlPaint.DrawBorder(e.Graphics, new Rectangle(Point.Empty, Size),
Color.White, 2, ButtonBorderStyle.Solid,
Color.White, 2, ButtonBorderStyle.Solid,
Color.White, 2, ButtonBorderStyle.Solid,
Color.White, 2, ButtonBorderStyle.Solid);
}
}
}
I want to use C# code to simulate a user dragging and dropping a file onto a control in a separate process. As a stepping stone to this goal, I am trying to send a WM_DROPFILES message to my own TextBox and verifying that the DragDrop event is triggered.
With the code below in a Form containing a single TextBox and two Buttons, clicking on button1 successfully sets the text of textBox1 to "Hello world". So, it seems that I'm using SendMessage correctly and am able to supply arguments via pointers. Dragging and dropping a file from Windows Explorer onto textBox1 displays the MessageBox, so textBox1 is set up to receive drag-dropped files correctly. However, when I click button2, nothing happens. Why don't I see a MessageBox when I click button2?
using System;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace StackOverflow
{
public partial class BadDragDrop : Form
{
#region WINAPI
[Serializable]
[StructLayout(LayoutKind.Sequential)]
struct POINT
{
public Int32 X;
public Int32 Y;
}
[Serializable]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
class DROPFILES
{
public Int32 size;
public POINT pt;
public Int32 fND;
public Int32 WIDE;
}
const uint WM_DROPFILES = 0x0233;
const uint WM_SETTEXT = 0x000C;
[DllImport("Kernel32.dll", SetLastError = true)]
static extern int GlobalLock(IntPtr Handle);
[DllImport("Kernel32.dll", SetLastError = true)]
static extern int GlobalUnlock(IntPtr Handle);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
#endregion
public BadDragDrop()
{
InitializeComponent();
textBox1.AllowDrop = true;
}
private void button1_Click(object sender, EventArgs e)
{
string textToSet = "Hello world\0";
IntPtr p = Marshal.AllocHGlobal(textToSet.Length);
Marshal.Copy(textToSet.Select(c => (byte)c).ToArray(), 0, p, textToSet.Length);
int success = GlobalUnlock(p);
SendMessage(textBox1.Handle, WM_SETTEXT, IntPtr.Zero, p);
Marshal.FreeHGlobal(p);
}
private void button2_Click(object sender, EventArgs e)
{
string filePath = #"C:\Windows\win.ini" + "\0\0";
DROPFILES s = new DROPFILES()
{
size = Marshal.SizeOf<DROPFILES>(),
pt = new POINT() { X = 10, Y = 10 },
fND = 0,
WIDE = 0,
};
int wparamLen = s.size + filePath.Length;
IntPtr p = Marshal.AllocHGlobal(wparamLen);
int iSuccess = GlobalLock(p);
Marshal.StructureToPtr(s, p, false);
Marshal.Copy(filePath.Select(c => (byte)c).ToArray(), 0, p + s.size, filePath.Length);
iSuccess = GlobalUnlock(p);
var verify = new byte[wparamLen];
Marshal.Copy(p, verify, 0, wparamLen);
var ipSuccess = SendMessage(textBox1.Handle, WM_DROPFILES, p, IntPtr.Zero);
Marshal.FreeHGlobal(p);
}
private void textBox1_DragDrop(object sender, DragEventArgs e)
{
MessageBox.Show(this, "Drag drop!");
}
private void textBox1_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Copy;
}
}
}
The reason you don't see your MessageBox appear is likely because the TextBox doesn't handle WM_DROPFILES messages to begin with. It implements its drop support using OLE Drag&Drop instead by implementing the IDropTarget interface (see Drag and Drop Overview in the WPF documentation).
WM_DROPFILES has been deprecated ever since DoDragDrop() was introduced way back in Windows 95. OLE Drag&Drop has been the preferred way to implement drag&drop on Windows for a very long time. WM_DROPFILES is still supported by Windows (but not .NET) but only for backwards compatibility with legacy apps.
Dragging items from Windows Explorer to other apps uses OLE Drag&Drop under the hood, even if the receiver doesn't implement OLE Drag&Drop.
If you drag&drop an IDataObject onto a window that has had RegisterDragDrop() called on it (like your TextBox has), the IDataObject will be passed to the window's IDropTarget interface for handling.
If you drag&drop an IDataObject onto a window that doesn't implement IDropTarget, but has had DragAcceptFiles() called on it, or at least has the WS_EX_ACCEPTFILES window style, Windows will generate a WM_DROPFILES message if the IDataObject contains CF_HDROP data in it.
So, to do what you are attempting with your TextBox, you need to implement the IDropSource and IDataObject interfaces, and then call DoDragDrop() with them. Let Windows handle the actual dragging and dropping for you. See Shell Clipboard Formats and Handling Shell Data Transfer Scenarios.
Now, with that said, if you are still determined to send a WM_DROPFILES message to another process (which your question says is your ultimate goal), you can do that, and Windows will marshal your DROPFILES struct across process boundaries for you, but you need to use PostMessage() instead of SendMessage() (not sure why, only that it is required), and be sure to free the DROPFILES only if PostMessage() fails. Windows will free the DROPFILES for you if PostMessage() is successful.
But even then, there is no guarantee that the receiving process actually handles WM_DROPFILES messages (and even if it does, your manual WM_DROPFILES message might get blocked by UIPI, unless the receiver calls ChangeWindowMessageFilter/Ex() on itself to allow WM_DROPFILES and WM_COPYGLOBALDATA messages). The receiver might be expecting IDataObject instead. If the receiver even supports drag&drop at all.
I have a desktop application installed on my machine. When I start a program some kind of window gets open. let's say, something like this (just example):
So, I want to write an application in C# that will find this window and capture some data from it.
What tools should I look at? I want to go with a path of least resistance.
I need to capture images, text from textboxes, and also find controls by text and click on them.
I suggest you use the cool but little-known UI Automation API for this work.
For this, the first thing to test is launch the associated UISpy tool. It will display a tree of all accessible windows on screen. It also is able to run some actions like pressing a menu, selecting an item, etc. This is using what's called UI Automation Control Patterns, which provide a way to categorize and expose a control's functionality independent of the control type or the appearance of the control.
So, if you can automate this application with UI Spy, you also can do the exact same thing using .NET code (UISpy is itself simply using the underlying API).
Here is an interesting tutorial article about UI automation programming: The Microsoft UI Automation Library
You should start enumerating handles of all windows for that process :
https://stackoverflow.com/a/2584672/351383
Then for each handle get information about text and position, with position infomation you can take screenshots of desktop on that position to get images AFAIK there is no other way to get images from a window of running application.
When you got screen positions of the controls then use from link below to simulate left mouse click, search windows for some text and then click on some point inside control, here is the method that will click a point :
https://stackoverflow.com/a/10355905/351383
I put toghether quick class to gather that data for process :
public static class ProcessSpy
{
public static List<ProcessSpyData> GetDataForProcess(string processName)
{
var result = new List<ProcessSpyData>();
Process myProc = Process.GetProcessesByName(processName).FirstOrDefault();
if (myProc != null)
{
var myHandles = EnumerateProcessWindowHandles(myProc);
foreach (IntPtr wndHandle in myHandles)
{
result.Add(new ProcessSpyData(wndHandle));
}
}
return result;
}
delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
static IEnumerable<IntPtr> EnumerateProcessWindowHandles(Process prc)
{
var handles = new List<IntPtr>();
foreach (ProcessThread thread in prc.Threads)
EnumThreadWindows(thread.Id, (hWnd, lParam) => { handles.Add(hWnd); return true; }, IntPtr.Zero);
return handles;
}
}
public class ProcessSpyData
{
private const uint WM_GETTEXT = 0x000D;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam);
[DllImport("user32.dll")]
private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
int left, top, right, bottom;
public Rectangle ToRectangle()
{
return new Rectangle(left, top, right - left, bottom - top);
}
}
[DllImport("user32.dll")]
static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);
public IntPtr WindowHandle { get; private set; }
public string WindowText { get; private set; }
public Rectangle ClientRect { get; private set; }
public Rectangle ScreenPos { get; private set; }
public ProcessSpyData(IntPtr windowHandle)
{
this.WindowHandle = windowHandle;
GetWindowText();
GetWindowSize();
}
private void GetWindowText()
{
StringBuilder message = new StringBuilder(1024);
SendMessage(this.WindowHandle, WM_GETTEXT, message.Capacity, message);
this.WindowText = message.ToString();
}
private void GetWindowSize()
{
var nativeRect = new RECT();
GetClientRect(this.WindowHandle, out nativeRect);
this.ClientRect = nativeRect.ToRectangle();
Point loc = this.ClientRect.Location;
ClientToScreen(this.WindowHandle, ref loc);
this.ScreenPos = new Rectangle(loc, this.ClientRect.Size);
}
}
That should get you started, but you have to be aware if app is using non standard controls then there is no way to get text out of it with this method, and for images maybe you will get better results looking at executable resources.
UPDATE
Geting controls text for various control types (MFC, winforms, Delphi VCL etc.) would be very hard task, but for winforms see excelent Managed Windows API, they even have some sort of spy application in tools, look at that.
What kind of data are you trying to capture?
You may try listening to windows messages or reading the memory.
Depending on how much of these type of tasks you are going to be doing in the future (or how important this one is) you could try investing in something like Ranorex Spy (Ranorex studio is ott).
Link: http://www.ranorex.com/product/tools/ranorex-spy.html
there is no other way than to inject the application you want to inspect. This is how UISpy actually runs. This is also why UISpy should be run with Administrative credential.
I'm trying to get a handle to the foreground window in C#/Net 2.0/WinForms by calling the native GetForegroundWindow WinAPI function, in my app's form's constructor.
When I run the program directly from Windows Explorer or Total Commander, it correctly identifies the Windows Explorer or Total Commander window.
However, if I create a shortcut to my program on desktop and set a shortcut key for the shortcut (let's say Ctrl+Alt+X), when I'm running my program using the shortcut, the foreground window is identified as the "Shell_TrayWnd window" (Handle 0x00010064), and not the actual window. (Let's say I'm running Firefox on top, when I press Ctrl+Alt+X my program starts and says that the foreground window is not Firefox, as it should, it says it's the taskbar - Shell_TrayWnd.)
public MainForm()
{
this.InitializeComponent();
IntPtr handle = WinAPI.GetForegroundWindow();
this.Text = handle.ToString();
StringBuilder title = new StringBuilder(255);
if (WinAPI.GetWindowText(handle, title, 255) > 0)
{
this.Text += title.ToString();
}
}
How can I get the real foreground window? Should I (also) use other functions like GetWindow?
Thank you
Note that the task bar may be the real foreground window at the time you call GetForegroundWindow, simply because it's Explorer who's handling the shortcut press, and the task bar belongs to Explorer (Shell_TrayWnd is the window class of the task bar).
If you want to do something with the global active window, you may be better off by starting your application and letting it wait in the background. Then you can handle key presses while your application is running, so Explorer won't interfere.
Somehow, this reminds me of an article by Raymond Chen.
I think you're trying to do the same thing as I am — open a shell from Explorer at the current path.
I ran into exactly the same problem. Here's a program that's working for me. It uses EnumWindows to search through all visible windows until it finds one whose title is a real path.
using System;
using System.IO;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
public class ShellHere
{
// Thanks to pinvoke.net for the WinAPI stuff
[DllImport("user32.dll")]
private static extern int EnumWindows(CallBackPtr callPtr, int lPar);
[DllImport("user32.dll")]
static extern int GetWindowText(int hWnd, StringBuilder text, int count);
[DllImport("user32.dll", EntryPoint="GetWindowLong")]
private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, GWL nIndex);
[DllImport("user32.dll", EntryPoint="GetWindowLongPtr")]
private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, GWL nIndex);
public delegate bool CallBackPtr(int hwnd, int lParam);
private static CallBackPtr _callBackPtr;
// This static method is required because Win32 does not support
// GetWindowLongPtr directly
public static IntPtr GetWindowLongPtr(IntPtr hWnd, GWL nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLongPtr64(hWnd, nIndex);
else
return GetWindowLongPtr32(hWnd, nIndex);
}
public static bool FindPathInTitle( int hwnd, int lparams )
{
const int nChars = 256;
StringBuilder buffer = new StringBuilder( nChars );
IntPtr result = GetWindowLongPtr( new IntPtr(hwnd), GWL.GWL_STYLE );
// ignore invisible windows
if ( (result.ToInt64() & WS_VISIBLE) != 0 )
{
if ( GetWindowText( hwnd, buffer, nChars ) > 0 )
{
string title = buffer.ToString();
// ignore the taskbar
if ( title.ToLower() != "start" && Directory.Exists( title ) )
{
_folder = title;
return false;
}
}
}
return true;
}
private static string _folder;
public static void Main()
{
_callBackPtr = new CallBackPtr( FindPathInTitle );
EnumWindows( _callBackPtr, 0 );
Process shell = new Process();
shell.StartInfo.FileName = "cmd.exe";
if ( !string.IsNullOrEmpty( _folder ) )
shell.StartInfo.WorkingDirectory = _folder;
shell.Start();
}
public enum GWL
{
GWL_WNDPROC = (-4),
GWL_HINSTANCE = (-6),
GWL_HWNDPARENT = (-8),
GWL_STYLE = (-16),
GWL_EXSTYLE = (-20),
GWL_USERDATA = (-21),
GWL_ID = (-12)
}
// Window Styles
const UInt32 WS_VISIBLE = 0x10000000;
}
It's working for me so far (Win7-64). Note that you don't have to be directly on the Explorer window for it to work — it'll use the next one in the tab order.
I'm not sure what you need the foreground window for, so this might or might not help:
You probably can detect that you were started through a shortcut by:
calling GetStartupInfo
checking dwFlags for STARTF_TITLEISLINKNAME
In that case you might try to get the window that's previous in Z order, or on top on the Desktop window.