qt,win. Застывает окошко при перетаскивании.

Phoenix

Приложение ниже замирает(не происходит возврат управления из app.processEvents когда перетаскивается окошко/меняются размеры.
При этом, ничего похожего не наблюдается, например, с консолью (cmd).
Может есть какая-нибудь магическая функция?
 

# -*- coding:utf-8 -*-

from __future__ import with_statement

from PyQt4 import QtGui
from PyQt4.QtGui import QDialog,QApplication,QPalette,QColor, QLineEdit, QFont
from PyQt4.QtCore import *

import sys





class MyDlg(QtGui.QMainWindow):
def __init__(self):
super(MyDlg,self).__init__(None)


self.resize(524, 366)


self.setWindowTitle('CRASH TEST')




def main:
app = QApplication(sys.argv)

mainwnd = MyDlg
mainwnd.show
x=0
while 1:
app.processEvents(QEventLoop.AllEvents)
print 'event N%d' % x
x += 1

#app.exec_







if __name__ == "__main__":
main

Похоже проблема в QT.
Такой код тоже самое выдаёт без всякого питона.

#include <qapplication>
#include <qcoreapplication>
#include <qlabel>
#include <qtgui>
#include <qeventloop>

class MyDlg : public QMainWindow
{
public:
MyDlg
{
this->resize(500,200);
this->setWindowTitle("CRASH TEST");
};
};


int main(int argc, char *argv[])
{
QApplication app(argc, argv );
MyDlg *mywnd = new MyDlg ;

mywnd->show;

int i=0;

printf("it %d\n",i);
for (i=0;i<100000000; i++)
{
app.processEvents(QEventLoop::AllEvents);
printf("it %d\n",i);
}

delete mywnd;
return 0;

}


Phoenix

если сделать на C# - то такого нет.
моделька приложения: Есть timer. Который вызывает timeout каждую секунду, которая что-то пишет в консоль.

Dasar

моделька приложения: Есть timer. Который вызывает timeout каждую секунду, которая что-то пишет в консоль.
в коде у тебя чуть другое. в коде ты сам крутишь цикл сообщений - и есть вероятность, что делаешь это неправильно.

Dasar

кстати здесь: http://forum.vingrad.ru/forum/s/ca68de077284fb6c29cda9924b3a...
ты более правильно задаешь вопрос.
т.е. у тебя перестает вызываться printf, а не приложение замирает?
так это понятно, т.к. processEvents не возвращает управление пока не обработает все сообщения.
можно попробовать использовать processEvents с timeout-ом (например, в 100ms)
void QCoreApplication::processEvents ( QEventLoop::ProcessEventsFlags flags, int maxtime ) [static]
Данная перегруженная функция-член предоставлена для удобства. Ее поведение аналогично поведению вышеприведенной фунции.
Обрабатывает ожидающие сообщения в течение maxtime миллисекунд или меньше, если не больше событий.

Phoenix

ага. нашёл это. Уже попробовал - не то.
это другое. Это время на обработку всех сообщений. т.е. в очереди 200 сообщений, он обрабатывает либо пока 100мс не пройдёт, либо пока они не закончатся.

Dasar

т.е. в очереди 200 сообщений, он обрабатывает либо пока 100мс не пройдёт
а тебе разве не это надо?

Phoenix

есть вероятность, что делаешь это неправильно.

ждал этого :grin:
Специально сделал усилие и вспомнил с++ (изначально приложение на python/pyqt)
crashtest.cpp

#include <qapplication>
#include <qcoreapplication>
#include <qlabel>
#include <qtgui>
#include <qeventloop>
#include <qtimer>
#include "crashtest.h"



MyDlg::MyDlg
{
this->resize(500,200);
this->setWindowTitle("CRASH TEST");

this->i = 0;

this->qtimer = new QTimer;

this->qtimer->setInterval(100);

this->connect(this->qtimer, SIGNAL(timeout this, SLOT(timeout;
this->qtimer->start;
}


MyDlg::~MyDlg
{
delete this->qtimer;
};

void MyDlg::timeout
{
printf("itS %d\n",this->i);
this->i ++;

};
.
main.cpp

#include <qapplication>
#include <qcoreapplication>
#include <qlabel>
#include <qtgui>
#include <qeventloop>
#include <qtimer>
#include "crashtest.h"



int main(int argc, char *argv[])
{
QApplication app(argc, argv );
MyDlg *mywnd = new MyDlg ;


mywnd->show;

int i=0;

printf("it %d\n",i);
app.exec;



delete mywnd;
return 0;

}


crashtest.h

#include <qapplication>
#include <qcoreapplication>
#include <qlabel>
#include <qtgui>
#include <qeventloop>
#include <qtimer>




class MyDlg : public QMainWindow
{

Q_OBJECT

public:
int i;

QTimer *qtimer;


MyDlg;
~MyDlg;
public slots:
void timeout;

};

И вот так тоже не работало.
Но сейчас скомпилил - и заработало. :grin:
если заменить
exec на
 
for (i=0;i<100000000; i++)
{
app.processEvents(QEventLoop::AllEvents,1000);
printf("it %d\n",i);
}

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

Phoenix

а тебе разве не это надо?

сейчас понял, что оно и надо, но почему-то этот таймаут не пашет.

Phoenix

 
void QEventLoop::processEvents ( ProcessEventsFlags flags, int maxTime )
Process pending events that match flags for a maximum of maxTime milliseconds, or until there are no more events to process, whichever is shorter. This function is especially useful if you have a long running operation and want to show its progress without allowing user input, i.e. by using the ExcludeUserInputEvents flag.
Notes:
1)This function does not process events continuously; it returns after all available events are processed.
2)Specifying the WaitForMoreEvents flag makes no sense and will be ignored.

второе выделенное говорит о том, что функция не должна работать с новыми сообщениями, которые появлились после её запуска, я правильно понимаю? Тогда это точно баг.

Phoenix

глянул в исходники, оказалось, что processEvent с maxTime вызывает processEvent, пока не закончится время.



void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags, int maxtime)
{
[...]
QTime start;
start.start;
[...]

while (data->eventDispatcher->processEvents(flags & ~QEventLoop::WaitForMoreEvents {
if (start.elapsed > maxtime)
break;
[...]

}
}

Dasar

глянул в исходники, оказалось, что processEvent с maxTime вызывает processEvent, пока не закончится время.
с другими флагами вызывается
второе выделенное говорит о том, что функция не должна работать с новыми сообщениями, которые появлились после её запуска, я правильно понимаю? Тогда это точно баг.
и похоже как раз с такими, чтобы не было обработки новых сообщений

Phoenix

ненене :grin:
Он просто снимает флаг WaitForMoreEvents, который(см. ниже код) просто добавляет дополнительных итераций, если не было сообщений. а я изначально этот флаг не ставил.
обработчика выглядит так.
есть цикл do, который выполняется 1 раз, если не стоит WaitForMoreEvents, и может выполняться несколько, если этот флаг стоит.
внутри есть цикл while (!d->interrupt из которого походу и не выходит.
внутри этого цикла есть проверка некой очереди(в которой, видимо лежат эти спец.сообщения)
d->queuedUserInputEvents
и даже есть нет сообщений ( if (!haveMessage - он там что-то ждёт. (check for signalled objects)
всё это вызывает ощущение какого-то виндового костыля, чтобы исправить какой-то другой костыль.

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32);

if (!d->internalHwnd)
createInternalHwnd;

d->interrupt = false;
emit awake;

bool canWait;
bool retVal = false;
bool seenWM_QT_SENDPOSTEDEVENTS = false;
bool needWM_QT_SENDPOSTEDEVENTS = false;
do {
DWORD waitRet = 0;
HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];
QVarLengthArray<MSG> processedTimers;




while (!d->interrupt) {



DWORD nCount = d->winEventNotifierList.count;
Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);

MSG msg;
bool haveMessage;



if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty {
// process queued user input events
haveMessage = true;
msg = d->queuedUserInputEvents.takeFirst;



} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty {
// process queued socket events
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst;



} else {
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)
&& msg.message >= WM_KEYFIRST
&& msg.message <= WM_KEYLAST)
|| (msg.message >= WM_MOUSEFIRST
&& msg.message <= WM_MOUSELAST)
|| msg.message == WM_MOUSEWHEEL
|| msg.message == WM_MOUSEHWHEEL
|| msg.message == WM_CLOSE {
// queue user input events for later processing
haveMessage = false;
d->queuedUserInputEvents.append(msg);
}



if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd {
// queue socket events for later processing
haveMessage = false;
d->queuedSocketEvents.append(msg);
}
}




if (!haveMessage) {
// no message - check for signalled objects
for (int i=0; i<(int)nCount; i++)
pHandles[i] = d->winEventNotifierList.at(i)->handle;
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
if haveMessage = (waitRet == WAIT_OBJECT_0 + nCount {
// a new message has arrived, process it
continue;
}
}




if (haveMessage) {
#ifdef Q_OS_WINCE
// WinCE doesn't support hooks at all, so we have to call this by hand :(
(void) qt_GetMessageHook(0, PM_REMOVE, (LPARAM) &msg);
#endif




if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
if (seenWM_QT_SENDPOSTEDEVENTS) {
needWM_QT_SENDPOSTEDEVENTS = true;
continue;
}
seenWM_QT_SENDPOSTEDEVENTS = true;



} else if (msg.message == WM_TIMER) {
// avoid live-lock by keeping track of the timers we've already sent
bool found = false;
for (int i = 0; !found && i < processedTimers.count; ++i) {
const MSG processed = processedTimers.constData[i];
found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
}
if (found)
continue;
processedTimers.append(msg);



} else if (msg.message == WM_QUIT) {
if (QCoreApplication::instance
QCoreApplication::instance->quit;
return false;
}

if (!filterEvent(&msg {
TranslateMessage(&msg);
DispatchMessage(&msg);
}



} else if (waitRet >= WAIT_OBJECT_0 && waitRet < WAIT_OBJECT_0 + nCount) {
d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0;



} else {
// nothing todo so break
break;
}
retVal = true;
}





// still nothing - wait for message or signalled objects
canWait = (!retVal
&& !d->interrupt
&& (flags & QEventLoop::WaitForMoreEvents;



if (canWait) {
DWORD nCount = d->winEventNotifierList.count;
Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
for (int i=0; i<(int)nCount; i++)
pHandles[i] = d->winEventNotifierList.at(i)->handle;

emit aboutToBlock;
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE);
emit awake;
if (waitRet >= WAIT_OBJECT_0 && waitRet < WAIT_OBJECT_0 + nCount) {
d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0;
retVal = true;
}
}



} while (canWait);






if (!seenWM_QT_SENDPOSTEDEVENTS && (flags & QEventLoop::EventLoopExec) == 0) {
// when called "manually", always send posted events
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
}

if (needWM_QT_SENDPOSTEDEVENTS)
PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);

return retVal;
}


Вообще, мне это нужно, чтобы полу

Dasar

Он просто снимает флаг WaitForMoreEvents, который(см. ниже код) просто добавляет дополнительных итераций, если не было сообщений. а я изначально этот флаг не ставил.
угу.
я думал, что может AllEvents в себя включает флаг WaitForMoreEvents, но сейчас посмотрел - нет не включает.
значит ты прав, похоже на багу

Dasar

Идея была такая, чтобы поместить обработчик событй под некоторый мутекс. Но тогда необходимо, чтобы обработчик очень быстро выполнялся.
это обычно делают через invoke (запаковки функции в сообщение и отправке его в гуишный поток) - так подводных камней меньше, но код, конечно, намного более громоздкий получается.

Phoenix

так всё преимущество тредов съедается.
Не сложнее тогда сделать рабочий процес и управляющий gui-процесс. Зато демон теперь будет не зависить от gui и стабильнее работать.

kokoc88

Приложение ниже замирает(не происходит возврат управления из app.processEvents когда перетаскивается окошко/меняются размеры.
При этом, ничего похожего не наблюдается, например, с консолью (cmd).
Может быть, я не прав, тему полностью не читал, но...
В винде есть одна очень интересная особенность: при перетаскивании окна запускается обработчик событий, встроенный в недрах виндового GUI. Поэтому когда пишешь GUI/WIN32 программу, основанную на очереди сообщений, ты должен использовать механизм оконных callbacks вместо просмотра сообщений вручную. Иначе ты будешь терять эти сообщения.

Phoenix

другой процесс получает доступ к памяти моей проги? :crazy:
или что понимается по callback ?

kokoc88

или что понимается по callback ?
Обратный вызов. Оконная процедура, которая вызывается из недр DispatchMessage

Phoenix

Тогда всё нормально.
что мешает выйти из processEvents и вызвать dispatch при следующем вызове processEvents, а состояние где-нибудь запомнить?

kokoc88

что мешает выйти из processEvents и вызвать dispatch при следующем вызове processEvents, а состояние где-нибудь запомнить?
Читай мой пост внимательнее: внутренности WINAPI съедают все сообщения. С точки зрения WINAPI это выглядит примерно так:
1. очередь сообщений в QT вызывает DispatchMessage для начала перетаскивания окна
2. во внутренностях WINAPI запускается своя очередь сообщений, которая работает до тех пор, пока ты не перетащишь окно (тут твоя прога "замирает")
3. после того, как пользователь перестал перетаскивать окно, завершается работа очереди сообщений во внутренностях WINAPI и ты получаешь управление обратно
Таким образом, единственно верный и надёжный способ сделать что-то через очередь WINAPI сообщений - это использовать оконную процедуру, которая вызывается из DispatchMessage в любом случае.

Phoenix

Понял. Т.е. этот баг, который я думал, что в qt, на самом деле глубже, в самой dispatchmessage. Подстава.
#include <windows.h>
#include <stdio.h>

const char g_szClassName[] = "myWindowClass";

#define IDC_MAIN_EDIT 101

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{

case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
int i;


wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSHCOLOR_WINDOW+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = g_szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

if(!RegisterClassEx(&wc
{
MessageBox(NULL, "Window Registration Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}

hwnd = CreateWindowEx(
0,
g_szClassName,
"theForger's Tutorial Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 480, 320,
NULL, NULL, hInstance, NULL);

if(hwnd == NULL)
{
MessageBox(NULL, "Window Creation Failed!", "Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
printf("it_b %d\n", i);
DispatchMessage(&Msg);
printf("it_e %d\n", i);
i++;
}
return Msg.wParam;
}

при кликанье по меню есть it_b, но нет it_e.

kokoc88

Т.е. этот баг, который я думал, что в qt, на самом деле глубже, в самой dispatchmessage.
Скорее это у тебя баг. :) От незнания как работает WINAPI, чего там можно делать, а чего нельзя...

Phoenix

Ты считаешь что то поведение, что ты описал - это не баг? (Слово баг не нравится. Костыль тогда.)

kokoc88

Ты считаешь что то поведение, что ты описал - это не баг? (Слово баг не нравится. Костыль тогда.)
Это всё - документированное поведение WINAPI. Так что это не баг (неправильное поведение программы).
Ну а костыль-то тут при чём? :) Так обычно называют проблему, решённую каким-то некрасивым способом.

Phoenix

Так наверняка такое поведение было сделано, чтобы решить какую-то проблему.

kokoc88

Так наверняка такое поведение было сделано, чтобы решить какую-то проблему.
Возможно. Но это не кривой способ её решить.
Подумай, что будет с твоей программой, когда ты добавишь в свой GUI модальный диалог?
Оставить комментарий
Имя или ник:
Комментарий: