Chcę użyć obrazu lub ikony jako niestandardowego kursora w aplikacji WPF. Jaki jest najlepszy sposób na zrobienie tego?Niestandardowy kursor w WPF?
Odpowiedz
masz dwie podstawowe opcje:
Gdy kursor myszy znajduje się nad kontrolą, ukryć kursor systemowy poprzez ustawienie
this.Cursor = Cursors.None;
i wyciągnąć własne za pomocą kursora dowolną techniką chcesz. Następnie zaktualizuj położenie i wygląd kursora, odpowiadając na zdarzenia myszy. Oto dwa przykłady:utworzyć nowy obiekt Cursor poprzez załadowanie obrazu z .cur ANI lub pliku. Możesz tworzyć i edytować tego rodzaju pliki w Visual Studio. Istnieją również wolne zasoby, które mogą się nimi zająć. Zasadniczo są to obrazy (lub animowane obrazy), które określają "gorący punkt" wskazujący, w którym punkcie obrazu znajduje się kursor.
Jeśli zdecydujesz się załadować z pliku, należy pamiętać, że trzeba bezwzględną ścieżkę do systemu plików użyć konstruktora Cursor(string fileName)
. Lamely, ścieżka względna lub URI Pack nie będą działać. Jeśli chcesz załadować kursor ze ścieżki względnej lub z zasobu spakowanego z zestawem, musisz pobrać strumień z pliku i przekazać go do konstruktora Cursor(Stream cursorStream)
. Irytujące, ale prawdziwe.
Z drugiej strony, określając kursor jako ścieżkę względną podczas ładowania za pomocą atrybutu XAML wykonuje się, fakt, którego można użyć do załadowania kursora do ukrytego elementu sterującego, a następnie skopiowania odwołania do użycia na kolejna kontrola. Nie próbowałem tego, ale powinno działać.
można spróbować to
<Window Cursor=""C:\WINDOWS\Cursors\dinosaur.ani"" />
Xamlog link jest tylko dla członków-niestety :( – jschroedl
Również sprawdzić Scott Hanselman za BabySmash (www.codeplex.com/babysmash). Używał bardziej „brute force” Metoda ukrywanie kursora PC i pokazano jego nowy kursor na płótnie, a następnie przesuwając kursor były „prawdziwe” kursor byłby
Czytaj więcej tutaj: http://www.hanselman.com/blog/DeveloperDesigner.aspx
Tak jak wspomniany wyżej Peter, jeśli już posiadasz plik .cur, możesz go użyć jako zasobu osadzonego, tworząc fikcyjny element w sekcji zasobów, a następnie odwołując się do kursora manekina, kiedy go potrzebujesz.
Załóżmy na przykład, że chcesz wyświetlić niestandardowe kursory w zależności od wybranego narzędzia.
Dodaj do zasobów:
<Window.Resources>
<ResourceDictionary>
<TextBlock x:Key="CursorGrab" Cursor="Resources/Cursors/grab.cur"/>
<TextBlock x:Key="CursorMagnify" Cursor="Resources/Cursors/magnify.cur"/>
</ResourceDictionary>
</Window.Resources>
Przykład wbudowanego kursora odwołanie w kodzie:
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
Czy istnieje jakiś powód, dla którego użyłeś TextBlock do buforowania odniesień kursora w stosunku do FrameworkElement, gdzie najpierw zdefiniowano właściwość Cursor? – PaulJ
Bez powodu; FrameworkElement byłby lepszym wyborem. Dzięki! –
Jest łatwiejszy sposób niż kierowanie kursorem wyświetla się lub przy użyciu programu Visual Studio do skonstruowania wielu niestandardowych kursorów.
Jeśli masz FrameworkElement można stworzyć kursor z nim za pomocą następującego kodu:
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);
}
pamiętać, że wielkość Twój FrameworkElement „s musi być standardowy rozmiar kursora (np 16x16 lub 32x32), na przykład :
<Grid x:Name="customCursor" Width="32" Height="32">
...
</Grid>
byłoby stosować tak:
someControl.Cursor = ConvertToCursor(customCursor, new Point(0.5, 0.5));
Oczywiście Twój obiekt FrameworkElement może być formantem <Image>
, jeśli masz istniejący obraz lub możesz narysować co chcesz, używając wbudowanych narzędzi do rysowania WPF.
Należy pamiętać, że szczegółowe informacje dotyczące formatu pliku .cur można znaleźć pod adresem ICO (file format).
Hej, próbowałem użyć tego fragmentu kodu do zdefiniowania niestandardowego kursora z xaml. Niestety po prostu nic nie wyświetla zamiast "zdefiniowanego przeze mnie"
Wiem, że ten temat ma teraz kilka lat, ale wczoraj chciałem załadować niestandardowy plik kursora z zasobów projektu i napotkałem podobne problemy. Szukałem rozwiązania w Internecie i nie znalazłem tego, czego potrzebowałem: ustawić this.Cursor
na niestandardowy kursor przechowywany w folderze Moje zasoby w moim projekcie w czasie wykonywania. Wypróbowałem rozwiązanie Xaml firmy Ben, ale nie znalazłem wystarczająco eleganckiego. PeterAllen stwierdził:
Niestety, względna ścieżka lub URI opakowania nie będą działać. Jeśli chcesz załadować kursor ze ścieżki względnej lub z zasobu spakowanego z zestawem, musisz pobrać strumień z pliku i przekazać go do konstruktora Cursor (Stream cursorStream). Irytujące, ale prawdziwe.
Natknąłem się na miły sposób, aby to zrobić i rozwiązuje mój problem:
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" należy zastąpić * nazwą * twojej aplikacji. "Zasoby" powinny zostać zastąpione względną ścieżką do plików * .cur wewnątrz twojego projektu. –
Bardzo łatwym sposobem jest stworzenie kursora w Visual Studio jako plik .cur, a następnie dodać, że do zasoby projektów.
Następnie wystarczy dodać następujący kod kiedy chcesz przypisać kursor:
myCanvas.Cursor = new Cursor(new System.IO.MemoryStream(myNamespace.Properties.Resources.Cursor1));
Wielkie dzięki za to! – Cipher
Upewnij się, że każdy zasób GDI (np bmp.GetHIcon) zostanie usunięte. W przeciwnym razie skończy się przeciek pamięci. Poniższy kod (metoda rozszerzenia dla ikony) działa idealnie w przypadku WPF. Tworzy kursor strzałki z małą ikoną w prawym dolnym rogu.
Uwaga: Ten kod używa ikony do utworzenia kursora. Nie używa aktualnej kontroli interfejsu użytkownika.
Matthias
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);
Aby użyć niestandardowego kursora w XAML I zmieniony kod Ben McIntosh przewidzianego nieznacznie:
<Window.Resources>
<Cursor x:Key="OpenHandCursor">Resources/openhand.cur</Cursor>
</Window.Resources>
używać kursora tylko odwoływać się do zasobów:
<StackPanel Cursor="{StaticResource OpenHandCursor}" />
Używanie zasobu Cursor zamiast elementu szkieletowego "manekin" ma dużo więcej sensu – DiamondDrake
Jeśli korzystasz z wizualnego studio, możesz
- Nowy kursor plik
- skopiować/wkleić obraz
- zapisać go do .cur pliku.
Jeszcze rozwiązanie nieco podobny do Raya, ale zamiast powolnego i uciążliwego kopiowania pikseli ta wykorzystuje pewne wewnętrzne 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));
}
istnieje metoda rozszerzenie w środku, że wolę mieć w sposób klasa rozszerzenia dla takich przypadków:
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;
}
To wszystko jest proste i proste.
I, jeśli zdarzy ci się nie należy określić swój własny hotspot, można nawet wyciąć ten krótszy (nie trzeba się struct lub P/powołuje albo):
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));
}
Ten działa świetnie (to niesamowite tworzyć Cursor z dowolnego grafiku WPF, jakiego potrzebuję), jednak ciągle otrzymywałem wyjątek SEH w dtor Kursor utworzony za pomocą tej metody, gdy powiązany obiekt został zniszczony. Jedynym sposobem, aby tego nie uzyskać, jest utworzenie singletonu kursora i ponowne użycie go wszędzie. Jakiś powód, dla którego to wiesz, spowoduje wyjątek SEH? Mogę zgadywać cały dzień, ale wygląda na to, że obiekt użyty do stworzenia obrazu dla kursora zostaje usunięty, a klasa Cursor wysadza w powietrze b/c tego. – outbred
Dobry przykład, który działa dobrze, ale istnieje błąd, np. 'Info.yHotspot = (byte) (HotSpot.X * cursor.Height);' (powinien być HotSpot.Y, a nie HotSpot.X). W tym przykładzie zmienia się także zakres oryginalnego kodu punktu aktywnego, skalując go według wymiarów źródłowej bitmapy, więc należy o tym pamiętać przy określaniu przesunięcia. –
Wystarczająco uczciwe, poprawione. –
Oto bogate w funkcje, darmowe narzędzie, które pozwala utworzyć plik cur z dowolnego obrazu: http://www.rw-designer.com/cursor-maker
Nazywa się RealWorld Cursor Editor.
A oto link na jak umieścić kursor w projekcie:
W przypadku ktoś szuka UIElement się jako kursorem, I połączył rozwiązania Ray i 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'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);
}
Czyściłbym to, używając instrukcji do twoich strumieni, ale poza tym nie mam problemów z tą metodą (w przeciwieństwie do innych implementacji). – outbred
Zauważyłem, że wywoływanie polecenia 'Rozmieść' na kontrolce powoduje chwilowe zniknięcie zarówno obiektu ListBoxItems, jak i obiektu TreeViewItems, a następnie ponowne pojawienie się później po zmianie układu macierzystego (np. Rozwinięcie elementu TreeViewItem). Masz pomysł, dlaczego tak jest? –
można to zrobić przez kodeksu jak
this.Cursor = new Cursor(@"<your address of icon>");
Może to się zmieniło z Visual Studio 2017, ale udało mi się odwoływać plik .cur jako osadzonego zasobu:
<Setter
Property="Cursor"
Value="/assembly-name;component/location-name/curser-name.cur" />
1st przykład nie pracuje już bez autoryzacji. –
Należy również pamiętać, że można skonstruować kursor w locie z dowolnej zawartości WPF. Zobacz, jak to zrobić, zobacz http://stackoverflow.com/questions/2835502/rotating-cursor- recording-to-rotated-textbox/2836904#2836904. –
Link opublikowany w poprzedniej commem dotyczy obracania istniejącego kursora.Właśnie napisałem nową odpowiedź na to pytanie (patrz poniżej), która mówi, jak przekonwertować dowolny wizualny na kursor. –