1、概要 最近一直比较忙,根本没时间记录自己想写的东西,趁着国庆小长假,我自己也列出了想记录的一些小东西,方便他人借鉴,也方便我自己学习。废话不多说,我先来贴上我自己的demo展示图,这个demo比较复杂,我可能会分两篇博客来讲述。与其说是因为demo复杂,还不如说是这个月份是按照完全不同的两种思路来展示。 月份展示效果 如上图所示,这是两种日历展示形式,上方的日历是用label拼成,下方的日历是用一个窗口绘制而成,完全不同的两种路线,各有优缺点。接下来我将会分别介绍这两种月份的实现过程。2、优缺点比较 这两种日历的优缺点我总结了一个简单的表格,如下表: label平凑 widget自绘 优点实现思路简单容易理解,每天都是一个label 绘制不同于其他天比较方便(蓝点)实现稍微复杂,每天的位置需要自己计算 相对于第一种相率高,内存占用率低,事件处理层级少 窗口放大缩小效率高 背景色渐变容易实现 缺点每天都是一个label,对性能有影响 窗口大小变化时麻烦 背景色渐变难以实现 widget每次整个重绘,每一天上的内容绘制比较难以计算 两种月份优缺点比较 本片文字我重点介绍下第二种日历的绘制,也就是整个窗口重新绘制,这种日历的实现难点就在于窗口事件的处理上,设计了好的数据结构,我们的程序处理才会变得简单。3、数据结构设计1structtDayFlag2{3signedcharmchFlagM;1pre0cur1next4unsignedshortmchFlagD;daynum5}; 这个结构负责存储每一天的信息,mchFlagM这个字段表示是否是当前月份的,1:上一个月,1:下一个月,0表示当前月份;mchFlagD存储是一个月中的那一天。 接下来是一个impl接口类DrawDateTimePrivate,这个类中存储了大量的计算信息,负责日历的数据计算和比较1structDrawDateTime::DrawDateTimePrivate2{3public:4DrawDateTimePrivate(DrawDateTimes)5:mSelf(s)6,mpOnChanged(NULL)7{8maRectnewQRect〔mcolumncountmrowcount〕;9maDayFlagnewtDayFlag〔mcolumncountmrowcount〕;10msOverIndex1;1112SYSTEMTIME13GetLocalTime(st);14mwYearst。wY15mwMonthst。wM16mwDayst。wD17}1819DrawDateTimePrivate()20{21delete〔〕maR22delete〔〕maDayF23}2425public:26unsignedintGetColumnLeft(intcolumn)27{28unsignedintleft(widthleftBorderrightBordercolumnSpace)mcolumncountcolumnleftB293031}3233unsignedintGetColumnRight(intcolumn)34{35if(column0columnmcolumncount)36{37column0;38}3940总宽度左border由border(mcolumncount1)列间隙41unsignedintitemWidth(widthleftBorderrightBorder(mcolumncount1)columnSpace)42unsignedintrightGetColumnLeft(column)itemW434445}4647unsignedintGetRowTop(introw)48{49QFontMetricsfm(weekFont);50intweekHeightfm。height();5152unsignedinttop(heighttopBorderbottomBorderweekHeightrowSpace)mrowcountrow53spaceweekH545556}5758unsignedintGetRowBottom(introw)59{60if(row0rowmrowcount)61{62row0;63}6465QFontMetricsfm(weekFont);66intweekHeightfm。height();6768总高度上border下border(mrowcount1)行间隙week高69unsignedintitemHeight(heighttopBorderbottomBorderweekHeightspace(mrowcount1)rowSpace)70unsignedintbottomGetRowTop(row)itemH717273}7475public:76intcolumnSpace5;77introwSpace5;7879intleftBorder10;80intrightBorder10;81inttopBorder5;82intbottomBorder5;8384intwidth100;85intheight80;8687intspace10;周名称和天之间距离8889intmcolumncount7;列数90intmrowcount6;行数9192QFontweekFontQFont(STR(微软雅黑,14));93QFontdayF9495public:96DrawDateTimemS9798IDateInfoChangedNotifympOnC99100unsignedshortmwY101unsignedshortmwM102unsignedshortmwD103104tDayFlagmaDayF各个按钮日期号105QRectmaR各个按钮区域106shortmsOverI热点按钮下标107108boolMatchRealDate(tDayFlagdf)109{110if(df。mchFlagDmwDay0df。mchFlagM)111{112113}114115}116117重置当前月份上的日期flag及显示的数据118voidResetDayFlag()119{120unsignedshortpreY,preM;121GetPreviousMonth(preY,preM);122123intnPreMonDaysDayofMonth(preY,preM);124intnCurMonDaysDayofMonth(mwYear,mwMonth);125intweekCalDayofWeek(mwYear,mwMonth,1);126127intindex0;128129for(inti0;i,index)130{131maDayFlag〔index〕。mchFlagM1;132maDayFlag〔index〕。mchFlagD(nPreMonDaysweek1)i;133}134135for(inti0;inCurMonDi,index)136{137maDayFlag〔index〕。mchFlagM0;138maDayFlag〔index〕。mchFlagDi1;139}140141mrowcountindex7(index70?0:1);142143for(intj1;j,index)144{145maDayFlag〔index〕。mchFlagM1;146maDayFlag〔index〕。mchFlagDj;147}148}149150获取上一个月的年和月份151voidGetPreviousMonth(unsignedshortpreYear,unsignedshortpreMonth)152{153if(mwMonth1)154{155preYearmwY156preMonthmwMonth1;157}158else159{160preYearmwYear1;161preMonth12;162}163}164165获取下一个月的年和月份166voidGetNextMonth(unsignedshortnextYear,unsignedshortnextMonth)167{168if(mwMonth12)169{170nextYearmwYear1;171nextMonth1;172}173else174{175nextYearmwY176nextMonthmwMonth1;177}178}179};GetColumnLeft:获取指定列的左边界GetColumnRight:获取指定列的右边界GetRowTop:获取指定行的上边界GetRowBottom:获取指定行的下边界MatchRealDate:检测给定日期是否是当前天ResetDayFlag:重置impl中的内存数据GetPreviousMonth:获取上一个月份的年和日GetNextMonth:获取下一个月份的年和日4、区域生成 区域生成顾名思义就是生成日期的绘制区域,这个需要根据当前窗口的大小、列间距、行间距等信息来计算每一天的矩形区域,当有月份切换时需要重新计算该信息,如果觉着这个过程对性能没有影响可以在每次整个绘制的时候都重新计算,这样有助于程序在出错时自动恢复。关于区域自动生成,在上一个小节我们已经给出了接口解释,接下来我将贴出实现代码,并做相应解释1重置当前月份上的日期flag及显示的数据2voidResetDayFlag()3{4unsignedshortpreY,preM;5GetPreviousMonth(preY,preM);67intnPreMonDaysDayofMonth(preY,preM);8intnCurMonDaysDayofMonth(mwYear,mwMonth);9intweekCalDayofWeek(mwYear,mwMonth,1);1011intindex0;1213for(inti0;i,index)重置上一个月份的日期14{15maDayFlag〔index〕。mchFlagM1;16maDayFlag〔index〕。mchFlagD(nPreMonDaysweek1)i;17}1819for(inti0;inCurMonDi,index)重置本月份的日期20{21maDayFlag〔index〕。mchFlagM0;22maDayFlag〔index〕。mchFlagDi1;23}2425mrowcountindex7(index70?0:1);更新行数2627for(intj1;j,index)重置下一个月份的日期28{29maDayFlag〔index〕。mchFlagM1;30maDayFlag〔index〕。mchFlagDj;31}32}5、点击位置是哪一天 在自绘制日历的时候,点击位置或者hover位置是哪一个日期判断是非常重要的,这个涉及到整个日历是否是有一个友好的交互。在开始做这个基于widget绘制日的时候我也迷茫过,觉得判断点击位置是哪一天非常困难,实时也是这样的,判断起来是非常困难的,直到后来我看到了一个网友的数据结构设计,原来这个判断是如此的简单,不过简单的判断都是基于一个优秀的结构设计,判断代码如下:1intDrawDateTime::GetIndex(constQPointpoint)2{3intid1;4for(inti0;iptrmrowcountptrmcolumncount4;i)5{6QRectrcptrmaRect〔i〕;7if(point。x()rc。left()point。x()rc。right()8point。y()rc。top()point。y()rc。bottom())9{101112}13}141516} 上述判断只用了一个循环就可以判断出点击位置是在那个日期上,ptrmaRect这个结构存储了所有日期的矩形区域,他在合适的时机就会重置。6、周内容绘制1voidDrawDateTime::DrawWeek(QPainterpainter)2{3QStringaText〔7〕{STR(周日),STR(周一),STR(周二),STR(周三),STR(周四),STR(周五),STR(周六)};45painter。save();6painter。setFont(ptrweekFont);7QFontMetricsfm(ptrweekFont);8intheightfm。height();9for(inti0;i7;i)10{11intleftptrGetColumnLeft(i);12intrightptrGetColumnRight(i);13QRectrect(left,ptrtopBorder,rightleft,height);14painter。drawRect(rect);15painter。drawText(rect,Qt::AlignCenter,aText〔i〕);16}17painter。restore();18}7、日期内容绘制1voidDrawDateTime::DrawDay(QPainterpainter)2{3ptrResetDayFlag();45painter。save();67QFontMetricsfm(ptrweekFont);8intweekHeightfm。height();910for(intcolumn0;column)11{12intcolumnleftptrGetColumnLeft(column);13intcolumnrightptrGetColumnRight(column);14for(introw0;row)15{1617QRectrcTmpptrmaRect〔index〕;18tDayFlagflagptrmaDayFlag〔index〕;1920rcTmp。setLeft(columnleft);21rcTmp。setRight(columnright);22rcTmp。setTop(ptrGetRowTop(row));23rcTmp。setBottom(ptrGetRowBottom(row));2425QPainterP26path。addRoundRect(rcTmp,25);2728if(indexptrmsOverIndex)hover时背景色29{30painter。fillPath(path,QColor(144,151,151));31}3233painter。save();34if(1flag。mchFlagM1flag。mchFlagM)35{36painter。setPen(QColor(Qt::blue));37}38else39{40if(ptrMatchRealDate(flag))41{42painter。setPen(QColor(Qt::red));43}44elseif(indexptrmsOverIndex)45{46painter。setPen(QColor(Qt::white));47}48else49{5051}52}53painter。setOpacity(0。5);绘制半透明度字54painter。drawText(rcTmp,Qt::AlignCenter,QString::number(flag。mchFlagD));5556painter。restore();5758painter。drawPath(path);59}60}6162painter。restore();63}8、月份切换1voidDrawDateTime::PreviousMonth()上一个月份2{3unsignedshortyear,4ptrGetPreviousMonth(year,month);56intacturlDaysDayofMonth(year,month);7if(acturlDaysptrmwDay)8{9ptrmwDayacturlD10}11SetDate(year,month,ptrmwDay);1213update();14}1516voidDrawDateTime::NextMonth()下一个月份17{18unsignedshortyear,19ptrGetNextMonth(year,month);2021intacturlDaysDayofMonth(year,month);22if(acturlDaysptrmwDay)23{24ptrmwDayacturlD25}26SetDate(year,month,ptrmwDay);2728update();29}9、日期点击时效果1voidDrawDateTime::mousePressEvent(QMouseEventevent)2{3if(eventbutton()Qt::LeftButton)4{5intcurGetIndex(eventpos());6tDayFlagflagptrmaDayFlag〔cur〕;78unsignedshortyearptrmwYear,monthptrmwM9if(flag。mchFlagM1)10{11ptrGetPreviousMonth(year,month);12}13elseif(flag。mchFlagM1)14{15ptrGetNextMonth(year,month);1617}18boolb(ptrmwDay!flag。mchFlagDmonth!ptrmwMonthyear!ptrmwYear);19if(b)20{21ptrmwDayflag。mchFlagD;22ptrmwM23ptrmwY24update();25}26}27} 日期点击时,如果点击的是上一个月份的日期,则把当前月份切换到上一个月;如果点击的是下一个月的日期,则把当前月份切换到下一个月;否则当前月份不变,当前点击的日期颜色变成白色。10、鼠标移动时效果1voidDrawDateTime::mouseMoveEvent(QMouseEventevent)2{3intcurGetIndex(eventpos());4boolb(cur!ptrmsOverIndex);5if(b)6{7intpreviousHoverptrmsOverI8ptrmsOverI9update(ptrmaRect〔cur〕);10update(ptrmaRect〔previousHover〕);11}1213QWidget::mouseMoveEvent(event);14} 鼠标hover时,修改当前所hover的日期,然后在重新绘制时,如果是hover的日期,则绘制颜色变为QColor(144,151,151) 点击领取Qt学习资料视频教程链接 11、实现动画 本文所讲述的这种日历在demo中实现时没有使用动画来切换月份,但是用label拼凑的日历使用了动画来切换月份,如果有兴趣的同学可以把本文后面提供的demo下载下来,自行进行修改,修改的时候可以参考label拼凑月份的动画,由于label拼凑的月份实现起来更为繁琐,因此本片文章就不继续讲解了,在下一篇文章中我将讲解一些关键的实现思路和不足。顺便提一句,支持动画切换月份的日历控件时在一个窗口的基础上切换的,感兴趣的同学也可以实现5个月份的切换,就像一些音乐播放器主页上的音乐提示一样,是一个轮播的形式。例如网易音乐 网易轮播 日历同意可以以这样的形式来切换,这样的功能我已经实现了,如果感兴趣的同学可以私聊,demo中的代码在完善优化下,就可以做出这个效果,基于widget完全绘制的日历可能更合适这样的切换,label的堆砌在切换的时候可能会有效率的问题,这就取决于你的机器了,一般的机器还都是没有问题的,除非你要做对效率要求很高的程序。 关于label拼凑的日历我将会在自定义日历(二)给出详细讲解 作者:朝十晚八orTwowords 转载:https:www。cnblogs。comswarmbeesp5927823。html