Сообщений 0    Оценка 42        Оценить  
Система Orphus

Обобщенный CRectTracker

Автор: Вадим Сухоруков
Опубликовано: 04.10.2002
Исправлено: 13.03.2005
Версия текста: 1.0

Введение
Базовый класс CTracker
Модифицированный tracker для прямоугольников
Tracker для ломаной линии
MultiTracker для произвольных объектов
Пример использования: схемный редактор
Особенности функционирования при сдвинутых полосах прокрутки
Установка специфического курсора

Исходные тексты - классы
Исходные тексты - демонстрационная программа

Введение

Как известно, в библиотеке MFC имеется класс CRectTracker. Следуя документации, он был спроектирован для взаимодействия с внедренными OLE-элементами. Также его можно использовать для отображения, перемещения и изменения размеров любых графических объектов.

Действительно, с его помощью легко реализовать, скажем, графический редактор для прямоугольных объектов. Однако для объектов другой формы он не годится.

В свое время мне пришлось реализовать графический редактор, в котором были прямоугольные объекты (блоки) и ломаные линии (связи между блоками). Для этих целей я написал семейство классов, аналогичных CRectTracker.

За основу я взял CRectTracker и реализацию его методов из исходных кодов MFC. Я решил обобщить этот класс на случай объекта произвольной формы и завести базовый класс CTracker. Все остальные tracker’ы должны наследоваться от него. Хотя я привожу примеры для двух геометрических объектов (прямоугольник и ломаная линия), можно написать tracker для объекта любой другой формы. (Мой tracker для прямоугольника немного отличается от CRectTracker из MFC.)

Еще одним новшеством является класс-tracker, позволяющий перемещать группу разнородных объектов.

Базовый класс CTracker

В классе CTracker имеются все операции, которые есть в CRectTracker, за исключением GetTrueRect и NormalizeHit, так как в общем случае они не имеют смысла. Все операции в CTracker стали абстрактными:

    virtual void Draw(CDC* pDC) const = 0;
    virtual int HitTest(CPoint point) const = 0;
    virtual BOOL SetCursor(CWnd* pWnd, UINT nHitTest) const = 0;
    virtual BOOL Track(CWnd* pWnd, CPoint point, BOOL bAllowInvert = FALSE,      CWnd* pWndClipTo = NULL) = 0;
    virtual BOOL TrackRubberBand(CWnd* pWnd, CPoint point, BOOL bAllowInvert = TRUE) = 0;

Подробное их описание можно найти в MSDN для класса CRectTracker.

От данных остались m_nHandleSize, m_sizeMin и m_nStyle. Правда, стилей поубавилось: остались только dottedLine и resizeBorder – штрихованой рамки вокруг объектов теперь нет, а метки-манипуляторы (handles) теперь расположены по центру линий, а не снаружи или внутри.

Переопределяемые методы AdjustRect, DrawTrackerRect и OnChangedRect относятся к форме объекта и должны появиться в наследуемых классах с учетом этой самой формы. Метод GetHandleMask не используется.

В переменной m_nHandleSize класса CRectTracker хранится размер метки-манипулятора, по умолчанию устанавливается системным значением. В CTracker эта переменная защищенная, для чтения/записи в нее есть методы

    int GetHandleSize() const;
    void SetHandleSize(int nNewSize);

Это сделано потому, что в моей реализации размер метки-манипулятора может быть только нечетным числом. Это обязательное требование.

Класс CRectTracker имеет еще один недостаток: если в окне редактора сдвинуты полосы прокрутки, то надо самостоятельно это отслеживать и вносить изменения в координаты перетаскиваемых объектов. Теперь этот недостаток устранен: в CTracker я завел метод

void SetScrollOffset(CPoint ptOffset);

Далее я расскажу, как им пользоваться.

Перемещаются объекты дискретно, с шагом в m_nHandleSize. В принципе, эту особенность можно и отключить; я считаю это достоинством.

Модифицированный tracker для прямоугольников

Для перемещения и изменения размеров прямоугольных объектов я написал класс CMRectTracker : public CTracker. Он “сильно напоминает” CRectTracker. Разница в следующем:

  1. в моем классе по границе объекта рисуется только пунктирная линия (без штрихованной полоски);
  2. «хватать объект» можно в любом месте прямоугольника (а не только на границе);
  3. метки-манипуляторы расположены только по углам, центр квадрата метки-манипулятора находится точно в углу прямоугольника (у CRectTracker метки-манипуляторы расположены или внутри прямоугольника, или снаружи).

На рис.1 приведен фрагмент окна редактора в момент перемещения прямоугольного объекта.


Рис. 1

Tracker для ломаной линии

Для моего редактора был необходим объект «связь между блоками». Выглядит она как ломаная линия, соседние звенья которой расположены друг относительно друга под углом 90(. Если связываемые блоки находятся на одной линии, то и связь представляет собой прямую линию. Если один блок ниже другого, то связь имеет три звена: два горизонтальных и одно вертикальное. Число звеньев можно увеличить или уменьшить, перетаскивая связь за звено или изгиб. Для этой цели я написал класс CChainTracker.

Для задания прямоугольника (прямоугольного объекта) используется объект класса CRect. Для задания описанной выше ломаной линии я написал класс CChainLink (звено цепи). Упрощенно он выглядит следующим образом:

class CChainLink
{
// Конструкторы
public:
    CChainLink(CPoint ptCoords);
protected:
    CChainLink(CChainLink* pParent, CPoint ptCoords);
    CChainLink(CChainLink* pPrev, CChainLink* pNext, CPoint ptCoords);
// Атрибуты
public:
    CPoint m_ptCoords;
    CChainLink* GetNext();
    CChainLink* GetPrev();
    int GetType() const;
// Операции
public:
    CChainLink* AddAfter(CPoint ptCoords);
    CChainLink* AddBefore(CPoint ptCoords);
    void Append(CChainLink* pChain);
    BOOL EqualChain(const CChainLink* pChain) const;
    void DeleteChain();
    void Optimize(CChainLink** ppHandle);
// Реализация
public:
    virtual ~CChainLink();
protected:
    CChainLink* m_pPrev;
    CChainLink* m_pNext;
    int m_nType; 
};

Ломаная собирается из звеньев цепи. Звено имеет координаты m_ptCoords. От одного звена до другого рисуется линия.

В классе CChainTracker имеются методы для записи и чтения ломанной. Особо следует отметить, что объект CChainTracker работает с копией ломаной линии. При манипуляциях в цепи может увеличиться или уменьшиться число звеньев цепи. Для манипуляции с прямоугольным объектом нужно написать примерно следующее:

    CMRectTracker tracker;
    // копируем местоположение объекта
    tracker.m_rect = object.m_rect;
    if( tracker.Track(...) )
    {
        // устанавливаем новое местоположение объекта
        object.m_rect = tracker.m_rect;
    };

При манипуляции с ломаной ее тоже нужно копировать. Только копирование для нее имеет свои особенности. Если, например, цепь имела 3 звена, а после манипуляций (вызова Track) в ней стало 5, то нужно заменить старую цепь новой. При этом нужно скопировать цепь из tracker’а, а не использовать непосредственно ее звенья.

На рис.2 приведен фрагмент окна редактора в момент перемещения связи.


Рис. 2

MultiTracker для произвольных объектов

Мой редактор должен был иметь возможность перемещения части схемы (несколько блоков и связей). Мне потребовался tracker, который бы мог работать с разнородными объектами. Для этой цели я написал класс CMultiEOTracker (multi edit object tracker). Он способен работать с объектами разных типов.

Если в CMRectTracker и CChainTracker имеется переменная соответствующего типа, то в CMultiEOTracker имеется список (реализован с помощью vector, но сейчас это неважно), причем не список копий объектов, а список указателей на интерфейс. Для манипулирования разнородными объектами я объявил интерфейс (не СОМ-интерфейс) для объектов редактирования:

class CEditObject
{
public:
    virtual void DrawLines(CDC *pDC, CPoint ptOffset) const = 0;
    virtual void DrawHandles(CDC *pDC, CPoint ptOffset) const = 0;
    virtual BOOL HitTest(CPoint point) const = 0;
    virtual CPoint GetPosition() const = 0;
    virtual void SetPosition(CPoint NewPosition) = 0;
    virtual BOOL HitInRect(CRect rect) const = 0;
    virtual HandleTypes HitTestHandles(CPoint point) const = 0;   
};

При вызове DrawLines объект должен нарисовать линии, очерчивающие его границу (для прямоугольного объекта – сам прямоугольник, для связи – ломаную линию).

При вызове DrawHandles объект должен нарисовать свои метки-манипуляторы (для прямоугольного объекта – квадратики по углам, для связи – квадратики на изгибах).

При вызове HitTest объект должен вернуть TRUE, если точка point находится на нем.

При вызове GetPosition должен вернуть координаты своей позиции. Не важно, какая это точка, главное, чтобы при вызове SetPosition обновлялась та же самая точка (в моей реализации для прямоугольного объекта – левый верхний угол, для связи – координаты первого звена). В SetPosition нужно вычислять смещение относительно старой позиции и переместить объект на это смещение.

При вызове HitInRect объект должен вернуть TRUE, если прямоугольник rect пересекается с объектом.

При вызове HitTestHandles объект должен вернуть тип метки-манипулятора, над которой находится точка point. Используется для установки специфического курсора.

Любой объект редактирования, наследованный, в том числе и от CEditObject, может быть добавлен в список выделенных объектов и перемещен вместе с остальными подобными. Особо следует отметить, что CMRectTracker и CChainTracker работают с копией объекта, а CMultiEOTracker работает непосредственно с объектами через вызовы функций класса CEditObject. Еще одной особенностью является то, что с помощью MultiTracker’а нельзя изменять форму и размер объектов – только перемещать.

На рис.3 приведен фрагмент окна редактора в момент перемещения группы объектов.


Рис. 3

Пример использования: схемный редактор

Все вышеописанное потребовалось мне для реализации схемного редактора. В нем имеются блоки и связи между блоками.

Для представления блоков и связей имеются соответствующие классы. В классе блока имеется переменная CRect m_rect, которая задает текущий прямоугольник. В классе связи имеется переменная CChainLink* m_pChain, которая содержит указатель на цепь, задающую ломаную линию.

В классе документа имеется список блоков и список связей. Функции редактора реализованы в классе представления. В классе представления имеется переменная CMultiEOTracker m_MultiTracker. В m_MultiTracker хранятся выделенные объекты. При нажатии на левую кнопку мыши проверяется, попал ли курсор на один из объектов. Если попал, то объект выделяется. Если выделен один объект, то устанавливается специфический для него tracker. Если выделено больше одного объекта, то их можно только перемещать. Добавить объект к выделенным или снять выделение можно с помощью клавиши Shift.

В моей программе размер метки-манипулятора равен 5.

Перечислю важные места класса представления (для редактора): OnDraw, OnLButtonDown, OnSetCursor.

Особенности функционирования при сдвинутых полосах прокрутки

При сдвинутых полосах прокрутки перемещаемый объект будет рисовать себя не там, где нужно. Дело в том, что в объекте хранится его позиция относительно виртуального окна документа. Если это окно не умещается в видимом окне представления, то нужно установить полосы прокрутки. Далее, если полосы прокрутки сдвинуты, то во время действия метода Track объект будет рисовать себя в окне представления с координатами относительно документа. Поэтому изображение самого объекта и его пунктирной рамочки не будут совпадать (даже если рамочку не двигали). Чтобы решить данную проблему, в классе CTracker я определил метод void SetScrollOffset(CPoint ptOffset). Вызывая этот метод, tracker’у сообщается величина смещения полос прокрутки. Дополнительно, функции DrawLines(CDC *pDC, CPoint ptOffset) и DrawHandles(CDC *pDC, CPoint ptOffset) имеют второй параметр ptOffset. В эти функции передается величина смещения прокрутки с обратным знаком. Поэтому рисовать, скажем, прямоугольник, нужно со смещением ptOffset.

На рис.4 поясняет написанное выше.


Рис. 4

Установка специфического курсора

В моей реализации курсоры «грузятся» в tracker’ы из ресурсов приложения (в CRectTracker немного по-другому). У каждого tracker’а свои курсоры. Обычно курсор меняется при наведении на метку-манипулятор. В моей программе курсор также меняет вид при наведении его на ножку блока, откуда делается связь. Для того чтобы изменить курсор над ножкой другого блока, куда ее прицепить, пришлось в класс связи добавить указатель на документ. Поэтому код ChainTracker’а получился документо-зависимый.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 0    Оценка 42        Оценить