Send MouseWheel messages to System.Windows.Forms.WebBrowser - winforms

I wanted to forward MouseWheel events produced at the form level, so that they would be processed by an embedded WebBrowser control, even when that control didn't have the focus.
Here is what I have done:
Implemented IMessageFilter.PreFilterMessage.
Registered the filter it with Application.AddMessageFilter.
In the filter, listen for WM_MOUSEWHEEL messages.
Forward the messages using SendMessage to the target control (in my case WebBrowser).
In code, this looks like this:
bool IMessageFilter.PreFilterMessage(ref Message m)
{
if (m.Msg == 0x20A) // WM_MOUSEWHEEL
{
if (this.target != null)
{
var handle = this.target.Handle;
Native.SendMessage (handle, m.Message, m.WParam, m.LParam);
return true;
}
}
return false;
}
// Registering the message filter:
System.Windows.Forms.Application.AddMessageFilter (this);
// Win32 code:
protected static class NativeMethods
{
[System.Runtime.InteropServices.DllImport ("user32.dll")]
public static extern System.IntPtr SendMessage(System.IntPtr hWnd, System.Int32 Msg, System.IntPtr wParam, System.IntPtr lParam);
}
This does not work. Nothing happens.
However, if instead of a WebBrowser I specify a Panel as the target, then this works wonderfully well.

Pierre's answer works for me (can't up vote as not enough reputation). However, it requires a tweak to work in VB.NET so I thought I'd post in case there's anyone stuck on this point:
Imports System.Runtime.InteropServices
Public Class Form1 Implements IMessageFilter
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
System.Windows.Forms.Application.AddMessageFilter(Me)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Me.WebBrowser1.Navigate("D:\Development\test3.html")
End Sub
Private Function IMessageFilter_PreFilterMessage(ByRef m As Message) As Boolean Implements IMessageFilter.PreFilterMessage
If m.Msg = &H20A Then
' WM_MOUSEWHEEL
If m.HWnd <> 0 Then
Dim handle = m.HWnd
handle = NativeMethods.FindWindowEx(handle, IntPtr.Zero, "Shell Embedding", Nothing)
handle = NativeMethods.FindWindowEx(handle, IntPtr.Zero, "Shell DocObject View", Nothing)
handle = NativeMethods.FindWindowEx(handle, IntPtr.Zero, "Internet Explorer_Server", Nothing)
NativeMethods.SendMessage(handle, m.Msg, m.WParam, m.LParam)
Return True
End If
End If
Return False
End Function
Protected NotInheritable Class NativeMethods
Private Sub New()
End Sub
<System.Runtime.InteropServices.DllImport("user32.dll")> _
Public Shared Function SendMessage(hWnd As System.IntPtr, Msg As System.Int32, wParam As System.IntPtr, lParam As System.IntPtr) As System.IntPtr
End Function
<System.Runtime.InteropServices.DllImport("user32.dll")> _
Public Shared Function FindWindowEx(hwndParent As System.IntPtr, hwndChildAfter As System.IntPtr, className As String, windowName As String) As System.IntPtr
End Function
End Class
End Class

Investigating with Spy++ revealed that the WebBrowser control in WinForms is using several layers of containers to wrap the real IE component:
System.Windows.Forms.WebBrowser
Shell Embedding
Shell DocObject View
Internet Explorer_Server
Sending the events to any container won't have any effect. The WM_MOUSEWHEEL events have to be sent to the Internet Explorer_Server handle in order for this to work.
Here is the modified code, which finds the IE component by digging into the containers:
bool IMessageFilter.PreFilterMessage(ref Message m)
{
if (m.Msg == 0x20A) // WM_MOUSEWHEEL
{
if (this.target != null)
{
var handle = this.target.Handle;
handle = NativeMethods.FindWindowEx (handle, IntPtr.Zero, "Shell Embedding", null);
handle = NativeMethods.FindWindowEx (handle, IntPtr.Zero, "Shell DocObject View", null);
handle = NativeMethods.FindWindowEx (handle, IntPtr.Zero, "Internet Explorer_Server", null);
Native.SendMessage (handle, m.Msg, m.WParam, m.LParam);
return true;
}
}
return false;
}
protected static class NativeMethods
{
[System.Runtime.InteropServices.DllImport ("user32.dll")]
public static extern System.IntPtr SendMessage(System.IntPtr hWnd, System.Int32 Msg, System.IntPtr wParam, System.IntPtr lParam);
[System.Runtime.InteropServices.DllImport ("user32.dll")]
public static extern System.IntPtr FindWindowEx(System.IntPtr hwndParent, System.IntPtr hwndChildAfter, string className, string windowName);
}

