Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Visual C++/MFC/WTL > Не перекрашиваются контролы


Автор: Dreamer_0x01 5.12.2005, 19:18
А конеретно - кнопки. Все остальные элементы, включая фон диалога, ведут себя нормально.

Делаю так:

В описании класса:
Код

    CBrush EditBrush,StaticBrush,ButtonBrush,DlgBrush;


В конструкторе:
Код

    this->ButtonBrush.CreateSolidBrush(RGB(0,0,0));
    this->StaticBrush.CreateSolidBrush(RGB(255,255,255));
    this->EditBrush.CreateSolidBrush(RGB(220,220,220));
    this->DlgBrush.CreateSolidBrush(RGB(0,0,0));


В обработчике сообщения WM_CTLCOLOR:

Код

HBRUSH CTestCtrlColorDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
    switch(nCtlColor)
    {
        case CTLCOLOR_STATIC:   
            {
                pDC->SetBkColor(RGB(255,255,255));
                pDC->SetTextColor(RGB(255,0,0));
                return (HBRUSH)this->StaticBrush.GetSafeHandle();
            }
        break;
        case CTLCOLOR_BTN:   
            pDC->SetTextColor(RGB(255,128,128));
            pDC->SetBkColor(RGB(0,0,0));
            return (HBRUSH)this->ButtonBrush.GetSafeHandle();
        break;
        case CTLCOLOR_EDIT:
        case CTLCOLOR_MSGBOX:
            pDC->SetBkColor(RGB(220,220,220));
            return (HBRUSH)this->EditBrush.GetSafeHandle();
        break;
        case CTLCOLOR_DLG:
            pDC->SetTextColor(RGB(255,128,128));
            return (HBRUSH)this->DlgBrush.GetSafeHandle();
        break;
    }
    return hbr;
}

Все перекрашивается, кроме кнопок. Как быть?


Автор: Dov 5.12.2005, 23:04
Цитата(Dreamer_0x01 @ 5.12.2005, 19:18)
А конеретно - кнопки.

имхо, нужно писать свой класс, производный от CButton, хотя могу ошибаться.

Автор: dont 6.12.2005, 00:58
The color of a standard CButton object is determined by system settings. If you want a different color for push buttons, use a CBitmapButton
P.S. Copyright by MSDN smile

Автор: Earnest 6.12.2005, 09:00
Цитата
The WM_CTLCOLORBTN message is sent to the parent window of a button before drawing the button. The parent window can change the button's text and background colors. However, only owner-drawn buttons respond to the parent window processing this message.

Так что... owner draw по тебе плачет. smile

Автор: Dreamer_0x01 6.12.2005, 19:10
Earnest
сделал OWNERDRAW.

Теперь кнопка действительно перекрашивается, но и не более. То есть вместо кнопки теперь однородное пятно цвета задаваемого фона кнопки. Ни натписей, ни границ кнопки, ничего нет..

Автор: Любитель 7.12.2005, 11:47
Можно использовать ещё Custom Draw (переопределяем только цвет), в XP - точно работает, раньше - вообще не уверен, могу прислать пример.

Автор: Dreamer_0x01 7.12.2005, 16:54
Нужно, чтобы работало и в 98.

Автор: JoyEx 7.12.2005, 21:36
Цитата(Dreamer_0x01 @ 7.12.2005, 16:54)
Нужно, чтобы работало и в 98.


Dreamer_0x01, ну тогда smile тебе на курсы художника-офмителя надо записываться и дружить с BS_OWNERDRAW, а если серЪёзно, то поищи src кнопки на http://codeguru.com/
http://www.codeproject.com/

Автор: takedo 8.12.2005, 09:15
Dreamer_0x01
Наверное ты хочешь, чтобы кнопка у тебя отображала твое устройство, и в зависимости от его состояния имела разный цвет?
У меня так и есть, но я воспользовался DrawItem - в мсдн в мемберсах CButton есть примерчик. Там немного доработать в плане более правильной отрисовки, добавить в класс, производный от CButton переменную Condition и т.д. Если тебе ещё хочется, чтобы при выборе соотв. аппарата вылазили его настройки и кнопка в этот момент была нажата(когда на экране настройки), производи от CBitmapButton, только мне эта идея не понравилась - там конкретные рисунки надо сразу рисовать, немаштабируемые...
А с Button я и сам разобрался в свое время за 20 минут, без знания англ, так что у тебя не должно быть проблем.
Добавлено @ 09:19
Dreamer_0x01
Цитата
Теперь кнопка действительно перекрашивается, но и не более. То есть вместо кнопки теперь однородное пятно цвета задаваемого фона кнопки. Ни натписей, ни границ кнопки, ничего нет..
после этого надо прописать DrawItem
Добавлено @ 09:20
после стиля BS_OWNERDRAW
Добавлено @ 09:24
Кто знает как нарисовать объемный эллипс? В том смысле, честь ли функция типа ::DrawFrameControl только рисующая эллипс или произвольный многоугольник. Можно и самому отрисовывать, но с объемом проблемы. smile

Автор: AlexPro 8.12.2005, 13:41
Dreamer_0x01, думаю, стоит прислушаться к совету JoyEx (имею в виду посещение сайтов smile ). У меня тоже недавно была проблема с кнопками, хотя и несколько иного плана: понадобилась пара кнопок с картинками. Картиночки-то на кнопках я отобразил, но при этом самым возмутительным образом отвалились темы XP. Как говорится в рекламе: "И чем я его только не пробовала!... Но потом поимела себе класс CXPStyleButtonST - и пятен как не бывало!". smile Качал с http://www.codeproject.com/.
Есть также класс CButtonST того же автора - всякие там плоские, прозрачные, текстурные кнопки - в демо-проекте выглядит весьма симпатично. Вроде как раз то, что тебе надо. Легко подключается к проекту (CXPStyleButtonST по крайней мере, второй класс не использовал) - мне понравилось.

Автор: Dreamer_0x01 8.12.2005, 18:46
Не думал, что с кнопками все так сложно...Ладно, переопределим класс, что ж тут поделать...

Автор: Earnest 8.12.2005, 19:22
Сочувствую и понимаю... из-за такой ерунды всю кнопку самому рисовать.

Но, в общем, кнопку нарисовать не так сложно. Посмотри функцию DrawFrameControl. У нее есть такой чудесный флажок DFCS_TRANSPARENT. Обещает, что фон не поменяет. Т.е. красишь фон как надо, и рисуешь DrawFrameControl с флагами DFC_BUTTON и этим транспарент, ну и другие флажки посмотри.
Именно это сама не пробовала, но вроде должно получиться.

Это, так сказать, программа-минимум.
Но можно и советом AlexPro воспользоваться - готовое решение поискать.

Автор: Dreamer_0x01 8.12.2005, 20:07
Earnest
Я вот поглядел функцию DrawItem,там я так понял контест устройства передается для различных состояний кнопки. Я правильно понимаю, что нарисовав что-то в этом контексте, я получу изображения кнопок для различных ее состояний? Или опять какая-то тонкость функция мне весь кайф испортит? И надо ли будет в этом случае DrawFrameControl вызывать (либо как-то наоборот запрещать его вызов, если он будет мешать?)

Просто я хочу полностью свой дизайн приложения сделать, ни на что не похожий, но сохраняющий некие стандарты. (в том числе это касается контролов).
Битмапы лепить не хочется, я наоборот, хочу как можно менее загруженный всякими объемностями/выпуклостями/рендерами интерфейс сделать, "строгий" и "максимально наглядный". Поэтому нарисовать нужные мне рамочки/значки в контексте устройства мне труда не составит. (Если это конечно тот самый контекст, в котором можно рисовать.)
Битмапами принципиально делать не хочу, так как мне нужно будет еще и ресайзинг контролов делать, то есть желательно все размеры каждый раз высчитывать динамически.

Цитата(Earnest @ 8.12.2005, 19:22)
Но можно и советом AlexPro воспользоваться - готовое решение поискать.


Во-первых, там все по-английски, а меня от таких страниц мутит =)
Во-вторых, там есть действительно очень хорошие примеры,может быть, возьму их за основу, но все равно, слишком много лишнего, и опять же, слишком "избыточно".


