1

我有几个关于在 MFC 中绘制笔画的问题。假设类 CStroke 已事先声明。示例代码如下所示。

BOOL CStroke::DrawStroke(CDC* pDC)
{
    CPen penStroke;
    if ( !penStroke.CreatePen(PS_SOLID, m_nPenWidth, m_color) )
        return FALSE;
    CPen *pOldPen = pDC->SelectObject(&penStroke);
    pDC->MoveTo(m_pointArray[0]);

    for( int i = 0; i < m_pointArray.GetSize(); i++ )
    {
        pDC->LineTo(m_pointArray[i]);
    }

    pDC->SelectObject(pOldPen);
    return TRUE;
}

我的问题是:

  1. 为什么我需要 pOldPen?
  2. 到底为什么要用pDC->SelectObject(pOldPen)pOldPen来选择?
4

1 回答 1

1

简短的回答:

  1. 为什么我需要pOldPen?

    因为你欠你的来电者。这不是作为礼物的意思。

  2. 为什么需要我用pDC->SelectObject(pOldPen)selectpOldPen到底?

    因为选择另一个资源进入 DC 是选择当前资源退出 DC 的唯一方法。


当通过调用将 GDI 资源选择到 DC 时,设备上下文会代表您使用 GDI 资源SelectObject。在任何时候,DC 中都只有一个图形对象(钢笔、画笔、位图……)被选中。但是,它不会为您管理资源。资源管理留给应用程序。

不幸的是,上面的 MFC 实现隐藏了一个关于资源管理的重要细节。将代码转换为普通的 WinAPI 实现将使CPen隐藏的内容更加明显:

BOOL CStroke::DrawStroke(HDC hDC)
{
    // Create a new pen resource
    HPEN penStroke = CreatePen(PS_SOLID, m_nPenWidth, m_color);
    if ( penStroke == NULL )
        return FALSE;

    // Select it into the device context
    HPEN oldPen = static_cast<HPEN>( SelectObject(hDC, &penStroke) );

    // Render strokes
    if ( m_pointArray.GetSize() > 0 )  // bugfix *
    {
        pDC->MoveTo(m_pointArray[0]);

        for( int i = 1; i < m_pointArray.GetSize(); i++ )  // bugfix **
        {
            pDC->LineTo(m_pointArray[i]);
        }
    }

    // Select pen out of DC
    HPEN penCreatedAbove = static_cast<HPEN>( SelectObject(hDC, oldPen) );

    // Clean up our resource (this is what CPen::~CPen() hides)
    DeleteObject(penCreatedAbove)

    return TRUE;
}

由于上面的代码创建了一个图形对象(请参阅CreatePen参考资料),它还负责释放与其关联的资源。释放应用程序调用的资源DeleteObject。此 API 调用具有以下要求:

当绘图对象(钢笔或画笔)仍被选入 DC 时,请勿删除它。

为了满足该前提条件,需要从 DC 中选择在上述代码中创建的笔。这就是调用的SelectObject(hDC, oldPen)作用。(我通过引入一个变量来存储返回值,使这一点更加明显。)

由于您必须在 DC 中选择某些东西才能将资源拉回,因此实际上很少有选择:此时您既不需要也不需要销毁的唯一图形对象是oldPen. 这也确保您可以嵌套渲染代码,如下面的伪代码所示:

HPEN bestPenEver = CreatePen();
HPEN oldPen = SelectObject(bestPenEver);

// Do some painting with bestPenEver

    call DrawStroke()

// Do some more painting with bestPenEver
SelectObject(oldPen);
DeleteObject(bestPenEver);

为完整起见,并将此实现与 MFC 代码对齐,这里是相关的 MFC 实现:

CPen::CreatePen(int nPenStyle, int nWidth, COLORREF crColor)
{ return Attach(::CreatePen(nPenStyle, nWidth, crColor)); }

CPen penStroke是一个自动变量,所以它的析构函数在控制离开封闭块时运行(即DrawStroke返回时)。析构函数调用它的基类实现CGdiObject::DeleteObject,如下所示:

BOOL CGdiObject::DeleteObject()
{
    if (m_hObject == NULL)
        return FALSE;
    return ::DeleteObject(Detach());
}

错误修正 * 不再访问空序列的第一个元素。

错误修正 ** 第一个元素已用于MoveTo.

于 2013-08-02T23:46:27.400 回答