使用GDI接口在视图上绘制一个时钟。
GDI是Graphics Device Interface的缩写,含义是图形设备接口,它的主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形输出。
在Windows操作系统下,绝大多数具备图形界面的应用程序都离不开GDI,我们利用GDI所提供的众多函数就可以方便的在屏幕、打印机及其它输出设备上输出图形,文本等操作。
GDI函数大致可分类为:
设备上下文函数(如GetDC、CreateDC、DeleteDC)、 画线函数(如LineTo、Polyline、Arc)、填充画图函数(如Ellipse、FillRect、Pie)、画图属性函数(如SetBkColor、SetBkMode、SetTextColor)、文本、字体函数(如TextOut、GetFontData)、位图函数(如SetPixel、BitBlt、StretchBlt)、坐标函数(如DPtoLP、LPtoDP、ScreenToClient、ClientToScreen)、映射函数(如SetMapMode、SetWindowExtEx、SetViewportExtEx)、元文件函数(如PlayMetaFile、SetWinMetaFileBits)、区域函数(如FillRgn、FrameRgn、InvertRgn)、路径函数(如BeginPath、EndPath、StrokeAndFillPath)、裁剪函数(如SelectClipRgn、SelectClipPath)等。
——百度百科
最初想法:
建立一个单文档工程,在OnDraw利用
1 2 CClientDC dc (this ) ;dc.Ellipse (CRect (100 ,100 ,500 ,500 ));
画圆,但是打开的窗口太大了。于是,想初始化窗口,在CMainFrame里的PreCreateWindow设置窗口大小:
1 2 3 4 5 6 7 8 9 10 11 BOOL CMainFrame::PreCreateWindow (CREATESTRUCT& cs) { if ( !CFrameWnd::PreCreateWindow (cs) ) return FALSE; cs.cx = 630 ; cs.cy = 680 ; return TRUE; }
接下来画钟面上的格子。用MoveTo和LineTo确定线段起始点和终点。我用了一个比较蠢的方法:手算坐标!于是,产生了这样的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 void CClockView::OnDraw (CDC* pDC) { CClockDoc* pDoc = GetDocument (); ASSERT_VALID (pDoc); CClientDC dc (this ) ; dc.Ellipse (CRect (100 ,100 ,500 ,500 )); dc.MoveTo (300 ,140 ); dc.LineTo (300 ,100 ); dc.MoveTo (300 ,460 ); dc.LineTo (300 ,500 ); dc.MoveTo (140 ,300 ); dc.LineTo (100 ,300 ); dc.MoveTo (460 ,300 ); dc.LineTo (500 ,300 ); dc.MoveTo (380 ,300 -80 *1.73 ); dc.LineTo (400 ,300 -100 *1.73 ); dc.MoveTo (300 +80 *1.73 ,220 ); dc.LineTo (300 +100 *1.73 ,200 ); dc.MoveTo (300 +80 *1.73 ,380 ); dc.LineTo (300 +100 *1.73 ,400 ); dc.MoveTo (380 ,300 +80 *1.73 ); dc.LineTo (400 ,300 +100 *1.73 ); dc.MoveTo (220 ,300 +80 *1.73 ); dc.LineTo (200 ,300 +100 *1.73 ); dc.MoveTo (300 -80 *1.73 ,380 ); dc.LineTo (300 -100 *1.73 ,400 ); dc.MoveTo (300 -80 *1.73 ,220 ); dc.LineTo (300 -100 *1.73 ,200 ); dc.MoveTo (220 ,300 -80 *1.73 ); dc.LineTo (200 ,300 -100 *1.73 ); }
[连三角函数都近似等于的我……等会儿会谈优化问题]
效果是这样:
在视类添加WM_CREATE响应函数,设置一个定时器,1秒发送一次消息。
1 2 3 4 5 6 7 8 9 10 int CClockView::OnCreate (LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate (lpCreateStruct) == -1 ) return -1 ; SetTimer (1 , 1000 , NULL ); return 0 ; }
因为指针是在动的,所以要及时刷新页面,在视类添加WM_TIMER响应函数:
1 2 3 4 5 6 7 8 void CClockView::OnTimer (UINT nIDEvent) { InvalidateRect (NULL , TRUE); UpdateWindow (); CView::OnTimer (nIDEvent); }
为了和系统时间一致,就要获取时间,在OnDraw中加入:
1 CTime Time = CTime::GetCurrentTime ();
接下来就是创建三个指针:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 double Radians;Radians = Time.GetHour () + Time.GetMinute ()/60.0 + Time.GetSecond ()/3600.0 ; Radians *= 2 * 3.14 / 12.0 ; dc.MoveTo (300 , 300 ); dc.LineTo (300 + (int )((double )100 * sin (Radians)), 300 - (int )((double )100 * cos (Radians))); Radians = Time.GetMinute () + Time.GetSecond ()/60 ; Radians *= 2 * 3.14 / 60.0 ; dc.MoveTo (300 , 300 ); dc.LineTo (300 + (int )((double )150 * sin (Radians)), 300 - (int )((double )150 * cos (Radians))); Radians = Time.GetSecond (); Radians *= 2 * 3.14 / 60.0 ; dc.MoveTo (300 , 300 ); dc.LineTo (300 + (int )((double )190 * sin (Radians)), 300 - (int )((double )190 * cos (Radians)));
为了三角函数能用,在函数头部添加#include "math.h"
。
美化界面:
1 2 3 4 5 6 7 BOOL CClockApp::InitInstance () {… m_pMainWnd->SetWindowText ("博靖牌时钟 V1.0" ); return TRUE; }
隐藏菜单、状态栏、工具条:
1 2 3 4 5 6 7 8 9 10 int CMainFrame::OnCreate (LPCREATESTRUCT lpCreateStruct) { … SetMenu (NULL ); ShowControlBar (&m_wndToolBar,FALSE,FALSE); ShowControlBar (&m_wndStatusBar,FALSE,FALSE); return 0 ; }
效果:
详解:
InvalidateRect是一个函数,该函数向指定的窗体更新区域添加一个矩形,然后窗口客户区域的这一部分将被重新绘制。
ShowControlBar:
void ShowControlBar(CControlBar* pBar,BOOL bShow,Bool bDelay);
参数:
pBar 指向要显示或隐含的控件条
bShow 如果为TRUE ,指定控件条将显示;如果为FALSE,则隐藏。
bDelay 如果为TRUE,延迟显示控件条;如果为FALSE,则立即显示
说明:
调用该函数显示或隐藏一个控件条。
缺点:1.不能修改指针和线条样式(例如颜色、粗细等)。
2.窗口拉伸后,钟面不能随窗口拉伸。
改进版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 void CClock2View::OnDraw (CDC* pDC) { CClock2Doc* pDoc = GetDocument (); ASSERT_VALID (pDoc); CRect r1; GetClientRect (&r1); CTime Time = CTime::GetCurrentTime (); CPen pen (PS_SOLID, 10 , RGB(139 , 115 , 8 )) ; CPen *pOldPen = pDC->SelectObject (&pen); pDC->Ellipse (CRect (30 , 30 , r1.right-30 , r1.bottom-30 )); CPoint pt = r1.CenterPoint (); pDC->SetTextColor (RGB (255 ,0 ,0 )); CString strNumber; CSize size; double Radians; for (int i = 1 ; i <= 12 ; i++) { strNumber.Format ("%d" ,i); Radians = (double )i*2 *3.14 /12.0 ; size = pDC->GetTextExtent (strNumber,strNumber.GetLength ()); double x = pt.x - (size.cx/2 ) + (int )((double )(pt.x - 70 ) * sin (Radians)); double y = pt.y - (size.cy/2 ) - (int )((double )(pt.y - 70 ) * cos (Radians)); pDC->TextOut (x, y, strNumber); } for (int j = 1 ; j <= 12 ; j++) { Radians = (double )j*2 *3.14 /12.0 ; int x = pt.x + (int )((double )(pt.x - 30 ) * sin (Radians)); int y = pt.y - (int )((double )(pt.y - 30 ) * cos (Radians)); int m = pt.x + (int )((double )(pt.x - 50 ) * sin (Radians)); int n = pt.y - (int )((double )(pt.y - 50 ) * cos (Radians)); pDC->MoveTo (m, n); pDC->LineTo (x, y); } CPen HourPen (PS_SOLID, 6 , RGB(255 , 20 , 147 )) ; pDC->SelectObject (&HourPen); Radians = Time.GetHour () + Time.GetMinute ()/60.0 + Time.GetSecond ()/3600.0 ; Radians *= 2 * 3.14 / 12.0 ; pDC->MoveTo (pt.x, pt.y); pDC->LineTo (pt.x + (int )((double )(pt.x/3 ) * sin (Radians)), pt.y - (int )((double )(pt.y/3 ) * cos (Radians))); CPen MinPen (PS_SOLID, 4 , RGB(78 , 238 , 148 )) ; pDC->SelectObject (&MinPen); Radians = Time.GetMinute () + Time.GetSecond ()/60 ; Radians *= 2 * 3.14 / 60.0 ; pDC->MoveTo (pt.x, pt.y); pDC->LineTo (pt.x + (int )((double )(pt.y*1 /2 ) * sin (Radians)), pt.y - (int )((double )(pt.y*1 /2 ) * cos (Radians))); CPen SecPen (PS_SOLID, 2 , RGB(0 , 0 , 205 )) ; pDC->SelectObject (&SecPen); Radians = Time.GetSecond (); Radians *= 2 * 3.14 / 60.0 ; pDC->MoveTo (pt.x, pt.y); pDC->LineTo (pt.x + (int )((double )(pt.x*2 /3 ) * sin (Radians)), pt.y - (int )((double )(pt.y*2 /3 ) * cos (Radians))); }
详解:
弧长公式:时针:α=l/r=(2πr/12)/r=2π/12 分针、秒针同理。
GetTextExtent
函数功能:使用该函数获得所选字体中指定字符串的高度和宽度
函数原型一:CSize GetTextExtent(LPCTSTR lpszString, int nCount) const;
参数:
lpszString是字符串的指针,也可以用CString 对象
nCount是指字符串的长度
函数原型二: CSize GetTextExtent( const CString& str) const;
参数:
str是一个字符串对象,包含指定的字符。
返回值:
以逻辑单位返回字符串的尺寸,保存在一个CSize对象中。
TextOut,函数名。该函数用当前选择的字体、背景颜色和正文颜色将一个字符串写到指定位置。
效果:
值得改进处:
1.每秒刷新时整个频幕闪动较大。
2.可以突出12、3、6、9的格子长度。
3.调整数字的大小和字体。
4.指针做的更美观,考虑用位图或图片替代。