65.9K
CodeProject 正在变化。 阅读更多。
Home

创建“进度光标”

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (50投票s)

2011年9月6日

CPOL

2分钟阅读

viewsIcon

85646

downloadIcon

3378

一个实用工具,用于将圆形进度条显示为光标。

progresscursor/cursor.png

引言

本文档解释了如何自定义光标以显示圆形进度条。

由于我经常收到关于扩展此实用程序功能的提问,它现在已经进入了 GitHub 的开源世界。你可以在这里fork 仓库.  

类图

progresscursor/classdiagram.png

使用代码

使用代码非常简单,如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();
1-1 ProgressCursor 的基本用法

该库还具有一些可扩展性,通过处理 '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;
}
1-2 使用事件扩展 ProgressCursor

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
    }
}
1-3 ProgressCursor 实现 IDisposable

为什么要实现 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;
}
2-2 光标用法的经典示例

工作原理

创建自定义光标

基本上,所有的“繁重工作”都由两个导入的 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);
}
1-3 P/Invoke 方法

这些方法在 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);
}
1-4 光标魔法!

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:初始版本。
© . All rights reserved.