PyQt4: Databinding? - python

Coming from the .NET world over to Python and PyQt4. Was wondering if anyone is familiar with any functionality that would allow me to bind data to Qt widgets? For example (using sqlalchemy for data):
gems = session.query(Gem).all()
list = QListWidget()
list.datasource = gems
Is such a thing possible?

Although not a direct replacement, you might find it useful to look at the QDataWidgetMapper class:
http://pyqt.sourceforge.net/Docs/PyQt4/qdatawidgetmapper.html
If you're not scared of reading C++ code, this example might also prove to be helpful:
https://doc.qt.io/qt-4.8/qt-sql-sqlwidgetmapper-example.html
Note that the mapper operates within Qt's Model/View framework. In this example, the model just happens to be a SQL database model.

One option would have a function that returns a list (or tuple) object from a query, and then use that to update the QListWidget. Remember that the QListWidget stores QListStrings. Your update function might look like this:
def updateQListWidget(qlistwidget, values):
""" Updates a QListWidget object with a list of values
ARGS:
qlistwidget - QListWidget object
values - list of values to add to list widget
"""
qlistwidget.clear()
qlist = QtCore.QStringList()
for v in values:
s = QtCore.QString(v)
qlist.append(s)
qlistwidget.addItems(qlist)

Related

What is the best way for allowing for dynamic object creation and removal in a Plotly Dash dashboard?

Currently, I have a class which stores a dictionary of Card elements, each of which is unique. The class can also generate these cards and append them to the dictionary or remove a card from a dictionary. However, I am not sure how to best allow for this action through a callback function since the ID for a card doesn't exist until the card is made, and the functionality isn't directly within the Dash framework since a dictionary object acts as an intermediary where the objects are stored.
Basically, I am wondering what the best way to dynamically create and destroy objects with a callback is?
Thank you!
Assuming you want to avoid extra computation for building cards you wont use, I'd suggest create a which creates each card and store those functions in a dictionary. (You can also create a universal function with params that allow specificity)
my_card_functions = {
'id_1': make_id_1,
'id_2': make_id_2,
}
Creating a card could be done as such:
my_id = 'id_1'
f = my_card_functions[my_id] # will break if id isn't registered
my_card = f()
You can store the cards you want to create in a dcc.store object. Here's an example of some code you might consider:
# Pretend these are structured properly
dcc.Store(id='cards_data')
html.Div(id='my_cards',children=[])
#app.callback(
Output('my_cards','children'),
[Input('cards_data','data')],
[State('my_cards','children')]
)
def make_cards(data, children):
"""Makes cards for each id"""
if not data:
raise PreventUpdate
# The following code is not correct but serves as a demonstrative example
# Some data structure manipulation is necessary to access
# the ids from the children elements
children_ids = [x['id'] for x in children]
# Assuming your data looks something like this:
# {'add':['id_1','id_2']}
for x in data['add']:
if x not in children_ids:
f = my_card_functions[my_id]
my_card = f()
# Store values
children.append(my_card)
return children
Note, this approach does not resolve removal of cards. That could easily be done but would probably require a more dynamic use of caching.
Just on the basis of your question, I have some immediate suggestions (since there is no working code that you have posted).
1. Generate all card elements by default. They can be generated, but not 'displayed'
2. Add your callbacks to toggle the display/rendering of the cards dynamically based on the use case. That way you will have card element ids to play around with in the callbacks.
Hope this helps.

Using TraitsUI ListEditor Factory for arbitrary collections

