Appreciate guidance and help here please;
I have searched and tried many solutions and it seems non are working with .Net 6 when I tried to center the MessageBox.Show() at the center of the parent Form!
So I have followed this:
Winforms-How can I make MessageBox appear centered on MainForm?
AND
How to Make MessageBoxes Center on their Parent Forms
When I tried to debug the code above and step through, the hook is not being called. Win32 is a wrapper calss of User32.dll
public static DialogResult Show(string msg, string title, MessageBoxButtons btns, MessageBoxIcon icon)
{
// Create a callback delegate
_hookProcDelegate = new Win32.WindowsHookProc(HookCallback);
// Remember the title & message that we'll look for.
// The hook sees *all* windows, so we need to make sure we operate on the right one.
_msg = msg;
_title = title;
// Set the hook.
// Suppress "GetCurrentThreadId() is deprecated" warning.
// It's documented that Thread.ManagedThreadId doesn't work with SetWindowsHookEx()
#pragma warning disable 0618
_hHook = Win32.SetWindowsHookEx(Win32.WH_CBT, _hookProcDelegate, IntPtr.Zero, AppDomain.GetCurrentThreadId());
#pragma warning restore 0618
// Pop a standard MessageBox. The hook will center it.
DialogResult rslt = MessageBox.Show(msg, title, btns, icon);
// Release hook, clean up (may have already occurred)
Unhook();
return rslt;
}
private static int HookCallback(int code, IntPtr wParam, IntPtr lParam)
{
int hHook = _hHook; // Local copy for CallNextHookEx() JIC we release _hHook
// Look for HCBT_ACTIVATE, *not* HCBT_CREATEWND:
// child controls haven't yet been created upon HCBT_CREATEWND.
if (code == Win32.HCBT_ACTIVATE)
{
string cls = Win32.GetClassName(wParam);
if (cls == "#32770") // MessageBoxes are Dialog boxes
{
string title = Win32.GetWindowText(wParam);
string msg = Win32.GetDlgItemText(wParam, 0xFFFF); // -1 aka IDC_STATIC
if ((title == _title) && (msg == _msg))
{
CenterWindowOnParent(wParam);
Unhook(); // Release hook - we've done what we needed
}
}
}
return Win32.CallNextHookEx(hHook, code, wParam, lParam);
}
_hook returns 0 / Last Error Code 87 (ERROR_INVALID_PARAMETER)
_hHook = Win32.SetWindowsHookEx(Win32.WH_CBT, _hookProcDelegate, IntPtr.Zero, AppDomain.GetCurrentThreadId());
int lastErrorCode = Marshal.GetLastWin32Error();
The SetWindowsHookEx:
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, WindowsHookProc lpfn, IntPtr hInstance, int threadId)
;
I ran the same code with .Net Framework 4.8 and it works fine! is there anything I have to update to make it works with .Net 6
Related
Consider the following unit test for WinApi functionality:
public class WinApiTest
{
[TestMethod]
public void WinApiFindFormTest_SimpleNesting()
{
var form = new Form();
form.Text = #"My form";
var button = new Button();
button.Text = #"My button";
form.Controls.Add(button);
//with below line commented out, the test fails
form.Show();
IntPtr actualParent = WinApiTest.FindParent(button.Handle);
IntPtr expectedParent = form.Handle;
//below 2 lines were added for debugging purposes, they are not part of test
//and they don't affect test results
Debug.WriteLine("Actual: " + WinApi.GetWindowTitle(actualParent));
Debug.WriteLine("Expected: " + WinApi.GetWindowTitle(expectedParent));
Assert.AreEqual(actualParent, expectedParent);
}
//this is a method being tested
//please assume it's located in another class
//I'm not trying to test winapi
public static IntPtr FindParent(IntPtr child)
{
while (true)
{
IntPtr parent = WinApi.GetParent(child);
if (parent == IntPtr.Zero)
{
return child;
}
child = parent;
}
}
}
Problem is that to make it work, I have to show the form, i.e. do form.Show(), otherwise, it fails with this output:
Actual: WindowsFormsParkingWindow
Expected: My form
Exception thrown: 'Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException' in Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
I read about this mysterious WindowsFormsParkingWindow, and it seems to be relevant only if there was no parent specified. So all controls without a parent live under this window. In my case, however, button was clearly specified to be part of form's controls.
Question: Is there a proper way to make this test pass? I'm trying to test FindParent method. In true spirit of unit tests, nothing should suddenly pop up in front of the user. It's possible to do Show and Hide sequence, but I think it's a rather hack-ish approach to solve the problem.
Code for WinApi class is provided below - it does not add much value to the question, but if you absolutely must see it, here it goes (major portion comes from this answer on SO):
public class WinApi
{
/// <summary>
/// Get window title for a given IntPtr handle.
/// </summary>
/// <param name="handle">Input handle.</param>
/// <remarks>
/// Major portition of code for below class was used from here:
/// https://stackoverflow.com/questions/4604023/unable-to-read-another-applications-caption
/// </remarks>
public static string GetWindowTitle(IntPtr handle)
{
if (handle == IntPtr.Zero)
{
throw new ArgumentNullException(nameof(handle));
}
int length = WinApi.SendMessageGetTextLength(handle, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (length > 0 && length < int.MaxValue)
{
length++; // room for EOS terminator
StringBuilder windowTitle = new StringBuilder(length);
WinApi.SendMessageGetText(handle, WM_GETTEXT, (IntPtr)windowTitle.Capacity, windowTitle);
return windowTitle.ToString();
}
return String.Empty;
}
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
[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);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);
}
When you access the Handle property, the window needs to be created. Child windows need to have a parent window, and if the parent window has not yet been created, the child window is created with the parking window as its parent. Only when the parent window is created, does the child window get re-parented.
IntPtr actualParent = WinApiTest.FindParent(button.Handle);
IntPtr expectedParent = form.Handle;
When you access button.Handle, the button's window is created, but since the form's window is not yet created, the parking window is the parent. The simplest way for you to handle this is to ensure that the form's window is created before the buttons's window. Make sure that you refer to form.Handle, before you call GetParent on the button's handle, for example in your test you could reverse the order of assignment:
IntPtr expectedParent = form.Handle;
IntPtr actualParent = WinApiTest.FindParent(button.Handle);
Obviously you'd want to comment this code so that a future reader knew that the order of assignment was critical.
I do wonder however why you feel the need to make a test like this however. I cannot imagine this sort of testing revealing a bug in your code.
I'm trying to intercept Revit and keep a window from opening. Specifically, I'm trying to apply a keynote to an object and then let the user create a keynote tag, however any way I do it it lets them place the keynote but then immediately gives them the dialog to select a keynote, but I don't want that dialog to come up because I already know what the selection should be. However every way I can think of isn't able to interrupt the process to apply the keynote before the user gets the dialog. Is it possible to perhaps monitor for the window to appear then close it via Windows API? or even better intercept when it's going to be shown and stop it from showing?
you can always delete warrnings with:failuresAccessor.DeleteWarning(fma);
this is what i use for my code
public class FloorPreProcessor : IFailuresPreprocessor
{
FailureProcessingResult
IFailuresPreprocessor.PreprocessFailures(
FailuresAccessor failuresAccessor)
{
IList<FailureMessageAccessor> fmas
= failuresAccessor.GetFailureMessages();
if (fmas.Count == 0)
{
return FailureProcessingResult.Continue;
}
// We already know the transaction name.
if (fmas.Count != 0)
{
foreach (FailureMessageAccessor fma in fmas)
{
// DeleteWarning mimics clicking 'Ok' button.
failuresAccessor.DeleteWarning(fma);
}
return FailureProcessingResult
.ProceedWithCommit;
}
return FailureProcessingResult.Continue;
}
}
I hope it will help
Try the following, it searches for a window name, button name, then clicks this button:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
private const uint BM_CLICK = 0x00F5;
public static bool clickButton (string popUpTitle, string ButtonName)
{
// Get the handle of the window
IntPtr windowHandle = FindWindow((string)null, popUpTitle);
if (windowHandle.ToInt32() == 0)
{
return false;
}
// Get button handle
IntPtr buttonHandle = FindWindowEx(windowHandle, IntPtr.Zero, (string)null, ButtonName);
if (buttonHandle.ToInt32() == 0)
{
return false;
}
// Send click to the button
SendMessage(buttonHandle, BM_CLICK, 0, 0);
return true;
}
You should set the popUpTitle (window name) and the ButtonName to click.
Call this into a timer event that waits for a window to pop-up.
Timer timer = new Timer();
timer.Start();
timer.Tick += new EventHandler(timer_Tick);
//when done call timer.Stop();
private void timer_Tick(object sender, EventArgs e)
{
//set your code to clickButton("","")
}
Try it and let me know.
Ok well since there was a new comment I will make this an official answer. The best I came up with is that you can call OverrideResult() on the dialog even though you can't cancel it. It sill flashes the dialog which isn't ideal but it's better than it was... If anyone has a better way I'd love to hear it :)
I'm running into a weird problem.
I have a WinForms application that opens another program (billing system emulator), sets it as a child window and then disables it. This works fine, the user cannot send any keys to the that child window, and the winforms application does its thing, sending commands to the child window.
However, it's been discovered that pushing the shift or control, even if the winforms application doesn't have focus, causes an error in the billing system emulator as they aren't valid keys. Users have taken to not using the shift or control keys while the winforms app runs but that's obviously not a practical solution.
My attempted solution was:
Global keyboard hook to capture when those keys are pressed.
Overriding OnKeyDown in the winforms application to stop those keys.
That however still doesn't solve the problem of the shift and alt keys being sent to the child window when the winforms app is not in focus. I can stop shift and alt globally while the winforms app is running but I don't think that is valid. So I need to somehow in the global hook stop the keypress for the winforms app and its children but allow globally. Any ideas/thoughts?
This is my code.
I don't think there's a good answer for your scenario... =\
Here's a hack you can try. It will "release" Control/Shift if they are down, then you send your message afterwards:
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int extraInfo);
[DllImport("user32.dll")]
static extern short MapVirtualKey(int wCode, int wMapType);
private void button1_Click(object sender, EventArgs e)
{
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
{
keybd_event((int)Keys.ShiftKey, (byte)MapVirtualKey((int)Keys.ShiftKey, 0), 2, 0); // Shift Up
}
if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
{
keybd_event((int)Keys.ControlKey, (byte)MapVirtualKey((int)Keys.ControlKey, 0), 2, 0); // Control Up
}
// ... now try sending your message ...
}
This obviously isn't foolproof.
I took a look at the only constructor of the globalKeyboardHook and looks like it is designed only for global hook. You can add another overload to hook into the current running module only like this:
class globalKeyboardHook {
[DllImport("kernel32")]
private static extern int GetCurrentThreadId();
[DllImport("user32")]
private static extern IntPtr SetWindowsHookEx(int hookType, KeyBoardProc proc, IntPtr moduleHandle, int threadId);
[DllImport("user32")]
private static extern int CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);
public globalKeyboardHook(bool currentModuleOnly){
if(currentModuleOnly){
proc = KeyBoardCallback;
//WH_KEYBOARD = 0x2
hhook = SetWindowsHookEx(2, proc, IntPtr.Zero, GetCurrentThreadId());
} else hook();
}
public delegate int KeyBoardProc(int nCode, IntPtr wParam, IntPtr lParam);
public int KeyBoardCallback(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode >= 0) {
Keys key = (Keys)wParam;
var lp = lParam.ToInt64();
//your own handling with the key
if ((lp >> 31) == 0)//Key down
{
//your own code ...
} else { //Key up
//your own code ...
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
KeyBoardProc proc;
//other code ...
}
//then use the new overload constructor instead of the parameterless constructor:
globalHook = new globalKeyboardHook(true);
NOTE: You can implement your own KeyDown and KeyUp event based on what I posted above (the comment your own code ...). After some searching I understand that the WH_KEYBOARD_LL supports global hook only while the WH_KEYBOARD is for thread hook only. That should be what you want instead of the WH_KEYBOARD_LL.
BTW, I doubt that the IMessageFilter which can be registered/added by your application can be used in this case. It also supports a PreFilterMessage method helping you to intercept any key and mouse messages at the application-level. You should try searching on that, it's easy to follow.
In one of my programs I need to test if the user is currently focusing the desktop/shell window. Currently I'm using GetShellWindow() from user32.dll and compare the result to GetForegroundWindow().
This approach is working until someone changes the desktop wallpaper, but as soon as the wallpaper is changed the handle from GetShellWindow() doesn't match the one from GetForegroundWindow() anymore and I don't quite get why that is. (OS: Windows 7 32bit)
Is there a better approach to check if the desktop is focused? Preferably one that won't be broken if the user changes the wallpaper?
EDIT: I designed a workaround: I'm testing the handle to have a child of class "SHELLDLL_DefView". If it has, the desktop is on focus. Whilst it's working at my PC that doesn't mean it will work all the timeā¦
The thing changed a little bit since there are slideshows as wallpaper available in Windows 7.
You are right with WorkerW, but this works only with wallpaper is set to slideshow effect.
When there is set the wallpaper mode to slideshow, you have to search for a window of class WorkerW and check the children, whether there is a SHELLDLL_DefView.
If there is no slideshow, you can use the good old GetShellWindow().
I had the same problem some months ago and I wrote a function for getting the right window. Unfortunately I can't find it. But the following should work. Only the Win32 Imports are missing:
public enum DesktopWindow
{
ProgMan,
SHELLDLL_DefViewParent,
SHELLDLL_DefView,
SysListView32
}
public static IntPtr GetDesktopWindow(DesktopWindow desktopWindow)
{
IntPtr _ProgMan = GetShellWindow();
IntPtr _SHELLDLL_DefViewParent = _ProgMan;
IntPtr _SHELLDLL_DefView = FindWindowEx(_ProgMan, IntPtr.Zero, "SHELLDLL_DefView", null);
IntPtr _SysListView32 = FindWindowEx(_SHELLDLL_DefView, IntPtr.Zero, "SysListView32", "FolderView");
if (_SHELLDLL_DefView == IntPtr.Zero)
{
EnumWindows((hwnd, lParam) =>
{
if (GetClassName(hwnd) == "WorkerW")
{
IntPtr child = FindWindowEx(hwnd, IntPtr.Zero, "SHELLDLL_DefView", null);
if (child != IntPtr.Zero)
{
_SHELLDLL_DefViewParent = hwnd;
_SHELLDLL_DefView = child;
_SysListView32 = FindWindowEx(child, IntPtr.Zero, "SysListView32", "FolderView"); ;
return false;
}
}
return true;
}, IntPtr.Zero);
}
switch (desktopWindow)
{
case DesktopWindow.ProgMan:
return _ProgMan;
case DesktopWindow.SHELLDLL_DefViewParent:
return _SHELLDLL_DefViewParent;
case DesktopWindow.SHELLDLL_DefView:
return _SHELLDLL_DefView;
case DesktopWindow.SysListView32:
return _SysListView32;
default:
return IntPtr.Zero;
}
}
In your case you would call GetDesktopWindow(DesktopWindow.SHELLDLL_DefViewParent); to get the top-level window for checking whether it is the foreground window.
Here is a workaround that uses GetClassName() to detect if the desktop is active:
When Windows first starts, the desktop's Class is "Progman"
After changing the wallpaper, the desktop's Class will be "WorkerW"
You can test against these to see if the desktop is focused.
[DllImport("user32.dll")]
static extern int GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount);
public void GetActiveWindow() {
const int maxChars = 256;
int handle = 0;
StringBuilder className = new StringBuilder(maxChars);
handle = GetForegroundWindow();
if (GetClassName(handle, className, maxChars) > 0) {
string cName = className.ToString();
if (cName == "Progman" || cName == "WorkerW") {
// desktop is active
} else {
// desktop is not active
}
}
}
I have a WebBrowser control displaying some HTML.
I want the user to be able to copy the entire document, but not do anything else.
I've set the IsWebBrowserContextMenuEnabled and WebBrowserShortcutsEnabled properties to false, and I want to handle KeyUp and run some code when the user presses Ctrl+C.
How can I do that?
The WebBrowser control doesn't support keyboard events.
I tried using the form's KeyUp event with KeyPreview, but it didn't fire at all.
EDIT: Here's my solution, inspired by Jerb's answer.
class CopyableWebBrowser : WebBrowser {
public override bool PreProcessMessage(ref Message msg) {
if (msg.Msg == 0x101 //WM_KEYUP
&& msg.WParam.ToInt32() == (int)Keys.C && ModifierKeys == Keys.Control) {
DoCopy();
return true;
}
return base.PreProcessMessage(ref msg);
}
void DoCopy() {
Document.ExecCommand("SelectAll", false, null);
Document.ExecCommand("Copy", false, null);
Document.ExecCommand("Unselect", false, null);
}
}
You could try this method as well. Put it in your main form area and it should catch all of the keyboard commands. I use it to add keyboard shortcuts to dynamically created tabs.
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
switch (keyData)
{
case Keys.Control|Keys.Tab:
NextTab();
return true;
case Keys.Control|Keys.Shift|Keys.Tab:
PreviousTab();
return true;
case Keys.Control|Keys.N:
CreateConnection(null);
return true;
}
return false;
It is a bug in Windows Forms. Its IDocHostUIHandler.TranslateAccelerator implementation actually tries to send the keystroke to the ActiveX host by returning S_OK after checking WebBrowserShortcutsEnabled and comparing the key data to predefined shortcuts. unfortunately in Windows Forms's keyboard processing, the shortcutkey property is checked during ProcessCmdKey, which means IDocHostUIHandler.TranslateAccelerator returned a little bit too late. That causes anything in the Shortcut enum (e.g. Control+C, Del, Control+N etc) stops working when WebBrowserShortcutsEnabled is set to false.
You can create or find a webbrowser ActiveX wrapper class (e.g. csexwb2) that provides a different IDocHostUIHandler.TranslateAccelerator implementation to check shortcut keys again. The Windows Forms webbrowser control does not allow customizing its IDocHostUIHandler implementation.
you can set a keyboard messages hook to your webbrowser control and filter out keyup keys messages or do some handling for them. Please see if code below would work for you:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, IntPtr windowTitle);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
public const int WH_KEYBOARD = 2;
public static int hHook = 0;
// keyboard messages handling procedure
public static int KeyboardHookProcedure(int nCode, IntPtr wParam, IntPtr lParam)
{
Keys keyPressed = (Keys)wParam.ToInt32();
Console.WriteLine(keyPressed);
if (keyPressed.Equals(Keys.Up) || keyPressed.Equals(Keys.Down))
{
Console.WriteLine(String.Format("{0} stop", keyPressed));
return -1;
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
// find explorer window
private IntPtr FindExplorerWindow()
{
IntPtr wnd = FindWindowEx(webBrowser1.Handle, IntPtr.Zero, "Shell Embedding", IntPtr.Zero);
if (wnd != IntPtr.Zero)
{
wnd = FindWindowEx(wnd, IntPtr.Zero, "Shell DocObject View", IntPtr.Zero);
if (wnd != IntPtr.Zero)
return FindWindowEx(wnd, IntPtr.Zero, "Internet Explorer_Server", IntPtr.Zero);
}
return IntPtr.Zero;
}
...
// install hook
IntPtr wnd = FindExplorerWindow();
if (wnd != IntPtr.Zero)
{
// you can either subclass explorer window or install a hook
// for hooking you don't really need a window handle but can use it
// later to filter out messages going to this exact window
hHook = SetWindowsHookEx(WH_KEYBOARD, new HookProc(KeyboardHookProcedure),
(IntPtr)0, GetCurrentThreadId());
//....
}
...
hope this helps, regards
After investigating a lot, we came to know it is browser compatibility issue.
We have added meta tag into the HTML page,then shortcuts are working fine. Below is the sample code.
<html>
<body>
<Head>
<meta http-equiv="X-UA-Compatible" content="IE=IE8" />
</head>
<form>
First name:<br>
<input type="text" name="firstname">
<br>
Last name:<br>
<input type="text" name="lastname">
</form></body>
</html>
There are three different solutions for this problem.
Adding meta tag to make the Web site browser compatible.
Override "PreocessCmdKey" method and handle the shortcuts.
Emulate browser by adding the key under FEATURE_BROWSER_EMULATION.
If you don't want to set the meta tag in html code, you can assign meta tag to the Document text property of webbrowser control before navigating the URL. Below is the sample.
//Setting compatible mode of IE.
this.m_oWebBrowser.DocumentText = #"<html>
<head><meta http-equiv=""X-UA-Compatible"" content=""IE=IE8"" /> </head>
<body></body>
</html>";
this.m_oWebBrowser.Navigate("www.google.com");