Как изменить курсор wpf

I want to use an image or icon as a custom cursor in WPF app. How can I do that?

This will convert any image stored in your project to a cursor using an attached property. The image must be compiled as a resource!

Example

<Button MyLibrary:FrameworkElementExtensions.Cursor=""{MyLibrary:Uri MyAssembly, MyImageFolder/MyImage.png}""/>

FrameworkElementExtensions

using System;
using System.Windows;
using System.Windows.Media;

public static class FrameworkElementExtensions
{
    #region Cursor

    public static readonly DependencyProperty CursorProperty = DependencyProperty.RegisterAttached("Cursor", typeof(Uri), typeof(FrameworkElementExtensions), new UIPropertyMetadata(default(Uri), OnCursorChanged));
    public static Uri GetCursor(FrameworkElement i) => (Uri)i.GetValue(CursorProperty);
    public static void SetCursor(FrameworkElement i, Uri input) => i.SetValue(CursorProperty, input);
    static void OnCursorChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender is FrameworkElement frameworkElement)
        {
            if (GetCursor(frameworkElement) != null)
                frameworkElement.Cursor = new ImageSourceConverter().ConvertFromString(((Uri)e.NewValue).OriginalString).As<ImageSource>().Bitmap().Cursor(0, 0).Convert();
        }
    }

    #endregion
}

ImageSourceExtensions

using System.Drawing;
using System.Windows.Media;
using System.Windows.Media.Imaging;

public static class ImageSourceExtensions
{
    public static Bitmap Bitmap(this ImageSource input) => input.As<BitmapSource>().Bitmap();
}

BitmapSourceExtensions

using System.IO;
using System.Windows.Media.Imaging;

public static class BitmapSourceExtensions
{
    public static System.Drawing.Bitmap Bitmap(this BitmapSource input)
    {
        if (input == null)
            return null;

        System.Drawing.Bitmap result;
        using (var outStream = new MemoryStream())
        {
            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(input));
            encoder.Save(outStream);
            result = new System.Drawing.Bitmap(outStream);
        }
        return result;
    }
}

BitmapExtensions

using System;
using System.Drawing;
using System.Runtime.InteropServices;

public static class BitmapExtensions
{

    [StructLayout(LayoutKind.Sequential)]
    public struct ICONINFO
    {
        /// <summary>
        /// Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies an icon; FALSE specifies a cursor. 
        /// </summary>
        public bool fIcon;

        /// <summary>
        /// Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot spot is always in the center of the icon, and this member is ignored.
        /// </summary>
        public Int32 xHotspot;

        /// <summary>
        /// Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot spot is always in the center of the icon, and this member is ignored. 
        /// </summary>
        public Int32 yHotspot;

        /// <summary>
        /// (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, this bitmask is formatted so that the upper half is the icon AND bitmask and the lower half is the icon XOR bitmask. Under this condition, the height should be an even multiple of two. If this structure defines a color icon, this mask only defines the AND bitmask of the icon. 
        /// </summary>
        public IntPtr hbmMask;

        /// <summary>
        /// (HBITMAP) Handle to the icon color bitmap. This member can be optional if this structure defines a black and white icon. The AND bitmask of hbmMask is applied with the SRCAND flag to the destination; subsequently, the color bitmap is applied (using XOR) to the destination by using the SRCINVERT flag. 
        /// </summary>
        public IntPtr hbmColor;
    }

    [DllImport("user32.dll")]
    static extern IntPtr CreateIconIndirect([In] ref ICONINFO piconinfo);

    [DllImport("user32.dll")]
    static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool DestroyIcon(IntPtr hIcon);

    public static System.Windows.Forms.Cursor Cursor(this Bitmap input, int hotX, int hotY)
    {
        ICONINFO Info = new ICONINFO();
        IntPtr Handle = input.GetHicon();
        GetIconInfo(Handle, out Info);

        Info.xHotspot = hotX;
        Info.yHotspot = hotY;
        Info.fIcon = false;

        IntPtr h = CreateIconIndirect(ref Info);
        return new System.Windows.Forms.Cursor(h);
    }
}

