reactos/base/shell/explorer/trayclock.cpp
Carl J. Bialorucki 5b40f6f353
[EXPLORER] Add support for Windows 7 style system tray icon spacing (#5489)
When using large taskbar icon:
- Increase the padding around system tray icons.
- Push the clock text further to the right, increasing the left padding
  between the clock and notification icons.
  This matches Windows 7 shell behavior.
- Correct the spacing for the clock area, in the case when bPreferDate is
  enabled, only two lines are visible, and the day of the week is shorter
  than the date.

CORE-11698
2023-08-04 14:20:19 +03:00

783 lines
20 KiB
C++

/*
* ReactOS Explorer
*
* Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
* Copyright 2018 Ged Murphy <gedmurphy@reactos.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "precomp.h"
/*
* TrayClockWnd
*/
const struct
{
BOOL IsTime;
DWORD dwFormatFlags;
LPCWSTR lpFormat;
} ClockWndFormats[] = {
{ TRUE, 0, NULL },
{ FALSE, 0, L"dddd" },
{ FALSE, DATE_SHORTDATE, NULL }
};
const UINT ClockWndFormatsCount = _ARRAYSIZE(ClockWndFormats);
#define CLOCKWND_FORMAT_COUNT ClockWndFormatsCount
#define CLOCKWND_FORMAT_TIME 0
#define CLOCKWND_FORMAT_DAY 1
#define CLOCKWND_FORMAT_DATE 2
static const WCHAR szTrayClockWndClass[] = L"TrayClockWClass";
class CTrayClockWnd :
public CComCoClass<CTrayClockWnd>,
public CComObjectRootEx<CComMultiThreadModelNoCS>,
public CWindowImpl < CTrayClockWnd, CWindow, CControlWinTraits >,
public IOleWindow
{
HFONT hFont;
COLORREF textColor;
RECT rcText;
SYSTEMTIME LocalTime;
CTooltips m_tooltip;
union
{
DWORD dwFlags;
struct
{
DWORD IsTimerEnabled : 1;
DWORD IsInitTimerEnabled : 1;
DWORD LinesMeasured : 1;
DWORD IsHorizontal : 1;
};
};
DWORD LineSpacing;
SIZE CurrentSize;
WORD VisibleLines;
SIZE LineSizes[CLOCKWND_FORMAT_COUNT];
WCHAR szLines[CLOCKWND_FORMAT_COUNT][48];
public:
CTrayClockWnd();
virtual ~CTrayClockWnd();
private:
LRESULT OnThemeChanged();
LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
BOOL MeasureLines();
WORD GetMinimumSize(IN BOOL Horizontal, IN OUT PSIZE pSize);
VOID UpdateWnd();
VOID Update();
UINT CalculateDueTime();
BOOL ResetTime();
VOID CalibrateTimer();
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
VOID SetFont(IN HFONT hNewFont, IN BOOL bRedraw);
LRESULT DrawBackground(HDC hdc);
LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnTaskbarSettingsChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnLButtonDblClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
VOID PaintLine(IN HDC hDC, IN OUT RECT *rcClient, IN UINT LineNumber, IN UINT szLinesIndex);
public:
HRESULT WINAPI GetWindow(HWND* phwnd)
{
if (!phwnd)
return E_INVALIDARG;
*phwnd = m_hWnd;
return S_OK;
}
HRESULT WINAPI ContextSensitiveHelp(BOOL fEnterMode)
{
return E_NOTIMPL;
}
DECLARE_NOT_AGGREGATABLE(CTrayClockWnd)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CTrayClockWnd)
COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
END_COM_MAP()
DECLARE_WND_CLASS_EX(szTrayClockWndClass, CS_DBLCLKS, COLOR_3DFACE)
BEGIN_MSG_MAP(CTrayClockWnd)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
MESSAGE_HANDLER(WM_TIMER, OnTimer)
MESSAGE_HANDLER(WM_CONTEXTMENU, OnContextMenu)
MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE, OnGetMinimumSize)
MESSAGE_HANDLER(TWM_SETTINGSCHANGED, OnTaskbarSettingsChanged)
MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDblClick)
END_MSG_MAP()
HRESULT Initialize(IN HWND hWndParent);
};
#define ID_TRAYCLOCK_TIMER 0
#define ID_TRAYCLOCK_TIMER_INIT 1
#define TRAY_CLOCK_WND_SPACING_X 5
#define TRAY_CLOCK_WND_SPACING_Y 0
CTrayClockWnd::CTrayClockWnd() :
hFont(NULL),
textColor(0),
dwFlags(0),
LineSpacing(0),
VisibleLines(0)
{
ZeroMemory(&rcText, sizeof(rcText));
ZeroMemory(&LocalTime, sizeof(LocalTime));
ZeroMemory(&CurrentSize, sizeof(CurrentSize));
ZeroMemory(LineSizes, sizeof(LineSizes));
ZeroMemory(szLines, sizeof(szLines));
}
CTrayClockWnd::~CTrayClockWnd() { }
LRESULT CTrayClockWnd::OnThemeChanged()
{
LOGFONTW clockFont;
HTHEME clockTheme;
HFONT hFont;
clockTheme = OpenThemeData(m_hWnd, L"Clock");
if (clockTheme)
{
GetThemeFont(clockTheme, NULL, CLP_TIME, 0, TMT_FONT, &clockFont);
hFont = CreateFontIndirectW(&clockFont);
GetThemeColor(clockTheme, CLP_TIME, 0, TMT_TEXTCOLOR, &textColor);
if (this->hFont != NULL)
DeleteObject(this->hFont);
SetFont(hFont, FALSE);
CloseThemeData(clockTheme);
}
return TRUE;
}
LRESULT CTrayClockWnd::OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
return OnThemeChanged();
}
BOOL CTrayClockWnd::MeasureLines()
{
HDC hDC;
HFONT hPrevFont;
UINT c, i;
BOOL bRet = TRUE;
hDC = GetDC();
if (hDC != NULL)
{
if (hFont)
hPrevFont = (HFONT) SelectObject(hDC, hFont);
for (i = 0; i < CLOCKWND_FORMAT_COUNT && bRet; i++)
{
if (szLines[i][0] != L'\0' &&
!GetTextExtentPointW(hDC, szLines[i], wcslen(szLines[i]),
&LineSizes[i]))
{
bRet = FALSE;
break;
}
}
if (hFont)
SelectObject(hDC, hPrevFont);
ReleaseDC(hDC);
if (bRet)
{
LineSpacing = 0;
/* calculate the line spacing */
for (i = 0, c = 0; i < CLOCKWND_FORMAT_COUNT; i++)
{
if (LineSizes[i].cx > 0)
{
LineSpacing += LineSizes[i].cy;
c++;
}
}
if (c > 0)
{
/* We want a spacing of 1/2 line */
LineSpacing = (LineSpacing / c) / 2;
}
return TRUE;
}
}
return FALSE;
}
WORD CTrayClockWnd::GetMinimumSize(IN BOOL Horizontal, IN OUT PSIZE pSize)
{
WORD iLinesVisible = 0;
UINT i;
SIZE szMax = { 0, 0 };
if (!LinesMeasured)
LinesMeasured = MeasureLines();
if (!LinesMeasured)
return 0;
/* Prevents the date from being cut off when the day of the week is shorter than the date. */
if (VisibleLines > 1 && g_TaskbarSettings.bPreferDate)
szMax.cx = LineSizes[CLOCKWND_FORMAT_DATE].cx;
for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
{
if (LineSizes[i].cx != 0)
{
if (iLinesVisible > 0)
{
if (Horizontal)
{
if (szMax.cy + LineSizes[i].cy + (LONG) LineSpacing >
pSize->cy - (2 * TRAY_CLOCK_WND_SPACING_Y))
{
break;
}
}
else
{
if (LineSizes[i].cx > pSize->cx - (2 * TRAY_CLOCK_WND_SPACING_X))
break;
}
/* Add line spacing */
szMax.cy += LineSpacing;
}
iLinesVisible++;
/* Increase maximum rectangle */
szMax.cy += LineSizes[i].cy;
if (LineSizes[i].cx > szMax.cx)
szMax.cx = LineSizes[i].cx;
}
}
szMax.cx += 2 * TRAY_CLOCK_WND_SPACING_X;
szMax.cy += 2 * TRAY_CLOCK_WND_SPACING_Y;
*pSize = szMax;
return iLinesVisible;
}
VOID CTrayClockWnd::UpdateWnd()
{
SIZE szPrevCurrent;
UINT BufSize, i;
INT iRet;
RECT rcClient;
ZeroMemory(LineSizes, sizeof(LineSizes));
szPrevCurrent = CurrentSize;
for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
{
szLines[i][0] = L'\0';
BufSize = _countof(szLines[0]);
if (ClockWndFormats[i].IsTime)
{
iRet = GetTimeFormat(LOCALE_USER_DEFAULT,
g_TaskbarSettings.bShowSeconds ? ClockWndFormats[i].dwFormatFlags : TIME_NOSECONDS,
&LocalTime,
ClockWndFormats[i].lpFormat,
szLines[i],
BufSize);
}
else
{
iRet = GetDateFormat(LOCALE_USER_DEFAULT,
ClockWndFormats[i].dwFormatFlags,
&LocalTime,
ClockWndFormats[i].lpFormat,
szLines[i],
BufSize);
}
if (iRet != 0 && i == 0)
{
/* Set the window text to the time only */
SetWindowText(szLines[i]);
}
}
LinesMeasured = MeasureLines();
if (LinesMeasured &&
GetClientRect(&rcClient))
{
SIZE szWnd;
szWnd.cx = rcClient.right;
szWnd.cy = rcClient.bottom;
VisibleLines = GetMinimumSize(IsHorizontal, &szWnd);
CurrentSize = szWnd;
}
if (IsWindowVisible())
{
InvalidateRect(NULL, TRUE);
if (szPrevCurrent.cx != CurrentSize.cx ||
szPrevCurrent.cy != CurrentSize.cy)
{
/* Ask the parent to resize */
NMHDR nmh = {GetParent(), 0, NTNWM_REALIGN};
GetParent().SendMessage(WM_NOTIFY, 0, (LPARAM) &nmh);
}
}
int iDateLength = GetDateFormat(LOCALE_USER_DEFAULT,
DATE_LONGDATE,
&LocalTime,
NULL,
NULL,
0);
if (iDateLength <= 0)
{
return;
}
WCHAR* szDate = new WCHAR[iDateLength];
if (GetDateFormat(LOCALE_USER_DEFAULT,
DATE_LONGDATE,
&LocalTime,
NULL,
szDate,
iDateLength) > 0)
{
m_tooltip.UpdateTipText(m_hWnd,
reinterpret_cast<UINT_PTR>(m_hWnd),
szDate);
}
delete[] szDate;
}
VOID CTrayClockWnd::Update()
{
GetLocalTime(&LocalTime);
UpdateWnd();
}
UINT CTrayClockWnd::CalculateDueTime()
{
UINT uiDueTime;
GetLocalTime(&LocalTime);
uiDueTime = 1000 - (UINT) LocalTime.wMilliseconds;
if (!g_TaskbarSettings.bShowSeconds)
uiDueTime += (59 - (UINT) LocalTime.wSecond) * 1000;
return uiDueTime;
}
BOOL CTrayClockWnd::ResetTime()
{
UINT uiDueTime;
BOOL Ret;
/* Disable all timers */
if (IsTimerEnabled)
{
KillTimer(ID_TRAYCLOCK_TIMER);
IsTimerEnabled = FALSE;
}
else if (IsInitTimerEnabled)
{
KillTimer(ID_TRAYCLOCK_TIMER_INIT);
}
uiDueTime = CalculateDueTime();
/* Set the new timer */
Ret = SetTimer(ID_TRAYCLOCK_TIMER_INIT, uiDueTime, NULL) != 0;
IsInitTimerEnabled = Ret;
return Ret;
}
VOID CTrayClockWnd::CalibrateTimer()
{
UINT uiDueTime;
BOOL Ret;
UINT uiWait1, uiWait2;
/* Kill the initialization timer */
KillTimer(ID_TRAYCLOCK_TIMER_INIT);
IsInitTimerEnabled = FALSE;
uiDueTime = CalculateDueTime();
if (g_TaskbarSettings.bShowSeconds)
{
uiWait1 = 1000 - 200;
uiWait2 = 1000;
}
else
{
uiWait1 = 60 * 1000 - 200;
uiWait2 = 60 * 1000;
}
if (uiDueTime > uiWait1)
{
/* The update of the clock will be up to 200 ms late, but that's
acceptable. We're going to setup a timer that fires depending
uiWait2. */
Ret = SetTimer(ID_TRAYCLOCK_TIMER, uiWait2, NULL) != 0;
IsTimerEnabled = Ret;
}
else
{
/* Recalibrate the timer and recalculate again when the current
minute/second ends. */
ResetTime();
}
/* Update the time */
Update();
}
LRESULT CTrayClockWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
/* Disable all timers */
if (IsTimerEnabled)
{
KillTimer(ID_TRAYCLOCK_TIMER);
}
else if (IsInitTimerEnabled)
{
KillTimer(ID_TRAYCLOCK_TIMER_INIT);
}
return TRUE;
}
LRESULT CTrayClockWnd::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
RECT rcClient;
HFONT hPrevFont;
INT iPrevBkMode;
UINT i, line;
PAINTSTRUCT ps;
HDC hDC = (HDC) wParam;
if (wParam == 0)
hDC = BeginPaint(&ps);
if (hDC == NULL)
return FALSE;
if (LinesMeasured &&
GetClientRect(&rcClient))
{
iPrevBkMode = SetBkMode(hDC, TRANSPARENT);
if (!IsAppThemed())
textColor = ::GetSysColor(COLOR_BTNTEXT);
::SetTextColor(hDC, textColor);
hPrevFont = (HFONT) SelectObject(hDC, hFont);
rcClient.top = (rcClient.bottom - CurrentSize.cy) / 2;
rcClient.bottom = rcClient.top + CurrentSize.cy;
if (VisibleLines == 2)
{
/* Display either time and weekday (by default), or time and date (opt-in) */
PaintLine(hDC, &rcClient, 0, CLOCKWND_FORMAT_TIME);
PaintLine(hDC, &rcClient, 1,
g_TaskbarSettings.bPreferDate ? CLOCKWND_FORMAT_DATE : CLOCKWND_FORMAT_DAY);
}
else
{
for (i = 0, line = 0;
i < CLOCKWND_FORMAT_COUNT && line < VisibleLines;
i++)
{
PaintLine(hDC, &rcClient, i, i);
line++;
}
}
SelectObject(hDC, hPrevFont);
SetBkMode(hDC, iPrevBkMode);
}
if (wParam == 0)
EndPaint(&ps);
return TRUE;
}
VOID CTrayClockWnd::PaintLine(IN HDC hDC, IN OUT RECT *rcClient, IN UINT LineNumber, IN UINT szLinesIndex)
{
if (LineSizes[LineNumber].cx == 0)
return;
INT HShift = ((IsHorizontal && (VisibleLines <= 1 ||
g_TaskbarSettings.bCompactTrayIcons)) ? 0 : TRAY_CLOCK_WND_SPACING_X);
TextOut(hDC,
((rcClient->right - LineSizes[szLinesIndex].cx) / 2) + HShift,
rcClient->top + TRAY_CLOCK_WND_SPACING_Y,
szLines[szLinesIndex],
wcslen(szLines[szLinesIndex]));
rcClient->top += LineSizes[LineNumber].cy + LineSpacing;
}
VOID CTrayClockWnd::SetFont(IN HFONT hNewFont, IN BOOL bRedraw)
{
hFont = hNewFont;
LinesMeasured = MeasureLines();
if (bRedraw)
{
InvalidateRect(NULL, TRUE);
}
}
LRESULT CTrayClockWnd::DrawBackground(HDC hdc)
{
RECT rect;
GetClientRect(&rect);
DrawThemeParentBackground(m_hWnd, hdc, &rect);
return TRUE;
}
LRESULT CTrayClockWnd::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HDC hdc = (HDC) wParam;
if (!IsAppThemed())
{
bHandled = FALSE;
return 0;
}
return DrawBackground(hdc);
}
LRESULT CTrayClockWnd::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
switch (wParam)
{
case ID_TRAYCLOCK_TIMER:
Update();
break;
case ID_TRAYCLOCK_TIMER_INIT:
CalibrateTimer();
break;
}
return TRUE;
}
LRESULT CTrayClockWnd::OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
IsHorizontal = (BOOL) wParam;
return (LRESULT) GetMinimumSize((BOOL) wParam, (PSIZE) lParam) != 0;
}
LRESULT CTrayClockWnd::OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
return GetParent().SendMessage(uMsg, wParam, lParam);
}
LRESULT CTrayClockWnd::OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SetFont((HFONT) wParam, (BOOL) LOWORD(lParam));
return TRUE;
}
LRESULT CTrayClockWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
m_tooltip.Create(m_hWnd, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP);
TOOLINFOW ti = { 0 };
ti.cbSize = TTTOOLINFOW_V1_SIZE;
ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
ti.hwnd = m_hWnd;
ti.uId = reinterpret_cast<UINT_PTR>(m_hWnd);
ti.lpszText = NULL;
ti.lParam = NULL;
m_tooltip.AddTool(&ti);
if (!g_TaskbarSettings.sr.HideClock)
{
ResetTime();
}
/* Update the time */
Update();
return TRUE;
}
LRESULT CTrayClockWnd::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SIZE szClient;
szClient.cx = LOWORD(lParam);
szClient.cy = HIWORD(lParam);
VisibleLines = GetMinimumSize(IsHorizontal, &szClient);
CurrentSize = szClient;
InvalidateRect(NULL, TRUE);
return TRUE;
}
LRESULT CTrayClockWnd::OnTaskbarSettingsChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
BOOL bRealign = FALSE;
TaskbarSettings* newSettings = (TaskbarSettings*)lParam;
if (newSettings->bShowSeconds != g_TaskbarSettings.bShowSeconds)
{
g_TaskbarSettings.bShowSeconds = newSettings->bShowSeconds;
if (!g_TaskbarSettings.sr.HideClock)
{
bRealign = TRUE;
ResetTime();
}
}
if (newSettings->sr.HideClock != g_TaskbarSettings.sr.HideClock)
{
g_TaskbarSettings.sr.HideClock = newSettings->sr.HideClock;
ShowWindow(g_TaskbarSettings.sr.HideClock ? SW_HIDE : SW_SHOW);
bRealign = TRUE;
if (g_TaskbarSettings.sr.HideClock)
{
/* Disable all timers */
if (IsTimerEnabled)
{
KillTimer(ID_TRAYCLOCK_TIMER);
IsTimerEnabled = FALSE;
}
else if (IsInitTimerEnabled)
{
KillTimer(ID_TRAYCLOCK_TIMER_INIT);
IsInitTimerEnabled = FALSE;
}
}
else
{
ResetTime();
}
}
if (newSettings->bPreferDate != g_TaskbarSettings.bPreferDate)
{
g_TaskbarSettings.bPreferDate = newSettings->bPreferDate;
bRealign = TRUE;
}
if (bRealign)
{
/* Ask the parent to resize */
NMHDR nmh = {GetParent(), 0, NTNWM_REALIGN};
GetParent().SendMessage(WM_NOTIFY, 0, (LPARAM) &nmh);
Update();
}
return 0;
}
LRESULT CTrayClockWnd::OnLButtonDblClick(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (IsWindowVisible())
{
//FIXME: use SHRunControlPanel
ShellExecuteW(m_hWnd, NULL, L"timedate.cpl", NULL, NULL, SW_NORMAL);
}
return TRUE;
}
HRESULT CTrayClockWnd::Initialize(IN HWND hWndParent)
{
IsHorizontal = TRUE;
/* Create the window. The tray window is going to move it to the correct
position and resize it as needed. */
DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
if (!g_TaskbarSettings.sr.HideClock)
dwStyle |= WS_VISIBLE;
Create(hWndParent, 0, NULL, dwStyle);
if (!m_hWnd)
return E_FAIL;
SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
return S_OK;
};
HRESULT CTrayClockWnd_CreateInstance(HWND hwndParent, REFIID riid, void **ppv)
{
return ShellObjectCreatorInit<CTrayClockWnd>(hwndParent, riid, ppv);
}