Kivy: how to update a bool outside class - python-3.x

The bool is currently updated when I click on and off the checkbox however when I click the button it will always print the default value I define at the start. How can I update a bool that is outside the class, so that when I click the button and call the function, it updates the bool?
Also, how can I call a function like this when clicking the button, that is outside of the class?
Thanks
someBool = False
def someFunction():
print(someBool)
class MyGridLayout(GridLayout):
def __init__(self, **kwargs):
#grid layout constructor
super(MyGridLayout, self).__init__(**kwargs)
#set columns for the layout
self.cols = 2
self.add_widget(Label(text="checkbox"))
self.checkbox= CheckBox(active = False)
self.add_widget(self.checkbox)
#this will bind the label and checkbox
self.checkbox.bind(active = self.checkboxActive)
self.button= Button(text="Button")
self.button.bind(on_press=someFunction)
self.add_widget(self.button)
def checkboxActive(self, checkbox, value):
if value:
someBool = True
else:
someBool = False
class MyApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
MyApp().run()

If you want to change a value outside a function in python you have to specify its scope. It can be easily done with global variable.
Then for the button you have to pass an anonymous function to kivy on_press method linking it to the function you want. That way the press will trigger the function passed. So we will have to use lambda for this.
Try this code:
someBool = False
def someFunction():
global someBool
print(someBool)
class MyGridLayout(GridLayout):
def __init__(self, **kwargs):
# grid layout constructor
super(MyGridLayout, self).__init__(**kwargs)
# set columns for the layout
self.cols = 2
self.add_widget(Label(text="checkbox"))
self.checkbox = CheckBox(active=False)
self.add_widget(self.checkbox)
# this will bind the label and checkbox
self.checkbox.bind(active=self.checkboxActive)
self.button = Button(text="Button")
self.button.bind(on_press=lambda x: someFunction())
self.add_widget(self.button)
def checkboxActive(self, checkbox, value):
global someBool
if value:
someBool = True
else:
someBool = False
class MyApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
MyApp().run()

Related

PYQT QTableView Delegate can not show createEditor when applied with Proxy

I'm having problem to show the Editor Widget when Delegate is applied with the Proxy situation.
-> self.table.setModel(self.proxy)
If the Delegate is applied to the View/Model structure, then there is no problem at all.
-> #self.table.setModel(self.model)
Refer to: https://www.pythonfixing.com/2021/10/fixed-adding-row-to-qtableview-with.html
See the code below:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2", "3"]
def createEditor(self, parent, option, index):
if index.column() == 0:
comboBox = QComboBox(parent)
comboBox.addItems(self.type_items)
return comboBox
# no need to check for the other columns, as Qt automatically creates a
# QLineEdit for string values and QTimeEdit for QTime values;
return super().createEditor(parent, option, index)
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def appendRowData(self, data):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._data.append(data)
self.endInsertRows()
def data(self, index, role=Qt.DisplayRole):
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
def flags(self, index):
# allow editing of the index
return super().flags(index) | Qt.ItemIsEditable
class CustomProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._filters = dict()
#property
def filters(self):
return self._filters
def setFilter(self, expresion, column):
if expresion:
self.filters[column] = expresion
elif column in self.filters:
del self.filters[column]
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
for column, expresion in self.filters.items():
text = self.sourceModel().index(source_row, column, source_parent).data()
regex = QRegExp(
expresion, Qt.CaseInsensitive, QRegExp.RegExp
)
if regex.indexIn(text) == -1:
return False
return True
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
localWidget = QWidget()
self.table = QTableView(localWidget)
data = [["1", "Hi", QTime(2, 1)], ["2", "Hello", QTime(3, 0)]]
self.model = TableModel(data)
self.proxy = CustomProxyModel() # Customized Filter
self.proxy.setSourceModel(self.model)
#self.table.setModel(self.model) # Original code, for View/Model
self.table.setModel(self.proxy) # Revised code, for View/Proxy/Model
self.table.setItemDelegate(Delegate())
self.add_row = QPushButton("Add Row", localWidget)
self.add_row.clicked.connect(self.addRow)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.table.openPersistentEditor(index) # openPersistentEditor for createEditor
layout_v = QVBoxLayout()
layout_v.addWidget(self.table)
layout_v.addWidget(self.add_row)
localWidget.setLayout(layout_v)
self.setCentralWidget(localWidget)
self.show()
def addRow(self):
row = self.model.rowCount()
new_row_data = ["3", "Howdy", QTime(9, 0)]
self.model.appendRowData(new_row_data)
for i in range(self.model.columnCount()):
index = self.model.index(row, i)
self.table.openPersistentEditor(index) # openPersistentEditor for createEditor
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Test with View/Model, Widget Editor display.
Test with View/Proxy/Model, Widget Editor not display.
Any attempt to access the view indexes must use the view's model.
Your code doesn't work because the index you are providing belongs to another model, so the editor cannot be created because the view doesn't recognize the model of the index as its own: the view uses the proxy model, while you're trying to open an editor for the source model.
While in this case the simplest solution would be to use self.proxy.index(), the proper solution is to always refer to the view's model.
Change both self.model.index(...) to self.table.model().index(...).
Yes, thanks you very much.
Revised the code as below, now the Widget Editor display accordingly.
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
#index = self.model.index(row, column) # original code, which get the wrong index from the model
index = self.proxy.index(row, column) # revised code, get the correct index from the proxy
self.table.openPersistentEditor(index) # openPersistentEditor for createEditor

