Domanda

Voglio utilizzare un'immagine o un'icona come cursore personalizzato nell'app WPF.Qual è il modo migliore per farlo?

È stato utile?

Soluzione

Hai due opzioni di base:

  1. Quando il cursore del mouse è sopra il tuo controllo, nascondi il cursore di sistema impostando this.Cursor = Cursors.None; e disegna il tuo cursore utilizzando la tecnica che preferisci.Quindi, aggiorna la posizione e l'aspetto del cursore rispondendo agli eventi del mouse.Ecco due esempi:

  2. Crea un nuovo oggetto Cursore caricando un'immagine da un file .cur o .ani.È possibile creare e modificare questi tipi di file in Visual Studio.Ci sono anche alcune utilità gratuite in giro per gestirli.Fondamentalmente sono immagini (o immagini animate) che specificano un "punto caldo" che indica in quale punto dell'immagine è posizionato il cursore.

Se scegli di caricare da un file, tieni presente che è necessario un percorso assoluto del file system per utilizzare il file Cursor(string fileName) costruttore.Lamentevolmente, un percorso relativo o un URI di pacchetto non funzionerà. Se devi caricare il cursore da un percorso relativo o da una risorsa compressa con il tuo assembly, dovrai ottenere uno stream dal file e passarlo al Cursor(Stream cursorStream) costruttore.Fastidioso ma vero.

D'altro canto, specificando un cursore come percorso relativo durante il caricamento utilizzando un attributo XAML fa funziona, un fatto che potresti usare per caricare il cursore su un controllo nascosto e quindi copiare il riferimento da utilizzare su un altro controllo.Non l'ho provato, ma dovrebbe funzionare.

Altri suggerimenti

Come Peter menzionato sopra, se hai già un file .cur, puoi usarlo come risorsa incorporata creando un elemento fittizio nella sezione delle risorse e quindi facendo riferimento al cursore del manichino quando ne hai bisogno.

Ad esempio, supponiamo che tu voglia visualizzare cursori non standard a seconda dello strumento selezionato.

Aggiungi alle risorse:

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

Esempio di cursore incorporato a cui si fa riferimento nel codice:

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

Esiste un modo più semplice rispetto alla gestione autonoma della visualizzazione del cursore o all'utilizzo di Visual Studio per costruire numerosi cursori personalizzati.

Se hai un FrameworkElement puoi costruire un Cursor da esso usando il seguente codice:

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

Tieni presente che la dimensione del tuo FrameworkElement deve essere una dimensione del cursore standard (ad esempio 16x16 o 32x32), ad esempio:

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

Sarebbe usato in questo modo:

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

Ovviamente il tuo FrameworkElement potrebbe essere un <Image> controlla se hai un'immagine esistente oppure puoi disegnare tutto ciò che ti piace utilizzando gli strumenti di disegno integrati di WPF.

Tieni presente che i dettagli sul formato file .cur sono disponibili all'indirizzo ICO (formato file).

Per utilizzare un cursore personalizzato in XAML ho modificato leggermente il codice fornito da Ben McIntosh:

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

Per utilizzare il cursore basta fare riferimento alla risorsa:

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

Un modo molto semplice consiste nel creare il cursore in Visual Studio come file .cur e quindi aggiungerlo alle risorse del progetto.

Quindi aggiungi semplicemente il seguente codice quando vuoi assegnare il cursore:

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

Nel caso qualcuno stia cercando un UIElement stesso come cursore, ho combinato le soluzioni di Ray E Arturo:

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

So che questo argomento risale a qualche anno fa, ma ieri volevo caricare un file di cursore personalizzato dalle risorse del progetto e ho riscontrato problemi simili.Ho cercato una soluzione su Internet e non ho trovato ciò di cui avevo bisogno:per impostare il this.Cursor su un cursore personalizzato memorizzato nella cartella delle risorse nel mio progetto in fase di esecuzione.Ho provato la soluzione xaml di Ben, ma non l'ho trovata abbastanza elegante.PeterAllen ha dichiarato:

Purtroppo, un percorso relativo o un URI di pacchetto non funzionerà.Se è necessario caricare il cursore da un percorso relativo o da una risorsa compressa con l'assembly, sarà necessario ottenere un flusso dal file e passarlo al costruttore Cursor(Stream cursorStream).Fastidioso ma vero.

Mi sono imbattuto in un bel modo per farlo e risolve il mio problema:

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

Un'altra soluzione in qualche modo simile a quella di Ray ma invece della lenta e scomoda copia dei pixel, utilizza alcuni interni di Windows:

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

C'è un metodo di estensione nel mezzo che preferisco avere in una classe di estensione per questi casi:

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

Con tutto questo, è piuttosto semplice e diretto.

E, se ti capita di non aver bisogno di specificare il tuo hotspot, puoi anche abbreviarlo (non hai nemmeno bisogno della struttura o dei 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));
}

Potresti provare questo

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

Dai un'occhiata anche a BabySmash di Scott Hanselman (www.codeplex.com/babysmash).Ha usato un metodo più "forza bruta" per nascondere il cursore di Windows e mostrare il suo nuovo cursore su una tela e quindi spostare il cursore dove sarebbe stato il cursore "reale"

Leggi di più qui:http://www.hanselman.com/blog/DeveloperDesigner.aspx

Assicurarsi che qualsiasi risorsa GDI (ad esempio bmp.GetHIcon) venga eliminata.Altrimenti ti ritroverai con una perdita di memoria.Il seguente codice (metodo di estensione per l'icona) funziona perfettamente per WPF.Crea il cursore a freccia con una piccola icona in basso a destra.

Nota:Questo codice utilizza un'icona per creare il cursore.Non utilizza un controllo dell'interfaccia utente corrente.

Mattia

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

Se utilizzi Visual Studio, puoi farlo

  1. Nuovo file cursore
  2. Copia/incolla l'immagine
  3. Salvalo nel file .cur.

puoi farlo tramite Code like

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

Potrebbe essere cambiato con Visual Studio 2017 ma sono riuscito a fare riferimento a un file .cur come risorsa incorporata:

<Setter
    Property="Cursor"
    Value="/assembly-name;component/location-name/curser-name.cur" />
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top