桌面挂件时钟/多功能时钟C++

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

这个时钟平时的时候浮在桌面上,如果挡住桌面了只要把鼠标放上去就隐身了。

鼠标左键可以移动它,右键可以选择各种选项。

比如:选择颜色,选择大小,和退出。

可以成为你桌面上会动的小挂件哦。

因为它和现实中的时钟一样秒针,分针,和时针一直再转。

在我的资源里可以下载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(&currentTime);
            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;
}```



(●'◡'●)
相关推荐
Rust语言中文社区1 小时前
【Rust日报】 walrus:分布式消息流平台,比 Kafka 快
开发语言·分布式·后端·rust·kafka
全栈视界师1 小时前
《机器人实践开发②:Foxglove 嵌入式移植 + CMake 集成》
c++·机器人·数据可视化
多多*1 小时前
Threadlocal深度解析 为什么key是弱引用 value是强引用
java·开发语言·网络·jvm·网络协议·tcp/ip·mybatis
Python×CATIA工业智造1 小时前
Python多进程爬虫实战:豆瓣读书数据采集与法律合规指南
开发语言·爬虫·python
一只乔哇噻1 小时前
java后端工程师+AI大模型进修ing(研一版‖day56)
java·开发语言·学习·算法·语言模型
美团测试工程师1 小时前
软件测试面试题2025年末总结
开发语言·python·测试工具
小熳芋1 小时前
排序链表- python-非进阶做法
数据结构·算法·链表
繁华似锦respect1 小时前
Linux-内核核心组成部分
linux·c++
F***74171 小时前
PHP操作redis
开发语言·redis·php