I'm using a QStackedWidget where on Screen 1, I have a QTreeWidget with a list of items and Screen 2 has a few comboboxes and checkboxes. Double clicking on an item in the tree widget takes me to Screen 2. What I want to do is develop a way to remember chosen settings.
So for eg. if I double click on'Item1' in the treewidget, choose some options in the check and combo boxes in screen 2 and return to screen 1 and choose 'Item2' wherein this time I choose a different set of combo items etc. On going back to the first screen again and double clicking on 'Item1', I should restore the options I had previously associated with it.
Hope this makes sense. I needed help on the best way to do this and some code examples would be great.
Really appreciate any help.
All tree-widget items have a setData method that you can use to store associated values, which in this case would just be a dict containing the settings.
To make saving and restoring the settings easier, it would be advisable to make sure all the checkboxes, comboboxes, etc have a common parent, and that they are all given a unique objectName. That way, it will make it easy to iterate over them:
def saveSettings(self):
settings = {}
for child in self.settingsParent.children():
name = child.objectName()
if not name:
continue
if isinstance(child, QtGui.QCheckBox):
settings[name] = child.isChecked()
elif isinstance(child, QtGui.QComboBox):
settings[name] = child.currentIndex()
...
return settings
def restoreSettings(self, settings):
for child in self.settingsParent.children():
name = child.objectName()
if name not in settings:
continue
if isinstance(child, QtGui.QCheckBox):
child.setChecked(settings[name])
elif isinstance(child, QtGui.QComboBox):
child.setCurrentIndex(settings[name])
...
To add the settings to the tree-widget item, you just need to do something like this:
settings = self.saveSettings()
item.setData(0, QtCore.Qt.UserRole, settings)
and to retrieve them, do this:
settings = item.data(0, QtCore.Qt.UserRole)
self.restoreSettings(settings)
But note that you may need to take an extra step here if you are using python2, because data will return a QVariant, rather than a dict. If that is the case, to get the dict, you will need to do this instead:
settings = item.data(0, Qt.QtCore.Qt.UserRole).toPyObject()
Alternatively, you can get rid of QVariant everywhere but putting this at the beginning of your program:
import sip
sip.setapi('QVariant', 2)
from PyQt4 import ... etc
Related
I have disabled an option in an OptionMenu to fix something in the code where if I click on it just duplicates it, so I thought by disabling the option is an easy work around, but when the user may want to change to another option both the options are now disabled making the first one not usable again. I now need to return the option to normal. I thought of getting all the options which are disabled but couldn't figure out on how to do that. Sorry for the long para.
Any suggestions are useful.
Assume optmenu is the instance of OptionMenu, the following code will return all the items that are disabled:
# optmenu is the instance of OptionMenu
menu = optmenu['menu']
items = [] # list to hold all disabled items
# go through all option items
for i in range(menu.index('end')+1):
if menu.entrycget(i, 'state') == 'disabled':
items.append(menu.entrycget(i, 'label'))
print(items)
You might want to consider an object-oriented approach, defining for your object either a dict, list or some other array of settings, from which you can yourself easily fetch the status of any single control. Even if you don't want to do OOP, you should probably yourself save the current settings to an array.
I want to create an auto-complete TextField.
I mean - when you type something in the field and see a prompt list below. The prompt list is an array with possible values. The best way to explain it show a similar picture.
I already have some experience in Pythonista 3 but it was not UI programming experience.
I understand this is complex and that maybe I should use an additional View and Delegate mechanism but I don't have any idea how to start. I have already spent several days in Google looking for a solution, but I can't, in the context of Pythonista.
Has anybody done this? Or could someone provide useful links for reading?
A drop-down list can be created in Pythonista using a TableView. TableViews are really just single-column lists, they’re not just for tables.
So the steps would be:
Create a tableview.
Align it with your text field.
Hide the tableview until typing starts.
Update the tableview’s item list with the auto-completion options whenever the typing changes.
Potentially hide the tableview again when typing ends.
You can hide any view by setting its .hidden property to True.
You can do things when typing starts in a TextField by creating a delegate object that implements textfield_did_change.
You set a TableView to have a list of items by giving the TableView a data_source, probably an implementation of ui.ListDataSource. Whenever the items property on the data source changes, the list of options will also automatically change.
You can react to the user choosing an option from the TableView by setting an action on the TableView’s delegate.
The documentation for TableView, TextField, ListDataSource, delegates, and actions, can be found at Pythonista’s Native GUI for iOS documentation.
Here is a basic example:
# documentation at http://omz-software.com/pythonista/docs/ios/ui.html
import ui
# the phoneField delegate will respond whenever there is typing
# the delegate object will combine phoneField delegate, tableview delegate, and data source, so that it can share data
class AutoCompleter(ui.ListDataSource):
def textfield_did_change(self, textfield):
dropDown.hidden = False
# an arbitrary list of autocomplete options
# you will have a function that creates this list
options = [textfield.text + x for x in textfield.text]
# setting the items property automatically updates the list
self.items = options
# size the dropdown for up to five options
dropDown.height = min(dropDown.row_height * len(options), 5*dropDown.row_height)
def textfield_did_end_editing(self, textfield):
#done editing, so hide and clear the dropdown
dropDown.hidden = True
self.items = []
# this is also where you might act on the entered data
pass
def optionWasSelected(self, sender):
phoneField.text = self.items[self.selected_row]
phoneField.end_editing()
autocompleter = AutoCompleter(items=[])
autocompleter.action = autocompleter.optionWasSelected
# a TextField for phone number input
phoneField = ui.TextField()
phoneField.delegate = autocompleter
phoneField.keyboard_type = ui.KEYBOARD_PHONE_PAD
phoneField.clear_button_mode = 'while_editing'
# the drop-down menu is basically a list of items, which in Pythonista is a TableView
dropDown = ui.TableView()
dropDown.delegate = autocompleter
dropDown.data_source = autocompleter
# hide the dropdown until typing starts
dropDown.hidden = True
# create interface
mainView = ui.View()
mainView.add_subview(phoneField)
mainView.add_subview(dropDown)
# present the interface before aligning fields, so as to have the window size available
mainView.present()
# center text field
phoneField.width = mainView.width*.67
phoneField.height = 40
phoneField.x = mainView.width/2 - phoneField.width/2
phoneField.y = mainView.height/3 - phoneField.height/2
# align the dropdown with the phoneField
dropDown.x = phoneField.x
dropDown.y = phoneField.y + phoneField.height
dropDown.width = phoneField.width
dropDown.row_height = phoneField.height
On my iPhone, this code creates an interface that looks like this:
I'm working on something that gets and stores the transforms of an object moved by the user and then allows the user to click a button to return to the values set by the user.
So far, I have figured out how to get the attribute, and set it. However, I can only get and set once. Is there a way to do this multiple times within the script running once? Or do I have to keep rerunning the script? This is a vital question for me get crystal clear.
basically:
btn1 = button(label="Get x Shape", parent = layout, command ='GetPressed()')
btn2 = button(label="Set x Shape", parent = layout, command ='SetPressed()')
def GetPressed():
print gx #to see value
gx = PyNode( 'object').tx.get() #to get the attr
def SetPressed():
PyNode('object').tx.set(gx) #set the attr???
I'm not 100% on how to do this correctly, or if I'm going the right way?
Thanks
You aren't passing the variable gx so SetPressed() will fail if you run it as written(it might work sporadically if you tried executing the gx = ... line directly in the listener before running the whole thing -- but it's going to be erratic). You'll need to provide a value in your SetPressed() function so the set operation has something to work with.
As an aside, using string names to invoke your button functions isn't a good way to go -- you code will work when executed from the listener but will not work if bundled into a function: when you use a string name for the functions Maya will only find them if they live the the global namespace -- that's where your listener commands go but it's hard to reach from other functions.
Here's a minimal example of how to do this by keeping all of the functions and variables inside another function:
import maya.cmds as cmds
import pymel.core as pm
def example_window():
# make the UI
with pm.window(title = 'example') as w:
with pm.rowLayout(nc =3 ) as cs:
field = pm.floatFieldGrp(label = 'value', nf=3)
get_button = pm.button('get')
set_button = pm.button('set')
# define these after the UI is made, so they inherit the names
# of the UI elements
def get_command(_):
sel = pm.ls(sl=True)
if not sel:
cmds.warning("nothing selected")
return
value = sel[0].t.get() + [0]
pm.floatFieldGrp(field, e=True, v1= value[0], v2 = value[1], v3 = value[2])
def set_command(_):
sel = pm.ls(sl=True)
if not sel:
cmds.warning("nothing selected")
return
value = pm.floatFieldGrp(field, q=True, v=True)
sel[0].t.set(value[:3])
# edit the existing UI to attech the commands. They'll remember the UI pieces they
# are connected to
pm.button(get_button, e=True, command = get_command)
pm.button(set_button, e=True, command = set_command)
w.show()
#open the window
example_window()
In general, it's this kind of thing that is the trickiest bit in doing Maya GUI -- you need to make sure that all the functions and handlers etc see each other and can share information. In this example the function shares the info by defining the handlers after the UI exists, so they can inherit the names of the UI pieces and know what to work on. There are other ways to do this (Classes are the most sophisticated and complex) but this is the minimalist way to do it. There's a deeper dive on how to do this here
I have a
class Main(QtGui.QMainWindow):
That is able to click>spawn a x number of windows that are:
class dataWindow(QtGui.QWidget)
Is there a way in PyQt to now find all spawned dataWindow's and get their objectName?
each window has unique objectName.
I tried going via :
a= self.findChild(QWidget, self.newDataWids[0]["window_name"]) - as I have all names stored in dict upon creation
but it only returns None. I think its because the dataWindow are not parented to Main window class I believe... so I either have to parent them - not sure how. Or somehow find them out in the "wild"...
Any ideas would be great.
Regards, Dariusz
Edit_1: A glitch in my code bugged out my current attempt. After relooking I managed to get it to work. I simply stored the window in temporary dictionary and then used that to retrieve access to window.
You parent objects by passing in the parent to their constructor. You'll have to check the documentation for each widget to get the correct argument position.
widget = QtGui.QWidget(self)
btn = QtGui.QPushButton('Button Text', self)
But really, you shouldn't have to do a search for children to get the child windows. Your main window should be keeping handles to them.
def __init__(...)
...
self._windows = []
def createSubWindow(self):
window = WindowClass(self)
self._windows.append(window)
I can't find a way to hide QComboBox items. So far the only way to filter its items out is to delete the existing ones (with .clear() method). And then to rebuild the entire QComboBox again using its .addItem() method.
I would rather temporary hide the items. And when they are needed to unhide them back.
Is hide/unhide on QCombobox items could be accomplished?
In case someone still looking for an answer:
By default, QComboBox uses QListView to display the popup list and QListView has the setRowHidden() method:
qobject_cast<QListView *>(comboBox->view())->setRowHidden(0, true);
Edit: fix code according to #Tobias Leupold's comment.
Edit: Python version:
# hide row
view = comboBox.view()
view.setRowHidden(row, True)
# disable item
model = comboBox.model()
item = model.item(row)
item.setFlags(item.flags() & ~Qt.ItemIsEnabled)
# enable item
view.setRowHidden(row, false)
item.setFlags(item.flags() | Qt.ItemIsEnabled)
To build on what #kef answered:
(excuse the C++ on the python question)
By default the QComboBox will use a QListView for the view, thus you can do the following:
QListView* view = qobject_cast<QListView *>(combo->view());
Q_ASSERT(view != nullptr);
view->setRowHidden(row, true);
The one drawback with the above is, that even though the item will be hidden from the popup, the user can still select it using the mouse wheel. To overcome this add the following for the hidden row:
QStandardItemModel* model = qobject_cast<QStandardItemModel*>(combo->model());
Q_ASSERT(model != nullptr);
QStandardItem* item = model->item(row);
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
With the above the row will be hidden and the user will not be able to scroll to it with the mouse wheel.
To unhide it, just do the reverse:
view->setRowHidden(row, false);
item->setFlags(item->flags() | Qt::ItemIsEnabled);
You can use the removeItem() method to remove an item from the QComboBox.
void QComboBox::removeItem ( int index )
Removes the item at the given index from the combobox. This will update the current index if the index is removed.
This function does nothing if index is out of range.
If you don't know the index, use the findText() method.
There are no hide/unhide methods for QComboBox items.
Althought there is no direct way to hide the item of the QComboBox, but you can use QComboBox::setItemData and set the size to (0,0) to hide the item of QComboBox:
comboBox->setItemData(row, QSize(0,0), Qt::SizeHintRole);
Use the setVisible() to alter the visibility of your object:
.setVisible(False) # Not Visible
.setVisible(True) # Visible
To show the item again:
comboBox->setItemData(row, QVariant(), Qt::SizeHintRole);
Note: changing the SizeHintRole doesn't work on OS X.
I came across this thread after getting frustrated with a lack of hide functionality that would keep the item indexing etc.
I got it to work based on #Kef and #CJCombrink answers. This is basically just a python translation.
I had a problem with qobject_cast.
Solved it by setting .setView(QListView()) to the QComboBox.
combo=QComboBox()
combo.setView(QListView())
hide:
combo.view().setRowHidden(rowindex,True)
tmp_item=combo.model().item(rowindex)
tmp_item.setFlags(tmp_item.flags() & ~Qt.ItemIsEnabled)
unhide:
combo.view().setRowHidden(rowindex,False)
tmp_item=combo.model().item(rowindex)
tmp_item.setFlags(tmp_item.flags() | Qt.ItemIsEnabled)
I decided to subclass the QComboBox and add the hide functionality. Bellow is an use example for testing.
You are welcome to use it. I make no assurances.
import sys
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *
#subclassed QComboBox with added hide row functionality
class ComboBox_whide(QComboBox):
def __init__(self):
super().__init__()
self.setView(QListView())#default self.view() is a QAbstractItemView object which is missing setRowHidden, therefore a QListView needs to be set
def hide_row_set(self,row,value=True):
"""sets the row accesibility
value=True hides the row"""
self.view().setRowHidden(row,value)#hides the item from dropdown, however the item is stil accesible by moving down with arrow keys or mouse wheel. The following disables solves that
tmp_item=self.model().item(row)
if value:#hide -> disable
tmp_item.setFlags(tmp_item.flags() & ~Qt.ItemIsEnabled)
else:#enable
tmp_item.setFlags(tmp_item.flags() | Qt.ItemIsEnabled)
def hide_row_toggle(self,row):
"""toggles the row accesibility"""
if self.view().isRowHidden(row):#is hidden, therefore make available
self.hide_row_set(row,False)
else:#is not hidden, therefore hide
self.hide_row_set(row,True)
class Main(QMainWindow):
def __init__(self):
super().__init__()
cwidg=QWidget()
clayer=QVBoxLayout()
cwidg.setLayout(clayer)
self.setCentralWidget(cwidg)
#button for testing
self.btn=QPushButton('Button')
self.btn.setCheckable(True)
clayer.addWidget(self.btn)
#subclassed QComboBox
self.combo=ComboBox_whide()
for n in range(3):#add 3 items with tooltips
self.combo.addItem('item%i'%n)
self.combo.setItemData(n,'tip%i'%n,Qt.ToolTipRole)
clayer.addWidget(self.combo)
#button test function - choose either or for testing
self.btn.clicked.connect(self.btn_clicked)
#uncomment for add/remove example self.btn.clicked.connect(self.remove_add_item)
def btn_clicked(self):
self.combo.hide_row_toggle(1)
def remove_add_item(self):# here for naive comparison and to show why removing and adding is not ok
if self.combo.count()==3:
self.combo.removeItem(1)
else:
self.combo.addItem('new')#new "item1" withouth the ToolTip
if __name__ == '__main__':
app = QApplication.instance()
if app is None:#Pyside2 ipython notebook check
app = QApplication(sys.argv)
main = Main()
main.show()
app.exec_()