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


Автор: Rickert 29.5.2008, 08:56
Есть окно, на нём куча элементов, все там по своему инициализируются и всё такое. Как мне поймать момент конца полной инициализации всех элементов, то бишь момент, после которого окно перейдёт в режим ожидания сообщений: когда уже всё отрисованно?

Автор: deniska 29.5.2008, 10:02
может OnIdle переопределить? вроде как вызывается когда очередь сообщений пуста

Автор: Rickert 29.5.2008, 10:44
Пардон, не сказал: у меня модальный диалог, для него OnIdle не перегрузишь насколько мне известно.

Автор: Rickert 29.5.2008, 11:46
OnEnterIdle тоже ни при делах.

Автор: deniska 29.5.2008, 12:14
объект CWinApp есть в каждом приложении...  как модальность\немодальность окна может влиять?
ты ведь не для диалога перегружаешь а для основного потока своего приложения. как только освобождается очередь сообщений для него, по идее сразу должен попасть в свой обработчик OnIdle

Автор: Rickert 30.5.2008, 07:30
deniska, в одной из тем, здесь же на форуме я задавал вопрос немного другого плана и там, как мне было объяснено: OnIdle не вызывается, потом что модальный диалог идёт не через InitInstance().
Плюс к этому я уже трижды пробовал перегружать OnIdle в потомке от CWinApp - результата нет.

Автор: Andrey44 30.5.2008, 07:47
Может поможет
Код

BOOL GetMessage(
  LPMSG lpMsg, 
  HWND hWnd, 
  UINT wMsgFilterMin, 
  UINT wMsgFilterMax 
); 


Автор: deniska 30.5.2008, 08:22
Rickert, по ходу ты прав. сейчас попробовал - не заходит. тогда можно в какойнить таймер воткнуть функцию, которую Andrey44 дал... 
а вообще если вся инициализация контролов находится в OnInitDialog, то наверно следующий после этой функции вызов OnPaint последний. по крайней мере можно с GetMessage по экспериментировать, проверить.

Автор: Earnest 30.5.2008, 11:36
Модальному диалогу должно прийти WM_KICKIDLE - первый раз оно приходит после окончания инициализации, а дальше - как OnIdle. Это private MFC сообщение, определено в afxpriv.h

Автор: Rickert 2.6.2008, 03:35
Цитата(Earnest @  30.5.2008,  11:36 Найти цитируемый пост)
Модальному диалогу должно прийти WM_KICKIDLE - первый раз оно приходит после окончания инициализации, а дальше - как OnIdle. Это private MFC сообщение, определено в afxpriv.h 

Поставил у диалога в обработку PreTranslateMessage на WM_KICKIDLE - простой вывод MessageBox'а: никаких результатов. smile 

Автор: Earnest 2.6.2008, 15:38
WM_KICKIDLE не помещается в очередь, а посылается прямо. Ставь обработчик - поймаешь.

Автор: Rickert 3.6.2008, 03:59
Цитата(Earnest @  2.6.2008,  15:38 Найти цитируемый пост)
WM_KICKIDLE не помещается в очередь, а посылается прямо. Ставь обработчик - поймаешь. 

Так как я его поставлю?
В BEGIN_MESSAGE_MAP()? Нету там ON_WM_KICKIDLE.
MSDN тоже, кстати, мало что внятного может сказать про WM_KICKIDLE.
Где ты про него вычитала интересно? smile

Автор: dizzy1984 3.6.2008, 08:22
Цитата(Rickert @  3.6.2008,  05:59 Найти цитируемый пост)
Где ты про него вычитала интересно? 

Подобное можно узнать кропотливой отладкой исходников MFC.

Интересующее нас место тут (файл wincore.cpp, урезаны незначимые места)
Код

// acquire and dispatch messages until the modal state is done
    for (;;)
    {
        ASSERT(ContinueModal());

        // phase1: check to see if we can do idle work
        while (bIdle &&
            !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
        {
            ASSERT(ContinueModal());

            if ((dwFlags & MLF_NOKICKIDLE) ||
                !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))
            {
                // stop idle processing next time
                bIdle = FALSE;
            }
        }

        // phase2: pump messages while available
        do
        {
            ASSERT(ContinueModal());

            // pump message, but quit on WM_QUIT
            if (!AfxGetThread()->PumpMessage())
            {
                AfxPostQuitMessage(0);
                return -1;
            }

                           if (!ContinueModal())
                goto ExitModal;

            // reset "no idle" state after pumping "normal" message
            if (AfxGetThread()->IsIdleMessage(pMsg))
            {
                bIdle = TRUE;
                lIdleCount = 0;
            }

        } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
    }


Видно, что в код, расположенный в первой фазе бесконечного цикла, будет выполнен на следующей итерации после того, как очередь сообщений опустеет. Выход из него осуществляется в зависимости от результата ответа на магическое сообщение "SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++)". Ясно, что если это FALSE - бесконечные цикл войдет во вторую фазу и на этом успокоится.
Соответственно Earnest уже сказала, что SendMessage(WM_KICKIDLE...) займется тем, что вызовет процедуру окна, которую MFC зарегистрировала как "AfxWndProc"(тот же Wincore.cpp) и которая в конечном счете найдет CWnd, соответсвующее полученному ей HWND и просмотрит его карту отклика. Там она может увидеть обработчик сообщения WM_KICKIDLE. В таком случае вместо стандартного обработчика DefWindowProc будет взят именно он.
Резюмируя, пишем код
Код

#include <afxpriv.h>
//...

BEGIN_MESSAGE_MAP(CTest2Dlg, CDialog)
//...
ON_MESSAGE(WM_KICKIDLE, Proc)
//...
END_MESSAGE_MAP()

