WPF Change mouse cursor (VB.NET)
Introduction
There are times when it's beneficial to change the mouse cursor to alert users of long operations or simply when the current selection of mouse cursors are insufficient.
Change cursor basic
The following class provides two methods, one will change the mouse cursor to wait cursor for an indicated amount of milliseconds, in this case the default of 5000 which is five seconds while the second method the cursor type is passed in.
Class
Imports System.Threading
Public Class CursorHelper
''' <summary>
''' Change mouse cursor to wait for five seconds.
''' </summary>
Public Shared Sub ChangeToWait(Optional mlliSeconds As Integer = 5000)
Task.Factory.StartNew(
Sub()
Windows.Application.Current.Dispatcher.Invoke(Sub() Mouse.OverrideCursor = Cursors.Wait)
Try
Thread.Sleep(mlliSeconds)
Finally
Windows.Application.Current.Dispatcher.Invoke(Sub() Mouse.OverrideCursor = Nothing)
End Try
End Sub)
End Sub
Public Shared Sub ChangeTo(cursor As Cursor, Optional mlliSeconds As Integer = 5000)
Task.Factory.StartNew(
Sub()
Windows.Application.Current.Dispatcher.Invoke(Sub() Mouse.OverrideCursor = cursor)
Try
Thread.Sleep(mlliSeconds)
Finally
Windows.Application.Current.Dispatcher.Invoke(Sub() Mouse.OverrideCursor = Nothing)
End Try
End Sub)
End Sub
End Class
Usage
Class MainWindow
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
CursorHelper.ChangeTo(Cursors.No)
End Sub
End Class
Drag/Drop operation
To handle changing the mouse cursor during drag-n-Drop operations, in this case between two labels or between a label and dropping files in from file explorer a cursor is created in GiveFeedback event of the destination label.
To create a cursor in this case, add a suitable bitmap as a resource to a project, in the above screen shot a document image is used which can be accessed via MyResources.Dynamic where Dynamic is the resource name.
The following function takes the bitmap from resources and creates a cursor.
''' <summary>
''' Use bitmap image from project resources.
''' </summary>
''' <returns></returns>
Public Shared Function CreateDropLabelCursorFromImage() As Cursor
Dim iconInfo As New NativeMethods.IconInfo()
'
' Here we read an image from project resources.
'
NativeMethods.GetIconInfo(My.Resources.Dynamic.GetHicon(), iconInfo)
iconInfo.xHotspot = 0
iconInfo.yHotspot = 0
iconInfo.fIcon = False
Dim cursorHandle As SafeIconHandle = NativeMethods.CreateIconIndirect(iconInfo)
Return CursorInteropHelper.Create(cursorHandle)
End Function
In the window which will present the new cursor, a private variable of type cursor is used, if when GiveFeedback event is triggered on a copy operation and it's not create it's created and if created already simply used.
Full source for cursor operations
Add the following class to a project
- In the method CreateDropLabelCursorFromImage change My.Resources.Dynamic to a bitmap resource in the current project.
- Optionally pass the resource into CreateDropLabelCursorFromImage.
Cursor class
Option Infer On
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Security.Permissions
Imports System.Windows.Interop
Imports Microsoft.Win32.SafeHandles
Public Class CursorHelper
Private NotInheritable Class NativeMethods
Public Structure IconInfo
Public fIcon As Boolean
Public xHotspot As Integer
Public yHotspot As Integer
Public hbmMask As IntPtr
Public hbmColor As IntPtr
End Structure
Private Sub New()
End Sub
<DllImport("user32.dll")>
Public Shared Function CreateIconIndirect(ByRef icon As IconInfo) As SafeIconHandle
End Function
<DllImport("user32.dll")>
Public Shared Function DestroyIcon(hIcon As IntPtr) As Boolean
End Function
<DllImport("user32.dll")>
Public Shared Function GetIconInfo(hIcon As IntPtr, ByRef pIconInfo As IconInfo) _
As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
End Class
<SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode:=True)>
Private Class SafeIconHandle
Inherits SafeHandleZeroOrMinusOneIsInvalid
Public Sub New()
MyBase.New(True)
End Sub
Protected Overrides Function ReleaseHandle() As Boolean
Return NativeMethods.DestroyIcon(handle)
End Function
End Class
Private Shared Function InternalCreateCursor(bmp As Bitmap) As Cursor
Dim iconInfo = New NativeMethods.IconInfo()
NativeMethods.GetIconInfo(bmp.GetHicon(), iconInfo)
iconInfo.xHotspot = 0
iconInfo.yHotspot = 0
iconInfo.fIcon = False
Dim cursorHandle As SafeIconHandle = NativeMethods.CreateIconIndirect(iconInfo)
Return CursorInteropHelper.Create(cursorHandle)
End Function
''' <summary>
''' Use bitmap image from project resources.
''' </summary>
''' <returns></returns>
Public Shared Function CreateDropLabelCursorFromImage() As Cursor
Dim iconInfo As New NativeMethods.IconInfo()
'
' Here we read an image from project resources.
'
NativeMethods.GetIconInfo(My.Resources.Dynamic.GetHicon(), iconInfo)
iconInfo.xHotspot = 0
iconInfo.yHotspot = 0
iconInfo.fIcon = False
Dim cursorHandle As SafeIconHandle = NativeMethods.CreateIconIndirect(iconInfo)
Return CursorInteropHelper.Create(cursorHandle)
End Function
End Class
Window xaml
<Window
x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ChangeCursorDragDrop"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Drop-Drop"
Width="332"
Height="244.66"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Grid>
<StackPanel
Width="216"
Margin="55,45"
HorizontalAlignment="Center"
Orientation="Vertical">
<Label
Margin="10"
Padding="15,10"
HorizontalContentAlignment="Center"
Background="AliceBlue"
Content="Data to drag"
GiveFeedback="Label_GiveFeedback"
MouseLeftButtonDown="Label_MouseLeftButtonDown" />
<Label
Margin="10"
Padding="15,10"
HorizontalContentAlignment="Center"
AllowDrop="True"
Background="Khaki"
BorderThickness="5,10,5,10"
Content="Drag to here"
Drop="Label_Drop" />
</StackPanel>
</Grid>
</Window>
Window code behind
- Line 10, start drag operation where CType(e.Source,Label).Content gets the current text of the label the operation starts on
- Line 28 checks if the drop operation can as a file(s) drop from Windows Explorer while line 38 handles text which if the user started the operation on the top label.
- Lines 33 and 34 lead to methods to show which file(s) were dropped onto the label.
- Line 46 handles the cursor change.
01.Imports System.Collections.Specialized
02.Imports ChangeCursorDragDrop.Classes
03.
04.Class MainWindow
05. ''' <summary>
06. ''' Start drag operation
07. ''' </summary>
08. ''' <param name="sender"></param>
09. ''' <param name="e"></param>
10. Private Sub Label_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs)
11.
12. '
13. ' CType(e.Source, Label).Content has the text for this label
14. '
15. Dim data As New DataObject(DataFormats.Text, CType(e.Source, Label).Content)
16.
17. DragDrop.DoDragDrop(CType(e.Source, DependencyObject), data, DragDropEffects.Copy)
18.
19. End Sub
20. ''' <summary>
21. ''' End drag operation, determine if the drop has files or text
22. ''' </summary>
23. ''' <param name="sender"></param>
24. ''' <param name="e"></param>
25. Private Sub Label_Drop(sender As Object, e As DragEventArgs)
26.
27.
28. If e.Data.GetDataPresent(DataFormats.FileDrop) Then
29.
30. Dim dataObject = CType(e.Data, DataObject)
31.
32. Dim fileNames As StringCollection = dataObject.GetFileDropList()
33. Operations.IterateFiles(fileNames)
34. 'Operations.Inspect(fileNames)
35.
36. CType(e.Source, Label).Content = $"Count: {fileNames.Count}"
37.
38. ElseIf e.Data.GetDataPresent(DataFormats.Text) Then
39. CType(e.Source, Label).Content = CStr(e.Data.GetData(DataFormats.Text))
40. End If
41.
42. End Sub
43.
44. Private _customCursor As Cursor = Nothing
45.
46. Private Sub Label_GiveFeedback(sender As Object, e As GiveFeedbackEventArgs)
47.
48. If e.Effects = DragDropEffects.Copy Then
49.
50. '
51. ' Create cursor if not created yet (first time)
52. '
53. If _customCursor Is Nothing Then
54.
55. _customCursor = CursorHelper.CreateDropLabelCursorFromImage()
56.
57. End If
58.
59. '
60. ' Set cursor
61. '
62. If _customCursor IsNot Nothing Then
63. e.UseDefaultCursors = False
64. Mouse.SetCursor(_customCursor)
65. End If
66.
67. Else
68. e.UseDefaultCursors = True
69. End If
70.
71. e.Handled = True
72.
73. End Sub
74.
75.End Class
Summary
Steps and code has been provided to show the very basics to change a mouse cursor for a WPF window. There are many possibilities were this code can be applied but keep in mind changing the cursor should not be done for any other reason then a business requirement.