QTreeView and ScrollArea size - qt

Great Greetings.
How can I find out the size of the scroll area for a QTreeView?
I can get the size of the viewport, but I cannot get the size of the area over which this viewport moves. My thanks for any help!

This was my recommendation per comments:
You could retrieve the QScrollBar::maximum() of QTreeView::horizontalScrollBar() and QTreeView::verticalScrollBar(). (I guess the minimum() is 0 in both cases.)
and
It might be necessary to add the view width/height. (Sorry, I didn't check in detail - just tried to give a possible direction to search into.)
As the OP had doubts about that recommendation, I made an MCVE to demonstrate my intention.
testQTreeWidgetContentsSize.cc:
// standard C++ header:
#include <string>
#include <vector>
// Qt header:
#include <QtWidgets>
class TreeWidget: public QTreeWidget {
private:
int _w, _h; // last determined contents size
public:
TreeWidget(QWidget *pQParent = nullptr);
virtual ~TreeWidget() = default;
TreeWidget(const TreeWidget&) = delete;
TreeWidget& operator=(const TreeWidget&) = delete;
protected:
virtual void paintEvent(QPaintEvent *pQEvent) override;
};
TreeWidget::TreeWidget(QWidget *pQParent):
QTreeWidget(pQParent), _w(0), _h(0)
{
setHorizontalScrollMode(ScrollPerPixel);
setVerticalScrollMode(ScrollPerPixel);
}
void TreeWidget::paintEvent(QPaintEvent *pQEvent)
{
QTreeWidget::paintEvent(pQEvent);
const int wView = viewport()->size().width();
const int hView = viewport()->size().height();
const int w = wView + horizontalScrollBar()->maximum();
const int h = hView + verticalScrollBar()->maximum();
if (_w == w && _h == h) return;
_w = w; _h = h;
qDebug() << "View Size:" << wView << "x" << hView;
const bool wFit = horizontalScrollBar()->maximum() == 0;
const bool hFit = verticalScrollBar()->maximum() == 0;
qDebug() << "Contents Size:"
<< (wFit ? "<=" : "") << w << "x" << (hFit ? "<=" : "") << h;
}
struct Node {
std::string name;
std::vector<Node> children;
};
void populate(const Node &node, QTreeWidget &qTree);
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// build UI
TreeWidget qTree;
qTree.setHeaderHidden(true);
qTree.setWindowTitle(QString::fromUtf8("QTreeView Contents Size"));
qTree.resize(200, 200);
qTree.show();
// populate tree
Node tree = {
"root", {
{ "child 1", {
{ "child 1-1", { } },
{ "child 1-2", { } },
{ "child 1-3", { } }
}
},
{ "child 2", {
{ "child 2-1", { } },
{ "child 2-2", { } },
{ "child 2-3", { } }
}
},
{ "child 3", {
{ "child 3-1", {
{ "child 3-1-1", { } },
{ "child 3-1-2", { } },
{ "child 3-1-3", { } }
}
},
{ "child 3-2", {
{ "child 3-2-1", { } },
{ "child 3-2-2", { } },
{ "child 3-2-3", { } }
}
},
{ "child 3-3", { } },
}
}
}
};
populate(tree, qTree);
//qTree.expandAll();
// runtime loop
return app.exec();
}
void populate(const Node &node, QTreeWidgetItem *pQParent)
{
QTreeWidgetItem *const pQTreeItem
= new QTreeWidgetItem(pQParent, { QString::fromStdString(node.name) });
for (const Node &child : node.children) populate(child, pQTreeItem);
}
void populate(const Node &node, QTreeWidget &qTree)
{
QTreeWidgetItem *const pQTreeItem
= new QTreeWidgetItem(&qTree, { QString::fromStdString(node.name) });
for (const Node &child : node.children) populate(child, pQTreeItem);
}
Output:
Qt Version: 5.13.0
View Size: 198 x 198
Contents Size: <= 198 x <= 198
I started to expand tree nodes interactively until tree size exceeded the view.
View Size: 181 x 198
Contents Size: <= 181 x 221
I resized the tree view a bit interactively until tree was fitting again into view.
View Size: 198 x 221
Contents Size: <= 198 x <= 221
View Size: 198 x 222
Contents Size: <= 198 x <= 222
View Size: 198 x 223
Contents Size: <= 198 x <= 223
View Size: 181 x 223
Contents Size: <= 198 x <= 223
I expanded more tree nodes interactively until tree size exceeded the view again.
View Size: 181 x 223
Contents Size: <= 181 x 272
View Size: 181 x 223
Contents Size: <= 181 x 323
Notes:
This appears to be a hacky solution with some limitations.
For proper results from the scroll bars, I had to use
setHorizontalScrollMode(ScrollPerPixel);
setVerticalScrollMode(ScrollPerPixel);
Otherwise, the maximum() value would reflect some kind of item increment which would be useless for my purpose.
The results got if contents is larger than the view port appear reasonable to me.
If the contents fits into view port, the contents size cannot be determined this way.
(This limitation might be acceptable.)
I placed the contents size report in an overridden paintEvent().
That was the only idea I had ad hoc to grant that the internal layout is done when retrieving the relevant data.
Before I tried to connect to the QTreeView::expanded() and QTreeView::collapsed() signals but with this I got outdated results, and moving it in a separate event via QTimer::singleShot() didn't fix it (although this is my usual, reliable “Swiss Army Knife” for such issues).
Before sending my answer, I spent a certain time to scroll through the Qt doc. for
QTreeView
QAbstractItemView
QAbstractItemModel
QAbstractScrollArea.
I struggled to believe that there isn't any way offered to retrieve the contents size.
Usually, when I use any class derived from QAbstractScrollArea then I either don't care about contents size (in full trust to the automatisms inside the Qt widgets) or I define it in my own code so that I have access to it.
Either I didn't search carefully enough or the Qt guys just couldn't imagine that a Qt using developer will ever need this data.
If I had to research further I probably would have a look onto the source code (e.g. on woboq.org) to see how the contents size is managed and where.

