




Вы можете попробовать это
<Window Cursor = ""C:\WINDOWS\Cursors\dinosaur.ani"" />
Также проверьте BabySmash Скотта Хансельмана (www.codeplex.com/babysmash). Он использовал метод «грубой силы», чтобы скрыть курсор окна и показать его новый курсор на холсте, а затем переместить курсор туда, где «настоящий» курсор мог бы быть
Подробнее читайте здесь: http://www.hanselman.com/blog/DeveloperDesigner.aspx
У вас есть два основных варианта:
Когда курсор мыши находится над вашим элементом управления, скройте системный курсор, установив this.Cursor = Cursors.None;, и нарисуйте свой собственный курсор, используя любую технику, которая вам нравится. Затем обновите положение и внешний вид курсора, реагируя на события мыши. Вот два примера:
http://www.hanselman.com/blog/DeveloperDesigner.aspx
Дополнительные примеры можно найти здесь:
Учебное пособие по WPF - Как использовать настраиваемые курсоры
Создайте новый объект Cursor, загрузив изображение из файла .cur или .ani. Вы можете создавать и редактировать такие файлы в Visual Studio. Есть также несколько бесплатных утилит для работы с ними. В основном это изображения (или анимированные изображения), которые определяют «горячую точку», указывающую, в какой точке изображения находится курсор.
Если вы выбрали загрузку из файла, обратите внимание, что для использования конструктора Cursor(string fileName) вам нужен абсолютный путь к файловой системе. К сожалению, относительный путь или Pack URI не будут работать.. Если вам нужно загрузить курсор из относительного пути или из ресурса, упакованного вашей сборкой, вам нужно будет получить поток из файла и передать его конструктору Cursor(Stream cursorStream). Раздражает, но факт.
С другой стороны, указание курсора в качестве относительного пути при его загрузке с использованием атрибута XAML делает работает, факт, который вы можете использовать, чтобы загрузить курсор в скрытый элемент управления, а затем скопировать ссылку для использования в другом элементе управления. Не пробовал, но должно работать.
Также обратите внимание, что вы можете создать курсор «на лету» из любого содержимого WPF. См. stackoverflow.com/questions/2835502/… для примера того, как это делается.
Ссылка, которую я разместил в предыдущем комментарии, касается поворота существующего курсора. Я только что опубликовал новый ответ на этот вопрос (см. Ниже), в котором рассказывается, как преобразовать произвольный визуал в курсор.
Как и Петр упомянул, если у вас уже есть файл .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 сначала определено?
Нет причин; FrameworkElement был бы лучшим выбором. Спасибо!
Есть более простой способ, чем управлять отображением курсора самостоятельно или использовать 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"/>
Я знаю, что этой теме уже несколько лет, но вчера я хотел загрузить файл настраиваемого курсора из ресурсов проекта и столкнулся с аналогичными проблемами. Я поискал в Интернете решение и не нашел того, что мне было нужно: установить 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 файлами внутри вашего проекта.
Очень простой способ - создать курсор в 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 вместо "фиктивного" элемента фреймворка имеет гораздо больший смысл.
Если вы используете Visual Studio, вы можете
Еще одно решение, несколько похожее на решение Рэя, но вместо медленного и громоздкого копирования пикселей здесь используются некоторые внутренние компоненты 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 взрывает его.
Хороший пример, который хорошо работает, но есть ошибка, например info.yHotspot = (byte)(HotSpot.X * cursor.Height); (должен быть HotSpot.Y, а не HotSpot.X). В этом примере также изменяется диапазон исходного кода точки доступа, масштабируя его по размерам исходного растрового изображения, поэтому имейте это в виду при указании смещения.
Если кто-то ищет сам 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 вокруг ваших потоков, но кроме этого, у меня нет проблем с этим методом (в отличие от других реализаций).
Я заметил, что вызов Arrange в элементе управления приводит к тому, что ListBoxItems и TreeViewItems на мгновение исчезают, а затем снова появляются после изменения их родительских макетов (например, при расширении TreeViewItem). Есть идеи, почему это так?
вы можете сделать это с помощью кода, например
this.Cursor = new Cursor(@"<your address of icon>");
Возможно, это изменилось в Visual Studio 2017, но я смог сослаться на файл .cur как на встроенный ресурс:
<Setter
Property = "Cursor"
Value = "/assembly-name;component/location-name/curser-name.cur" />
Ссылка Xamlog предназначена только для участников :(