BOOL CTest2Dlg::Proc()
{
return FALSE;
}

Замечательно, работает, но зачем мы все это написали? Я не знаю.
Все это ни к чему, т.к идеологически неверно - такой обработчик будет вызываться постоянно, после появления новых сообщений в очереди.
Нормальный вариант решения проблемы с моей точки зрения будет посылка произвольного сообщения из OnInitDialog.
Обработчик такого сообщения будет вызван после того, как очередь опустеет.
Пишем код
Код

BEGIN_MESSAGE_MAP(CTest2Dlg, CDialog)
//...
ON_MESSAGE(WM_USER, Proc2)
//...
END_MESSAGE_MAP()

BOOL CTest2Dlg::OnInitDialog()
{
         //...
    SendMessage(WM_USER, 0, 0);
    return FALSE;  // return TRUE  unless you set the focus to a control
}

void CTest2Dlg::Proc2() 
{    
    return ;
}


Автор: Earnest 3.6.2008, 10:27
Цитата(Rickert @  3.6.2008,  04:59 Найти цитируемый пост)
В BEGIN_MESSAGE_MAP()? Нету там ON_WM_KICKIDLE.

Убойный аргумент, что и говорить.

Насчет WM_KICKIDLE... я его использую для аснхронного обновления контролов диалога, очень полезная вешь (примерно так, как работает WM_IDLEUPDATECMDUI в фрейворке...
Насчет "приходить много раз" - да, конечно, однако нетрудно поставить какой-то флаг инициализации. Но это дело вкуса - я лично терпеть не могу юзеровские сообщения плодить. По-хорошему, нужно использовать Registered Messages, а это хлопотно. Или поддерживать общий пул юзеровских сообщений... что порождает лишние связи между модулями... Можно, конечно, плюнуть и плыть по течению, но в большом проекте такой бардак рано или поздно приведет к проблемам...
В общем, ваш выбор...

Цитата(dizzy1984 @  3.6.2008,  09:22 Найти цитируемый пост)
SendMessage(WM_USER, 0, 0);

Цитата(dizzy1984 @  3.6.2008,  09:22 Найти цитируемый пост)
Обработчик такого сообщения будет вызван после того, как очередь опустеет.

Ты сам-то понял, что чушь написал? Ну, будем считать это опиской  smile 

Автор: Rickert 4.6.2008, 04:15
Да, что-то я относительно ON_MESSAGE прогнал smile
Всем спасибо, solved smile 

Автор: dizzy1984 4.6.2008, 05:17
Цитата(Earnest @  3.6.2008,  12:27 Найти цитируемый пост)
Ты сам-то понял, что чушь написал? Ну, будем считать это опиской   

Написал по-проще для ясности, имелось в виду, что очередь значимых сообщений (идущих до WM_USER) уже будет обработана, останется только "надуманное" сообщение (WM_USER), которое, конечно, формально будет последним в той же очереди, но кого это волнует? Это как раз и нужно.

Автор: dizzy1984 4.6.2008, 07:29
А на счет RegisterWindowMessage, это было дельное предложение. Вот более верный вариант :
Код

static const UINT messageAppInit = ::RegisterWindowMessage("3D5914BB-6689-4697-81F9-4DADA4A5964B");

BEGIN_MESSAGE_MAP(CTest2Dlg, CDialog)
//...    
ON_REGISTERED_MESSAGE(messageAppInit, Proc2)    
//...    
END_MESSAGE_MAP()    

BOOL CTest2Dlg::OnInitDialog()    
{    
         //...    
    SendMessage(messageAppInit, 0, 0);    
    return FALSE;  // return TRUE  unless you set the focus to a control    
}    
void CTest2Dlg::Proc2()    
{     
    return ;    
}

Автор: Earnest 4.6.2008, 07:29
ОК, поясняю: ни в какую очередь SendMessage не идет - это, считай, прямой вызов CallWindowProc.

Автор: dizzy1984 4.6.2008, 11:35
И в самом деле лопухнулся! Ведь знал же, что если SendMessage вызывается кодом процесса породившим окно-получатель, сообщение не ставится в очередь! Выходит, мой код ничем не лучше вызова процедуры из OnInitDialog. Ладно, тогда вот 3-я редакция. На этот раз с PostMessage :
Код

static const UINT messageAppInit = ::RegisterWindowMessage("3D5914BB-6689-4697-81F9-4DADA4A5964B");    
BEGIN_MESSAGE_MAP(CTest2Dlg, CDialog)    
//...     
ON_REGISTERED_MESSAGE(messageAppInit, Proc2)     
//...     
END_MESSAGE_MAP()     
BOOL CTest2Dlg::OnInitDialog()     
{     
    //...     
    PostMessage(messageAppInit, 0, 0);     
    return FALSE;  // return TRUE  unless you set the focus to a control     
}     
void CTest2Dlg::Proc2()     
{      
    return ;     
}

Поставил флажки afxTraceFlags = traceAppMsg | traceWinMsg - вроде бы уж последнее-распоследнее.
Здесь-то хоть не накосячил?

Автор: Earnest 4.6.2008, 14:29
Увы... smile  
В студии 7 и выше просто не скомпилируется, а в более ранних - весьма вероятны проблемы в релизе, т.к.
у обработчика должна быть другая сигнатура:
LRESULT CTest2Dlg::OnProc  (WPARAM, LPARAM);

И еще: использовать для имени зарегистрированного сообщения GUID, мягко говоря, неудобно. Смысл ведь в том, чтобы из разных мест можно было к одному сообщению обратиться, не перевязывая код общими хедерами... так что сообщение должно быть типа "Test2Dlg_InitMessage"... Но это мелочи


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