Virtual Developer Workshop: Containerized Development with Docker
Today, I will show you how to use the simple, yet powerful, WndProc command and its associated methods to override various window and mouse functions.
But first, the basics.
WindowProc (or window procedure) is a user-defined callback function that processes messages sent to a window. Here is more information on it:
The Windows API is a set of several hundred functions and subroutines that are located in a set of files called Dynamic Link Libraries (DLLs). You can make a function from the Windows API available to your Visual Basic program by declaring the function to be callable from your program. You then can use the Windows API function as you would any built-in Visual Basic function or a function that you have written yourself.
End users cannot access these functions; however, programmers can access the code written in the DLLs through the API and use this code in their programs. This enables you to use existent code in the DLLs and save you time in the programming development cycle. The advantage of using Windows APIs in your code is that they can save development time because they contain dozens of useful functions that are already written and waiting to be used. The disadvantage is that Windows APIs can be difficult to work with and unforgiving when things go wrong.
Read here for more information:
Today, I will show you different examples on how to use this feature productively.
Example 1: Disabling Right Click
The purpose of this project is to show you how to override a standard right-click event on a form to do something completely different.
Create a new Visual Basic Windows Forms project. You do not need to add any controls to it apart from a ContextMenuStrip, which is shown in Figure 1. The ContextMenuStrip deals with a user right-clicking at an appropriate place. A standard context menu is shown in Figure 2.
Figure 1: ContextMenuStrip
Figure 2: Standard Context menu
Surprisingly, there isn't much code needed to subclass your window, or mouse handles for it to be overridden, as you will see now in the following code demonstration.
Add the Right button system constant:
'Right Button Up API Constant Public Const WM_RBUTTONUP = &H205
Add the Override method:
'Override Main Window Protected Overrides Sub WndProc(ByRef m As Message) 'If Message Sent = Right Button Up If m.Msg = WM_RBUTTONUP Then 'Just To Show How You Can Manipulate This :) MessageBox.Show("Caught Event") 'Do Nothing Further. IOW, Do Not Show 'Context Menu Exit Sub End If MyBase.WndProc(m) End Sub
Overrides means that this sub will replace the standard function. In this case, we are overriding the WM_RBUTTONUP system message which is the action of the right mouse button being clicked. In the IF structure, I determined whether the message being sent is the right mouse button. If it is the right mouse button being pressed, I included a Messagebox that gives a dull message.
In order to put this to the test, add the following code:
'Added A ContextMenuStrip To The Form, With Some Items Private Sub Test1ToolStripMenuItem_Click(ByVal sender _ As System.Object, ByVal e As System.EventArgs) _ Handles Test1ToolStripMenuItem.Click 'This Will Never Be Displayed :) MessageBox.Show("I Will Not Show") End Sub
The menu items will not show when the right button is clicked, so the preceding code will never get the chance to be executed.
Example 2: Disable Mouse Scroll
You actually could achieve this with two ways. Both are quite easy; it is just the messages that differ at the end of the day.
You could add the following System constants:
'Middle Button Down Private Const WM_MBUTTONDOWN As Int32 = &H207 'Middle Button Up Private Const WM_MBUTTONUP As Int32 = &H208
And then catch either event in the WndProc procedure:
'Override Main Window Protected Overrides Sub WndProc(ByRef m As Message) 'If Message Sent = Right Button Up If m.Msg = WM_MBUTTONUP Then 'Just To Show How You Can Manipulate This :) MessageBox.Show("Caught Event") 'Do Nothing Further. IOW, Do Not Show 'Context Menu Exit Sub End If MyBase.WndProc(m) End Sub
As you can see, it resembles the code in Example 1 quite closely.
You could also do it like this:
Private Const WM_MOUSEWHEEL As Integer = &H20A Protected Overrides Sub WndProc(ByRef m As _ System.Windows.Forms.Message) If Not m.Msg = WM_MOUSEWHEEL Then _ MyBase.WndProc(m) End Sub
Here you make use of the WM_MOUSEWHEEL constant and check for its associated message.
Example 3: Disable System Menus (Titlebar Buttons)
This example is a bit more complicated. With this example, you will learn how to override the Titlebar buttons.
You have to keep one thing in mind here, and that is that the close, maximize, and minimize buttons on a window's titlebar are simply just small little windows. For that reason, you have to get a handle to each of those tiny windows via the GetWindowLong and SetWindowLong APIs. To make them do what you want them to do, you need to use the CallWindowProc API function. Here is the full code of a Module containing all the necessary code:
Module Module1 'Retrieves information about the specified window Declare Function GetWindowLong Lib "user32.dll" Alias _ "GetWindowLongA" (ByVal hwnd As Int32, _ ByVal nIndex As Int32) As Int32 'Passes message information to the specified window 'procedure Declare Function CallWindowProc Lib "user32.dll" Alias _ "CallWindowProcA" (ByVal lpPrevWndFunc As Int32, _ ByVal hwnd As Int32, ByVal msg As Int32, _ ByVal wParam As Int32, ByVal lParam As Int32) As Int32 'Changes an attribute of the specified window Declare Function SetWindowLong Lib "USER32.DLL" Alias _ "SetWindowLongA" (ByVal hwnd As Integer, _ ByVal attr As Integer, _ ByVal lVal As SubClassProcDelegate) As Integer ' Sets a new address for the window procedure Const GWL_WNDPROC As Short = (-4) 'This message is sent to a window when the user chooses 'a command from the window menu, formerly known as the 'system or control menu, or when the user chooses the 'maximize button or the minimize button. Private Const WM_SYSCOMMAND As Short = &H112S Public WndProcOld As Int32 'Old Window Procedure 'All messages are sent to the WndProc method after 'getting filtered through the PreProcessMessage method Public Function WindProc(ByVal hwnd As Int32, _ ByVal wMsg As Int32, ByVal wParam As Int32, _ ByVal lParam As Int32) As Int32 'Do nothing when any system buttons are pressed If wMsg = WM_SYSCOMMAND Then Exit Function 'Override old Window method WindProc = CallWindowProc(WndProcOld, hwnd, wMsg, _ wParam, lParam) End Function 'Function to override normal activity Delegate Function SubClassProcDelegate(ByVal hwnd As Integer, _ ByVal msg As Integer, ByVal wParam As Integer, _ ByVal lParam As Integer) As Integer 'Subclass ( override ) procedure Sub SubClassWindow(ByVal hwnd As Integer) If WndProcOld = 0 Then 'Replace function WndProcOld = SetWindowLong(hwnd, GWL_WNDPROC, _ AddressOf WindProc) End If End Sub End Module
Inside the Form, you simply can do the following for your system windows to be overridden:
Private Sub cmdQuit_Click(ByVal eventSender As System.Object, _ ByVal eventArgs As System.EventArgs) Handles cmdquit.Click Me.Close() End Sub Private Sub Form1_Load(ByVal eventSender As System.Object, _ ByVal eventArgs As System.EventArgs) Handles MyBase.Load SubClassWindow(Handle.ToInt32) 'Override System Menu End Sub
Note that you will have to provide another way to exit the window, as the Close button will not work as it should; hence, the code for a button named cmdQuit.
I hope you have enjoyed this introduction to using WndProc inside your programs. Until we meet again.