CursorExtensions

using Microsoft.Win32.SafeHandles;

public static class CursorExtensions
{
    public static System.Windows.Input.Cursor Convert(this System.Windows.Forms.Cursor Cursor)
    {
        SafeFileHandle h = new SafeFileHandle(Cursor.Handle, false);
        return System.Windows.Interop.CursorInteropHelper.Create(h);
    }
}

As

public static Type As<Type>(this object input) => input is Type ? (Type)input : default;

Uri

using System;
using System.Windows.Markup;

public class Uri : MarkupExtension
{
    public string Assembly { get; set; } = null;

    public string RelativePath { get; set; }

    public Uri(string relativePath) : base()
    {
        RelativePath = relativePath;
    }

    public Uri(string assembly, string relativePath) : this(relativePath)
    {
        Assembly = assembly;
    }

    static Uri Get(string assemblyName, string relativePath) => new Uri($"pack://application:,,,/{assemblyName};component/{relativePath}", UriKind.Absolute);

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (Assembly == null)
            return new System.Uri(RelativePath, UriKind.Relative);

        return Get(Assembly, RelativePath);
    }
}


Table of Contents

  • Introduction
  • Change cursor basic
    • Class
    • Usage
  • Drag/Drop operation
    • Full source for cursor operations
      • Cursor class
      • Window xaml
      • Window code behind
  • Summary
  • Source code
  • See also

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:local="clr-namespace:ChangeCursorDragDrop"

    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

  • Basic code sample
  • Drag-n-Drop sample

See also

  • WPF ListBox data templating/style
  • WPF: Tips — Bind to Current item of Collection
  • WPF Data, Item and Control Templates — Minimum Code, Maximum Awesomeness
  • Different ways to dynamically select DataTemplate for WPF ListView
  • Moving from WinForms to WPF
  • WPF Control Templates

C# WPF Tutorial — How To Use Custom Cursors [Intermediate]

So a while back, I did a tutorial on how to do custom cursors inWinForms. In that
post I said that at some point in the future, I would write a post on
how to do custom cursors in WPF — and here we are! A lot of the code
used today is based off of the code from that previous tutorial, so if
you haven’t read it, I would go do so before you continue.

Sadly, creating and using a custom cursor for WPF is actually more
difficult than for WinForms. This is pretty much all due in the end to a
single problem — WPF does not use GDI, but the cursor still does. Since
the cursor is something at the Windows level (it needs to exist across
all application and work smoothly), it is still GDI, and has all the
benefits and flaws that come with that fact. So we end up needing to
cross that boundary every time you want to do something special with the
cursor in WPF.

But don’t worry! Fortunately, WPF wraps all the standard cursors (so you
can still easily change to, say, a wait cursor), but as soon as you want
to create a special image to use as your cursor, you are on your own.
Well, not really on your own — that is what this tutorial is here for.
And so, in we go. First, we are going to look at the rather small change
to the actual «cursor creation part of the code. If you remember, to
create a cursor for WinForms, you can just say something like this:

IntPtr curPtr;

/* CurPtr gets set to
 a pointer to an icon */

Cursor myCur = new Cursor(curPtr);
 

Sadly, you can’t quite do that in WPF. This is because it is a different
Cursor object we are creating, and Microsoft did not give us a
constructor that takes an IntPtr. For WinForms, we used a
System.Windows.Forms.Cursor, but for WPF, we will be creating a
System.Windows.Input.Cursor. But while there is no constructor for it,
there is still a way to get a Cursor out of an IntPtr. It just happens
to be hidden elsewhere:

IntPtr curPtr;

/* CurPtr gets set to
 a pointer to an icon */

SafeFileHandle handle = new SafeFileHandle(ptr, true);
Cursor myCur = System.Windows.Interop.CursorInteropHelper.Create(handle);

