使用Direct2D绘制一个圆形进度条

3
我正在创建一个圆形进度条控件并使用Direct2D进行绘制。
我想得到像这样的效果:
我很难想象如何绘制这种渐变。我已经成功用纯色画刷或使用不同的渐变(线性、径向)绘制了进度,但我无法像图片中那样绘制沿着进度条的渐变。
这是我现在所拥有的: 任何人可以给我一些提示,我应该使用哪种画刷才能得到我想要的效果吗?
提前致谢!
我想要一个弧形渐变...就像这样:

你想要的效果不是很清楚,能否发一个 gif 动图? - zdd
嗨zdd。我添加了一张带有我想要的效果的图片...实际上我不需要那么多颜色,透明到一种颜色的过渡会很棒! - Javier De Pedro
Javier,我认为你应该使用@zdd,这样他就会收到你的回复通知。 - CodeAngry
我认为这需要一个转换的画笔,不是很容易,我稍后会尝试。 - zdd
我已经尝试使用转换的画笔了...但我也没有成功。感谢@zdd的帮助。 - Javier De Pedro
OP想要的是锥形渐变。虽然D2D本身不支持,但你可以通过为每个停止点模拟分段来实现它。 - Rudy Velthuis
1个回答

4

我刚刚完成了一个初步版本,不确定这是否符合您的要求,仅供参考。

最终效果

输入图像描述

编程步骤:

  1. 创建圆的轮廓(可以通过组合两个椭圆构建几何图形组来完成)。
  2. 创建半径渐变画刷,使用此画刷填充圆。
  3. 在每帧中旋转渐变画刷,以实现进度条的旋转效果。

完整代码:

注意,您需要链接d2d1.lib和winmm.lib才能使其正常工作。

由于您熟悉Direct2D,因此我不会详细介绍代码的细节,请在需要进一步讨论时留下评论。谢谢。您也可以在GitHub上下载完整项目此处

#include <D2D1.h>

#define SAFE_RELEASE(P) if(P){P->Release() ; P = NULL ;}
const int GEOMETRY_COUNT = 2;

ID2D1Factory*           g_pD2DFactory   = NULL; // Direct2D factory
ID2D1HwndRenderTarget*  g_pRenderTarget = NULL; // Render target
ID2D1SolidColorBrush*   g_pBlackBrush   = NULL; // Outline brush
ID2D1RadialGradientBrush* g_pRadialGradientBrush = NULL ; // Radial gradient brush

// 2 circle to build up a geometry group. this is the outline of the progress bar
D2D1_ELLIPSE g_Ellipse0 = D2D1::Ellipse(D2D1::Point2F(300, 300), 150, 150);
D2D1_ELLIPSE g_Ellipse1 = D2D1::Ellipse(D2D1::Point2F(300, 300), 200, 200);

D2D1_ELLIPSE g_Ellipse[GEOMETRY_COUNT] = 
{
    g_Ellipse0, 
    g_Ellipse1,
};

ID2D1EllipseGeometry* g_pEllipseArray[GEOMETRY_COUNT] = { NULL };
ID2D1GeometryGroup* g_pGeometryGroup = NULL;

VOID CreateD2DResource(HWND hWnd)
{
    if (!g_pRenderTarget)
    {
        HRESULT hr ;

        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &g_pD2DFactory) ;
        if (FAILED(hr))
        {
            MessageBox(hWnd, "Create D2D factory failed!", "Error", 0) ;
            return ;
        }

        // Obtain the size of the drawing area
        RECT rc ;
        GetClientRect(hWnd, &rc) ;

        // Create a Direct2D render target
        hr = g_pD2DFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(
            hWnd, 
            D2D1::SizeU(rc.right - rc.left,rc.bottom - rc.top)
            ), 
            &g_pRenderTarget
            ) ;
        if (FAILED(hr))
        {
            MessageBox(hWnd, "Create render target failed!", "Error", 0) ;
            return ;
        }

        // Create the outline brush(black)
        hr = g_pRenderTarget->CreateSolidColorBrush(
            D2D1::ColorF(D2D1::ColorF::Black),
            &g_pBlackBrush
            ) ;
        if (FAILED(hr))
        {
            MessageBox(hWnd, "Create outline brush(black) failed!", "Error", 0) ;
            return ;
        }

        // Define gradient stops
        D2D1_GRADIENT_STOP gradientStops[2] ;
        gradientStops[0].color = D2D1::ColorF(D2D1::ColorF::Blue) ;
        gradientStops[0].position = 0.f ;
        gradientStops[1].color = D2D1::ColorF(D2D1::ColorF::White) ;
        gradientStops[1].position = 1.f ;

        // Create gradient stops collection
        ID2D1GradientStopCollection* pGradientStops = NULL ;
        hr = g_pRenderTarget->CreateGradientStopCollection(
            gradientStops,
            2, 
            D2D1_GAMMA_2_2,
            D2D1_EXTEND_MODE_CLAMP,
            &pGradientStops
            ) ;
        if (FAILED(hr))
        {
            MessageBox(NULL, "Create gradient stops collection failed!", "Error", 0);
        }

        // Create a linear gradient brush to fill in the ellipse
        hr = g_pRenderTarget->CreateRadialGradientBrush(
            D2D1::RadialGradientBrushProperties(
            D2D1::Point2F(170, 170),
            D2D1::Point2F(0, 0),
            150,
            150),
            pGradientStops,
            &g_pRadialGradientBrush
            ) ;

        if (FAILED(hr))
        {
            MessageBox(hWnd, "Create linear gradient brush failed!", "Error", 0) ;
            return ;
        }

        // Create the 2 ellipse.
        for (int i = 0; i < GEOMETRY_COUNT; ++i)
        {
            hr = g_pD2DFactory->CreateEllipseGeometry(g_Ellipse[i], &g_pEllipseArray[i]);
            if (FAILED(hr)) 
            {
                MessageBox(hWnd, "Create Ellipse Geometry failed!", "Error", 0);
                return;
            }
        }

        // Create the geometry group, the 2 circles make up a group.
        hr = g_pD2DFactory->CreateGeometryGroup(
            D2D1_FILL_MODE_ALTERNATE,
            (ID2D1Geometry**)&g_pEllipseArray,
            ARRAYSIZE(g_pEllipseArray),
            &g_pGeometryGroup
        );
    }
}

