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.
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"
you might be able to tell that I'm pretty new to QT...
My program contains a window with several Widgets in a QGridLayout. Some of these Widgets have a layout and child widgets themselves. Pressing the Tab key moves the focus like I expect it to, in the order I created the widgets.
Problems occur when a widget changes it's content (I do that by deleting all child widgets and then constructing new ones.) If I do that, new widgets are moved to the end of the tab chain (that indicates to me, that tab order is kind of global for a window). I have tried to use QWidget::setTabOrder() to reorder all widgets (I tried both, setting tab order for only the contends of the main window and setting it for the children too) but the actual order doesn't change. I did this by emitting a signal when the contend of a widget changes and connecting that to a slot on my main Window.
I think I understand the way the setTabOrder() function should work. I do something similar to this:
QWidget* a = firstWidget;
QWidget* b = secondWidget;
QWidget::setTabOrder(a,b);
for (int i = 0; i < widgets.size(); ++i) {
a = b;
b = widgets[i];
QWidget::setTabOrder(a,b);
}
Is there anything special one has to do when changing the tab order?
I also tried to reimplement focusNextPrevChild(bool next) and focusInEvent(QFocusEvent* e) similar to what can be found at this site. 1
I managed to mess up tab order a lot more like this... is this approach a step in the right direction?
I'm sorry if this is something simple that I managed to miss, but I'm struggling for a while now and I can't find a solution.
Any help is very appreciated.
I had the same problem, and resolved it using the info Tim Meyer provided in the comments.
Tab order is not hierarchical - calling setTabOrder on a parent widget that doesn't accept focus will not cause the focus to be passed to the child. You will need to fetch the appropriate children from the widget and set the order on them
In my case, a dynamically constructed QTreeWidget contained editable widgets, and I needed to reset setTabOrder to account for widgets being created out-of-order.
The following code is (most) of the implementation
// Perform a depth-first gather of the child widgets of this item
void gatherTabWidgets( QObject* item, QWidgetList& tabWidgets )
{
if (item->isWidgetType())
{
QWidget* itemWidget = static_cast<QWidget*>(item);
if (itemWidget->focusPolicy() & Qt::TabFocus)
tabWidgets.push_back( itemWidget );
}
const QObjectList& children = item->children();
for (QObjectList::const_iterator itr = children.begin(); itr != children.end(); itr++)
{
gatherTabWidgets( const_cast<QObject*>(*itr), tabWidgets );
}
}
// Perform a depth-first gather of the child items widgets;
void gatherTabWidgets( QTreeWidgetItem* item, QWidgetList& tabWidgets )
{
QWidget* itemWidget = fetchWidgetForItem( item );
gatherTabWidgets( itemWidget, tabWidgets );
for (int i = 0; i < item->childCount(); i++)
{
gatherTabWidgets( item->child( i ), tabWidgets );
}
}
void VETreeWidget::sortTree()
{
// Ensure ordering is maintained.
sortItems( 0, Qt::AscendingOrder );
// Once the items have been re-ordered, re-create the tab ordering
QTreeWidgetItem* baseItem = topLevelItem( 0 );
QWidgetList tabWidgets;
gatherTabWidgets( baseItem, tabWidgets );
if (!tabWidgets.empty())
{
QWidget* lastWidget = tabWidgets.first();
// Connect this treeview to the first widget. This ensures
// if the treeview is tabbed-to, it will tab to the correct sub-widget
setTabOrder( this, lastWidget );
for (QWidgetList::iterator itr = tabWidgets.begin() + 1; itr != tabWidgets.end(); itr++)
{
setTabOrder( lastWidget, *itr );
lastWidget = *itr;
}
}
}
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.
I have an item renderer that checks an external source for display information. If that information changes, I want to force all item renderer instances to check it.
What's the best way for force all the item renderers in a list or grid to either commitProperties or execute some other method?
I've read that resetting the
grid.itemRenderer property will make
them all initialize.
I've also received the suggestion to
iterate recursively through all the
grid's children and call invalidateProperties
on all the UIComponents I find.
Any thoughts? Alternatives?
Remember that in Flex Lists you're dealing with virtualization and itemRenderer recycling, so generally only the currently visible itemRenderers actually exist, and are therefore the ones that actually need updating.
The following works for Spark list-based controls:
for ( var i:int=0; i< sparkList.dataGroup.numElements; i++ )
{
var element:UIComponent = sparkList.dataGroup.getElementAt( i ) as UIComponent;
if ( element )
element.invalidateProperties();
else
trace("element " + i.toString() + " wasn't there");
}
If you've got 100 items, this will update the 10 visible ones and ignore the virtual rest.
If you're working with mx DataGrid, you might want to try a variant of this- but it doesn't use DataGroup / Spark virtualization so I don't have an answer for you off the top of my head.
P.S. I'm putting the finishing touches on a completely Spark-based DataGrid, I'll post the link when I'm done.
Datagroup has getItemIndicesInView() which will give you the indicies of all item renderers that are in view. Call getElementAt with those indicies.
I also usually extend ItemRenderer and add the following which will cause the item renderer's state to refresh.
public function invalidateSkinState():void
{
super.invalidateRendererState();
}
public function updateAllRenderer():void
{
if (!list.dataGroup)
return;
if (!list.dataGroup.dataProvider)
return;
for ( var index:int=0; index< list.dataGroup.numElements; index++ )
{
var item:Object = list.dataGroup.dataProvider.getItemAt(index);
var renderer:IVisualElement = list.dataGroup.getElementAt( index ) as IVisualElement;
if ( renderer )
list.updateRenderer( renderer, index, item );
}
}
works fine for me
I have a list component and I have an item editor for the items in the list. I would like to have a button that the user clicks once they are done with their changes because I am having them edit multiple pieces of data in the editor and I would also like to validate the data before closing the editor as well. I just don't know what to do on the button's click event to make the item editor close and commit it's changes to the data provider.
You'll want to use a validator to validate the data, and I think maybe do something with the updateComplete and change events to delay the updating of the list component:
http://livedocs.adobe.com/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=celleditor_073_17.html
I would use data binding and let Flex do the work for you.
Have an object myObject with a bindable property myList:IList. Bind the display to this object.
When you start editing, copy that list.
On MouseEvent.CLICK:
var ed:Editor // Your list editing object.
var edProvider:IList = ed.dataProvider;
var targList:IList = myObject.myList;
var bool:Boolean = ( myObject.myList.length > edProvider.length );
var len:int = ( bool )? targList.length: edProvider.length;
var item:* = null;
for( var i:int = 0; i < len; i++ )
{
try // a "just in case". You probably will never have a problem.
{
item = edProvider.getItemAt( i );
targList.setItemAt( item, i );
}
catch( error:Error )
{
continue;
}
}
To handle the editing of multiple fields in a List control, you will need to catch the ItemEditEnd event and then manually change the fields you are interested in.
See "Example: Using a custom item editor with a List control" in here - http://livedocs.adobe.com/flex/3/html/help.html?content=celleditor_9.html#226555.
Usually the List will handle the dispatching of this event for you when you focus out of a cell. I'm not sure of its properties off the top of my head, but you should be able to construct this event in your button click handler, and then just dispatch it yourself.