Автор: Earnest 8.12.2005, 21:07
Не очень поняла твой вопрос. C таким же успехом, как DrawItem, ты мог бы переопределить OnPaint - как для любого своего окна. Но Windows делает за тебя часть работы: набивает структуру DRAWITEMSTRUCT и посылает паренту кнопки WM_DRAWITEM. Это стандартный путь Windows API. А MFC перехватывает это сообщение и вызывает виртуальную функцию DrawItem.
Контекст, который туда передается - это текущий контекст PAINT DC, ровно тот, где и надо рисовать. Ты должен отрисовать туда кнопку в текущем состоянии. Это состояние ты можешь получить из той же структуры DRAWITEMSTRUCT.
Собственно, в описании функции DrawItem для CButton есть пример кода:

Код

void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
   UINT uStyle = DFCS_BUTTONPUSH;

   // This code only works with buttons.
   ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);

   // If drawing selected, add the pushed style to DrawFrameControl.
   if (lpDrawItemStruct->itemState & ODS_SELECTED)
      uStyle |= DFCS_PUSHED;

   // Draw the button frame.
   ::DrawFrameControl(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, 
      DFC_BUTTON, uStyle);

   // Get the button's text.
   CString strText;
   GetWindowText(strText);

   // Draw the button text using the text color red.
   COLORREF crOldColor = ::SetTextColor(lpDrawItemStruct->hDC, RGB(255,0,0));
   ::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(), 
      &lpDrawItemStruct->rcItem, DT_SINGLELINE|DT_VCENTER|DT_CENTER);
   ::SetTextColor(lpDrawItemStruct->hDC, crOldColor);
}


В этом примере, правда, наоборот, фон стандартный, а текст рисуется другим цветом.
Я вот подумала, что зря посоветовала тебе DrawFrameControl:
все, что она может для тебя сделать - это нарисовать правильную рамку (вдавленную или выдавленную). Но для этого есть отдельные функции DrawEdge и Draw3DRect (выбирай какая больше нравится).
Рамку фокуса рисовать - DrawFocusRect, дизабленный текст - GrayString.

Автор: Dreamer_0x01 8.12.2005, 22:00
Сморел я этот пример, еще тогда, когда takedo про него сказал.
Пробовал, перекрашивается...Но вот если присмотреться к стандартной кнопки, то при ее нажимании текст кнопки нажимается вместе с ней. А тут он остается неподвижным. Хотя, мне это как раз и не надо, я пожалуй так и сделаю, создам switch на lpDrawItemStruct->itemState и в его пунктах нарисую нужные мне рамки и значки.

Кстати, если в этом примере устроить в качестве фона SetBkColor - то получится мягко говоря не очень красивая надпись =)
Так что пришлось фон принудительно заливать в обработке OnPaint.


Автор: takedo 9.12.2005, 09:01
Dreamer_0x01
Цитата
Но вот если присмотреться к стандартной кнопки, то при ее нажимании текст кнопки нажимается вместе с ней. А тут он остается неподвижным.
Я же говорил, что надо доработать smile -в зависимости от состояния меняю координаты начала текста и др. и кнопка начинает вести себя как и все другие. А вот использовать в таком случае OnPaint по моему не верно, у меня так даже и не заходит туда - все в DrawItem smile

Earnest Я вот и спрашивал
Цитата
Кто знает как нарисовать объемный эллипс? В том смысле, честь ли функция типа ::DrawFrameControl только рисующая эллипс или произвольный многоугольник. Можно и самому отрисовывать, но с объемом проблемы. 
- вот и вы до этого же дошли smile Dreamer_0x01 по моему этого то и не хватает...

Автор: Earnest 9.12.2005, 09:04
Лучше бы все делать в одном месте, т.е. в DrawItem. Кто тебе мешает заливать фон здесь? В этой функции можешь делать все то же самое - вызываешь какой-нибудь FillRect, и все.
Конечно, пример - это только пример, далеко не полный.
При нажатии текст кнопки действительно надо сдвигать, примерно на 1,1 вниз по диагонали.
Не забудь после отрисовки состояние DC поменять на исходное - MSDN на этом очень настаивает.
Я обычно, чтобы не парится с востановлением всего, что наменяла, использую SaveDC - RestoreDC. Еще удобнее - завернуть все в классик:
Код

