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

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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
53
0
58 984
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

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

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

Ссылка Xamlog предназначена только для участников :(

jschroedl 08.09.2009 17:57

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

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

Ответ принят как подходящий

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

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

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

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

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

Также обратите внимание, что вы можете создать курсор «на лету» из любого содержимого WPF. См. stackoverflow.com/questions/2835502/… для примера того, как это делается.

Ray Burns 14.05.2010 23:42

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

Ray Burns 14.05.2010 23:53

Как и Петр упомянул, если у вас уже есть файл .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;

-Бен

Есть ли причина, по которой вы использовали TextBlock для кэширования ссылок Cursor через FrameworkElement, где свойство Cursor сначала определено?

PaulJ 21.03.2011 13:07

Нет причин; FrameworkElement был бы лучшим выбором. Спасибо!

Ben McIntosh 22.03.2011 11:18

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

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

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 (формат файла).

Привет, я попытался использовать этот фрагмент кода для определения настраиваемого курсора с помощью xaml. К сожалению, вместо указанного мной <Image />-элемента ничего не отображается. Отлаживая код, я понял, что массив var pixels просто содержит 0 для каждого пикселя после запуска метода CopyPixels(). Я получил ошибку для параметра stride для метода CopyPixels(), поэтому я немного изменил код в соответствии с некоторыми другими найденными мной фрагментами: int stride = width * ((bitmapSource.Format.BitsPerPixel + 7) / 8); За исключением того, что код выглядит так же, как указано выше. visual - это: <Image Height = "32" Width = "32"/>

andineupert 30.10.2013 23:06

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

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.

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

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

"MainApp" следует заменить на имя вашего приложения. «Ресурсы» следует заменить на относительный путь к папке с вашими * .cur файлами внутри вашего проекта.

Mark Miller 26.07.2016 06:43

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

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

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

Убедитесь, что любой ресурс 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);

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

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

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

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

Использование ресурса Cursor вместо "фиктивного" элемента фреймворка имеет гораздо больший смысл.

DiamondDrake 01.08.2016 09:46

Если вы используете Visual Studio, вы можете

  1. Создать файл курсора
  2. Копировать / вставить изображение
  3. Сохраните его в файл .cur.

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

Этот отлично работает (замечательно создавать Cursor из любого визуального элемента WPF, который я хочу), однако я продолжал получать исключение SEH в dtor курсора, созданного этим методом всякий раз, когда связанный объект был уничтожен. Единственный способ не получить его - создать одиночный элемент курсора и повторно использовать его повсюду. Какая-нибудь известная вам причина может вызвать исключение SEH? Я могу догадываться об этом весь день, но на самом деле кажется, что объект, используемый для создания изображения для курсора, удаляется, и класс Cursor взрывает его.

outbred 26.08.2015 22:03

Хороший пример, который хорошо работает, но есть ошибка, например info.yHotspot = (byte)(HotSpot.X * cursor.Height); (должен быть HotSpot.Y, а не HotSpot.X). В этом примере также изменяется диапазон исходного кода точки доступа, масштабируя его по размерам исходного растрового изображения, поэтому имейте это в виду при указании смещения.

Mark Feldman 23.07.2017 04:40

Если кто-то ищет сам UIElement в качестве курсора, я объединил решения Луч и Арктур:

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

Я бы очистил его с помощью операторов using вокруг ваших потоков, но кроме этого, у меня нет проблем с этим методом (в отличие от других реализаций).

outbred 26.08.2015 22:42

Я заметил, что вызов Arrange в элементе управления приводит к тому, что ListBoxItems и TreeViewItems на мгновение исчезают, а затем снова появляются после изменения их родительских макетов (например, при расширении TreeViewItem). Есть идеи, почему это так?

James M 25.04.2016 06:27

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

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

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

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

Другие вопросы по теме