I have a QMap with a custom class as key but when I try to insert elements with different keys, sometimes the map replace another element.
In my case the problem is this:
#include <QCoreApplication>
#include <QMap>
#include <QDebug>
class A
{
public:
A(int value, const QString& string) { m_value = value; m_string = string; }
bool operator<(const A& other) const { return m_value < other.m_value && m_string < other.m_string; }
private:
int m_value;
QString m_string;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QMap<A, QString> map;
map.insert(A(10, "ONE"), "FIRST");
map.insert(A(10, "TWO"), "SECOND");
map.insert(A(20, "THREE"), "THIRD");
return 0;
}
If you run this code, you will note that "FIRST" element and "SECOND" are mixed up.
The map result like that:
Am i doing something wrong?
There is an error in your definition of the operator<. It actually short-circuits at the first condition if the condition is false and the second condition just doesn't matter.
Be very careful with this, it is considered UB to provide wrong realization to a container.
But in your case there is a problem only with the keys. According to your code A(10, "ONE") and A(10, "TWO") are the same key. So everything works the following way:
map.insert(A(10, "ONE"), "FIRST"); // insert first element
map.insert(A(10, "TWO"), "SECOND"); // put value `SECOND` in the found key
map.insert(A(20, "THREE"), "THIRD"); // put second element
And you've got a mixed up keys in the map of a wrong size.
Try this:
bool operator<(const A& other) const {
if (m_value < other.m_value) return true;
return m_string < other.m_string;
}
Related
I have a requirement where I want my QLineEdit should accept hexadecimal values ranging from [0 - FFFFF]. can someone help me out with this?
I have tried the below code, but it holds good for only 1 char display.
I don't see any code in your post, but what you're looking for is a validator, derived from the QValidator class. Create a sub-class, say HexValidator, and implement the "validate" method. You check the input string for the allowed characters and range and return the appropriate state.
You then assign the validator to the QLineEdit using the QLineEdit::setValidator method. Note that the QLineEdit doesn't take ownership of the validator, so you need to make sure you delete it separately or give it a parent so that it gets cleaned up when the parent is deleted. You can create a single validator and assign it to multiple fields if needed.
I was wrong in my comment about Qt docs having a Hex spin box example... so I found one in my archive instead. It could be more flexible, but OTOH it's very simple and short. I know this isn't a QLineEdit but maybe it'll help anyway. The QValidator used here could be used in a line edit also.
#ifndef _HEXSPINBOX_H_
#define _HEXSPINBOX_H_
#include <QSpinBox>
#include <QRegularExpressionValidator>
// NOTE: Since QSpinBox uses int as the storage type, the effective editing range
// is +/- 0x7FFF FFFF, so it can't handle a full unsigned int.
// QDoubleSpinBox would be a more suitable base class if a wider range is needed.
class HexSpinBox : public QSpinBox
{
Q_OBJECT
public:
HexSpinBox(QWidget *parent = nullptr,
bool showPrefix = false,
const QString &format = QStringLiteral("%x")) :
QSpinBox(parent),
format(format)
{
// Validates hex strings up to 8 chars with or w/out leading "0x" prefix.
// For arbitrary prefix/suffix, the regex could be built dynamically
// in validate(), or override setPrefix()/setSuffix() methods.
const QRegularExpression rx("(?:0[xX])?[0-9A-Fa-f]{1,8}");
validator = new QRegularExpressionValidator(rx, this);
setShowPrefix(showPrefix);
}
public slots:
void setShowPrefix(bool show)
{
if (show)
setPrefix(QStringLiteral("0x"));
else
setPrefix(QString());
}
void setFormat(const QString &text)
{
format = text;
lineEdit()->setText(textFromValue(value()));
}
protected:
QValidator::State validate(QString &text, int &pos) const override
{
return validator->validate(text, pos);
}
int valueFromText(const QString &text) const override
{
return text.toInt(0, 16);
}
QString textFromValue(int value) const override
{
return QString().sprintf(qPrintable(format), value);
}
private:
QRegularExpressionValidator *validator;
QString format;
};
#endif // _HEXSPINBOX_H_
Example:
#include "HexSpinBox.h"
#include <QApplication>
#include <QBoxLayout>
#include <QDialog>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QDialog d;
d.setLayout(new QVBoxLayout);
HexSpinBox* sb = new HexSpinBox(&d);
sb->setMaximum(0xFFFFF);
sb->setValue(0x0A234);
d.layout()->addWidget(sb);
HexSpinBox* sb2 = new HexSpinBox(&d, true, QStringLiteral("%05X"));
sb2->setMaximum(0xFFFFF);
sb2->setValue(0x0A234);
d.layout()->addWidget(sb2);
return d.exec();
}
read folder tree from a Rest API, then show them to user
Example json response after call API:
[
{"name":"/folder1/file1.txt";"size":"1KB"},
{"name":"/folder1/file2.txt";"size":"1KB"},
{"name":"/folder1/sub/file3.txt";"size":"1KB"},
{"name":"/folder2/file4.txt";"size":"1KB"},
{"name":"/folder2/file5.txt";"size":"1KB"}
]
I only want to make GUI like below image:
There are 2 options:
QTreeView
QTreeWidget
In this photo, I used QTreeWidget (with static data).
Currently, I don't know make data model for this.
I made TreeModel to set data for QtreeView.
But When them shown in GUI, All of the data only show in column Name.
I copied the code from http://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html
But now I can't resolve this problem. I need an example source code for this.
Plus, I also only want show a tree view simply.
don't use QFileSystem because data get from Rest API.
What you have to do is parsing the json by getting the name of the file and the size, then you separate the name using the "/", and add it to the model in an appropriate way.
#include <QApplication>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QStandardItemModel>
#include <QTreeView>
#include <QFileIconProvider>
QStandardItem * findChilItem(QStandardItem *it, const QString & text){
if(!it->hasChildren())
return nullptr;
for(int i=0; i< it->rowCount(); i++){
if(it->child(i)->text() == text)
return it->child(i);
}
return nullptr;
}
static void appendToModel(QStandardItemModel *model, const QStringList & list, const QString & size){
QStandardItem *parent = model->invisibleRootItem();
QFileIconProvider provider;
for(QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
{
QStandardItem *item = findChilItem(parent, *it);
if(item){
parent = item;
continue;
}
item = new QStandardItem(*it);
if(std::next(it) == list.end()){
item->setIcon(provider.icon(QFileIconProvider::File));
parent->appendRow({item, new QStandardItem(size)});
}
else{
item->setIcon(provider.icon(QFileIconProvider::Folder));
parent->appendRow(item);
}
parent = item;
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QStandardItemModel model;
model.setHorizontalHeaderLabels({"Name", "Size"});
const std::string json = R"([
{"name":"/folder1/file1.txt";"size":"1KB"},
{"name":"/folder1/file2.txt";"size":"1KB"},
{"name":"/folder1/sub/file3.txt";"size":"1KB"},
{"name":"/folder2/file4.txt";"size":"1KB"},
{"name":"/folder2/file5.txt";"size":"1KB"}
])";
QJsonParseError parse;
// The string is not a valid json, the separator must be a comma
// and not a semicolon, which is why it is being replaced
QByteArray data = QByteArray::fromStdString(json).replace(";", ",");
QJsonDocument const& jdoc = QJsonDocument::fromJson(data, &parse);
Q_ASSERT(parse.error == QJsonParseError::NoError);
if(jdoc.isArray()){
for(const QJsonValue &element : jdoc.array() ){
QJsonObject obj = element.toObject();
QString name = obj["name"].toString();
QString size = obj["size"].toString();
appendToModel(&model, name.split("/", QString::SkipEmptyParts), size);
}
}
QTreeView view;
view.setModel(&model);
view.show();
return a.exec();
}
Note: the semicolon is not a valid separator for the json, so I had to change it.
For my serialization method i need to store a QList<T> where T is my custom Type, in a QVariantList.
QList<T> l;
l.append(T());
QVariant var = QVariant::fromValue(l);
var.canConvert(QVariant::List); // returns true
//So i can easily iterate over the variant with sth like this:
QVariantList list;
QSequentialIterable it = var.value<QSequentialIterable>();
for (const QVariant &v : it)
list << v;
/* deserialization side */
var = list;
var.value<QList<T>>(); //returns an empty list which is not my serialized list;
My problem is that i cannot convert back the variant list into QList<T>
EDIT:
#define PROPERTY(type, name) \
Q_PROPERTY(type name MEMBER name) \
type name;
class Measurement
{
Q_GADGET
public:
PROPERTY(int, index)
PROPERTY(QString, name)
PROPERTY(QString, unit)
PROPERTY(double, factor)
PROPERTY(bool, isVisible)
PROPERTY(quint8, decimal)
bool operator ==(const Measurement &other)
{
return (this->index == other.index);
}
};
you can consider this class as my custom type (T). i also save the class name (here "Measurement") along with serialized data for furthur uses, because as you know we can get the registered type with QMetaType::type(char*) but with that type i can only construct a QVariant with QVariant(int typeId, const void *copy) but here i want to construct the QList<Measurement> itself.
You will need to deserialize the QVariant list one item at a time. I am also not sure that this line:
var = list;
is performing what you intended. It will take your QVariantList list and wrap it inside another QVariant called var, which is of type QVariant(QVariantList, (QVariant(MyType, ), QVariant(MyType, ))). There doesn't seem to be much benefit to doing this.
Nonetheless, the example below shows a way to recover the list from var.
#include <QCoreApplication>
#include <QVariant>
class MyType {
public:
MyType() {}
MyType(QString value) { m_value = value; }
QString m_value;
};
Q_DECLARE_METATYPE(MyType)
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<MyType> l;
l.append(MyType("foo"));
l.append(MyType("bar"));
QVariant var = QVariant::fromValue(l);
var.canConvert(QVariant::List); // returns true
//So i can easily iterate over the variant with sth like this:
QVariantList list;
QSequentialIterable it = var.value<QSequentialIterable>();
for (const QVariant &v : it)
list << v;
/* deserialization side */
var = list;
QList<MyType> deserializedList;
foreach(QVariant v, var.value<QVariantList>()) {
deserializedList << v.value<MyType>();
}
return a.exec();
}
I am developing a GUI for a SQLite database in Qt5. I use QSqlQueryModel and QTableView for storing and displaying the data.
I then created a custom delegate to replace the numeric values of certain columns with their literals in the table view (e.g. 1 = "Hello", 2 = "World") using a switch statement.
The delegate displays the data as it should and works functionally. However, the columns that the custom delegate paints over have a different format compared to the default paint method of QStyledItemDelegate. The values are up in the top left rather than centre left, the altered column no longer automatically expands the column to display the full values, and the cells in column do not turn blue or have the dotted outline when selected.
I created this example program:
#include <QApplication>
#include <QModelIndex>
#include <QPainter>
#include <QStandardItemModel>
#include <QStyledItemDelegate>
#include <QTableView>
class TestDelegate: public QStyledItemDelegate {
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
const Q_DECL_OVERRIDE
{
if (index.column() == 0) {
int value = index.model()->data(index, Qt::DisplayRole).toInt();
QString str;
switch (value) {
case 1:
str = "Hello0000";
break;
case 2:
str = "World0000";
break;
}
if (option.state.testFlag (QStyle::State_Selected)) {
painter->fillRect(option.rect, option.palette.highlight());
qApp->style()->drawItemText(painter, option.rect, option.displayAlignment, option.palette, true, str, QPalette::HighlightedText);
} else {
painter->drawText(option.rect, option.displayAlignment, str);
}
} else {
return QStyledItemDelegate::paint(painter, option, index);
}
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
QStandardItemModel model(2, 2);
model.setHorizontalHeaderItem(0, new QStandardItem(QString("A")));
model.setHorizontalHeaderItem(1, new QStandardItem(QString("B")));
model.setData(model.index(0, 0, QModelIndex()), 1);
model.setData(model.index(1, 0, QModelIndex()), 2);
model.setItem(0, 1, new QStandardItem(QString("Hello")));
model.setItem(1, 1, new QStandardItem(QString("World0000")));
QTableView view;
view.setItemDelegate(new TestDelegate);
view.setModel(&model);
view.resizeColumnsToContents();
view.show();
app.exec();
}
This fixes the text alignment by adding options.displayAlignment to painter->drawText(); I have also added additional code in the if(option.state & QStyle::State_Selected) statement that paints the cell according to its selection state. So if it isn't selected the text is black, if it is the text turns white and the background blue. However I still cannot get the columns to expand to fit the cells' content or add a dotted line around the outside of the cell as it does with the standard delegate.
Is there a simple way to maintain the default style of the table view when using my custom paint method?
The delegate is a rather circuitous and unnecessary way of going about it. We already have a view that paints the elements perfectly fine, no need to redo that. We only need to pass modified data to the view. Thus we insert a QIdentityProxyModel viewmodel between the source and the view.
// https://github.com/KubaO/stackoverflown/tree/master/questions/proxy-reformat-39244309
#include <QtWidgets>
class RewriteProxy : public QIdentityProxyModel {
QMap<QVariant, QVariant> m_read, m_write;
int m_column;
public:
RewriteProxy(int column, QObject * parent = nullptr) :
QIdentityProxyModel{parent}, m_column{column} {}
void addReadMapping(const QVariant & from, const QVariant & to) {
m_read.insert(from, to);
m_write.insert(to, from);
}
QVariant data(const QModelIndex & index, int role) const override {
auto val = QIdentityProxyModel::data(index, role);
if (index.column() != m_column) return val;
auto it = m_read.find(val);
return it != m_read.end() ? it.value() : val;
}
bool setData(const QModelIndex & index, const QVariant & value, int role) override {
auto val = value;
if (index.column() == m_column) {
auto it = m_write.find(value);
if (it != m_write.end()) val = it.value();
}
return QIdentityProxyModel::setData(index, val, role);
}
};
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QStandardItemModel model{2,2};
model.setData(model.index(0, 0), 1);
model.setData(model.index(1, 0), 2);
model.setData(model.index(0, 1), "Zaphod");
model.setData(model.index(1, 1), "Beeblebrox");
RewriteProxy proxy{0};
proxy.setSourceModel(&model);
proxy.addReadMapping(1, "Hello");
proxy.addReadMapping(2, "World");
QTableView ui;
ui.setModel(&proxy);
ui.show();
return app.exec();
}
In my application, I want to disable some items (i.e. not selectable, no highlights when mouse hovering above, and the texts are greyed out) in the QComboBox when certain conditions are met.
I indeed found someone did ask the same question here: Disable Item in Qt Combobox
But none of these solutions in the answers seem to actually work (including the trick).
Is there a decent and 'correct' way to implement this?
EDIT:
I found out why setting the flags wouldn't disable the items in my application: for some reasons, I had to set the style QStyle::SH_ComboBox_UseNativePopup(see https://codereview.qt-project.org/#/c/82718/). And this setting for some reasons blocked the flag setting. Does anyone have an idea why, and how to work around? A minimum test example is included (modified from the answer of #Mike):
#include <QApplication>
#include <QComboBox>
#include <QStandardItemModel>
#include <QProxyStyle>
class ComboBoxStyle : public QProxyStyle
{
public:
int styleHint ( StyleHint hint, const QStyleOption * option = 0, const QWidget * widget = 0, QStyleHintReturn * returnData = 0 ) const override
{
if ( hint == QStyle::SH_ComboBox_UseNativePopup )
{
return 1;
}
return QProxyStyle::styleHint( hint, option, widget, returnData );
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QComboBox comboBox;
// Setting this style would block the flag settings later on.
comboBox.setStyle( new ComboBoxStyle() );
comboBox.insertItem(0, QObject::tr("item1"));
comboBox.insertItem(1, QObject::tr("item2"));
QStandardItemModel* model = qobject_cast<QStandardItemModel*>(comboBox.model());
QStandardItem* item= model->item(1);
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
comboBox.show();
return a.exec();
}
The answer linked in my comment above seems to be talking about an old version of Qt. I have tested on Qt5.4 and Qt5.6 and there is no need set the color yourself here, you just need to set and/or clear the Qt::ItemIsEnabled flag, here is an example:
#include <QtWidgets>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QComboBox comboBox;
comboBox.addItem(QObject::tr("item1"));
comboBox.addItem(QObject::tr("item2"));
comboBox.addItem(QObject::tr("item3"));
QStandardItemModel *model =
qobject_cast<QStandardItemModel *>(comboBox.model());
Q_ASSERT(model != nullptr);
bool disabled = true;
QStandardItem *item = model->item(2);
item->setFlags(disabled ? item->flags() & ~Qt::ItemIsEnabled
: item->flags() | Qt::ItemIsEnabled);
comboBox.show();
return a.exec();
}
Here is the technique #Mike describes, wrapped up in a convenient utility function:
void SetComboBoxItemEnabled(QComboBox * comboBox, int index, bool enabled)
{
auto * model = qobject_cast<QStandardItemModel*>(comboBox->model());
assert(model);
if(!model) return;
auto * item = model->item(index);
assert(item);
if(!item) return;
item->setEnabled(enabled);
}