这个时钟在上个的基础上增加了更多的功能

这个时钟平时的时候浮在桌面上,如果挡住桌面了只要把鼠标放上去就隐身了。
鼠标左键可以移动它,右键可以选择各种选项。
比如:选择颜色,选择大小,和退出。
可以成为你桌面上会动的小挂件哦。
因为它和现实中的时钟一样秒针,分针,和时针一直再转。
在我的资源里可以下载exe绿色版(已更新开机启动)。
下面的代码就可以实现。
cpp
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define _USE_MATH_DEFINES
#include <windows.h>
#include <d2d1.h>
#include <d2d1helper.h> // ←←← 关键!提供 D2D1::ColorF, Matrix3x2F 等
#include <dwrite.h>
#include <atlbase.h>
#include <cmath>
#include <algorithm>
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")
#ifndef GET_X_LPARAM
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#endif
#ifndef GET_Y_LPARAM
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
#endif
// =============================================
// GraphicsScene - 渲染基类
// =============================================
class GraphicsScene
{
protected:
CComPtr<ID2D1Factory> m_pFactory;
CComPtr<ID2D1DCRenderTarget> m_pDCRenderTarget;
CComPtr<IDWriteFactory> m_pDWriteFactory;
HWND m_hwnd = nullptr;
HDC m_hdcMem = nullptr;
HBITMAP m_hBitmap = nullptr;
UINT m_width = 0;
UINT m_height = 0;
BYTE m_alpha = 255;
public:
GraphicsScene() = default;
virtual ~GraphicsScene() { CleanUp(); }
HRESULT Initialize(HWND hwnd)
{
if (!hwnd) return E_INVALIDARG;
m_hwnd = hwnd;
HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pFactory);
if (FAILED(hr)) return hr;
hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&m_pDWriteFactory.p)
);
return hr;
}
void SetAlpha(BYTE alpha) { m_alpha = alpha; }
protected:
ID2D1RenderTarget* GetRenderTarget() const { return m_pDCRenderTarget; }
HRESULT CreateGraphicsResources()
{
if (m_pDCRenderTarget || m_width == 0 || m_height == 0)
return S_OK;
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = static_cast<LONG>(m_width);
bmi.bmiHeader.biHeight = -static_cast<LONG>(m_height);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void* pBits = nullptr;
m_hBitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0);
if (!m_hBitmap) return E_FAIL;
m_hdcMem = CreateCompatibleDC(nullptr);
if (!m_hdcMem) { DeleteObject(m_hBitmap); m_hBitmap = nullptr; return E_FAIL; }
SelectObject(m_hdcMem, m_hBitmap);
D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
0, 0,
D2D1_RENDER_TARGET_USAGE_NONE,
D2D1_FEATURE_LEVEL_DEFAULT
);
HRESULT hr = m_pFactory->CreateDCRenderTarget(&props, &m_pDCRenderTarget);
if (SUCCEEDED(hr))
{
RECT rc = { 0, 0, static_cast<LONG>(m_width), static_cast<LONG>(m_height) };
hr = m_pDCRenderTarget->BindDC(m_hdcMem, &rc);
}
if (SUCCEEDED(hr))
{
hr = CreateDeviceDependentResources();
}
if (SUCCEEDED(hr))
{
CalculateLayout();
}
return hr;
}
public:
void Render()
{
if (!m_hwnd) return;
HRESULT hr = CreateGraphicsResources();
if (FAILED(hr)) return;
auto pRT = GetRenderTarget();
if (!pRT) return;
pRT->BeginDraw();
pRT->Clear(D2D1::ColorF(0, 0, 0, 0));
RenderScene();
hr = pRT->EndDraw();
if (hr == D2DERR_RECREATE_TARGET)
{
DiscardDeviceDependentResources();
m_pDCRenderTarget.Release();
if (m_hBitmap) { DeleteObject(m_hBitmap); m_hBitmap = nullptr; }
if (m_hdcMem) { DeleteDC(m_hdcMem); m_hdcMem = nullptr; }
}
POINT ptDst = { 0, 0 };
ClientToScreen(m_hwnd, &ptDst);
SIZE size = { static_cast<LONG>(m_width), static_cast<LONG>(m_height) };
POINT ptSrc = { 0, 0 };
BLENDFUNCTION blend = { AC_SRC_OVER, 0, m_alpha, AC_SRC_ALPHA };
UpdateLayeredWindow(m_hwnd, nullptr, &ptDst, &size, m_hdcMem, &ptSrc, 0, &blend, ULW_ALPHA);
}
HRESULT Resize(UINT width, UINT height)
{
if (width == 0 || height == 0) return E_INVALIDARG;
if (m_width == width && m_height == height) return S_OK;
m_width = width;
m_height = height;
DiscardDeviceDependentResources();
m_pDCRenderTarget.Release();
if (m_hBitmap) { DeleteObject(m_hBitmap); m_hBitmap = nullptr; }
if (m_hdcMem) { DeleteDC(m_hdcMem); m_hdcMem = nullptr; }
return S_OK;
}
void CleanUp()
{
DiscardDeviceDependentResources();
m_pDCRenderTarget.Release();
if (m_hBitmap) { DeleteObject(m_hBitmap); m_hBitmap = nullptr; }
if (m_hdcMem) { DeleteDC(m_hdcMem); m_hdcMem = nullptr; }
m_pFactory.Release();
m_pDWriteFactory.Release();
}
virtual HRESULT CreateDeviceIndependentResources() = 0;
virtual void DiscardDeviceIndependentResources() = 0;
virtual HRESULT CreateDeviceDependentResources() = 0;
virtual void DiscardDeviceDependentResources() = 0;
virtual void CalculateLayout() = 0;
virtual void RenderScene() = 0;
};
// =============================================
// Scene - 模拟时钟(增强版)
// =============================================
class Scene : public GraphicsScene
{
public:
enum class ClockColor {
LightBlue,
LightRed,
LightYellow,
LightGreen
};
private:
CComPtr<ID2D1SolidColorBrush> m_pFill;
CComPtr<ID2D1SolidColorBrush> m_pStroke;
CComPtr<IDWriteTextFormat> m_pTextFormat;
CComPtr<IDWriteTextLayout> m_pTextLayouts[12];
D2D1_ELLIPSE m_ellipse;
D2D_POINT_2F m_NumberPositions[12];
ClockColor m_currentColor = ClockColor::LightBlue;
D2D1_COLOR_F GetColorFor(ClockColor color) const
{
switch (color)
{
case ClockColor::LightBlue: return D2D1::ColorF(0.68f, 0.85f, 0.90f); // #ADD8E6
case ClockColor::LightRed: return D2D1::ColorF(1.00f, 0.71f, 0.76f); // #FFB6C1
case ClockColor::LightYellow:return D2D1::ColorF(1.00f, 1.00f, 0.88f); // #FFFFE0
case ClockColor::LightGreen: return D2D1::ColorF(0.88f, 0.98f, 0.88f); // #E0F0E0
default: return D2D1::ColorF(1.0f, 1.0f, 0);
}
}
public:
void SetClockColor(ClockColor color)
{
if (m_currentColor == color) return;
m_currentColor = color;
if (m_pFill)
{
m_pFill->SetColor(GetColorFor(color));
}
}
HRESULT CreateDeviceIndependentResources() override
{
return m_pDWriteFactory->CreateTextFormat(
L"Segoe UI", nullptr,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
16.0f, L"",
&m_pTextFormat
);
}
void DiscardDeviceIndependentResources() override
{
m_pTextFormat.Release();
}
HRESULT CreateDeviceDependentResources() override
{
auto pRT = GetRenderTarget();
HRESULT hr = pRT->CreateSolidColorBrush(GetColorFor(m_currentColor), &m_pFill);
if (SUCCEEDED(hr))
{
hr = pRT->CreateSolidColorBrush(D2D1::ColorF(0, 0, 0), &m_pStroke);
}
return hr;
}
void DiscardDeviceDependentResources() override
{
m_pFill.Release();
m_pStroke.Release();
for (auto& layout : m_pTextLayouts) layout.Release();
}
void CalculateLayout() override
{
if (!GetRenderTarget()) return;
float x = static_cast<float>(m_width) / 2.0f;
float y = static_cast<float>(m_height) / 2.0f;
float radius = std::min(x, y) * 0.9f;
float fontSize = radius * 0.15f;
m_pTextFormat.Release();
m_pDWriteFactory->CreateTextFormat(
L"Segoe UI", nullptr,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
fontSize, L"",
&m_pTextFormat
);
m_ellipse = D2D1::Ellipse(D2D1::Point2F(x, y), radius, radius);
for (int i = 1; i <= 12; ++i)
{
wchar_t num[4];
swprintf_s(num, L"%d", i);
m_pTextLayouts[i - 1].Release();
m_pDWriteFactory->CreateTextLayout(num, static_cast<UINT32>(wcslen(num)),
m_pTextFormat, 100.0f, 100.0f, &m_pTextLayouts[i - 1]);
float angle = (360.0f / 12) * (i - 3);
float rad = angle * static_cast<float>(M_PI) / 180.0f;
float r = radius * 0.75f;
m_NumberPositions[i - 1] = D2D1::Point2F(
x + cos(rad) * r,
y + sin(rad) * r
);
}
}
void DrawClockHand(float fHandLength, float fAngle, float fStrokeWidth)
{
auto pRT = GetRenderTarget();
D2D1_MATRIX_3X2_F oldTransform;
pRT->GetTransform(&oldTransform);
pRT->SetTransform(D2D1::Matrix3x2F::Rotation(fAngle, m_ellipse.point));
D2D_POINT_2F endPoint = D2D1::Point2F(
m_ellipse.point.x,
m_ellipse.point.y - m_ellipse.radiusY * fHandLength
);
pRT->DrawLine(m_ellipse.point, endPoint, m_pStroke, fStrokeWidth);
pRT->SetTransform(oldTransform);
}
void RenderScene() override
{
auto pRT = GetRenderTarget();
pRT->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
pRT->FillEllipse(m_ellipse, m_pFill);
pRT->DrawEllipse(m_ellipse, m_pStroke, 2.0f);
for (int i = 0; i < 12; ++i)
{
float angle = 30.0f * i;
D2D1::Matrix3x2F mat = D2D1::Matrix3x2F::Rotation(angle, m_ellipse.point);
D2D_POINT_2F inner = mat.TransformPoint(D2D1::Point2F(m_ellipse.point.x, m_ellipse.point.y - m_ellipse.radiusY * 0.85f));
D2D_POINT_2F outer = mat.TransformPoint(D2D1::Point2F(m_ellipse.point.x, m_ellipse.point.y - m_ellipse.radiusY * 0.95f));
pRT->DrawLine(inner, outer, m_pStroke, (i % 3 == 0) ? 2.0f : 1.0f);
}
for (int i = 0; i < 12; ++i)
{
DWRITE_TEXT_METRICS metrics;
if (SUCCEEDED(m_pTextLayouts[i]->GetMetrics(&metrics)))
{
D2D_POINT_2F origin = D2D1::Point2F(
m_NumberPositions[i].x - metrics.width / 2,
m_NumberPositions[i].y - metrics.height / 2
);
pRT->DrawTextLayout(origin, m_pTextLayouts[i], m_pStroke);
}
}
SYSTEMTIME time;
GetLocalTime(&time);
float hourAngle = (360.0f / 12) * (time.wHour % 12) + time.wMinute * 0.5f;
float minuteAngle = (360.0f / 60) * time.wMinute;
float secondAngle = (360.0f / 60) * time.wSecond + (360.0f / 60000) * time.wMilliseconds;
DrawClockHand(0.6f, hourAngle, 6.0f);
DrawClockHand(0.85f, minuteAngle, 4.0f);
DrawClockHand(0.85f, secondAngle, 1.0f);
}
};
// =============================================
// MainWindow
// =============================================
class MainWindow
{
public:
HWND m_hwnd = nullptr;
private:
Scene m_scene;
bool m_hovering = false;
POINT m_dragStart;
enum WindowSize { Small = 150, Medium = 250, Large = 350, ExtraLarge = 450 };
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
MainWindow* self = nullptr;
if (msg == WM_NCCREATE)
{
CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
self = static_cast<MainWindow*>(cs->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(self));
self->m_hwnd = hwnd;
}
else
{
self = reinterpret_cast<MainWindow*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
}
if (self)
return self->HandleMessage(msg, wParam, lParam);
return DefWindowProc(hwnd, msg, wParam, lParam);
}
public:
bool Create()
{
WNDCLASSEXW wc = {};
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = WndProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = L"TransparentClock";
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
RegisterClassExW(&wc);
m_hwnd = CreateWindowExW(
WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
L"TransparentClock",
L"Analog Clock",
WS_POPUP,
CW_USEDEFAULT, CW_USEDEFAULT, Medium, Medium,
nullptr, nullptr, GetModuleHandle(nullptr), this
);
return m_hwnd != nullptr;
}
void ApplyWindowSize(WindowSize size)
{
SetWindowPos(m_hwnd, HWND_TOPMOST, 0, 0, size, size, SWP_NOMOVE | SWP_NOACTIVATE);
m_scene.Resize(size, size);
}
LRESULT HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
if (FAILED(m_scene.Initialize(m_hwnd)))
return -1;
m_scene.Resize(Medium, Medium);
m_scene.SetAlpha(255);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(m_hwnd, &ps);
EndPaint(m_hwnd, &ps);
return 0;
}
case WM_SIZE:
m_scene.Resize(LOWORD(lParam), HIWORD(lParam));
return 0;
case WM_MOUSEMOVE:
if (!m_hovering)
{
m_hovering = true;
m_scene.SetAlpha(30);
TRACKMOUSEEVENT tme = { sizeof(tme), TME_LEAVE, m_hwnd, 0 };
TrackMouseEvent(&tme);
}
if (GetCapture() == m_hwnd)
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
ClientToScreen(m_hwnd, &pt);
SetWindowPos(m_hwnd, HWND_TOPMOST,
pt.x - m_dragStart.x,
pt.y - m_dragStart.y,
0, 0, SWP_NOSIZE | SWP_NOACTIVATE);
}
return 0;
case WM_MOUSELEAVE:
m_hovering = false;
m_scene.SetAlpha(255);
return 0;
case WM_LBUTTONDOWN:
m_dragStart.x = GET_X_LPARAM(lParam);
m_dragStart.y = GET_Y_LPARAM(lParam);
SetCapture(m_hwnd);
return 0;
case WM_LBUTTONUP:
if (GetCapture() == m_hwnd)
ReleaseCapture();
return 0;
case WM_RBUTTONUP:
{
POINT pt;
GetCursorPos(&pt);
HMENU hMenu = CreatePopupMenu();
AppendMenuW(hMenu, MF_STRING, 101, L"淡蓝色");
AppendMenuW(hMenu, MF_STRING, 102, L"淡红色");
AppendMenuW(hMenu, MF_STRING, 103, L"淡黄色");
AppendMenuW(hMenu, MF_STRING, 104, L"淡绿色");
AppendMenuW(hMenu, MF_SEPARATOR, 0, nullptr);
AppendMenuW(hMenu, MF_STRING, 201, L"小 (150×150)");
AppendMenuW(hMenu, MF_STRING, 202, L"中 (250×250)");
AppendMenuW(hMenu, MF_STRING, 203, L"大 (350×350)");
AppendMenuW(hMenu, MF_STRING, 204, L"超大 (450×450)");
AppendMenuW(hMenu, MF_SEPARATOR, 0, nullptr);
AppendMenuW(hMenu, MF_STRING, 999, L"退出");
UINT cmd = TrackPopupMenu(hMenu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, m_hwnd, nullptr);
DestroyMenu(hMenu);
switch (cmd)
{
case 101: m_scene.SetClockColor(Scene::ClockColor::LightBlue); break;
case 102: m_scene.SetClockColor(Scene::ClockColor::LightRed); break;
case 103: m_scene.SetClockColor(Scene::ClockColor::LightYellow); break;
case 104: m_scene.SetClockColor(Scene::ClockColor::LightGreen); break;
case 201: ApplyWindowSize(Small); break;
case 202: ApplyWindowSize(Medium); break;
case 203: ApplyWindowSize(Large); break;
case 204: ApplyWindowSize(ExtraLarge); break;
case 999: DestroyWindow(m_hwnd); break;
}
return 0;
}
case WM_CAPTURECHANGED:
return 0;
case WM_ERASEBKGND:
return 1;
default:
return DefWindowProc(m_hwnd, msg, wParam, lParam);
}
}
void RunMessageLoop()
{
LARGE_INTEGER freq, lastTime;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&lastTime);
const double targetInterval = 1.0 / 60.0;
MSG msg = {};
while (true)
{
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
return;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
LARGE_INTEGER currentTime;
QueryPerformanceCounter(¤tTime);
double elapsed = double(currentTime.QuadPart - lastTime.QuadPart) / double(freq.QuadPart);
if (elapsed >= targetInterval)
{
m_scene.Render();
lastTime = currentTime;
}
else
{
DWORD sleepMs = static_cast<DWORD>((targetInterval - elapsed) * 1000);
if (sleepMs > 0 && sleepMs < 16)
Sleep(sleepMs);
}
}
}
};
// =============================================
// Entry Point
// =============================================
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)))
return 0;
MainWindow win;
if (!win.Create())
{
CoUninitialize();
return 1;
}
ShowWindow(win.m_hwnd, SW_SHOW);
win.RunMessageLoop();
CoUninitialize();
return 0;
}```
(●'◡'●)