[WPF][C#] 沒錯!!XAML寫的UserControl也能當作滑鼠游標!!

  • 12275
  • 0
  • C#
  • 2013-07-14

通常在實作系統的時候,為了配合不同的需求,可能會需要自訂游標的形狀,雖然WPF中有Cursors類別可以用,但是提供的游標仍然有限,所以用圖片來當作游標,或是用XAML寫出來的UserControl來當作游標,似乎就成為不錯的選擇。
這次就來介紹怎麼在WPF中覆寫原來的系統游標,以自訂的游標取代。

 

通常在實作系統的時候,為了配合不同的需求,可能會需要自訂游標的形狀,雖然WPF中有Cursors類別可以用,但是提供的游標仍然有限,所以用圖片來當作游標,或是用XAML寫出來的UserControl來當作游標,似乎就成為不錯的選擇。

這次就來介紹怎麼在WPF中覆寫原來的系統游標,以自訂的游標取代。

 

首先,我們需要實作一個CursorHelper類別,用來覆寫原來的系統游標,而這個類別會使用到System.Drawing類別庫,所以別忘記引用這個類別庫喔~

image

接著就是建立CursorHelper.cs啦(這邊就不詳述了,直接提供原始碼供各位服用)~

CursorHelper.cs
namespace Wpf_CustomCursor
{
    using System;
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Security.Permissions;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Interop;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using Microsoft.Win32.SafeHandles;

    public class CursorHelper
    {
        private static class NativeMethods
        {
            //參考 http://msdn.microsoft.com/en-us/library/ms648052(v=vs.85).aspx ,做出一樣的Struct,用來複寫系統的游標。
            public struct IconInfo
            {
                public bool fIcon;
                public int xHotspot;
                public int yHotspot;
                public IntPtr hbmMask;
                public IntPtr hbmColor;
            }

            [DllImport( "user32.dll" )]
            public static extern SafeIconHandle CreateIconIndirect( ref IconInfo icon );

            [DllImport( "user32.dll" )]
            public static extern bool DestroyIcon( IntPtr hIcon );

            [DllImport( "user32.dll" )]
            [return: MarshalAs( UnmanagedType.Bool )]
            public static extern bool GetIconInfo( IntPtr hIcon , ref IconInfo pIconInfo );
        }

        [SecurityPermission( SecurityAction.LinkDemand , UnmanagedCode = true )]
        private class SafeIconHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public SafeIconHandle()
                : base( true )
            {
            }

            override protected bool ReleaseHandle()
            {
                return NativeMethods.DestroyIcon( handle );
            }
        }

        /// <summary>
        /// 透過Bitmap建立圖形游標
        /// </summary>
        /// <param name="bitmap">要當成游標的Bitmap</param>
        /// <param name="xHotSpot">游標頂點的X軸位移</param>
        /// <param name="yHotSpot">游標頂點的Y軸位移</param>
        /// <returns>自訂的游標物件</returns>
        private static Cursor InternalCreateCursor( System.Drawing.Bitmap bitmap , int xHotSpot , int yHotSpot )
        {
            var iconInfo = new NativeMethods.IconInfo();
            NativeMethods.GetIconInfo( bitmap.GetHicon() , ref iconInfo );

            iconInfo.xHotspot = xHotSpot;
            iconInfo.yHotspot = yHotSpot;
            iconInfo.fIcon = false;

            SafeIconHandle cursorHandle = NativeMethods.CreateIconIndirect( ref iconInfo );
            return CursorInteropHelper.Create( cursorHandle );
        }

        /// <summary>
        /// 用來以UIElement建立自定游標
        /// </summary>
        /// <param name="element">要當成游標的UIElement</param>
        /// <param name="xHotSpot">游標頂點的X軸位移</param>
        /// <param name="yHotSpot">游標頂點的Y軸位移</param>
        /// <returns>自訂的游標物件</returns>
        public static Cursor CreateCursor( UIElement element , int xHotSpot = 0 , int yHotSpot = 0 )
        {
            element.Measure( new Size( double.PositiveInfinity , double.PositiveInfinity ) );
            element.Arrange( new Rect( new Point() , element.DesiredSize ) );

            RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(
                ( int ) element.DesiredSize.Width , ( int ) element.DesiredSize.Height ,
                96 , 96 , PixelFormats.Pbgra32 );

            renderTargetBitmap.Render( element );

            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add( BitmapFrame.Create( renderTargetBitmap ) );

            using( var memoryStream = new MemoryStream() )
            {
                encoder.Save( memoryStream );
                using( var bitmap = new System.Drawing.Bitmap( memoryStream ) )
                {
                    return InternalCreateCursor( bitmap , xHotSpot , yHotSpot );
                }
            }
        }
    }
}

原理是透過Window的API,將UIElement轉為Bitmap去覆寫作業系統內建的游標 (但是因為會轉成Bitmap,所以如果XAML中套用有動畫效果的話,動畫將不會起作用)。

用法也相當的簡單,只要呼叫CursorHelper.CreateCursor,傳入UIElement(可以直接像範例一樣實作自己的UserControl當做游標,並依照需求給予游標頂點的位移值就行啦~

我自己寫了一個小範例,大概長得像下面的樣子:

image image
image  

 

不過這次的範例如果要放上來讓大家體驗的話,必需要將專案發佈而且設定為Full Trust Application才行,所以就只先提供原始碼讓有興趣的朋友們自行體驗啦!!~

 

範例原始碼在此,請自行服用: