Пользовательский курсор в WPF? - PullRequest
45 голосов
/ 06 сентября 2008

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

Ответы [ 14 ]

33 голосов
/ 05 ноября 2008

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

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

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

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

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

30 голосов
/ 04 января 2009

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

-Бен

16 голосов
/ 14 мая 2010

Существует более простой способ, чем управлять отображением курсора самостоятельно или использовать 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 (формат файла) .

10 голосов
/ 15 мая 2013

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

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

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

<StackPanel Cursor="{StaticResource OpenHandCursor}" />
9 голосов
/ 22 ноября 2014

Если кто-то ищет сам 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);
    }
9 голосов
/ 03 января 2012

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

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

myCanvas.Cursor = new Cursor(new System.IO.MemoryStream(myNamespace.Properties.Resources.Cursor1));
8 голосов
/ 01 декабря 2011

Я знаю, что этой теме уже несколько лет, но вчера я хотел загрузить пользовательский файл курсора из ресурсов проекта и столкнулся с похожими проблемами. Я искал в интернете решение и не нашел то, что мне нужно: установить this.Cursor для пользовательского курсора, хранящегося в моей папке ресурсов в моем проекте во время выполнения. Я пробовал решение Бена 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); 
7 голосов
/ 16 июня 2014

Еще одно решение, несколько похожее на 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));
}
2 голосов
/ 06 сентября 2008

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

<Window Cursor=""C:\WINDOWS\Cursors\dinosaur.ani"" />
1 голос
/ 21 мая 2017

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

<Setter
    Property="Cursor"
    Value="/assembly-name;component/location-name/curser-name.cur" />
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...