在许多监控应用程序中,如呼叫中心软件,监控CPU和内存的使用情况是非常有用的。希望创建一个类似于Windows任务管理器中性能仪表的控件,它应该具有相似的外观和感觉,特别是背景网格应该随着数据的变化而移动,以避免看起来静态。
为了实现这一目标,创建了一个派生自CView的类,用于显示性能仪表的绘制。由于所有的绘制代码都在DrawPerf(CDC& dc, CRect rect)类方法中,因此如果需要创建一个控件类,应该能够使用相同的代码。
为了减少闪烁,使用了一个位图DC。这里简单地使用了MFC特性包中包含的CMemDC类。如果使用的是旧版本的VC++,可以在这里发布的一些项目中查看无闪烁绘图。
以下是创建视图类的代码示例:
class CPerfMeterView : public CView
{
public:
virtual void OnDraw(CDC* pDC);
protected:
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;
};
绘制性能仪表的实际代码并不困难。大部分代码涉及计算绘制的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));
if (rect.Width() < 100 || rect.Height() < 60) return;
CString reading;
int r, total;
pDoc->GetMeterReading(reading, r, total);
CRect lrect(rect);
lrect.right = 100;
DrawPerfLeft(dc, lrect, reading, r, total);
CRect rrect(rect);
rrect.left = 101;
if (rrect.Width() < 100) return;
DrawPerfRight(dc, rrect, total);
}
左侧仪表包含一个标题,一个带有显示最大仪表值(total)和当前读数r的条形图的仪表盒,使用实心绿色笔或点状绿色笔。
右侧仪表用于绘制历史数据。它包含一个标题和一个折线图。这里使用成员变量m_leadingTick来跟踪每次绘制时背景网格的移动量。网格被硬编码为12像素,每次移动是网格单位的1/6。
以下是绘制右侧仪表的代码示例:
void CPerfMeterView::DrawPerfRight(CDC& dc, CRect rect, int total)
{
...
dc.DrawText("History", titlerect, DT_SINGLELINE | DT_TOP | DT_LEFT);
...
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:
CArray 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++;
}
}
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);
}