How to handle WM_MOUSEHWHEEL the way Explorer does it? - c#

I have Logitech M705 mouse with a scroll wheel that allows for horizontal scrolling. I have successfully implemented a handler for this button event in my C# program (implemented as described here), but so far I can only get it to scroll once. In Explorer, when I press the wheel to the right, it scrolls to the right continuously until I release the wheel. In my program, it scrolls only one step. The WM_MOUSEHWHEEL message is not seen until I release and press the wheel again!
Q: How do you implement continuous horizontal scrolling for the WM_MOUSEHWHEEL message?

Add this to all controls (form, children etc):
protected override void WndProc(ref System.Windows.Forms.Message m)
{
base.WndProc(ref m);
const int WM_MOUSEHWHEEL = 0x020E;
if (m.Msg == WM_MOUSEHWHEEL)
{
m.Result = new IntPtr(HIWORD(m.WParam) / WHEEL_DELTA);
}
}
The key is to return a nonzero value for all controls that might process the message!

Use IMessageFilter
public partial class MyForm: Form, IMessageFilter
...
public ImageForm(Image initialImage)
{
InitializeComponent();
Application.AddMessageFilter(this);
}
/// <summary>
/// Filters out a message before it is dispatched.
/// </summary>
/// <returns>
/// true to filter the message and stop it from being dispatched; false to allow the message to continue to the next filter or control.
/// </returns>
/// <param name="m">The message to be dispatched. You cannot modify this message. </param><filterpriority>1</filterpriority>
public bool PreFilterMessage( ref Message m )
{
if (m.Msg.IsWindowMessage(WindowsMessages.MOUSEWHEEL)) //if (m.Msg == 0x20a)
{ // WM_MOUSEWHEEL, find the control at screen position m.LParam
var pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
var hWnd = WindowFromPoint(pos);
if (hWnd != IntPtr.Zero && hWnd != m.HWnd && FromHandle(hWnd) != null)
{
SendMessage(hWnd, m.Msg, m.WParam, m.LParam);
return true;
}
}
return false;
}
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(Point pt);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
also in form closing add:
Application.RemoveMessageFilter(this);
This will pick up an all windows messages (although only mousewheel is trapped here) - by using the mouseposition to find the control its over, you can then force windows to send the message to that control even if it has no focus.
NOTE: I have used WindowsMessages.MOUSEWHEEL which is from a class I have that enumerates the messages, just replace
if (m.Msg.IsWindowMessage(WindowsMessages.MOUSEWHEEL))
with the commented bit at the end
if (m.Msg == 0x20a)

Related

Disabling maximizing form in C# [duplicate]

I'm annoyed that the I'm promised a fixed window that the user can't resize, but then of course they're allowed to double click the title bar to maximize this 'unresizable' window. How can I turn this off? Can I do it with winforms code, or must I go down to Win32?
Thanks!
You could set the MaximizeBox property of the form to false
You can disable the double-click message on a title bar in general (or change the default behavior which is maximizing the window). it works on any FormBorderStyle:
private const int WM_NCLBUTTONDBLCLK = 0x00A3; //double click on a title bar a.k.a. non-client area of the form
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCLBUTTONDBLCLK)
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
MSDN Source
Cheers!
///
/// This is we are overriding base WIN32 window procedure to prevent the form from being moved by the mouse as well as resized by the mouse double click.
///
///
protected override void WndProc(ref Message m)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_MOVE = 0xF010;
const int WM_NCLBUTTONDBLCLK = 0x00A3; //double click on a title bar a.k.a. non-client area of the form
switch (m.Msg)
{
case WM_SYSCOMMAND: //preventing the form from being moved by the mouse.
int command = m.WParam.ToInt32() & 0xfff0;
if (command == SC_MOVE)
return;
break;
}
if(m.Msg== WM_NCLBUTTONDBLCLK) //preventing the form being resized by the mouse double click on the title bar.
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
I know I'm late to the party, May help someone who is searching for the same.
private const int WM_NCLBUTTONDBLCLK = 0x00A3; //double click on a title bar a.k.a. non-client area of the form
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WM_NCLBUTTONDBLCLK: //preventing the form being resized by the mouse double click on the title bar.
handled = true;
break;
default:
break;
}
return IntPtr.Zero;
}
I just checked it in VB.Net. Below code worked for me.
Private Const Win_FormTitleDoubleClick As Integer = 163
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = Win_FormTitleDoubleClick Then
m.Result = IntPtr.Zero
Return
End If
MyBase.WndProc(m)
End Sub
Note: 163 is the event code
I approached the problem slightly differently. First I removed the minimize and maximize options from the control box via the MaximizeBox and MinimizeBox properties as you'd expect.
Then I added the following OnResizeEnd() event and attached it to the Form's ResizeEnd event handler:
/// <summary>Locks the form to fill the screen that it's placed on and remain in that state as long as it's open.</summary>
private void GuiMain_ResizeEnd( object sender, EventArgs e )
{
this.Location = Screen.WorkingArea.Location;
this.Size = Screen.WorkingArea.Size;
this.MaximizedBounds = Screen.WorkingArea;
this.MinimumSize = Screen.WorkingArea.Size;
this.WindowState = FormWindowState.Normal;
}
This solution necessarily relies on the existence of the following accessor which you can copy, or you can simply replace each instance of Screen. in the above with Screen.FromHandle( this.Handle ).
protected Screen Screen => Screen.FromHandle( this.Handle );
Obviously, if ironically, this actually keeps the form in the FormWindowState.Normal state, but it mimics the effect of maximization, and resets this fullscreen state after any attempt to change it.
Interestingly, due to the use of the Screen.FromHandle() settings (as opposed to hard-coded ones), you can actually drag the form from one display to another, whereupon it immediately "snaps" to fill THAT screen instead. Something I found quite handy, but which may require additional code to correct if you don't want that functionality in your application.

