创建“进度光标”






4.93/5 (50投票s)
一个实用工具,用于将圆形进度条显示为光标。
引言
本文档解释了如何自定义光标以显示圆形进度条。
由于我经常收到关于扩展此实用程序功能的提问,它现在已经进入了 GitHub 的开源世界。你可以在这里fork 仓库.
类图
使用代码
使用代码非常简单,如1-1所示。
var progressCursor = Van.Parys.Windows.Forms.CursorHelper.StartProgressCursor(100);
for (int i = 0; i < 100; i++)
{
progressCursor.IncrementTo(i);
//do some work
}
progressCursor.End();
该库还具有一些可扩展性,通过处理 'EventHandler<CursorPaintEventArgs> CustomDrawCursor
' 事件。通过处理此事件,开发人员可以选择通过在 CursorPaintEventArgs
实例上运行 DrawDefault
方法来扩展默认行为 (1-2)。
...
progressCursor.CustomDrawCursor += progressCursor_CustomDrawCursor;
...
void progressCursor_CustomDrawCursor(object sender,
ProgressCursor.CursorPaintEventArgs e)
{
e.DrawDefault();
//add text to the default drawn cursor
e.Graphics.DrawString("Test",
SystemFonts.DefaultFont, Brushes.Black, 0,0);
//set Handled to true, or else nothing will happen,
//and default painting is done
e.Handled = true;
}
IProgressCursor
还实现了 IDisposable
,这使得 'using
' 语句对此接口有效。优点是无需进行自定义异常处理来确保在 ProgressCursor
上调用 End()
方法. 用法示例见 1-3。
using (var progressCursor = CursorHelper.StartProgressCursor(100))
{
for (int i = 0; i < 100; i++)
{
progressCursor.IncrementTo(i);
//simulate some work
}
}
为什么要实现 IDisposable
默认光标类的经典用法如下
private void DoStuff()
{
Cursor.Current = Cursors.WaitCursor;
try
{
//do heavy duty stuff here...
}
finally
{
Cursor.Current = Cursors.Default;
}
}
如果不是以这种方式实现光标更改,光标可能会“卡住”并保持“WaitCursor”。为了避免这种 Try Finally 编码风格,我在 IProgressCursor
上实现了 IDisposable
,如下所示 (2-2)
public ProgressCursor(Cursor originalCursor)
{
OriginalCursor = originalCursor;
}
~ProgressCursor()
{
Dispose();
}
public void Dispose()
{
End();
}
public void End()
{
Cursor.Current = OriginalCursor;
}
工作原理
创建自定义光标
基本上,所有的“繁重工作”都由两个导入的 user32.dll 方法完成 (1-3)。这些可以在 UnManagedMethodWrapper
类中找到(这个类的命名是否合适?)。
public sealed class UnManagedMethodWrapper
{
[DllImport("user32.dll")]
public static extern IntPtr CreateIconIndirect(ref IconInfo iconInfo);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetIconInfo(IntPtr iconHandle, ref IconInfo iconInfo);
}
这些方法在 CreateCursor
中调用 (1-4)
private Cursor CreateCursor(Bitmap bmp, Point hotSpot)
{
//gets the 'icon-handle' of the bitmap
//(~.net equivalent of bmp as Icon)
IntPtr iconHandle = bmp.GetHicon();
IconInfo iconInfo = new IconInfo();
//fill the IconInfo structure with data from the iconHandle
UnManagedMethodWrapper.GetIconInfo(iconHandle, ref iconInfo);
//set hotspot coordinates
iconInfo.xHotspot = hotSpot.X;
iconInfo.yHotspot = hotSpot.Y;
//indicate that this is a cursor, not an icon
iconInfo.fIcon = false;
//actually create the cursor
iconHandle =
UnManagedMethodWrapper.CreateIconIndirect(ref iconInfo);
//return managed Cursor object
return new Cursor(iconHandle);
}
MSDN 文档
圆形进度光标绘制
int fontEmSize = 7;
var totalWidth = (int) Graphics.VisibleClipBounds.Width;
var totalHeight = (int) Graphics.VisibleClipBounds.Height;
int margin_all = 2;
var band_width = (int) (totalWidth*0.1887);
int workspaceWidth = totalWidth - (margin_all*2);
int workspaceHeight = totalHeight - (margin_all*2);
var workspaceSize = new Size(workspaceWidth, workspaceHeight);
var upperLeftWorkspacePoint = new Point(margin_all, margin_all);
var upperLeftInnerEllipsePoint = new Point(upperLeftWorkspacePoint.X + band_width,
upperLeftWorkspacePoint.Y + band_width);
var innerEllipseSize = new Size(((totalWidth/2) - upperLeftInnerEllipsePoint.X)*2,
((totalWidth/2) - upperLeftInnerEllipsePoint.Y)*2);
var outerEllipseRectangle =
new Rectangle(upperLeftWorkspacePoint, workspaceSize);
var innerEllipseRectangle =
new Rectangle(upperLeftInnerEllipsePoint, innerEllipseSize);
double valueMaxRatio = (Value/Max);
var sweepAngle = (int) (valueMaxRatio*360);
var defaultFont = new Font(SystemFonts.DefaultFont.FontFamily,
fontEmSize, FontStyle.Regular);
string format = string.Format("{0:00}", (int) (valueMaxRatio*100));
SizeF measureString = Graphics.MeasureString(format, defaultFont);
var textPoint = new PointF(upperLeftInnerEllipsePoint.X +
((innerEllipseSize.Width - measureString.Width)/2),
upperLeftInnerEllipsePoint.Y +
((innerEllipseSize.Height - measureString.Height)/2));
Graphics.Clear(Color.Transparent);
Graphics.DrawEllipse(BorderPen, outerEllipseRectangle);
Graphics.FillPie(FillPen, outerEllipseRectangle, 0, sweepAngle);
Graphics.FillEllipse(new SolidBrush(Color.White), innerEllipseRectangle);
Graphics.DrawEllipse(BorderPen, innerEllipseRectangle);
Graphics.DrawString(format, defaultFont, FillPen, textPoint);
它(试图)解决什么
最终用户倾向于认为在没有进度可视化的过程中等待的时间比有进度指示的过程更长。
历史
- 2011-08-30:初始版本。