Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 2 августа!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

ПРОГРАММИРОВАНИЕ НА C и С++



Программисты долго мучаются с кодом прогаммы, изучают С++, WinAPI функции, MSDN. Потом пишут банальную систему защиты или навешивают банальный протектор, а крэкеры и реверсеры справляются с такой защитой за 5 минут. В итоге, продажи программы почти нулевые. Чтобы такого не допустить, тут самому надо немного поднабрать опыта отладки, реверсинга, тот же отладчик Ollydbg изучить или дизассемблер IDA Pro. Но где искать по крохам эти знания? Нет, конечно можно годами "методом тыка" разбираться, но куда быстрее видеокурс специальный посмотреть. Вот тут он есть: ссылка. Автор курса с большим опытом и объясняет понятно, я из этого курса много узнал про то как работает компьютер, процессор, про инструменты специальные и как с ними работать. Мои коллеги программисты на работе ничего такого и не знают, теперь я им нос утру.
Часто можно увидеть в разных программах красивые менюшки, которые
нельзя создать с помощью мастеров. Такие меню есть и в WordXP ExсelXP. Эта статья научит вас
создавать такие меню.

Такие меню не создаются автоматически - им надо рисовать себя самим. Итак:
ШАГ 1. Чтобы пункт меню был саморисующийся ему надо установить стиль MF_OWNERDRAW. Поскольку оно само себя рисует - то надо создать обработчик DrawItem сообщения WM_DRAWITEM. Также мы должны сами определить размеры меню: надо создать обработчик MeasureItem сообщения WM_MEASUREITEM. И MF_OWNERDRAW и WM_DRAWITEM вызывается для _каждого_ пункта меню.
Сделаем наше меню на основе MFC класса CMenu и назовём его CMenuEx. Оно будет простенькое, но при желании можно усложнить до требуемого состояния самому. Главное понять принципы, по которым оно работает.
Значит у нас есть:
class CMenuEx : public CMenu  
{
public:
    virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
    virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
    CMenuEx() {};
    virtual ~CMenuEx() {};
};
void CMenuEx::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{}
void CMenuEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{}
ШАГ 2. Допустим к нашему класу уже присоединён описатель стандартного меню. Нам надо для каждого пункта установить стиль MF_OWNERDRAW и ещё некоторые атрибуты (которые будут использоваться при отрисовке, или заданиии размеров). Для этого обьявим структуру:
struct MYOWNMENUITEM : public CObject
    {
        bool bIsTop;    // признак является ли пункт верхним в menu bar
        CString sCaption;   // надпись меню

        MYOWNMENUITEM()
        {
            this->bIsTop = false;
            this->sCaption = "";
        }
    };
Также, для усложнения, в ней можно хранить значки, картинки либо признак какой-то особенности.
Создадим метод, в котором пройдёмся по всем пунктам изменяя их стиль и заполняя их структуры.
Пункты и субменю будем хранить в переменных класса
    CPtrArray m_MenuArray;
    CPtrArray m_ItemArray;
А вот наш метод:
void CMenuEx::Prepare(bool bTopLevel /*= false*/)
{
    // bTopLevel - признак, что пункт меню есть верхним в menu bar
    for (UINT i=0; i < GetMenuItemCount(); i++)
    {
        MYOWNMENUITEM* pItem = new MYOWNMENUITEM;

        pItem->bIsTop = bTopLevel;
        GetMenuString(i, pItem->sCaption, MF_BYPOSITION);
        ModifyMenu(i, MF_BYPOSITION|MF_OWNERDRAW, GetMenuItemID(i), (TCHAR*) pItem);
        m_ItemArray.Add(pItem);

        if(GetSubMenu(i))
        {
            CMenuEx* pMenu = new CMenuEx;
            pMenu->m_pWnd = this->m_pWnd;
            pMenu->Attach((this->GetSubMenu(i))->GetSafeHmenu());
            m_MenuArray.Add(pMenu);
            pMenu->Prepare();
        }
    };
}