Kivy: How to call class from another class

I have a next part of code:
main.py
class VerticalGame(Widget):
...
def update(self, dt):
...
if self.player1.score >= 8 or self.player2.score >= 8:
return StartScreen()
class StartScreen(Screen):
vertical_button = ObjectProperty(None)
horizontal_button = ObjectProperty(None)
exit_button = ObjectProperty(None)
name_of_project = ObjectProperty(None)
def remove(self):
self.ids.layout.remove_widget(self.ids.vertical_button)
self.ids.layout.remove_widget(self.ids.horizontal_button)
self.ids.layout.remove_widget(self.ids.exit_button)
self.ids.layout.remove_widget(self.ids.name_of_project)
def start_vertical(self):
game = VerticalGame()
game.serve_ball()
Clock.schedule_interval(game.update, 1.0/60.0)
self.game_id.add_widget(game)
def start_horizontal(self):
game = HorizontalGame()
game.serve_ball()
Clock.schedule_interval(game.update, 1.0/60.0)
self.game_id.add_widget(game)
(Here's full code: https://github.com/Greenboyisyourdream/Ping-pong)
How i can call class StartScreen if construction return StartScreen() doesn't work?
You can use get_screen(name_screen). The same way you would change a label from another screen like here.

Using QComboBox in QTableView properly - issues with data being set and clearing QComboBoxes

In my application im using a QTableView, QStandardItemModel and a QSortFilterProxyModel in between for filtering.
The content is updated via a method for columns 1 & 2, and I want there to be a 3rd column for user to select options. I would prefer to use a QComboBox.
I've got everything pretty much working, except that when I select the item from the QComboBox in any of the cells in column 3, it doesn't populate. Does it have something to do with my setModelData() method?
I also have a clear button that I would like to reset all of the QComboBoxes to the first item which is an empty entry. I am not sure how to tackle this, i've found such things as using deleteLater() or setting the QTableView's setItemDelegateForColumn() to None and re-apply.
Obviously these are not the most efficient. What am I missing?
Working example:
import win32com.client
from PyQt5 import QtCore, QtGui, QtWidgets
outApp = win32com.client.gencache.EnsureDispatch("Outlook.Application")
outGAL = outApp.Session.GetGlobalAddressList()
entries = outGAL.AddressEntries
class ComboDelegate(QtWidgets.QItemDelegate):
def __init__(self,parent=None):
super().__init__(parent)
self.items = ['','To', 'CC']
def createEditor(self, widget, option, index):
editor = QtWidgets.QComboBox(widget)
editor.addItems(self.items)
return editor
def setEditorData(self, editor, index):
if index.column() == 2:
editor.blockSignals(True)
text = index.model().data(index, QtCore.Qt.EditRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
editor.blockSignals(False)
else:
QtWidgets.QItemDelegate.setModelData(editor,model,index)
def setModelData(self, editor, model, index):
if index.column() == 2:
model.setData(index, editor.currentText())
else:
QtWidgets.QItemDelegate.setModelData(editor,model,index)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def paint(self, painter, option, index):
QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter)
class App(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
"""This method creates our GUI"""
self.centralwidget = QtWidgets.QWidget()
self.setCentralWidget(self.centralwidget)
self.lay = QtWidgets.QVBoxLayout(self.centralwidget)
self.filterEdit = QtWidgets.QLineEdit()
self.filterEdit.setPlaceholderText("Type to filter name.")
self.label = QtWidgets.QLabel("Select an option for each person:")
self.button = QtWidgets.QPushButton("Test Button")
self.button.clicked.connect(self.runButton)
self.resetbutton = QtWidgets.QPushButton("Clear")
self.resetbutton.clicked.connect(self.clear)
self.lay.addWidget(self.filterEdit)
self.lay.addWidget(self.label)
self.tableview=QtWidgets.QTableView(self.centralwidget)
self.model=QtGui.QStandardItemModel()
self.model.setHorizontalHeaderLabels(['Name','Address','Option'])
self.tableview.verticalHeader().hide()
self.tableview.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
self.tableview.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
self.proxyModel = QtCore.QSortFilterProxyModel(self)
self.proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.proxyModel.setSourceModel(self.model)
self.proxyModel.sort(0,QtCore.Qt.AscendingOrder)
self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.tableview.setModel(self.proxyModel)
self.model.insertRow(self.model.rowCount(QtCore.QModelIndex()))
#self.fillModel(self.model) #uncomment if you have outlook
self.tableview.resizeColumnsToContents()
self.tableview.verticalHeader().setDefaultSectionSize(10)
self.filterEdit.textChanged.connect(self.onTextChanged)
self.lay.addWidget(self.tableview)
self.delegate = ComboDelegate()
self.tableview.setItemDelegateForColumn(2, self.delegate)
self.lay.addWidget(self.button)
self.lay.addWidget(self.resetbutton)
self.setMinimumSize(450, 200)
self.setMaximumSize(1500, 200)
self.setWindowTitle('Application')
def clear(self):
###clear tableview comboboxes in column 3
print("clear")
def runButton(self,index):
print("Do stuff")
def fillModel(self,model):
"""Fills model from outlook address book """
nameList = []
addressList = []
for row,entry in enumerate(entries):
if entry.Type == "EX":
user = entry.GetExchangeUser()
if user is not None:
if len(user.FirstName) > 0 and len(user.LastName) > 0:
nameItem = QtGui.QStandardItem(str(user.Name))
emailItem = QtGui.QStandardItem(str(user.PrimarySmtpAddress))
nameItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
emailItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
model.appendRow([nameItem,emailItem])
#QtCore.pyqtSlot(str)
def onTextChanged(self, text):
self.proxyModel.setFilterRegExp(text)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = App()
w.show()
sys.exit(app.exec_())
The problem is that you override the paint method unnecessarily since you don't want to customize anything. Before override I recommend you understand what it does and for this you can use the docs or the source code. But to summarize, in the case of the QItemDelegate the paint method establishes the information of the roles in the "option" and then just paints, and within that information is the text. But in your case it is not necessary so there is no need to override. On the other hand, if your delegate has the sole function of establishing a QComboBox then you don't have to verify the columns. Considering all of the above, I have simplified your delegate to:
class ComboDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self.items = ["", "To", "CC"]
def createEditor(self, widget, option, index):
editor = QtWidgets.QComboBox(widget)
editor.addItems(self.items)
return editor
def setEditorData(self, editor, index):
editor.blockSignals(True)
text = index.model().data(index, QtCore.Qt.EditRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.currentText())
On the other hand, the QItemEditorFactory uses the qproperty user as the parameter for the update, and in the case of the QComboBox it is the "currentText", so it can be further simplified using that information:
class ComboDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self.items = ["", "To", "CC"]
def createEditor(self, widget, option, index):
editor = QtWidgets.QComboBox(widget)
editor.addItems(self.items)
return editor
For the clear method is simple: Iterate over all the rows of the third column and set the empty text:
def clear(self):
# clear tableview comboboxes in column 3
for i in range(self.model.rowCount()):
index = self.model.index(i, 2)
self.model.setData(index, "")

How to make tkinter toggle button class by deriving from tkinter.Button?

I am trying to make a toggle button class by deriving from the tkinter.Button object. To that end, I am using this StackOverflow answer and these code examples.
The problem is that I get my desired toggle behavior from the button only after I click it twice; the first two clicks, it does not enact the self.config(relief="sunken"). I tried using the command keyword argument sample from this answer and that works from the start.
import tkinter as tk
class ToggleButton(tk.Button):
def __init__(self, parent=None, toggle_text="Toggled", toggle_bg_color="green", **kwargs):
tk.Button.__init__(self, parent, **kwargs)
self.toggled = False
self.default_bg_color = self['bg']
self.default_text = self["text"]
self.toggle_bg_color = toggle_bg_color
self.toggle_text = toggle_text
self.bind("<Button-1>", self.toggle, add="+")
def toggle(self, *args):
if self["relief"] == "sunken":
self["bg"] = self.default_bg_color
self["text"] = self.default_text
self.config(relief="raised")
# self["relief"] = "raised"
self.toggled = False
else:
self["bg"] = self.toggle_bg_color
self["text"] = self.toggle_text
# self["relief"] = "sunken"
self.config(relief="sunken")
self.toggled = True
def button_placeholder():
print("TO BE IMPLEMENTED")
root = tk.Tk()
button = ToggleButton(parent=root,
toggle_text="ON", toggle_bg_color="green",
text="OFF", command=button_placeholder)
button.pack()
root.mainloop()
Here are screenshots of the behavior of the buttons after numerous clicks
After the first two clicks on the button, the expected behavior occurs. However, if the user focuses on another window (for instance by minimizing the tkinter window) and then back, again the first two clicks do not cause the desired behavior.
Can some explain this? If not, can someone provide a solution where I can have consistent behavior on toggling my button?
Information about my system
Windows 10; 64 bit
Python 3.7.3 (64 bit)
Tkinter 8.6
The problem you seem to have is that the bg parameter is not defined when you first create the button; it only gets a value assigned upon the first button press.
Then, the logic to toggle is hard to follow: you have a self.toggled boolean, yet you are testing if the button is sunken or not to differentiate between states...
I reorganized the logic to make it easier to follow; after all, toggle is a binary change from one state to another. I therefore placed the definition of the ON and OFF states in the body of the class (into two class dictionaries), and the code swaps the two configs upon toggling.
On Windows:
import tkinter as tk
class ToggleButton(tk.Button):
ON_config = {'bg': 'green',
'text': 'button is ON',
'relief': 'sunken',
}
OFF_config = {'bg': 'white',
'text': 'button is OFF',
'relief': 'raised',
}
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.toggled = False
self.config = self.OFF_config
self.config_button()
self.bind("<Button-1>", self.toggle)
def toggle(self, *args):
if self.toggled: # True = ON --> toggle to OFF
self.config = self.OFF_config
else:
self.config = self.ON_config
self.toggled = not self.toggled
return self.config_button()
def config_button(self):
self['bg'] = self.config['bg']
self['text'] = self.config['text']
self['relief'] = self.config['relief']
return "break"
def __str__(self):
return f"{self['text']}, {self['bg']}, {self['relief']}"
def button_placeholder():
print('toggling now!')
if __name__ == '__main__':
root = tk.Tk()
button = ToggleButton(root)
button.pack()
root.mainloop()
On OSX:
Where the buttons aspect is fixed, using a tk.Label can mimic the desired behavior:
import tkinter as tk
class ToggleButtonLBL(tk.Label):
ON_config = {'bg': 'green',
'text': 'button is ON',
'relief': 'sunken',
}
OFF_config = {'bg': 'white',
'text': 'button is OFF',
'relief': 'raised',
}
def __init__(self, parent, *args, command=None, **kwargs):
super().__init__(parent, *args, **kwargs)
self.toggled = False
self.config = self.OFF_config
self.config_button()
self.bind("<Button-1>", self._toggle_helper)
self.bind("<ButtonRelease-1>", self._toggle)
self.command = command
def _toggle_helper(self, *args):
return 'break'
def _toggle(self, dummy_event):
self.toggle()
self.cmd()
def toggle(self, *args):
if self.toggled: # True = ON --> toggle to OFF
self.config = self.OFF_config
else:
self.config = self.ON_config
self.toggled = not self.toggled
self.config_button()
return 'break'
def config_button(self):
self['bg'] = self.config['bg']
self['text'] = self.config['text']
self['relief'] = self.config['relief']
return "break"
def __str__(self):
return f"{self['text']}, {self['bg']}, {self['relief']}"
def cmd(self):
self.command()
def button_placeholder():
print('toggling now!')
if __name__ == '__main__':
root = tk.Tk()
button = ToggleButtonLBL(root, command=button_placeholder)
button.pack()
root.mainloop()

PyQt QWidget in QAbstractListModel gets deleted with QSortFilterProxyModel

I need to populate a listview with widgets, then have a custom proxyfilter work with it.
Without the filter it works great, when active it seems to delete the widgets attach to the model.
It shows up fine showing all items, filtering works but when erasing the filter, when hidden widgets should be shown again following error gets thrown:
custom_widget.setGeometry(option.rect)
RuntimeError: underlying C/C++ object has been deleted
Tried not using QVariant and going the internalPointer route but breaks at the same spot.
Thanks for having a look!
Setup:
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
# create temp data
self.list_data = []
for x in xrange(500):
widget = ListItemWidget(text=str(x), parent=self)
self.list_data.append((str(x), widget)) # testing to put in inmut tuple
# create listviewmodel
self.lm = ListViewModel(parent=self)
# create listview widget
self.lv = QtGui.QListView()
# create filter proxy
self.proxy_model = ListViewFilterProxyModel()
self.proxy_model.setFilterPattern('')
self.proxy_model.setSourceModel(self.lm)
# set model of listview to filter proxy
self.lv.setModel(self.proxy_model)
# set delegate for column 0
self.lv.setItemDelegateForColumn(0, CustomWidgetDelegate(self.lv))
self.lm.updateData(self.list_data)
self.proxy_model.invalidate()
self.connect(self.filter_edit, QtCore.SIGNAL("textChanged(QString)"), self.update_filter)
def update_filter(self, pattern):
self.proxy_model.setFilterPattern(pattern)
self.proxy_model.invalidate()
Custom widget
class ListItemWidget(QtGui.QWidget):
def __init__(self, text=None, parent=None):
QtGui.QWidget.__init__(self)
self.text = text
#QtCore.pyqtProperty(QtCore.QString)
def text(self):
return self.__text
#text.setter
def text(self, value):
self.__text = value
Delegate for painting the view
class CustomWidgetDelegate(QtGui.QItemDelegate):
def __init__(self, parent=None):
super(CustomWidgetDelegate, self).__init__(parent)
def paint(self, painter, option, index):
custom_widget = index.model().data(index, QtCore.Qt.DisplayRole).toPyObject()[1]
>>>>>> custom_widget.setGeometry(option.rect)
if not self.parent().indexWidget(index):
self.parent().setIndexWidget(index, custom_widget)
List view model:
class ListViewModel(QtCore.QAbstractListModel):
def __init__(self, parent=None, *args):
QtCore.QAbstractListModel.__init__(self, parent, *args)
self.listdata = []
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.listdata)
def data(self, index, role):
if role == QtCore.Qt.SizeHintRole:
return QtCore.QSize(80, 80)
if index.isValid() and role == QtCore.Qt.DisplayRole:
return QtCore.QVariant(self.listdata[index.row()]).toPyObject()
return QtCore.QVariant()
def updateData(self, listdata):
self.listdata = listdata
index = len(self.listdata)
return True
Finally the filter proxy model:
class ListViewFilterProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
self.filter_str = None
QtGui.QSortFilterProxyModel.__init__(self, parent)
def setFilterPattern(self, pattern):
self.filter_str = QtCore.QString(pattern)
def filterAcceptsRow(self, sourceRow, sourceParent):
if self.filter_str is None:
return True
index = self.sourceModel().index(sourceRow, 0, sourceParent)
# just testing on the str here...
text = index.data().toPyObject()[0]
if not str(self.filter_str) in text:
return False
return True

Resources