Related

GetComboBoxinfo API failing with error code 87 in 64 Bit OS

I am trying to hook mouse events to combobox. sample code is here.
But it is working fine if keep project properties [Prefer 32 bit TRUE] but GetComboBoxInfo API is failing with error code 87 if i keep Prefer 32 bit FALSE.
Here is place where code is failing:
public static IntPtr GetChildListWindowHandle(Control ctrl)
{
Win32.ComboBoxInfo comboBoxInfo = new Win32.ComboBoxInfo();
comboBoxInfo.cbSize = (uint)Marshal.SizeOf(comboBoxInfo);
Win32.GetComboBoxInfo(ctrl.Handle, ref comboBoxInfo);
uint error = GetLastError() ;
if (true == Win32.GetComboBoxInfo(ctrl.Handle, ref comboBoxInfo))
{
return comboBoxInfo.hwndList;
}
return IntPtr.Zero;
}
[StructLayout(LayoutKind.Sequential)]
public struct ComboBoxInfo
{
public uint cbSize;
public RECT rcItem;
public RECT rcButton;
public IntPtr stateButton;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
[DllImport("user32.dll")]
public static extern bool GetComboBoxInfo(IntPtr hwndCombo, ref ComboBoxInfo info);

SaveFileDialog button events in WPF

I have a SaveFileDialog in my WPF Project.
If the SaveFileDialog is closed via the red 'x' in the corner, I want the dialog to close and the user to be returned to the main application.
However, if the SaveFileDialog is 'canceled', I want the entire application to close.
The only issue I am having is knowing if the user has pressed the red x or the cancel button. They both evaluate to false in the code below.
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Text file (*.txt)|*.txt";
var dlg = saveFileDialog.ShowDialog();
if (dlg == true)
{
//When the user presses save
File.WriteAllLines(saveFileDialog.FileName, rowList);
ExitApp();
}
else if (dlg == false)
{
//This occurs when red x or cancel is pressed.
}
Is there any way to distinguish between the two in WPF?
The answer is short and simple:
"no"
See Microsoft documentation here
It is not so simple, but you can do it by using hooks and the SetWindowsHookEx method.
First of all we need a class for registering and unregistering our hook:
public class WinHooker
{
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
private HookProc hookDelegate;
private int hookHandle;
private bool wasClosedButtonPressed;
private static WinHooker instance;
private WinHooker()
{
}
public static WinHooker Instance
{
get
{
if (instance == null)
{
instance = new WinHooker();
}
return instance;
}
}
public bool WasClosedButtonPressed
{
get
{
return wasClosedButtonPressed;
}
}
public void Register()
{
wasClosedButtonPressed = false;
hookDelegate = this.HookProcHandler;
hookHandle = SetWindowsHookEx(5,
hookDelegate,
IntPtr.Zero,
AppDomain.GetCurrentThreadId());
}
public void Unregister()
{
UnhookWindowsHookEx(hookHandle);
hookHandle = 0;
hookDelegate = null;
}
private int HookProcHandler(int nCode, int wParam, IntPtr lParam)
{
if (nCode >= 0)
{
if (nCode == 8 && wParam == 0xF060)
{
wasClosedButtonPressed = true;
}
}
return CallNextHookEx(hookHandle, nCode, wParam, lParam);
}
}
As you can see I used 5 as the first parameter for the SetWindowsHookEx, since it correspond to the WH_CBT value (refer to the SetWindowsHookEx page). Moreover the values in the method HookProcHandler (i.e. nCode == 8 && wParam == 0xF060) can be retrived from here.
Now let's use our class in the code:
SaveFileDialog saveFileDialog = new SaveFileDialog();
WinHooker.Instance.Register();
saveFileDialog.ShowDialog();
WinHooker.Instance.Unregister();
if (WinHooker.Instance.WasClosedButtonPressed)
{
MessageBox.Show("Oh my God! What have you done??");
}
As you can understand WasClosedButtonPressed is set to true just if the user closes the dialog by clicking on the red 'x' in the corner. Otherwise it is false.
I hope it can help you.

WPF Listview does not scroll (with mouse wheel) when Application not in focus

I have a WPF app (written in C#) which has a Listview control which scrolls perfectly with the mouse wheel when the app is in focus.
However when the app is not in focus, even when the mouse pointer is over the app & list view area, the Listview does not scroll. I continue to see mousehover related effects on the app but no mousewheel event is received. This is inline with how most of the other apps work on my desktop however some of them (like Facebook messenger) support scrolling without focus which i would like to mimic in my WPF app.
I have searched MSDN forums and Stackoverflow and seen multiple solutions for Windows Forms however they were questions asked over 5 years ago and i was wondering if someone has managed to do it relatively easily on .net 4.5 and can point me to possible solutions.
---Edit---
I was able to progress to some extent on this thanks to this thread C# ListView mouse wheel scroll without focus
Here is how my the function that receives the mousewheel looks
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 &&
MouseMessages.WM_MOUSEWHEEL == (MouseMessages)wParam)
{
MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
Console.WriteLine(hookStruct.pt.x + ", " + hookStruct.pt.y);
Console.WriteLine((short)((hookStruct.mouseData)>>16));
MouseWheelEventArgs myArgs = new MouseWheelEventArgs(System.Windows.Input.Mouse.PrimaryDevice, (int)hookStruct.time, (short)((hookStruct.mouseData)>>16));
myMainFrame.SidePanelControl.ScrollTheListView(myArgs);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
As you can see i am initializing a MouseWheelEventArgs instance and have the time, delta and the mouse device attributes.
How do i go about passing this mousewheel event to my listview scrollviewer?
Managed to get this working. Here is my class.
All one needs to do to use the class is
Initialize InterceptMouse passing it the app/listview/etc pointer
Start intercepting the mouse when the app is not in focus but the corresponding mouseenter event has occured.
As long as the event is mousewheel the scrollviewer of the listview will be sent the mouseWheel event.
Stop intercepting the mouse when the app gets activated or mouseleave is called.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Windows.Forms;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Input;
using System.Diagnostics;
using System.Windows.Forms.Integration;
namespace myApp.HelperClasses
{
public class InterceptMouse
{
public static System.Windows.Controls.ListView myListview;
private static LowLevelMouseProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public InterceptMouse(System.Windows.Controls.ListView myListviewParam)
{
myListview = myListviewParam;
}
public static void StartIntercepting()
{
_hookID = SetHook(_proc);
}
public static void StopIntercepting()
{
UnhookWindowsHookEx(_hookID);
}
private static IntPtr SetHook(LowLevelMouseProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_MOUSE_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 &&
MouseMessages.WM_MOUSEWHEEL == (MouseMessages)wParam)
{
MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
//Console.WriteLine(hookStruct.pt.x + ", " + hookStruct.pt.y + "," + hookStruct.mouseData);
var delta = (short)((hookStruct.mouseData) >> 16);
var mouse = InputManager.Current.PrimaryMouseDevice;
var args = new MouseWheelEventArgs(mouse, Environment.TickCount, delta);
args.RoutedEvent = WindowsFormsHost.MouseWheelEvent;
Decorator border = VisualTreeHelper.GetChild(myListview, 0) as Decorator;
// Get scrollviewer
ScrollViewer scrollViewer = border.Child as ScrollViewer;
scrollViewer.RaiseEvent(args);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private const int WH_MOUSE_LL = 14;
private enum MouseMessages
{
WM_LBUTTONDOWN = 0x0201,
WM_LBUTTONUP = 0x0202,
WM_MOUSEMOVE = 0x0200,
WM_MOUSEWHEEL = 0x020A,
WM_RBUTTONDOWN = 0x0204,
WM_RBUTTONUP = 0x0205
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
}

When minimized into the system tray, a WPF Window does not respond to message from PostMessage from the other instance when

I am working on a task to restore and maximize a window from system tray when the second instance of the same application starts.
When the 2nd instance starts and fail to grab the mutex. It calls the following code to signal the first instance to show itself:
public static void ShowFirstInstance()
{
WinApi.PostMessage(
(IntPtr)WinApi.HWND_BROADCAST,
WM_SHOWFIRSTINSTANCE,
IntPtr.Zero,
IntPtr.Zero);
}
The message is registered using the following:
public static readonly int WM_SHOWFIRSTINSTANCE =
WinApi.RegisterWindowMessage("WM_SHOWFIRSTINSTANCE|{0}", 250);
I have following in the code behind of the window to catch the message and show the window:
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == SingleInstance.WM_SHOWFIRSTINSTANCE)
{
WinApi.ShowToFront(hwnd);
}
return IntPtr.Zero;
}
When I test it out. Whenever the first instance hide in the system tray, the message never caught. Do I miss anything?
Thanks,
Here's how I've done this in the past:
App.xaml.cs:
private static readonly Mutex Mutex = new Mutex(true, "{" + YourGuidHere + "}");
//return true if other instance is open, allowing for UI cleanup/suspension while shutdown() is completed
private bool EnforceSingleInstance()
{
//try...catch provides safety for if the other instance is closed during Mutex wait
try
{
if (!Mutex.WaitOne(TimeSpan.Zero, true))
{
var currentProcess = Process.GetCurrentProcess();
var runningProcess =
Process.GetProcesses().Where(
p =>
p.Id != currentProcess.Id &&
p.ProcessName.Equals(currentProcess.ProcessName, StringComparison.Ordinal)).FirstOrDefault();
if (runningProcess != null)
{
WindowFunctions.RestoreWindow(runningProcess.MainWindowHandle);
Shutdown();
return true;
}
}
Mutex.ReleaseMutex();
}
catch (AbandonedMutexException ex)
{
//do nothing, other instance was closed so we may continue
}
return false;
}
WindowFunctions:
//for enum values, see http://www.pinvoke.net/default.aspx/Enums.WindowsMessages
public enum WM : uint
{
...
SYSCOMMAND = 0x0112,
...
}
//for message explanation, see http://msdn.microsoft.com/en-us/library/windows/desktop/ms646360(v=vs.85).aspx
private const int SC_RESTORE = 0xF120;
public static void RestoreWindow(IntPtr hWnd)
{
if (hWnd.Equals(0))
return;
SendMessage(hWnd,
(uint)WM.SYSCOMMAND,
SC_RESTORE,
0);
}
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd,
uint msg,
uint wParam,
uint lParam);

Making a WPF system context menu item toggleable

I have the following code, which adds an 'Always on Top' item to the system context menu as displayed on the window chrome. It works correctly, but I'd like it to display a check mark or similar to indicate if it's been toggled on/off.
Any idea how I can do this?
public RibbonShell()
{
InitializeComponent();
Loaded += (s,e) =>
{
// Get the Handle for the Forms System Menu
var systemMenuHandle = GetSystemMenu(Handle, false);
// Create our new System Menu items just before the Close menu item
InsertMenu(systemMenuHandle, 5, MfByposition | MfSeparator, 0, string.Empty); // <-- Add a menu seperator
InsertMenu(systemMenuHandle, 6, MfByposition, SettingsSysMenuId, "Always on Top");
// Attach our WindowCommandHandler handler to this Window
var source = HwndSource.FromHwnd(Handle);
source.AddHook(WindowCommandHandler);
};
}
#region Win32 API Stuff
// Define the Win32 API methods we are going to use
[DllImport("user32.dll")]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
private static extern bool InsertMenu(IntPtr hMenu, Int32 wPosition, Int32 wFlags, Int32 wIDNewItem, string lpNewItem);
/// Define our Constants we will use
private const int WmSyscommand = 0x112;
private const int MfSeparator = 0x800;
private const int MfByposition = 0x400;
#endregion
// The constants we'll use to identify our custom system menu items
private const int SettingsSysMenuId = 1000;
/// <summary>
/// This is the Win32 Interop Handle for this Window
/// </summary>
public IntPtr Handle
{
get { return new WindowInteropHelper(this).Handle; }
}
private IntPtr WindowCommandHandler(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Check if a System Command has been executed
if (msg == WmSyscommand && wParam.ToInt32() == SettingsSysMenuId)
{
Topmost = !Topmost;
handled = true;
}
return IntPtr.Zero;
}
You need to call CheckMenuItem whenever you change Topmost. See the CheckMenuItem documentaton for details. Here's the P/Invoke signature and constants you'll need:
[DllImport("user32.dll")]
private static extern bool CheckMenuItem(IntPtr hMenu, Int32 uIDCheckItem, Int32 uCheck);
private const int MfChecked = 8;
private const int MfUnchecked = 0;
Now to check the item, just:
CheckMenuItem(systemMenuHandle, SettingsSysMenuId, MfChecked);
and to uncheck:
CheckMenuItem(systemMenuHandle, SettingsSysMenuId, MfUnchecked);

Resources