Related

drawing with Canvas in realtime (Qt/QML)

I hope i will be as clear as possible for my problem.
I'm currently devlopping an Application using a QML file as GUI.
The goal is simple : i become a large amount of data via QTcpSocket (raw data from a linear camera) and I would like to display the image. It's important to know that each time I receive a frame, it's a 1*2048 array of colors. I'd like to display it as fast as possible after receiving a data.
I've tried to set up a property string containing the color and 2 others properties containing the position (x;y) and then :
Canvas {
id: camera
objectName: "cameradrawer"
x: 1270
y: 30
width: 650
height: 500
renderStrategy: Canvas.Threaded
property string iColor
property int imageX
property int imageY
property var line
property var ctx
onPaint: {
ctx = getContext('2d');
ctx.clearRect(0, 0, width, height);
// draw here
ctx.fillStyle = iColor;
ctx.fillRect(imageY,imageX, 1, 1);
}
}
When i change the properties and then use ìnvokeMethod("requestPaint"), the application keeps crashing.
Am i doing something wrong or is the QML not used for high speed processes?
Thank you in advance for your help!
PS : i can post more code (C++ part) if necessary.
I had a bit of free time so I researched the issue as I found it quite interesting. So the faster way to implement that I guess is creating an image from the data and so create a texture from it that can be used in a QQuickItem-based item.
The custom item:
CustomItem.h
class CustomItem : public QQuickItem
{
Q_OBJECT
QML_ELEMENT
public:
CustomItem(QQuickItem *parent = nullptr);
~CustomItem();
QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *) override;
void componentComplete() override;
protected:
void createImage();
void timerHandler();
private:
uint32_t m_buffer[BUFFER_SIZE] = {};
QSGTexture *m_texture = nullptr;
QSGTexture *m_newTexture = nullptr;
QTimer m_timer;
};
CustomItem.cpp
CustomItem::CustomItem(QQuickItem *parent):
QQuickItem(parent)
{
setFlag( QQuickItem::ItemHasContents, true);
QObject::connect(&m_timer, &QTimer::timeout, this, &CustomItem::timerHandler);
}
CustomItem::~CustomItem()
{
if(m_texture != nullptr)
{
delete m_texture;
m_texture = nullptr;
}
if(m_newTexture != nullptr)
{
delete m_newTexture;
m_newTexture = nullptr;
}
}
QSGNode *CustomItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
QSGSimpleTextureNode *n = static_cast<QSGSimpleTextureNode *>(node);
if (n == nullptr)
{
if(m_newTexture != nullptr)
{
n = new QSGSimpleTextureNode();
n->setRect(boundingRect());
}
}
if(n != nullptr)
{
if(m_newTexture != nullptr)
{
if(m_texture != nullptr)
{
delete m_texture;
m_texture = nullptr;
}
m_texture = m_newTexture;
m_newTexture = nullptr;
n->setTexture(m_texture);
}
}
return n;
}
void CustomItem::componentComplete()
{
createImage();
m_timer.start(1000);
QQuickItem::componentComplete();
}
void CustomItem::createImage()
{
if(m_newTexture == nullptr)
{
QRandomGenerator::global()->fillRange(m_buffer, BUFFER_SIZE);
QImage img(reinterpret_cast<uchar *>(m_buffer), IMAGE_WIDTH, IMAGE_HEIGHT, QImage::Format_ARGB32);
auto wnd = window();
if(wnd != nullptr)
{
m_newTexture = wnd->createTextureFromImage(img);
}
}
}
void CustomItem::timerHandler()
{
createImage();
update();
}
Some short clarification:
the class contains a big array of integers. I simulate the periodical data updating with timer so the array is updated once per second with random data.
as soon as data changed I create a texture. I use 2 pointers to avoid deleting the old texture while rendering and so I delete the old one inside updatePaintNode when it's safety. I use QSGSimpleTextureNode since that allows use textures but I guess you can use any other classes you want. The texture has alpha channel, you can use Format_RGB32 instead to avoid that. If the item bigger the the texture it will be scaled, you can play with that too.
The main.qml for testing can be like this:
import QtQuick
import QtQuick.Window
import CustomItems 1.0
Window {
id: window
visible: true
height: 400
width: 400
Rectangle {
width: 300
height: 300
color: "orange"
anchors.centerIn: parent
CustomItem {
width: 200
height: 200
anchors.centerIn: parent
}
}
}
To register the custom item in case of CMake the following lines should be added into CMakeFiles.txt:
set(CMAKE_AUTOMOC ON)
qt_add_qml_module(qml_test
URI CustomItems
VERSION 1.0
QML_FILES main.qml
SOURCES CustomItem.h CustomItem.cpp
The sources could be found here

How to create a QVariant-based generic model?

Quite often I find myself in need of some custom scheme model, mandating the implementation of more and more models, made even more tedious by the inability of QObject derived classes to be templates.
Qt has the QStandardItemModel but that seems a bit verbose and inconvenient to use, especially from the qml side, and total overkill for a basic list model.
There is also the basic qml ListModel, but that is limited and not elegant to use on the C++ side, and I do suspect a tad more bloated than it needs to be.
Qt has QVariant, which is what its model/view architecture uses internally, so it is surprising that the framework doesn't provide something as simple as:
// qml code
VarMod {
roles: ["name", "age", "weight"]
Component.onCompleted: {
insert(["Jack", 34, 88.5], -1) // qml doesn't support
insert(["Mary", 26, 55.3], -1) // default arg values
}
}
// cpp code
VarMod vm { "name", "age", "weight" }; // member declaration
vm.insert({ "Jack", 34, 88.5 });
vm.insert({ "Mary", 26, 55.3 });
And here it is.
Note that you do have to be responsible with the parameters, as there is no type safety, in fact it has implicit analog to ListModel's dynamicRoles - that is, it will accept and work with any QVariant compatible value on every role slot.
As for memory efficiency, consider that QVariant has 8 bytes for data, plus 4 bytes for type id, plus another 4 bytes of padding, for a total of 16 bytes. That is not insignificant if you are using it for small data types, like say bool, so in case you have a data scheme that has a lot of small (1 - 4 bytes) fields and a scores of items, implementing a full model will still be the better option. It is still a lot better than the generic object model I am using, which has to carry the bloat of QObject, and even more significant in the case of qml objects.
Additionally, QVariant being 16 bytes, I opted to not use the convenience of QVariantList for data storage, which has an underlying QList, making the situation worse than it needs to be. Although that is fixed in Qt 6, which gets rid of QList as it is, and replaces it with an alias of QVector. Still, std::vector helps to avoid that in any case, plus it might actually be a tad faster, since it doesn't have to deal with COW and atomic ref counters. There are several auxiliary methods to help with pre-allocation and release of memory as well.
The model has a safeguard against the change the roles for obvious reasons, the latter is primarily intended to be initialized just once, but there is reset() that is intended to be used in a more dynamic qml context, making it possible to redefine the model schema on the fly and provide a compatible delegate. For the sake of certainty, the roles can only be redefined after the model has been explicitly reset.
There is a minute difference in inserting, on the c++ side, the parameter pack is passed wrapped in {}, in qml it is wrapped in [], both leveraging implicit conversion in the context specific way. Also, note that qml currently doesn't support omitting parameters with default values provided on the c++ side, so for appending you do have to provide an invalid index. Naturally, it would be trivial to add convenience methods for appending and prepending if needed.
In addition to the syntax example of the question, it is also possible to add multiple items at once, from "declarative-y" qml structure such as:
let v = [["Jack", 34, 88.5],
["Mary", 26, 55.3],
["Sue", 22, 69.6]]
vm.insertList(v, -1)
Finally, type safety is possible to implement if the scenario really calls for it, then each role can be specified with the expected type to go with it, such as:
VarMod vm {{"name", QMetaType::QString},
{"age", QMetaType::Int},
{"weight", QMetaType::QReal}};
and then iterating and making the necessary checks to ensure type safety when inserting.
Update: I also added serialization, and save/load from disk features, note that this will serialize the data together with the mode schema.
class VarMod : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(QVariantList roles READ roles WRITE setRoles NOTIFY rolesChanged)
QVariantList vroles;
QVariantList roles() const { return vroles; }
QHash<int, QByteArray> _roles;
std::vector<std::vector<QVariant>> _data;
inline bool checkArgs(int rc) const {
if (rc == _roles.size()) return true;
qWarning() << "arg size mismatch, got / expected" << rc << _roles.size();
return false;
}
inline bool inBounds(int i, bool ok = false) const {
if (i > -1 && i < (int)_data.size()) return true;
if (!ok) qWarning() << "out of bounds" << i; // do not warn if intentionally appending
return false;
}
inline bool validRole(int r) const { return (r > -1 && r < _roles.size()); }
protected:
QHash<int, QByteArray> roleNames() const override { return _roles; }
int rowCount(const QModelIndex &) const override { return _data.size(); }
QVariant data(const QModelIndex &index, int r) const override {
r = r - Qt::UserRole - 1;
if (inBounds(index.row()) && validRole(r)) return _data[index.row()][r];
return QVariant();
}
public:
VarMod() {} // for qml
VarMod(std::initializer_list<QByteArray> r) {
int rc = Qt::UserRole + 1;
for (const auto & ri : r) {
_roles.insert(rc++, ri);
vroles << QString::fromLatin1(ri);
}
rolesChanged();
}
inline void insert(std::initializer_list<QVariant> s, int i = -1) {
if (!checkArgs(s.size())) return;
insert(QVariantList(s), i);
}
inline bool setItem(int i, std::initializer_list<QVariant> s) {
if (checkArgs(s.size())) return setItem(i, QVariantList(s));
return false;
}
void setRoles(QVariantList r) {
if (_roles.empty()) {
int rc = Qt::UserRole + 1;
for (const auto & vi : r) _roles.insert(rc++, vi.toByteArray());
vroles = r;
rolesChanged();
} else qWarning() << "roles are already initialized";
}
void read(QDataStream & d) {
reset();
QVariantList vr;
d >> vr;
quint32 s;
d >> s;
_data.resize(s);
for (uint i = 0; i < s; ++i) {
_data[i].reserve(vr.size());
for (int c = 0; c < vr.size(); ++c) {
QVariant var;
d >> var;
_data[i].push_back(std::move(var));
}
}
setRoles(vr);
beginResetModel();
endResetModel();
}
void write(QDataStream & d) const {
d << vroles;
d << (quint32)_data.size();
for (const auto & v : _data) {
for (const auto & i : v) d << i;
}
}
public slots:
void insert(QVariantList s, int i) {
if (!inBounds(i, true)) i = _data.size();
if (!checkArgs(s.size())) return;
beginInsertRows(QModelIndex(), i, i);
_data.insert(_data.begin() + i, { s.cbegin(), s.cend() });
endInsertRows();
}
void insertList(QVariantList s, int i) {
if (!inBounds(i, true)) i = _data.size();
int added = 0;
for (const auto & il : s) {
QVariantList ll = il.value<QVariantList>();
if (checkArgs(ll.size())) {
_data.insert(_data.begin() + i + added++, { ll.cbegin(), ll.cend() });
}
}
if (added) {
beginInsertRows(QModelIndex(), i, i + added - 1);
endInsertRows();
}
}
bool setData(int i, int r, QVariant d) {
if (!inBounds(i) || !validRole(r)) return false;
_data[i][r] = d;
dataChanged(index(i), index(i));
return true;
}
bool setDataStr(int i, QString rs, QVariant d) { // a tad slower
int r = _roles.key(rs.toLatin1()); // role is resolved in linear time
if (r) return setData(i, r - Qt::UserRole - 1, d);
qWarning() << "invalid role" << rs;
return false;
}
bool setItem(int i, QVariantList d) {
if (!inBounds(i) || !checkArgs(d.size())) return false;
_data[i] = { d.cbegin(), d.cend() };
dataChanged(index(i), index(i));
return true;
}
QVariantList item(int i) const {
if (!inBounds(i)) return QVariantList();
const auto & v = _data[i];
return { v.begin(), v.end() };
}
QVariant getData(int i, int r) const {
if (inBounds(i) && validRole(r)) return _data[i][r];
return QVariant();
}
QVariant getDataStr(int i, QString rs) const {
int r = _roles.key(rs.toLatin1()); // role is resolved in linear time
if (r) return getData(i, r);
qWarning() << "invalid role" << rs;
return QVariant();
}
QVariantList take(int i) {
QVariantList res = item(i);
if (res.size()) remove(i);
return res;
}
bool swap(int i1, int i2) {
if (!inBounds(i1) || !inBounds(i2)) return false;
std::iter_swap(_data.begin() + i1, _data.begin() + i2);
dataChanged(index(i1), index(i1));
dataChanged(index(i2), index(i2));
return true;
}
bool remove(int i) {
if (!inBounds(i)) return false;
beginRemoveRows(QModelIndex(), i, i);
_data.erase(_data.begin() + i);
endRemoveRows();
return true;
}
void clear() {
beginResetModel();
_data.clear();
_data.shrink_to_fit();
endResetModel();
}
void reset() {
clear();
_roles.clear();
vroles.clear();
rolesChanged();
}
void reserve(int c) { _data.reserve(c); }
int size() const { return _data.size(); }
int capacity() const { return _data.capacity(); }
void squeeze() { _data.shrink_to_fit(); }
bool fromFile(QString path) {
QFile f(path);
if (!f.open(QIODevice::ReadOnly)) return false;
QDataStream d(&f);
read(d); // assumes correct data
return true;
}
bool toFile(QString path) const {
QFile f(path);
if (!f.open(QIODevice::WriteOnly)) return false;
QDataStream d(&f);
write(d);
return true;
}
signals:
void rolesChanged();
};
I also created this sorting/filtering view to supplement the model:
class View : public QSortFilterProxyModel {
Q_OBJECT
Q_PROPERTY(QJSValue filter READ filter WRITE set_filter NOTIFY filterChanged)
Q_PROPERTY(bool reverse READ reverse WRITE setReverse NOTIFY reverseChanged)
bool reverse() const { return _reverse; }
void setReverse(bool v) {
if (v == _reverse) return;
_reverse = v;
reverseChanged();
sort(0, (Qt::SortOrder)_reverse);
}
bool _reverse = false;
mutable QJSValue m_filter;
QJSValue & filter() const { return m_filter; }
void set_filter(QJSValue & f) {
if (!m_filter.equals(f))
m_filter = f;
filterChanged();
invalidateFilter();
}
}
public:
View(QObject *parent = 0) : QSortFilterProxyModel(parent) { sort(0, (Qt::SortOrder)_reverse); }
signals:
void filterChanged();
void reverseChanged();
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &) const override {
if (!m_filter.isCallable()) return true;
VarMod * vm = qobject_cast<VarMod *>(sourceModel());
if (!vm) {
qWarning() << "model is not varmod";
return true;
}
return m_filter.call({_engine->toScriptValue(vm->item(sourceRow))}).toBool();
}
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override {
VarMod * vm = qobject_cast<VarMod *>(sourceModel());
if (!vm) {
qWarning() << "model is not varmod";
return false;
}
return vm->getData(left.row(), sortRole()) < vm->getData(right.row(), sortRole());
}
};
For sorting, you just have to specify the sorting role, note that it is the index of the "column" rather than the int value from the roles hash. For filtering it works via a qml functor that receives the model item as a JS array, and expects to return a bool, a c++ functor can be easily added via std::function if needed. Also note that it needs a pointer to the actual qml engine.
View {
id: vv
sourceModel: vm
sortRole: sr.value
reverse: rev.checked
filter: { sa.value; o => o[1] < sa.value } // "capturing" sa.value to react to value changes
}

