范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

自定义日历(一)

  1、概要
  最近一直比较忙,根本没时间记录自己想写的东西,趁着国庆小长假,我自己也列出了想记录的一些小东西,方便他人借鉴,也方便我自己学习。废话不多说,我先来贴上我自己的demo展示图,这个demo比较复杂,我可能会分两篇博客来讲述。与其说是因为demo复杂,还不如说是这个月份是按照完全不同的两种思路来展示。
  月份展示效果
  如上图所示,这是两种日历展示形式,上方的日历是用label拼成,下方的日历是用一个窗口绘制而成,完全不同的两种路线,各有优缺点。接下来我将会分别介绍这两种月份的实现过程。 2、优缺点比较
  这两种日历的优缺点我总结了一个简单的表格,如下表:
  label平凑
  widget自绘
  优点 实现思路简单容易理解,每天都是一个label
  绘制不同于其他天比较方便(蓝点) 实现稍微复杂,每天的位置需要自己计算
  相对于第一种相率高,内存占用率低,事件处理层级少
  窗口放大缩小效率高
  背景色渐变容易实现
  缺点 每天都是一个label,对性能有影响
  窗口大小变化时麻烦
  背景色渐变难以实现
  widget每次整个重绘,每一天上的内容绘制比较难以计算
  两种月份优缺点比较
  本片文字我重点介绍下第二种日历的绘制,也就是整个窗口重新绘制,这种日历的实现难点就在于窗口事件的处理上,设计了好的数据结构,我们的程序处理才会变得简单。 3、数据结构设计1 struct tDayFlag 2 { 3     signed char m_chFlagM; // -1 pre 0 cur 1 next 4     unsigned short m_chFlagD; // day num 5 };
  这个结构负责存储每一天的信息,m_chFlagM这个字段表示是否是当前月份的,-1:上一个月,1:下一个月,0表示当前月份;m_chFlagD存储是一个月中的那一天。
  接下来是一个impl接口类 DrawDateTimePrivate,这个类中存储了大量的计算信息,负责日历的数据计算和比较  1 struct DrawDateTime::DrawDateTimePrivate   2 {   3 public:   4     DrawDateTimePrivate(DrawDateTime * s)   5         : m_Self(s)   6         //, m_pOnChanged(NULL)   7     {   8         m_aRect = new QRect[m_column_count * m_row_count];   9         m_aDayFlag = new tDayFlag[m_column_count * m_row_count];  10         m_sOverIndex = -1;  11   12         SYSTEMTIME st;  13         GetLocalTime(&st);  14         m_wYear = st.wYear;  15         m_wMonth = st.wMonth;  16         m_wDay = st.wDay;  17     }  18   19     ~DrawDateTimePrivate()  20     {  21         delete []m_aRect;  22         delete []m_aDayFlag;  23     }  24   25 public:  26     unsigned int GetColumnLeft(int column)  27     {  28         unsigned int left = (width - leftBorder - rightBorder + columnSpace) / m_column_count * column + leftBorder;  29   30         return left;  31     }  32   33     unsigned int GetColumnRight(int column)  34     {  35         if (column < 0 || column > m_column_count)  36         {  37             column = 0;  38         }  39   40         //总宽度-左border-由border-(m_column_count-1)*列间隙  41         unsigned int itemWidth = (width - leftBorder - rightBorder - (m_column_count - 1) * columnSpace) / m_column_count;  42         unsigned int right = GetColumnLeft(column) + itemWidth;  43   44         return right;  45     }  46   47     unsigned int GetRowTop(int row)  48     {  49         QFontMetrics fm(weekFont);  50         int weekHeight = fm.height();  51   52         unsigned int top = (height - topBorder - bottomBorder - weekHeight + rowSpace) / m_row_count * row  53             + space + weekHeight;  54   55         return top;  56     }  57   58     unsigned int GetRowBottom(int row)  59     {  60         if (row < 0 || row > m_row_count)  61         {  62             row = 0;  63         }  64   65         QFontMetrics fm(weekFont);  66         int weekHeight = fm.height();  67   68         //总高度-上border-下border-(m_row_count-1)*行间隙-week高  69         unsigned int itemHeight = (height - topBorder - bottomBorder - weekHeight - space - (m_row_count - 1) * rowSpace) / m_row_count;  70         unsigned int bottom = GetRowTop(row) + itemHeight;  71   72         return bottom;  73     }  74   75 public:  76     int columnSpace = 5;  77     int rowSpace = 5;  78   79     int leftBorder = 10;  80     int rightBorder = 10;  81     int topBorder = 5;  82     int bottomBorder = 5;  83   84     int width = 100;  85     int height = 80;  86   87     int space = 10;//周名称和天之间距离  88   89     int m_column_count = 7;//列数  90     int m_row_count = 6;//行数  91   92     QFont weekFont = QFont(STR("微软雅黑", 14));  93     QFont dayFont;  94   95 public:  96     DrawDateTime * m_Self;  97   98     //    IDateInfoChangedNotify * m_pOnChanged;  99  100     unsigned short m_wYear; 101     unsigned short m_wMonth; 102     unsigned short m_wDay; 103  104     tDayFlag * m_aDayFlag;    // 各个按钮日期号 105     QRect * m_aRect;        // 各个按钮区域 106     short m_sOverIndex;    // 热点按钮下标 107  108     bool MatchRealDate(tDayFlag df) 109     { 110         if (df.m_chFlagD == m_wDay && 0 == df.m_chFlagM) 111         { 112             return true; 113         } 114         return false; 115     } 116  117     //重置当前月份上的日期flag及显示的数据 118     void ResetDayFlag() 119     { 120         unsigned short  preY, preM; 121         GetPreviousMonth(preY, preM); 122  123         int nPreMonDays = DayofMonth(preY, preM); 124         int nCurMonDays = DayofMonth(m_wYear, m_wMonth); 125         int week = CalDayofWeek(m_wYear, m_wMonth, 1); 126  127         int index = 0; 128  129         for (int i = 0; i < week; ++i, index++) 130         { 131             m_aDayFlag[index].m_chFlagM = -1; 132             m_aDayFlag[index].m_chFlagD = (nPreMonDays - week + 1) + i; 133         } 134  135         for (int i = 0; i < nCurMonDays; ++i, index++) 136         { 137             m_aDayFlag[index].m_chFlagM = 0; 138             m_aDayFlag[index].m_chFlagD = i + 1; 139         } 140  141         m_row_count = index / 7 + (index % 7 == 0 ? 0 : 1); 142  143         for (int j = 1; index < m_column_count * m_row_count; ++j, ++index) 144         { 145             m_aDayFlag[index].m_chFlagM = 1; 146             m_aDayFlag[index].m_chFlagD = j; 147         } 148     } 149  150     //获取上一个月的年和月份 151     void GetPreviousMonth(unsigned short & preYear, unsigned short & preMonth) 152     { 153         if (m_wMonth > 1) 154         { 155             preYear = m_wYear; 156             preMonth = m_wMonth - 1; 157         } 158         else 159         { 160             preYear = m_wYear - 1; 161             preMonth = 12; 162         } 163     } 164  165     //获取下一个月的年和月份 166     void GetNextMonth(unsigned short & nextYear, unsigned short & nextMonth) 167     { 168         if (m_wMonth >= 12) 169         { 170             nextYear = m_wYear + 1; 171             nextMonth = 1; 172         } 173         else 174         { 175             nextYear = m_wYear; 176             nextMonth = m_wMonth + 1; 177         } 178     } 179 };GetColumnLeft:获取指定列的左边界GetColumnRight:获取指定列的右边界GetRowTop:获取指定行的上边界GetRowBottom:获取指定行的下边界 MatchRealDate:检测给定日期是否是当前天ResetDayFlag:重置impl中的内存数据GetPreviousMonth:获取上一个月份的年和日GetNextMonth:获取下一个月份的年和日4、区域生成
  区域生成顾名思义就是生成日期的绘制区域,这个需要根据当前窗口的大小、列间距、行间距等信息来计算每一天的矩形区域,当有月份切换时需要重新计算该信息,如果觉着这个过程对性能没有影响可以在每次整个绘制的时候都重新计算,这样有助于程序在出错时自动恢复。关于区域自动生成,在上一个小节我们已经给出了接口解释,接下来我将贴出实现代码,并做相应解释  1 //重置当前月份上的日期flag及显示的数据  2     void ResetDayFlag()  3     {  4         unsigned short  preY, preM;  5         GetPreviousMonth(preY, preM);  6   7         int nPreMonDays = DayofMonth(preY, preM);  8         int nCurMonDays = DayofMonth(m_wYear, m_wMonth);  9         int week = CalDayofWeek(m_wYear, m_wMonth, 1); 10  11         int index = 0; 12  13         for (int i = 0; i < week; ++i, index++)//重置上一个月份的日期 14         { 15             m_aDayFlag[index].m_chFlagM = -1; 16             m_aDayFlag[index].m_chFlagD = (nPreMonDays - week + 1) + i; 17         } 18  19         for (int i = 0; i < nCurMonDays; ++i, index++)//重置本月份的日期 20         { 21             m_aDayFlag[index].m_chFlagM = 0; 22             m_aDayFlag[index].m_chFlagD = i + 1; 23         } 24  25         m_row_count = index / 7 + (index % 7 == 0 ? 0 : 1);//更新行数 26  27         for (int j = 1; index < m_column_count * m_row_count; ++j, ++index)//重置下一个月份的日期 28         { 29             m_aDayFlag[index].m_chFlagM = 1; 30             m_aDayFlag[index].m_chFlagD = j; 31         } 32     }5、点击位置是哪一天
  在自绘制日历的时候,点击位置或者hover位置是哪一个日期判断是非常重要的,这个涉及到整个日历是否是有一个友好的交互。在开始做这个基于widget绘制日的时候我也迷茫过,觉得判断点击位置是哪一天非常困难,实时也是这样的,判断起来是非常困难的,直到后来我看到了一个网友的数据结构设计,原来这个判断是如此的简单,不过简单的判断都是基于一个优秀的结构设计,判断代码如下:  1 int DrawDateTime::GetIndex(const QPoint & point)  2 {  3     int id = -1;  4     for (int i = 0; i < _ptr->m_row_count * _ptr->m_column_count + 4; ++i)  5     {  6         QRect & rc = _ptr->m_aRect[i];  7         if (point.x() > rc.left() && point.x()< rc.right()  8             && point.y() > rc.top() && point.y() < rc.bottom())  9         { 10             id = i; 11             break; 12         } 13     } 14  15     return id; 16 }
  上述判断只用了一个循环就可以判断出点击位置是在那个日期上,_ptr->m_aRect这个结构存储了所有日期的矩形区域,他在合适的时机就会重置。 6、周内容绘制 1 void DrawDateTime::DrawWeek(QPainter & painter)  2 {  3     QString aText[7] = { STR("周日"), STR("周一"), STR("周二"), STR("周三"), STR("周四"), STR("周五"), STR("周六") };  4   5     painter.save();  6     painter.setFont(_ptr->weekFont);  7     QFontMetrics fm(_ptr->weekFont);  8     int height = fm.height();  9     for (int i = 0; i < 7; ++i) 10     { 11         int left = _ptr->GetColumnLeft(i); 12         int right = _ptr->GetColumnRight(i); 13         QRect rect(left, _ptr->topBorder, right - left, height); 14         painter.drawRect(rect); 15         painter.drawText(rect, Qt::AlignCenter, aText[i]); 16     } 17     painter.restore(); 18 }7、日期内容绘制 1 void DrawDateTime::DrawDay(QPainter & painter)  2 {  3     _ptr->ResetDayFlag();  4   5     painter.save();  6   7     QFontMetrics fm(_ptr->weekFont);  8     int weekHeight = fm.height();  9  10     for (int column = 0; column < _ptr->m_column_count; ++column) 11     { 12         int column_left = _ptr->GetColumnLeft(column); 13         int column_right = _ptr->GetColumnRight(column); 14         for (int row = 0; row < _ptr->m_row_count; ++row) 15         { 16             int index = row * _ptr->m_column_count + column; 17             QRect & rcTmp = _ptr->m_aRect[index]; 18             tDayFlag & flag = _ptr->m_aDayFlag[index]; 19  20             rcTmp.setLeft(column_left); 21             rcTmp.setRight(column_right); 22             rcTmp.setTop(_ptr->GetRowTop(row)); 23             rcTmp.setBottom(_ptr->GetRowBottom(row)); 24  25             QPainterPath path; 26             path.addRoundRect(rcTmp, 25); 27  28             if (index == _ptr->m_sOverIndex)//hover时背景色 29             { 30                 painter.fillPath(path, QColor(144, 151, 151)); 31             } 32  33             painter.save(); 34             if (-1 == flag.m_chFlagM || 1 == flag.m_chFlagM) 35             { 36                 painter.setPen(QColor(Qt::blue)); 37             } 38             else 39             { 40                 if (_ptr->MatchRealDate(flag)) 41                 { 42                     painter.setPen(QColor(Qt::red)); 43                 } 44                 else if (index == _ptr->m_sOverIndex) 45                 { 46                     painter.setPen(QColor(Qt::white)); 47                 } 48                 else 49                 { 50  51                 } 52             } 53             painter.setOpacity(0.5);//绘制半透明度字 54             painter.drawText(rcTmp, Qt::AlignCenter, QString::number(flag.m_chFlagD)); 55  56             painter.restore(); 57  58             painter.drawPath(path); 59         } 60     } 61  62     painter.restore(); 63 }8、月份切换 1 void DrawDateTime::PreviousMonth()//上一个月份  2 {  3     unsigned short year, month;  4     _ptr->GetPreviousMonth(year, month);  5   6     int acturlDays = DayofMonth(year, month);  7     if (acturlDays < _ptr->m_wDay)  8     {  9         _ptr->m_wDay = acturlDays; 10     } 11     SetDate(year, month, _ptr->m_wDay); 12  13     update(); 14 } 15  16 void DrawDateTime::NextMonth()//下一个月份 17 { 18     unsigned short year, month; 19     _ptr->GetNextMonth(year, month); 20  21     int acturlDays = DayofMonth(year, month); 22     if (acturlDays < _ptr->m_wDay) 23     { 24         _ptr->m_wDay = acturlDays; 25     } 26     SetDate(year, month, _ptr->m_wDay); 27  28     update(); 29 }9、日期点击时效果 1 void DrawDateTime::mousePressEvent(QMouseEvent * event)  2 {  3     if (event->button() == Qt::LeftButton)  4     {  5         int cur = GetIndex(event->pos());  6         tDayFlag & flag = _ptr->m_aDayFlag[cur];  7           8         unsigned short year = _ptr->m_wYear, month = _ptr->m_wMonth;  9         if (flag.m_chFlagM == -1) 10         { 11             _ptr->GetPreviousMonth(year, month); 12         } 13         else if (flag.m_chFlagM == 1) 14         { 15             _ptr->GetNextMonth(year, month); 16  17         } 18         bool b = (_ptr->m_wDay != flag.m_chFlagD || month != _ptr->m_wMonth || year != _ptr->m_wYear); 19         if (b) 20         { 21             _ptr->m_wDay = flag.m_chFlagD; 22             _ptr->m_wMonth = month; 23             _ptr->m_wYear = year; 24             update(); 25         } 26     } 27 }
  日期点击时,如果点击的是上一个月份的日期,则把当前月份切换到上一个月;如果点击的是下一个月的日期,则把当前月份切换到下一个月;否则当前月份不变,当前点击的日期颜色变成白色。 10、鼠标移动时效果 1 void DrawDateTime::mouseMoveEvent(QMouseEvent * event)  2 {  3     int cur = GetIndex(event->pos());  4     bool b = (cur != _ptr->m_sOverIndex);  5     if (b)  6     {  7         int previousHover = _ptr->m_sOverIndex;  8         _ptr->m_sOverIndex = cur;  9         update(_ptr->m_aRect[cur]); 10         update(_ptr->m_aRect[previousHover]); 11     } 12  13     QWidget::mouseMoveEvent(event); 14 }
  鼠标hover时,修改当前所hover的日期,然后在重新绘制时,如果是hover的日期,则绘制颜色变为 QColor( 144 ,  151 ,  151 )
  点击领取Qt学习资料+视频教程~「链接」
  11、实现动画
  本文所讲述的这种日历在demo中实现时没有使用动画来切换月份,但是用label拼凑的日历使用了动画来切换月份,如果有兴趣的同学可以把本文后面提供的demo下载下来,自行进行修改,修改的时候可以参考label拼凑月份的动画,由于label拼凑的月份实现起来更为繁琐,因此本片文章就不继续讲解了,在下一篇文章中我将讲解一些关键的实现思路和不足。顺便提一句,支持动画切换月份的日历控件时在一个窗口的基础上切换的,感兴趣的同学也可以实现5个月份的切换,就像一些音乐播放器主页上的音乐提示一样,是一个轮播的形式。例如网易音乐
  网易轮播
  日历同意可以以这样的形式来切换,这样的功能我已经实现了,如果感兴趣的同学可以私聊,demo中的代码在完善优化下,就可以做出这个效果,基于widget完全绘制的日历可能更合适这样的切换,label的堆砌在切换的时候可能会有效率的问题,这就取决于你的机器了,一般的机器还都是没有问题的,除非你要做对效率要求很高的程序。
  关于label拼凑的日历我将会在自定义日历(二)给出详细讲解
  作者: 朝十晚八 or  Twowords
  转载:https://www.cnblogs.com/swarmbees/p/5927823.html

祖山济心寺,真正的面朝大海,聚山之灵气水之精华图文刘新田本文由作者授权发布祖山,秦皇岛第一名山,北距海岸约50千米,自然风光清幽旖旎,人文遗迹也十分丰富。万里长城逶迤而过,铁瓦乌龙殿深藏幽谷,更有那云遮雾隐意蕴悠长的济心古寺高药物的不当使用,会成为肝脏的索命鬼生老病死,是人之常情。大家好,我是肝胆外科医生王学栋。但有些权威研究表明,细胞器官的衰老,并不会影响肝脏,也就是说在人的身体里肝脏细胞会不断地更新,不会衰老,始终会保持在3岁。但是经常头晕,吃亏都得知道这5点今天跟大家讨论讨论经常头晕,可能是哪几种情况!带你30秒辨证自己的头晕是属于哪种情况。(1)头晕伴有耳鸣,并且入睡困难,多梦易醒,白天容易发无明火,在中医上叫做肝火上冲,脑络失和,入侵GTA6的17岁黑客已被关押在青少年拘留中心文Shaon伦敦警方逮捕了一名17岁的嫌疑人,其是涉嫌对RockstarGames进行了攻击的黑客,导致近90多个开发中的侠盗猎车手6镜头泄露。据Eurogamer报道,嫌疑人提出官宣!黄石国庆假期安排国庆将至你是否已按捺不住想要出去玩耍的心吃美食?看表演?十一长假怎么过?黄石市商务局黄石市住建局下陆区人民政府为大家安排好啦!9月30日至10月6日我市将在人民广场举办2022黄石泰国驻华使馆举办美食节,邀请市民品尝泰国美食北京日报客户端记者白波泰式冬阴功汤烤猪颈肉绿咖喱鸡咖喱皇炒大虾泰国驻华使馆近日在北京举办2022泰国美食节,希望以中国十一假期为契机,通过邀请市民品尝泰国美食,进一步加强中泰友谊和国庆假期,带你领略大西北的豪迈国庆假期马上来临,很多朋友在关注假期要去哪儿玩?我给大家推荐一个地方,非常适合国庆假期游玩。这就是甘肃。我给大家推荐2个好玩的地方,嘉峪关和张掖七彩丹霞景区。这两个地方我刚刚去过。(环境)碧水连天万绿湖万绿湖建于1958年,又名新丰江水库,位于广东省河源市东源县境内,因湖水常绿得名,水域面积达370平方公里,水质优良。这是9月23日拍摄的万绿湖(无人机照片)。新华社记者邓华摄这是国庆还是想出游?这些地方可以冲一冲在伏波山上看桂林市全景LeeC的视界图虫创意国庆假期进入倒计时2天,如果你还在纠结要不要出门,到底能去哪里,今天的推文就是为你准备的。当然,前提是你所在的地区无风险区与新增病例,以兰州国庆假期出行提示来啦!码住2022年国庆假期即将到来,兰州公安交警对全市道路交通安全形势进行预判分析,确有出行需求的市民朋友,请合理选择出行路线,遵章守法守规,安全文明出行,平平安安回家,共度安全祥和的国庆浙南那处,竟有这样一个充满人间烟火的ampampquot香格里拉,咫尺可触!国庆高铁直达!最近浙游君的快乐源泉,就属马上到来的国庆节!7天长假,太适合出门兜兜风了!不过,网红打卡地人太多,热门景点又逛腻了,不如跟着浙游君来趟这两年正在起势的集成之旅吧!云上世界浅笑云上世
女排世界最佳重返国家队!蔡斌点名中肯评价,有望从陪练变主角今年27岁的王梦洁是中国女排在里约和东京两个奥运周期的国家队成员,特别是东京奥运周期王梦洁力压奥运冠军林莉成为了首发主力,这位处在当打之年的自由人尽管没能参加今年的世界女排联赛,但患有强直性脊柱炎的人,能吃牛肉和羊肉吗对于强直性脊柱炎,这是一种非常普遍的骨科疾病,而且需要很长时间才能治愈。大家都要认识它,在日常生活中要注意许多问题,在饮食方面要特别注意。所以,强直性脊柱炎患者是否可以食用牛羊肉?建议50岁女人穿衣远离大妈装,学学这些素人妈妈,气质更优雅人要衣装佛要金装,这句话相信大家都耳熟能详,特别对于50岁左右的女人,若你懂得穿衣打扮,那么气质想不变好都难。反过来,如果穿大妈裙,那么长年累月所积蓄的气质反而无法展现出来。大家可归化名将艾克森被申请逮捕,刚公布与现任喜讯,被前妻告上法庭近日,据国外媒体报道,早已回归巴西联赛的中国归化国脚艾克森在巴西国内惹上了麻烦,他因为未能履行支付子女的赡养费,被前妻告上了法庭。在法庭上,艾克森被指控拖欠了近8万人民币的抚养费,谁是海港拯救者?武磊?巴尔加斯?眼下还得先靠他记者寒冰特约记者朱兰报道核心奥斯卡巴西单练,谁是海港的拯救者?是已经回到国内的武磊,还是传说中的巴尔加斯?8月16日的晚上,恩迪亚耶给出了答案,至少短期间内,还得靠他。本赛季,海港广东送别周鹏阿联难舍告别,徐杰真情流露胡明轩永远的队长北京时间8月17日,周鹏转会的消息成为各大体育网站和社交媒体的热议话题,其中,一度推上热搜。可见,这种国字号球员的转会,引发的轰动是非常之大。周鹏在广东效力16年,获得8个总冠军,三方交易!杜兰特欧文加盟太阳?保罗搭档比尔?篮网彻底摆烂?太阳队已经连续两个赛季迷失在通往总冠军的道路上,球队管理层今年夏天碌碌无为的表现让菲尼克斯球迷非常失望,他们曾经无比接近得到杜兰特,但随着总经理詹姆斯琼斯匹配艾顿的顶薪合同,这笔交狂轰1280881横扫小光头北京时间8月17日消息,2021斯诺克欧洲大师赛正式开赛。一场焦点比赛中,中国18岁小将吴宜泽,在与不久前刚刚夺得本赛季产生的首项冠军冠军联赛冠军卢卡布雷切尔比赛中,轰出一杆破百和19场6球1助攻!17岁巴西神童横空出世,违约金1亿欧,两豪门疯抢巴西足球人才辈出,在近几年的巴甲联赛之上,不断涌现出一些才华横溢的天才新星。而在2022新赛季的巴甲联赛上,又诞生了一位得到外界公认的天才神童。他就是目前效力于巴拉纳竞技队的锋线小女排世界最佳重返国家队!蔡斌点名中肯评价,有望从陪练变主角今年27岁的王梦洁是中国女排在里约和东京两个奥运周期的国家队成员,特别是东京奥运周期王梦洁力压奥运冠军林莉成为了首发主力,这位处在当打之年的自由人尽管没能参加今年的世界女排联赛,但原神提前熟悉3。0须弥机制特殊区域玩法阴间区域活动玩法教程温馨提示部分词汇采用机翻,还请以官方为准哦Kusava和Aranakin当你和Kusava一起旅行时,你可以使用Kusava召唤他的力量并修复某些破碎的元素方碑。当你和Aranak