Russian Qt Forum
Май 03, 2024, 02:29 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: How To: перехват закрытия приложения на Маке  (Прочитано 11059 раз)
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« : Февраль 02, 2012, 15:40 »

При закрытии приложения на маке из меню в доке Qt сразу же вызывает QCoreApplication::quit(), не давая возможности спросить пользователя или отменить закрытие приложения.
При наличии в приложении открытых window-modal окон, созданных на стеке, есть вероятность краш при закрытии приложения. Это связано с тем, что при стандартном разрушении объектов (удалении то-левел окон) будет сначала разрушаться топ-левел окно, оно будет удалять модальный диалог, для к-ого является родителем, а потом тот будет повторно разрушаться на стеке при выходе из exec().
К счастью, при закрытии приложения из дока, Qt посылает QCloseEvent QApplication'у и его можно перехватить. Ниже привожу пример перехвата и установку фокуса на одно из активных модальных окон при попытке закрытия приложения.
Код:
class Application : public QApplication
{
    Q_OBJECT
public:
    explicit Application(int & argc, char ** argv);

    static bool tryQuit();

protected:
    bool event(QEvent *e);
};

bool Application::tryQuit()
{
    QList<QWidget *> widgets = qApp->allWidgets();
    foreach (QWidget *widget, widgets) {
        QDialog *dialog = qobject_cast<QDialog*>(widget);
        if (dialog) {
            if (dialog->windowModality() == Qt::WindowModal && dialog->isVisible()) {
                dialog->raise();
                dialog->activateWindow();
                dialog->focusWidget()->setFocus();
                qApp->beep();
                return false;
            }
        }
    }
    return true;
}

bool Application::event(QEvent *e)
{
    if (e->type() == QEvent::Close) {
        if (tryQuit())
            e->accept();
        else
            e->ignore();
        return true;
    }
    return QApplication::event(e);
}
Записан
Akon
Гость
« Ответ #1 : Февраль 02, 2012, 21:03 »

имхо, некоторые вещи в Qt крупно гранулированны, например, QWidget - на редкость жирен. Например, множеству виджетов не нужен фокус ввода, им не нужны системные ресурсы и функционал, связанный с обработкой фокуса.

QWidget::parent имеет две обязанности: владение (т.е. удаляет детей) и отрисовку (детей). В некоторых либах эти обязанности разделяются на owner (владение) и parent (отрисовка). При таком подходе в случае с модальным стековым диалогом у него был бы только parent - и никаких проблем.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #2 : Февраль 03, 2012, 12:51 »

Диалоги и топлевелы это скорее исключение из правила - большая часть виджетов имеет и овнера и парента.
Записан
Akon
Гость
« Ответ #3 : Февраль 03, 2012, 13:25 »

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

Ну и если посмотреть снизу, то QObject::isWidgetType() (ну нах. на уровне QObject знать чего то там о виджетах) как раз костыть для разруливания двух обязанностей (овнера и парента) в одном поле.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #4 : Февраль 03, 2012, 14:42 »

QObject::isWidgetType нужен тупо для того, что он работает гораздо быстрее, чем каст к виджету. Явот ни разу не сталкивался с тем, что чайлдвиджеты должны удаляться после парента.
И проблема не в том, что виндоу модал диалог удаляется после парент окна, а в том, как это решить правильно для юзера - он не имеет право выключать приложение, имея модальные окна (например вопрос о том, нужно ли сохранять документ, к-ый задается при выходе).
Записан
Akon
Гость
« Ответ #5 : Февраль 03, 2012, 15:37 »

Цитировать
QObject::isWidgetType нужен тупо для того, что он работает гораздо быстрее, чем каст к виджету.
Я имел ввиду не реализацию этого метода, а контекст использования. Почему бы не сделать по аналогии QWidget::isPushButtonType()?  А потому что начиная с QWidget::parent() паренту для виджета добавляется обязанность отрисовки, т.е. парент должен быть виджетом, и для этого QWidget добавляет setParent(QWidget*). Т.е. имеет место сокрытие метода.