class CDCState
{
public:
   CDCState (CDC& dc):  m_dc (dc) {  m_nState = m_dc.SaveDC(); }
   ~CDCState()   { m_dc.RestoreDC (m_nState); }

private:
   CDC& m_dc;
   int m_nState;
};

#define SAVE_DC_STATE(dc) CDCState _dcState(dc)

Ставишь в начала функции SAVE_DC_STATE() и больше ни о чем не заботишься.
Добавлено @ 09:08
takedo, на пару минут опередил smile

Автор: takedo 9.12.2005, 09:13
Я вот так сделал, много лишнего, но это вырезка из кода, думаю не страшно smile
Код

void CMButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{

    if(m_draw)return;
    ::InterlockedExchange(&m_draw,1);//после изменения
    peek(&dat);
    BYTE nWnd = dat.r_N(); 
    CRect rect(lpDrawItemStruct->rcItem);
    CRect rectclient;
    CRect rect_dt;//по пожеланиям Ромы
    int cx = GetSystemMetrics(SM_CXEDGE);
    int cy = GetSystemMetrics(SM_CYEDGE);
    CDC dc;

    rect.bottom-=cy+1;
    rect.right-=cx+1;
    rect.top+=cy+1;
    rect.left+=cx+1;
    rectclient = rect;

    dc.Attach(lpDrawItemStruct->hDC);
    CPen mypen;
    CPen* oldpen;
    COLORREF crText,crBk,crPen,crBrush,crTextOld,crBkOld;
    
    CBrush* oldbrush;
    CString strText;

    GetColor(&crText,&crBk,&crPen,&crBrush);

    
     mypen.CreatePen(PS_SOLID,1,crPen);
     CBrush mybrush(crBrush);
     
     oldpen = dc.SelectObject(&mypen);
     oldbrush = dc.SelectObject(&mybrush);
     crTextOld = dc.SetTextColor(crText);
     crBkOld = dc.SetBkColor(crBk);

   UINT uStyle = DFCS_BUTTONPUSH;
   // This code only works with buttons.
   ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);

   if (lpDrawItemStruct->itemState & ODS_SELECTED)
   {
      uStyle |= DFCS_PUSHED;
      rectclient.left+=cx;
      rectclient.top+=cy;
      rectclient.right+=cx;
      rectclient.bottom+=cy;
     
   }else

   // Draw the button frame.
   dc.Rectangle(&rectclient);

   dc.DrawFrameControl(&lpDrawItemStruct->rcItem,lpDrawItemStruct->CtlType,uStyle);   
   
   dc.Rectangle(&rectclient);

   strText.Format(_T("№ %d"),nWnd);
  

   dc.DrawText(strText,&rectclient,DT_VCENTER|DT_CENTER);
   
   rectclient.top+= (rectclient.bottom - rectclient.top)/3;


   rect_dt = rectclient; 
   rect_dt.right = dc.GetTextExtent(_T(" Out:")).cx;
   strText.Format(_T(" In:"));

   dc.DrawText(strText,&rect_dt,DT_VCENTER|DT_RIGHT);

   strText.Format(_T("%10d "),dat.r_InCount());
   dc.DrawText(strText,&rectclient,DT_VCENTER|DT_RIGHT);
   
   rectclient.top+= (rectclient.bottom - rectclient.top)/2;

   rect_dt.top = rectclient.top;

   strText.Format(_T(" Out:"));

   dc.DrawText(strText,&rect_dt,DT_VCENTER|DT_RIGHT);

   strText.Format(_T("%10d "),dat.r_OutCount());
   dc.DrawText(strText,&rectclient,DT_VCENTER|DT_RIGHT);
   

   dc.SetTextColor(crTextOld);
   dc.SetBkColor(crBkOld);
   dc.SelectObject(oldpen);
   dc.SelectObject(oldbrush);

   dc.Detach();
    

   ::InterlockedExchange(&m_draw,0);
   return;
} 

Автор: takedo 9.12.2005, 09:32
Earnest
посмотрел SaveDC так в мсдн говорят, что все текущее состояние сохраняется в "context stack" - это куда, можно узнать? Спрашиваю потому, что как и обычно твой классик - весьма удобен, но вот сомнения терзанули, а сколько может быть сохранено в этом контексте?