Также для удобства я создал метод
void CMenuEx::MakeMenuEx(CWnd* pWnd, bool bToolBar/* = false*/)
{
    m_pWnd = pWnd;
    if(bToolBar)
    {
        for(UINT i=0; i < GetMenuItemCount(); i++)
            Prepare(true);
    }
    else
        Prepare();
}
Где переменная класса CWnd* m_pWnd - это окно, в котором показывается меню, а bToolBar - признак, является ли меню popup или toolbar.
Соответственно, выделенную память нужно освободить в деструкторе:
CMenuEx::~CMenuEx()
{
    for(INT32 a=0; a<m_MenuArray.GetSize(); a++)
    {
        delete (CMenuEx*) m_MenuArray[a];
    };
    for(INT32 b=0; b<m_ItemArray.GetSize(); b++)
    {
        delete (MYOWNMENUITEM*) m_ItemArray[b];
    };
}

ШАГ 3. ::Примечание:: id для сепаратора всегда =0, а для субменю =-1 !!!
Надо определить размеры меню. Мы получаем LPMEASUREITEMSTRUCT - это указатель на
структуру MEASUREITEMSTRUCT:
typedef struct tagMEASUREITEMSTRUCT { 
    UINT CtlType;   // для меню всегда равно ODT_MENU
    UINT CtlID;     // не используется в меню
    UINT itemID;    // содержит ID пукта
    UINT itemWidth;     // ширина меню - нам надо установить желаемую ширину
    UINT itemHeight;    // высота меню - нам надо установить желаемую высоту
    DWORD itemData  // данные, которые добавлены к пункту с помощью методов CMenu::AppendMenu,
            // CMenu::InsertMenu, CMenu::ModifyMenu
            // Тут содержится наша структура MYOWNMENUITEM , которую мы добавляли в Prepare()
} MEASUREITEMSTRUCT;
Размер, для простоты, можно просто вбить:
void CMenuEx::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    lpMeasureItemStruct->itemHeight = 20;
    lpMeasureItemStruct->itemWidth = 50;
}

(в присоединённом проекте сделано чуть сложнее - там ширина зависит от длины надписи, от.....)

Теперь рисуем пунк меню. Мы получаем LPDRAWITEMSTRUCT - это указатель на структуру DRAWITEMSTRUCT:
typedef struct tagDRAWITEMSTRUCT { 
    UINT CtlType;   // для меню всегда равно ODT_MENU
    UINT CtlID;     // не используется в меню
    UINT itemID;    // содержит ID пукта
    UINT itemAction;    // Сообщает какое действие требуется отрисовать. Может содержать такие биты:
            // ODA_DRAWENTIRE - этот бит установлен, когда пункт надо отрисовать
            // ODA_FOCUS - этот бит установлен, когда пункт получает или теряет фокус.
            // ODA_SELECT - этот бит установлен, когда пункт получает или теряет выделение
            // [COLOR=blue]!!!Совет: Надо проверить itemState чтобы определить, когда пункт выделен.[/COLOR]
    UINT itemState;     // состояние пункта. Для меню может быть:
            // ODS_CHECKED - установлен, когда пункт в состоянии checked
            // ODS_DISABLED - установлен, когда пункт отключён
            // ODS_FOCUS - установлен, когда пункт получает фокус
            // ODS_GRAYED - установлен, когда пункт недоступный (dimmed, серый)
            // ODS_SELECTED - установлен, когда пункт выбран
            // ODS_DEFAULT - установлен, если пункт есть пунктом по умолчанию
    HWND hwndItem;  // определяет дескриптор меню (HMENU) которое содержит пункт меню
    HDC hDC;        // определяет контекст устройства, который используется для рисования пункта
    RECT rcItem;    // прямоугольник, который ограничивает наш пункт (его мы задавали в MeasureItem)
    DWORD itemData;     // данные, которые добавлены к пункту с помощью методов CMenu::AppendMenu,
            // CMenu::InsertMenu, CMenu::ModifyMenu
            // Тут содержится наша структура MYOWNMENUITEM , которую мы добавляли в Prepare()
} DRAWITEMSTRUCT;
Для простоты отрисовку можно сделать такую:
void CMenuEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    // Получаем нашу структуру
    MYOWNMENUITEM* pItem = (MYOWNMENUITEM*)lpDrawItemStruct->itemData;

    CRect RFull(lpDrawItemStruct->rcItem); // Ограничивающий пункт прямоугольник
    // Зона значка, или в нашем случае - градиентной заливки
    CRect RIcon(RFull.left,RFull.top,RFull.left+m_szIconPadding.cx,RFull.top+RFull.bottom);
    // зона текста
    CRect RText(RIcon.right,RFull.top,RFull.right,RFull.bottom);

    COLORREF ColorIconRL = COLORREF(RGB(246,245,244)); // Цвет левой части заливки
    COLORREF ColorIconRR = COLORREF(RGB(0,209,201)); // Цвет правой части заливки
    COLORREF TextColor = COLORREF(RGB(249, 248, 247)); // Цвет фона текста

    if(pItem->bIsTop) // признак, что пункт меню есть верхним в menu bar
    {
        ZeroMemory(&RIcon, sizeof(CRect));
        RText = RFull;
        TextColor = GetSysColor(COLOR_BTNFACE);// COLORREF(RGB(192,192,192));
    }

    // получаем контекст, на котором будем рисовать
    CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);

    // Функция градиентной заливки
    FillFluentRect(pDC->GetSafeHdc(), RIcon, 246,245,244,213,209,201);

    pDC->FillSolidRect(&RText, TextColor); // Рисуем фон текста

    pDC->SetBkColor(TextColor);
    
    // Рисуем текст пункта
    pDC->DrawText(pItem->sCaption, &RText, DT_EXPANDTABS|DT_LEFT|DT_VCENTER|DT_EDITCONTROL );
}

