How to clear both widgets and their out by other widget - python

I load the list of files with checkbox to be filtered using ipywidgets:
from ipywidgets import Checkbox, interact, Layout, Button
import ipywidgets as widgets
import glob
from traitlets import traitlets
from IPython.display import display, clear_output
class LoadedButton(widgets.Button):
def __init__(self, value=None, *args, **kwargs):
super(LoadedButton, self).__init__(*args, **kwargs)
# Create the value attribute.
self.add_traits(value=traitlets.Any(value))
def file_list(**all_kwargs):
cols = [k for k, v in all_kwargs.items() if v==True]
return cols
def reload_files(rr):
for c in all_files:
c.close()
clear_output()
print('Unselect the csv files above *to EXCLUDE*')
rr.value =interact(file_list, **all_kwargs)
return rr
extension = 'csv' # extention of file you want to read, csv, dat, etc.
all_file_list = [i for i in glob.glob('*.{}'.format(extension))]
all_files = [Checkbox(description=a, value=True) for a in all_file_list ]
all_kwargs = {c.description: c.value for c in all_files}
lb = LoadedButton(description="Load/reload file list", value=None)
lb.on_click(reload_files)
display(lb)
I wish to overwrite the previous outputs (both widget and widget outputs) for every time I click on button created using the Button widget, but instead of overwriting it creates another instance of output. I tried both clear_output and widget.close() options, but nothing helps. I know clear_output is not expected to clear the widget, but I expect this to be possible using close(). Does anyone know how to clear/overwrite the ipywidgets as well ipywidget outputs when reloading by button?

If I understand the question correctly I think that this might work for you;
from ipywidgets import Checkbox, interact, Layout, Button
import ipywidgets as widgets
import glob
from traitlets import traitlets
from IPython.display import display, clear_output
class LoadedButton(widgets.Button):
def __init__(self, value=None, *args, **kwargs):
super(LoadedButton, self).__init__(*args, **kwargs)
# Create the value attribute.
self.add_traits(value=traitlets.Any(value))
def file_list(**all_kwargs):
cols = [k for k, v in all_kwargs.items() if v==True]
return cols
def reload_files(rr):
if rr.value is not None:
# print("Reloading...")
# Reset the panel to just the button.
panel.children = [panel.children[0]]
rr.value = None
if rr.value is None:
extension = 'csv' # extention of file you want to read, csv, dat, etc.
all_file_list = [i for i in glob.glob('*.{}'.format(extension))]
all_files = [Checkbox(description=a, value=True) for a in all_file_list ]
all_kwargs = {c.description: c.value for c in all_files}
rr.value=widgets.interactive(file_list, **all_kwargs)
panel.children += (rr.value,)
# display(rr.value)
lb = LoadedButton(description="Load/reload file list", value=None)
lb.on_click(reload_files)
# A VBox for the Button and checkboxes.
panel = widgets.VBox([lb])
panel
I think that the problem was coming from relying on the display function to represent the widget. When you have one widget modified the visibilty of another the best thing that you could probably do is add and remove them from a layout widget like VBox or HBox. Either way let me know how it works out.
Update:
glob and all other file name operations also need to update when the button is clicked. so it does that now.
Also here is how to access the checkboxes on the interactive widget';
panel.children[1].children
OR
lb.value.children
Both statements refer to the same object.

Related

How to create a toogle button to show and clear output using ipywidgets?

I want to create a toggle button to display some output and clear it when switched in jupyter notebook using ipywidget.
I tried to do it using widgets.Output(). This however displays empty lines as I click more and more times.
Please help me out.
import ipywidgets as w
toggle = w.ToggleButton(description='click me')
out = w.Output()
def fun(obj):
global out
with out:
display('asd')
if obj['new']:
display(out)
else:
out.clear_output()
out = w.Output()
toggle.observe(fun, 'value')
display(toggle)
I think the combination of global keyword with the display(out) is causing the issue here. I've simplified a little bit below:
Display the output widget in the main body of your code, and use the clear_output command (the output widget is still 'visible' but is zero height).
import ipywidgets as w
toggle = w.ToggleButton(description='click me')
out = w.Output(layout=w.Layout(border = '1px solid black'))
def fun(obj):
with out:
if obj['new']:
display('asd')
else:
out.clear_output()
toggle.observe(fun, 'value')
display(toggle)
display(out)

Python onclick button widget return object

I am trying to build a file/data selector in jupyter notebooks with python. The idea is that I select some files and data channels in the files with the multipleSelect widget and then with a button return a dataFrame.
How can I access the df_object?
#stack example
from ipywidgets import widgets
from IPython.display import display
from IPython.display import clear_output
import pandas as pd
import numpy as np
filenames = ["file1", "file2"]
file_dict = {
"file1":pd.DataFrame(np.arange(5)),
"file2":pd.DataFrame(np.arange(10,15))
}
def data_selection():
sel_file = widgets.SelectMultiple(description="Files",
options=filenames)
display(sel_file)
button = widgets.Button(description="OK")
display(button)
def on_button_clicked(button):
clear_output(wait=True) #clears the previous output
display(sel_file) #displays new selection window
display(button) #displays new button
for f in sel_file.value:
print (f)
display (file_dict[f])
#global df_object #would be a solution but not recommended for sure
df_object = file_dict[f]
return df_object #doesn't work
button.on_click(on_button_clicked)
data_selection()
You really should be using a class for this, and then define all your functions as acting on an instance of that class. Not all of them need to be publicly accessible as well. You can also store the df_objects in a separate attribute like a dictionary and access the dictionary using a separate function. Check out the code below:
class foo(object):
def __init__(self, file1, file2):
self.filenames = [file1, file2]
self.file_dict = {
file1:pd.DataFrame(np.arange(5)),
file2:pd.DataFrame(np.arange(10,15))
}
def _create_widgets(self):
self.sel_file = widgets.SelectMultiple(description='Files',
options=self.filenames,
value=[self.filenames[0]],
)
self.button = widgets.Button(description="OK")
self.button.on_click(self._on_button_clicked)
def _on_button_clicked(self, change):
self.out.clear_output()
self.df_objects = {}
with self.out:
for f in self.sel_file.value:
print(f)
display(self.file_dict[f])
self.df_objects[f] = self.file_dict[f]
def display_widgets(self):
self._create_widgets()
self.out = widgets.Output() # this is the output widget in which the df is displayed
display(widgets.VBox(
[
self.sel_file,
self.button,
self.out
]
)
)
def get_df_objects(self):
return self.df_objects
Then you can create instances and display the widgets like so:
something = foo('a', 'b')
something.display_widgets()
something.get_df_objects() will return a dictionary with the required 'file:dataframe_of_file' key-value pairs.
Hope this helps :)

Bokeh - How to have the same widget (or duplicate a widget) in two different tabs?

I'm trying to create a widget filter (made up of TextInput and MultiSelect) that is replicated on two different Bokeh Tabs. The desired functionality is that filtering results should be preserved between tabs, regardless of which filter receives the text to filter off of.
The code below(it is working code) builds the Filter widget which is instantiated as filter1 and filter2. The callback is the update function which does the actual filtering and updates the MultiSelect part of the filter.
from bokeh.io import curdoc
from bokeh.layouts import column, widgetbox, row, layout, gridplot
from bokeh.models import Slider, Select, TextInput, MultiSelect
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial
df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])
multiselect = None
input_box = None
def update(widget, attr, old, new):
print("df['fruits']: {}".format(list(df['fruits'])))
print("{} : {} changed: Old [ {} ] -> New [ {} ]".format(widget, attr, old, new))
if widget == 'input':
col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
print("col_date: {}".format(col_data))
multiselect.update(options = sorted(list(col_data)))
def init():
global multiselect
multiselect = MultiSelect(title = 'multiselect',
name = 'multiselect',
value = [],
options = list(df["fruits"]))
multiselect.on_change('value', partial(update, multiselect.name))
global input_box
input_box = TextInput(title = 'input',
name ='input',
value='Enter you choice')
input_box.on_change('value', partial(update, input_box.name))
class Filter:
def __init__(self):
self.multiselect = multiselect
self.input_box = input_box
self.widget = widgetbox(self.input_box, self.multiselect)
init()
filter1 = Filter().widget
filter2 = Filter().widget
curdoc().add_root(row(filter1, filter2))
The code above produces/assembles the widget as shown here:
Also, the functionality of the two mirrored filters is as desired; when text is entered in one of the boxes, the results are displayed on both filters.
Now, and here is where I need help, I want the same filters with the same functionality but I need them in two different tabs; one filter in one tab and the other filter in the other tab.
The code used to build the two tabs structure is:
p1 = Panel(child = filter1, title = "Panel1")
p2 = Panel(child = filter2, title = "Panel2")
tabs = Tabs(tabs=[ p1, p2 ])
curdoc().add_root(layout(tabs))
On the results side, the code preserves the desired functionality but filters are displayed on the same page. More than that, panels/tabs are not even being built.
Any idea what's missing? (If you want to play with the code it should work right off the bat if you have bokeh installed.)
I do not think your example should even build a document, both your textinputs and multiselect models have the same id, which may be why the display of tabs gets messed up.
My solution is similar to HYRY's, but with a more general function to share attributes using two different things:
model.properties_with_values()
Can be used with any bokeh model and returns a dictionary of all the attribute:value pairs of the model. It's mostly useful in ipython to explore bokeh objects and debug
Document.select({'type':model_type})
Generator of all the widgets of the desired type in the document
Then I just filter out the widgets that do not share the same tags as the input widget, which would avoid "syncing" other inputs/multiselect not generated with box_maker(). I use tags because different models cannot have the same name.
When you change a TextInput value, it will change the associated Multiselect in the update function, but it will also change all the other TextInputs and trigger their update in the same way too. So each Input triggers update once and changes the options of their respective multiselect (and not multiplte times each because it's a "on_change" callback, if you give the same value for the new input it does not trigger).
For the Multiselect the first trigger of update will do the job, but since it changed the values of the other Multiselect it still triggers as many times as there are Multiselect widgets.
from bokeh.io import curdoc
from bokeh.layouts import widgetbox
from bokeh.models import TextInput, MultiSelect
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial
df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])
def sync_attr(widget):
prop = widget.properties_with_values() # dictionary of attr:val pairs of the input widget
for elem in curdoc().select({'type':type(widget)}): # loop over widgets of the same type
if (elem!=widget) and (elem.tags==widget.tags): # filter out input widget and those with different tags
for key in prop: # loop over attributes
setattr(elem,key,prop[key]) # copy input properties
def update(attr,old,new,widget,other_widget):
print("\ndf['fruits']: {}".format(list(df['fruits'])))
print("{} : {} changed: Old [ {} ] -> New [ {} ]".format(str(widget),attr, old, new))
if type(widget)==TextInput:
col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
print("col_date: {}".format(col_data))
other_widget.update(options = sorted(list(col_data)))
sync_attr(widget)
def box_maker():
multiselect = multiselect = MultiSelect(title = 'multiselect',tags=['in_box'],value = [],options = list(df["fruits"]))
input_box = TextInput(title = 'input',tags=['in_box'],value='Enter you choice')
multiselect.on_change('value',partial(update,widget=multiselect,other_widget=input_box))
input_box.on_change('value',partial(update,widget=input_box,other_widget=multiselect))
return widgetbox(input_box, multiselect)
box_list = [box_maker() for i in range(2)]
tabs = [Panel(child=box,title="Panel{}".format(i)) for i,box in enumerate(box_list)]
tabs = Tabs(tabs=tabs)
curdoc().add_root(tabs)
Note that the highlighting of the options in multiselect may not look consistent, but that just seems to be visual as the values/options of each of them are changing correctly.
But unless you are particularly attached to the layout look when you put the widgets inside the panels, you could just put one input and multiselect outside, and write their callbacks to deal with what will be in the different panels.
You can't use the same widget model to create multiple views. You can create new widgets in every tabs and link the value:
from bokeh.io import curdoc
from bokeh.layouts import column, widgetbox, row, layout, gridplot
from bokeh.models import Slider, Select, TextInput, MultiSelect, CustomJS
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial
df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])
class Filter:
def __init__(self):
self.multiselect = MultiSelect(title = 'multiselect',
name = 'multiselect',
value = [],
options = list(df["fruits"]))
self.multiselect.on_change('value', self.selection_changed)
self.input_box = TextInput(title = 'input',
name ='input',
value='Enter you choice')
self.input_box.on_change('value', self.input_box_updated)
self.widget = widgetbox(self.input_box, self.multiselect)
def input_box_updated(self, attr, old, new):
print(attr, old, new)
col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
self.multiselect.update(options = sorted(list(col_data)))
def selection_changed(self, attr, old, new):
print(new)
filter1 = Filter()
filter2 = Filter()
def link_property(property_name, *widgets):
wb = widgetbox(*widgets)
wb.tags = [property_name, 0]
def callback(widgets=wb):
if widgets.tags[1] != 0:
return
widgets.tags[1] = 1
for widget in widgets.children:
widget[widgets.tags[0]] = cb_obj.value
widgets.tags[1] = 0
jscallback = CustomJS.from_py_func(callback)
for widget in widgets:
widget.js_on_change(property_name, jscallback)
link_property("value", filter1.input_box, filter2.input_box)
link_property("value", filter1.multiselect, filter2.multiselect)
p1 = Panel(child = filter1.widget, title = "Panel1")
p2 = Panel(child = filter2.widget, title = "Panel2")
tabs = Tabs(tabs=[ p1, p2 ])
curdoc().add_root(layout(tabs))
It seems that there is a bug in MultiSelect that doesn't deselect previous items.

Uploading files using Browse Button in Jupyter and Using/Saving them

I came across this snippet for uploading files in Jupyter however I don't know how to save this file on the machine that executes the code or how to show the first 5 lines of the uploaded file. Basically I am looking for proper commands for accessing the file after it has been uploaded:
import io
from IPython.display import display
import fileupload
def _upload():
_upload_widget = fileupload.FileUploadWidget()
def _cb(change):
decoded = io.StringIO(change['owner'].data.decode('utf-8'))
filename = change['owner'].filename
print('Uploaded `{}` ({:.2f} kB)'.format(
filename, len(decoded.read()) / 2 **10))
_upload_widget.observe(_cb, names='data')
display(_upload_widget)
_upload()
_cb is called when the upload finishes. As described in the comment above, you can write to a file there, or store it in a variable. For example:
from IPython.display import display
import fileupload
uploader = fileupload.FileUploadWidget()
def _handle_upload(change):
w = change['owner']
with open(w.filename, 'wb') as f:
f.write(w.data)
print('Uploaded `{}` ({:.2f} kB)'.format(
w.filename, len(w.data) / 2**10))
uploader.observe(_handle_upload, names='data')
display(uploader)
After the upload has finished, you can access the filename as:
uploader.filename
I am working on ML with Jupyter notebook, and I was looking for a solution to select the local files containing the datasets by browsing amongst the local file system. Although, the question here refers more to uploading than selecting a file. I am putting here a snippet that I found here because when I was looking for a solution for my particular case, the result of the search took me several times to here.
import os
import ipywidgets as widgets
class FileBrowser(object):
def __init__(self):
self.path = os.getcwd()
self._update_files()
def _update_files(self):
self.files = list()
self.dirs = list()
if(os.path.isdir(self.path)):
for f in os.listdir(self.path):
ff = os.path.join(self.path, f)
if os.path.isdir(ff):
self.dirs.append(f)
else:
self.files.append(f)
def widget(self):
box = widgets.VBox()
self._update(box)
return box
def _update(self, box):
def on_click(b):
if b.description == '..':
self.path = os.path.split(self.path)[0]
else:
self.path = os.path.join(self.path, b.description)
self._update_files()
self._update(box)
buttons = []
if self.files:
button = widgets.Button(description='..', background_color='#d0d0ff')
button.on_click(on_click)
buttons.append(button)
for f in self.dirs:
button = widgets.Button(description=f, background_color='#d0d0ff')
button.on_click(on_click)
buttons.append(button)
for f in self.files:
button = widgets.Button(description=f)
button.on_click(on_click)
buttons.append(button)
box.children = tuple([widgets.HTML("<h2>%s</h2>" % (self.path,))] + buttons)
And to use it:
f = FileBrowser()
f.widget()
# <interact with widget, select a path>
# in a separate cell:
f.path # returns the selected path
4 years later this remains an interesting question, though Fileupload has slightly changed and belongs to ipywidgets....
Here is some demo that shows how to get the file/files after the button click and reset the button to get more files....
from ipywidgets import FileUpload
def on_upload_change(change):
if not change.new:
return
up = change.owner
for filename,data in up.value.items():
print(f'writing [{filename}] to ./')
with open(filename, 'wb') as f:
f.write(data['content'])
up.value.clear()
up._counter = 0
upload_btn = FileUpload()
upload_btn.observe(on_upload_change, names='_counter')
upload_btn
And here is a "debug" version that shows what is going on / why things work...
from ipywidgets import FileUpload
def on_upload_change(change):
if change.new==0:
print ('cleared')
return
up = change.owner
print (type(up.value))
for filename,data in up.value.items():
print('==========================================================================================')
print(filename)
for k,v in data['metadata'].items():
print(f' -{k:13}:[{v}]')
print(f' -content len :[{len(data["content"])}]')
print('==========================================================================================')
up.value.clear()
up._counter = 0
upload_btn = FileUpload()
upload_btn.observe(on_upload_change, names='_counter')
upload_btn
I stumbled into this thread ~2 years late. For those still confused about how to work with the fileupload widget I have built off of the excellent answer posted by minrk with some other usage examples below.
from IPython.display import display
import fileupload
uploader = fileupload.FileUploadWidget()
def _handle_upload(change):
w = change['owner']
with open(w.filename, 'wb') as f:
f.write(w.data)
print('Uploaded `{}` ({:.2f} kB)'.format(
w.filename, len(w.data) / 2**10))
uploader.observe(_handle_upload, names='data')
display(uploader)
From the widget documentation:
class FileUploadWidget(ipywidgets.DOMWidget):
'''File Upload Widget.
This widget provides file upload using `FileReader`.
'''
_view_name = traitlets.Unicode('FileUploadView').tag(sync=True)
_view_module = traitlets.Unicode('fileupload').tag(sync=True)
label = traitlets.Unicode(help='Label on button.').tag(sync=True)
filename = traitlets.Unicode(help='Filename of `data`.').tag(sync=True)
data_base64 = traitlets.Unicode(help='File content, base64 encoded.'
).tag(sync=True)
data = traitlets.Bytes(help='File content.')
def __init__(self, label="Browse", *args, **kwargs):
super(FileUploadWidget, self).__init__(*args, **kwargs)
self._dom_classes += ('widget_item', 'btn-group')
self.label = label
def _data_base64_changed(self, *args):
self.data = base64.b64decode(self.data_base64.split(',', 1)[1])
Get the data in bytestring format:
uploader.data
Get the data in a regular utf-8 string:
datastr= str(uploader.data,'utf-8')
Make a new pandas dataframe from the utf-8 string (e.g. from a .csv input):
import pandas as pd
from io import StringIO
datatbl = StringIO(datastr)
newdf = pd.read_table(datatbl,sep=',',index_col=None)
You have to enable the file upload option in your code, to enable the browse button to appear in your notebook.
Run the following
!jupyter nbextension enable fileupload --user --py

Python PyQt - QTableWidget, JSON, and emitSignal causing blank cells

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.

Categories

Resources