14.4.3 使用鼠标绘图(10)
绘制元素的逻辑有点复杂。只要形状不是曲线,就可以使用R2_NOTXORPEN模式绘制一个形状。当创建曲线时,我们并不想在每次移动鼠标时都绘制一个新曲线,而只是想将曲线延伸出一个新线段。这就意味着必须将绘制曲线的情况视为一个例外。我们也想当旧元素存在时删除它,但只有当它不是曲线时才行。下面的代码说明了如何实现此功能:
- void CSketcherView::OnMouseMove(UINT nFlags, CPoint point)
- {
- // Define a Device Context object for the view
- CClientDC aDC(this); // DC is for this view
- if(nFlags & MK_LBUTTON) // Verify the left button is down
- {
- m_SecondPoint = point; // Save the current cursor position
- if(m_pTempElement)
- {
- // An element was created previously
- if(ElementType::CURVE == GetDocument()->GetElementType()) // A curve
- { // We are drawing a curve so add a segment to the existing curve
- std::static_pointer_cast<CCurve>(m_pTempElement)->AddSegment(m_
- SecondPoint);
- m_pTempElement->Draw(&aDC); // Now draw it
- return; // We are done
- }
- else
- {
- // If we get to here it’s not a curve so
- // redraw the old element so it disappears from the view
- aDC.SetROP2(R2_NOTXORPEN); // Set the drawing mode
- m_pTempElement->Draw(&aDC); // Redraw the old element to erase it
- }
- }
- // Create a temporary element of the type and color that
- // is recorded in the document object, and draw it
- m_pTempElement.reset(CreateElement());
- m_pTempElement->Draw(&aDC);
- }
- }
第一行新代码创建了一个局部CClientDC对象。传递给这个构造函数的this指针标识当前视图对象,所以CClientDC对象是一个设备上下文,对应于当前视图的工作区。这个对象有我们需要的所有绘图函数,因为它们都是从CDC类继承的。
如果指针m_pTempElement不是nullptr,则说明存在一个旧的临时元素。根据m_pTempElement是否指向曲线,来执行不同的操作,所以需要检查用户是否在绘制曲线。shared_ptr<T>类型支持转换到bool类型,所以可以在if表达式中使用它。对文档对象调用GetElementType()函数,以获得当前元素的类型。如果当前元素的类型是ElementType::CURVE,则强制将m_pTempElement转换为shared_ptr<CCurve>类型,以便为对象调用AddSegment()函数,将下一线段添加到曲线。必须使用一个特殊的强制转换操作static_pointer_cast来转换智能指针,这等价于普通指针的static_cast。智能指针还能使用dynamic_pointer_cast和const_pointer_cast。这些操作由memory头文件和std名称空间中的函数模板定义。最后绘制曲线并返回。必须在SketcherView.cpp中给Curve.h添加#include。在最终的视图类中要引用其他元素类,所以可以包含其他三个元素类的头文件。
当前元素存在,但不是曲线时,在调用aDC对象的SetROP2()函数将绘图模式设置为R2_NOTXORPEN之后重新绘制旧元素,这会擦除旧的临时元素。不需要重置m_pTempElement,因为在if语句后要替代它包含的指针。
如果临时元素存在,但不是曲线,或者没有临时元素存在,就执行if语句后面的代码。在这两种情况下,都需要创建当前类型的新元素,按正常方式给它指定颜色,并绘制它。我们创建新元素,并把它作为参数传递给其reset()成员,把它的地址存储在m_pTempElement中。在用参数替换它之前,这会减少m_pTempElement包含的当前指针的引用计数。在本例中,以前元素如果存在,其引用计数就减少到0,所以销毁它。如果m_pTempElement包含nullptr,就用新元素指针替换它。reset()函数有第二个无参数的重载版本,如果当前指针的计数是1,调用这个版本就会给该计数减去1,并在shared_ptr对象中用nullptr替换当前指针。代码会自动拉伸正在创建的形状,所以形状在光标移动时附着在光标位置上。
使用指向新元素的智能指针,调用其Draw()成员,让这个对象绘制自身的形状。CClientDC对象的地址将作为参数传递。因为在基类CElement中已经把Draw()成员函数定义为虚函数,所以无论m_pTempElement指向什么类型的元素,都将自动选择这个函数。新元素通常将以R2_NOTXORPEN绘图模式绘制,因为这是第一次在白色背景上绘制该元素。
创建元素
把CreateElement()函数作为protected成员添加到CSketcherView类的Implementation部分:
- class CSketcherView: public CView
- {
- // Rest of the class definition as before...
- // Implementation
- // Rest of the class definition as before...
- protected:
- CElement* CreateElement(void) const; // Create a new element on the heap
- // Rest of the class definition as before...
- };
这时可以通过添加粗体显示的行直接修改类定义,也可以在Class View窗格中右击类名CSketcherView,然后从上下文菜单中选择Add | Add Function菜单项,并通过显示的对话框添加函数。函数的访问特性是protected,因为它只能从视图类内部调用。