Custom listModel does not notify the view

I have my custom list model where I put data which should be displayed on the QML view. But for some kind of reason the view in QML sometimes is updated normally, sometimes with previous data and sometimes updating is not performed.
Here is the function where I fill the model - this function is called from some other thread.
void MyScreen::fillListModel()
{
const QString SEPARATOR = " ";
myListModel->resetModel();
for (int i = 0; i < MAX_ROWS; ++i)
{
QString key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
QString val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
myListModel->addItem(key + SEPARATOR + val);
}
}
Implementation of model reset:
void BrowsingModelBase::resetModel()
{
beginResetModel();
m_items.clear();
endResetModel();
}
Implementation of addItem():
void BrowsingModelBase::addItem(const BrowsingItemModelBase &item)
{
int count = m_items.size();
beginInsertRows(QModelIndex(), count, count);
m_items.append(item);
endInsertRows();
}
Finally my QML file:
MyScreen {
Column {
id: myFlowList
y: 110
x: 220
ListView {
height:1000
spacing: 35;
model: myListModelRoot.myListModel
delegate: Text {
text: text1
}
}
}
}
The strange thing is that after loop with line
myListModel->addItem(key + SEPARATOR + val);
when I print logs with data from myListModel it is filled with proper data, but the view is usually updated with previous data. Is it possible that data change signal is stuck somewhere? Any idea what is the solution?
Assuming that you call your model's methods from another thread, with the model being fundamentally not thread-safe, you have two options:
Make certain methods of your model thread-safe, or
Explicitly invoke the methods in a thread-safe fashion.
But first, you'd gain a bit of performance by adding all the items at once, as a unit. That way, the model will emit only one signal for all of the rows, instead of one signal per row. The views will appreciate it very much.
class BrowsingModelBase {
...
};
Q_DECLARE_METATYPE(QList<BrowsingItemModelBase>)
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
}
You should probably also have a method called clear instead of resetModel, since to reset a model has a much more general meaning: "change it so much that it's not worth emitting individual change signals". To reset a model does not mean "clear it"! Thus:
void BrowsingModelBase::clear()
{
beginResetModel();
m_items.clear();
endResetModel();
}
Finally, following the 2nd approach of safely invoking the model's methods, fillListModel becomes as follows. See this answer for discussion of postTo.
template <typename F>
void postTo(QObject * obj, F && fun) {
if (obj->thread() != QThread::currentThread()) {
QObject signalSource;
QObject::connect(&signalSource, &QObject::destroyed, obj, std::forward<F>(fun));
} else
fun();
}
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
postTo(myListModel, [this, items]{
myListModel->clear();
myListModel->addItems(items);
});
}
Alternatively, following the first approach, you can make the clear and addItems methods thread-safe:
/// This method is thread-safe.
void BrowsingModelBase::addItems(const QList<BrowsingItemModelBase> & items)
{
postTo(this, [this, items]{
beginInsertRows(QModelIndex(), m_items.size(), m_items.size() + items.size() - 1);
m_items.append(items);
endInsertRows();
});
}
/// This method is thread-safe.
void BrowsingModelBase::clear()
{
postTo(this, [this]{
beginResetModel();
m_items.clear();
endResetModel();
});
}
You then need no changes to fillListModel, except to make it use addItems:
void MyScreen::fillListModel()
{
auto separator = QStringLiteral(" ");
myListModel->clear();
QList<BrowserItemModelBase> items;
for (int i = 0; i < MAX_ROWS; ++i) {
auto key = QString::fromUtf16(MyData::getParameterKey(i).c_str());
auto val = QString::fromUtf16(MyData::getParameterVal(i).c_str());
items << BrowserItemModelBase(key + separator + val);
}
myListModel->addItems(items);
}
The problem is most likely the fact that you are calling the fillListModel() method not from the main GUI thread. You can update the model from other threads but the beginResetModel();, endResetModel();, beginInsertRows(QModelIndex(), count, count);... methods have to be called in the main GUI thread.
One way to call these method in GUI thread (maybe not the most efficient) is to :
Create signals for each method you want to call:
signals:
//these signals are emitted from worker thread
void requestBeginResetModel();
void requestEndResetModel();
Create slots that will actually call the methods:
private slots:
//these slots execute the model reset operations in main thread
void callBeginResetModel();
void callEndResetModel();
Connect the signals and the slots:
//connect the appropriate signals
connect(this, SIGNAL(requestBeginResetModel()),
this, SLOT(callBeginResetModel()));
connect(this, SIGNAL(requestEndResetModel()),
this, SLOT(callEndResetModel()));
Your reset model will then be:
void BrowsingModelBase::resetModel()
{
emit requestBeginResetModel();
m_items.clear();
emit requestEndResetModel();
}
Finally the slots are implemented as:
void ObjectModel::callBeginResetModel()
{
beginResetModel();
}
void ObjectModel::callEndResetModel()
{
endResetModel();
}
Note that you will have to do the same for row insert methods as well. Or alternatively you could fill you model in the resetModel() method in between emitted signals.

program crashes with haarcascade_fullbody.xml

I am working with OpenCV for Qt.
I am working on doing a program which is able to detect several objects. So far I could make a face, eye and nose detector, but when I try to make a full body detection I get either totally wrong detections, no detections at all or the program crashes. For detecting the full body I just use the same code as for the other detections but with the haarcascade_fullbody.xml file. Is it not possible to use the same code? Why does it work for the other features and not for the full body?
I have also tried to implement a car detection using OpenCV's pretrained models from https://github.com/Itseez/opencv_extra/tree/master/testdata/cv/latentsvmdetector/models_VOC2007 but I get parsing errors.
Thanks in advance!
Code from MainWindow:
void MainWindow::on_btnFullBody_clicked()
{
WriteInLog("Full body detection requested");
QString xml = tr("%1/%2").arg(QApplication::applicationDirPath()).arg(FULL_BODY_FILE);
FeatureDetector detector(xml);
std::vector<QRect> rest;
float scaleFactor= 1.1f;
uint neighbours= 2;
bool ret = detector.DetectFeature(&mSelectedImage, rest, scaleFactor, neighbours);
if (!ret)
{
WriteInLog("No full body has been detected");
}
else
{
QVector<QRect> qRect = QVector<QRect>::fromStdVector(rest);
processedImage(qRect);
WriteInLog("Bodys detected: "+QString::number(qRect.size()));
}
}
Code from DetectFeature:
bool FeatureDetector::DetectFeature(QImage* image, std::vector<QRect> &returnList, float scaleFactor, uint neighbours)
{
returnList.clear();
bool ok = false;
qDebug() << "Starting...";
if (!image->isNull()) {
//Changing from QImage to matrix
QImage temp = image->copy();
cv::Mat res(temp.height(),temp.width(),CV_8UC3,(uchar*)temp.bits(),temp.bytesPerLine());
cv::Mat res_gray;
//Changing the image to grey scale an equalizing the result
cvtColor(res, res_gray,CV_BGR2GRAY);
cv::equalizeHist(res_gray,res_gray);
cv::CascadeClassifier detector;
std::vector< cv::Rect > featureVec;
bool retDetector=true; // detector.load("C:/Users/ansurbcn_2/Pictures/cara.jpg");
qDebug()<<mXmlFilePath;
if (!detector.load(mXmlFilePath.toLatin1().constData()))
{
qDebug() << "Error loading detector";
return false;
}
detector.detectMultiScale(res_gray, featureVec);
//detector.detectMultiScale(res_gray, featureVec, scaleFactor, neighbours, 18|9);
if (retDetector) {
qDebug() << "OK Detector";
}
else {
qDebug() << "Failed Detector";
}
for(size_t i=0; i<featureVec.size();i++)
{
cv::Rect oneFeature =featureVec[i];
QRect qrect(oneFeature.x, oneFeature.y, oneFeature.width, oneFeature.height);
returnList.push_back(qrect);
ok = true;
}
}
return ok;
}

