how-to-use-custom-cursors
来源:互联网 发布:js 模拟表单上传文件 编辑:程序博客网 时间:2024/06/12 19:39
WPF Tutorial - How To Use Custom Cursors
- Tutorials
- .NET
- WPF
- .NET 3.0
- C#
So a while back, I did a tutorial on how to do custom cursors in WinForms. In that post I said that at some point in the future, I would write a post on how to do custom cursors in WPF - and here we are! A lot of the code used today is based off of the code from that previous tutorial, so if you haven't read it, I would go do so before you continue.
Sadly, creating and using a custom cursor for WPF is actually more difficult than for WinForms. This is pretty much all due in the end to a single problem - WPF does not use GDI, but the cursor still does. Since the cursor is something at the Windows level (it needs to exist across all application and work smoothly), it is still GDI, and has all the benefits and flaws that come with that fact. So we end up needing to cross that boundary every time you want to do something special with the cursor in WPF.
But don't worry! Fortunately, WPF wraps all the standard cursors (so you can still easily change to, say, a wait cursor), but as soon as you want to create a special image to use as your cursor, you are on your own. Well, not really on your own - that is what this tutorial is here for. And so, in we go. First, we are going to look at the rather small change to the actual "cursor creation part of the code. If you remember, to create a cursor for WinForms, you can just say something like this:
/* CurPtr gets set to
a pointer to an icon */
Cursor myCur = new Cursor(curPtr);
Sadly, you can't quite do that in WPF. This is because it is a different Cursor object we are creating, and Microsoft did not give us a constructor that takes an IntPtr
. For WinForms, we used a System.Windows.Forms.Cursor
, but for WPF, we will be creating a System.Windows.Input.Cursor
. But while there is no constructor for it, there is still a way to get a Cursor out of an IntPtr. It just happens to be hidden elsewhere:
/* CurPtr gets set to
a pointer to an icon */
SafeFileHandle handle = new SafeFileHandle(ptr, true);
Cursor myCur = System.Windows.Interop.CursorInteropHelper.Create(handle);
So first we have to convert the IntPtr into a SafeHandle
(SafeFileHandle
extends SafeHandle
- you can't just create a SafeHandle
, it is an abstract class). Then we pass that handle into the nice and easy to find (thats sarcasm in case you can't tell) Create
method under System.Windows.Interop.CursorInteropHelper
, and we get a Cursor
back that we can use with WPF.
So if we bring in the rest of the code from the previous tutorial, we might get a class that looks like this:
using System.Windows.Interop;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Windows.Input;
namespace WPFCursorTest
{
public class CursorHelper
{
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 static Cursor CreateCursor(System.Drawing.Bitmap bmp, int xHotSpot,
int yHotSpot)
{
IconInfo tmp = new IconInfo();
GetIconInfo(bmp.GetHicon(), ref tmp);
tmp.xHotspot = xHotSpot;
tmp.yHotspot = yHotSpot;
tmp.fIcon = false;
IntPtr ptr = CreateIconIndirect(ref tmp);
SafeFileHandle handle = new SafeFileHandle(ptr, true);
return CursorInteropHelper.Create(handle);
}
}
}
One thing you will need to remeber if you use this is that we are dealing with a System.Drawing.Bitmap
here. This is the old GDI type bitmap object - a concept foreign to the new WPF classes. So you will probably need to pull in a reference to System.Drawing
in your references section in your Visual Studio project, since WPF projects do not have this reference by default.
I tried, quite hard, to figure out a way to create a cursor in WPF without having to lean on System.Drawing.Bitmap
. But in the end, I need to get an Icon pointer out of the bitmap (thats the function GetHicon
), and the WPF bitmap objects refuse to ever give you a pointer.
But wait, not all hope is lost! I may have to deal with a System.Drawing.Bitmap
, but that does not mean that everyone will need to. It is possible to wrap this method in another method that can take in a WPF construct, instead of a GDI one:
{
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(0, 0, element.DesiredSize.Width,
element.DesiredSize.Height));
RenderTargetBitmap rtb = new RenderTargetBitmap((int)element.DesiredSize.Width,
(int)element.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(element);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream ms = new MemoryStream();
encoder.Save(ms);
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(ms);
ms.Close();
ms.Dispose();
Cursor cur = InternalCreateCursor(bmp, xHotSpot, yHotSpot);
bmp.Dispose();
return cur;
}
Here, I am taking in a UIElement - a core WPF construct. I measure and arrage it (to make sure that it is internally rendered properly), and then I "take a picture of it". I do this by creating a RenderTargetBitmap
. This is a class for WPF thats lets you take a UIElement and draw it to a bitmap - but remember, this is a WPF bitmap (a System.Windows.Media.Imaging.RenderTargetBitmap
, to be precise). It is not the same thing as a System.Drawing.Bitmap
. So I create my RenderTargetBitmap
to be the correct size, and I give it a dpi of 96 (pretty standard). Then, I render the UIElement
on the bitmap.
Ok, now I have a RenderTargetBitmap
in my hands, and I want to get to a System.Drawing.Bitmap
. Microsoft could not have possibly made this more difficult, and I'm really quite annoyed by it. There are a couple methods that let you go from a System.Drawing.Bitmap
to a WPF Bitmap (they create a System.Windows.Interop.InteropBitmap
), but there are no methods that go in the other direction. Or at least none that I could find - and I scoured the docs and read through Bitmap code in Reflector for quite a while. If anyone knows of such a method (or a better way than the way I am about to describe), please let me know.
I convert from a RenderTargetBitmap
to a System.Drawing.Bitmap
by first creating an encoder, and encoding my bitmap as a PNG (you could pick any encoder you like). I then create a MemoryStream
and save my encoded bitmap to the stream. And now, since the System.Drawing.Bitmap
has a constructor that can take a stream, I can create my new bitmap. Once that is done, I clean up the memory stream, and proceed to create the cursor. And, of course, once the cursor is created, I dispose the System.Drawing.Bitmap
that I had created.
So in the end, the code for the whole class looks like this:
using System.Windows.Interop;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using System.Windows;
namespace WPFCursorTest
{
public class CursorHelper
{
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);
private static Cursor InternalCreateCursor(System.Drawing.Bitmap bmp,
int xHotSpot, int yHotSpot)
{
IconInfo tmp = new IconInfo();
GetIconInfo(bmp.GetHicon(), ref tmp);
tmp.xHotspot = xHotSpot;
tmp.yHotspot = yHotSpot;
tmp.fIcon = false;
IntPtr ptr = CreateIconIndirect(ref tmp);
SafeFileHandle handle = new SafeFileHandle(ptr, true);
return CursorInteropHelper.Create(handle);
}
public static Cursor CreateCursor(UIElement element, int xHotSpot, int yHotSpot)
{
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(0, 0, element.DesiredSize.Width,
element.DesiredSize.Height));
RenderTargetBitmap rtb = new RenderTargetBitmap((int)element.DesiredSize.Width,
(int)element.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(element);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream ms = new MemoryStream();
encoder.Save(ms);
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(ms);
ms.Close();
ms.Dispose();
Cursor cur = InternalCreateCursor(bmp, xHotSpot, yHotSpot);
bmp.Dispose();
return cur;
}
}
}
Kind of ugly, don't you think? But at least it is encapsulated.
Now you probably wondering how to use this class. Well, it is really easy:
{
public CursorTest()
{
InitializeComponent();
TextBlock tb = new TextBlock();
tb.Text = "{ } Switch On The Code";
tb.FontSize = 10;
tb.Foreground = Brushes.Green;
this.Cursor = CursorHelper.CreateCursor(tb, 5, 5);
}
}
Here, I have a window, and I want my cursor to say "{ } Switch On The Code". So in the constructor, I make a TextBlock
with that text, and just call CreateCursor
. And all I need to do is set the Cursor
property of my window to the result. That code would make something that looks like this:
I hope this code helps anyone who has been trying to create and use custom cursors in WPF. If you would like, you can download a Visual Studio solution here, which contains both the CursorHelper
class and the sample test window shown above. And if anyone comes up with a way to get rid of the need for having to bring in System.Drawing
, or figures out how to convert from a WPF and GDI bitmap easily, let us know! As always, questions and comments are welcome.
http://www.switchonthecode.com/tutorials/wpf-tutorial-how-to-use-custom-cursors
- how-to-use-custom-cursors
- How to Use Custom NSAttributedString Attributes
- How to use custom delegates in Objective-C
- How to use custom delegates in Objective-C
- How to Use Custom TTF Font on iOS
- HOW TO Custom DSDT
- How to use isInEditMode() to see layout with custom View in the editor
- Custom CSS Cursors
- How to custom RedHat DVD
- How to add custom scripts
- How To Use DataGird
- how to use typedef
- how to use gz
- How to use chkconfig
- How to use ,,,,
- how to use dialog
- How to use UIDs
- How to use dmalloc
- 架构设计师与SOA
- 如何在ActiveX控件中使用字体3
- Oracle 临时表的创建与应用
- Step by step learn PPC-----1-----开发环境
- toString、hashCode、equals的重写原因与重写示例
- how-to-use-custom-cursors
- 如何更改服务器身份验证模式
- 富士康面试经历
- 使用VC6和com控制excel的一些方法 (试验有效)
- 未能加载文件或程序集system.web.extensions解决方法
- ==, .equals(), compareTo(), and compare() (之一)
- 小测SQLite内存与非内存万条插入
- Android Bind Service机制详解
- 第7章 函数