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

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

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Отрисовка поверх виджета  (Прочитано 18205 раз)
jasf
Гость
« : Ноябрь 05, 2009, 23:51 »

Здравствуйте. Хочется как-нибудь отрисоваться поверх виджета, при этом не вызывая функцию update() (repaint()) данного виджета. Т.е. вот у меня есть виджет с неким содержимым. Содержимое довольно тяжело прорисовывается. Но у меня есть координаты квадрата в этом виджете, который я хочу обновить.
Если отнаследоваться от этого виджета, переопределить paintEvent, сделать код типа
QMyWidget::needUpdateRect()
{
   redrawingRect = true;
   redraw();
   redrawingRect = false;
}
QMyWidget::paintEvent(QPaintEvent* event)
{
 if(redrawingRect) {
   redrawRect();
   return;
 }

 QTextEdit::paintEvent(event);
}

 - на некоторых платформах в QTextEdit начинаются артефакты  Если я всегда вызываю
 QTextEdit::paintEvent(event); в QTopMyWidget::paintEvent - никаких артефактов. Поэтому хотелось бы вообще обойти paintEvent. заранее спасибо за любые идеи.

P.S. этот способ с отнаследованием равносилен installEventFilter и перехват QEvent::Paint.
P.P.S. я думаю, что если бы я мог в начале функции needUpdateRect()  узнать, что виджет нуждается в перерисовке, и вызывать repaint().. артефакты бы прекратились. но мне неизвестен способ, как узнать, вызван ли для виджета update() и с какой областью (все эти данные расположены в d_func() ).
« Последнее редактирование: Ноябрь 06, 2009, 02:01 от jasf » Записан
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #1 : Ноябрь 06, 2009, 00:22 »

Рисование поверх дочерних виджетов подойдёт?

Gif-рисунки наложенные на окно:
Мужик и его подружка
« Последнее редактирование: Ноябрь 06, 2009, 00:25 от lit-uriy » Записан

Юра.
jasf
Гость
« Ответ #2 : Ноябрь 06, 2009, 01:12 »

Вот как раз-таки нет Грустный оно делает:
QApplication::sendEvent(obj, event); //заставляем ребенка себя нарисовать
а в QTextEdit без вызова paintEvent начинаются артефакты.. а вызывать его всегда, когда нужно обновить rect - слишком накладко.
Записан
jasf
Гость
« Ответ #3 : Ноябрь 06, 2009, 01:46 »

Я СЧАСТЛИВ!!!!  Веселый Веселый Веселый Проблема решена.
Ещё раз постановлю задачу: мне нужно отрисовывать нечто (например кадр gif файла) поверх QTextEdit. Для максимально-быстрого отображения при отрисовке не следует вызывать QTextEdit::paintEvent, а только лишь обновить фреймы гифок поверх отрисованного виджета. Но может такое случиться, что при вызове таймера обновления фрейма gifа - QTextEdit так же может нуждаться в перерисовке (ввод символа генерирует update(), но timerEvent получит первенство ), и т.к. при отображении фрейма .gifа QTextEdit::paintEvent не вызывается (для оптимизации) - как следствие получаем артефакт (QTextEdit должным образом не отрисовался).

проблема решается следующим способом:

class QWidgetBackingStorePublic
{
public:
    QWidget *tlw;
    QRegion dirtyOnScreen; // needsFlush
    QRegion dirty; // needsRepaint
    QRegion dirtyFromPreviousSync;
    QVector<QWidget *> dirtyWidgets;
    QVector<QWidget *> *dirtyOnScreenWidgets;
    QList<QWidget *> staticWidgets;
    QWindowSurface *windowSurface;
};


void QMyTextEdit::timerEvent(QTimerEvent* event) // частый таймер, в котором происходит отрисовка rect()ов на поверхность виджета
{
   QMyTextEdit* ppp = (QMyTextEdit*)viewport()->window(); // через window() можно получить доступ к QWidgetBackingStore
   void** pp = ((void**)&ppp->d_ptr); // получаем доступ к protected переменной QWidget
   QWidgetPrivate* p = (QWidgetPrivate*)*pp; /

   QTLWExtra* extra = p->maybeTopData();
   if(extra) {
      QWidgetBackingStorePublic* bs = (QWidgetBackingStorePublic*) extra->backingStore; // что-бы иметь доступ к private переменным класса QWidgetBackingStore
      if(bs) {
         if(bs->dirtyWidgets.contains(viewport())) { // в список dirtyWidgets помещаются указатели на виджеты, которые нуждаются в перерисовке. тем самым я узнаю, нужно ли перерисовать виджет, и если необходимо - перерисовываю.
            viewport()->repaint();
         }
      }
   }

   drawingFromTimer_ = true;
   viewport()->repaint();
   drawingFromTimer_ = false;
}

void QMyTextEdit::paintEvent(QPaintEvent *event)
{
   QPainter painter(viewport());
   
    if(drawingFromTimer_) {
       drawAnimatedIcons(painter);
       return;
    }
        QTextEdit::paintEvent(event);
}
« Последнее редактирование: Ноябрь 06, 2009, 01:50 от jasf » Записан
SABROG
Гость
« Ответ #4 : Ноябрь 06, 2009, 10:02 »

А можно подробней как это работает? Судя по коду весь этот хак нужен, чтобы получить доступ к backingStore, затем идет проверка на наличие в списке виджета и вызывается насильная перерисовка вьюпорта. А каким образом тогда идет отрисовка поверх виджета, часть кода отсутствует?
Записан
jasf
Гость
« Ответ #5 : Ноябрь 06, 2009, 12:42 »

нет нет. на viewport() внимание обращать не нужно. Пока я копал, обнаружил, что отрисовка текста (QTextObject) происходит как раз не в QTextEdit, а в QTextEdit::viewport(); т.е. хоть я и работаю с QTextEdit, но любую прорисовку нужно проводить в viewport(). видимо это особенность прорисовки в классах, отнаследованных от QAbstractScrollArea.

А код нужен для следющего:
я хочу отрисовываться поверх виджета по таймеру. Для этого есть код

void QMyTextEdit::timerEvent(QTimerEvent* event)
{
....
   drawingFromTimer_ = true;
   viewport()->repaint();
   drawingFromTimer_ = false;
}

void QMyTextEdit::paintEvent(QPaintEvent *event)
{
   QPainter painter(viewport());
  
    if(drawingFromTimer_) {
       drawAnimatedIcons(painter);
       return;
    }
}

в этом коде мы видим, что при отрисовке через таймер не вызывается QTextEdit::paintEvent(); Тем самым мы получаем чистый доступ к содержимому QTextEdit (ну нужно ещё установить viewport()->setAttrebute( Qt::OpaquePainterEvent), viewport()->setautoFillBackground(false); тогда уж точно ничего перерисовываться при отрисовке из таймера не будет.)

но, если в момент захода в функцию void QMyTextEdit::timerEvent(QTimerEvent* event)  QTextEdit нуждается в перерисовке, без проверки мы бы просто её (прорисовку) не произвели (при отрисовке из таймера QTextEdit::paintEvent не вызывается, но очищаются флаги, ответственные за информирование движка о надобности перерисовки).

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

прорисовка поверх виджета осуществляется посредством невызова QTextEdit::paintEvent в QMyTextEdit::paintEvent. Улыбающийся
« Последнее редактирование: Ноябрь 06, 2009, 12:46 от jasf » Записан
SABROG
Гость
« Ответ #6 : Ноябрь 06, 2009, 14:20 »

А можно компилябельный пример?
Записан
jasf
Гость
« Ответ #7 : Ноябрь 06, 2009, 15:12 »

Ну раз пример.. вот пример на основе QLabel. Правда здесь практически невозможно показать артефакт, о котором я говорил, (который по сути не возникает даже в QTextEdit под виндовс), но суть переносит.


#include <QtGui/QApplication>
#include <QObject>
#include <QLabel>
#include <QPainter>
#include <QEvent>
#include <..\src\gui\kernel\qwidget_p.h>
#include <..\src\gui\kernel\qevent.h>


class QWidgetBackingStorePublic
{
public:
   QWidget *tlw;
   QRegion dirtyOnScreen; // needsFlush
   QRegion dirty; // needsRepaint
   QRegion dirtyFromPreviousSync;
   QVector<QWidget *> dirtyWidgets;
   QVector<QWidget *> *dirtyOnScreenWidgets;
   QList<QWidget *> staticWidgets;
   QWindowSurface *windowSurface;
};

class QGetDPtr : public QObject
{
   Q_OBJECT;
public:
   void** getD_PtrAddr() { return(void**)&d_ptr; }
};

bool needUpdate(QWidget* widget)
{
   if(!widget) return false;
   QGetDPtr* ppp = (QGetDPtr*)widget->window();
   void** pp = ppp->getD_PtrAddr();
   QWidgetPrivate* p = (QWidgetPrivate*)*pp;

   QTLWExtra* extra = p->maybeTopData();
   if(extra) {
      QWidgetBackingStorePublic* bs = (QWidgetBackingStorePublic*) extra->backingStore;
      if(bs) {
         if(bs->dirtyWidgets.contains(widget)) {
            return true;
         }
      }
   }
   return false;
}

class QDrawer : public QObject
{
   Q_OBJECT;
public:
   QDrawer(QWidget* widget):QObject(0),widget_(widget)
   { widget_->installEventFilter(this); startTimer(50); drawOnWidget_ = false; counter = 0; }
   ~QDrawer(){}

public:
   virtual bool eventFilter(QObject *object, QEvent *event);
   virtual void timerEvent(QTimerEvent *e);
   void drawOnWidget();

private:
   QWidget*   widget_;
   bool      drawOnWidget_;
   int         counter;
};

void QDrawer::timerEvent(QTimerEvent *e)
{
   counter++;
   if(needUpdate(widget_)) {
      widget_->repaint();
   }
   drawOnWidget_ = true;


   bool isOpaquePainterEvent = widget_->testAttribute(Qt::WA_OpaquePaintEvent);
   bool isAutoFillBackground = widget_->autoFillBackground();
   widget_->setAttribute(Qt::WA_OpaquePaintEvent);
   widget_->setAutoFillBackground(false);
   widget_->repaint();
   if(!isOpaquePainterEvent)widget_->setAttribute(Qt::WA_OpaquePaintEvent,false);
   if(isAutoFillBackground)widget_->setAutoFillBackground(true);

   drawOnWidget_ = false;
}

bool QDrawer::eventFilter(QObject *object, QEvent *event)
{
   if(event->type()==QEvent::Paint) {
      if(drawOnWidget_) {
         drawOnWidget();
         return true;
      }
   }
   return QObject::eventFilter(object,event);
}


void QDrawer::drawOnWidget()
{
   QPainter painter(widget_);
   while(counter>=255/8)counter-=255/8;
   painter.fillRect( QRect(50,50,50,50), QColor(255,0+counter*8,0+counter* 8 ) );
}

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   QLabel label("Hello qt!");
   label.resize(240,320);
   label.show();

   QDrawer handler(&label);

   return a.exec();
}

#include "main.moc"

QDrawer::drawOnWidget - сюда по таймеру попадает управление для отрисовки квадратика. Если в этот же момент QLabel будет нуждаться в прорисовке,  код
   if(needUpdate(widget_)) {
      widget_->repaint();
   }
выполнит это.

[edit] - Убрал QMyLabel
« Последнее редактирование: Ноябрь 06, 2009, 15:24 от jasf » Записан
SABROG
Гость
« Ответ #8 : Ноябрь 06, 2009, 20:35 »

Поправь меня если я не прав. Таймер с периодичностью 20 раз в секунду сканирует приватный список виджетов, которые требуют перерисовки, при этом срабатывает таймер быстрей, чем приходит эвент QEvent::Paint. Вероятно потому, что никто не инициирует это событие, а виджеты сами не перерисовываются. Т.е. тут либо пользователь должен вызывать сам repaint(), либо это событие должна генерить ОС. Получается в последнем случае таймер может сработать, когда ОС уже спровоцировала обновление, но это не страшно потому, что отсутствие фрейма при 20 кадрах в секунду может быть не заметно, т.к. следующий же фрейм затрет его, если ОС не будет "издеваться" над окном постоянно его перерисовывая раньше таймера. При изменении размера главного окна этот недостаток виден.

Далее, чтобы изображение не затерлось при перерисовке устанавливается аттрибут Qt::WA_OpaquePaintEvent. Это позволяет нарисовать на готовом изображении что-то другое. Честно сказать я удивлен, что тебе удалось заставить работать этот аттрибут. Как я ни пытался его заставить работать раньше в моих первых опытах по оверпеинтингу, ничего не выходило.

Соответственно widget_->repaint(); тут играет роль QApplication::sendEvent(), т.к. пораждает QEvent::Paint, который мы удачно ловим в eventFilter и рисуем на не затертом виджете уже что-то своё.

Я правильно понял?

И главный вопрос. Возможно ли нарисовать этим методом что-то на родительском виджете таким образом, чтобы картинка перекрывала все дочерние виджеты?

Судя по всему нужно еще допиливать:

« Последнее редактирование: Ноябрь 06, 2009, 20:46 от SABROG » Записан
jasf
Гость
« Ответ #9 : Ноябрь 06, 2009, 21:55 »

Qt::WA_OpaquePaintEvent - я с ним познакомился ещё в первые дни изучения Qt Улыбающийся работает без проблем.

Поповоду
  if(needUpdate(widget_)) {
      widget_->repaint();
   }
- впринципе этот код можно удалить! т.к. он срабатывает крайне редко. Нельзя сказать, что я в таймере сканирую приватный список виджетов, т.к. это делается для исключения артефактов, а не для корректной работы отрисовки поверх виджета.

"Вероятно потому, что никто не инициирует это событие, а виджеты сами не перерисовываются." - нет. Постараюсь обьяснить. Рассмотрим код без if(needUpdate(widget_)){...

Код:
void QDrawer::timerEvent(QTimerEvent *e)
{
   counter++;

   drawOnWidget_ = true; /// очень важная переменная


   bool isOpaquePainterEvent = widget_->testAttribute(Qt::WA_OpaquePaintEvent);
   bool isAutoFillBackground = widget_->autoFillBackground();
   widget_->setAttribute(Qt::WA_OpaquePaintEvent);
   widget_->setAutoFillBackground(false);
   widget_->repaint();
   if(!isOpaquePainterEvent)widget_->setAttribute(Qt::WA_OpaquePaintEvent,false);
   if(isAutoFillBackground)widget_->setAutoFillBackground(true);

   drawOnWidget_ = false;   /// очень важная переменная
}

 К примеру обработка нажатия клавиши Enter в QTextEdit ведёт к вызову функции update(), которая помечает виджет как dirty. Для наглядности представим QTextEdit размерами 1280х1024 и весь заполненный текстом. Курсор вверху. Так же Enter смещает все строки на одну вниз, соответственно меняется содержание почти всего виджета. Но что будет, если сразу после нажатия на энтер сработает таймер (до того, как разошлётся эвент QEvent::Paint)? виджет нуждается в перерисовке, но прорисовка из таймера не вызывает QTextEdit::paintEvent(). Обработка таймера устанавливает аттрибуты, устанавливает drawOnWidget_ = true; , что говорит eventFilterу, что нужно просто отрисовать маленький (50х50) квадратик, и сделать return true; Если drawOnWidget_ == false, тогда не происходит ничего, а эвент рисования просто передаётся дальше. Переменная drawOnWidget_ разделяет просто прорисовку от прорисовки поверх виджета. Т.е. только что мы получили, что при нажатии на Энтер у нас никакие строки не сьехали вниз! И тем самым получился артефакт. Данный артефакт и решается проверкой if(needUpdate(widget_))...

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

для твоего применения нужно допиливать. У меня же применение ограничивается лишь одним виджетом (а не окном Улыбающийся ). Может что-нибудь и найду.

« Последнее редактирование: Ноябрь 06, 2009, 21:58 от jasf » Записан
jasf
Гость
« Ответ #10 : Ноябрь 06, 2009, 22:36 »

Ну вот к примеру рисовать совсем на экран можно вот так:

QPaintDevice* device = bs->windowSurface->paintDevice(); // bs - QWidgetBackingStore
if(device) {
   QPainter painter(device);
   painter.fillRect(QRect(0,0,30,30),Qt::yellow);
}
bs->windowSurface->flush(window(),QRegion(rect),QPoint(0,0));

но помоиму это неочень элегантный способ решения проблем. и любой QEvent::Paint может перерисовать нарисованный данным образом контент. Правда их можно перехватить Улыбающийся
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #11 : Ноябрь 06, 2009, 23:43 »

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

- виджет имеет свойство "прозрачность", его метод paint перекрыт - и только
- в перекрытом paint виджет сначала рендерит в QPixmap все что под ним и затем рисует себя, используя созданную QPixmap

Z-порядок известен, нужно добраться до MainWindow (через parent), взять список его children и рисовать в QPixmap каждого кто пересекается с нашим. Разумеется, это экспериментальная идея  Улыбающийся 
Записан
jasf
Гость
« Ответ #12 : Ноябрь 06, 2009, 23:54 »

2Igors: Не могу согласиться. Вот как раз моя реализация очень даже не тонкая. Там не используется прорисовка непосредственно на экран. Используется прорисовка в widget. При этом widget один. Нет необходимости отслеживать родителей или потомков. И всё очень даже чётко работает Улыбающийся
А более медленно, но совсём чётко - нет. 50 аним. обьектов в QTextEdit, вызывающие по 20раз в секунду перерисовку этого виджета - заставляют его тормазить даже под windows., а 50 отображений этих обьектов поверх виджета, не затрагивая QTextEdit::paintEvent - выдают и 100 фпс.
Записан
SABROG
Гость
« Ответ #13 : Ноябрь 07, 2009, 11:55 »

Через windowSurface действительно рисует, но точно также как и обычным образом, то есть картинку перекрывают дочерние виджеты.
Записан
jasf
Гость
« Ответ #14 : Ноябрь 07, 2009, 12:11 »

2SABROG: вот точно нет. У меня есть окно, в нём виджет, в нём лайоут, в нём верхний и нижний виджет. Так вот через сурфейс я спокойно отрисовался на весь экран, перекрыв все виджеты. Да и тут логика немного другая. Мы получаем указатель на window() - самый нижний виджет (к которому присвоено окно). Поэтому тут логики widgetов впринципе нет. Всё ограничено лишь окном.
и я видимо понял в чём проблема. изиняюсь. моя ошибка Улыбающийся

bs->windowSurface->flush(window(),QRegion(window()->rect()),QPoint(0,0));

нужно ведь обновить содержимое QPaintDevice окна на экран по всему контуру окна, а не только по ректанглу виджета.
« Последнее редактирование: Ноябрь 07, 2009, 12:13 от jasf » Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


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