Но в нашем случае (во вложении) всё немного сложнее:
void CMenuEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    //TRACE("CMenuEx::DrawItem\n");

    // Получаем нашу структуру
    MYOWNMENUITEM* pItem = (MYOWNMENUITEM*)lpDrawItemStruct->itemData;

        CRect RFull(lpDrawItemStruct->rcItem); // Ограничивающий пункт прямоугольник
    // Зона значка, или в нашем случае - градиентной заливки
    CRect RIcon(RFull.left,RFull.top,RFull.left+m_szIconPadding.cx,RFull.top+RFull.bottom);
    // зона текста
    CRect RText(RIcon.right,RFull.top,RFull.right,RFull.bottom);

    COLORREF ColorIconRL = COLORREF(RGB(246,245,244)); // Цвет левой части заливки
    COLORREF ColorIconRR = COLORREF(RGB(0,209,201)); // Цвет правой части заливки
    COLORREF TextColor = COLORREF(RGB(249, 248, 247)); // Цвет фона текста

    if(pItem->bIsTop) // признак, что пункт меню есть верхним в menu bar
    {
        ZeroMemory(&RIcon, sizeof(CRect));
        RText = RFull;
        TextColor = GetSysColor(COLOR_BTNFACE);// COLORREF(RGB(192,192,192));
    }

    // получаем контекст, на котором будем рисовать
    CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);

    // Функция градиентной заливки
    FillFluentRect(pDC->GetSafeHdc(), RIcon, 246,245,244,213,209,201);
    // если есть значёк, то его можно отрисовать 
    // поверх заливки с помощью функции BitBlt

    pDC->FillSolidRect(&RText, TextColor); // Рисуем фон текста

    if(lpDrawItemStruct->itemID == 0) // если этот пункт - это Separator
    {
        // Функция градиентной заливки
        FillFluentRect(pDC->GetSafeHdc(), RIcon, 246,245,244,213,209,201);

        pDC->FillSolidRect(&RText, TextColor); // рисуем фон
        CPen pen;
        pen.CreatePen(PS_SOLID, 1, GetSysColor(25));
        CPen* pOldPen = pDC->SelectObject(&pen);

        // рисуем сепаратор
        pDC->MoveTo(RText.left+5,  RText.top+(RText.bottom-RText.top)/2);
        pDC->LineTo(RText.right, RText.top+(RText.bottom-RText.top)/2);

        pDC->SelectObject(pOldPen);
        DeleteObject(pen);
    }

    else if ((lpDrawItemStruct->itemState & ODS_SELECTED) &&
             (lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)) )
    {
        // Если пункт выделен - то рисуем выделение
        if (!(lpDrawItemStruct->itemState & ODS_GRAYED)) // проверка доступен ли пункт
        {
            TextColor = COLORREF(RGB(182, 189, 210));
            pDC->FillSolidRect(&RFull, TextColor); // фон
            CBrush* br = new CBrush;
            br->CreateSolidBrush(COLORREF(RGB(10, 36, 106)));
            pDC->FrameRect(&RFull, br); // рамка
            delete br;
        };
    };

    if(lpDrawItemStruct->itemState & ODS_CHECKED) // если пункт в состоянии checked
    {
        // Checked Item
        HBITMAP     hBmp;
        CBitmap*    pBmp;
        BITMAP      bmp;
        CSize       szBmp;
        CPoint      ptBmp;
        ZeroMemory(&bmp, sizeof(BITMAP));
        
        // Загружаем значёк checked
        hBmp = ::LoadBitmap(NULL, MAKEINTRESOURCE(32760));
        pBmp = CBitmap::FromHandle(hBmp);
        pBmp->GetBitmap(&bmp);
        szBmp = CSize(bmp.bmWidth, bmp.bmHeight);
        ptBmp = CPoint(RIcon.left+(m_szIconPadding.cx-szBmp.cx)/2+1, 
                        RIcon.top+(m_szIconPadding.cy-szBmp.cy)/2);
        // рисуем состояние
        pDC->DrawState(ptBmp, szBmp, hBmp, DSS_NORMAL|DSS_UNION);
        DeleteObject(hBmp);
    };

    // Устанавливаем цвет фона и границу надписи
    pDC->SetBkColor(TextColor);
    RText.left += m_szTextPadding.cx;
    RText.top += m_szTextPadding.cy;
    RText.bottom -= m_szTextPadding.cy;

    // если пункт недоступен - устанавливаем соответствующий цвет текста
    if (lpDrawItemStruct->itemState & ODS_GRAYED)
        pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
    // Рисуем текст пункта
    if(pItem->bIsTop) 
        // если пункт меню есть верхним в menu bar - то выравниваем по центру
        pDC->DrawText(pItem->sCaption, &RText, DT_EXPANDTABS|DT_CENTER|DT_VCENTER);
    else
        // иначе - по левому краю
        pDC->DrawText(pItem->sCaption, &RText, DT_EXPANDTABS|DT_LEFT|DT_VCENTER|
                      DT_EDITCONTROL );
}