How can I adapt a ListEditor to list the contents of an arbitrary collection using TraitsUI? Here is a sample code
from traits.api import HasStrictTraits, Instance, Int, List, Str
from traitsui.api import View, Item, ListEditor, InstanceEditor
from sortedcontainers import SortedListWithKey
class Person(HasStrictTraits):
name = Str
age = Int
class Office(HasStrictTraits):
# employees = Instance(SortedListWithKey,
kw={'key': lambda employee: employee.age})
employees = List
employee_view = View(
Item(name='name', show_label=False, style='readonly')
)
office_view = View(
Item(name='adults',
show_label=False,
style='readonly',
editor=ListEditor(
style='custom',
editor=InstanceEditor(view=employee_view),
),
),
resizable=True
)
employee_list = [Person(name='John', age=31), Person(name='Mike', age=31),
Person(name='Jill', age=37), Person(name='Eric', age=28)]
#office = Office()
#office.employees.update(employee_list)
office = Office(employees=employee_list)
office.configure_traits(view=office_view)
If I replace the standard list with SortedListWithKey by using the code I commented out, I get the 'AttributeError: 'Office' object has no attribute 'value'' error. How can I resolve this?
Traits uses a list subclass (TraitListObject) for anything stored in a List trait: this is what allows trait events to be fired on changes to items in the list as well as to the attribute. I'm guessing that the SortedListWithKey class is from the "Sorted Containers" third-party package and so isn't a Traits list. The ListEditor expects a TraitsListObject (or a work-alike) for it to work properly, since it needs to know if the list items have changed.
Fixes/work-arounds that I can think of:
Using two List traits, one unsorted (which could be a Set) and one sorted, and have trait change handlers to synchronize the two. This sort of pattern works well if your unordered data is part of the "model" layer and the way it is sorted is part of the user-facing "view" or "presentation" layer (ie. possibly in a TraitsUI Controller or ModelView object).
Write a subclass of TraitListObject that has the self-sorting behaviour of SortedListWithKey. Use a regular List trait but assign instances of your subclass into it, or for really slick behaviour subclass List to do conversion to your new subclass on any set operation.
Use a regular List trait, but a TableEditor with columns for the name and age: this is a different UI from what you are intending, and may not suit what your real-world is, but the TableEditor can be set to auto-sort on columns. For more simple examples, the ListStrEditor may also work.
Add functionality to the TraitsUI ListEditor so that the list items are optionally displayed in a sorted order. This is probably the most difficult option.
While it is clearly the least elegant solution, I'd probably just go with the first in most cases. You might also consider posting this question on the ETS-Users group to see if anyone else has some thoughts on it.

How do I separate the functions from the gui class in PySide?

In my PySide project I have 3 files:
one that contains all the gui stuff converted to python from Qt Designer,
another which has the signals, all the logic and functions and
one more that starts the whole application.
I think it's better to separate the logic from the functions.
The following is a simple function inserting items in a tablewidget:
# my_functions.py
def fill_table():
for row in range(10):
for col in range(10):
item_value = "...."
item = QtGui.QTableWidgetItem()
item.setText(str(item_value))
table_widget.setItem(row, col, item)
My main problem is how would you reference a widget from your application in a separate module.
Assuming my_functions.py is second item in your list of files, it appears that fill function uses a module-global table_widget. I don't recommend that but if that's really what you want, then just access it. So with
# my_functions.py
table_widget = ... # somewhere in that file, not shown in your post
def fill_table():
...populate table_widget...
Then:
# main.py
import my_functions
...
... somewhere, use my_functions.table_widget...
Better would be to define a custom class in my_functions.py, instantiate in main.py, and make fill_table() a method on custom class:
# my_functions.py
class MyTableWidget(QTableWidget):
...
def fill(self):
for row in range(10):
for col in range(10):
item_value = "...."
item = QtGui.QTableWidgetItem()
item.setText(str(item_value))
self.setItem(row, col, item)
# main.py
from my_functions import MyTableWidget
table = MyTableWidget()
table.fill()
There are lots of ways, but basically it seems that your current design is procedural rather than object-oriented. Nothing wrong with that, but you will find it rather clashes with the rest of PyQt and Python after a while, and is not as easy to maintain and debug once your app passes the stage of prototyping. So I recommend second approach.
The objects in a typical Qt application are connected together in parent/child relationships. Most often, there is a top-level main-window which functions as the root object, with all the other objects (widgets, layouts, etc) arranged in a hierarchy below it.
Given this, it is very natural to put all the gui-related program logic in the main-window class, because all the other objects will then be accessible via self. But if you put all the gui-related logic into functions in separate modules, there is no self available. So it would be up to you to provide that missing functionality.
The most obvious way to do this would be to keep a reference to the top-level window in the module that starts the application, so that the other modules can import it:
from app_module import main_window
# my_functions.py
def fill_table():
for row in range(10):
for col in range(10):
item_value = "...."
item = QtGui.QTableWidgetItem()
item.setText(str(item_value))
main_window.table_widget.setItem(row, col, item)
Alternatively, you could re-design all the functions so that they operate on only one object (or class of objects), and then explicitly pass in an instance:
# my_functions.py
def fill_table(table_widget):
...
However, whatever way you do it, it's hard to see how this could ever be the "best" way to structure the code.
Most gui applications consist of several largish sub-components that work more-or-less independently of one another, along with a central controller/manager that organises them all into a single functioning unit. The sub-components will usually be widget sub-classes (probably living in separate modules), which will become children of a main-window which may also function as the controller/manager. Organising your code along these lines is much more in line with the way Qt is designed to work, and will automatically avoid most of the potential communication problems between the various parts of the application.

