时钟的实现(MFC)

文章目录

    • 1.预备知识
      • 1.日期和时间类
        • 1.概述
        • 2.构造
        • 3.`CTime`类主要成员函数
        • 3.`CTimeSpan`类主要成员函数
      • 2.计时器
        • 1.创建计时器
        • 2.销毁计时器
      • 3.位图类
        • 1.构造
        • 2.初始化
        • 3.属性
        • 4.操作
    • 2.实验目的
    • 3.实验内容
    • 4.代码实现
      • 1.准备工作
      • 2.基类`CClockBase`
        • ClockBase.h
        • ClockBase.cpp
      • 3.时钟背景类`CClockBackground`
      • 4.时钟指针类`CClockHourHand`、`CClockMinuteHand`、`CClockSecondHand`
      • 5.封装为控件`CClockWidget`
      • 6.实例化控件
    • 5.运行结果
    • 6.总结
      • 1.实验中遇到的困难
        • 如何实现时钟指针
      • 2.项目改进
      • 3.心得体会

1.预备知识

1.日期和时间类

1.概述

在MFC中,用CTime类表示绝对的时间和日期,用CTimeSpan类表示时间间隔。它们都没有基类,是不可派生的。因为没有虚函数,CTimeCTimeSpan对象的大小都正好是4个字节。其大多数成员函数都是内联的。

CTimeCTimeSpan类引入了ANSI time_t数据类型以及其相关的运行时函数,其中包括向或自一个Gregorian日期和24小时时间的转换功能。这些函数将秒转换为日、时、分和秒的各种组合。

CTime值是基于世界标准时间(UCT)的,UCT时间等于格林威治(Greenwich)时间(GMT)。其表示的日期上限是3000年12月31日,下限是1970年1月1日12:00:00 AM GMT。本地时区则是由TZ环境变量控制的。一个CTimeSpan对象以秒为单位保存时间。由于CTimeSpan对象以带符号的四字节数存储,所以最大的时间跨度近似为$\pm$68年。

当创建一个CTime时,将nDST参数设置为0表示有效的是标准时间,或将其设置为大于0表示有效的白天保留时间,将其设置为小于零的值表示由C运行时库代码来计算有效的是标准时间还是白天保留时间。如果没有设置这个参数,则它的值是不明确的,而从mktime返回的值是不可预知的。如果timeptr指向一个由先前调用asctimegmtime,或localtime返回的tm结构,则tm_isdst域包含了适当的值。

2.构造
CTime( );
CTime( const CTime& timeSrc );
CTime( time_t time );
CTime( int nYear, int nMonth, int nDay, int nHour, int nMin, int nSec, int nDST = -1 );
CTime( WORD wDosDate, WORD wDosTime, int nDST = -1 );
CTime( const SYSTEMTIME& sysTime, int nDST = -1 );
CTime( const FILETIME& fileTime, int nDST = -1 );
参数说明
参数含义
timeSrc表示一个已经存在的CTime对象。
time表示一个时间值。
nYear,nMonth,nDay,nHour,nMin,nSec表示要拷贝到新的CTime对象中去的日期和时间值。
nDST表明有效的是否是day light saving time。可以是下列三个值中的某一个:nDST被设置为0表示起作用的是标准时间。nDST被设置为一个大于0的值表示起作用的是day light saving time。nDST被设置为一个小于0的值表示要自动计算起作用的是标准时间还是day light saving time。
wDosDate,wDosTime要被转换为一个日期/时间值并被拷贝到新的CTime对象中去的MSDOS日期和时间。
sysTime要被转换为一个日期/时间值并被拷贝到新的CTime对象中去的SYSTEMTIME结构。
fileTime要被转换为一个日期/时间值并被拷贝到新的CTime对象中去的FILETIME结构。
CTimeSpan( );
CTimeSpan( const CTimeSpan& timeSpanSrc );
CTimeSpan( time_t time );
CTimeSpan( LONG lDays, int nHours, int nMins, int nSecs );
参数说明
参数含义
timeSpanSrc一个已存在的CTimeSpan对象。
time一个time_t时间值。
lDays,nHours,nMins,nSecs分别代表日、时、分、秒
3.CTime类主要成员函数
static CTime PASCAL GetCurrentTime( );

说明:此成员函数返回一个代表当前时间的CTime对象。

time_t GetTime( ) const;

说明:此成员函数返回一个给定CTime对象的time_t值。

int GetYear( ) const;

说明:此成员函数根据本地时间返回范围在1970年1月1日至2038年1月18日之间的年。这个函数调用GetLocalTm,该函数使用了一个内部的、静态分配的缓冲区。调用其它的CTime成员函数会导致这个缓冲区中的数据被覆盖。

int GetMonth( ) const;

说明:此成员函数根据本地时间返回范围在1至12之间的月(1=一月)。这个函数调用GetLocalTm,该函数使用了一个内部的、静态分配的缓冲区。调用其它的CTime成员函数会导致这个缓冲区中的数据被覆盖。

int GetDay( ) const;

说明:此成员函数根据本地时间返回范围在1-31之间的该月的天。这个函数调用GetLocalTm,该函数使用了一个内部的、静态分配的缓冲区。调用其它的CTime成员函数会导致这个缓冲区中的数据被覆盖。

int GetHour( ) const;

说明:此成员函数根据本地时间返回范围在0至23之间的小时。这个函数调用GetLocalTm,该函数使用了一个内部的、静态分配的缓冲区。调用其它的CTime成员函数会导致这个缓冲区中的数据被覆盖。

int GetMinute( ) const;

说明:此成员函数根据本地时间返回范围在0至59之间的分钟。这个函数调用GetLocalTm,该函数使用了一个内部的、静态分配的缓冲区。调用其它的CTime成员函数会导致这个缓冲区中的数据被覆盖。

int GetSecond( ) const;

说明:此成员函数根据本地时间返回范围在0至59之间的秒。这个函数调用GetLocalTm,该函数使用了一个内部的、静态分配的缓冲区。调用其它的CTime成员函数会导致这个缓冲区中的数据被覆盖。

int GetDayOfWeek( ) const;

