一个永久日历生成器……嗯……至少到 2099 年!






4.46/5 (10投票s)
将“我的图片”文件夹中的图片与“即时”生成的日历网格相结合,并将桌面背景设置为生成的图像。
引言
我厌倦了将鼠标悬停在任务栏的时钟上查看日期。或者更糟的是,双击它才能瞥一眼当前月份的样子。另外,我想以某种方式使用我自己的图片,让它们自动成为我的桌面背景。我编写这个项目是为了减轻我的痛苦。
市面上也有类似的工程,但这个工程独一无二,因为它使用了基于 GDI+ 的 ATL“CImage
”对象,从“我的图片”文件夹中随机选择图片,以及一个自制的永久日历生成器。此外,该工程是使用 Visual Studio 2005 生成的。
作为一个实用工具:PCalGen 是一个永久日历生成器(至少在我们的生命周期内是如此),它在每次启动计算机、更改显示设置或日期更改时,使用“我的图片”文件夹中的随机图片自动生成日历网格。图片用作日历网格下的水印。生成日历页面文本时使用桌面颜色。
作为一个示例:PCalGen 工程展示了几个很酷的功能。该工程包含用于生成 1901 年至 2099 年之间任意给定日期的日历页面的代码。有关更多详细信息,请参阅名为“CalendarMaker.cpp”的文件。同一个文件还包含演示如何使用AlphaBlend()
函数创建类似水印的图像的代码。该工程还包含演示如何让 Shell 使用新生成的日历图像作为桌面背景的代码。最后,该工程包含演示用于关闭隐藏窗口应用程序的机制的简单实现的示例代码。有关此机制的更多信息,请参阅“calgen.cpp”文件中的CcalgenApp::InitInstance()
。
最新发布
请使用本文顶部“最新发布”链接获取最新的发布版本。下载包含功能齐全的产品,并附带安装和卸载程序 (MSI)。
格里高利历
日历生成器(CalendarMaker.h、.cpp)代码编写起来很有趣。我只做了足够的研究来开始。这意味着我在生成逻辑所使用的信息的准确性方面做了一些假设。我相信我已经很好地将所有相关因素隔离到该文件中定义的对像中了。如果您想使用这段代码,我只希望您能进行代码审查(并在本文的消息部分发表评论)。如果需要,我将更新本文和相关下载。
关注点
首先,在图 (1) 中,我们将查看CcalgenApp::InitInstance()
方法。这是我们进行关闭处理的地方。关闭处理大致如下:查找现有的互斥体,如果找到,则按类名查找现有的窗口。超出关闭处理范围的应用程序的任何实例都创建了一个基于此类名的窗口。如果找到窗口并且我们有关闭标志,我们将通过用户消息将其传达给窗口,这反过来会导致窗口强制拥有它的应用程序实例退出。如果互斥体先前不存在并且我们有关闭标志,则当前实例退出。这使我们无需首先检查先前实例即可使用关闭标志。之后的代码注册窗口类,创建基于新注册类的窗口实例,并将其指定为主框架,以便 MFC 正常工作。
BOOL CcalgenApp::InitInstance() { // Single instance stuff all mixed // in with '-shutdown' command line processing. // Used to enforce a single instance< HANDLE hMutex = ::CreateMutex( NULL, false, s_lpszMutexGUID ); // If the mutex already existed then // we can assume a previous instance exists. if(hMutex && (ERROR_ALREADY_EXISTS == GetLastError())) { // Let this instance's handle to the mutex go. ::CloseHandle(hMutex); hMutex = NULL; // Find the window in the previous instance and signal // it to shutdown if '-shutdown' is on the command line. HWND hWnd = FindPreExistingHWND(); if( hWnd ) { if(m_lpCmdLine && !lstrcmp( TEXT("-shutdown"), m_lpCmdLine) ) { ::PostMessage(hWnd, WM_USER_SHUTDOWN, 0, 0); } } return FALSE; // Exist this instance. }else { // Still want to test for the shutdown flag... // but not a second time so in this else here. if(m_lpCmdLine && !lstrcmp( TEXT("-shutdown", m_lpCmdLine) ) { } } // If we got here this is the first (primary) instance... ... }
图 (1)
文件 'CalGenWnd.cpp' 定义了 MFC CWnd
的派生类,名为 'CCalGenWnd
',它被用作我们的主框架。此对像处理:标准的窗口开销;创建用于随机选择图像的图像文件列表;处理用于检查更新需求的计时器;在通过关闭通知发出信号时退出应用程序。我没有特别想在 'CalGenWnd.cpp' 中突出显示的内容,但如果您需要,应该检查此文件。
接下来我们想检查的文件名为 'MakeCalander.cpp'。这个文件是项目的核心。它包含用于根据 2001 年至 2097 年左右的任何日期生成日历网格的代码。
在图 (2) 中,我们看到了用于确定闰年的方法。逻辑概述在五个步骤中。这段代码是我尝试编写逻辑的结果,所以如果您愿意,请随意改进它。
//1. If the year is evenly divisible by 4, go to step 2. Otherwise, go to step 5. //2. If the year is evenly divisible by 100, go to step 3. Otherwise, go to step 4. //3. If the year is evenly divisible by 400, go to step 4. Otherwise, go to step 5. //4. The year is a leap year (it has 366 days). //5. The year is not a leap year (it has 365 days). /*static*/ bool CCalendarMaker::IsLeapYear(int nYear) { if(nYear%4 == 0) { // step 1 if(nYear%100 == 0) { // step 2 if(nYear%400 == 0 ) { // step 3 return true; // step 4 } } else return true; // step 4 } return false; // step 5 }
图 (2)
图 (3) 中的两个数组可能需要更好地研究以进行确认。它们用于确定一年中第一天的名称。我做了一些测试,它们似乎有效……我只是没有数学证明。如果您精通此类事务,请随意提供任何证明,我将在本文中详细介绍。
第一个数组是一个表(二维),包含从 2001 年到 2097 年的年份,按四的增量递增排列,列表示一年第一天所在的星期名称。请注意,这些日期并非按正常顺序排列。第二个数组表示第一个数组标题中指定的日期的顺序。数字代表星期几的正常顺序。
要使用该表确定给定年份的第一天名称,必须找到最接近所查询年份且不超过它的最大值。注意该年份的列标题中的日期,并根据需要添加的年数(以达到所查询年份)逐年计算。
/*static*/ int CCalendarMaker::s_years2000[] = { /* Mon Sat Thu Tue Sun Fri Wed */ 2001, 2005, 2009, 2013, 2017, 2021, 2025, 2029, 2033, 2037, 2041, 2045, 2049, 2053, 2057, 2061, 2065, 2069, 2073, 2077, 2081, 2085, 2089, 2093, 2097 }; /*static*/ int CCalendarMaker::s_years1900[] = { /* Tue Sun Fri Wed Mon Sat Thu */ 1901, 1905, 1909, 1913, 1917, 1921, 1925, 1929, 1933, 1937, 1941, 1945, 1949, 1953, 1957, 1961, 1965, 1969, 1973, 1977, 1981, 1985, 1989, 1993, 1997 }; /*static*/ int CCalendarMaker::s_days2000[] = { 1, 6, 4, 2, 0, 5, 3 }; /*static*/ int CCalendarMaker::s_days1900[] = { 2, 0, 5, 3, 1, 6, 4 };
图 (3)
最后,在图 (4) 中,我们看到了用于确定给定年份(1 月 1 日)第一天是星期几(通过数字引用)的数组。
// 0=sun, 1=mon, 2=tue, 3=wed, 4=thu, 5=fri, 6=sat /*static*/ int CCalendarMaker::GetStartDay(int nYear) { if(nYear >= 1901 && nYear <= 2000) { int nIndex = 0; while( nYear >= s_years1900[nIndex] ) nIndex++; int nDay = s_days1900[(nIndex-1)%]; nDay += (nYear - s_years1900[nIndex-1]); if(nDay>6) nDay -= 7; return nDay; } else if(nYear >= 2001 && nYear <= 3000) { int nIndex = 0; while( nYear >= s_years2000[nIndex] ) nIndex++; int nDay = s_days2000[(nIndex-1)%]; nDay += (nYear - s_years2000[nIndex-1]); if(nDay>6) nDay -= 7; return nDay; } return -1; }
图 (4)
在图 (5) 中,我们可以看到用于表示每个月天数的数值数组。稍后我们将查看代码,了解如何计算闰年。
/*static*/ int CCalendarMaker::s_daysPerMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
图 (5)
图 (6) 中其余的代码是 'CCalendarMaker::MakeCalendar()
' 方法。乍一看,这个方法似乎有点难,但实际上非常简单。
首先,我们获取当前日期,并从中提取一些所需的计算数据。接下来,我们确定当前月份第一天是星期几。现在,我们拥有生成日历网格所需的一切。
在接下来的注释中,我包含了我要生成的网格的平面图。生成网格和混合图像的代码就在后面。在这里,您将找到使用CImage
对象和AlphaBlend()
方法的示例。
// Image file to use, output path... will be a jpg. bool CCalendarMaker::MakeCalendar(LPCWSTR lpszInFilePathName, LPCWSTR lpszOutFilePathName){ // Get the current time // and stash it in one of these cool CTime objects. SYSTEMTIME st = {0}; GetLocalTime(&st); CTime timeNow(st); // Use the time object to get some value // we need for generating a calendar page. int nCurrentYear = timeNow.GetYear(); int nCurrentMonth = timeNow.GetMonth(); int nCurrentDayOfMonth = timeNow.GetDay(); int nStartDayOfCurrentYear = GetStartDay(nCurrentYear); // Get the month and year display strings ready. CString strMonth = CCalendarMaker::s_months[nCurrentMonth-1]; CString strYear; strYear.Format(L"%d", timeNow.GetYear()); int nFirstDayOfMonth = 0; if(nCurrentMonth==1) { nFirstDayOfMonth = nStartDayOfCurrentYear; } else { int nDayAccum = 0; int nIndexMonth = 0; while(nIndexMonth < (nCurrentMonth-1)) { nDayAccum += s_daysPerMonth[nIndexMonth++]; } nDayAccum += nCurrentDayOfMonth; if(nCurrentMonth >= 3 && IsLeapYear(nCurrentYear)) nDayAccum += 1; nFirstDayOfMonth = (nDayAccum-(nCurrentDayOfMonth-nStartDayOfCurrentYear))%7; } // I think we have everything we need now. // Generate the calendar image to using // this floorprint... *today* should be in red. // 0 1 2 3 4 5 6 Given: January 9th, 2006 // |---------------------------| |---------------------------| // 0 | <MONTH> <YEAR> | | January 2006 | // |---|---|---|---|---|---|---| |---|---|---|---|---|---|---| // 1 | S | M | T | W | T | F | S | | S | M | T | W | T | F | S | // |---|---|---|---|---|---|---| |---|---|---|---|---|---|---| // 2 | | | | | | | | | | | | | | | 1 | // |---|---|---|---|---|---|---| |---|---|---|---|---|---|---| // 3 | | | | | | | | | 2 | 3 | 4 | 5 | 6 | 7 | 8 | // |---|---|---|---|---|---|---| |---|---|---|---|---|---|---| // 4 | | | | | | | | |<9>| 10| 11| 12| 13| 14| 15| // |---|---|---|---|---|---|---| |---|---|---|---|---|---|---| // 5 | | | | | | | | | 16| 17| 18| 19| 20| 21| 22| // |---|---|---|---|---|---|---| |---|---|---|---|---|---|---| // 6 | | | | | | | | | 23| 24| 25| 26| 27| 28| 29| // |---|---|---|---|---|---|---| |---|---|---|---|---|---|---| // 7 | | | | | | | | | 30| 31| | | | | | // |---|---|---|---|---|---|---| |---|---|---|---|---|---|---| // 8 | copyright | | copyright | // |---|---|---|---|---|---|---| |---|---|---|---|---|---|---| // Get the screen dims. int scX = GetSystemMetrics(SM_CXSCREEN); int scY = GetSystemMetrics(SM_CYSCREEN); scX = (scX - (scX/10)); scY = (scY - (scY/10)); // Make a bitmap 10% smaller in width and height HWND hwndDesktop = GetDesktopWindow(); HDC hDCDeskTop = GetDC(hwndDesktop); HDC hDC = CreateCompatibleDC(hDCDeskTop); if(hDC) { HBITMAP hBitmap = CreateCompatibleBitmap(hDCDeskTop, scX, scY); CDC dc; CBitmap bitmapCal; bitmapCal.Attach(hBitmap); dc.Attach(hDC); dc.SelectObject(bitmapCal); dc.SelectObject(GetStockObject(WHITE_BRUSH)); // Create the background dc.Rectangle(0,0,scX, scY); CImage imagePicture; HRESULT hr = imagePicture.Load(lpszInFilePathName); int nPicWidth = imagePicture.GetWidth(); int nPicHeight = imagePicture.GetHeight(); if(S_OK == hr) { HDC hDCPic = CreateCompatibleDC(hDCDeskTop); HBITMAP hBitmapPic = CreateCompatibleBitmap(hDCDeskTop, scX, scY); CBitmap bitmapPic; bitmapPic.Attach(hBitmapPic); CDC dcPic; dcPic.Attach(hDCPic); dcPic.SelectObject(hBitmapPic); dcPic.SelectObject(GetStockObject(WHITE_BRUSH)); dcPic.Rectangle(0,0,scX, scY); imagePicture.BitBlt(dcPic.GetSafeHdc(), 0, 0, SRCCOPY); BITMAP bm; bitmapPic.GetBitmap(&bm); // dc.BitBlt(0,0,scX,scY,&dcPic,0,0,SRCCOPY); // Normal draw code for debugging. BLENDFUNCTION bf; bf.AlphaFormat = 0; bf.BlendFlags = 0; bf.BlendOp = AC_SRC_OVER; bf.SourceConstantAlpha = 64; if(scX > nPicWidth) { dc.AlphaBlend((scX/2)-(nPicWidth/2),(scY/2)-(nPicHeight/2), nPicWidth,nPicHeight,&dcPic,0,0,nPicWidth,nPicHeight,bf); // Watermark type drawing. } else { int nLeftPos = nPicWidth/2-(scX/2); int nTopPos = nPicHeight/2-(scY/2); if(nLeftPos<0) nLeftPos=0; if(nTopPos<0) nTopPos=0; dc.AlphaBlend(0,0,scX,scY,&dcPic,nLeftPos,nTopPos, scX-nLeftPos,scY-nTopPos,bf); // Watermark type drawing. } } dc.SetBkMode(TRANSPARENT); int nRectWidth = scX/7; int nRectHeight = scY/8; if(m_fontTitle.GetSafeHandle()==0) { MakeFonts(dc); } dc.SetTextColor(GetSysColor(COLOR_DESKTOP)); // Draw header (row 0) dc.SelectObject(&m_fontTitle); CString strOut = strMonth; strOut += L" "; strOut += strYear; CRect rectHeader(0, 0, scX, nRectHeight/2); dc.DrawText(strOut, strOut.GetLength(), rectHeader, DT_CENTER|DT_VCENTER|DT_SINGLELINE); // Draw days of the week (row 1) dc.SelectObject(&m_fontDays); CRect rectDay(0, nRectHeight/2, nRectWidth, nRectHeight*2); for(int nIndexDayNames = 0; nIndexDayNames <= 6; nIndexDayNames++) { dc.DrawText(s_dayNames[nIndexDayNames], lstrlen(s_dayNames[nIndexDayNames]), rectDay, DT_CENTER|DT_VCENTER|DT_SINGLELINE); rectDay.left += nRectWidth; rectDay.right = rectDay.left + nRectWidth; } // Draw days of the month in each the grid (rows 2-7) dc.SelectObject(&m_fontNumbers); rectDay.left = nRectWidth * nFirstDayOfMonth; rectDay.right = rectDay.left + nRectWidth; rectDay.top = nRectHeight+nRectHeight/2; rectDay.bottom = rectDay.top+nRectHeight; int nNextRow = nFirstDayOfMonth; int nLastDayInMonth = CCalendarMaker::s_daysPerMonth[nCurrentMonth-1]; if(nCurrentMonth==2 && IsLeapYear(nCurrentYear) ) nLastDayInMonth += 1; for(int nIndexDayNumbers = 1; nIndexDayNumbers <= nLastDayInMonth; nIndexDayNumbers++) { CString strDayNumber; strDayNumber.Format(L"%d", nIndexDayNumbers); if(nIndexDayNumbers==nCurrentDayOfMonth) { dc.SetTextColor(RGB(255,0,0)); } dc.DrawText(strDayNumber, strDayNumber.GetLength(), rectDay, DT_CENTER|DT_VCENTER|DT_SINGLELINE); if(nIndexDayNumbers==nCurrentDayOfMonth) dc.SetTextColor(GetSysColor(COLOR_DESKTOP)); rectDay.left += nRectWidth; rectDay.right = rectDay.left + nRectWidth; nNextRow++; if(nNextRow == 7) { rectDay.left = 0; rectDay.right = nRectWidth; rectDay.top += nRectHeight; rectDay.bottom = rectDay.top+nRectHeight; nNextRow = 0; } } //Draw footer (row 8) dc.SelectObject(&m_fontTitle); CRect rectFooter(0, nRectHeight*7, scX, nRectHeight*8); rectFooter.bottom -= 10; strOut.LoadStringW(IDS_WEBSITE); dc.DrawText(strOut, strOut.GetLength(), rectFooter, DT_CENTER|DT_VCENTER|DT_SINGLELINE); dc.SelectObject(&m_fontDays); strOut.LoadStringW(IDS_COPYRIGHT); dc.DrawText(strOut, strOut.GetLength(), rectFooter, DT_CENTER|DT_BOTTOM|DT_SINGLELINE); // Store the image CImage newImage; newImage.Attach((HBITMAP)dc.GetCurrentBitmap()->GetSafeHandle()); newImage.Save(lpszOutFilePathName); } return true; }
图 (6)
如果我遗漏了任何你认为应该在文章中提及的细节,请告诉我。
如果你能花一秒钟时间来评价这篇文章,甚至留下评论,将不胜感激。
感谢阅读!
历史
- 1.04 - 2006 年 7 月 1 日
- 修复了安装程序中的一个错误,该错误导致程序在重启后无法启动。
- 配置对话框的视图模式组合框是一个下拉列表……无法再键入。
- 1.03 - 2006 年 6 月 16 日
- 修复了导致 2007 年和 2008 年起始日期计算错误的 bug。
- 1.02 - 2006 年 6 月 15 日
- 指示一些节日(新年、情人节、总统节、圣帕特里克节、复活节、阵亡将士纪念日、五月五日节、母亲节、父亲节、独立日、劳动节、万圣节和圣诞节)。
- 1.01 - 2006 年 6 月 14 日
- 包含配置工具。
- 用户选择的布局。
- 用户选择的颜色。
- 用户选择的 Alpha 混合值。
- 功能齐全的安装和卸载。
- 1.00 - 2006 年 6 月 10 日
- 初始发布。