How do I redirect mouse wheel messages from one window to another?

WM_MOUSEWHEEL messages are sent to the control with the focus. My application has a complex control hierarchy, with controls containing other controls, some of which are invisible or overlapping. I would like the mouse wheel to scroll a specific ScrollableControl.
This question has an answer with a IMessageFilter implementation that catches WM_MOUSEWHEEL messages. This works well and I see the messages being caught. I tried manipulating ScrollableControl's VerticalScroll property to scroll its contents, by changing the value of VerticalScroll.Value. Unfortunately, there are some undesirable side effects like the mouse thumb in the scrollbar becoming unsynchronized with the ScrollableControl's contents. Perhaps this is because this work is being done inside the message pump instead of in an event handler.
This post describes a technique where WM_MOUSEWHEEL messages are reposted to another window. I would like to implement a IMessageFilter that catches WM_MOUSEWHEEL messages, and forwards them to a designated recipient.
I create the following IMessageFilter that tries to do this. I can see the forwarded message being caught by my filter, and I return false from the filter to tell the control to handle the message. The target control does not receive an OnMouseWheel event.
Can this filter be modified to to allow my targetControl to be scrolled using redirected messages?
public static class MouseWheelMessageRedirector
{
public static void Add(Control rootControl, ScrollableControl targetControl)
{
var filter = new MouseWheelMessageFilter(rootControl, targetControl);
Application.AddMessageFilter(filter);
rootControl.Disposed += (sender, args) => Application.RemoveMessageFilter(filter);
targetControl.MouseWheel += (sender, args) =>
{
// ... this code never executes
System.Diagnostics.Trace.WriteLine("WHEEL ON TARGET");
};
}
private class MouseWheelMessageFilter : IMessageFilter
{
const int WM_MOUSEWHEEL = 0x020A;
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
public MouseWheelMessageFilter(Control rootControl, ScrollableControl targetControl)
{
_rootControl = rootControl;
_targetControl = targetControl;
_targetWindowHandle = _targetControl.Handle;
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg != WM_MOUSEWHEEL)
return false;
if (m.HWnd == _targetWindowHandle)
return false;
// ... get the control that the mouse is over
// ... determine if this is a control that we want to handle the message for
// ... (omitted)
PostMessage(_targetWindowHandle, m.Msg, m.WParam, m.LParam);
return true;
}
private Control _rootControl;
private ScrollableControl _targetControl;
private IntPtr _targetWindowHandle;
}
}
I just did this same thing. Here's what I did:
public bool PreFilterMessage(ref Message m)
{
if ((WM)m.Msg == WM.MOUSEWHEEL)
{
// if mouse is over a certain component, prevent scrolling
if (comboBoxVendors.Bounds.Contains(PointToClient(Cursor.Position)))
{
// return true which says the message is already processed
return true;
}
// which direction did they scroll?
int delta = 0;
if ((long)m.WParam >= (long)Int32.MaxValue)
{
var wParam = new IntPtr((long)m.WParam << 32 >> 32);
delta = wParam.ToInt32() >> 16;
}
else
{
delta = m.WParam.ToInt32() >> 16;
}
delta = delta*-1;
var direction = delta > 0 ? 1 : 0;
// post message to the control I want scrolled (I am converting the vertical scroll to a horizontal, bu you could just re-send the same message to the control you wanted
PostMessage(splitContainerWorkArea.Panel2.Handle, Convert.ToInt32(WM.HSCROLL), (IntPtr) direction, IntPtr.Zero);
// return true to say that I handled the message
return true;
}
// message was something other than scroll, so ignore it
return false;
}
Also, I used the windows message enum from PInvoke.net:
http://www.pinvoke.net/default.aspx/Enums.WindowsMessages
Finally, be sure to remove the message filter when you close the form, or when you no longer need to process the messages:
Application.RemoveMessageFilter(thisForm)

