다음을 통해 공유


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.

Source code

See also