error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'Car' (or there is no acceptable conversion)

Queue class
#ifndef Queue_H
#define Queue_H
#include "Car.h"
#include <iostream>
#include <string>
using namespace std;
const int Q_MAX_SIZE = 20;
class Queue {
private:
int size; // size of the queue
Car carQueue[Q_MAX_SIZE];
int front, rear;
public:
Queue();
~Queue();
bool isEmpty();
bool isFull();
void enqueue(Car c);
void dequeue(); // just dequeue the last car in the queue
void dequeue(Car c); // if a certain car wants to go out of the queue midway.
// Condition: Car is not in washing. Meaning is not the 1st item in the queue
void dequeue(int index); // same as the previous comment
Car getFront();
void getCarQueue(Queue);
int length();
Car get(int);
};
Queue::Queue() {
size = 0;
front = 0;
rear = Q_MAX_SIZE -1;
}
Queue::~Queue() {
while(!isEmpty()) {
dequeue();
}
}
void Queue::enqueue(Car c) {
if (!isFull()) {
rear = (rear + 1) % Q_MAX_SIZE; // circular array
carQueue[rear] = c;
size++;
} else {
cout << "Queue is currently full.\n";
}
}
void Queue::dequeue() {
}
void Queue::dequeue(int index) {
if(!isEmpty()) {
front = (front + 1) % Q_MAX_SIZE;
if(front != index) {
carQueue[index-1] = carQueue[index];
rear--;
size--;
} else {
cout << "Not allowed to dequeue the first car in the queue.\n";
}
} else {
cout << "There are no cars to dequeue.\n";
}
}
bool Queue::isEmpty() {
return size == 0;
}
bool Queue::isFull() {
return (size == Q_MAX_SIZE);
}
Car Queue::getFront() {
return carQueue[front];
}
int Queue::length() {
return size;
}
Car Queue::get(int index) {
return carQueue[index-1];
}
void Queue::getCarQueue(Queue q) {
for(int i = 0; i< q.length(); i++)
cout << q.get(i) << endl; // Error here
}
#endif
error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'Car' (or there is no acceptable conversion)
I get this error which is kind of odd. so is there anything wrong? Thanks!
cout has no idea how to process a car object; it has never seen a car object and doesn't know how you output a car as text. cout can only process types it knows about, string, char, int, etc. The specific error is because there is version of operator << that takes an ostream and a car.
There are two options:
Creation an overload for operator<< that takes an ostream and a car. That will show cout how to output a car. This isn't usually done becuase there is usually more than one way your would want to display a car.
Write the output statement so that it manually prints out car properties like
cout << c.getMake() << " " << c.getModel()

Resources