说明:此成员函数根据本地时间返回该星期的天;1就是星期日,2就是星期一,…,7就是星期六。这个函数调用GetLocalTm,该函数使用了一个内部的,静态分配的缓冲区。调用其它的CTime成员函数会导致这个缓冲区中的数据被覆盖。

CString Format( LPCTSTR pFormat ) const;
CString Format( UINT nFormatID ) const;

返回值:返回一个包含了格式化时间的CString

参数:

  • pFormat 一个类似于printf格式化字符串的格式化字符串。前面有一个百分号(%)标记的格式化代码,被相应的CTime成分替换。格式化字符串中的其它字符被不作变动地拷贝到返回字符串中。参见运行时函数strftime可以获得详细的信息。Format的格式化代码的值和意义如下所示:
    • %a:周的英文缩写形式。
    • %A:周的英文全名形式。
    • %b:月的英文缩写形式。
    • %B:月的英文全名形式。
    • %c:完整的日期和时间。
    • %d:十进制形式的日期(01-31)。
    • %H:24小时制的小时(00-23)。
    • %I:12小时制的小时(00-11)。
    • %j:十进制表示的一年中的第几天(001-366)。
    • %m:月的十进制表示(01-12)。
    • %M:十进制表示的分钟(00-59)。
    • %p:12小时制的上下午标示(AM/PM)。
    • %S:十进制表示的秒(00-59)。
    • %U:一年中的第几个星期(00-51),星期日是一周的第一天。
    • %W:一年中的第几个星期(00-51),星期一是一周的第一天。
    • %w:十进制表示的星期几(0-6)。
    • %Y:十进制表示的年。
  • nFormatID 用来表示这个格式的字符串的ID。

说明:此成员函数用来创建一个日期/时间值的格式化表达式。如果此CTime对象的状态是空,则返回值是一个空字符串。如果CTime对象的状态是无效,则返回值是一个空字符串。

CString FormatGmt( LPCTSTR pFormat ) const;
CString FormatGmt( UINT nFormatID ) const;

说明:此成员函数用来生成一个对应于这个CTime对象的格式化字符串。这个时间值没有被转换,因此是反映UTC的。

const CTime& operator =( const CTime& timeSrc );
const CTime& operator =( time_t t );

说明:这些重载的赋值操作符将源时间拷贝到此CTime对象中。保存在一个CTime对象中的内部时间与时区无关。在赋值操作中不用进行时区转换。

CTime operator +( CTimeSpan timeSpan ) const;
CTime operator - ( CTimeSpan timeSpan ) const;
CTimeSpan operator - ( CTime time ) const;

说明:CTime对象表示绝对时间。CTimeSpan对象表示相对时间。前两个操作符允许你向或从CTime对象中加上或减去一个CTimeSpan对象。第三个操作符允许你将两个CTime对象相减产生一个CTimeSpan对象。

const CTime& operator +=( CTimeSpan timeSpan );
const CTime& operator -=( CTimeSpan timeSpan );

说明:这些操作符允许你从此CTime对象中加上或减去一个CTimeSpan对象。

BOOL operator ==( CTime time ) const;
BOOL operator !=( CTime time ) const;
BOOL operator <( CTime time ) const;
BOOL operator >( CTime time ) const;
BOOL operator <=( CTime time ) const;
BOOL operator >=( CTime time ) const;

说明:这些操作符比较两个绝对时间,如果测试条件为真则返回非零值;否则返回0。

3.CTimeSpan类主要成员函数
LONG GetDays( ) const;

说明:此成员函数返回完整的天数。如果该时间段是负的,则这个值可能是负的。

int GetHours( ) const;

说明:此成员函数返回当前日的小时数。其范围是-23到+23。

int GetMinutes( ) const;

说明:此成员函数返回当前小时中的分钟数。范围是-59到59。

int GetSeconds( ) const;

说明:此成员函数返回当前分钟中的秒数。范围是-59到59。

LONG GetTotalHours( ) const;

说明:此成员函数返回此CTimeSpan中的完整小时数。

LONG GetTotalMinutes( ) const;

说明:此成员函数返回此CTimeSpan中的完整分钟数。

LONG GetTotalSeconds( ) const;

说明:此成员函数返回此CTimeSpan中的完整秒数。

CString Format( LPCSTR pFormat ) const;
CString Format( LPCTSTR pFormat ) const;
CString Format( UINT nID ) const;

返回值:返回一个包含格式化的时间的CString对象。

参数:

  • pFormat 一个类似于printf格式化字符串的格式化字符串。前面有一个百分号(%)标记的格式化代码,将被相应的CTimeSpan成分替换。格式化字符串中的其它字符被不作变动地拷贝到返回字符串中。
  • Format 格式化代码的值和意义同CTime
  • nID 用来表示这个格式的字符串的ID。

说明:生成一个对应于此CTimeSpan的格式化字符串。库的调试版检查格式化代码,如果代码不在上面的列表中,则将给出断言。

const CTimeSpan& operator =( const CTimeSpan& timeSpanSrc );

说明:这些重载的赋值操作符将源CTimeSpan timeSpanSrc对象拷贝到此CTimeSpan对象中。

CTimeSpan operator +( CTimeSpan timeSpan ) const;
CTimeSpan operator -( CTimeSpan timeSpan ) const;

说明:这两个操作符允许你将两个CTimeSpan对象相加或相减。

const CTimeSpan& operator +=( CTimeSpan timeSpan );
const CTimeSpan& operator -=( CTimeSpan timeSpan );

说明:这些操作符允许你向或从此CTimeSpan中加上或减去一个CTimeSpan对象。

BOOL operator ==( CTimeSpan timeSpan ) const;
BOOL operator !=( CTimeSpan timeSpan ) const;
BOOL operator <( CTimeSpan timeSpan ) const;
BOOL operator >( CTimeSpan timeSpan ) const;
BOOL operator <=( CTimeSpan timeSpan ) const;
BOOL operator >=( CTimeSpan timeSpan ) const;

说明:这些操作符比较两个相对时间值。如果测试条件为真,则返回非零值;否则返回0。

2.计时器

计时器的作用就是用于计时,当到达规定的时间点时,会触发事先设置好的动作。SetTimer函数用于创建一个计时器,KillTimer函数用于销毁一个计时器。计时器属于系统资源,使用完应及时销毁。

1.创建计时器

SetTimer函数的原型如下:

UINT CWnd::SetTimer(UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT*lpfnTimer) (HWND, UINT, UINT, DWORD) );

返回值:如果函数成功,则返回新定时器的标识符。应用程序可以将这个值传递给KillTimer成员函数以销毁定时器。如果成功,则返回非零值;否则返回0。

参数说明
参数含义
nIDEvent指定了不为零的定时器标识符。
nElapse指定了定时值;以毫秒为单位。
lpfnTimer指定了应用程序提供的TimerProc回调函数的地址,该函数被用于处理WM_TIMER消息。如果这个参数为NULL,则WM_TIMER消息被放入应用程序的消息队列并由CWnd对象来处理。

说明:这个函数设置一个系统定时器。指定了一个定时值,每当发生超时,则系统就向设置定时器的应用程序的消息队列发送一个WM_TIMER消息,或者将消息传递给应用程序定义的TimerProc回调函数。lpfnTimer回调函数不需要被命名为TimerProc,但是它必须按照如下方式定义:

void CALLBACK EXPORT TimerProc(
   HWND hWnd,      // 调用SetTimer的CWnd的句柄
   UINT nMsg,      // WM_TIMER
   UINT nIDEvent,  // 定时器标识
   DWORD dwTime    // 系统时间
);

定时器是有限的全局资源;因此对于应用程序来说,检查SetTimer返回的值以确定定时器是否可用是很重要的。

2.销毁计时器

KillTimer函数的原型如下:

BOOL CWnd::KillTimer( int nIDEvent );

返回值:指定了函数的结果。如果事件已经被销毁,则返回值为非零值。如果KillTimer成员函数不能找到指定的定时器事件,则返回0。

参数: nIDEvent传递给SetTimer的定时器事件值。

说明:销毁以前调用SetTimer创建的用nIDEvent标识的定时器事件。任何与此定时器有关的未处理的WM_TIMER消息都从消息队列中清除。

3.位图类

CBitmap封装了Windows图形设备接口(GDI)中的位图,并且提供了操纵位图的成员函数。使用CBitmap对象之前要先构造CBitmap对象,调用其中的一个初始化成员函数设置位图对象的句柄。此后就可以调用其它成员函数了。

1.构造
CBitmap();

说明:构造一个CBitmap对象。生成的对象必须用下面的一个成员函数进行初始化。

2.初始化
BOOL LoadBitmap( LPCTSTR lpszRecourceName );
BOOL LoadBitmap( UINT nIDResource );

返回值:调用成功时返回非零值,否则为0。

参数:

  • lpszResourceName指向一个包含了位图资源名字的字符串(该字符串以null结尾)。
  • nIDResource指定位图资源中资源的ID号。

说明:本函数从应用的可执行文件中加载由lpszResourceName指定名字或者由nIDResource指定的ID号标志的位图资源。加载的位图被附在CBitmap对象上。如果由lpszResourceName指定名字的对象不存在,或者没有足够的内存加载位图,函数将返回0。可以调用函数CgdiObject::DeleteObject删除由LoadBitmap加载的位图,否则CBitmap的析构函数将删除该位图对象。

警告:在删除位图对象之前,要保证它没有被选到设备上下文中。在Windows3.1以及以后的版本中,增加了如下的位图:

  • OBM_UPARROWI
  • OBM_DNARROWI
  • OBM_RGARROWI
  • OBM_LFARROWI

在Windows3.0或者更早版本的设备驱动程序中不支持这些位图。

BOOL LoadOEMBitmap( UINT nIDBitmap );

返回值:调用成功返回非零值,否则为0。

参数:nIDBitmap:预定义的Windows位图的ID号。具体定义在头文件WINDOWS.H中。可用的值如下:

  • OBM_BTNCORNERS
  • OBM_OLD_RESTORE
  • OBM_BTSIZE
  • OBM_OLD_RGARROW
  • OBM_CHECK
  • OBM_OLD_UPARROW
  • OBM_CHECKBOXES
  • OBM_OLD_ZOOM
  • OBM_CLOSE
  • OBM_REDUCE
  • OBM_COMBO
  • OBM_REDUCED
  • OBM_DNARROW
  • OBM_RESTORE
  • OBM_DNARROWD
  • OBM_RESTORED
  • OBM_DNARROWI
  • OBM_RGARROW
  • OBM_LFARROW
  • OBM_RGARROWD
  • OBM_LFARROWD
  • OBM_RGARROWI
  • OBM_LFARROWI
  • OBM_SIZE
  • OBM_MNARROW
  • OBM_UPARROW
  • OBM_OLD_CLOSE
  • OBM_UPARROWD
  • OBM_OLD_DNARROW
  • OBM_UPARROWI
  • OBM_OLD_LFARROW
  • OBM_ZOOM
  • OBM_OLD_REDUCE
  • OBM_ZOOMD

说明:本函数用于加载一个Windows预定义的位图。以OBM_OLD开头的位图名表示它们是在Windows3.0之前的版本采用的。使用不是以OBM_OLD开头的常量,需要在包括头文件WINDOW.H之前定义常量OEMRESOURCE

BOOL LoadmappedBitmap(
     UINT nIDBitmap,
     UNIT nFlags = 0,
     LPCOLORMAP lpColorMap = NULL,
     int nMapSize = 0
);

返回值:调用成功时返回非零值,否则为0。

参数说明
参数含义
nIDBitmap位图资源的ID号。
nFlags位图的标记。可以是0或者CMB_MASKED
lpColorMap指向COLORMAP结构的一个指针。结构中记录了映射位图所需的颜色信息。如果本参数为NULL,函数将使用缺省的颜色映射。
nMapsizelpColorMap指向的颜色映射的数目。

说明:本函数加载一个位图并把它的颜色映射为当前系统颜色。缺省时LoadMapped Bitmap将映射通常在按钮图形中采用的颜色。

BOOL CreateBitmap( int nWidth, int nHeight, UINT nPlanes, UINT nBitcount, const void* lpBits );

返回值:调用成功时返回非零值,否则为0。

