65.9K
CodeProject 正在变化。 阅读更多。
Home

渐变条控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (7投票s)

2012 年 11 月 4 日

Zlib

6分钟阅读

viewsIcon

40579

downloadIcon

4037

用于显示质量评分的 WTL 控件。

Demo application

引言

在寻找一个检查密码强度的算法实现时,我偶然发现了The Password Meter 页面,其中密码强度由一个变化的颜色条来表示。这是通过显示一个从红色到绿色逐渐变化的更大位图的选定部分来实现的。我想为我正在使用的、使用 WTL 的 C++ 项目创建一个类似的控件会很不错。除了渐变的颜色,该控件还应该遵循 Vista/Win 7 进度条控件的视觉风格,具有 3D 发光效果和运行中的跑马灯效果。

实现

最初,该控件的实现方式与上述网站相同,使用的是一个渐变位图,只显示其中的一个特定部分。然而,在控件开发过程中,我发现最初的样本位图根本不需要。也就是说,为了按照 Vista/Windows 7 的视觉风格显示条形,必须如文章后面所述,操纵 HSL(色相、饱和度、亮度)数据。控件上的颜色从红色、黄色变为绿色,这在 HSL 空间中对应于色相值从 0 度变为 120 度。因此,我们不需要样本位图,而是需要一个能够生成具有不同色相的颜色的方法。不幸的是,Windows API 只识别 RGB 格式的颜色数据,因此必须执行从 RGB 到 HSL 以及反向的转换。因此,实现了一个用于这些转换的实用工具类并在控件中使用。

CGradientBarCtrlImpl 类包含了控件的实现。以下小节将介绍实现的细节。

绘制条形

WM_PAINT 消息处理程序绘制条形及其上可选的值。对于非主题的条形,绘制非常直接:首先计算要显示的值的起始色相,然后从条形的左端到右端,对每个像素评估相应的色相,并将其转换为传递给 DC::SetPixel() 方法的 RGB 数据。

CRect rect;
GetClientRect(&rect);
// draw the edge (similarly to progress bar, border is allways displayed)
dc.DrawEdge(&rect, EDGE_ETCHED, BF_RECT);
rect.DeflateRect(1, 1, 1, 1); 
// since bar doesn't fill the entire client area, fill
// the client with button face color first
dc.FillSolidRect(&rect, ::GetSysColor(COLOR_BTNFACE));
// calculate starting hue for the current value to be presented
double hueStart = HUE_RED + (HUE_GREEN - HUE_RED - HUE_RANGE) * m_value / 100.;
// draw individual pixels
int columns = rect.Width();
int rows = rect.Height();
for (int column = 2; column < columns; ++column)
{
    double hue = hueStart + HUE_RANGE * column / columns;
    // create HSL for evaluated hue, full saturation and 50% lightness
    HSL hsl(hue, 1., 0.5);
    // convert to RGB and return 
    COLORREF color = hsl.GetRGB();
    for (int row = 2; row < rows; ++row)
        dc.SetPixel(column, row, color);
}

上面的代码中的 HSL 是用于 RGB – HSL 转换的实用工具类。HUE_RED(0 度)和 HUE_GREEN(120 度)分别是最低(0%)和最高(100%)值的边界色相。HUE_RANGE 是给定值显示的色相范围。

Hue range displayed

真正的挑战是绘制一个条形,使其在启用视觉主题时看起来像 Windows Vista 和 Windows 7 上的进度条,具有 3D 发光效果和倾斜的左右端。语句

CRect rect;
GetClientRect(&rect);
// first draw progress bar using theme
OpenThemeData(L"PROGRESS");
DrawThemeBackground(dc, PP_BAR, 0, &rect, NULL);
// draw the (green) chunk
DrawThemeBackground(dc, PP_FILL, PBFS_NORMAL, &rect, NULL);
CloseThemeData();

绘制一个带有绿色块的进度条,因为传递给 DrawThemeBackground() 方法的第三个(istateId)参数等于 PBFS_NORMAL(实际值为 1)。将此参数更改为 PBFS_ERROR2)、PBFS_PAUSED3)或 PBFS_PARTIAL4)将分别绘制红色、黄色或青色块。
为了模仿进度条的外观,首先使用上面的代码绘制块,然后为每个像素选择饱和度和亮度,并与计算出的色相相结合。将生成的颜色应用回像素。

// calculate starting hue for the current value to be presented
double hueStart = HUE_RED + (HUE_GREEN - HUE_RED - HUE_RANGE) * m_value / 100.;
// correct individual pixels
int columns = rect.Width() + 1;
int rows = rect.Height() + 1;
for (int column = 0; column < columns; ++column)
{
    // calculate hue for the current column
    double hue = hueStart + HUE_RANGE * column / columns;
    for (int row = 0; row < rows; ++row)
    {
        // pick color from themed bar
        COLORREF originalColor = dc.GetPixel(column, row);
        // convert to HSL format
        HSL hsl(originalColor);
        // and apply our calculated hue
        hsl.SetHue(hue);
        // convert to RGB and apply it to the pixel
        COLORREF color = hsl.GetRGB();
        dc.SetPixel(column, row, color);
    }
}

Applying hue on themed progress bar

请注意,实际实现与上述代码片段略有不同,因为一部分功能已被提取到一个单独的 CEvaluateColor 类中。这样做的目的是允许用自定义算法轻松替换上述算法。CEvaluateColor 类被作为默认模板参数传递给 CGradientBarCtrlImpl 模板类,但用户可以提供另一个类,如果默认实现不符合他们的期望。

HSL 到 RGB 的转换

色相是绘制渐变条时处理的关键值。然而,Windows API 无法处理 HSL 数据——它只能处理 RGB 数据。为了简化这些格式之间的转换,实现了一个名为 HSL 的实用工具类。该类部分改编自 Alex Kuhl 提供的代码,并以双精度类型处理 HSL 组件。
由于 Windows API 将 RGB 分量视为一个字节的整数值,HSL 类的初始实现使用了整数算术,希望它能执行得更快。然而,速度比较测试显示,与浮点实现相比,速度仅略有提高(约 150%)。另一方面,整数算术会在转换中引入微小的误差,因此,最终实现中使用的是双精度类型版本。

运行中的跑马灯 

当进度条空闲时,它会显示一个运行中的跑马灯。我认为在渐变条中实现类似的功能会很引人注目,尤其是在用户修改由控件表示的质量值时。例如(正如“真实世界”示例所示),如果控件显示用户输入的密码强度,并且密码输入控件具有焦点且用户没有输入任何内容,则渐变条将运行跑马灯。
使用两个计时器来处理跑马灯:一个(INITIAL_DELAY_TIMER_ID)暂停跑马灯的运行,另一个则启动在不同位置重新绘制高亮部分。每次设置要表示的新值时,都会激活第一个计时器。

void SetValue(int value)
{
    m_value = value;
    // restart initial delay timer
    KillTimer(INITIAL_DELAY_TIMER_ID);
    SetTimer(INITIAL_DELAY_TIMER_ID, INITIAL_DELAY);
}

这可以防止在用户输入新值时跑马灯运行。当初始延迟超时后,OnTimer 处理程序会触发第二个计时器(MARQUEE_TIMER_ID);当其超时时,高亮部分的が位置会向右移动,并且条形会被使失效以用偏移的高亮部分重新绘制条形。

LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    RECT rect;
    GetClientRect(&rect);
    switch (wParam)
    {
    case INITIAL_DELAY_TIMER_ID:
        // initial delay has expired
        KillTimer(INITIAL_DELAY_TIMER_ID); 
        // run the marquee
        m_marqueeX = -MARQUEE_WIDTH;
        m_marqueeRunning = true;
        SetTimer(MARQUEE_TIMER_ID, MARQUEE_TICK);
        break;
    case MARQUEE_TIMER_ID:
        // draw marquee at new position
        m_marqueeX += MARQUEE_DELTAX;
        Invalidate();
        if (m_marqueeX >= rect.right)
        {
            // marquee passed the end of bar so reset timers
            KillTimer(MARQUEE_TIMER_ID);
            m_marqueeRunning = false;
            SetTimer(INITIAL_DELAY_TIMER_ID, INITIAL_DELAY);
        }
        break;
    default:
        bHandled = false;
        break;
    }
    return 0;
}

显然,必须修改绘制过程以包含绘制高亮部分。

CRect rect;
GetClientRect(&rect);
// first draw progress bar using theme
OpenThemeData(L"PROGRESS");
DrawThemeBackground(dc, PP_BAR, 0, &rect, NULL);
// draw the (green) chunk
DrawThemeBackground(dc, PP_FILL, PBFS_NORMAL, &rect, NULL);
// draw running overlay
if (m_marqueeRunning)
{
    CRect rectOverlay(rect);
    rectOverlay.right = MARQUEE_WIDTH;
    rectOverlay.MoveToX(m_marqueeX);
    DrawThemeBackground(dc, PP_MOVEOVERLAY, PBFS_NORMAL, &rectOverlay, &rect);
}
CloseThemeData();

关于演示应用程序

演示应用程序揭示了渐变条控件的行为。用户可以通过提供的复选框简单地打开/关闭显示的值和跑马灯。通过单击“RW 示例”按钮(代表“真实世界”)会弹出另一个对话框,其中渐变条用于表示用户键入的密码的强度。请注意,跑马灯仅在相应的密码输入控件具有焦点且用户未输入时运行。对于密码强度评估,已修改并采用了 www.passwordmeter.com 上提供的算法。

使用代码

要使用该控件,只需将 GradientBarCtrl.hHSL.h 文件包含到您的项目中。要在对话框上显示 CGradientBarCtrl,只需在对话框资源上放置一个虚拟控件,并在 WM_INITDIALOG 消息处理程序中调用 SubclassWindow() 方法或通过 DDX_CONTROL 宏将其附加。
只有三个方法 intended to be invoked by the user code

  • void SetValue(int value) – 设置由控件表示的新值(值必须在 0 – 100 的范围内);
  • void DisplayValue(bool show) – 控制是否显示数字值(参数的默认值为 true);
  • void EnableMarquee(bool enable) – 控制是否显示运行中的跑马灯(参数的默认值为 true)。

CGradientBarCtrlImpl 是一个模板类,它将 TEvaluateColor 作为模板参数之一。CEvaluateColor 用作此参数的默认实现,但用户可以提供自己的实现。

历史

  • 2012 年 11 月 5 日 - 初始版本
© . All rights reserved.