Wine Too
Wine Too

Reputation: 4655

MouseWheel event on NumericUpDown

I just find a way to resize form (winforms) based on font size of form.
For that I make routine in form's MouseWheel event handler:

Private Sub myfrm_MouseWheel(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseWheel

     If controlpressed Then
        fs = Me.Font.Size
        If e.Delta < 0 Then
            If fs < 16 Then fs += 1
        Else
            If fs > 8 Then fs -= 1
        End If
        Me.Font = New Font("Arial", fs, FontStyle.Regular, GraphicsUnit.Point)
    End If

That resizes my form with MouseWheel if control key is pressed, similar like browsers behave.
This is wery good in cases when user don't see good and have high monitor resolution.

But problem is with NumericUpDown control. They catch mousewheel event from form and resizing don't happen like with textboxes. I set "increment" property to 0 but that don't helps.

Is here any way to tell NumericUpDown control that they don't catch mousewheel events at way that I don't need to do some special code in mousewheel handler for every NumericUpDown control on the form?

EDIT: Based on Cody Gray's brief tutorial I give finished code for apply "zoom" functionality for winforms.

At startup form's _Load event handler:

Application.AddMessageFilter(New MouseWheelMessageFilter())

Class:

Public Class MouseWheelMessageFilter
Implements IMessageFilter
Public Function PreFilterMessage(ByRef m As Message) As Boolean Implements IMessageFilter.PreFilterMessage
    Const WM_MOUSEWHEEL As Integer = &H20A
    If m.Msg = WM_MOUSEWHEEL And My.Computer.Keyboard.CtrlKeyDown Then
        If Form.ActiveForm IsNot Nothing Then
            Try 'this solves too fast wheeling
                Dim delta As Integer = m.WParam.ToInt32() >> 16
                Dim fs As Single = Form.ActiveForm.Font.Size
                If delta < 0 Then
                    If fs < 16 Then fs += CSng(0.25)
                Else
                    If fs > 8 Then fs -= CSng(0.25)
                End If
                Form.ActiveForm.Font = New Font("Microsoft Sans Serif", fs!, FontStyle.Regular, GraphicsUnit.Point)
            Catch
            End Try
        End If
        Return True
    End If
    Return False
End Function
End Class

That will work for all forms in project. Unexpectedly nice and useful!

Note, for full zooming efect when resize textboxes and so it is needed to open form.Designer file and delete ".font" property for each control. Then font will be inherited from form.

Upvotes: 2

Views: 2667

Answers (3)

Hans Passant
Hans Passant

Reputation: 941455

You have a much bigger problem, it isn't just NUD that grabs the MouseWheel event. The way the Windows message gets handled is unusual and unintuitive to many users:

  • Windows sends the WM_MOUSEWHEEL message to the control that has the focus, regardless of where the mouse cursor is located. This stumps many users, and programmers alike, they are used to the way wheeling works on a browser or a program like Word, programs that don't use any controls. Programs where it doesn't matter where the mouse is located, wheeling always scrolls the page.

  • Next unusual thing that happens is that the message bubbles, if the control that has the focus doesn't handle the message then it is passed to its parent. If the parent doesn't handle it then it gets passed to the parent's parent. Etcetera. Which is why your form's MouseWheel event fires, even though the form never has the focus. Message bubbling is pretty unusual on Windows and thus in Winforms. But not unusual in browsers or a GUI class library like WPF.

So this explains why NUD eats the message, it has a use for wheeling. You haven't yet run into the other ones, your program will also misbehave when you put any control on your form that derives from ScrollableControl, like NumericUpDown, Panel, UserControl, SplitContainer, PropertyGrid or ToolStrip. Or when a control that naturally has a use for scrolling, like TreeView, ListView, TrackBar, RichTextBox, a multi-line TextBox.

This is quite fixable in Winforms, at the risk of breaking the expected behavior of all those other controls, you can intercept the WM_MOUSEWHEEL message before it reaches the control with the focus and pass it to your form instead. You do so by implementing IMessageFilter in your form. You'll find sample code in my answer here.


UPDATE: beware of new behavior in Windows 10, it now has the “Scroll inactive windows when I hover over them” option and it is turned on by default. Which causes the mouse wheel messages to be sent to the control that's being hovered instead of the one that has the focus. Few users are going to turn that off, it makes using the mouse wheel a lot more intuitive.

Upvotes: 3

Cody Gray
Cody Gray

Reputation: 244732

Your problem is similar to the one that this guy is having, namely that you have a child control (the NumericUpDown control) on your form that is stealing event notifications because it is currently focus. Thus, the parent (your Form) is not getting those event notifications because the child control is handling them and not forwarding them on to its parent. If that didn't make complete sense to you, I strongly recommend reading my answer to his question until you feel like you understand what exactly is happening here. It's important to understand that all Windows programming is event-driven, that event notifications are delivered to the focused window/control, and that only one window/control can have the focus at a time.

The solution to your problem is also the same as the one I proposed to the other guy. You need to move your handling of the MouseWheel event notification up to a higher level in order to ensure that it is not "stolen" and handled by the currently focused control.

You have things quite a bit easier than him, though, because you're using WinForms instead of raw C++. WinForms wraps all of the messy Win32 stuff up nicely in an object-oriented API. What you're looking for is called a message filter, and is implemented in WinForms in terms of the IMessageFilter interface. You add a message filter by calling the Application.AddMessageFilter function and specifying your message filtering class (which implements the IMessageFilter interface). In your message filtering class, you will implement the IMessageFilter.PreFilterMessage function, which is the function that decides whether the message should be passed on to the specific control to which it is destined. What you'll do is check to see if the message is WM_MOUSEWHEEL, and if it is, handle it yourself instead of letting it get passed on. That will prevent all child controls (not just NumericUpDown controls) from handling MouseWheel events and ensure a consistent behavior for this event throughout your application.

A wee bit o' sample code:

Public Class MouseWheelMessageFilter : Implements IMessageFilter
    Public Function PreFilterMessage(ByRef m As Message) As Boolean
      ' Filter out WM_MOUSEWHEEL messages, which raise the MouseWheel event,
      ' whenever the Ctrl key is pressed. Otherwise, let them through.
      Const WM_MOUSEWHEEL As Integer = &H20A
      If m.Msg = WM_MOUSEWHEEL && My.Computer.Keyboard.CtrlKeyDown Then
         ' Process the message here.
         If Form.ActiveForm IsNot Nothing Then
            ' TODO: Insert your code here to adjust the size of the active form.
            ' As shown above in the If statement, you can retrieve the form that
            ' is currently active using the static Form.ActiveForm property.
            ' ...
         End If
         Return True  ' swallow this particular message
      End If
      Return False    ' but let all other messages through
   End Function
End Class

You will also need to ensure that you add the call to Application.AddMessageFilter to your app's Main function, before you display the first form:

Application.AddMessageFilter(New MouseWheelMessageFilter())

(Yes, there are also other hacky solutions that involve modifying the way that your NumericUpDown controls handle the MouseWheel event. But, as I implied above, such approaches will affect only the NumericUpDown controls, not other controls that might also handle this event and thereby prevent it from bubbling up to your parent form. I think installing a message filter is by far the cleanest solution.)

Upvotes: 2

Marius
Marius

Reputation: 369

The best solution i found for this is a little project in codeproject.com. There is also an explaination how to do it. Extended NumbericUpDown Control

I cant think of a global solution, since you cannot cancel the mouseevent in early stages (you would be unable to click a button, since you canceled the event). And only disable the mouseWheel part of the mouse event seems also to be nearly impossible.

Upvotes: 1

Related Questions