QCompleter on QLineEdit for parts of the inserted text

I made a QLineEdit for reading an infix maths expression. Operators are limited to the +-*/ and brackets. Values can be numeric or a variable name representing a numeric value. I want to autocomplete for variable names.
The problem is that apparently simple QComplete only works for single pre-defined words/phrases. They don't work in between other words (As you might expect to do when modifying an expression).
I tried reading the Tree Model Completer, but since I'm programming in Python that wasn't too helpful to me. Does anyone know of a simple Tree Model Completer example coded in python?
After reading ekhumoros comment I decided to make a short example for a custom Completer.
Here is the example:
from PySide import QtGui
class CustomCompleter(QtGui.QCompleter):
def __init__(self):
super().__init__()
def splitPath(self, path):
if path.endswith('ha'):
self.setModel(QtGui.QStringListModel([path + 'llo']))
return [path]
app = QtGui.QApplication([])
e = QtGui.QLineEdit()
c = CustomCompleter()
e.setCompleter(c)
e.show()
app.exec_()
Everytime the text ends with 'ha' it proposes to continue it with 'llo'. It looks for example like:
All of the work is done in splitPath(path) of QCompleter which is called twice(?) everytime I change the text of my edit field. After some processing of the text one should set the model new with a simple string list containing one or more proposals. It seems the model has to be set again everytime. See also QCompleter Custom Completion Rules.
This is not yet the full formula parsing and variable names completion, but a reasonable step towards this. It just explains how QCompleter can be used for that goal. To summarize: Subclass QCompleter and put all your custom logic into splitpath().

A sorted and filtered treemodel in Python Gtk+3..?

I am trying to get a treemodel (a liststore in fact) that can be filtered and also sorted. I have the following piece of code
self.modelfilter = self.liststore.filter_new()
self.modelfilter.set_visible_func(\
self._visible_filter_function)
self.treeview.set_model(self.modelfilter)
where self.liststore and self.treeview are standard Gtk.ListStore and Gtk.TreeView objects that I get from a glade file, and self._visible_filter_function is a filtering function.
The problem is that self.modelfilter does not seem to be sortable. When I click on the column headers (of the columns in self.treeview) to sort them, I get
Gtk-CRITICAL **: gtk_tree_sortable_get_sort_column_id: assertion `GTK_IS_TREE_SORTABLE (sortable)' failed
saying that the treemodel is not sortable.
This problem seems to be surmountable in PyGtk as suggested here. The idea is to stack a ListStore, a TreeModelFilter and a TreeSortFilter one inside the other and feed the last one as the model for the treeview.
However this trick does not seem to be working in Python Gtk+3. When I try
self.modelfilter = self.liststore.filter_new()
self.modelfilter.set_visible_func(\
self._visible_filter_function)
self.sorted_and_filtered_model = \
Gtk.TreeModelSort(self.modelfilter)
self.treeview.set_model(self.sorted_and_filtered_model)
it complains
Gtk.TreeModelSort(self.modelfilter)
TypeError: GObject.__init__() takes exactly 0 arguments (1 given)
Now I tried to get a instance of Gtk.TreeModelSort with no arguments. But this instance does not have any set_model method.
I am lost here.
Is there another way to set the model for Gtk.TreeModelSort? Or is there a totally different way to get a filtered and sortable treemodel object that can be displayed in a treeview?
>>> from gi.repository import Gtk
>>> mymodel = Gtk.ListStore()
>>> Gtk.TreeModelSort(model=mymodel)
<TreeModelSort object at 0x1d4d190 (GtkTreeModelSort at 0x1f0d3f0)>
In my opinion PyGObject is not ready yet. It has no browsable documentation, some things are not instrospected yet and in particular this:
Sometimes a widget work with Gtk.MyWidget(attr=foo), like this one.
Sometimes with Gtk.MyWidget.new_with_label('Foo'), like buttons. Yes, Gtk.MyWidget(label='Foo') doesn't work.
Kind regards

Categories

Resources