Подтвердить что ты не робот

Пользовательский курсор в WPF?

Я хочу использовать изображение или значок в качестве настраиваемого курсора в приложении WPF. Какой лучший способ сделать это?

4b9b3361

Ответ 1

У вас есть два основных варианта:

  • Когда курсор мыши находится над вашим контролем, скройте системный курсор, установив this.Cursor = Cursors.None; и нарисуйте свой собственный курсор, используя любую технику, которая вам нравится. Затем обновите положение и внешний вид вашего курсора, отвечая на события мыши. Вот два примера:

  • Создайте новый объект курсора, загрузив изображение из файла .cur или .ani. Вы можете создавать и редактировать эти файлы в Visual Studio. Есть также некоторые бесплатные утилиты, плавающие вокруг для борьбы с ними. В основном это изображения (или анимированные изображения), которые указывают "горячую точку", указывающую, на какой точке изображения находится курсор.

Если вы решите загрузить из файла, обратите внимание, что вам нужен абсолютный путь к файловой системе для использования конструктора Cursor(string fileName). А именно, относительный путь или URI пакета не будет работать. Если вам нужно загрузить курсор из относительного пути или из ресурса, собранного вместе с вашей сборкой, вам нужно будет получить поток из файла и передать его в конструктор Cursor(Stream cursorStream). Досадно, но верно.

С другой стороны, указание курсора как относительного пути при его загрузке с использованием атрибута XAML действительно работает, что вы могли бы использовать, чтобы курсор загрузился на скрытый элемент управления, а затем скопировал ссылку для использования на другом элементе управления. Я не пробовал, но он должен работать.

Ответ 2

Как упоминалось выше, Peter, если у вас уже есть .cur файл, вы можете использовать его как встроенный ресурс, создав фиктивный элемент в разделе ресурсов, а затем ссылаясь на манекен-курсор, когда вам это нужно.

Например, скажем, вы хотите отображать нестандартные курсоры в зависимости от выбранного инструмента.

Добавить в ресурсы:

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

Пример встроенного курсора, на который ссылается код:

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;

-Бен

Ответ 3

Существует более простой способ, чем управлять отображением курсора самостоятельно или с помощью Visual Studio для создания множества пользовательских курсоров.

Если у вас есть FrameworkElement, вы можете построить курсор из него, используя следующий код:

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

Обратите внимание, что ваш размер FrameworkElement должен быть стандартным размером курсора (например, 16x16 или 32x32), например:

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

Он будет использоваться следующим образом:

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

Очевидно, что ваш FrameworkElement может быть элементом управления <Image>, если у вас есть существующее изображение, или вы можете рисовать все, что вам нравится, используя встроенные инструменты рисования WPF.

Обратите внимание, что подробную информацию о формате .cur можно найти в ICO (формат файла).

Ответ 4

Я знаю, что эта тема уже несколько лет, но вчера я захотел загрузить пользовательский файл курсора из ресурсов проекта и столкнулся с аналогичными проблемами. Я искал в Интернете решение и не нашел нужное: установить this.Cursor на пользовательский курсор, который хранится в моей папке ресурсов в моем проекте во время выполнения. Я пробовал решение Ben xaml, но не нашел его достаточно элегантным. Питер Аллен заявил:

А именно, относительный путь или URI пакета не будет работать. Если вам нужно загрузить курсор из относительного пути или из ресурса, собранного вместе с вашей сборкой, вам нужно будет получить поток из файла и передать его в конструктор Cursor (Stream cursorStream). Досадно, но верно.

Я наткнулся на хороший способ сделать это и разрешил свою проблему:

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

Ответ 5

Очень простой способ - создать курсор в Visual Studio в качестве файла .cur, а затем добавить это к ресурсам проектов.

Затем просто добавьте следующий код, если вы хотите назначить курсор:

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

Ответ 6

Если кто-то ищет сам UIElement как курсор, я объединил решения Ray и 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 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);
    }

Ответ 7

Чтобы использовать пользовательский курсор в XAML, я изменил код Ben McIntosh немного:

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

Чтобы использовать курсор только ссылку на ресурс:

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

Ответ 8

Еще одно решение, похожее на Ray, но вместо медленного и громоздкого копирования пикселей использует некоторые внутренние элементы 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));
}

В середине есть метод расширения, который я предпочитаю иметь в классе расширения для таких случаев:

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

При всем этом это довольно просто и просто.

И если вам не нужно указывать свою собственную точку доступа, вы можете даже сократить это более короткое (вам также не нужна структура или 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));
}

Ответ 9

Вы можете попробовать это

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

Ответ 10

Если вы используете визуальную студию, вы можете

  • Новый файл курсора
  • Копировать/Вставить изображение
  • Сохраните файл .cur.

Ответ 11

Также ознакомьтесь с Scott Hanselman BabySmash (www.codeplex.com/babysmash). Он использовал метод "грубой силы" для скрытия курсора Windows и отображения его нового курсора на холсте, а затем перемещение курсора в "реальный" курсор было бы

Подробнее здесь: http://www.hanselman.com/blog/DeveloperDesigner.aspx

Ответ 12

Убедитесь, что любой ресурс GDI (например, bmp.GetHIcon) удаляется. В противном случае вы получите утечку памяти. Следующий код (метод расширения для значка) отлично работает для WPF. Он создает стрелку стрелки с маленькой иконкой на ней справа внизу.

Примечание. Этот код использует значок для создания курсора. Он не использует текущий пользовательский интерфейс.

Маттиас

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

Ответ 14

Здесь полнофункциональная бесплатная утилита, которая позволяет создавать cur файл из любого изображения: http://www.rw-designer.com/cursor-maker

Он называется редактором курсора RealWorld.

И вот ссылка о том, как вставить курсор в проект:

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

Ответ 15

вы можете сделать это с помощью кода, например

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

Ответ 16

Возможно, это изменилось с помощью Visual Studio 2017, но я смог ссылаться на файл .cur как на встроенный ресурс:

<Setter
    Property="Cursor"
    Value="/assembly-name;component/location-name/curser-name.cur" />