参数说明
参数含义
nWidth指定位图的宽度(以像素数为单位)。
nHeight指定位图的高度(以像素数为单位)。
nPlanes指定位图中的彩色位面数。
nBitCount指定位图中每个像素颜色的位数。
lpBits指向一个短整型数组,数组中记录了位图的初始位值。如果为NULL,则新的位图没有被初始化。

说明:本函数用指定的宽度、高度和位模式初始化依赖于设备的内存位图。对彩色位图来说,参数nPlanesnBitcount要有一个被设置为1。如果二者都被设置为1,则建立一个黑白位图。虽然不能为显示设备直接选中一个位图,但可以调用CDC::SelectObject把位图置为内存设备上下文(memory device context)的当前位图,然后调用CDC::BitBlt函数把它拷贝到任何兼容的设备上下文中。终止用CreateBitmap建立的CBitmap对象,先要从设备上下文中移出该位图,然后删除该对象。

BOOL CreateBitmapIndirect( LPBITMAP lpBitmap );

返回值:调用成功时返回非零值,否则为0。

参数:lpBitmap指向包含有关位图信息的BITMAP结构。

说明:本函数用lpBitmap指向的结构中指定的宽度、高度和位模式(可以不指定)初始化位图对象。虽然显示设备不能直接选中一个位图,但可以调用CDC::Select Object把位图置为内存设备上下文(memory device context)的当前位图,然后调用CDC::BitBltCDC::StrechBlt把它拷贝到任何兼容的设备上下文中(CDC::PatBlt函数能把当前画刷的位图直接拷贝到显示设备的上下文中)。如果已经调用函数GetObject填充了lpBitmap指向的结构,则位图的位值没有指定,并且位图未被初始化。要初始化该位图,应用可以调用CDC:BitBlt::SetDIBitsCgdiObject::GetObject函数的第一个参数指定的位图的位值拷贝到CreateBitmapIndirect建立的位图中。终止用CreateBitmapIndirect建立的CBitmap对象,要先从设备上下文中移出该位图,然后删除该对象。

BOOL CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight );

返回值:调用成功时返回非零值,否则为0。

参数说明
参数含义
pDC指定设备上下文。
nWidth指定位图的宽度(以像素数为单位)。
nHeight指定位图的高度(以像素数为单位)。

说明:初始化一个与pDC指定的设备上下文兼容的位图。位图与指定的设备上下文具有相同的颜色位面数或相同的每个像素的位数。任何与pDC指定的设备兼容的内存设备都可以选择它作为当前位图。如果pDC指向的是内存设备上下文,则返回的位图与设备上下文中当前选中的位图具有相同的格式。“内存设备上下文”是一块表示一块显示区域的内存,它可以把图像存储在内存中,以备拷贝到兼容设备的真实显示区域中。建立一个内存设备上下文时,GDI自动地为它选择一个黑白原始位图。既然彩色内存设备上下文的当前位图既可以是彩色的也可以是黑白的,CreateCompatibleBitmap返回的位图就不一定是相同的格式设置。但是,非内存设备上下文的兼容位图的格式总是和设备的格式一致。终止用CreateCompatibleBitmap建立的CBitmap对象,要先从设备上下文中移出位图,然后删除该对象。

BOOL CreateDiscardableBitmap( CDC* pDC, int nWidth, int nHeight );

返回值:调用成功时返回非零值,否则为0。

参数同CreateCompatibleBitmap

说明:本函数初始化一个与pDC指定的设备上下文兼容的可丢弃的位图。位图与指定的设备上下文具有相同的颜色位面数或相同的每个像素的位数。任何与pDC指定的设备兼容的内存设备都可以选择它作为当前位图。应用没有把该函数建立的位图选到某个显示上下文中时,Windows可以丢弃该位图。如果在Windows丢弃了该位图之后,应用又试图选中该位图,函数CDC::SelectObject将返回NULL。终止用CreateDiscardableBitmap建立的CBitmap对象,要先从设备上下文中移出该位图,然后删除该对象。

3.属性
int GetBimap( BITMAP* pBitMap );

返回值:调用成功时返回非零值,否则为0。

参数:pBitMap指向BITMAP结构的一个指针,不能为NULL

说明:本函数用于查看CBitmap对象的信息。返回的信息存放在pBitMap指向的BITMAP结构中。

operator HBITMAP( ) const;

返回值:调用成功时返回一个由CBitmap对象表示的Windows GDI对象的句柄,否则返回NULL

说明:本操作符用于取得CBitmap对象上的Windows GDI对象句柄。这是一个校验性操作符,可直接参考HBITMAP对象。

4.操作
static CBitmap* PASCAL FromHandle( HBITMAP hBitmap );

返回值:调用成功时返回一个指向CBitmap对象的指针,否则返回NULL

参数:hBitmap指定一个Windows GDI 位图的句柄。

说明:本函数在调用时指定一个Windows GDI 位图的句柄,返回一个指向CBitmap对象的指针。如果该句柄上没有相联系的CBitmap对象,则为该句柄建立一个临时CBitmap对象。该临时CBitmap对象保持有效,直到应用在它的事件循环中出现空闲时间,此时Windows会删除所有的临时图形对象。换句话说,临时对象仅在一个Windows消息的处理过程中有效。

DWORD SetBitmapBits( DWORD dwCount, const void* lpBits );

返回值:调用成功时返回设置位图位值的字节数,否则为0。

参数说明
参数含义
dwCount指定由lpBits指向的字节数。
lpBits指向一个BYTE类型的数组,数组中记录了要拷贝到CBitmap对象的位值。

说明:本函数用lpBits指定的位值设置位图的位值。

DWORD GetBitmapBits( DWORD dwCount, LPVOID lpBits ) const;

返回值:调用成功时返回位图的实际字节数,否则为0。

参数说明
参数含义
dwCount指定要拷贝的字节数。
lpBits指向接收位图内容的缓冲。位图用字节数组表示,该数组与一个由很多双字节(16位)组成的结构等价。

说明:本函数把CBitmap对象的位模式拷贝到lpBits指向的缓冲中。参数dwCount指定待拷贝的字节数。可以调用函数CgdiObject::GetObject得到指定位图的正确dwCount值。