Make MouseWheel event pass from child to parent

I have a Panel where AutoScroll is true. This panel contains many smaller panels that fill all available space like tiles. When there are too many sub-panels to display I get the vertical scroll bar as expected.
Each of these "tiles" have some event handlers tied to them to handle MouseDown / MouseUp / MouseMove as they can be dragged around.
The problem I'm having is that mouse wheel scroll doesn't work on the parent panel as it won't have focus. I can't give it focus because in all probability I'll be scrolling while moving a sub-panel which will have the focus instead, and even then that would require workarounds since panels don't like focus.
I've been trying (and failing) to find a way to propagate only mousewheel events from the children to the parent.
I've read that in Winforms if a control cannot handle a mouse event it will bubble it up to the parent of that control, and then to the parent of that control and so on until it finds a suitable handler.
With this in mind I figured the best solution would be to use WndProc to override all scroll related events on the sub-panel and pass them to the parent while leaving all other events intact but admittedly this isn't my strong suit and I'm lost.
I've tried a few other solutions such as making the sub-panels invisible to all mouse events but as you might have guessed this was bad. I've read about implementing a message filter but didn't understand it.
Here's the code that will give you a very basic example of the panel and its children:
private void Form1_Load(object sender, EventArgs e)
{
Height = 600;
Width = 300;
Color[] colors = new Color[]{ Color.PowderBlue, Color.PeachPuff };
Panel panel = new Panel()
{
Height = this.ClientSize.Height - 20,
Width = 200,
Top = 10,
Left = 10,
BackColor = Color.White,
BorderStyle = BorderStyle.FixedSingle,
AutoScroll = true
};
for (int i = 0; i < 10; i++)
{
Panel subPanel = new Panel()
{
Name = #"SubPanel " + i.ToString(),
Height = 100,
Width = panel.Width - System.Windows.Forms.SystemInformation.VerticalScrollBarWidth - 2,
BackColor = colors[i % 2],
Top = i * 100
};
subPanel.MouseClick += subPanel_MouseClick;
panel.Controls.Add(subPanel);
}
Controls.Add(panel);
}
void subPanel_MouseClick(object sender, MouseEventArgs e)
{
Panel panel = sender as Panel;
Text = panel.Name;
}
Here's my attempt at overriding WndProc in a custom panel:
class NoScrollPanel : Panel
{
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int MOUSEWHEEL = 0x020A;
private const int KEYDOWN = 0x0100;
protected override void WndProc(ref Message m)
{
if ((m.HWnd == Handle) && (m.Msg == MOUSEWHEEL || m.Msg == WM_VSCROLL || (m.Msg == KEYDOWN && (m.WParam == (IntPtr)40 || m.WParam == (IntPtr)35))))
{
PostMessage(Parent.Handle, m.Msg, m.WParam, m.LParam);
}
else
{
base.WndProc(ref m);
}
}
[DllImport("User32.dll")]
private static extern IntPtr PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
}
Any help or alternative approaches are most welcome. Thanks!
At my side #a-clymer's solution does not work, perhaps different environment. Currently there is no direct, clear answer for my problem so I try to combine several thoughts of other professionals and succeed.
In my current project, I have a few input controls contained in a Panel. I managed to make the mouse wheel scroll the panel instead of the ComboBox items by creating a child class of ComboBox and override its WndProc:
public class ComboBoxWithParentMouseWheel : ComboBox
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
const int WM_MOUSEWHEEL = 0x020A;
//thanks to a-clymer's solution
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MOUSEWHEEL)
{
//directly send the message to parent without processing it
//according to https://stackoverflow.com/a/19618100
SendMessage(this.Parent.Handle, m.Msg, m.WParam, m.LParam);
m.Result = IntPtr.Zero;
}else base.WndProc(ref m);
}
}
All credit goes to Hans Passant (again), taken from the thread he suggested: https://stackoverflow.com/a/3562449/17034
Allowing the the containing panel to take focus worked fine. For the demo code above the class needs no changes, just use it for only the containing panel. I had to make some tweaks to my project to call focus when necessary, but it was far from rocket science.
Thanks again.
I could not get any of these solutions to work. I am overriding the WndProc fxn just like you. Finally found the solution! I noticed that if the container holding my TextBox was in focus, then the scroll worked, just not when the TextBox was in focus.
I didn't need to change the transparency of the event, I needed to send the event to a different Control Handle! (It seems so simple now, but I've trying to figure this out for days!)
internal class myTextBox : TextBox
{
const int WM_MOUSEWHEEL = 0x020A;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_MOUSEWHEEL)
m.HWnd = this.Parent.Handle; // Change the Handle of the message
base.WndProc(ref m);
}
}
I haven't seen any mention of this method in all my Google searches, if there is some reason NOT to do this I hope someone will reply.
An improved version of #a-clymer version that works better.
const int WM_MOUSEWHEEL = 0x020A;
if (m.Msg == WM_MOUSEWHEEL)
{
// find the first scrollable parent control
Control p = this;
do
{
p = p.Parent;
} while (p != null && !(p is ScrollableControl));
// rewrite the destination handle of the message
if (p != null)
m.HWnd = p.Handle;
}

