I'm recently new to the c++ world. My background is mainly in python and some application specific scripting languages. Anywho, I wanted to get some general feedback and converting my subclass of QLabel written in pyside and convert it to work with a C++ application in Qt Creator. I'm not looking for someone to do the entire project for me. I just need some guidance on how to add/setup a custom control in my Qt project.
You'll notice in my subclass I've simply just overwritten the paint event of the label in order to create the dotted pattern to fill the empty space to the right of the label as seen here:
Code for my custom label in Pyside:
class QLabelHeader(QtWidgets.QLabel):
def __init__(self, parent=None, **kwargs):
super(QLabelHeader, self).__init__(parent)
# events
def paintEvent(self, event):
# calculate font width
metrics = QtGui.QFontMetrics(self.font())
text_width = metrics.boundingRect(self.text()).width()
# calculate dimensions
y = int(self.height() * 0.5 - 2)
x = text_width + 4
width = self.width() - x
# create pattern
px = QtGui.QPixmap(4,4)
px.fill(QtCore.Qt.transparent)
pattern_painter = QtGui.QPainter(px)
pattern_painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
pattern_painter.setBrush(QtGui.QBrush(QtGui.QColor(200,200,200), QtCore.Qt.SolidPattern))
pattern_painter.drawRect(0,0,1,1)
pattern_painter.drawRect(2,2,1,1)
pattern_painter.end()
# draw tiled pixmap
painter = QtGui.QPainter(self)
painter.drawTiledPixmap(x,y,width,5,px)
painter.end()
super(QLabelHeader, self).paintEvent(event)
Question:
I've seen other sample projects online that include custom controls with a folder structure like seen here, which is a subfolder within a larger project. How do they create this folder structure and it's set of files?
As an added bonus if anyone feels like showing me a preview/psuedo code of what my h and cpp file would look like for overriding the QLabel's paint event like my pyside code.
Solution
Add a new class to your project in Qt creator going to File->New File or Project->C++->C++ Class. Make the class inherit from QWidget from the drop-down menu. Select a subfolder and give the class a name
Note: As a piece of advise, do not name your classes with Q prefix, as it might lead to a confusion.
In the header substitute the base class and the include with QLabel
Right click the name of the base class and select Refactor->Insert Virtual Functions of Base Classes and add paintEvent
Right click paintEvent and select Refactor->Add Definition in xxx.cpp
Go to the definition and put your code there
The translation should be pretty much straightforward, once you know the syntax of both languages.
Example
To help you with the process, I have prepared an example of how your code could be translated into C++:
LabelHeader.h:
#ifndef LABELHEADER_H
#define LABELHEADER_H
#include <QLabel>
class LabelHeader : public QLabel
{
Q_OBJECT
public:
explicit LabelHeader(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
};
#endif // LABELHEADER_H
LabelHeader.cpp:
#include "LabelHeader.h"
#include <QPainter>
LabelHeader::LabelHeader(QWidget *parent) :
QLabel(parent)
{
}
void LabelHeader::paintEvent(QPaintEvent *event)
{
// calculate font width
QFontMetrics metrics(font());
int text_width = metrics.boundingRect(text()).width();
// calculate dimensions
int y = height() * 0.5 - 2;
int x = text_width + 4;
int w = width() - x;
// create pattern
QPixmap px(4, 4);
px.fill(Qt::transparent);
QPainter pattern_painter(&px);
pattern_painter.setPen(Qt::NoPen);
pattern_painter.setBrush(QBrush(QColor(200, 200, 200), Qt::SolidPattern));
pattern_painter.drawRect(0, 0, 1, 1);
pattern_painter.drawRect(2, 2, 1, 1);
// pattern_painter.end();
// draw tiled pixmap
QPainter painter(this);
painter.drawTiledPixmap(x, y, w, 5, px);
// painter.end();
QLabel::paintEvent(event);
}
Note: I also took the liberty to revise the code after it has been translated and omit or commented parts, which are not necessary.
The full code of the example is available on GitHub.
Result
When used in the provided example application, this class produces a similar result:
Related
I'm developing a Qt Application and I'm trying to find a way to use QTextEdit as a label with long text without the scroll bar. In my ui I have a QScrollArea and inside of it I want to place a couple off QTextEdit widgets and I only want use scrolling inside QScrollArea. Problem is that no matter how I try to resize the QTextEdit it seems it has a maximum height and cuts of text, even if I set the size manually and QTextEdit::size returns the correct value.
I did the same thing with QLabel and it works fine, but in this case I need some methods that are only provided in QTextEdit.
I found this post:
Resizing QT's QTextEdit to Match Text Height: maximumViewportSize()
And the answer given was the following:
I have solved this issue. There were 2 things that I had to do to get
it to work:
Walk up the widget hierarchy and make sure all the size policies made
sense to ensure that if any child widget wanted to be big/small, then
the parent widget would want to be the same thing.
This is the main
source of the fix. It turns out that since the QTextEdit is inside a
QFrame that is the main widget in a QScrollArea, the QScrollArea has a
constraint that it will not resize the internal widget unless the
"widgetResizable" property is true. The documentation for that is
here: http://doc.qt.io/qt-4.8/qscrollarea.html#widgetResizable-prop.
The documentation was not clear to me until I played around with this
setting and got it to work. From the docs, it seems that this property
only deals with times where the main scroll area wants to resize a
widget (i.e. from parent to child). It actually means that if the main
widget in the scroll area wants to ever resize (i.e. child to parent),
then this setting has to be set to true. So, the moral of the story is
that the QTextEdit code was correct in overriding sizeHint, but the
QScrollArea was ignoring the value returned from the main frame's
sizeHint.
The problem is that I have no idea how to access the QTextEdit's QScrollArea to enable widgetResizable. Can anyone explain how I can achieve this or suggest a different way of resizing QTextEdit to perfectly fit it's content?
This will allow the height of the text box to change as required. You can edit the code a little to handle the width as well.
connect( m_textField, SIGNAL( textChanged() ), this, SLOT( onTextChanged() ) );
void MyClass::onTextChanged()
{
QSize size = m_textField->document()->size().toSize();
m_textField->setFixedHeight( size.height() + 3 );
}
Try this one :
QTextEdit textEdit;
textEdit.setHtml("<p>test test test test test test</p><p>|||||||||</p>");
textEdit.show();
textEdit.setFixedWidth(textEdit.document()->idealWidth() +
textEdit.contentsMargins().left() +
textEdit.contentsMargins().right());
Without a concrete example it's difficult to judge, but... it sounds as if you simply want a QTextEdit whose sizeHint depends on the current document size.
class text_edit: public QTextEdit {
using super = QTextEdit;
public:
explicit text_edit (QWidget *parent = nullptr)
: super(parent)
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
virtual QSize sizeHint () const override
{
QSize s(document()->size().toSize());
/*
* Make sure width and height have `usable' values.
*/
s.rwidth() = std::max(100, s.width());
s.rheight() = std::max(100, s.height());
return(s);
}
protected:
virtual void resizeEvent (QResizeEvent *event) override
{
/*
* If the widget has been resized then the size hint will
* also have changed. Call updateGeometry to make sure
* any layouts are notified of the change.
*/
updateGeometry();
super::resizeEvent(event);
}
};
Then use as...
QScrollArea sa;
sa.setWidgetResizable(true);
text_edit te;
te.setPlainText(...);
sa.setWidget(&te);
sa.show();
It appears to work as expected in the few tests I've done.
In ui i defined QTextEdit *textEdit object. I write it as height scalable-content :
int count = 0;
QString str = "";
// set textEdit text
ui->textEdit->setText("hfdsf\ncsad\nfsc\dajkjkjkjhhkdkca\n925");
str = ui->textEdit->toPlainText();
for(int i = 0;i < str.length();i++)
if(str.at(i).cell() == '\n')
count++;
// resize textEdit (width and height)
ui->textEdit->resize(ui->textEdit->fontMetrics().width("this is the max-length line in qlabel")
, ui->textEdit->fontMetrics().height() * (count + 2));
Notice : this work if you change QTextEdit font face or size! just in height scalable (before every thing set your QTextEdit frameShape to BOX).
if you want do width scalable-content, you should do these steps :
read QTextEdit(textEdit object) text as line to line
calculate every line length
select maximum of line length
use of QTextEdit::fontMetrics().width(QString str) for investigate str size in width
I hope this can help you...
A QTreeView is rendered with the help of a custom QStyledItemDelegate::paint method. The intention is to add graphical elements to the nodes, e.g. to draw (and fill) a box around the item texts. The tree items may have check boxes, or not.
The Ruby code below achieves the goal, except that I cannot obtain the coordinates of the text element. An empirical offset (x=29; y=4) serves as a workaround. The super method draws the text on top of the box.
How can I obtain the coordinates of the text element?
Is this the right approach at all, or do I have to use drawText and drawControl instead of calling the superclass paint method? In that case, how do you control the layout of the sub elements?
(This question is not Ruby specific. Answers containing C++ are welcome.)
class ItemDelegate < Qt::StyledItemDelegate
def paint(painter, option, index)
text = index.data.toString
bg_color = Qt::Color.new(Qt::yellow)
fg_color = Qt::Color.new(Qt::black)
offset = Qt::Point.new(29,4)
painter.save
painter.translate(option.rect.topLeft + offset)
recti = Qt::Rect.new(0, 0, option.rect.width, option.rect.height)
rectf = Qt::RectF.new(recti)
margin = 4
bounding = painter.boundingRect(rectf, Qt::AlignLeft, text)
tbox = Qt::RectF.new(Qt::PointF.new(-margin,0), bounding.size)
tbox.width += 2*margin
painter.fillRect(tbox, bg_color)
painter.drawRect(tbox)
painter.restore
super
end
end
Edit: Please find a self-contained example here in this Gist.
I had the same problem in C++. Unfortunately, the workaround on option.rect.* properties seems to be the only way to find the text coords.
Here the paint method of my delegate:
void ThumbnailDelegate::paint(QPainter *p_painter, const QStyleOptionViewItem &p_option, const QModelIndex &p_index) const
{
if(p_index.isValid())
{
const QAbstractItemModel* l_model = p_index.model();
QPen l_text_pen(Qt::darkGray);
QBrush l_brush(Qt::black, Qt::SolidPattern);
/** background rect **/
QPen l_pen;
l_pen.setStyle(Qt::SolidLine);
l_pen.setWidth(4);
l_pen.setBrush(Qt::lightGray);
l_pen.setCapStyle(Qt::RoundCap);
l_pen.setJoinStyle(Qt::RoundJoin);
p_painter->setPen(l_pen);
QRect l_border_rect;
l_border_rect.setX(p_option.rect.x() + 5);
l_border_rect.setY(p_option.rect.y() + 5);
l_border_rect.setWidth(p_option.rect.width() - 16);
l_border_rect.setHeight(p_option.rect.height() - 16);
QPainterPath l_rounded_rect;
l_rounded_rect.addRect(QRectF(l_border_rect));
p_painter->setClipPath(l_rounded_rect);
/** background color for hovered items **/
p_painter->fillPath(l_rounded_rect, l_brush);
p_painter->drawPath(l_rounded_rect);
/** image **/
QPixmap l_pixmap = bytearrayToPixmap(l_model->data(p_index, ImageRole).toByteArray()).scaled(150, 150, Qt::KeepAspectRatio);
QRect l_img_rect = l_border_rect;
int l_img_x = (l_img_rect.width()/2 - l_pixmap.width()/2)+l_img_rect.x();
l_img_rect.setX(l_img_x);
l_img_rect.setY(l_img_rect.y() + 12);
l_img_rect.setWidth(l_pixmap.width());
l_img_rect.setHeight(l_pixmap.height());
p_painter->drawPixmap(l_img_rect, l_pixmap);
/** label **/
QRect l_txt_rect = p_option.rect;
l_txt_rect.setX(l_border_rect.x()+5);
l_txt_rect.setY(l_border_rect.y() + l_border_rect.height() -20);
l_txt_rect.setHeight(20);
l_txt_rect.setWidth(l_txt_rect.width()-20);
QFont l_font;
l_font.setBold(true);
l_font.setPixelSize(12);
p_painter->setFont(l_font);
p_painter->setPen(l_text_pen);
QString l_text = l_model->data(p_index, TextRole).toString();
p_painter->drawText(l_txt_rect, Qt::ElideRight|Qt::AlignHCenter, l_text);
}
else
{
qWarning() << "ThumbnailDelegate::paint() Invalid index!";
}
}
I am not skilled on Ruby but, as you can see, I am using drawPath, drawPixmap and drawText.
Here is the result:
I think it is better to avoid invoking paint from the superclass, since it should be done automatically by Qt and you may break something on the UI lifecycle.
This is the standard Color Dialog box from Qt5
Is it possible to view the basic colors alone and remove the color gradient from the dialog?
I found simple solution of this problem. It is not a removing, but with my code, we cannot see this gradient and cannot use it.
We need create subclass. Let's code:
mycolordialog.h
#ifndef MYCOLORDIALOG_H
#define MYCOLORDIALOG_H
#include <QColorDialog>
#include <QLabel>
class MyColorDialog : public QColorDialog
{
Q_OBJECT
public:
explicit MyColorDialog(QWidget *parent = 0);
signals:
public slots:
};
#endif // MYCOLORDIALOG_H
mycolordialog.cpp
#include "mycolordialog.h"
MyColorDialog::MyColorDialog(QWidget *parent) :
QColorDialog(parent)
{
QLabel * l = new QLabel("Teeeeext",this);
l->setGeometry(245,5,325,215);//this values control the area and position of label
//you can change this values and remove another area of main dialog window
QPixmap pixmap("G:/2/qt.jpg");
l->setPixmap(pixmap.scaled(325,215,Qt::IgnoreAspectRatio));;//resize our picture
l->show();
}
How to use it???
#include "mycolordialog.h"
//...
void MainWindow::on_pushButton_16_clicked()
{
MyColorDialog cd;
cd.exec();
qDebug() << cd.selectedColor();
}
You can set in label beautiful logo of your app, or something another. I use logo from here http://reichertbrothers.com/images/qt-logo.png , but I convert it into jpg format.
What we get???
Notice, that all another area is available and you can choose any color and work as you want, but this gradient window is remove!!
I hope it helps.
The options that you can select to modify the look of the color dialog are in the QColorDialog documentation.
enum ColorDialogOption {
ShowAlphaChannel = 0x00000001,
NoButtons = 0x00000002,
DontUseNativeDialog = 0x00000004
};
This options do not hide the color picker, therefore you will have to implement your custom color picker.
I am trying to make a scroll area hold a widget which will serve as a drawing area for a drag-and-drop editor I am trying to build. However, I can't seem to get it to draw.
Here is a picture: http://i.imgur.com/rTBjg.png
On the right the black space is what is supposed to be my scrollarea
Here is the constructor for my the window class (I am using Qt-Creator):
ModelWindow::ModelWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ModelWindow)
{
ui->setupUi(this);
editor = new ModelEditorWidget(this);
ui->scrollArea->setWidget(editor);
}
The model editor widget looks like so:
//header
class ModelEditorWidget : public QWidget
{
Q_OBJECT
public:
explicit ModelEditorWidget(QWidget *parent = 0);
signals:
public slots:
protected:
virtual void paintEvent(QPaintEvent *e);
};
//.cpp file:
ModelEditorWidget::ModelEditorWidget(QWidget *parent) :
QWidget(parent)
{
this->setAcceptDrops(true);
this->resize(1000, 1000);
cout << this->rect().x() << " " << this->rect().width() << endl;
this->update();
}
void ModelEditorWidget::paintEvent(QPaintEvent *e)
{
cout << "painting";
QWidget::paintEvent(e);
QPainter painter(this);
painter.setBrush(QBrush(Qt::green));
painter.setPen(QPen(Qt::red));
painter.drawRect(400, 400, 50, 50);
painter.fillRect(e->rect(), Qt::SolidPattern);
}
I would think that this would set the modeleditorwidget's size to be 1000x1000 and then draw a green or red rectangle on the widget. However, the lack of a "painting" message in the command line from the cout at the beginning of the paintEvent shows that it isn't being executed. I first suspected this is because there was 0 width and 0 height on the widget. However, the cout in the constructor tells me that the widget is positioned at x = 0 and width = 1000 and so I assume that since that matches my resize statement that the height is also 1000 as was specified.
EDIT: By calling cout.flush() I got the "painting" output. However, that only deepens the mystery since the paint event doesn't look like it is actually painting. I am now calling show on both the scroll area and the widget as well.
Does anyone see what I could be doing wrong here? Perhaps I am not adding the ModelEditorWidget to the scrollarea properly?
Btw, I am very new to Qt and this is my first major GUI project using it. Most of my other GUI stuff was done using .NET stuff in C#, but since I want this to be cross platform I decided to stay away from C#.NET and mono and use Qt.
Documentation of QScrollArea::setWidget() says:
If the scroll area is visible when the widget is added, you must
show() it explicitly.
Note that You must add the layout of widget before you call this
function; if you add it later, the widget will not be visible -
regardless of when you show() the scroll area. In this case, you can
also not show() the widget later.
Have you tried that?
I have a custom class derived from QGraphicsView that implements a slot call scrollHorizontal(int dx), inside the code is simply
void CustomView::scrollHorizontal(int dx){
scrollContentsBy(dx, 0);
}
My problem is, scrolling like this works but doesn't update the scene properly, instead any pixels found on the edge of the view are repeated instead of having a fresh call to the item's paint() method.
I've attempted calling update() after, but nothing happens. I tried enabling scrolling by dragging and updates work fine! But I need it done programmatically, and since I have the scroll bars hidden things like horizontalScrollBar()->setValue() do not scroll the view.
I also tried :
scrollContentsBy(dx, 0);
this->scene()->invalidate(sceneRect());
this->update();
update:
QPointF center = mapToScene(viewport()->rect().center());
centerOn(center.x() - dx, center.y());
update();
is working, but now my top view is scrolling slower than my bottom view, which is a new problem. They are linked with signals and slots, in the bottom view i have scrollContentsBy(int dx, int dy) overrided to emit horizontalScroll(dx); which is caught by the above slot in the top view.
Any ideas why the scrolls happen at different rates? Might it have something to do with the scroll bars being a part of the bottom view effectively making it a "smaller" window?
update 2:
The different scroll rates seems to stem from some rounding happening to give me an integer based "center" using mapToScene(viewport()->rect().center()); , as you scroll and the slower you scroll the more this error adds up, the faster you scroll the less total error.
Is there a way for me to get around this? I don't see any way to get a floating point center point.
update 3:
So I have this mostly solved, turns out the mapToScene was needed(code I found elsewhere on the web).
I fixed this by storing QPointF of FP calculated center of the viewport, now the amount of error when scrolling the two views is unnoticeable.
The final issue is, the views no longer line up when you scroll ANY amount to the right, and then resize the window then scroll again. I assume this has something to do with the logical ordering of when the center point is calculated and when the centering happens.
Right now I use the following code snippet in QGraphicsScene::ResizeEvent() and elsewhere that updates the center as needed
QRectF viewPort(viewport()->rect());
QPointF rectCenter((viewPort.x() + viewPort.x() + viewPort.width())/2.0, (viewPort.y() + viewPort.y() + viewPort.height())/2.0);
viewCenter = rectCenter;
and my horizontalScroll(int dx) slot
void CustomView::horizontalScroll(int dx)
{
viewCenter.setX(viewCenter.x() - dx);
centerOn(viewCenter.x(), viewCenter.y());
update();
}
How can I fix the issue when re-sizing the window breaking the alignment of the two views? If any more clarification is needed please just ask, I can try to give skeletons of what I'm referring to if need be.
Update 4:
Rough code Skeleton
Class HeaderView:
class HeaderView View : public QGraphicsView
{
Q_OBJECT
public:
HeaderView(QWidget * parent = 0);
HeaderView(QGraphicsScene * scene, QWidget * parent = 0);
private:
QPointF viewCenter;
protected:
void resizeEvent ( QResizeEvent * event );
public slots:
void horizontalScroll(int);
void addModel(qreal, qreal, const QString&);
};
HeaderView.cpp
void HeaderView::resizeEvent(QResizeEvent *event)
{
QGraphicsView::resizeEvent(event);
QRectF viewPort(viewport()->rect());
QPointF rectCenter((viewPort.x() + viewPort.x() + viewPort.width())/2.0, (viewPort.y() + viewPort.y() + viewPort.height())/2.0);
viewCenter = rectCenter;
}
void HeaderView::horizontalScroll(int dx)
{
viewCenter.setX(viewCenter.x() - dx);
centerOn(viewCenter.x(), viewCenter.y());
update();
}
Class EventView:
class EventView : public QGraphicsView
{
Q_OBJECT
public:
EventView(QWidget * parent = 0);
EventView(QGraphicsScene * scene, QWidget * parent = 0);
QRectF visibleRect();
protected:
void scrollContentsBy ( int dx, int dy );
signals:
void horizontalScroll(int);
};
EventView.cpp
void EventView::scrollContentsBy(int dx, int dy)
{
QGraphicsView::scrollContentsBy(dx, dy);
if(dx != 0){
emit horizontalScroll(dx);
}
}
Somwhere in Class MainWindow:
connect(eventView, SIGNAL(horizontalScroll(int)), headerView, SLOT(horizontalScroll(int));
I've worked with QGraphicsView in Qt 4.6.3 - 4.7.2 and have to argue that you can use the respective QScrollBar in the following way:
//graphics view initialization
QGraphicsView *graphicsView = new QGraphicsView(parent);
QGraphicsScene *scene = new QGraphicsScene(0,0,widthOfScene,heightOfScene,parent);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
graphicsView->setScene(scene);
//in another method
QScrollBar* yPos=graphicsView->verticalScrollBar();
yPos->setValue((int) newValue);
It does not matter if they are hidden or not. They will still respond to setValue(int) as long as you have a graphics scene that is larger than the graphics view.
The QGraphicsView will also respond to ensureVisible, which moves the scrollbars to the appropriate location.
You are not supposed to call scrollContentsBy as explained here: http://qt-project.org/doc/qt-4.8/qabstractscrollarea.html#scrollContentsBy
I don't know if you can still call the hidden scrollbar to scroll it. If not, translate is an option.
Did you try to use the scroll bars? Hiding them doesn't make them non-existent, and the documentation says you should use QScrollBar::setValue to scroll to a given position.
Another option would be to use QGraphicsView::centerOn(QPointF) in conjunction with the current center point -- as you've also tried -- but directly calculating the center point within your method (do not precalculate and store the center point), by using QGraphicsView::mapToScene(int,int):
void CustomView::horizontalScroll(int dx)
{
QPointF viewCenter = mapToScene(width() / 2, height() / 2);
viewCenter += QPointF(dx, 0); // Why did you subtract instead of add dx?
centerOn(viewCenter); // BTW, you don't need to do .x(), .y()
// You can remove update(); as this is already called in centerOn().
}
Please note that if you have, as you said, "scrollContentsBy(int dx, int dy) overrided to emit horizontalScroll(dx)", you also have to call the super class method so that the view can scroll itself:
void CustomView::scrollContentsBy(int dx, int dy)
{
emit horizontalScrolled(dx); // (You should call it different than the slot!)
QGraphicsView::scrollContentsBy(dx, dy); // <-- This is what I mean!
}