CSize SetBitmapDimension (int nWidth, int nHeight);

返回值:返回前一个位图的维数。高度存放在CSize对象的成员cy中,宽度存放在成员cx中。

参数说明
参数含义
nWidth指定位图的宽度(以0.1毫米为单位)。
nHeight指定位图的高度(以0.1毫米为单位)。

说明:本函数用于设置位图的高度和宽度。GDI一般不用这些数字,除非应用调用成员函数GetBitmapDimension来获取它们。

CSize GetBitmapDimension( ) const;

返回值:返回位图的宽度和高度,以0.1毫米为单位。位图高度存放在Csize对象的成员cy中,宽度存放在成员cx中。如果没有调用SetBitmapDimension设置位图的宽度和高度,函数将返回0。

说明:本函数返回位图的宽度和高度。调用之前应已经调用SetBitmapDimension设置位图的宽度和高度。

2.实验目的

掌握MFC的常用类,对MFC类的派生和继承有详细了解以及熟练使用。

3.实验内容

利用OOP创建一个简单时钟如下:
在这里插入图片描述

4.代码实现

1.准备工作

首先利用向导创建项目,选择“基于对话框”后点击完成,并删除默认创建的按钮和静态文本控件以及将Caption属性改为Clock。

2.基类CClockBase

由于许多时钟元素存在颜色、外观等共同属性,因此可以创建一个基类用于派生实现,这样大大提高了代码的复用率。

ClockBase.h
#pragma once
class CClockBase
{
public:
	CClockBase();
	virtual ~CClockBase();
protected:
	COLORREF m_mainColor;
	COLORREF m_otherColor;
	CTime m_time;
	CRect m_region;
	int m_radius;
public:
	// 设置绘图区域
	void setRegion(LPRECT lprcRect);
	// 设置当前时间
	void setTime(const CTime& time);
	// 设置颜色
	void setColor(const COLORREF& mainColor, const COLORREF& otherColor);
	virtual void Draw(CDC *pDC) = 0;
};
ClockBase.cpp
#include "stdafx.h"
#include "ClockBase.h"

//#include <math.h>
//#define PI 3.14159265358979

CClockBase::CClockBase()
{
	m_radius = 0;
	m_mainColor = RGB(255, 255, 255);
	m_otherColor = RGB(128, 128, 128);
}


CClockBase::~CClockBase()
{
}


// 设置绘图区域
void CClockBase::setRegion(LPRECT lprcRect)
{
	m_region = lprcRect;
	m_radius = (m_region.Width() > m_region.Height() ? m_region.Height() : m_region.Width()) >> 1;
}


// 设置当前时间
void CClockBase::setTime(const CTime& time)
{
	m_time = time;
}


// 设置颜色
void CClockBase::setColor(const COLORREF& mainColor, const COLORREF& otherColor)
{
	m_mainColor = mainColor;
	m_otherColor = otherColor;
}

3.时钟背景类CClockBackground

用于绘制表盘的刻度,以公有方式继承,设置刻度颜色的默认值如下:

CClockBackground::CClockBackground()
{
	m_mainColor = RGB(0, 255, 0);
	m_otherColor = RGB(0, 128, 0);
	//m_otherColor = RGB(255, 255, 255);
}

此外,只需重写Draw函数即可,由于需要用到三角函数和int8_t,首先预处理如下:1

#include <math.h>
#define PI 3.14159265358979
#include <stdint.h>

然后实现绘图:

void CClockBackground::Draw(CDC* pDC)
{
	CPen mainPen(PS_SOLID, 1, m_mainColor), otherPen(PS_SOLID, 1, m_otherColor);
	CBrush mainBrush(m_mainColor), otherBrush(m_otherColor);
	CPoint center(m_region.CenterPoint());
	int8_t i(60);
	CPen* oldPen(pDC->SelectObject(&otherPen));
	CBrush* oldBrush(pDC->SelectObject(&mainBrush));
	int r(m_radius - 8);
	do
		if (--i % 5)
		{
			CPoint p(center);
			double theta(PI * i / 30);
			p.Offset(r*sin(theta), r*cos(theta));
			CRect dot(-2, -2, 2, 2);
			dot.OffsetRect(p);
			pDC->Ellipse(dot);
		}
	while (i);
	pDC->SelectObject(&mainPen);
	pDC->SelectObject(&otherBrush);
	do
	{
		CPoint p(center);
		double theta(PI * i / 6);
		p.Offset(r*sin(theta), r*cos(theta));
		CRect dot(-3, -3, 3, 3);
		dot.OffsetRect(p);
		pDC->Rectangle(dot);
	} while (++i < 12);
	pDC->SelectObject(oldPen);
	pDC->SelectObject(oldBrush);
}

4.时钟指针类CClockHourHandCClockMinuteHandCClockSecondHand

接着实现时钟指针类,类似地只需设置颜色初值以及重写绘图函数即可:

