سؤال

أريد استخدام صورة أو رمز كمؤشر مخصص في تطبيق WPF.ما هي أفضل طريقة للقيام بذلك؟

هل كانت مفيدة؟

المحلول

لديك خياران أساسيان:

  1. عندما يكون مؤشر الماوس فوق سيطرتك، قم بإخفاء مؤشر النظام عن طريق الإعداد this.Cursor = Cursors.None; وارسم المؤشر الخاص بك باستخدام أي تقنية تريدها.ثم قم بتحديث موضع المؤشر ومظهره من خلال الاستجابة لأحداث الماوس.فيما يلي مثالان:

  2. قم بإنشاء كائن مؤشر جديد عن طريق تحميل صورة من ملف .cur أو .ani.يمكنك إنشاء وتحرير هذه الأنواع من الملفات في Visual Studio.هناك أيضًا بعض الأدوات المساعدة المجانية للتعامل معها.إنها في الأساس صور (أو صور متحركة) تحدد "نقطة فعالة" تشير إلى النقطة التي تم وضع المؤشر عليها في الصورة.

إذا اخترت التحميل من ملف، فلاحظ أنك تحتاج إلى مسار نظام ملفات مطلق لاستخدام ملف Cursor(string fileName) البناء.عرجاء، لن يعمل المسار النسبي أو URI للحزمة. إذا كنت بحاجة إلى تحميل المؤشر من مسار نسبي أو من مورد مملوء بالتجميع الخاص بك، فسوف تحتاج إلى الحصول على دفق من الملف وتمريره إلى Cursor(Stream cursorStream) البناء.مزعج ولكن صحيح.

ومن ناحية أخرى، تحديد المؤشر كمسار نسبي عند تحميله باستخدام سمة XAML يفعل العمل، وهي حقيقة يمكنك استخدامها لتحميل المؤشر على عنصر تحكم مخفي ثم نسخ المرجع لاستخدامه في عنصر تحكم آخر.لم أحاول ذلك، ولكن يجب أن تعمل.

نصائح أخرى

كما ذكر بيتر أعلاه، إذا كان لديك بالفعل ملف .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 على إيكو (تنسيق الملف).

لاستخدام مؤشر مخصص في 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); 

هناك حل آخر يشبه إلى حد ما حل 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/Invoces أيضًا):

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"" />

تحقق أيضًا من BabySmash من Scott Hanselman (www.codeplex.com/babysmash).لقد استخدم طريقة "القوة الغاشمة" لإخفاء مؤشر النوافذ وإظهار مؤشره الجديد على لوحة قماشية ثم تحريك المؤشر إلى مكان المؤشر "الحقيقي"

اقرأ المزيد هنا:http://www.hanselman.com/blog/DeveloperDesigner.aspx

تأكد من التخلص من أي مورد 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);

إذا كنت تستخدم الاستوديو المرئي، يمكنك ذلك

  1. جديد ملف المؤشر
  2. نسخ/لصق الصورة
  3. احفظه في ملف .cur.

يمكنك القيام بذلك عن طريق Code like

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