VOID Render(HWND hwnd)
{
    // total angle to rotate
    static float totalAngle = 0.0f;

    // Get last time
    static DWORD lastTime = timeGetTime();

    // Get current time
    DWORD currentTime = timeGetTime();

    // Calculate time elapsed in current frame.
    float timeDelta = (float)(currentTime - lastTime) * 0.1;

    // Increase the totalAngle by the time elapsed in current frame.
    totalAngle += timeDelta;

    CreateD2DResource(hwnd) ;

    g_pRenderTarget->BeginDraw() ;

    // Clear background color to White
    g_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));

    // Draw geometry group
    g_pRenderTarget->DrawGeometry(g_pGeometryGroup, g_pBlackBrush);

    // Roatate the gradient brush based on the total elapsed time
    D2D1_MATRIX_3X2_F rotMatrix = D2D1::Matrix3x2F::Rotation(totalAngle, D2D1::Point2F(300, 300));
    g_pRadialGradientBrush->SetTransform(rotMatrix);

    // Fill geometry group with the transformed brush
    g_pRenderTarget->FillGeometry(g_pGeometryGroup, g_pRadialGradientBrush);

    HRESULT hr = g_pRenderTarget->EndDraw() ;
    if (FAILED(hr))
    {
        MessageBox(NULL, "Draw failed!", "Error", 0) ;
        return ;
    }

    // Update last time to current time for next loop
    lastTime = currentTime;
}

VOID Cleanup()
{
    SAFE_RELEASE(g_pRenderTarget) ;
    SAFE_RELEASE(g_pBlackBrush) ;
    SAFE_RELEASE(g_pGeometryGroup);
    SAFE_RELEASE(g_pRadialGradientBrush);

    for (int i = 0; i < GEOMETRY_COUNT; ++i)
    {
        SAFE_RELEASE(g_pEllipseArray[i]);
        g_pEllipseArray[i] = NULL;
    }

    SAFE_RELEASE(g_pD2DFactory) ;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)   
{
    switch (message)    
    {
    case   WM_PAINT:
        Render(hwnd) ;
        return 0 ;

    case WM_KEYDOWN: 
        { 
            switch( wParam ) 
            { 
            case VK_ESCAPE: 
                SendMessage( hwnd, WM_CLOSE, 0, 0 ); 
                break ; 
            default: 
                break ; 
            } 
        } 
        break ; 

    case WM_DESTROY: 
        Cleanup(); 
        PostQuitMessage( 0 ); 
        return 0; 
    }

    return DefWindowProc (hwnd, message, wParam, lParam) ;
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow )
{

    WNDCLASSEX winClass ;

    winClass.lpszClassName = "Direct2D";
    winClass.cbSize        = sizeof(WNDCLASSEX);
    winClass.style         = CS_HREDRAW | CS_VREDRAW;
    winClass.lpfnWndProc   = WndProc;
    winClass.hInstance     = hInstance;
    winClass.hIcon         = NULL ;
    winClass.hIconSm       = NULL ;
    winClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
    winClass.hbrBackground = NULL ;
    winClass.lpszMenuName  = NULL;
    winClass.cbClsExtra    = 0;
    winClass.cbWndExtra    = 0;

    if (!RegisterClassEx (&winClass))   
    {
        MessageBox ( NULL, TEXT( "This program requires Windows NT!" ), "error", MB_ICONERROR) ;
        return 0 ;  
    }   

    HWND hwnd = CreateWindowEx(NULL,  
        "Direct2D",                 // window class name
        "Circular Progressbar",         // window caption
        WS_OVERLAPPEDWINDOW,        // window style
        CW_USEDEFAULT,              // initial x position
        CW_USEDEFAULT,              // initial y position
        600,                        // initial x size
        600,                        // initial y size
        NULL,                       // parent window handle
        NULL,                       // window menu handle
        hInstance,                  // program instance handle
        NULL) ;                     // creation parameters

        ShowWindow (hwnd, iCmdShow) ;
        UpdateWindow (hwnd) ;

        MSG msg ;  
        ZeroMemory(&msg, sizeof(msg)) ;

        while (GetMessage (&msg, NULL, 0, 0))  
        {
            TranslateMessage (&msg) ;
            DispatchMessage (&msg) ;
        }

        return msg.wParam ;
}

感谢@zdd。这是一个技巧,我已经在使用了,但不完全是我想要的效果。我希望能够从开始到进度的当前位置绘制渐变。但无论如何,非常感谢您提供的完整示例! - Javier De Pedro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接