CClockHourHand::CClockHourHand()
{
	m_mainColor = RGB(0, 255, 100);
	m_otherColor = RGB(128, 128, 0);
}
void CClockHourHand::Draw(CDC* pDC)
{
	CPen mainPen(PS_SOLID, 1, m_mainColor), otherPen(PS_SOLID, 1, m_otherColor);
	CBrush mainBrush(m_mainColor), otherBrush(m_otherColor);
	CPen* oldPen(pDC->SelectObject(&otherPen));
	CBrush* oldBrush(pDC->SelectObject(&mainBrush));
	int time((m_time.GetHour() % 12) * 3600 + m_time.GetMinute() * 60 + m_time.GetSecond());
	double theta(PI * time / 21600), s_theta(sin(theta)), c_theta(cos(theta));
	/*CPoint p1(m_region.CenterPoint()), p2(p1), p3(p1), p4(p1);
	int r(m_radius >> 1);
	p1.Offset(r*s_theta, -r*c_theta);
	r >>= 2;
	p3.Offset(-r*s_theta, r*c_theta);
	r >>= 1;
	p2.Offset(r*c_theta, r*s_theta);
	p4.Offset(-r*c_theta, -r*s_theta);*/
	CPoint P[4], *p(P + 3), *q(p);
	*p = m_region.CenterPoint();
	do *--q = *p; while (q != P);
	int r(m_radius >> 1);
	q->Offset(r*s_theta, -r*c_theta);
	r >>= 2;
	(--p)->Offset(-r*s_theta, r*c_theta);
	r >>= 1;
	(++q)->Offset(r*c_theta, r*s_theta);
	(++p)->Offset(-r*c_theta, -r*s_theta);
	pDC->Polygon(P, 4);
	pDC->SelectObject(oldPen);
	pDC->SelectObject(oldBrush);
}
CClockMinuteHand::CClockMinuteHand()
{
	m_mainColor = RGB(0, 255, 100);
	m_otherColor = RGB(128, 128, 0);
}
void CClockMinuteHand::Draw(CDC *pDC)
{
	CPen mainPen(PS_SOLID, 1, m_mainColor), otherPen(PS_SOLID, 1, m_otherColor);
	CBrush mainBrush(m_mainColor), otherBrush(m_otherColor);
	CPen* oldPen(pDC->SelectObject(&otherPen));
	CBrush* oldBrush(pDC->SelectObject(&mainBrush));
	int time(m_time.GetMinute() * 60 + m_time.GetSecond());
	double theta(PI * time / 1800), s_theta(sin(theta)), c_theta(cos(theta));
	CPoint P[4], *p(P + 3), *q(p);
	*p = m_region.CenterPoint();
	do *--q = *p; while (q != P);
	int r((m_radius << 1) / 3);
	q->Offset(r*s_theta, -r*c_theta);
	r >>= 2;
	(--p)->Offset(-r*s_theta, r*c_theta);
	r >>= 1;
	(++q)->Offset(r*c_theta, r*s_theta);
	(++p)->Offset(-r*c_theta, -r*s_theta);
	pDC->Polygon(P, 4);
	pDC->SelectObject(oldPen);
	pDC->SelectObject(oldBrush);
}
CClockSecondHand::CClockSecondHand()
{
	m_mainColor = m_otherColor = RGB(0, 200, 200);
}
void CClockSecondHand::Draw(CDC *pDC)
{
	CPen mainPen(PS_SOLID, 1, m_mainColor);
	CPen* oldPen(pDC->SelectObject(&mainPen));
	CPoint P(m_region.CenterPoint());
	pDC->MoveTo(P);
	int r(m_radius - 10);
	double theta(PI * m_time.GetSecond() / 30);
	P.Offset(r*sin(theta), -r*cos(theta));
	pDC->LineTo(P);
	pDC->SelectObject(oldPen);
}

5.封装为控件CClockWidget

使用类向导添加类CClockWidget,并添加上面实现的4个成员变量,声明为私有的。此外,为了绘图方便,额外添加一个CRect类型的成员变量表示用户操作的区域:

#pragma once
#include "afxwin.h"
#include "ClockBackground.h"
#include "ClockHourHand.h"
#include "ClockMinuteHand.h"
#include "ClockSecondHand.h"
class CClockWidget :
	public CStatic
{
public:
	CClockWidget();
	~CClockWidget();
private:
	CClockBackground m_background;
	CClockHourHand m_hourhand;
	CClockMinuteHand m_minutehand;
	CClockSecondHand m_secondhand;
	CRect m_client;
	virtual void PreSubclassWindow();
public:
	DECLARE_MESSAGE_MAP()
	afx_msg void OnSize(UINT nType, int cx, int cy);
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	afx_msg void OnPaint();
	void DrawClock(CDC* pDC);
};

接着在创建控件的时候进行初始化:

void CClockWidget::PreSubclassWindow()
{
	GetClientRect(m_client);
	m_background.setRegion(m_client);
	m_hourhand.setRegion(m_client);
	m_minutehand.setRegion(m_client);
	m_secondhand.setRegion(m_client);
	SetTimer(1u, 1000u, nullptr);

	CStatic::PreSubclassWindow();
}

然后当控件改变大小的时候进行更新:

void CClockWidget::OnSize(UINT nType, int cx, int cy)
{
	CStatic::OnSize(nType, cx, cy);

	GetClientRect(m_client);
	m_background.setRegion(m_client);
	m_hourhand.setRegion(m_client);
	m_minutehand.setRegion(m_client);
	m_secondhand.setRegion(m_client);
}

一旦计时器打点,我们需要刷新出发绘图事件:

void CClockWidget::OnTimer(UINT_PTR nIDEvent)
{
	Invalidate(FALSE);

	CStatic::OnTimer(nIDEvent);
}

最后实现绘图:

void CClockWidget::OnPaint()
{
	CPaintDC dc(this);
	CDC dcMem;
	dcMem.CreateCompatibleDC(&dc);
	CBitmap bmp;
	bmp.CreateCompatibleBitmap(&dc, m_client.Width(), m_client.Height());
	dcMem.SelectObject(&bmp);
	//dcMem.SetBkColor(RGB(255, 255, 255));
	//static CBrush whiteBrush(RGB(255, 255, 255));
	//dcMem.SelectObject(&whiteBrush);
	DrawClock(&dcMem);
	dc.BitBlt(0, 0, m_client.Width(), m_client.Height(), &dcMem, 0, 0, SRCCOPY);
}
void CClockWidget::DrawClock(CDC* pDC)
{
	CTime time(CTime::GetTickCount());
	m_background.setTime(time);
	m_hourhand.setTime(time);
	m_minutehand.setTime(time);
	m_secondhand.setTime(time);
	m_background.Draw(pDC);
	m_secondhand.Draw(pDC);
	m_minutehand.Draw(pDC);
	m_hourhand.Draw(pDC);
}

6.实例化控件

在资源视图的对话框窗口中加入一个CStatic控件和CMonthCalCtrl控件,并调整为合适大小、修改合适的ID。对CStatic控件进行添加变量,变量类型选择CClockWidget即可。

5.运行结果

在这里插入图片描述

6.总结

1.实验中遇到的困难

如何实现时钟指针

我一开始是认为时针、分针、秒针都具有某种共性,一开始是选择了类模板来写(未完成):

CClockHandBase.h:

#pragma once
#include "ClockBase.h"
template <double dir1, double dir2, double dir3, double dir4>
class CClockHandBase :
	public CClockBase
{
public:
	CClockHandBase();
	virtual ~CClockHandBase();
	virtual void Draw(CDC *pDC);
};

CClockHandBase.cpp:

#include "stdafx.h"
#include "ClockHandBase.h"


template <double dir1, double dir2, double dir3, double dir4>
CClockHandBase<dir1, dir2, dir3, dir4>::CClockHandBase()
{
	m_mainColor = RGB(0, 255, 100);
	m_otherColor = RGB(128, 128, 0);
}


template <double dir1, double dir2, double dir3, double dir4>
CClockHandBase<dir1, dir2, dir3, dir4>::~CClockHandBase()
{
}


template <double dir1, double dir2, double dir3, double dir4>
void CClockHandBase<dir1, dir2, dir3, dir4>::Draw(CDC *pDC)
{
	CPen mainPen(PS_SOLID, 1, m_mainColor), otherPen(PS_SOLID, 1, m_otherColor);
	CBrush mainBrush(m_mainColor), otherBrush(m_otherColor);
	CPen* oldPen(pDC->SelectObject(&otherPen));
	CBrush* oldBrush(pDC->SelectObject(&mainBrush));
}

本来试图通过typedef或者using来实例化时针、分针和秒针,比如:

typedef CClockHandBase<0.5,0.1,0.05,0.05> CClockHourHand;
typedef CClockHandBase<0.8,0.2,0.1,0.1> CClockHourHand;
typedef CClockHandBase<0.99,0.1,0.05,0.05> CClockHourHand;

