I'm trying to do something that seems like it should be very simple, but the more I look into it I wonder if it's a Qt bug.
So, I have a QTableView that has columns that can be shown/hidden as the user likes. After I initialize the table, I call a custom restoreColumns() method that hides the columns (using QTableView::hideColumn()) that the user had hidden the last time the GUI was open.
The problem then comes when the user tries to show the columns that were hidden by the user the last time the GUI was ran. The appropriate signal/slot gets called and run through but for some reason the QTableView isn't updating to display the column.
What's weird is that any column that is already displayed (was not hidden by the user the last time the GUI was ran) has no problems with getting hidden/shown.
Any thoughts? Thanks!
Here's how I initialize the table...
m_tableModel = new mytablemodel();
m_tableView = new mytableview();
m_tableView->setItemDelegate(m_tableDelegate);
m_tableView->setModel(m_tableModel);
Meat of restoreColumns() method:
for (int i=0; i<horizontalHeader()->count(); i++) {
// load size to restore previous width
...
horizontalHeader()->resizeSection(i, width); // restore width
// load previous column position
...
// restore column order
int currentVisualIndex = horizontalHeader()->visualIndex(i);
if (currentVisualIndex != visualIndex)
horizontalHeader()->moveSection(currentVisualIndex, visualIndex);
// load previous hidden/shown state
...
if (columnHidden) {
hideColumn(i);
} else {
showColumn(i);
}
}
Below is some sample code to show/hide one of the columns.
void mytableview::showAColumn(bool checked) {
// mytableview is a subclass of qtableview
if (checked)
showColumn(COLUMN_A); // COLUMN_A is an enum for the column
else
hideColumn(COLUMN_A);
}
Which is connected to a QAction that can be accessed from the Menu and Context Menu of the QHeaderView of the QTableView.
connect(action, SIGNAL(toggled(bool)), this, SLOT(showAColumn(bool)));
When you are loading the previous width of the hidden columns, the width that was saved was 0.
So, when resizing the column make sure that the width is greater than 0.
Do this and then the columns will show/hide as expected.
Related
I have a grid I created in Gridx which lists a bunch of users. Upon clicking a ROW in the grid (any part of that row), a dialog pops up and shows additional information about that user and actions that can be done for that user (disable user, ignore user, etc.) - when one of these options is selected from the pop up, I want to DISABLE that row. The logic for getting the row, etc. I can take care of, but I can't figure out how to make a grid row actually "appear" disabled and how to make that row no longer clickable.
Is there a simple way to do this? If you aren't familiar with gridx, solutions that apply to EnhancedGrids or other Dojo grids are also appreciated.
Alright now that I have a little more information here is a solution:
Keep a list of all the rows you have disabled so far either inside the Grid widget or in its parent code. Then on the onRowClick listener I would write code like this:
on(grid, "onRowClick", function(e) {
if(disabledRows[rowIndex]) {
return;
}
// Do whatever pop up stuff you want and after
// a user selects the value, you can "disable"
// your row afterwards by adding it to the disabled
// list so that it can no longer be clicked on.
var rowIndex = e.rowIndex;
disabledRows[rowIndex] = true;
// This is just some random class I made up but
// you can use css to stylize the row however you want
var rowNode = e.rowNode;
domClass.add(rowNode, "disabled");
});
Note that domClass is what I named "dojo/dom-class". Hope this helps!
This is perhaps not exactly what you are seaching for:
If you want to hide one or more rows by your own filterfunction you could just add to these rows in the DOM your own class for nodisplay. Here I show you a function for display only those rows which have in a choiceable field/column a value inside your filterlist.
function hideRowFilter(gridId, fieldName, filterList)
{
var store = gridId.store;
var rowId;
store.query(function(object){
rowId = gridId.row(object.id,true).node();
if (filterList.indexOf(object[fieldName]) == -1)
domClass.add(rowId, "noDisplay"); // anzeigen
else
domClass.remove(rowId, "noDisplay"); // verstecken
});
}
CSS:
.noDisplay { display: none; }
So I can for example display only the entries with a myState of 3 or 4 with this call:
hideRowFilter(gridId, 'myState', [3, 4]);
Note that domClass is what I named "dojo/dom-class"
Let's say I have a listwidget with lots of items. When the user scrolls to see more items, I want to perform an action to the items being viewed. Is it possible to trigger an event itemsViewedChanged(QListWidgetItems *items) that gives me the currently viewed by the user items?
If no, how can I implement something like this?
Thanks for any answers
I don't know of a pre-existing method, however I've implemented something similar in a QTreeWidget to handle loading of icons for only visible items, the general technique I would use for a QListWidget would be a bit simpler (forgive the syntax, I normally use PyQt, so this could be off in C++):
QList<QListWidgetItem*> MyListWidget::visibleItems() {
QList<QListWidgetItem>* output = new QList<QListWidgetItem*>();
// make sure we have some items
if ( !this->count() ) {
return output;
}
// calculate the beginning and end items in our range
QListWidgetItem* minimumItem = this->itemAt(5, 5);
QListWidgetItem* maximumItem = this->itemAt(5, this->height() - 5);
if ( !minimumItem ) { minimumItem = this->item(0); }
if ( !maximumItem ) { maximumItem = this->item(this->count() - 1); }
// get the start and end rows
int minimum_row = this->indexForItem(minimumItem)->row();
int maximum_row = this->indexForItem(maximumItem)->row();
for (int row = minimum_row; row <= maximum_row; row++) {
output->append(this->item(row));
}
return output;
}
That would get you a list of the visible items for the list widget. To dynamically check and modify things as the user is editing, you can then connect to the valueChanged(int) and rangeChanged() signals for the list widget. That way, as the user scrolls or resizes your view, you are reacting to the signal and collecting the now visible list of items.
I'm writing a program using Qt 4.8 that displays a table (QTableWidget) filled with filenames and file's params. First an user adds files to the list and then clicks process. The code itself updates the contents of the table with simple progress description. I want the table by default to be scrolled automatically to show the last processed file and that code is ready.
If I want to scroll it by hand the widget is being scrolled automatically as soon as something changes moving the viewport to the last element. I want to be able to override the automated scroll if I detect that it was the user who wanted to change view.
This behavior can be seen in many terminal emulator programs. When there's a new line added the view is scrolled but when user forces the terminal to see some previous lines the terminal does not try to scroll down.
How could I do that?
Solution:
I created an object which filters event processed by my QTableWidget and QScrollBar embedded inside. If I spot the event that should turn off automatic scrolling I just set a flag and stop scrolling view if that flag is set.
Everything is implemented inside tableController class. Here are parts of three crucial methods.
bool tableController::eventFilter(QObject* object, QEvent* event)
{
switch (event->type())
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::Wheel:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
_autoScrollEnabled = false;
default:
break;
}
return QObject::eventFilter(object, event);
}
void tableController::changeFile(int idx)
{
[...]
if (_autoScrollEnabled)
{
QTableWidgetItem* s = _table.item(_engine.getLastProcessed(), 1);
_table.scrollToItem(s);
}
[...]
}
void tableController::tableController()
{
[...]
_autoScrollEnabled = true;
_table.installEventFilter(this);
_table.verticalTableScrollbar()->installEventFilter(this);
[...]
}
Thanks for all the help. I hope somebody will find it useful :)
Subclass QTableWidget and overload its wheelEvent. You can use the parameters of the supplied QWheelEvent object in order to determine if the user scrolled up or down.
Then use a simple boolean flag which is set (or reset) in your wheelEvent override. The method which is responsible for calling scrollToBottom() should then consider this boolean flag.
You will have to find a way to figure out when to set or reset that flag, e.g. always set it when the user scrolls up and reset it when the user scrolls down and the currently displayed area is at the bottom.
connect(_table->view()->verticalScrollBar(), &QAbstractSlider::actionTriggered, this, [this](int) {
_autoScrollEnabled = false;
});
I am trying to develop an image gallery application using Qt Framework. The application loads all the images from the selected folder and those images are displayed using QListView control.
But now i want to reduce the memory consumption by loading only the images that are visible to user. Since there is no direct function to get all the visible items in the view, i am not able to achieve this.
You can get the visible items of a list view using the indexAt function. For more details and an example you can check the following article:
http://qt-project.org/faq/answer/how_can_i_get_hold_of_all_of_the_visible_items_in_my_qlistview
I found it! You have to connect the vertical scrollbar of the listwidget to a signal:
connect(ui->listWidget->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(launch_timer()));
Every time the user scrolls, the valuechanged(int) signal is being omitted! The thing is that you shouldn't run the code provided by webclectic in this question every time the value of the vertical scrollbar of the listwidget changes, because the program will be unresponsive with so much code to run in so little time.
So, you have to have a singleshot timer and point it to the function that webclectic posted above. When launch_timer() is called, you do something like this:
if(timer->isActive()){
timer->stop();
timer->start(300);
}
else
timer->start(300);
and the timeout() signal of timer will be connected to the slot webclectic talked about. This way, if the user scrolls quickly all the way down only the last items will be updated. Generally, it will be updated anything visible for more than 300 milliseconds!
I think what you need is to implement your own model (take a look to the QAbstractListModel documentation) so that way you could decide when you have to load more images to show and maybe free some of the images that became non-visible.
although this is not so simple in Qt 4 but
it is always simple to copy below:
#include <private/qlistview_p.h>
class QListViewHelper : public QListView
{
typedef QListView super;
inline QListViewHelper() {} //not intended to be constructed
public:
inline static QVector<QModelIndex> indexFromRect(const QListView *view,
const QRect &rect)
{
const QListViewPrivate *d = static_cast<const QListViewPrivate *>(QObjectPrivate::get(view)); //to access "QListViewPrivate::intersectingSet(...)"
const QListViewHelper *helper = static_cast<const QListViewHelper *>(view); //to access "QListView::horizontalOffset()"
return d->intersectingSet(rect.translated(helper->horizontalOffset(), helper->verticalOffset()), false);
}
inline static QVector<QModelIndex> visibleItems(const QListView *view)
{ return indexFromRect(view, view->rect()); }
inline static QModelIndex firstVisible(const QListView *view)
{ return visibleItems(view).value(0); }
inline static QModelIndex lastVisible(const QListView *view) {
const QVector<QModelIndex> &items = visibleItems(view);
return items.value(items.count() - 1);
}
};
void ourTest(const QListView *view) {
QModelIndex &index = QListViewHelper::firstVisible(view);
qDebug("QListViewHelper: first visible row is %d", index.row());
index = QListViewHelper::lastVisible(view);
qDebug("QListViewHelper: last visible row is %d", index.row());
}
usage:
QModelIndex &index =
QListViewHelper::firstVisible(listViewPointerHere)
note: since it does use Qt 4.8 private-headers it may no longer work in latter versions and will need some changes.
You can keep track of all the elements that are drawn per paint event. I used a delegate and overloaded the paint event.
I also overloaded the paint event in the view. During this call, all the visible delegates will get a paint event.
If you just need to know if an item is visible, you can increment a frame count in view->paintEvent and set that number in the delegate item. The item is visible of the item matches the current frame number.
If you need a list of all visible items, clear the visible item list in view->paintEvent and add each item in the int the delegate->paintEvent to the visible items list.
I am attempting to create a model/view application in Qt 4.7.1. I am a very new Qt developer.
Summary of what I am attempting to do:
I have a treeview that is organized as a rectangular table of rows and columns. One column of items contains a button. By default this button is to be transparent and disabled. A given button is to become visible and enabled when the mouse is hovering over its row.
The approach I am pursuing is to
find the model index for the cell that the mouse is hovering over, and
obtain a pointer to the widget associated with the widget, and
using this pointer manipulate the visibility of the button within said widget.
I cannot get a valid pointer to the widget.
my current code looks like this:
void HistoryTreeView::mouseMoveEvent(QMouseEvent *event)
{
QAbstractItemModel *m(model());
// Only do something when a model is set.
if (m)
{
QModelIndex index = indexAt(event->pos());
if (index.isValid())
{
// if the mouse has moved to another row
if (index.row() != m_currentRow)
{
m_currentRow = index.row();
QMessageBox::information( this, "HistoryTreeView", QString("index(%1)").arg(index.row()));
QWidget * item = indexWidget(index);
Q_ASSERT(item != NULL );
}
}
else // model is invalid
{
m_currentRow = -1;
}
}
QTreeView::mouseMoveEvent(event);
}
The symptoms:
I expected the call to indexWidget() to return a valid pointer to the widget the mouse is over. Instead it unexpectedly returns a NULL pointer.
Commentary:
The variable named 'index' is acting as I expected because the QMessageBox shows the correct row value. Consequently I do not think there is anything wrong with the value I am providing to indexWidget().
This is just debug code. It is missing things like code that selects the column that holds the buttons.
OK, Here is the nature of my error as I understand it.
I had incorrectly understood that every item in a view is its own widget. I now understand that the view itself is a widget, but that individual items within the view are not widgets, per se.
Because I had misunderstood that view items were widgets I believed that could:
obtain an index from a given element in a model,
use indexWidget() to obtain a Widget * to the view item associated with the model element
and then use this pointer to manipulate the view item as though it was a widget.
indexWidget() simply returned a NULL because view items are not widgets.