Benutzerdefinierter Cursor in WPF?

Ich möchte ein Bild oder ein Symbol als benutzerdefinierten Cursor in der WPF-App verwenden. Was ist der beste Weg, es zu tun?

Sie haben zwei grundlegende Optionen:

  1. Wenn sich der Mauszeiger über Ihrem Steuerelement befindet, this.Cursor = Cursors.None; indem Sie dies this.Cursor = Cursors.None; und zeichne deinen eigenen Cursor mit der Technik, die du magst. Aktualisieren Sie dann die Position und das Aussehen Ihres Cursors, indem Sie auf Mausereignisse reagieren. Hier sind zwei Beispiele:

  2. Erstellen Sie ein neues Cursor-Objekt, indem Sie ein Bild aus einer .cur- oder .ani-Datei laden. Sie können diese Arten von Dateien in Visual Studio erstellen und bearbeiten. Es gibt auch einige kostenlose Utilities, die für den Umgang damit sorgen. Im Grunde handelt es sich um Bilder (oder animierte Bilder), die einen “Hot Spot” angeben, der angibt, an welchem ​​Punkt des Bildes sich der Cursor befindet.

Wenn Sie aus einer Datei laden, beachten Sie, dass Sie einen absoluten Dateisystempfad benötigen, um den Konstruktor Cursor(string fileName) verwenden. Lamey, ein relativer Pfad oder Pack-URI wird nicht funktionieren. Wenn Sie den Cursor von einem relativen Pfad oder von einer mit Ihrer Assembly gepackten Ressource laden müssen, müssen Sie einen Stream aus der Datei Cursor(Stream cursorStream) und ihn an den Konstruktor Cursor(Stream cursorStream) . Ärgerlich aber wahr.

Wenn Sie andererseits einen Cursor als relativen Pfad beim Laden mithilfe eines XAML-Attributs angeben, können Sie den Cursor auf ein ausgeblendetes Steuerelement laden und anschließend den Verweis für ein anderes Steuerelement kopieren. Ich habe es nicht versucht, aber es sollte funktionieren.

Wie bereits oben erwähnt, können Sie, wenn Sie bereits eine .cur-Datei haben, diese als eingebettete Ressource verwenden, indem Sie im Ressourcenabschnitt ein Dummy-Element erstellen und dann bei Bedarf auf den Cursor des Dummys verweisen.

Angenommen, Sie möchten nicht standardmäßige Cursor abhängig vom ausgewählten Werkzeug anzeigen.

Zu Ressourcen hinzufügen:

       

Beispiel eines eingebetteten Cursors, auf den im Code verwiesen wird:

 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; 

-Ben

Es gibt einen einfacheren Weg als die Cursor-Anzeige selbst zu verwalten oder Visual Studio zu verwenden, um viele benutzerdefinierte Cursor zu erstellen.

Wenn Sie ein FrameworkElement haben, können Sie mit dem folgenden Code einen Cursor daraus erstellen:

 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 

Beachten Sie, dass die Größe Ihres FrameworkElements eine Standard-Cursorgröße (zB 16x16 oder 32x32) sein muss, zum Beispiel:

  ...  

Es würde so verwendet werden:

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

Offensichtlich kann Ihr FrameworkElement ein -Steuerelement sein, wenn Sie ein vorhandenes Image haben, oder Sie können mit den integrierten WPF-Zeichenwerkzeugen alles zeichnen, was Ihnen gefällt.

Beachten Sie, dass Details zum .cur-Dateiformat unter ICO (Dateiformat) zu finden sind .

Um einen benutzerdefinierten Cursor in XAML zu verwenden, änderte ich den Code, den Ben McIntosh leicht lieferte:

  Resources/openhand.cur  

Um den Cursor nur auf die Ressource zu verweisen:

  

Ich weiß, dass dieses Thema jetzt ein paar Jahre alt ist, aber gestern wollte ich eine benutzerdefinierte Cursordatei aus den Projektressourcen laden und stieß auf ähnliche Probleme. Ich suchte im Internet nach einer Lösung und fand nicht, was ich brauchte: um den this.Cursor auf einen benutzerdefinierten Cursor zu setzen, der zur Laufzeit in meinem Ressourcenordner in meinem Projekt gespeichert wurde. Ich habe Bens xaml-Lösung ausprobiert, fand sie aber nicht elegant genug. PeterAllen sagte:

Lamey, ein relativer Pfad oder Pack-URI wird nicht funktionieren. Wenn Sie den Cursor von einem relativen Pfad oder von einer mit Ihrer Assembly gepackten Ressource laden müssen, müssen Sie einen Stream aus der Datei abrufen und ihn an den Konstruktor Cursor (Stream cursorStream) übergeben. Ärgerlich aber wahr.