但是这样会导致如下问题:

  • 颜色初始化不同的问题:比如时针和秒针就不应使用同一类颜色,否则大大影响美观性。
  • 每次的绘图角度问题:时针、分针、秒针在不同时刻的底层实现它们的角度 θ \theta θ是不同的,比如设当前时刻到当天零时刻距离为 t t t秒,并设
    { t ≡ t 1 ( m o d   3600 ) t ≡ t 2 ( m o d   60 ) \begin{cases} t\equiv t_1({\rm mod}\,3600)\\ t\equiv t_2({\rm mod}\,60) \end{cases} {tt1(mod3600)tt2(mod60)
    则有:
    { θ 时 = π 21600 t θ 分 = π 1800 t 1 θ 秒 = π 30 t 2 \begin{cases} \theta_\text{时}=\dfrac\pi{21600}t\\\\ \theta_\text{分}=\dfrac\pi{1800}t_1\\\\ \theta_\text{秒}=\dfrac\pi{30}t_2 \end{cases} θ=21600πtθ=1800πt1θ=30πt2
    这导致了模板不方便编写绘图函数。一种解决方案是再添加一个枚举类型的模板参数,比如:
enum HandClass{ HourHand, MinuteHand, SecondHand };

实际上这样做就完全没有必要进行前面的模板封装,甚至比单独实现时针、分针、秒针更加麻烦。

2.项目改进

由于时间原因,我没能继续改进程序,我认为它还可以进行如下方面的改进:

  1. CClockBackground类还可以进一步添加数据成员:
COLORREF m_textColor;

用来保存文字输出的颜色,并在合适位置用TextOut绘制文字。
2. CClockWidget类还可以进一步添加数据成员:

COLORREF m_backgroundColor;

从而设置背景板的颜色,而不是普通的黑色。
3. 这次实验没有将日期控件结合到目前时间上,将来可以进一步将CClockWidgetCMonthCalCtrl进行封装。
4. 从CClockWidget的实现方式可以看出,实际处理当前时间的时候还需要额外设置m_time的值,这样不仅运行效率低,而且造成了空间浪费。可以修改绘图函数的实现方式,将m_time改为引用传递:

void CClockBase::Draw(CDC* pDC, const CTime& time)

这样避免了拷贝的时间开销,可以提高程序运行效率。

3.心得体会

通过本次实验,我学会了如何利用MFC的常用类来创建一个简单的时钟控件。使用了类的派生和继承,大大提高了代码的复用率。同时,我也遇到了一些困难,比如如何实现时钟的指针类,但通过自己的思考和努力,最终找到了解决方案。

此外,我还学习到了双缓冲绘图技术。当数据量很大时,绘图可能需要几秒钟甚至更长的时间,而且有时还会出现闪烁现象,为了解决这些问题,可采用双缓冲技术来绘图。双缓冲即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。

这次实验让我更加熟悉了MFC的常用类的使用,也对面向对象的编程有了更深刻的理解。

代码地址:https://github.com/zsc118/MFC-exercises


  1. 今后不再一一指出这些预处理部分。 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/298762.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【大数据】Zookeeper 数据写入与分布式锁

Zookeeper 数据写入与分布式锁 1.数据是怎么写入的2.基于 Zookeeper 实现分布式锁 1.数据是怎么写入的 无论是 Zookeeper 自带的客户端 zkCli.sh&#xff0c;还是使用 Python&#xff08;或者其它语言&#xff09;实现的客户端&#xff0c;本质上都是连接至集群&#xff0c;然…

1982-2022年GIMMS 标准化差异植被指数

GIMMS 标准化差异植被指数 1982-2022 PKU GIMMS 归一化植被指数数据集&#xff08;PKU GIMMS NDVI&#xff0c;版本 1.2&#xff09;提供了从 1982 年到 2022 年以半个月为间隔、分辨率为 1/12 的一致的全球 NDVI 数据。其主要目标是解决现有领域中普遍存在的关键不确定性。全…

BMS电池管理系统带充放电控制过流过压保护

2.4G无线采集BMS开发板&#xff08;主从一体&#xff09; 全新升级 &#xff08;赠送上位机源码TTL 上位机&#xff0c;可以改成自己想要的界面&#xff09; 12串电池TTL上位机 CAN通信上位机源码有偿开源&#xff0c;供项目二次开发。 增加STM32平台 USB转TTL通信 CAN通信 增加…

C++面向对象核心-继承

1、继承 1.1 概念 继承是面向对象的三大特性之一&#xff0c;体现了代码复用的思想。 继承就是在一个已存在的类的基础上建立一个新的类&#xff0c;并拥有其特性。 已存在的类被称为“基类”或者“父类”新建立的类被称为“派生类”或者“子类”对象间没有继承关系 #include &…

数据恢复与并发控制例题

例1: (1)重做&#xff08;REDO&#xff09;&#xff1a;T1,T2,T3; 撤销&#xff08;UNDO&#xff09;&#xff1a;T4。 (2)重做&#xff1a;T1,T2&#xff1b; 撤销&#xff1a;T3。 (3)重做&#xff1a;T1; 撤销&#xff1a;T2,T3. (4)重做&#xff1a;T1&#xff1b; 撤销…

手机上下载 Linux 系统

我们首先要下载 Ternux 点击下载以及vnc viewer (提取码&#xff1a;d9sX)&#xff0c;需要魔法才行 下载完以后我们打开 Ternux 敲第一个命令 pkg upgrade 这个命令是用来跟新软件的 敲完命令就直接回车&#xff0c;如果遇到需要输入 Y/N 的地方全部输入 Y 下一步 #启动TMOE…

java SSM问卷调查系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM问卷调查管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主要采…

vite + vue3引入ant design vue 报错

npm install ant-design-vue --save下载插件并在main.ts 全局引入 报错 解决办法一&#xff1a; main.ts注释掉全局引入 模块按需引入 解决办法二 将package.json中的ant-design-vue的版本^4.0.0-rc.4改为 ^3.2.15版本 同时将将package-lock.json中的ant-design-vue的版本…

华为云服务器试用领取

系列文章目录 华为云服务器试用领取 领取的试用云服务器在哪 文章目录 系列文章目录介绍 介绍 我将会用该系列文章讲述如何在云服务器中安装大数据软件及其环境搭建。如有不足之处&#xff0c;还望指点。 本篇文章讲述的是华为云服务器的免费试用。 华为弹性云服务器 ECS 该云…

metaSPAdes,megahit,IDBA-UB:宏基因组装软件安装与使用

metaSPAdes,megahit,IDBA-UB是目前比较主流的宏基因组组装软件 metaSPAdes安装 GitHub - ablab/spades: SPAdes Genome Assembler #3.15.5的预编译版貌似有问题&#xff0c;使用源码安装试试 wget http://cab.spbu.ru/files/release3.15.5/SPAdes-3.15.5.tar.gz tar -xzf SP…

数据分析——快递电商

一、任务目标 1、任务 总体目的——对账 本项目解决同时使用多个快递发货&#xff0c;部分隔离区域出现不同程度涨价等情形下&#xff0c;如何快速准确核对账单的问题。 1、在订单表中新增一列【运费差异核对】来表示订单运费实际有多少差异&#xff0c;结果为数值。 2、将…

【书生·浦语大模型实战营02】《轻松玩转书生·浦语大模型趣味Demo》学习笔记

《轻松玩转书生浦语大模型趣味Demo》 1、InternLM-Chat-7B 智能对话&#xff1a;生成 300 字的小故事 本节中我们将使用InternLM-Chat-7B 模型部署一个智能对话 Demo。 1.1 环境准备 在InternStudio平台中选择 A100(1/4) 的配置&#xff0c;镜像选择 Cuda11.7-conda&#x…

idea中使用Lombok 失效,@Slf4j 找不到符号的解决办法

文章目录 一、前言二、问题排查和解决方案三、 其他解决方案3.1 另一种解决方案3.2 参考文章 一、前言 今天在一个多module工程中&#xff0c;新增了一个 springboot&#xff08;版本 2.2.4.RELEASE&#xff09; module&#xff0c;像往常一样&#xff0c;我引入了lombok依赖&…

电脑开启虚拟化如何查看自己的主机主板型号

问题描述 在使用virtualbox、vmware安装虚拟机的时候&#xff0c;需要本机电脑能够支持虚拟化。 但是不同厂家的主机&#xff08;主板&#xff09;幸好并不一致&#xff0c;所以需要先了解自己的电脑主板型号 操作方法 1、win r 键打开运行窗口&#xff0c;输入cmd并确定打开…

关于“Python”的核心知识点整理大全64

目录 20.2.15 确保项目的安全 settings.py 20.2.16 提交并推送修改 20.2.17 创建自定义错误页面 1. 创建自定义模板 500.html settings.py settings.py 注意 views.py 20.2.18 继续开发 往期快速传送门&#x1f446;&#xff08;在文章最后&#xff09;&#xff1a…

大数据Doris(五十一):Colocation Join介绍

文章目录 Colocation Join介绍 一、原理 二、使用方式 1、建表 2、删表

【Java EE初阶七】多线程案例(生产者消费者模型)

1. 阻塞队列 队列是先进先出的一种数据结构&#xff1b; 阻塞队列&#xff0c;是基于队列&#xff0c;做了一些扩展&#xff0c;适用于多线程编程中&#xff1b; 阻塞队列特点如下&#xff1a; 1、是线程安全的 2、具有阻塞的特性 2.1、当队列满了时&#xff0c;就不能往队列里…

MATLAB插值函数

一、MATLAB插值函数概览 1&#xff09;本节重点介绍的插值函数 MATLAB插值函数适用情况基础句式interp1 函数interp1 主要用于一维数据的插值interp1(x, y, x_interp, ‘linear’); 其中 x 和 y 是已知数据点&#xff0c;x_interp 是要插值的目标点。interp2 函数interp2 用于…

VS code的使用介绍

VS code的使用介绍 简介下载和安装常用的插件使用教程快捷键 集成Git未找到 Git。请安装 Git&#xff0c;或在 "git.path" 设置中配置。操作步骤打开文件夹初始化仓库文件版本控制状态提交文件到git打开git操作栏位 好用的插件ChineseDraw.io Integration实体关系 Gi…

SpringSecurity集成JWT实现后端认证授权保姆级教程-环境搭建篇

&#x1f341; 作者&#xff1a;知识浅谈&#xff0c;CSDN签约讲师&#xff0c;CSDN博客专家&#xff0c;华为云云享专家&#xff0c;阿里云专家博主 &#x1f4cc; 擅长领域&#xff1a;全栈工程师、爬虫、ACM算法 &#x1f492; 公众号&#xff1a;知识浅谈 &#x1f525;网站…