문제

WPF 앱에서 이미지나 아이콘을 사용자 정의 커서로 사용하고 싶습니다.가장 좋은 방법은 무엇입니까?

도움이 되었습니까?

해결책

두 가지 기본 옵션이 있습니다.

  1. 마우스 커서가 컨트롤 위에 있으면 다음을 설정하여 시스템 커서를 숨깁니다. this.Cursor = Cursors.None; 원하는 기술을 사용하여 자신만의 커서를 그려보세요.그런 다음 마우스 이벤트에 응답하여 커서의 위치와 모양을 업데이트합니다.다음은 두 가지 예입니다.

  2. .cur 또는 .ani 파일에서 이미지를 로드하여 새 Cursor 개체를 만듭니다.Visual Studio에서 이러한 종류의 파일을 만들고 편집할 수 있습니다.또한 이를 처리하기 위해 떠다니는 무료 유틸리티도 있습니다.기본적으로 커서가 이미지의 어느 지점에 있는지 나타내는 "핫스팟"을 지정하는 이미지(또는 애니메이션 이미지)입니다.

파일에서 로드하기로 선택한 경우 다음을 사용하려면 절대 파일 시스템 경로가 필요합니다. Cursor(string fileName) 건설자.라멜리, 상대 경로나 Pack 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가 있는 경우 다음 코드를 사용하여 커서를 생성할 수 있습니다.

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에서 사용자 정의 커서를 사용하기 위해 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 솔루션을 사용해 보았지만 충분히 우아하다고 생각하지 않았습니다.PeterAllen은 다음과 같이 말했습니다.

안타깝게도 상대 경로나 Pack URI는 작동하지 않습니다.상대 경로나 어셈블리로 압축된 리소스에서 커서를 로드해야 하는 경우 파일에서 스트림을 가져와서 Cursor(Stream 커서Stream) 생성자에 전달해야 합니다.짜증나지만 사실이다.

나는 이것을 수행하는 좋은 방법을 발견하고 내 문제를 해결했습니다.

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

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를 사용하는 경우 다음을 수행할 수 있습니다.

  1. 새 커서 파일
  2. 이미지 복사/붙여넣기
  3. .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" />
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top