ШАГ 4. Использование:
1) Если надо отобразить popup, то надо обьявить указатель CMenuEx* m_menu;
В конструкторе окна создать обьект и инициализировать его:
CmenuView::CmenuView()
{
    m_menu = new CMenuEx;
    m_menu->LoadMenuEx(IDR_MEMU, this);
}
соответственно в деструкторе - удалить обьект delete m_menu;
Создать обработчик OnMeasureItem и вызвать из него MeasureItem нашего класса
void CmenuView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    m_menu->MeasureItem(lpMeasureItemStruct);

    CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}

Запустить popup при клике правой кнопкой мышки:
void CmenuView::OnRButtonDown(UINT nFlags, CPoint point)
{
    ClientToScreen(&point);
    m_menu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON, point.x, point.y, this);

    CView::OnRButtonDown(nFlags, point);
}

2) Если надо отобразить как menu bar, то тоже надо сначала обьявить указатель.
Потом создать обьект в конструкторе и соответственно удаление в деструкторе.
В OnCreate окна инициализировать и установить меню:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   ...........
    m_myme->LoadMenuEx(IDR_MAINFRAME, this, true);
    ::DestroyMenu(m_hMenuDefault);
    SetMenu(m_myme);
    m_hMenuDefault = m_myme->GetSafeHmenu();
   ............
}
Создать обработчики OnMeasureItem и OnDrawItem окна и вызвать из них соответствующие
методы нашего меню:
void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    m_myme->MeasureItem(lpMeasureItemStruct);

    CFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}

void CMainFrame::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    m_myme->DrawItem(lpDrawItemStruct);

    CFrameWnd::OnDrawItem(nIDCtl, lpDrawItemStruct);
}

Вот и всё!

Демонстрационное приложение.В прикрепленном архиве содержится проект, который демонстрируют работу с owner-drawn menu.

Ссылки:Owner-Drawn Menu, не отрисовывается один пункт 3 вопроса про меню
http://www.codeproject.com/menu/
http://www.codeproject.com/menu/owndraw.asp

Список ключевых слов:
CMenu, owner-drawn, MeasureItem, LPMEASUREITEMSTRUCT, MEASUREITEMSTRUCT, LPDRAWITEMSTRUCT, DRAWITEMSTRUCT, DrawItem, OnDrawItem, OnMeasureItem
Скачать пример

<< ВЕРНУТЬСЯ В ПОДРАЗДЕЛ

<< ВЕРНУТЬСЯ В ОГЛАВЛЕНИЕ




Материалы находятся на сайте https://exelab.ru/pro/



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS