guys.I'm working on a GUI for controlling some robots.
There is a table which shows the current states data about that robot.And the table is updated periodically from the data in the test server.When the updating was not connected it was easy to select a row in the table and save the selected row's data.However,when the server is connected, it becomes impossible to select a row(when u click on the row,the selected row will be highlighted only in a flash and gone).I try to use some print method to see what has happened inside, it seems that the slots will not be implemented when the updating is running.And the updating will also deselect the row frequently.I'm considering block the updating when I manually select a row,then automatically select the same row with a piece of logic and the selectRow() method.Do u have any suggestion for how to block the updating or another way to deal with this problem?
1.updating method, which work as a slot in a Gui Controller class to manage input
def tbRobotChanged(self, currentCell):
# get the selected cell's index from currentCell,Implement the Slot method
tablemodel= currentCell.model()
#get the model of the currentcell
firstColumn = tablemodel.index(currentCell.row(),0)
#change it to the first column in the row with the robot name
self.statusBar().showMessage("Selected Robot is " +
firstColumn.data().toString())
self.selected_robot = int(firstColumn.data().toString())
# show the selected robot name in the statusbar
2.table definition ,this is part of a Gui class which define the main window:
def initRobotsFrame(self, rf):
hbox = QtGui.QHBoxLayout()
self.robot_table = QtGui.QTableView()
self.table_header = ['Robot', 'PosX', 'PosY', 'Heading']
tm = RobotTableModel([[0, 0, 0, 2],[1, 1,2,4]],
self.table_header)
self.robot_table.setModel(tm)
vh = self.robot_table.verticalHeader()
vh.setVisible(False)
self.robot_table.resizeColumnsToContents()
self.robot_table.setSizePolicy(QtGui.QSizePolicy(
QtGui.QSizePolicy.Minimum,
QtGui.QSizePolicy.Minimum))
hbox.addWidget(self.robot_table)
self.selected_robot = 0
block_true=GUIController.robot_data.blockSignals(True)
GUIController.robot_data.blockSignals(block_true)
# select table by row instead of by cell:
self.robot_table.setSelectionBehavior(
QtGui.QAbstractItemView.SelectRows)
# set the signal and slot using selectionModel:
self.robot_table.selectionModel().currentRowChanged.connect(
self.tbRobotChanged)
self.robot_table.selectionModel().currentRowChanged.connect(
self.updateActiveRobot)
# implement a statusbar to test the table selection functionality:
self.statusBar().showMessage("Ready")
rf.setLayout(hbox)
3.slot to get the selected row's data:
def tbRobotChanged(self, currentCell):
# get the selected cell's index from currentCell,Implement the Slot method
tablemodel= currentCell.model()
#get the model of the currentcell
firstColumn = tablemodel.index(currentCell.row(),0)
#change it to the first column in the row with the robot name
self.statusBar().showMessage("Selected Robot is " +
firstColumn.data().toString())
self.selected_robot = int(firstColumn.data().toString())
# show the selected robot name in the statusbar
Related
I do have a problem while updating my content on-demand.
My scenario: I want to create a GUI which stores information about some products and it should be displayed via a scroll are. This is working fine, but when I want to update the information like the quantity of my item, the GUI or the layout of my QVBoxLayout does not update the new information on the screen.
My code for the update function:
#pyqtSlot(GroupContent)
def _updateData(self, box: GroupContent) -> None:
prompt = UpdatePrompt(self._conn, box)
prompt.exec()
amount = self._top_layout.count()
for i in range(amount):
tmp = self._top_layout.itemAt(i).widget()
if tmp.id != box.id:
continue
tmp.setTitle(box.title)
tmp.lblPrice.setText(str(box.price))
tmp.lblQuantity.setText(str(box.quantity))
The param box does already get the updated information from a QDialog.
The self._top_layout variable will be created with self._top_layout = QVBoxLayout().
I already tried to call update on the Mainwindow and also on the top layout.
Also tried directly accessing the widget with self._top_layout.itemAt(i).widget().setTitle('test') for example.
If this information is necessary, here is my function to dynamic generate the groupboxes:
def _populate(self, insert: Boolean = False) -> None:
data = self._getDBData(insert)
for row in data:
group_box = GroupContent()
group_box.storage_id.connect(self._updateData)
group_box.id = row.storage_id
group_box.title = row.name
group_box.price = row.price
group_box.quantity = row.quantity
group_box.image = row.image
self._top_layout.addWidget(group_box)
I am building a user interface with several (as many as the user wants) tabular (spreadsheet-like) forms of user-specified size (but the size won't change once initialized). The user populates these tables either by copy-pasting data (usually from excel) or directly typing data to the cells. I am using the Tksheet Tkinter add-on.
It seems that there are several options in Tksheet to achieve the goal of opening an empty table of i rows and j columns:
a) set_sheet_data_and_display_dimensions(total_rows = None, total_columns = None).
This routine throws a TypeError. The error is raised in:
GetLinesHeight(self, n, old_method = False)
The subroutine expects the parameter n to be an integer, but receives a tuple.
The calling routine is sheet_display_dimensions, and the relevant line is:
height = self.MT.GetLinesHeight(self.MT.default_rh).
MT.default_rh is apparently a complex object, it can be an integer, but also a string or a tuple. Other routines that use it in Tksheet perform elaborate manipulation to make sure it is handed to the subroutine in integer form, but not so sheet_display_dimensions.
b) sheet_data_dimensions(total_rows = None, total_columns = None)
This seems to work programmatically, but does not display the table to the user.
One may add the line sheet_display_dimensions(i,j) but--you guessed it--this raises an error...
Sample code:
from tksheet import Sheet
import tkinter as tk
from tkinter import ttk
# This class builds and displays a test table. It is not part of the question but merely used to illustrate it
class SeriesTable(tk.Frame):
def __init__(self, master):
super().__init__(master) # call super class init to build frame
self.grid_columnconfigure(0, weight=1) # This configures the window's escalators
self.grid_rowconfigure(0, weight=1)
self.grid(row=0, column=0, sticky="nswe")
self.sheet = Sheet(self, data=[[]]) # set up empty table inside the frame
self.sheet.grid(row=0, column=0, sticky="nswe")
self.sheet.enable_bindings(bindings= # enable table behavior
("single_select",
"select_all",
"column_select",
"row_select",
"drag_select",
"arrowkeys",
"column_width_resize",
"double_click_column_resize",
"row_height_resize",
"double_click_row_resize",
"right_click_popup_menu",
"rc_select", # rc = right click
"copy",
"cut",
"paste",
"delete",
"undo",
"edit_cell"
))
# Note that options that change the structure/size of the table (e.g. insert/delete col/row) are disabled
# make sure that pasting data won't change table size
self.sheet.set_options(expand_sheet_if_paste_too_big=False)
# bind specific events to my own functions
self.sheet.extra_bindings("end_edit_cell", func=self.cell_edited)
self.sheet.extra_bindings("end_paste", func=self.cells_pasted)
label = "Change column name" # Add option to the right-click menu for column headers
self.sheet.popup_menu_add_command(label, self.column_header_change, table_menu=False, index_menu=False, header_menu=True)
# Event functions
def cell_edited(self, info_tuple):
r, c, key_pressed, updated_value = info_tuple # break the info about the event to individual variables
'''
updated_value checked here
'''
# passed tests
pass # go do stuff
def cells_pasted(self, info_tuple):
key_pressed, rc_tuple, updated_array = info_tuple # break the info about the event to individual variables
r, c = rc_tuple # row & column where paste begins
err_flag = False # will be switched if errors are encountered
'''
updated_array is checked here
'''
# passed tests
if err_flag: # error during checks is indicated
self.sheet.undo() # undo change
else:
pass # go do stuff
def column_header_change(self):
r, c = self.sheet.get_currently_selected()
col_name = sd.askstring("User Input", "Enter column name:")
if col_name is not None and col_name != "": # if user cancelled (or didn't enter anything), do nothing
self.sheet.headers([col_name], index=c) # This does not work - it always changes the 1st col
self.sheet.redraw()
# from here down is test code
tk_win = tk.Tk() # establish the root tkinter window
tk_win.title("Master Sequence")
tk_win.geometry("600x400")
tk_win.config(bg='red')
nb = ttk.Notebook(tk_win) # a notebook in ttk is a [horizontal] list of tabs, each associated with a page
nb.pack(expand=True, fill='both') # widget packing strategy
settings_page = tk.Frame(nb) # initiate 1st tab object in the notebook
nb.add(settings_page, text = "Settings") # add it as top page in the notebook
test = SeriesTable(nb) # creates a 1 row X 0 column table
nb.add(test, text = "Table Test") # add it as second page in the notebook
i = 4
j = 3
#test.sheet.set_sheet_data_and_display_dimensions(total_rows=i, total_columns=j) # raises TypeError
#test.sheet.sheet_data_dimensions(total_rows=i, total_columns=j) # extends the table to 4 X 3, but the display is still 1 X 0
#test.sheet.sheet_display_dimensions(total_rows=i, total_columns=j) # raises TypeError
test.sheet.insert_columns(j) # this works
test.sheet.insert_rows(i - 1) # note that we insert i-1 because table already has one row
test.mainloop()
I figured out a work-around with:
c)
insert_columns(j)
insert_rows(i - 1)
Note that you have to insert i-1 rows. This is because the sheet object is initiated with 1 row and 0 columns. (But does it say so in the documentation? No it does not...)
I've been working on this for a while and I can't find any information about adding a row to a window. I seen it done with pyside2 and qt, witch would work but the users are using multiple versions of Maya (2016 = pyside, 2017=pyside2).
I want it like adding a widget in in pyside. I done it where adding a row is a function like add row 1, add row 2, and add row 3 but the script get to long. I need to parent to rowColumnLayout and make that unique in order to delete that later. Also I have to query the textfield in each row. Maybe a for loop that adds a number to the row? I really don't know but this is what I have so far:
from maya import cmds
def row( ):
global fed
global info
item=cmds.optionMenu(mygroup, q=True, sl=True)
if item == 1:
cam=cmds.optionMenu(mygroup, q=True, v=True)
fed=cmds.rowColumnLayout(nc = 1)
cmds.rowLayout(nc=7)
cmds.text(l= cam )
cmds.text(l=u'Frame Range ')
start = cmds.textField('textField3')
cmds.text(l=u' to ')
finish = cmds.textField('textField2')
cmds.button(l=u'render',c='renderTedd()')
cmds.button(l=u'delete',c='deleteRow()')
cmds.setParent (fed)
def deleteRow ():
cmds.deleteUI(fed, layout=True)
if item == 2:
print item
global red
cam1=cmds.optionMenu(mygroup, q=True, v=True)
red = cmds.rowColumnLayout()
cmds.rowLayout(nc=7)
cmds.text(l= cam1 )
cmds.text(l=u'Frame Range ')
start = cmds.textField('textField3')
cmds.text(l=u' to ')
finish = cmds.textField('textField2')
cmds.button(l=u'render',c='renderTedd()')
cmds.button(l=u'delete',c='deleteRow2()')
cmds.setParent (red)
def deleteRow2 ():
cmds.deleteUI(red, control=True)
def cameraInfo():
info=cmds.optionMenu(mygroup, q=True, sl=True)
print info
def deleteRow ():
cmds.deleteUI(fed, control=True)
def getCamera():
layers=pm.ls(type="renderLayer")
for layer in layers:
pm.editRenderLayerGlobals(currentRenderLayer=layer)
cameras=pm.ls(type='camera')
for cam in cameras:
if pm.getAttr(str(cam) + ".renderable"):
relatives=pm.listRelatives(cam, parent=1)
cam=relatives[0]
cmds.menuItem(p=mygroup,label=str (cam) )
window = cmds.window()
cmds.rowColumnLayout(nr=10)
mygroup = cmds.optionMenu( label='Colors', changeCommand='cameraInfo()' )
getCamera()
cmds.button(l=u'create camera',aop=1,c='row ()')
cmds.showWindow( window )
This is totally doable with cmds. The trick is just to structure the code so that the buttons in each row know and can operate on the widgets in that row; once that works you can add rows all day long.
To make it work you want to do two things:
Don't use the string form of callbacks. It's never a good idea, for reasons detailed here
Do use closures to make sure your callbacks are referring to the right widgets. Done right you can do what you want without the overhead of a class.
Basically, this adds up to making a function which generates both the gui items for the row and also generates the callback functions -- the creator function will 'remember' the widgets and the callbacks it creates will have access to the widgets. Here's a minimal example:
def row_test():
window = cmds.window(title='lotsa rows')
column = cmds.columnLayout()
def add_row(cameraname) :
cmds.setParent(column)
this_row = cmds.rowLayout(nc=6, cw6 = (72, 72, 72, 72, 48, 48) )
cmds.text(l= cameraname )
cmds.text(l=u'Frame Range')
start = cmds.intField()
finish = cmds.intField()
# note: buttons always fire a useless
# argument; the _ here just ignores
# that in both of these callback functions
def do_delete(_):
cmds.deleteUI(this_row)
def do_render(_):
startframe = cmds.intField(start, q=True, v=True)
endframe = cmds.intField(finish, q=True, v=True)
print "rendering ", cameraname, "frames", startframe, endframe
cmds.button(l=u'render',c=do_render)
cmds.button(l=u'delete',c=do_delete)
for cam in cmds.ls(type='camera'):
add_row(cam)
cmds.showWindow(window)
row_test()
By defining the callback functions inside of add_row(), they have access to the widgets which get stored as start and finish. Even though start and finish will be created over and over each time the function runs, the values they store are captured by the closures and are still available when you click a button. They also inherit the value of cameraname so the rendering script can get that information as well.
At the risk of self-advertising: if you need to do serious GUI work using cmds you should check out mGui -- a python module that makes working with cmds gui less painful for complex projects.
this is a part of my code.
class AboutRelatedDialog(wx.Dialog):
def __init__(self,parent,list1,list2,list3):
wx.Dialog.__init__(self,parent,-1)
RelatedGrid = gridlib.Grid(self)
RelatedGrid.CreateGrid(sum(list2) + 1,5)
RelatedGrid.SetColLabelSize(1)
RelatedGrid.SetRowLabelSize(1)
RelatedGrid.SetCellSize(0,0,1,2)
RelatedGrid.SetCellAlignment(0,0,wx.ALIGN_CENTRE,wx.ALIGN_CENTRE)
RelatedGrid.SetCellValue(0,0,'label')
RelatedGrid.SetCellAlignment(0,2,wx.ALIGN_CENTRE,wx.ALIGN_CENTRE)
RelatedGrid.SetCellValue(0,2,'datasource')
RelatedGrid.SetCellAlignment(0,3,wx.ALIGN_CENTRE,wx.ALIGN_CENTRE)
RelatedGrid.SetCellValue(0,3,'data')
RelatedGrid.SetCellAlignment(0,4,wx.ALIGN_CENTRE,wx.ALIGN_CENTRE)
RelatedGrid.SetCellValue(0,4,'comment')
templist1 = ms.ExecQuery('SELECT RepGroup FROM RepGroup')
templist2 = []
for i in templist1:
j = i[0]
templist2.append(j)
for index in range(len(list3)):
RelatedGrid.SetCellAlignment(index + 1,1,wx.ALIGN_CENTRE,wx.ALIGN_CENTRE)
RelatedGrid.SetCellValue(index + 1,1,list3[index])
for i in range(sum(list2) + 1):
dbsource = gridlib.GridCellChoiceEditor(templist2)
RelatedGrid.SetCellEditor(i,2,dbsource)
#RelatedGrid.Bind(wx.EVT_CHOICE,self.EvtChoice,dbsource)
def EvtChoice(self,event):
print 1
and my code doesn't work,because i don't know how to bind a event for these combobox.
when i choose a datasource,i want to create another combobox to show the data that get from the RepGroup table in another cell.
so i must know how to get the event when i choose a datasoure.
You can't (easily) bind to the events generated by wxGrid editor controls, but you can handle EVT_GRID_CELL_CHANGED events generated by the grid itself whenever a value of one of its cells changes.
I am using PyQt for a simple application that reads from a log file with JSON formatted strings, and outputs them nicely in a table.
Everything is working as expected except when I try to emit a signal from a 'load' function. This signal is picked up by the main window, in a slot designed to resort the table with new information.
Without the signal emitted, the table populates fully and properly:
By uncommenting the self.emit so that the signal IS emitted, the table ends up being incomplete:
As you can see in the first image, the table is NOT sorted, but all fields are populated. In the second image, the table is sorted, but some fields are blank!
The code that populates the table and sends the signal:
#openLog function does stuff, then populates the table as follows
self.ui.tableWidget.setRowCount(len(entries))
self.ui.tableWidget.verticalHeader().setVisible(False)
for i, row in enumerate(entries):
for j, col in enumerate(row):
item = QtGui.QTableWidgetItem(col)
self.ui.tableWidget.setItem(i, j, item)
#When this is uncommented, the table ends up having a lot of blank cells.
#self.emit(QtCore.SIGNAL("updateSignal"))
The code for receiving the signal, and acting:
#main window class
#__init__
self.ui.tableWidget.connect(self,QtCore.SIGNAL("updateSignal"),self.updateTable)
def updateTable(self):
self.ui.tableWidget.sortItems(0,QtCore.Qt.DescendingOrder)
The program flow is called as : program_init->register_signal. User action to open log ->openLog function that populates table/emit signal->signal received/ resort table
For this method, I am using signals and slots, as if I do not, QT/Python throws a bunch of warnings about it not being safe to redraw the GUI/Pixmap from the function.
Question:
How can I make the QTableWidget sort on the column I desire, while also ensuring the table is fully populated?
I think solution is to disable sorting while populating table by calling QTableWidget.setSortingEnabled(False), and then restore sorting.
Example code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
class MainWindow(QtGui.QWidget):
updateSignal = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.table_widget = QtGui.QTableWidget()
self.button = QtGui.QPushButton('Populate')
self.button.clicked.connect(self.populate)
layout = QtGui.QVBoxLayout()
layout.addWidget(self.table_widget)
layout.addWidget(self.button)
self.setLayout(layout)
self.updateSignal.connect(self.update_table)
self.populate()
def populate(self):
nrows, ncols = 5, 2
self.table_widget.setSortingEnabled(False)
self.table_widget.setRowCount(nrows)
self.table_widget.setColumnCount(ncols)
for i in range(nrows):
for j in range(ncols):
item = QtGui.QTableWidgetItem('%s%s' % (i, j))
self.table_widget.setItem(i, j, item)
self.updateSignal.emit()
self.table_widget.setSortingEnabled(True)
def update_table(self):
self.table_widget.sortItems(0,QtCore.Qt.DescendingOrder)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
wnd = MainWindow()
wnd.resize(640, 480)
wnd.show()
sys.exit(app.exec_())
I've been working on something similar, but was not setting up a sort. I tried both ways, and both worked for me.
My list, is a list of dictionaries, so slightly different from yours.
I have created a tabledialog class, that contains my table widget, and this function, is called from my main window:
def setuptable(self, alist):
# setup variables
rows = len(alist)
cols = len(alist[0])
keys = ['number', 'name', 'phone', 'address'] # for dictonary order
# setup cols, rows
self.tableWidget.setRowCount(rows)
self.tableWidget.setColumnCount(cols)
# insert data
for row in range(rows):
for col in range(cols):
item = QtGui.QTableWidgetItem()
item.setText(alist[row][keys[col]] or '') # or '' for any None values
table.setItem(row, col, item)
keys = [item.title() for item in keys] # capitalize
self.tableWidget.setHorizontalHeaderLabels(keys) # add header names
self.tableWidget.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignLeft) # set alignment
self.tableWidget.resizeColumnsToContents() # call this after all items have been inserted
self.tableWidget.sortItems(1,QtCore.Qt.AscendingOrder)
Also tried using, at the end of my tablesetup function:
self.emit(QtCore.SIGNAL("loadingDone"))
and setup the slot in my main window, in the init section:
# setup the dialog
import dialogtable
self.tabledialog = dialogtable.dialogtable()
# signal from table dialog
self.tabledialog.connect(self.tabledialog,QtCore.SIGNAL("loadingDone"),self.tableSort)
And the function called:
def tableSort(self):
self.tabledialog.tableWidget.sortItems(1,QtCore.Qt.AscendingOrder)
My tablewidget setup functions:
# set table widget attributes
self.tableWidget.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked) # use NoEditTriggers to disable editing
self.tableWidget.setAlternatingRowColors(True)
self.tableWidget.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.tableWidget.verticalHeader().setDefaultSectionSize(18) # tighten up the row size
self.tableWidget.horizontalHeader().setStretchLastSection(True) # stretch last column to edge
self.tableWidget.setSortingEnabled(True) # allow sorting
I don't bother ever set sorting to false, as the answer above mine recommends.