Код:
QObject parent;
QObject* child = new QWidget;
child->setParent(&parent);  // вызывается QObject::setParent() и ошибка будет только в рантайме при помощи QObject::isWidgetType

Соответственно при разделении на овнер и парент такой костыль был бы не нужен: овнер - это QObject, парент - это QWidget, и ошибка в компайл-тайм.

Цитировать
Явот ни разу не сталкивался с тем, что чайлдвиджеты должны удаляться после парента.
Да я тохе не сталкивался, но если подумать, то типичный пример - сменный декоратор.

Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #6 : Февраль 03, 2012, 16:25 »

Я имел ввиду не реализацию этого метода, а контекст использования. Почему бы не сделать по аналогии QWidget::isPushButtonType()?  А потому что начиная с QWidget::parent() паренту для виджета добавляется обязанность отрисовки, т.е. парент должен быть виджетом, и для этого QWidget добавляет setParent(QWidget*). Т.е. имеет место сокрытие метода.


Еще раз, QObject::isWidgetType нужен чтобы повысить производительность кода.
Код:
if (object->isWidgetType()) {
    QWidget *w = static_cast<QWidget*>(w);
    ...
}
Польностью эквивалентно
Код:
QWidget *w = qobject_cast<QWidget*>(object);
if (w) {
    ...
}
Вам очевидно не приходилось вешать эвент фильтр на куаппликейшн с целью перехвата событий виджетов. Там каждая милисекунда важна, тк событый проходят через фильтр тысячи. И тогда, и только тогда этот метод нужен, когда каст слишком затратен.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #7 : Февраль 03, 2012, 19:01 »

Статья/урок по делу, попытка конструктивной критики:

- причем здесь Mac? Судя по изложению - проблема на любой платформе

- trуQuit сделано на соплях, не хватает солидности. А если один модал испустил другой, так что, хватать первый? Или нет вообще модалов, так что, потеряем последние изменения? Где вообще (virtual) CanClose() ?

По поводу приведения (isWidgetType и.т п.) - разумно обсудить в отдельной теме, здесь это только мешает
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #8 : Февраль 03, 2012, 20:56 »

Статья/урок по делу, попытка конструктивной критики:

- причем здесь Mac? Судя по изложению - проблема на любой платформе

- trуQuit сделано на соплях, не хватает солидности. А если один модал испустил другой, так что, хватать первый? Или нет вообще модалов, так что, потеряем последние изменения? Где вообще (virtual) CanClose() ?
Вся соль тут не в переборе модалов (я думаю с этим справятся все) а в том, как отследить на маке попытку закрыть приложение из дока. Это не описано в доках и помогло только курение сорцов. Вместо tryQuit() может быть любой пользовательский код. И в общем случае, его нужно звать из нескольких мест (например из клоз евента глав окна), не только из перехвата.
Если один модал открыл другой, то первым в списке будет второй (так устроен allWidgets - он хранит виджеты в порядке, обратным порядку создания).
Вся соль тут в этом куске кода:
Код:
bool Application::event(QEvent *e)
{
    if (e->type() == QEvent::Close) {
        if (...)
            e->accept();
        else
            e->ignore();
        return true;
    }
    return QApplication::event(e);
}
а не в том, что я делаю в перехватчике.
Записан
Akon
Гость
« Ответ #9 : Февраль 03, 2012, 21:04 »

Цитировать
Еще раз, QObject::isWidgetType нужен чтобы повысить производительность кода.
Вам очевидно не приходилось вешать эвент фильтр на куаппликейшн с целью перехвата событий виджетов. Там каждая милисекунда важна, тк событый проходят через фильтр тысячи. И тогда, и только тогда этот метод нужен, когда каст слишком затратен.
Да, согласен. Использование этого метода в setParent() непринципиальный момент.

Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Февраль 04, 2012, 05:56 »

А почему не решить это через QWidget::closeEvent (virtual), или он здесь не приходит?
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #11 : Февраль 04, 2012, 10:15 »

Куда он будет приходить, если окон нет?
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.051 секунд. Запросов: 23.