質問
WPF アプリで画像またはアイコンをカスタム カーソルとして使用したいと考えています。最善の方法は何ですか?
解決
次の 2 つの基本的なオプションがあります。
マウス カーソルがコントロール上にあるときは、設定によってシステム カーソルを非表示にします。
this.Cursor = Cursors.None;
好みのテクニックを使用して独自のカーソルを描画します。次に、マウス イベントに応答してカーソルの位置と外観を更新します。以下に 2 つの例を示します。.cur または .ani ファイルから画像をロードして、新しい Cursor オブジェクトを作成します。このような種類のファイルは Visual Studio で作成および編集できます。それらに対処するための無料のユーティリティもいくつか出回っています。基本的に、これらは、カーソルが画像内のどの点に位置するかを示す「ホットスポット」を指定する画像 (またはアニメーション画像) です。
ファイルからロードすることを選択した場合は、ファイル システムの絶対パスが必要であることに注意してください。 Cursor(string fileName)
コンストラクタ。ラメリー、 相対パスまたはパック URI は機能しません。 相対パスまたはアセンブリにパックされたリソースからカーソルを読み込む必要がある場合は、ファイルからストリームを取得して、それを Cursor(Stream cursorStream)
コンストラクタ。面倒ですが本当です。
一方、XAML 属性を使用してカーソルを読み込むときに相対パスとしてカーソルを指定する する これは、カーソルを非表示のコントロールにロードし、その参照をコピーして別のコントロールで使用するために使用できるという事実です。試していませんが、うまくいくはずです。
他のヒント
上で述べた 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;
-ベン
カーソル表示を自分で管理したり、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 ファイル形式の詳細については、次の URL を参照してください。 ICO(ファイル形式).
XAML でカスタム カーソルを使用するために、Ben McIntosh が提供したコードを少し変更しました。
<Window.Resources>
<Cursor x:Key="OpenHandCursor">Resources/openhand.cur</Cursor>
</Window.Resources>
カーソルを使用するには、リソースを参照するだけです。
<StackPanel Cursor="{StaticResource OpenHandCursor}" />
非常に簡単な方法は、Visual Studio 内でカーソルを .cur ファイルとして作成し、それをプロジェクトのリソースに追加することです。
次に、カーソルを割り当てたいときに次のコードを追加します。
myCanvas.Cursor = new Cursor(new System.IO.MemoryStream(myNamespace.Properties.Resources.Cursor1));
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);
}
このトピックが数年前のものであることは承知していますが、昨日、プロジェクト リソースからカスタム カーソル ファイルをロードしようとしたところ、同様の問題に遭遇しました。インターネットで解決策を検索しましたが、必要なものが見つかりませんでした。を設定するには 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);
もう 1 つの解決策は、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/Invoke も必要ありません)。
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));
}
これを試してみてもいいでしょう
<Window Cursor=""C:\WINDOWS\Cursors\dinosaur.ani"" />
Scott Hanselman の BabySmash (www.codeplex.com/babysmash) もチェックしてください。彼は、Windows カーソルを非表示にし、キャンバス上に新しいカーソルを表示してから、そのカーソルを「実際の」カーソルに移動するという、より「強引な」方法を使用しました。
詳細はこちらをご覧ください:http://www.hanselman.com/blog/DeveloperDesigner.aspx
GDI リソース (bmp.GetHIcon など) がすべて破棄されていることを確認してください。そうしないと、メモリ リークが発生します。次のコード (アイコンの拡張メソッド) は、WPF で完全に機能します。右下に小さなアイコンが付いた矢印カーソルが作成されます。
述べる:このコードでは、アイコンを使用してカーソルを作成します。現在の UI コントロールは使用しません。
マティアス
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);
Visual Studio を使用している場合は、次のことができます。
- カーソルファイルを新規作成する
- 画像をコピー/貼り付け
- .cur ファイルに保存します。
次のようなコードでこれを行うことができます
this.Cursor = new Cursor(@"<your address of icon>");
Visual Studio 2017 では変更された可能性がありますが、.cur ファイルを埋め込みリソースとして参照できました。
<Setter
Property="Cursor"
Value="/assembly-name;component/location-name/curser-name.cur" />