Ich bin auf einen netten Weg gestolpert und triggerse mein Problem:

 System.Windows.Resources.StreamResourceInfo info = Application.GetResourceStream(new Uri("/MainApp;component/Resources/HandDown.cur", UriKind.Relative)); this.Cursor = new System.Windows.Input.Cursor(info.Stream); 

Eine sehr einfache Möglichkeit besteht darin, den Cursor in Visual Studio als .cur-Datei zu erstellen und dann den Ressourcen des Projekts hinzuzufügen.

Dann fügen Sie einfach den folgenden Code hinzu, wenn Sie den Cursor zuweisen möchten:

 myCanvas.Cursor = new Cursor(new System.IO.MemoryStream(myNamespace.Properties.Resources.Cursor1)); 

Falls jemand nach einem UIElement selbst als Cursor sucht, habe ich die Lösungen von Ray und Arcturus kombiniert:

  public Cursor ConvertToCursor(UIElement control, Point hotSpot) { // convert FrameworkElement to PNG stream var pngStream = new MemoryStream(); control.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); Rect rect = new Rect(0, 0, control.DesiredSize.Width, control.DesiredSize.Height); RenderTargetBitmap rtb = new RenderTargetBitmap((int)control.DesiredSize.Width, (int)control.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32); control.Arrange(rect); rtb.Render(control); PngBitmapEncoder png = new PngBitmapEncoder(); png.Frames.Add(BitmapFrame.Create(rtb)); png.Save(pngStream); // write cursor header info var cursorStream = new MemoryStream(); cursorStream.Write(new byte[2] { 0x00, 0x00 }, 0, 2); // ICONDIR: Reserved. Must always be 0. cursorStream.Write(new byte[2] { 0x02, 0x00 }, 0, 2); // ICONDIR: Specifies image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid cursorStream.Write(new byte[2] { 0x01, 0x00 }, 0, 2); // ICONDIR: Specifies number of images in the file. cursorStream.Write(new byte[1] { (byte)control.DesiredSize.Width }, 0, 1); // ICONDIRENTRY: Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. cursorStream.Write(new byte[1] { (byte)control.DesiredSize.Height }, 0, 1); // ICONDIRENTRY: Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels. cursorStream.Write(new byte[1] { 0x00 }, 0, 1); // ICONDIRENTRY: Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. cursorStream.Write(new byte[1] { 0x00 }, 0, 1); // ICONDIRENTRY: Reserved. Should be 0. cursorStream.Write(new byte[2] { (byte)hotSpot.X, 0x00 }, 0, 2); // ICONDIRENTRY: Specifies the horizontal coordinates of the hotspot in number of pixels from the left. cursorStream.Write(new byte[2] { (byte)hotSpot.Y, 0x00 }, 0, 2); // ICONDIRENTRY: Specifies the vertical coordinates of the hotspot in number of pixels from the top. cursorStream.Write(new byte[4] { // ICONDIRENTRY: Specifies the size of the image's data in bytes (byte)((pngStream.Length & 0x000000FF)), (byte)((pngStream.Length & 0x0000FF00) >> 0x08), (byte)((pngStream.Length & 0x00FF0000) >> 0x10), (byte)((pngStream.Length & 0xFF000000) >> 0x18) }, 0, 4); cursorStream.Write(new byte[4] { // ICONDIRENTRY: Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file (byte)0x16, (byte)0x00, (byte)0x00, (byte)0x00, }, 0, 4); // copy PNG stream to cursor stream pngStream.Seek(0, SeekOrigin.Begin); pngStream.CopyTo(cursorStream); // return cursor stream cursorStream.Seek(0, SeekOrigin.Begin); return new Cursor(cursorStream); } 

Eine weitere Lösung, die der von Ray ähnlich ist, aber statt langsamem und umständlichem Kopieren von Pixeln, verwendet sie einige Windows-Interna:

 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 Cursor ConvertToCursor(FrameworkElement cursor, Point HotSpot) { cursor.Arrange(new Rect(new Size(cursor.Width, cursor.Height))); var bitmap = new RenderTargetBitmap((int)cursor.Width, (int)cursor.Height, 96, 96, PixelFormats.Pbgra32); bitmap.Render(cursor); var info = new IconInfo(); GetIconInfo(bitmap.ToBitmap().GetHicon(), ref info); info.fIcon = false; info.xHotspot = (byte)(HotSpot.X * cursor.Width); info.yHotspot = (byte)(HotSpot.Y * cursor.Height); return CursorInteropHelper.Create(new SafeFileHandle(CreateIconIndirect(ref info), true)); } 

Es gibt eine Erweiterungsmethode in der Mitte, die ich für solche Fälle lieber in einer Erweiterungsklasse habe:

 using DW = System.Drawing; public static DW.Bitmap ToBitmap(this BitmapSource bitmapSource) { var bitmap = new DW.Bitmap(bitmapSource.PixelWidth, bitmapSource.PixelHeight, DW.Imaging.PixelFormat.Format32bppPArgb); var data = bitmap.LockBits(new DW.Rectangle(DW.Point.Empty, bitmap.Size), DW.Imaging.ImageLockMode.WriteOnly, DW.Imaging.PixelFormat.Format32bppPArgb); bitmapSource.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride); bitmap.UnlockBits(data); return bitmap; } 

Mit all dem ist es ziemlich einfach und unkompliziert.

Und wenn Sie nicht Ihren eigenen Hotspot angeben müssen, können Sie dies sogar noch kürzer machen (Sie benötigen weder die Struktur noch die P / Invokes):

 public Cursor ConvertToCursor(FrameworkElement cursor, Point HotSpot) { cursor.Arrange(new Rect(new Size(cursor.Width, cursor.Height))); var bitmap = new RenderTargetBitmap((int)cursor.Width, (int)cursor.Height, 96, 96, PixelFormats.Pbgra32); bitmap.Render(cursor); var icon = System.Drawing.Icon.FromHandle(bitmap.ToBitmap().GetHicon()); return CursorInteropHelper.Create(new SafeFileHandle(icon.Handle, true)); } 

Du könntest das versuchen

  

Schauen Sie sich auch Scott Hanselmans BabySmash (www.codeplex.com/babysmash) an. Er verwendete eine “Brute-Force” -Methode, um den Windows-Cursor zu verbergen und seinen neuen Cursor auf einer canvas zu zeigen und dann den Cursor zu bewegen, wo der “echte” Cursor gewesen wäre

Lesen Sie hier mehr: http://www.hanselman.com/blog/DeveloperDesigner.aspx

Stellen Sie sicher, dass alle GDI-Ressourcen (z. B. bmp.GetHIcon) entsorgt werden. Andernfalls haben Sie ein Speicherleck. Der folgende Code (Erweiterungsmethode für das Symbol) funktioniert perfekt für WPF. Es erstellt den Pfeilcursor mit einem kleinen Symbol unten rechts.

Anmerkung: Dieser Code verwendet ein Symbol zum Erstellen des Cursors. Es verwendet kein aktuelles UI-Steuerelement.

Matthias

  public static Cursor CreateCursor(this Icon icon, bool includeCrossHair, System.Drawing.Color crossHairColor) { if (icon == null) return Cursors.Arrow; // create an empty image int width = icon.Width; int height = icon.Height; using (var cursor = new Bitmap(width * 2, height * 2)) { // create a graphics context, so that we can draw our own cursor using (var gr = System.Drawing.Graphics.FromImage(cursor)) { // a cursor is usually 32x32 pixel so we need our icon in the lower right part of it gr.DrawIcon(icon, new Rectangle(width, height, width, height)); if (includeCrossHair) { using (var pen = new System.Drawing.Pen(crossHairColor)) { // draw the cross-hair gr.DrawLine(pen, width - 3, height, width + 3, height); gr.DrawLine(pen, width, height - 3, width, height + 3); } } } try { using (var stream = new MemoryStream()) { // Save to .ico format var ptr = cursor.GetHicon(); var tempIcon = Icon.FromHandle(ptr); tempIcon.Save(stream); int x = cursor.Width/2; int y = cursor.Height/2; #region Convert saved stream into .cur format // set as .cur file format stream.Seek(2, SeekOrigin.Begin); stream.WriteByte(2); // write the hotspot information stream.Seek(10, SeekOrigin.Begin); stream.WriteByte((byte)(width)); stream.Seek(12, SeekOrigin.Begin); stream.WriteByte((byte)(height)); // reset to initial position stream.Seek(0, SeekOrigin.Begin); #endregion DestroyIcon(tempIcon.Handle); // destroy GDI resource return new Cursor(stream); } } catch (Exception) { return Cursors.Arrow; } } } ///  /// Destroys the icon. ///  /// The handle. ///  [DllImport("user32.dll", CharSet = CharSet.Auto)] public extern static Boolean DestroyIcon(IntPtr handle); 

Wenn Sie Visual Studio verwenden, können Sie

  1. Neu eine Cursordatei
  2. Kopieren / Einfügen des Bildes
  3. Speichern Sie es in der .cur-Datei.

Möglicherweise wurde es mit Visual Studio 2017 geändert, aber ich konnte eine .cur-Datei als eingebettete Ressource referenzieren:

  

Hier ist ein Feature-reiche, kostenlose Dienstprogramm, mit dem Sie eine Cur-Datei von jedem Bild erstellen können: http://www.rw-designer.com/cursor-maker

Es heißt RealWorld Cursor Editor.

Und hier ist ein Link zum Einbetten eines Cursors in ein Projekt:

http://wpf.2000things.com/tag/cursor/

Sie können dies durch Code wie tun

 this.Cursor = new Cursor(@"");