Автор: takedo 9.12.2005, 12:05
Earnest
Я почему спросил, мотому что не понял в каком стеке? То есть у каждого DC есть свой стек? В который мы и записываем. Но вот какая мне проблема видится: есть функция обработчик1 и функция обработчик2 сообщения 1 и 2. В фукнции 1 мы сохраняем SaveDC(hdc), в это время, приходит сообщение, вызывающее обработчик 2, в котором мы тоже SaveDC(hdc). В итоге, если сохраняем в одно и то же место можем восстановить по Restore не то. Или как то возвращаемый SaveDC параметр влияет. Вот не могу этого понять smile

Автор: JoyEx 9.12.2005, 15:27
Цитата(takedo @ 9.12.2005, 09:32)
посмотрел SaveDC так в мсдн говорят, что все текущее состояние сохраняется в "context stack" - это куда, можно узнать?


Где-то в GDI-памяти, механизм стека для этих целей можно и самому сделать, но лучше не быть smile Самоделкиным.

SaveDC(hdc) ==> RestoreDC(hdc, -1) ведь очень удобно!

Цитата
сколько может быть сохранено в этом контексте

Пока SaveDC() не вернет 0

Цитата(takedo @ 9.12.2005, 12:05)
Но вот какая мне проблема видится: есть функция обработчик1 и функция обработчик2 сообщения 1 и 2. В фукнции 1 мы сохраняем SaveDC(hdc), в это время, приходит сообщение, вызывающее обработчик 2, в котором мы тоже SaveDC(hdc). В итоге, если сохраняем в одно и то же место можем восстановить по Restore не то. Или как то возвращаемый SaveDC параметр влияет.


Да-да, "возвращаемый SaveDC параметр" и нужен для того, чтобы не появился хаос:
RestoreDC(hdc, сюда его прописывай).

Автор: Earnest 9.12.2005, 19:56
JoyEx все правильно говорит.
takedo
Честно говоря, я не знаю, сколько состояний выдержит этот стек, не экспериментировала. Обычно я ставлю сохранение DC контекста в начале самой внешней функции отрисовки (которую Windows или MFC вызывают), а во внутренних уже не забочусь о том, что изменилось - внешняя функция, завершаясь, все подчистит.
Пользуюсь давно, проблем не было.

Автор: Dreamer_0x01 10.12.2005, 15:25
А может, кто на пальцах пояснит, для чего вообще этот RestoreDC нужен? Только в MSDN просьба не отсылать, на английском не пойму...

Автор: JoyEx 10.12.2005, 16:34
Цитата(Dreamer_0x01 @ 10.12.2005, 15:25)
А может, кто на пальцах пояснит, для чего вообще этот RestoreDC нужен? Только в MSDN просьба не отсылать, на английском не пойму...

Нуууу... smile , Dreamer_0x01 , это типа smile ПоелИубериСамЗаСобой, а если серЪёзно, то все апишные ф-ции рисуют текущими карандашами (Pen) и т.п., а если ты не восстановишь (RestoreDC) этот карандаш, то кто-то (например, баз. класс) будет рисовать уже твоим карадашом.

Автор: Earnest 12.12.2005, 08:56
Цитата(Dreamer_0x01 @ 10.12.2005, 15:25)
А может, кто на пальцах пояснит, для чего вообще этот RestoreDC нужен?

Да ровно для того, чтобы не писать все эти многочисленные pDC->SelectObject(pOldPen) и не запоминать олд-пены.
Есть такая весьма распространенная ошибка: создаем в функции GDI-объект, выбираем его в контекст, по выходе из функции созданный объект удаляем, но забываем восстановить старый. Так вот, в этом случае GDI-объект вовсе не удаляется. Особенно в 98 это весело выглядит: пара минут работы, и все рисуется белым - кончились GDI-ресурсы, остались одни стандартные.
А когда пишешь более-менее сложный код, согласись, есть о чем думать, кроме того, чтобы запомнить, что ты там выбрал в контекст. В идеале - совсем об этом не заботиться. Вот для этого идеально подходит парочка SaveDC-RestoreDC.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)