Environment: VC6 SP2, NT4 SP3, Win95/98
The class I present performs the classic dragging and resizing of multiple objects, like vectoriel editors or CAD programs.
It does the same operations that the CRectTracker, with an array of rectangles instead of a single rectangle.
Those rectangles could be implemented with a CArray<CRect*, CRect*>, but the code in the project's view would be complex and not portable. So my solution is the CMRTObject (for CMultiRectTrackerObject), a base class for the edited objects, with just a rectangle information and two functions Get/Set. In a normal project, those objects are often based on CObject, but with multiple inheritance, this add-in is easy to be implemented. Another good news is that the CMultiRectTracker class gets the possibility to directly change the position/size of selected objects, so the CView code for the manipulation is light, as listed below.
class CDemoObject : public CMRTObject, public CObject
{
public:
CDemoObject (COLORREF rgb)
: CMRTObject(), m_color(rgb) {;}
CDemoObject (LPCRECT lpSrcRect, COLORREF rgb)
: CMRTObject(lpSrcRect), m_color(rgb) {;}
~CDemoObject () {;}
public:
void Draw (CDC* pDC) {
pDC->FillSolidRect (GetRect(), m_color);
}
protected:
COLORREF m_color;
};
Listed below is a possible implementation for your CxxView::OnLButtonDown(). It performs multiple selection with a local CRectTracker, and updates the CMultiRectTracker object contained by the view. If there is no rubber band, it selects the object clicked, and if the HitTest is different than -1 (CRectTracker::hitNothing), it starts the CMultiRectTracker::Track() function, wich contains the similar internal loop than CRectTracker. All objects moved or sized are directly updated in the Track() fct, the only work the CxxView needs to do is a call to Invalidate(). The CTRL key is scanned, and selected objects are removed if the user don't hit CTRL.
void CDemoView::OnLButtonDown(UINT nFlags, CPoint point)
{
CDemoDoc* pDoc = GetDocument();
if (multiTrack.HitTest(point) < 0) {
if (!(nFlags & MK_CONTROL))
multiTrack.RemoveAll();
CRect rcObject;
CDemoObject* pObject;
CRectTracker tracker;
if (tracker.TrackRubberBand(this, point, TRUE)) {
CRect rectT;
tracker.m_rect.NormalizeRect();
POSITION pos = pDoc->m_objects.GetHeadPosition ();
while (pos != NULL) {
pObject = (CDemoObject*)(pDoc->m_objects.GetNext(pos));
rcObject = pObject->GetRect();
rectT.IntersectRect(tracker.m_rect, rcObject);
if (rectT == rcObject) {
multiTrack.Add(pObject);
}
}
} else {
POSITION pos = pDoc->m_objects.GetHeadPosition ();
while (pos != NULL) {
pObject = (CDemoObject*)(pDoc->m_objects.GetNext(pos));
rcObject = pObject->GetRect();
if (rcObject.PtInRect(point)) {
multiTrack.Add(pObject);
break;
}
}
}
} else {
if (multiTrack.Track(this, point, FALSE))
pDoc->SetModifiedFlag();
}
Invalidate();
CView::OnLButtonDown(nFlags, point);
}
In order to be classic, the cursor needs to be updated, function of its position on the objects, so a call to SetCursor() is needed before the CView-base class OnSetCursor() call, like the CRectTracker usage.
BOOL CDemoView::OnSetCursor(CWnd* pWnd, UINT nHitTest,
UINT message)
{
if (pWnd == this && multiTrack.SetCursor(this, nHitTest))
return TRUE;
return CView::OnSetCursor(pWnd, nHitTest, message);
}