So first we have to convert the IntPtr into a SafeHandle
(SafeFileHandle extends SafeHandle — you can’t just create a
SafeHandle, it is an abstract class). Then we pass that handle into
the nice and easy to find (thats sarcasm in case you can’t tell)
Create method under System.Windows.Interop.CursorInteropHelper, and
we get a Cursor back that we can use with WPF.

So if we bring in the rest of the code from the previous tutorial, we
might get a class that looks like this:

using System;
using System.Windows.Interop;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Windows.Input;

namespace WPFCursorTest
{
  public class CursorHelper
  {
    private struct IconInfo
    {
      public bool fIcon;
      public int xHotspot;
      public int yHotspot;
      public IntPtr hbmMask;
      public IntPtr hbmColor;
    }

    [DllImport("user32.dll")]
    private static extern IntPtr CreateIconIndirect(ref IconInfo icon);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);


    public static Cursor CreateCursor(System.Drawing.Bitmap bmp, int xHotSpot, 
        int yHotSpot)
    {
      IconInfo tmp = new IconInfo();
      GetIconInfo(bmp.GetHicon(), ref tmp);
      tmp.xHotspot = xHotSpot;
      tmp.yHotspot = yHotSpot;
      tmp.fIcon = false;

      IntPtr ptr = CreateIconIndirect(ref tmp);
      SafeFileHandle handle = new SafeFileHandle(ptr, true);
      return CursorInteropHelper.Create(handle);
    }
  }
}
 

One thing you will need to remeber if you use this is that we are
dealing with a System.Drawing.Bitmap here. This is the old GDI type
bitmap object — a concept foreign to the new WPF classes. So you will
probably need to pull in a reference to System.Drawing in your
references section in your Visual Studio project, since WPF projects do
not have this reference by default.

I tried, quite hard, to figure out a way to create a cursor in WPF
without having to lean on System.Drawing.Bitmap. But in the end, I
need to get an Icon pointer out of the bitmap (thats the function
GetHicon), and the WPF bitmap objects refuse to ever give you a
pointer.

But wait, not all hope is lost! I may have to deal with a
System.Drawing.Bitmap, but that does not mean that everyone will need
to. It is possible to wrap this method in another method that can take
in a WPF construct, instead of a GDI one:

public static Cursor CreateCursor(UIElement element, int xHotSpot, int yHotSpot)
{
  element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  element.Arrange(new Rect(0, 0, element.DesiredSize.Width,
      element.DesiredSize.Height));

  RenderTargetBitmap rtb = new RenderTargetBitmap((int)element.DesiredSize.Width, 
      (int)element.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
  rtb.Render(element);

  PngBitmapEncoder encoder = new PngBitmapEncoder();
  encoder.Frames.Add(BitmapFrame.Create(rtb));

  MemoryStream ms = new MemoryStream();
  encoder.Save(ms);

  System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(ms);

  ms.Close();
  ms.Dispose();

  Cursor cur = InternalCreateCursor(bmp, xHotSpot, yHotSpot);

  bmp.Dispose();

  return cur;
}
 

Here, I am taking in a UIElement — a core WPF construct. I measure and
arrage it (to make sure that it is internally rendered properly), and
then I «take a picture of it». I do this by creating a
RenderTargetBitmap. This is a class for WPF thats lets you take a
UIElement and draw it to a bitmap — but remember, this is a WPF bitmap
(a System.Windows.Media.Imaging.RenderTargetBitmap, to be precise). It
is not the same thing as a System.Drawing.Bitmap. So I create my
RenderTargetBitmap to be the correct size, and I give it a dpi of 96
(pretty standard). Then, I render the UIElement on the bitmap.

Ok, now I have a RenderTargetBitmap in my hands, and I want to get to
a System.Drawing.Bitmap. Microsoft could not have possibly made this
more difficult, and I’m really quite annoyed by it. There are a couple
methods that let you go from a System.Drawing.Bitmap to a WPF Bitmap
(they create a System.Windows.Interop.InteropBitmap), but there are no
methods that go in the other direction. Or at least none that I could
find — and I scoured the docs and read through Bitmap code in Reflector
for quite a while. If anyone knows of such a method (or a better way
than the way I am about to describe), please let me know.

I convert from a RenderTargetBitmap to a System.Drawing.Bitmap by
first creating an encoder, and encoding my bitmap as a PNG (you could
pick any encoder you like). I then create a MemoryStream and save my
encoded bitmap to the stream. And now, since the System.Drawing.Bitmap
has a constructor that can take a stream, I can create my new bitmap.
Once that is done, I clean up the memory stream, and proceed to create
the cursor. And, of course, once the cursor is created, I dispose the
System.Drawing.Bitmap that I had created.

So in the end, the code for the whole class looks like this:

using System;
using System.Windows.Interop;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using System.Windows;

namespace WPFCursorTest
{
  public class CursorHelper
  {
    private struct IconInfo
    {
      public bool fIcon;
      public int xHotspot;
      public int yHotspot;
      public IntPtr hbmMask;
      public IntPtr hbmColor;
    }



    [DllImport("user32.dll")]
    private static extern IntPtr CreateIconIndirect(ref IconInfo icon);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo);


    private static Cursor InternalCreateCursor(System.Drawing.Bitmap bmp, 
        int xHotSpot, int yHotSpot)
    {
      IconInfo tmp = new IconInfo();
      GetIconInfo(bmp.GetHicon(), ref tmp);
      tmp.xHotspot = xHotSpot;
      tmp.yHotspot = yHotSpot;
      tmp.fIcon = false;

      IntPtr ptr = CreateIconIndirect(ref tmp);
      SafeFileHandle handle = new SafeFileHandle(ptr, true);
      return CursorInteropHelper.Create(handle);
    }

    public static Cursor CreateCursor(UIElement element, int xHotSpot, int yHotSpot)
    {
      element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
      element.Arrange(new Rect(0, 0, element.DesiredSize.Width,
          element.DesiredSize.Height));

      RenderTargetBitmap rtb = new RenderTargetBitmap((int)element.DesiredSize.Width, 
          (int)element.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
      rtb.Render(element);

      PngBitmapEncoder encoder = new PngBitmapEncoder();
      encoder.Frames.Add(BitmapFrame.Create(rtb));

      MemoryStream ms = new MemoryStream();
      encoder.Save(ms);

      System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(ms);

      ms.Close();
      ms.Dispose();

      Cursor cur = InternalCreateCursor(bmp, xHotSpot, yHotSpot);

      bmp.Dispose();

      return cur;
    }
  }
}
 

Kind of ugly, don’t you think? But at least it is encapsulated.

Now you probably wondering how to use this class. Well, it is really
easy:

public partial class CursorTest : Window
{
  public CursorTest()
  {
    InitializeComponent();

    TextBlock tb = new TextBlock();
    tb.Text = "{ } Switch On The Code";
    tb.FontSize = 10;
    tb.Foreground = Brushes.Green;

    this.Cursor = CursorHelper.CreateCursor(tb, 5, 5);
  }
}
 

Here, I have a window, and I want my cursor to say «{ } Switch On The
Code». So in the constructor, I make a TextBlock with that text, and
just call CreateCursor. And all I need to do is set the Cursor
property of my window to the result. That code would make something that
looks like this:

Example WPF Custom Cursor

I hope this code helps anyone who has been trying to create and use
custom cursors in WPF. If you would like, you can download a Visual
Studio solution
here, which
contains both the CursorHelper class and the sample test window shown
above. And if anyone comes up with a way to get rid of the need for
having to bring in System.Drawing, or figures out how to convert from
a WPF and GDI bitmap easily, let us know!

Source Files:

  • WPFCursorTest.zip
namespace UniverseMaintenanceTool.Helper { using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using Brush = System.Windows.Media.Brush; using Brushes = System.Windows.Media.Brushes; using Pen = System.Windows.Media.Pen; using Size = System.Windows.Size; public static class CustomCursors { public void SetCustomCursor() { Mouse.OverrideCursor = CursorHelper.CreateCursor(Brushes.Gold, new Pen(Brushes.Black, 0.1d), new Size(50, 50)); //Mouse.OverrideCursor = CursorHelper.CreateCursor(Properties.Resources.cursor_move_picture, ImageFormat.Png, 13, 17); } public static class CursorHelper { public static Cursor CreateCursor(Brush brush, Pen pen, Size size) { var vis = new DrawingVisual(); using (var dc = vis.RenderOpen()) { dc.DrawRectangle(brush, pen, new Rect(0, 0, size.Width, size.Height)); dc.Close(); } var rtb = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32); rtb.Render(vis); return CreateCursor(rtb, (int)(size.Width / 2), (int)(size.Height / 2)); } public static Cursor CreateCursor(Image picture, ImageFormat format, int hotspotX = 0, int hotspotY = 0) { var vis = new DrawingVisual(); var imageSource = ConvertBitmap(picture, format); using (var dc = vis.RenderOpen()) { dc.DrawImage(imageSource, new Rect(0, 0, imageSource.Width, imageSource.Height)); dc.Close(); } var rtb = new RenderTargetBitmap((int)imageSource.Width, (int)imageSource.Height, 96, 96, PixelFormats.Pbgra32); rtb.Render(vis); return CreateCursor(rtb, hotspotX, hotspotY); } private static Cursor CreateCursor(BitmapSource bitmapSource, int hotspotX, int hotspotY) { using (var ms1 = new MemoryStream()) { var pngEncoder = new PngBitmapEncoder(); pngEncoder.Frames.Add(BitmapFrame.Create(bitmapSource)); pngEncoder.Save(ms1); var pngBytes = ms1.ToArray(); var size = pngBytes.GetLength(0); using (var ms = new MemoryStream()) { //Reserved must be zero; 2 bytes ms.Write(BitConverter.GetBytes((short)0), 0, 2); //image type 1 = ico 2 = cur; 2 bytes ms.Write(BitConverter.GetBytes((short)2), 0, 2); //number of images; 2 bytes ms.Write(BitConverter.GetBytes((short)1), 0, 2); //image width in pixels ms.WriteByte(32); //image height in pixels ms.WriteByte(32); //Number of Colors in the color palette. Should be 0 if the image doesn’t use a color palette ms.WriteByte(0); //reserved must be 0 ms.WriteByte(0); //2 bytes. In CUR format: Specifies the horizontal coordinates of the hotspot in number of pixels from the left. ms.Write(BitConverter.GetBytes((short)hotspotX), 0, 2); //2 bytes. In CUR format: Specifies the vertical coordinates of the hotspot in number of pixels from the top. ms.Write(BitConverter.GetBytes((short)hotspotY), 0, 2); //Specifies the size of the image’s data in bytes ms.Write(BitConverter.GetBytes(size), 0, 4); //Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file ms.Write(BitConverter.GetBytes(22), 0, 4); ms.Write(pngBytes, 0, size); //write the png data. ms.Seek(0, SeekOrigin.Begin); return new Cursor(ms); } } } private static BitmapImage ConvertBitmap(Image src, ImageFormat format) { var ms = new MemoryStream(); src.Save(ms, format); var image = new BitmapImage(); image.BeginInit(); ms.Seek(0, SeekOrigin.Begin); image.StreamSource = ms; image.EndInit(); return image; } } } }

Like Peter mentioned, if you already have a .cur file, you can use it as an embedded resource by creating a dummy element in the resource section, and then referencing the dummy’s cursor when you need it.

For example, say you wanted to display non-standard cursors depending on the selected tool.

Add to resources:

<Window.Resources>
    <ResourceDictionary>
        <TextBlock x:Key="CursorGrab" Cursor="Resources/Cursors/grab.cur"/>
        <TextBlock x:Key="CursorMagnify" Cursor="Resources/Cursors/magnify.cur"/>
    </ResourceDictionary>
</Window.Resources>

Example of embedded cursor referenced in code:

if (selectedTool == "Hand")
    myCanvas.Cursor = ((TextBlock)this.Resources["CursorGrab"]).Cursor;
else if (selectedTool == "Magnify")
    myCanvas.Cursor = ((TextBlock)this.Resources["CursorMagnify"]).Cursor;
else
    myCanvas.Cursor = Cursor.Arrow;

There is an easier way than managing the cursor display yourself or using Visual Studio to construct lots of custom cursors.

If you have a FrameworkElement you can construct a Cursor from it using the following code:

public Cursor ConvertToCursor(FrameworkElement visual, Point hotSpot)
{
  int width = (int)visual.Width;
  int height = (int)visual.Height;

  // Render to a bitmap
  var bitmapSource = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
  bitmapSource.Render(visual);

  // Convert to System.Drawing.Bitmap
  var pixels = new int[width*height];
  bitmapSource.CopyPixels(pixels, width, 0);
  var bitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
  for(int y=0; y<height; y++)
    for(int x=0; x<width; x++)
      bitmap.SetPixel(x, y, Color.FromArgb(pixels[y*width+x]));

  // Save to .ico format
  var stream = new MemoryStream();
  System.Drawing.Icon.FromHandle(resultBitmap.GetHicon()).Save(stream);

  // Convert saved file into .cur format
  stream.Seek(2, SeekOrigin.Begin);
  stream.WriteByte(2);
  stream.Seek(10, SeekOrigin.Begin);
  stream.WriteByte((byte)(int)(hotSpot.X * width));
  stream.WriteByte((byte)(int)(hotSpot.Y * height));
  stream.Seek(0, SeekOrigin.Begin);

  // Construct Cursor
  return new Cursor(stream);
}

Note that your FrameworkElement ‘s size must be a standard cursor size (eg 16×16 or 32×32), for example:

<Grid x:Name="customCursor" Width="32" Height="32">
  ...
</Grid>

It would be used like this:

someControl.Cursor = ConvertToCursor(customCursor, new Point(0.5, 0.5));

Obviously your FrameworkElement could be an <Image> control if you have an existing image, or you can draw anything you like using WPF’s built-in drawing tools.

Note that details on the .cur file format can be found at ICO (file format).

You have two basic options:

  1. When the mouse cursor is over your control, hide the system cursor by setting this.Cursor = Cursors.None; and draw your own cursor using whatever technique you like. Then, update the position and appearance of your cursor by responding to mouse events. Here are two examples:
  • http://www.xamlog.com/2006/07/17/creating-a-custom-cursor/

  • http://www.hanselman.com/blog/DeveloperDesigner.aspx

    Additional examples can be found here:

  • WPF Tutorial — How To Use Custom Cursors

  • Setting the Cursor to Render Some Text While Dragging

  • Getting fancy and using the Visual we are dragging for feedback [instead of a cursor]

  • How can I drag and drop items between data bound ItemsControls?

  1. Create a new Cursor object by loading an image from a .cur or .ani file. You can create and edit these kinds of files in Visual Studio. There are also some free utilites floating around for dealing with them. Basically they’re images (or animated images) which specify a «hot spot» indicating what point in the image the cursor is positioned at.

If you choose to load from a file, note that you need an absolute file-system path to use the Cursor(string fileName) constructor. Lamely, a relative path or Pack URI will not work. If you need to load the cursor from a relative path or from a resource packed with your assembly, you will need to get a stream from the file and pass it in to the Cursor(Stream cursorStream) constructor. Annoying but true.

On the other hand, specifying a cursor as a relative path when loading it using a XAML attribute does work, a fact you could use to get your cursor loaded onto a hidden control and then copy the reference to use on another control. I haven’t tried it, but it should work.

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Как изменить курсор winapi
  • Как изменить курсор unity
  • Как изменить курсор ldplayer
  • Как изменить курсор cur
  • Как изменить курсовую чтобы она прошла антиплагиат

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии