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

«Липкие» диалоговые окна.

Автор: Сысолятин Павел
Опубликовано: 12.10.2005
Исправлено: 14.10.2005
Версия текста: 1.0

Введение
О реализации
Класс CStickyDialog.
Класс CStickyDragManager
Как использовать
Прмер 1 (TestDialog.h):
Пример 2:
Пример 3:
Заключение

Исходные тексты
Демонстрационный проект

Введение

При работе над одним из проектов, мне потребовалось реализовать «липкие» диалоговые окна наподобие окошек в Winamp. Как гласит высказывание одного человка: «Все что мы пишем, или еще только хотим написать - уже давно написано». Поэтому прежде чем писать все сам, я «прогулялся» по просторам Сети. В ходе этого занятия на www.codeguru.com мне попалась статья «Creating Sticky Windows».

При использовании кода из данной статьи практически сразу обнаружились недостатки приведенной в ней реализации:

О реализации

Реализация выполнена в виде 3 классов:

Класс CStickyDialog.

class CStickyDialog : public CDialog
{
    DECLARE_DYNAMIC(CStickyDialog)
public:
    CStickyDialog():
            CDialog() {}
    CStickyDialog(LPCTSTR lpszTemplateName,CWnd* pParent = NULL):
            CDialog(lpszTemplateName, pParent)  {}
    CStickyDialog(UINT nIDTemplate,CWnd* pParent = NULL):
            CDialog(nIDTemplate, pParent) {}
    virtual ~CStickyDialog();
    bool IsDocker(void) const; 
    bool IsDockable(void) const; 
    void Docker(const bool bDocker); 
    void Dockable(const bool bDockable); 
protected:
    static CStickyDragManager m_dragManager;
    CPoint m_relativeOffset;
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnClose();
    afx_msg void OnMoving(UINT fwSide, LPRECT pRect);
    afx_msg void OnSizing(UINT fwSide, LPRECT pRect);
    afx_msg void OnNcLButtonDown(UINT nHitTest, CPoint point);
    virtual BOOL OnInitDialog();
    afx_msg void OnShowWindow(BOOL bShow, UINT nStatus);
};

Основная идея заключается в том, что каждому «липкому» окну, необхдимо знать обо всех подобных окнах приложения и уметь манипулировать ими при необходимости. Для вплощения данной идеи в жизнь, класс CStickyDialog имеет статический член типа CStickyDragManager. Поскольку член статический, то для любого количества экземпляров класса производного от CStickyDialog, имеется единственный, общий, менеджер окон.

Каждое окно при создании должно быть добавлено в общий список, для этого в CStickyDialog реализуется метод OnInitDialog. С другой стороны, при закрытии или удалении окна оно должно исключаться из списка. Для этого служит метод OnClose. Перед тем как начать двигать окно, необходимо определить нужно ли вместе с двигаемым окно перемещать какие-то другие окна, и если нужно, то необходимо тем или иным способом выделить в общем списке требуемые окна. Подобные манипуляции проводятся в методе OnNcLButtonDown. Управление поведением окна при передвижении и изменении размера (прилипание к другим окнам, перемещение смежных окон) осуществляется в методах OnMoving и OnSizing, соответсвенно.

Основная работа по манипулированию окнами выполняется менеджером окон.

Класс CStickyDragManager

class CStickyDragManager
{
public:
    typedef std::map<CWnd *,CWindowInfo> Map;
    typedef std::map<CWnd *,CWindowInfo>::iterator MapIter;
    typedef std::map<CWnd *,CWindowInfo>::const_iterator MapCIter;
    typedef std::map<CWnd *,CWindowInfo>::value_type MapType;
    typedef std::list<CWindowInfo*> WndList;
protected:
    int m_slack; //Максимальное расстояние в пикселях на котором окно будет прилипать.
    Map m_windows; //все "липкие" окна
    mutable CRITICAL_SECTION m_lock; //Критическая секция, обеспечивающая атомарность выполнения некоторых операций 
    void GetDockedWindows(const CWnd *pWnd,WndList &list) const; //Получить список окон «приклеенных» (реально) к заданному
    void GetDockedWindowsEx(const CWnd *pWnd,WndList &ldocked,WndList &lnondocked) const; //рукурсиваня функция обходя всех окон вокруг заданного
    void GetAttachedWindows(const CWnd *pWnd,WndList &list) const; //получить список окон которые отмечены как «приклеенные» к данному
    void UndockAll(WndList *list=NULL); //Снять все метки у окон
public:
    CStickyDragManager(void);
    virtual ~CStickyDragManager();
    void AddWindow(const CWnd *); //Добавление окна в список
    void DeleteWindow(const CWnd *); //Удаление окан из списка
    void SetDocker(const CWnd *,const bool);
    void SetDockable(const CWnd *,const bool);
    bool GetDocker(const CWnd *) const;
    bool GetDockable(const CWnd *) const;
    void FixDockState(const CWnd *pWnd);
    void MoveDocked(const CWnd *pWnd,LPRECT pRect); //Перемещение всех окон, приклеенных к заданному
    void TryDock(const CWnd *pWnd,LPRECT pRect); //Проверка на необходимость «прилипания» заданного окна к какому-либо дургому окну
    void Resize(const CWnd *pWnd,LPRECT r); //Изменение размеров заданного окна
};

Последние 2 метода изменяют заданный прямоугольник в случае если это требуется. Метод MoveDocked рассчитывает необходимое смещение и применяет его к смежным окнам.

Как уже упоминалось, класс CWindowInfo не только хранит некотрую дополнительную информацию об окне, но еще и осущесвляет некоторые дополнительные операции, как:

Как использовать

Для начала нужно добавить *.h и *.cpp файлы классов CStickyDialog, CStickyDragManager и CWindowInfo к проекту.

Для использования необходимо наследовать диалог, который должен быть «липким», от CStickyDialog. Проще всего добиться этого, создав, простой наследник от CDiialog, с последующей заменой CDialog на CStickyDialog.

Прмер 1 (TestDialog.h):

#pragma once
#include "StickyDialog.h"
// CTestDialog dialog
class CTestDialog : public CStickyDialog
{
    DECLARE_DYNAMIC(CTestDialog)
public:
    CTestDialog(CWnd* pParent = NULL);   // standard constructor
    virtual ~CTestDialog();
// Dialog Data
    enum { IDD = IDD_TESTDIALOG };
protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
    DECLARE_MESSAGE_MAP()
};

Если необходимо чтобы диалог не просто цеплялся к себе подобным, но еще и «таскал» за собой «прилипшие» диалоги при его инициализации необходимо утсановить у него свойство isDocker. По умолчанию данное свойство установлено в false.

Пример 2:

BOOL CDockDialogDlg::OnInitDialog()
{
    CStickyDialog::OnInitDialog();
    Docker(true); //Set dialog as Docker
…
}

В методах, которые перекроют методы класса CStickyDialog, овечающие за функционирование «липкости» необходимо вызывать данные методы, также как это делается для обычного диалога, при наследовании от CDialog.

Пример 3:

void CTestDialog::OnSizing(UINT fwSide, LPRECT pRect)
{
    CStickyDialog::OnSizing(fwSide, pRect);
    // TODO: пишите Ваш код здесь…
}

Заключение

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


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