PostMessage does not seem to be working

I am trying to use PostMessage to send a tab key.
Here is my code:
// This class allows us to send a tab key when the the enter key
// is pressed for the mooseworks mask control.
public class MaskKeyControl : MaskedEdit
{
// [DllImport("coredll.dll", SetLastError = true, CharSet = CharSet.Auto)]
// static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
[return: MarshalAs(UnmanagedType.Bool)]
// I am calling this on a Windows Mobile device so the dll is coredll.dll
[DllImport("coredll.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, Int32 wParam, Int32 lParam);
public const Int32 VK_TAB = 0x09;
public const Int32 WM_KEYDOWN = 0x100;
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyData == Keys.Enter)
{
PostMessage(this.Handle, WM_KEYDOWN, VK_TAB, 0);
return;
}
base.OnKeyDown(e);
}
protected override void OnKeyPress(KeyPressEventArgs e)
{
if (e.KeyChar == '\r')
e.Handled = true;
base.OnKeyPress(e);
}
}
When I press enter the code gets called, but nothing happens. Then I press TAB and it works fine. (So there is something wrong with my sending of the Tab Message.)
You really shouldn't post windows messages related to user input directly to windows controls. Rather, if you want to simulate input, you should rely on the SendInput API function instead to send the key presses.
Also, as Chris Taylor mentions in his comment, the SendKeys class can be used to send key inputs to an application in the event that you want to use an existing managed wrapper (instead of calling SendInput function yourself through the P/Invoke layer).
PostMessage on key events does really really odd things.
In this case, maybe SendMessage with KEYDOWN, KEYPRESS, KEYUP (three calls) might work better.
An alternative to sending input messages to the control, you could be more explicit and do the following.
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
if (Parent != null)
{
Control nextControl = Parent.GetNextControl(this, true);
if (nextControl != null)
{
nextControl.Focus();
return;
}
}
}
base.OnKeyDown(e);
}
This will set the focus to the next control on the parent when the enter key is pressed.

C# ListView mouse wheel scroll without focus

I'm making a WinForms app with a ListView set to detail so that several columns can be displayed.
I'd like for this list to scroll when the mouse is over the control and the user uses the mouse scroll wheel. Right now, scrolling only happens when the ListView has focus.
How can I make the ListView scroll even when it doesn't have focus?
"Simple" and working solution:
public class FormContainingListView : Form, IMessageFilter
{
public FormContainingListView()
{
// ...
Application.AddMessageFilter(this);
}
#region mouse wheel without focus
// P/Invoke declarations
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(Point pt);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == 0x20a)
{
// WM_MOUSEWHEEL, find the control at screen position m.LParam
Point pos = new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16);
IntPtr hWnd = WindowFromPoint(pos);
if (hWnd != IntPtr.Zero && hWnd != m.HWnd && System.Windows.Forms.Control.FromHandle(hWnd) != null)
{
SendMessage(hWnd, m.Msg, m.WParam, m.LParam);
return true;
}
}
return false;
}
#endregion
}
You'll normally only get mouse/keyboard events to a window or control when it has focus. If you want to see them without focus then you're going to have to put in place a lower-level hook.
Here is an example low level mouse hook

Categories

Resources