一个简单的 GDI+ 时钟






4.70/5 (10投票s)
这是一个简单的模拟时钟,演示了 GDI+ 的使用。
引言
该代码展示了使用 GDI+ 进行简单绘图任务的简洁性。我用它制作了一个简单的桌面时钟。在这个小小的时钟中,用户可以选择他们想要的时钟图像(这只是一个图像文件);我还添加了一个小功能,即每个时钟图像都可以关联一个可选的.ini文件,其中包含该时钟图像的设置。这些设置包括:每个时钟指针的颜色和大小、时钟的中心(以防图像不对称)、显示日期等。
我实际上在项目文件夹中放入了一些时钟图像及其.ini文件,以演示此程序的特点。
代码结构
有一个 `AceClockForm` 类,它是程序中唯一使用的窗体,所有使用 GDI+ 的计算和绘图都在这个类中完成。`INISettingsReaderWriter` 类用于存储和检索程序的设置,以及检索时钟的设置。最后,`Settings` 和 `ClockSettings` 类存储程序和时钟的设置。
绘制时钟
数学原理!
要绘制时钟指针,在我的应用程序中它们只是简单的线,我们需要知道它们的坐标!对于每条线,线的两个点之一就是时钟的中心;对于另一个点,首先计算该线(或时钟指针)的角度。
// alpha is the angle of the hour hand
// second part(now.Minute / 2) is in fact now.Minute/60*(360/12)
double alpha = now.Hour * (360/12) + now.Minute / 2;
//beta is the angle of the minute hand
//in fact now.Minute * (360/60) + now.Second /60 * (360/60)
double beta = now.Minute * (360/60) + now.Second / 10;
//gama is the angle of the second hand
double gama = (now.Second+(drawSHContinuously? now.Millisecond/1000f : 0f)) * 360/60;
这是解释
时针:时钟表盘的圆周被分成12小时,所以每小时占圆周的1/12,而圆周是360度,所以每小时是360/12度。所以时针的角度是这样计算的,但正如大家所注意到的,没有一个模拟时钟的时针会精确地停在当前的小时数上,直到一个小时过去。相反,它会缓慢地朝下一个数字移动。为了计算这个角度,需要考虑时间的分钟部分。计算方法几乎相同,除了因为每60分钟等于一小时,所以要除以60。
分针:它和时针一样,不同之处在于每分钟占圆周的1/60,所以是360/60度。同样,为了使其看起来更流畅,需要考虑秒数,因为每60秒等于一分钟,所以要除以60。
秒针:同样,每秒占圆周的1/60,等等……但是有一个 `drawSHContinuously` 的功能。在我的应用程序中,有一个选项允许秒针连续绘制。如果启用了该选项,那么毫秒就会变得重要,因为在每2秒之间,秒针会被重绘很多次。
到目前为止,每个指针的角度都已计算出来,但这些角度是以度为单位的,它们是顺时针的(与三角学相反!),并且相对于垂直线。因此,它们被转换为逆时针、弧度、相对于水平线的角度。
alpha = -alpha * Math.PI / 180d + Math.PI / 2d;
beta = -beta * Math.PI / 180d + Math.PI / 2d;
gama = -gama * Math.PI / 180d + Math.PI / 2d;
然后,使用这些角度来计算每个指针末端的坐标。这在 `getTaleCoordinates()` 方法中完成,该方法获取直线的端点坐标、长度和角度,并计算其另一个端点。
使用 GDI+ 绘制时钟
我在代码中使用了 `BufferedGraphics` 来消除时钟图像可能的闪烁。这个 `BufferedGraphics` 在代码的 `SetImage()` 方法中分配。`BufferedGraphics` 类有一个 `Graphics` 属性,它返回用于绘制时钟的 `Graphics` 对象。
实际绘图是在 `drawClock()` 方法中完成的。下面这行代码绘制了时钟图像
bgg.DrawImage(this.image, 0, 0, this.Width, this.Height);
而这些行绘制了时钟指针
//hour hand
bgg.DrawLine(hourPen,
getTaleCoordinates(centerPoint.X, centerPoint.Y,
clockSettings.HourTale, alpha+Math.PI),
getTaleCoordinates(centerPoint.X, centerPoint.Y,
clockSettings.HourLength, alpha));
//minute hand
bgg.DrawLine(minutePen,
getTaleCoordinates(centerPoint.X, centerPoint.Y,
clockSettings.MinuteTale, beta + Math.PI),
getTaleCoordinates(centerPoint.X, centerPoint.Y,
clockSettings.MinuteLength, beta));
//second hand
bgg.DrawLine(secondPen, centerPoint,
getTaleCoordinates(centerPoint.X, centerPoint.Y,
clockSettings.SecondTale, gama + Math.PI));
bgg.DrawLine(secondPen, centerPoint,
getTaleCoordinates(centerPoint.X, centerPoint.Y,
clockSettings.SecondLength, gama));
`DrawLine` 方法的第一个参数是 `Pen`,第二个和第三个参数是要绘制的直线的端点。
日期也已绘制。`DrawString` 方法用于在 GDI+ 中绘制字符串。这个方法的使用很简单,它接受要绘制的字符串、绘制字符串的字体、一个可以用来定义文本纹理的画笔,以及字符串左上角的坐标。这是代码
bgg.DrawString(now.ToShortDateString(), dateFont, dateBrush,
clockSettings.DateCenterX - strsize.Width / 2,
clockSettings.DateCenterY - strsize.Height / 2);
Settings 和 ClockSettings 类
`Settings` 类存储程序的设置,`ClcokSettings` 类存储时钟的设置。加载和保存这些类的数据将在下面解释。
加载和保存时钟和程序设置
`INISettingsReaderWriter` 类用于保存和加载时钟和应用程序本身的设置。顾名思义,这些设置以 INI 文件的形式存储,其中每条数据都以这种形式存储在单独的一行中
Key=Value ;this is a comment!
这个类使用正则表达式来解析 INI 文件中存储的数据,并使用反射将这些数据填充到对象中,或将其写入文件。这是使用的正则表达式模式
static Regex iniFilePattern =
new Regex(@"^\s*(?<key>\w+)\s*=[\s-[\n]]*(?<value>" +
@"[\w//:/. -]*)/s*(;.*)?\\:\.]*)\s*(;.*)?",
RegexOptions.Multiline | RegexOptions.Compiled);
`dictionaryToObject` 和 `objcetToDictionary` 方法用于将数据存储到给定对象的属性中的 `System.Collections.Generic.Dictionary
Type t=obj.GetType();
foreach (var pi in t.GetProperties())
{
...
}
历史
- 版本 1.0。