呼叫中心软件中使用的性能计






4.90/5 (15投票s)
如何制作一个看起来像 Windows 任务管理器中的性能计。
引言
Windows 任务管理器有一个不错的性能指标控制,用于显示关于 CPU 和内存使用的当前和历史数据。这种控制在许多监控应用程序中都很有用,例如我们呼叫中心软件中使用的那个。我们用它来测量几个关键因素,比如登录的代理数量,当前活跃的呼叫数量,以及电话线路和代理的利用率。
我们想要类似于 Windows 任务管理器中的性能指标表。它应该具有相似的外观和感觉。特别是,背景网格应该随数据移动,这样它就不会看起来是静态的。
这里展示的就是最终的代码。
创建视图类
我们使用一个 CView
派生类来显示性能指标表的绘图。由于所有的绘图代码都在 DrawPerf(CDC& dc, CRect rect)
类方法中,所以如果您需要创建一个控件类,应该可以使用相同的代码。有关创建自定义控件类的更多信息,请查看 Chris Maunder 的文章创建自定义控件。
class CPerfMeterView : public CView
{
...
public:
virtual void OnDraw(CDC* pDC);
protected:
// actual drawing code
void DrawPerf(CDC& dc, CRect rect);
void DrawPerfLeft(CDC& dc, CRect rect, const CString& reading, int r, int total);
void DrawPerfRight(CDC& dc, CRect rect, int total);
void DrawPerfDataLineChart(CDC& dc, CRect rect);
protected:
CFont* m_pFont;
CPen m_penDottedGreen;
CPen m_penSolidGreen;
CPen m_penSolidYellow;
CPen m_penSolidDarkGreen;
int m_leadingTick;
UINT m_perfTimerId;
...
};
为了减少闪烁,我们使用了一个位图 DC。这里我们简单地使用了 MFC 功能包中包含的 CMemDC
类。包含文件是 "afxcontrolbarutil.h"。如果您使用的是旧版本的 VC++,您可以查看这里发布的一些关于无闪烁绘图的项目。
void CPerfMeterView::OnDraw(CDC* dc)
{
// calc sizes
CRect rcClient;
GetClientRect(&rcClient);
// use a buffer to reduce flicker
CMemDC memDC(*dc, this);
memDC.GetDC().SetBkMode(TRANSPARENT);
if (! m_pFont)
m_pFont = GetHeightFont("Tahoma", 12, memDC.GetDC());
CFont* pOldFont = memDC.GetDC().SelectObject(m_pFont);
// the actual drawing
DrawPerf(memDC.GetDC(), rcClient);
memDC.GetDC().SelectObject(pOldFont);
}
绘制指标表
性能指标表的实际绘制并不困难。大多数代码都涉及计算绘图的 CRect
。顶层的绘图代码在 DrawPerf
中,它是从 OnDraw
函数调用的。背景颜色是系统的 COLOR_BTNFACE
。此函数在计算每个指标表的宽度后,简单地调用 DrawPerfLeft
和 DrawPerfRight
。
void CPerfMeterView::DrawPerf(CDC& dc, CRect rect)
{
CPerfMeterDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (! pDoc)
return;
if (! dc.IsPrinting())
dc.FillSolidRect(rect, ::GetSysColor(COLOR_BTNFACE));
// do not draw if it is too small
if (rect.Width() < 100 || rect.Height() < 60)
return;
// get the meter reading
CString reading;
int r, total;
pDoc->GetMeterReading(reading, r, total);
// draw the left hand side meter
CRect lrect(rect);
lrect.right = 100;
DrawPerfLeft(dc, lrect, reading, r, total);
// draw the right hand side historical data
CRect rrect(rect);
rrect.left = 101;
if (rrect.Width() < 100)
return;
DrawPerfRight(dc, rrect, total);
}
左侧指标表包含一个标题、一个带条形图的计量框,显示计量值的最大值 (tota) 和当前读数 r,使用实心绿色画笔或虚线绿色画笔。
void CPerfMeterView::DrawPerfLeft(CDC& dc, CRect rect, const CString& reading, int r, int total) { CPerfMeterDoc* pDoc = GetDocument(); // draw title CRect titlerect(rect); titlerect.left += 10; titlerect.top += 4; titlerect.bottom += 16; dc.DrawText("Title", titlerect, DT_SINGLELINE | DT_TOP | DT_LEFT); // draw meter outer box ... // draw meter reading text ... // draw meter line graph ... CPen* pOldPen = dc.SelectObject(&m_penDottedGreen); draw_bars(dc, rect, middle, totalbars - rbars, 1); dc.SelectObject(&m_penSolidGreen); draw_bars(dc, rect, middle, rbars, 0); dc.SelectObject(pOldPen); }
右侧指标表用于绘制历史数据。它包含一个标题和一个折线图。这里我们使用一个成员变量 m_leadingTick
来跟踪每次绘图时背景网格的移动量。网格硬编码为 12 像素,每次移动是网格单元的 1/6。
void CPerfMeterView::DrawPerfRight(CDC& dc, CRect rect, int total)
{
...
dc.DrawText("History", titlerect, DT_SINGLELINE | DT_TOP | DT_LEFT);
...
// draw grid
CPen* pOldPen = dc.SelectObject(&m_penSolidDarkGreen);
int delta = 12;
for (int i = rect.bottom - delta; i > rect.top; i = i - delta) {
dc.MoveTo(rect.left, i);
dc.LineTo(rect.right, i);
}
for (int i = rect.right - (m_leadingTick * delta/6); i > rect.left; i = i - delta) {
if (i == rect.right)
continue;
dc.MoveTo(i, rect.top);
dc.LineTo(i, rect.bottom);
}
DrawPerfDataLineChart(dc, rect);
dc.SelectObject(pOldPen);
}
获取数据
视图类调用文档类以获取当前测量数据和历史数据。对于这个示例,实际数据是随机生成的。视图类使用以下三个方法。
class CPerfMeterDoc : public CDocument
{
...
void UpdatePerfData();
void GetMeterReading(CString& reading, int& r, int& total);
BOOL GetPerfDataNext(int index, double& r);
...
protected:
// sample data
CArray<int> m_perfs;
};
GetMeterReading
方法用于绘制左侧指标表。GetPerfDataNext
方法用于绘制历史折线图。
void CPerfMeterView::DrawPerfDataLineChart(CDC& dc, CRect rect)
{
...
while (drawIndex > rect.left) {
double r;
if (! pDoc->GetPerfDataNext(dataIndex, r))
break;
int v = rect.bottom - int (rect.Height() * r / 100);
if (lastr < 0)
dc.MoveTo(drawIndex, v);
else
dc.LineTo(drawIndex, v);
dc.LineTo(drawIndex - dataUnit, v);
lastr = r;
drawIndex -= dataUnit;
dataIndex++;
}
}
定时器
定时器用于刷新数据和指标表。当前定时器的流逝时间为 1 秒。在每个定时器计时时,m_leadingTick
被更新,并且图形失效以进行重绘。
void CPerfMeterView::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == m_perfTimerId) {
if (m_leadingTick++ >= 6)
m_leadingTick = 0;
CPerfMeterDoc* pDoc = GetDocument();
if (pDoc)
pDoc->UpdatePerfData();
Invalidate();
return;
}
CView::OnTimer(nIDEvent);
}