Версия для печати темы

Нажмите сюда для просмотра этой темы в обычном формате

Форум на CrossPlatform.RU _ Qt GUI _ Помогите разобраться с отрисовкой виджета

Автор: XpycT 20.7.2009, 17:51

Всем доброго времени суток :)
С Qt знаком всего полторы недели, и то, все это время читал книги. Вот захотелось реализовать виджет выбора рисунка (не хочется делать просто текстовое поле с адресом). За основу взял http://qt.gitorious.org/qt-labs/graphics-dojo/trees/master/dragremote с гита Qt. Но к моему сожалению понял, что прочитанных разделов о графике в книгах не достаточно :(.

Хочется сделать что-то типа такого :



Но напоролся на пару вопросов:

1) Как в mouseMoveEvent определить потерю фокуса мыши (event->Leave почемуто действует аналогично event->Enter)?
2) Каким образом прикрутить сигнал/слот к иконке "Открыть"/"Очистить", если они выведены через painter.drawPicture

Вот те исходники, над которыми я прыгал с бубном  imagewidget.zip ( 2.48 килобайт ) : 159


За раннее благодарю за помощь :rolleyes:

Автор: kwisp 20.7.2009, 18:52

XpycT,

Цитата
void ImageWidget::paintEvent(QPaintEvent*)
{


    QPainter painter(this);
    QPainter painter1(this);
...


на сколько я знаю так делать нельзя
надо тчобы на одном контексте один рисовальщик был.

+

зачем тебе mouseMoveEvent(QMouseEvent *event)
если есть enterEvent(QEvent *event) && leaveEvent(QEvent *event)

Цитата(XpycT @ 20.7.2009, 18:51) *
2) Каким образом прикрутить сигнал/слот к иконке "Открыть"/"Очистить", если они выведены через painter.drawPicture

вашпе не понятно что ты хочешь спросить. что значит прикрутить сигнал/слот к иконке если иконка выведена с помощью painter.drawPicture ??

Автор: XpycT 20.7.2009, 20:44

Первый вопрос решен, спасибо.

Цитата(kwisp)
вашпе не понятно что ты хочешь спросить. что значит прикрутить сигнал/слот к иконке если иконка выведена с помощью painter.drawPicture ??


Да, слабо объяснил. Я имел в виду что в paintEvent'e я добавил две иконки - на открытие нового рисунка и для очистки.
CODE

QImage pic_add(":/images/image_add.png");
QImage pic_del(":/images/image_delete.png");

painter.drawImage(rect().width()-pic_add.width()-25,
rect().height()-35,pic_add);
painter.drawImage(rect().width()-pic_add.width()-5,
rect().height()-35,pic_del);

Просто не пойму как к этим иконкам прикрутить вызов слотов при клике на них :(

Автор: ufna 20.7.2009, 20:54

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

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

но про слоты и т.п. - первый абзац.

Автор: XpycT 20.7.2009, 22:07

Как я понимаю, надо ловить координаты мыши в mousePressEvent и сравнивать, попадают ли они в координаты рисунка-кнопки в paintEvent'a, если да - вызывать действие. Ну и аналогично при движении d mouseMoveEvent для изменения состояния кнопки.

Или я не прав, и есть более простой способ?

Автор: ufna 20.7.2009, 22:52

Да, именно так. Кстати, вызывать действие я советую только если пользователь не только нажал на кнопку мыши над этим рисунком-кнопкой, но и отпустил мышь на нем. Проверь - нормальные кнопки под винду именно так и работают. Т.е. в pressEvent ты ловишь нажал ли пользователь эту кнопку, сохраняешь себе в память данные об этом нажатии (как угодно, хоть булевой переменной и т.п.), а в releaseEvent смотришь, была ли кнопка нажата, затем проверяешь попал ли этот эвент на нужный прямоугольник, и если попал, то только тогда вызываешь действие.

а в маус муве то же самое - если кнопка уже нажата, то картинку не меняем. Если не нажата - меняем на "в фокусе" ,если мышка на картинке, а если не на картике - на "не в фокусе".

я пользую у себя такой класс на данный момент:

//-----------------------------------------------------------------------------
// File: a_cupics_layoutbutton.h
//
// Desc:
//-----------------------------------------------------------------------------

#ifndef A_CUPICS_LAYOUTBUTTON_H
#define A_CUPICS_LAYOUTBUTTON_H

#include <QObject>
#include <QPixmap>
#include <QRect>

//--------------------------------------------------------------------------------------
// Name: cUPicsLayoutButton
// Desc:
//--------------------------------------------------------------------------------------
class cUPicsLayoutButton
    : public QObject
{
    Q_OBJECT

public:
    // Constructor & destructor
    //----------------------------------------------------------------------------------
    cUPicsLayoutButton(QPixmap pxm, QObject *parent = 0);

    // Enums & others
    //----------------------------------------------------------------------------------
    enum State { Normal, Pressed, Hot };

    // Access functions
    //----------------------------------------------------------------------------------
    QPixmap getView() const;

    QRect rect() { return m_btnRect; }
    int state() const { return m_btnState; }

    // Control functions
    //----------------------------------------------------------------------------------
    void setRect(QRect rect) { m_btnRect = rect; }
    void setState(State s) { m_btnState = s; }
    void setPixmap(QPixmap pxm, State s = Normal);

private:
    // Page paint data
    //----------------------------------------------------------------------------------
    QRect m_btnRect;
    State m_btnState;

    QPixmap m_pxmNormal,
            m_pxmPressed,
            m_pxmHot;

};

Автор: XpycT 21.7.2009, 0:20

На счет опускания мыши даже не подумал.. надо будет учесть.

А вот на счет определения позиции мыши над кнопкой.
Гдето в исходниках вроде как видел определение самого верхнего рисунка слоя при клике на нем, но не помню толи для пеинтера толи для график виевера, и как на зло не могу вспомнить где :( . Строка была чтото вроде

qobject_cast<const тип*>(sender)

Можно ли как-то таким способом определить нарисованую кнопку, а то мое условие проверки не очень радует, да и к размеру виджета привязано. Ну или на худой конец хоть упростить его :)
void ImageWidget::mouseMoveEvent(QMouseEvent *event)
{
    mousePos = event->pos();
    if((mousePos.x() > (rect().width()-pic_add.width()-25)) &
       (mousePos.x() < (rect().width()-25))    &
       (mousePos.y() > (rect().height()-22))   &
       (mousePos.y() < (rect().height()-6)))
    {
        setCursor(Qt::PointingHandCursor);
        setButton(Open);
    }else if((mousePos.x() > (rect().width()-pic_del.width()-5)) &
       (mousePos.x() < (rect().width()-5))    &
       (mousePos.y() > (rect().height()-22))   &
       (mousePos.y() < (rect().height()-6)))
    {
        setCursor(Qt::PointingHandCursor);
        setButton(Clear);
    }else{        
        setCursor(Qt::ArrowCursor);
        setButton(None);
    }
}

Автор: Litkevich Yuriy 21.7.2009, 4:11

Цитата(XpycT @ 21.7.2009, 4:20) *
Можно ли как-то таким способом определить нарисованую кнопку,
нет, и называй вещи своими именами, тогда всё встанет на свои места. у тебя нет кнопки, у тебя есть рисунок в одной из частей которого, ты нарисовал, что-то ещё. Следовательно у тебя есть только координаты того, что ты нарисовал.
Можешь после рисования куда-нибудь сохранить прямоугольник (QRect), в котором было рисование, потом воспользоваться им, что бы проверять находится ли указатель мыши внутри этого прямоугольника или нет.

Автор: ufna 21.7.2009, 7:56

Да, лучше всего работать через QRect в данном случае. У него есть очень удобная функция для проверки принадлежности точки данному прямоугольнику.

Автор: XpycT 21.7.2009, 8:15

Спасибо за советы, думаю разобрался :)

Автор: XpycT 21.7.2009, 20:40

Благодаря вашей помощи появился мой первый виджет, собственно скрин в первом посте. Реализована загрузка рисунков через QFileDialog, очистка, drag-and-drop локальных и удаленных(скачиваются в временную директорию) рисунков и запоминание пути к нему.
Делал для формы заливки на сайт - может кому и пригодится. Правда хотелось бы сделать правное "выезжание" панельки, но к сожалению в Qt еще не силен :)

Собственно сам виджет :  imagewidget.zip ( 5.79 килобайт ) : 132


ЗЫ Хотел сделать плагин для дизайнера, откомпилил нормально... но при клике в панели виджетов вываливается крит. ошибка и приглашение в отладчик :(

Автор: ufna 21.7.2009, 21:05

Молодец что доделал :)

Виджет твой скачал, посмотрел. Только он не комплится в таком виде, пришлось комментарии кое где убрать :)

Если хочешь, могу очень нудно и досконально откомментировать, т.к. есть очень много мест, над которыми нужно работать :)

Автор: XpycT 21.7.2009, 21:21

Цитата(ufna @ 21.7.2009, 21:05) *
Молодец что доделал :)

Виджет твой скачал, посмотрел. Только он не комплится в таком виде, пришлось комментарии кое где убрать :)

Если хочешь, могу очень нудно и досконально откомментировать, т.к. есть очень много мест, над которыми нужно работать :)


Я только за, ведь как еще научиться, если не на своих ошибках :)

Автор: ufna 21.7.2009, 22:44

1. оформление кода. Представь, что у тебя будет не один такой файлик, а десятки, многие из которых в кучу раз длиннее. Как ты в нем ориентироваться будешь?
2. комментирование кода. Название функций, что делают, и т.д. и т.п.

Т.е. код должен быть хорошо оформлен, более читабелен. Это приходит с опытом и по началу кажется излишним, но - лучше начни уделять этому внимание сейчас, потом будет куда легче.

3. Не стоит формировать виджет в main функции. Пусть это будет отдельный класс. В мейне - просто создаем что надо и делаем основную настройку приложения.

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



Диалоговые окна из такого виджета - тоже зло. Все должно делаться через родитель, то окно, которое управляет данным виджетом. А этот виджет тогда можно было бы распространять, модифицировать под свои нужды и т.д. В таком же виде - он нужен только однажды, а переделывать.. проще написать заново, при таких размерах.

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

Я говорю именно о таких вот, виджетах специализированного использование, но которые не должны делать больше, чем.

А - так - молодец :) Работай дальше, изучай программирование и Qt, и опыт появится. Главное, самое главное - хорошо оформляй код. Делай его читаемым и структурированым. Это позволит видеть свои ошибки в дальнейшем куда быстрее :)

Автор: XpycT 22.7.2009, 8:48

Комментариев не хватает,согласен, просто надо привыкнуть всегда их ставить :)
На счет сигналов/слотов и ... если я тебя правильно понял, то допустим для открытия рисунка через диалог лучше в mousePressEvent просто вызвать сигнал, допустим emit imageOpen(const QString &imageName)?

Хотел еще спросить на счет drag-and-drop'a .Почему, когда я использую в dragEnterEvent

if(event->mimeData()->hasImage())

ну или на худой конец
if (event->mimeData()->hasFormat("image/jpeg") && ...)

он игнорирует рисунки. Ну или как можно ограничить рисунками, чтобы другие типы файлов были запрещены(курсор перечеркнутой мыши)? Просто у меня идет проверка только после "скидывания" файла, и то по расширению. :(

Автор: ufna 22.7.2009, 9:06

по сигналам - не совсем. Откуда у тебя взялось имя файла? При нажатии на кнопку ты получаешь желание его добавить изображение, т.е. можно выслать emit( imageDemand() ). А вот когда ты на виджет перетащил файл мышью из системы, то вот тогда - добавляешь на виджет, и - посылаешь emit( imageAdded(QString) ). Или можно слать указатель на картинку и т.п. :)


А про дрэг н дроп - в dragEnterEvent сделай вывод qDebug() << event->mimeData()->formats() :) Там увидишь какие форматы и что шлет тебе систем. Я на память не помню. Так когда из системы перетаскиваешь, будет не картика, а путь к ней, если не ошибаюсь.

Автор: XpycT 22.7.2009, 15:19

Что-то у меня со слотами не получается :( в виджете на клик по рисунку "открыть" поставил emit imageBrowse();. в форме приложения прикрутил сигнал, но после выбора рисунок не открывается и путь к нему пустой :( Хотя если диалог открытия в самом виджете - все работает. Делал так :
testApp.h

#ifndef TESTAPP_H
#define TESTAPP_H

#include <QDialog>
#include "ImageWidget.h"
class TestApp : public QDialog
{
    Q_OBJECT

public:
    TestApp(QWidget *parent=0);
private slots:
    void openImage();
private:
    ImageWidget screen1;
    ImageWidget screen2;
};

#endif // TESTAPP_H

testApp.cpp
#include <QtGui>
#include "testapp.h"
#include "ImageWidget.h"

TestApp::TestApp(QWidget *parent)
        :QDialog(parent)
{
    ImageWidget *screen1=new ImageWidget;    
    ImageWidget *screen2=new ImageWidget;  
    screen2->loadImage("D:\\21.jpg"); // <<< Если на прямую с конструктора - все работает прекрасно.
    QHBoxLayout *layout =new QHBoxLayout;
    layout->addWidget(screen1);
    layout->addWidget(screen2);
    setLayout(layout);

    connect(screen1,SIGNAL(imageBrowse()),this,SLOT(openImage()));

}
void TestApp::openImage()
{    
    QString fileName = QFileDialog::getOpenFileName(this,tr("Select Image"),
                                       QApplication::applicationDirPath(),
                                       tr("Image Files (*.jpg *.jpeg *.png *.bmp *.gif)"));

          if(fileName.isEmpty())
            return;
          else
              screen1.loadImage(fileName);
}


Подскажите где я ошибся :mellow:

Автор: kwisp 22.7.2009, 15:30

при чем тут слоты?
ты посмотри что написал то?

...
private:
    ImageWidget screen1;
    ImageWidget screen2;
...

а в конструкторе
...
ImageWidget *screen1=new ImageWidget;    
    ImageWidget *screen2=new ImageWidget;  
....


П.С.
самая распространенная ошибка, если не ошибаюсь, людей только что представленных С++. :)

Автор: ufna 22.7.2009, 15:32

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

Т.е. посмотри на мессадж выше и кое-что подправь.

Автор: kwisp 22.7.2009, 15:51

Цитата(ufna @ 22.7.2009, 16:32) *
Слот у тебя приватный, а присоединяешь его к сигналу, поступаемому от чужого класса, никак не связанного с данным. Сделай слот public.

категорически не согласен
сигналы вообще protected и нормально коннектятся.
пример с приватным слотом
Раскрывающийся текст
#ifndef _R_
#define _R_

#include <QObject>
#include <QtDebug>
#include <QtGlobal>

class Sender: public QObject
{
    Q_OBJECT
public:
        Sender(QObject* par=0):QObject(par){ startTimer(1000);}
signals:
        void sg();
protected:
    void timerEvent(QTimerEvent*)
    {
        emit sg();
    }
};

class Rec: public QObject
{
    Q_OBJECT
public:
        Rec(QObject* par=0):QObject(par)
        {
            
            sender__ = new Sender;
            connect(sender__,SIGNAL(sg()),this,SLOT(sl()));
        }
private slots:
    void sl(){ qDebug()<<__func__;}

private:
    Sender* sender__;
};

#endif //_R_


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

Автор: XpycT 22.7.2009, 16:40

Цитата(kwisp @ 22.7.2009, 15:30) *
самая распространенная ошибка, если не ошибаюсь, людей только что представленных С++. :)


На самом деле с С++ знаком не так давно. До этого сидел только на С#, но т.к. не особо радовало Mono для linux систем, решил перейти на Qt. :)

С сигналами всеравно не разобрался.. в дебаге вроде все прекрасно...после выбора рисунка нормально скалит, выводит зум и путь к рисунку.
Rescaling image.... 
[Zoom 113%] "C:\Documents and Settings\XpycT\Мои документы\404.jpg"
i_fileName = C:\Documents and Settings\XpycT\Мои документы\404.jpg

но рисунок не появляется, и следующий вывод i_fileName возвращает пустую строку.

Пришел к тому с чего начал - вернул диалог обратно в виджет, хоть так работает :unsure:

Автор: Litkevich Yuriy 22.7.2009, 16:52

Цитата(ufna @ 22.7.2009, 19:32) *
Слот у тебя приватный, а присоединяешь его к сигналу, поступаемому от чужого класса, никак не связанного с данным. Сделай слот public.
он соеденяет со своим (this) слотом, который этим (this) классом видится. А а сигнал виден всегда, если виден объект.

Цитата(kwisp @ 22.7.2009, 19:30) *
при чем тут слоты?
ты посмотри что написал то?
угу

Автор: kwisp 22.7.2009, 16:54

XpycT,

Цитата(XpycT @ 22.7.2009, 17:40) *
С сигналами всеравно не разобрался..

не понял тебя.
на всякий случай напишу.
ты сделал класс членами которого являются ImageWidget screen1,screen2. Заметь это не указатели а объекты.

при вызове конструктора класса они благополучно инициализируются в списке инициализации конструкторами по умолчанию(хоть ты их и не трогаешь явно с++ это делает за тебя)
за тем ты зачем не ясно объявляешь( и инициализируешь из кучи) в конструкторе локальные переменные указетели одноименные с твоими объектами класса ImageWidget* screen1, *screen2.

потом вообще весело ты соединяешь сигнал одного локально видимого динамически созданного обекта screen1 с каким то слотом(не важно каким) надеясь что вызовется сигнал.:)

вариантов поправить эту ошибку много
один из вариантов будет таким:
#include <QtGui>
#include "testapp.h"
#include "ImageWidget.h"

TestApp::TestApp(QWidget *parent)
        :QDialog(parent)
{
    //screen2->loadImage("D:\\21.jpg"); // <<< Если на прямую с конструктора - все работает прекрасно.
    QHBoxLayout *layout =new QHBoxLayout;
    layout->addWidget(&screen1);
    layout->addWidget(&screen2);
    setLayout(layout);

    connect(&screen1,SIGNAL(imageBrowse()),this,SLOT(openImage()));

}
void TestApp::openImage()
{    
    QString fileName = QFileDialog::getOpenFileName(this,tr("Select Image"),
                                       QApplication::applicationDirPath(),
                                       tr("Image Files (*.jpg *.jpeg *.png *.bmp *.gif)"));

          if(fileName.isEmpty())
            return;
          else
              screen1.loadImage(fileName);
}



пробуй теперь.
здесь я оставил объекты ImageWidget как есть а вообще тролли настоятельно рекомендуют создавать виджеты динамически.

Автор: ufna 22.7.2009, 16:54

Цитата
категорически не согласен
сигналы вообще protected и нормально коннектятся.
пример с приватным слотом


ты сам мне приводишь пример, где Sender - член класса Rec. В конструкторе же у Хруста создается новый объект, никак не связанный с изначальным классом: ImageWidget *screen1=new ImageWidget;
И соединяет он этот screen1, а не TestApp::screen1. И доступа к приватному слоту у этого объекта быть не должно.


дополнение:
про this понял

Автор: kwisp 22.7.2009, 16:58

ufna,
не пойму тебя

приведи свой пример.
вот мой модифицированный

Раскрывающийся текст
    #ifndef _R_
    #define _R_

    #include <QObject>
    #include <QtDebug>
    #include <QtGlobal>

    class Sender: public QObject
    {
        Q_OBJECT
    public:
            Sender(QObject* par=0):QObject(par){ startTimer(1000);}
    signals:
            void sg();
    protected:
        void timerEvent(QTimerEvent*)
        {
            emit sg();
        }
    };

    class Rec: public QObject
    {
        Q_OBJECT
    public:
            Rec(QObject* par=0):QObject(par)
            {
                
                Sender* sender__ = new Sender;
                connect(sender__,SIGNAL(sg()),this,SLOT(sl()));
            }
    private slots:
        void sl(){ qDebug()<<__func__;}
    };

    #endif //_R_

или так
Раскрывающийся текст

    #ifndef _R_
    #define _R_

    #include <QObject>
    #include <QtDebug>
    #include <QtGlobal>

    class Sender: public QObject
    {
        Q_OBJECT
    public:
            Sender(QObject* par=0):QObject(par){ startTimer(1000);}
    signals:
            void sg();
    protected:
        void timerEvent(QTimerEvent*)
        {
            emit sg();
        }
    };

    class Rec: public QObject
    {
        Q_OBJECT
    public:
            Rec(QObject* par=0):QObject(par)
            {
                
//                Sender* sender__ = new Sender;
//                connect(sender__,SIGNAL(sg()),this,SLOT(sl()));
            }
    Sender* CreateSender(){return new Sender;};
    private slots:
        void sl(){ qDebug()<<__func__;}
    };

    #endif //_R_
////
файл main.cpp
////
#include <QApplication>
#include "rec.h"
int main(int a,char** b)
{
    QApplication app(a,b);
    Rec rec;
    Sender* sender__ = rec.CreateSender();
    QObject::connect(sender__,SIGNAL(sg()),&rec,SLOT(sl()));
    return app.exec();
}


что теперь скажете?

П.С.
что то я сегодня плохо понимаю.
пойду на пляж в волейбол играть.

Автор: ufna 22.7.2009, 17:15

kwisp, сорри, тупил. После поста Юрия в доки глянул, понял.

Как то всегда руководствовался правилом - private slot - для внутренних объектов, public slot - для внешних. Изначально как понял, так и делал. Блин )

Автор: kwisp 22.7.2009, 17:23

ufna,
принимается.
со всяким бывает.

Автор: ufna 22.7.2009, 17:24

Цитата(XpycT @ 22.7.2009, 17:40) *
Пришел к тому с чего начал - вернул диалог обратно в виджет, хоть так работает :unsure:


Лучше доразберись. Изначально поправь конструктор. Потом если все так и не идет, дай код, может еще куда что нить забралось.

Автор: XpycT 22.7.2009, 17:52

твою ж... точно не правильно виджеты создал. Вот что значит невнимательность. :( Спасибо все работает.

Вот только хотел спросить , как правельнее создать одинаковые QObject::connect'ы перебором, если у меня допустим их штук 20. Ведь как я понимаю - не правильно будет их дублировать типа

connect(screen1,SIGNAL(imageBrowse()),this,SLOT(openImage()));
    connect(screen...,SIGNAL(imageBrowse()),this,SLOT(openImage()));
    connect(screen20,SIGNAL(imageBrowse()),this,SLOT(openImage()));

видел макросы типа qobject_cast, qvariant_cast... но не совсем понял как ими пользоваться

Автор: Litkevich Yuriy 22.7.2009, 18:16

XpycT, для случая разных отправителей, лучше используй "карту сигналов" QSignalMaper, я где-то пример на форуме давал, да и в Асистенте он описан достаточно.

П.С. или "отображатель сигналов", или "перенаправитель сигналов", или "коллектор сигналов" не знаю как лучше сказать

Автор: XpycT 22.7.2009, 18:43

Цитата(Litkevich Yuriy @ 22.7.2009, 18:16) *
XpycT, для случая разных отправителей, лучше используй "карту сигналов" QSignalMaper...

Спасибо, добавил в закладки .

Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)