Frage

Ich möchte ein Bild oder Symbol als benutzerdefinierten Cursor in der WPF-App verwenden.Wie geht das am besten?

War es hilfreich?

Lösung

Sie haben grundsätzlich zwei Möglichkeiten:

  1. Wenn sich der Mauszeiger über Ihrem Steuerelement befindet, blenden Sie den Systemcursor durch Einstellung aus this.Cursor = Cursors.None; und zeichnen Sie Ihren eigenen Cursor mit der von Ihnen bevorzugten Technik.Aktualisieren Sie dann die Position und das Erscheinungsbild Ihres Cursors, indem Sie auf Mausereignisse reagieren.Hier zwei Beispiele:

  2. Erstellen Sie ein neues Cursor-Objekt, indem Sie ein Bild aus einer .cur- oder .ani-Datei laden.Sie können diese Art von Dateien in Visual Studio erstellen und bearbeiten.Es gibt auch einige kostenlose Dienstprogramme, die sich mit diesen Problemen befassen.Im Grunde handelt es sich um Bilder (oder animierte Bilder), die einen „Hotspot“ angeben, der angibt, an welcher Stelle im Bild sich der Cursor befindet.

Wenn Sie sich für das Laden aus einer Datei entscheiden, beachten Sie, dass Sie einen absoluten Dateisystempfad benötigen, um die zu verwenden Cursor(string fileName) Konstrukteur.Lahm, Ein relativer Pfad oder Pack-URI funktioniert nicht. 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 übergeben Cursor(Stream cursorStream) Konstrukteur.Ärgerlich, aber wahr.

Andererseits die Angabe eines Cursors als relativen Pfad beim Laden mithilfe eines XAML-Attributs tut Arbeit, eine Tatsache, die Sie nutzen könnten, um Ihren Cursor auf ein verstecktes Steuerelement zu laden und dann die Referenz zu kopieren, um sie auf einem anderen Steuerelement zu verwenden.Ich habe es nicht ausprobiert, aber es sollte funktionieren.

Andere Tipps

Wie Peter oben erwähnt hat, können Sie, wenn Sie bereits über eine .cur-Datei verfügen, 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 je nach ausgewähltem Werkzeug nicht standardmäßige Cursor anzeigen.

Zu den Ressourcen hinzufügen:

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

Beispiel für einen eingebetteten Cursor, 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 eine einfachere Möglichkeit, als die Cursoranzeige selbst zu verwalten oder mit Visual Studio viele benutzerdefinierte Cursor zu erstellen.

Wenn Sie ein FrameworkElement haben, können Sie daraus mit dem folgenden Code einen Cursor 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<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);
}

Beachten Sie, dass die Größe Ihres FrameworkElement einer Standard-Cursorgröße entsprechen muss (z. B. 16 x 16 oder 32 x 32), zum Beispiel:

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

Es würde so verwendet werden:

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

Offensichtlich könnte Ihr FrameworkElement ein sein <Image> Steuern Sie, ob Sie über ein vorhandenes Bild verfügen, oder zeichnen Sie mit den integrierten Zeichenwerkzeugen von WPF alles, was Sie möchten.

Beachten Sie, dass Einzelheiten zum .cur-Dateiformat unter gefunden werden können ICO (Dateiformat).

Um einen benutzerdefinierten Cursor in XAML zu verwenden, habe ich den von Ben McIntosh bereitgestellten Code leicht geändert:

<Window.Resources>    
 <Cursor x:Key="OpenHandCursor">Resources/openhand.cur</Cursor>
</Window.Resources>

Um den Cursor zu verwenden, verweisen Sie einfach auf die Ressource:

<StackPanel Cursor="{StaticResource OpenHandCursor}" />

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

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

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

Für den Fall, dass jemand nach einem UIElement selbst als Cursor sucht, habe ich die Lösungen von kombiniert Strahl Und Arcturus:

    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);
    }

Ich weiß, dass dieses Thema mittlerweile schon ein paar Jahre alt ist, aber gestern wollte ich eine benutzerdefinierte Cursordatei aus den Projektressourcen laden und bin auf ähnliche Probleme gestoßen.Ich habe im Internet nach einer Lösung gesucht und nicht gefunden, was ich brauchte:um das einzustellen this.Cursor zu einem benutzerdefinierten Cursor, der zur Laufzeit in meinem Ressourcenordner in meinem Projekt gespeichert ist.Ich habe Bens XAML-Lösung ausprobiert, fand sie aber nicht elegant genug.PeterAllen erklärte:

Leider funktioniert ein relativer Pfad oder Pack-URI nicht.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 Cursor(StreamcursorStream)-Konstruktor übergeben.Ärgerlich, aber wahr.

Ich bin auf eine nette Möglichkeit gestoßen, dies zu tun und mein Problem zu lösen:

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 weitere Lösung, die der von Ray etwas ähnelt, aber anstelle des langsamen und umständlichen Kopierens von Pixeln einige Windows-Interna nutzt:

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));
}

In der Mitte gibt es eine Erweiterungsmethode, 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;
}

Bei alledem ist es ziemlich einfach und unkompliziert.

Und wenn Sie keinen eigenen Hotspot angeben müssen, können Sie dies sogar kürzen (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

<Window Cursor=""C:\WINDOWS\Cursors\dinosaur.ani"" />

Schauen Sie sich auch Scott Hanselmans BabySmash an (www.codeplex.com/babysmash).Er verwendete eine „brutalere“ Methode, indem er den Windows-Cursor versteckte und seinen neuen Cursor auf einer Leinwand zeigte und ihn dann dorthin bewegte, 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 kommt es zu einem Speicherverlust.Der folgende Code (Erweiterungsmethode für Symbol) funktioniert perfekt für WPF.Es erstellt den Pfeilcursor mit einem kleinen Symbol unten rechts.

Anmerkung:Dieser Code verwendet ein Symbol, um den Cursor zu erstellen.Es wird kein aktuelles UI-Steuerelement verwendet.

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;
            }
        }
    }

    /// <summary>
    /// Destroys the icon.
    /// </summary>
    /// <param name="handle">The handle.</param>
    /// <returns></returns>
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public extern static Boolean DestroyIcon(IntPtr handle);

Wenn Sie Visual Studio verwenden, ist dies möglich

  1. Neue Cursordatei
  2. Kopieren/fügen Sie das Bild ein
  3. Speichern Sie es in der .cur-Datei.

Sie können dies per Code tun

this.Cursor = new Cursor(@"<your address of icon>");

Mit Visual Studio 2017 hat sich das möglicherweise geändert, aber ich konnte auf eine .cur-Datei als eingebettete Ressource verweisen:

<Setter
    Property="Cursor"
    Value="/assembly-name;component/location-name/curser-name.cur" />
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top