pyqt checkbox resolution for different scales - windows

I am developing a windows app with pyside6. I have a QTableView and have used a QItemDelegate to add checkboxes to the table. The checkboxes are drawn in the paint method of the QItemDelegate, using the method drawCheck
class CheckBoxDelegate(QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
"""
def __init__(self, parent):
QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
"""
Important, otherwise an editor is created if the user clicks in this cell.
"""
return None
def paint(self, painter, option, index):
"""
Paint a checkbox without the label.
"""
value = int(index.data())
if value == 0:
value = Qt.Unchecked
else:
value = Qt.Checked
self.drawCheck(painter, option, option.rect, value)
def editorEvent(self, event, model, option, index):
"""
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
"""
if not int(index.flags() & Qt.ItemIsEditable) > 0:
return False
if event.type() == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton:
# Change the checkbox-state
self.setModelData(None, model, index)
return True
return False
def setModelData(self, editor, model, index):
"""
Set new data in the model
"""
model.setData(index, 1 if int(index.data()) == 0 else 0, Qt.EditRole)
The problem is that the checkboxes are only correctly displayed if the scale is set at 100%. Changing it to 125% or more, makes the checkbox too big and the icon inside blurry (images below)
checkboxes at 125%
checkboxes at 100%
I have tried different settings for scaling the app. The combination of settings that works to scale the rest of the application is:
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "0"
os.environ["QT_FONT_DPI"] = "96"
os.environ["QT_SCALE_FACTOR"] = "1"
QApplication.setHighDpiScaleFactorRoundingPolicy(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.Floor)
Still changing these settings has no effect on the checkboxes resolution

Related

setText() and addItem() of lineEdit and QlistWidget respectively do not show the contents

I want to display the value stored in the varible "total" to the QlineEdit and add value of the variable 'points' to the QlistWidget when the button is clicked.
In the following block of code, i used "self.lineEdit.setText(str(points))" and "self.LW2.addItem(self.points)" to set text to QlineEdit and QlistWidget respectively. Still, the values won't show up.
def display(self):
sqlC = self.databaseConn()
#self.playersList = []
self.pointlist = []
self.total = 0
self.economic_rate=0
if "----SELECT TEAM----" == self.CB1.currentText():
self.msgbox("Warning","Select a team to evaluate")
else:
for x in range(self.LW1.count()):
player=self.LW1.item(x).text()
#print(player)
sqlC.execute("SELECT Scored,Faced,Fours,Sixes,Bowled,Wkts,Catches,Stumping,RO,Given FROM Match WHERE Player = ?",(player,))
score=sqlC.fetchall()
points = FantasyCricket.FantasyCricket.BattingBowlingPoints.batting(score)
points += FantasyCricket.FantasyCricket.BattingBowlingPoints.bowling(score)
self.pointlist.append(points)
#print(points)
self.total += points
#print(self.total)
self.LW2.addItem(str(points))
self.lineEdit.setText(str(self.total))
P.S- the values are displayed correctly on the Shell using print()

Some PyQt5 Checkboxes are unclickable after being moved

I'm creating a to-do list where a checkbox, textbox (QLineEdit), and QComboBox (to set Priority 1, 2, etc.) are added each time the "Add Task" button is clicked. When the checkbox for each corresponding task is clicked, the task would move to the bottom of the list of tasks. Conversely, if a priority is set, then the task is moved to the top of the list of tasks.
The problem is that in certain circumstances, when I click on the checkbox, nothing happens. The signal that identifies that the checkbox state has changed is never sent. It's almost like the checkbox isn't there, even though it (or at least, the image of it) is. This problem happens if none of the priorities are set with unchecking—checking any given task works, but certain tasks cannot be unchecked. Other tasks are able to be unchecked, and sometimes if I go back to the task that couldn't be unchecked after unchecking something else, it'll be checkable again. If I were to set some tasks to have a priority and then go and try to check the checkboxes, some checkboxes won't be able to be checked at all. Again, if I were to check some other task and then go back to the task that wasn't able to be checked, it might be able to be checked this time around.
I think the error might be coming from moving the checkboxes... for some reason the functionality behind some of the checkboxes is lost going through the code. One notable thing is tabbing through the items in the GUI. Once items are moved, hitting tab will go through the items in the same order that it was originally set up in (so the cursor will essentially jump around the textboxes instead of going in order from top to bottom). Not sure if that has anything to do with the error; it's just something I've noticed. I'm don't know if this is an error within PyQt5 itself, or if it's a fixable error within the code.
import sys
from PyQt5.QtWidgets import (QWidget, QComboBox,
QPushButton, QApplication, QLabel, QLineEdit, QTextEdit, QMainWindow, QAction, QShortcut, QCheckBox)
from PyQt5.QtGui import (QKeySequence)
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import pyqtSlot, QObject
class App(QMainWindow):
def __init__(self):
super().__init__()
self.i = 40
self.j = 80
self.counter = 1
self.list_eTasks = []
self.initUI()
def initUI(self):
#sets up window
self.setWindowTitle('To-Do List')
self.setGeometry(300, 300, 800, 855)
#button
self.btn1 = QPushButton("Add Task", self)
self.btn1.clicked.connect(self.add_task_button_clicked)
#close the window via keyboard shortcuts
self.exit = QShortcut(QKeySequence("Ctrl+W" or "Ctrl+Q"), self)
self.exit.activated.connect(self.close)
self.show()
#pyqtSlot()
def add_task_button_clicked(self):
self.label = QLabel(self)
self.label.setText(str(self.counter))
self.label.move(5, self.i)
self.btn1.move(50, self.j)
self.textbox = QLineEdit(self)
self.textbox.resize(280, 40)
self.checkbox = QCheckBox(self)
self.checkbox.stateChanged.connect(self.click_box)
self.combobox = QComboBox(self)
self.combobox.addItem("No Priority")
self.combobox.addItem("Priority 1")
self.combobox.addItem("Priority 2")
self.combobox.addItem("Priority 3")
self.combobox.activated[str].connect(self.combobox_changed)
self.obj = eTask(self.checkbox, self.textbox, self.combobox)
self.list_eTasks.append(self.obj)
self.textbox.show()
self.label.show()
self.checkbox.show()
self.combobox.show()
self.i += 40
self.j += 40
self.counter += 1
self.sort_eTasks()
def click_box(self, state):
self.sort_eTasks()
def move_everything(self, new_list):
count = 0
for item in new_list:
y_value = ((40)*(count + 1))
item.check.move(20, y_value)
item.text.move(50, y_value)
item.combo.move(350, y_value)
count += 1
return
def combobox_changed(self, state):
#make new list to not include any already checked off items
for task in self.list_eTasks:
if task.combo.currentText() == "Priority 1":
task.priority = 1
elif task.combo.currentText() == "Priority 2":
task.priority = 2
elif task.combo.currentText() == "Priority 3":
task.priority = 3
else:
task.priority = 10
self.sort_eTasks()
def sort_eTasks(self):
self.list_eTasks.sort(key=lambda eTask: eTask.getRank())
self.move_everything(self.list_eTasks)
for task in self.list_eTasks:
print(str(task.priority) + " pos: " + str(task.check.pos()))
print("-----------------------------------")
class eTask:
check = ""
text = ""
combo = ""
priority = 10
def __init__(self, c, t, co):
self.check = c
self.text = t
self.combo = co
def setP(self, p):
self.priority = p
def getRank(self):
if self.check.isChecked():
return self.priority + 100
else:
return self.priority
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
There are various problems with your code, I'll try to address all of them.
The sorting mechanism is not good: it only happens when an item changes the priority (while it should also apply whenever the user clicks the checkbox)
Using low values like those results in unexpected behavior: if I have a priority 1 checked its priority is augmented by 10, so why should it go under a priority 2 or even 3?
The sorting mechanism should be centralized, instead of being split into more functions that don't do anything else: "one function to rule them all" ;-) Don't check for the combobox contents and then sort basing on another external function call: just call a single function that checks for the combobox and the checkbox, then sort the contents; remember: while OOP is about modularity, you should avoid unnecessary fragmentation (remember the KISS principle);
You should probably not connect to the activated signal, and you should certainly not use the [str] overload (checking for a string is rarely a good idea for index based widgets); use currentIndex() and its currentIndexChanged() signal instead;
Using fixed geometries for child widgets is something that should be avoided as much as possible; layout managers exist for lots of very good reasons (for example, I couldn't click on the QLineEdit if the mouse cursor was too close to the check box, even if it's over the line edit); note that you should avoid using setGeometry for the top level window also;
You should never set instance attributes that are going to be overwritten by a different object: you're continuously overwriting them (self.label, self.textbox, etc.), and the result is that making them instance attributes is completely useless; since you're already creating them with a parent widget, the garbage collector won't delete them at the end of the function, so you should only use local variables instead (label = QLabel(), etc...);
Using a class like you did is not very useful: you're going to group all those widgets anyway, so it's better to use a container widget which not only will manage its children (see the point above about layouts), but will "centralize" all necessary programming logic;
Avoid similar names for class and variables if they refer to different object types: in your case, app refers to the QApplication instance, but App is a QMainWidget subclass;
Here's a possible reimplementation of your code:
from PyQt5 import QtCore, QtWidgets
class TaskWidget(QtWidgets.QWidget):
priorityChanged = QtCore.pyqtSignal()
lastChanged = QtCore.QDateTime.currentDateTime()
def __init__(self, title='', parent=None):
super().__init__(parent)
layout = QtWidgets.QHBoxLayout(self)
self.checkBox = QtWidgets.QCheckBox()
layout.addWidget(self.checkBox)
self.textBox = QtWidgets.QLineEdit(title)
layout.addWidget(self.textBox)
self.textBox.setMinimumWidth(240)
self.priorityCombo = QtWidgets.QComboBox()
layout.addWidget(self.priorityCombo)
self.priorityCombo.addItems([
'No priority',
'Priority 1',
'Priority 2',
'Priority 3',
])
self.priorityCombo.currentIndexChanged.connect(self.updatePriority)
self.checkBox.toggled.connect(self.updatePriority)
def updatePriority(self):
# update the "lastChanged" variable, ensuring that the sorting puts on
# top the last changed task
self.lastChanged = QtCore.QDateTime.currentDateTime()
self.priorityChanged.emit()
def priority(self):
priority = self.priorityCombo.currentIndex()
# set an arbitrary (but still reasonable) priority that would be fine for
# more items in the same priority
if priority > 0:
priority *= 100
else:
priority = self.priorityCombo.count() * 100
if self.checkBox.isChecked():
priority -= 1
return priority, self.lastChanged.msecsTo(QtCore.QDateTime.currentDateTime())
class TaskApp(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(central)
self.setCentralWidget(central)
self.taskLayout = QtWidgets.QGridLayout()
layout.addLayout(self.taskLayout)
self.addTaskButton = QtWidgets.QPushButton('Add task')
layout.addWidget(self.addTaskButton)
self.addTaskButton.clicked.connect(self.addTask)
self.tasks = []
self.addTask()
def addTask(self):
task = TaskWidget('Task no. {}'.format(len(self.tasks) + 1))
if not self.taskLayout.count():
# this is for the first item only; we cannot use rowCount, since it
# always returns at least 1, even if it's empty
row = 0
else:
row = self.taskLayout.rowCount()
self.taskLayout.addWidget(QtWidgets.QLabel(str(row + 1)))
self.taskLayout.addWidget(task, row, 1)
self.tasks.append(task)
task.priorityChanged.connect(self.sortTasks)
def sortTasks(self):
tasks = self.tasks[:]
self.tasks.clear()
# since we're using a QGridLayout, we don't need to relate to
# insertWidget(), which is index based and might result in some
# inconsistencies while "regenerating" the UI; in this case, the
# grid will be the same, we're only going to rearrange the child widgets
for row, taskWidget in enumerate(sorted(tasks, key=lambda w: w.priority())):
self.taskLayout.addWidget(taskWidget, row, 1)
self.tasks.append(taskWidget)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = TaskApp()
w.show()
sys.exit(app.exec_())
I just guessed the priority implementation, but that's not the point.
I also added a datetime check, so that the last changed item with the same priority will always be on top.

Function to set image of buttons causes image to jump between buttons

I'm trying to teach myself the tkinter module by programming minesweeper. I have created a grid with buttons and a method to set an image flag to cells. It works, in that when you press the right mouse button the image of the button changes as desired, but when you right click on the next button the image just moves to the next button, rather than creating a second flag. I want to be able to place a new flag image on each cell that I right click, rather than just shuffle the image around. Here's my code:
import tkinter as Tk
def main():
root = Tk.Tk()
root.geometry('{}x{}'.format(700, 700))
instance = Minesweeper(root, 10, 10)
root.mainloop()
class Minesweeper:
def __init__(self, parent, height, width):
self.top_frame = Tk.Frame(parent)
self.top_frame.place(anchor=Tk.CENTER, relx=0.5, rely=0.5)
self.frames = []
self.buttons = []
index = 0
for x in range(height):
for y in range(width):
self.frames.append(Tk.Frame(self.top_frame, height=50, width=50))
self.buttons.append(Tk.Button(self.frames[index], bg="white"))
self.frames[index].grid_propagate(False)
self.frames[index].columnconfigure(0, weight=1)
self.frames[index].rowconfigure(0, weight=1)
self.frames[index].grid(row=x, column=y)
self.buttons[index].grid(sticky="wens")
self.buttons[index].bind('<Button-3>', self.flag)
index += 1
def flag(self, event):
self.flag = Tk.PhotoImage(file="flag.png")
event.widget.configure(image=self.flag)
if __name__ == "__main__":
main()
Seems that the below fixed it:
def flag(self, event):
self.flag = Tk.PhotoImage(file="flag.png")
event.widget.image = self.flag # <---- this seemed to fix it
event.widget.configure(image=self.flag)

populating listbox from selection, python

Hi to all that look and might be able to help. I'm new to Python, and to coding in general. I'm working on a program that the basic concept of it is to have a few lists, make a selection from one list, and then another list is populated based on that first choice. I have been able to create the GUI, I've been able to create various different forms of "Listbox", I've been able to follow tutorials that show how to retrieve the indicies of that "Listbox", but I haven't been able to get the 2nd Listbox to fill in with the list I created for a choice made in the 1st box. I've tried checkboxes too, I did get closer on that one, but still no dice.
The supplied code below works in the sense that I can print the 2nd list into the python shell, but when I get a listbox set up in my GUI I can't get it to populate there. Nothing every happens. And I know that I don't have a listbox in this code to have it be populated with the 2nd list. So in this code, I would like the 'brand' choice if 'Apples', to populate the list from 'modelA' in a listbox. Or if the checkboxes actually weren't there and the items in 'brand' where in their own listbox. Any direction might help a lot more than what I've been able to do. Thanks again.
Python 3.3.2
Mac OS X 10.8.5
from tkinter import *
#brand would be for first listbox or checkboxes
brand = ['Apples','Roses', 'Sonic', 'Cats']
#model* is to be filled into the 2nd listbox, after selection from brand
modelA = ['Ants', 'Arrows', 'Amazing', 'Alex']
modelR = ['Early', 'Second', 'Real']
modelS= ['Funny', 'Funky']
modelC= ['Cool', 'Daring', 'Double']
#create checkboxes
def checkbutton_value():
if(aCam.get()):
print("Models are: ", modelA)
if(rCam.get()):
print("Models are: ", modelR)
if(sCam.get()):
print("Models are: ", modelS)
if(cCam.get()):
print("Models are: ", modelC)
#create frame, and check checkbuttons state, print value of model
root = Tk()
aCam = IntVar()
rCam = IntVar()
sCam = IntVar()
cCam = IntVar()
#Checkbutton functions
apples = Checkbutton(root, text = "Apples", variable=aCam, command=checkbutton_value)
apples.pack(anchor="w")
roses = Checkbutton(root, text = "Roses", variable=rCam, command=checkbutton_value)
roses.pack(anchor="w")
sonic = Checkbutton(root, text = "Sonic", variable=sCam, command=checkbutton_value)
sonic.pack(anchor="w")
cats = Checkbutton(root, text = "Cats", variable=cCam, command=checkbutton_value)
cats.pack(anchor="w")
#general stuff for GUI
root.title('My Brand')
root.geometry("800x300")
root.mainloop()
To populate a Listbox based on the selection of another Listbox, you need to bind a method to the 1st Listbox's selection. Here's an example using makes/models of cars:
import Tkinter
class Application(Tkinter.Frame):
def __init__(self, master):
Tkinter.Frame.__init__(self, master)
self.master.minsize(width=512, height=256)
self.master.config()
self.pack()
self.main_frame = Tkinter.Frame()
self.main_frame.pack(fill='both', expand=True)
self.data = {
'Toyota': ['Camry', 'Corolla', 'Prius'],
'Ford': ['Fusion', 'Focus', 'Fiesta'],
'Volkswagen': ['Passat', 'Jetta', 'Beetle'],
'Honda': ['Accord', 'Civic', 'Insight']
}
self.make_listbox = Tkinter.Listbox(self.main_frame)
self.make_listbox.pack(fill='both', expand=True, side=Tkinter.LEFT)
# here we bind the make listbox selection to our method
self.make_listbox.bind('<<ListboxSelect>>', self.load_models)
self.model_listbox = Tkinter.Listbox(self.main_frame)
self.model_listbox.pack(fill='both', expand=True, side=Tkinter.LEFT)
# insert our items into the list box
for i, item in enumerate(self.data.keys()):
self.make_listbox.insert(i, item)
def load_models(self, *args):
selection = self.make_listbox.selection_get()
# clear the model listbox
self.model_listbox.delete(0, Tkinter.END)
# insert the models into the model listbox
for i, item in enumerate(self.data[selection]):
self.model_listbox.insert(i, item)
root = Tkinter.Tk()
app = Application(root)
app.mainloop()

how to select multiple objects with mouse in tkinter python gui?

I am trying to select multiple objects using mouse just like in windows click and drag. i am using tkinter in python to buils this gui. i am creating objects as shown in below code.
import Tkinter as tk
from Tkinter import *
root = Tk()
w= Canvas(root, width=800, height=768)
w.grid()
w.create_line(200,200,300,300, width=3, tags="line1")
w.create_oval(150,100,170,300, fill="red", tags="oval")
mainloop()
what i am trying to do is if i drag my mouse over multiple objects some def should return the tags of the objects. how can i do this.
Thank you
Save the coordinates on a button-down event, and then on a button-up event use the find_enclosed or find_overlapping method of the canvas to find all items enclosed by the region.
The following code renders a rectangle that follows the cursor and returns a list of element IDs of elements within the rectangle. It uses bindings for when the mouse is pressed within the canvas, when it moves, and when it is released. I then kept a list of elements with their canvas identifier and used that to back lookup the object from the list of selected IDs.
# used to record where dragging from
originx,originy = 0,0
canvas.bind("<ButtonPress-1>", __SelectStart__)
canvas.bind("<B1-Motion>", __SelectMotion__)
canvas.bind("<ButtonRelease-1>", __SelectRelease__)
# binding for drag select
def __SelectStart__(self, event):
oiginx = canvas.canvasx(event.x)
originy = canvas.canvasy(event.y)
selectBox = canvas.create_rectangle(originx,originy,\
originx,originy)
# binding for drag select
def __SelectMotion__(self, event):
xnew = canvas.canvasx(event.x)
ynew = canvas.canvasy(event.y)
# correct cordinates so it gives (upper left, lower right)
if xnew < x and ynew < y:
canvas.coords(selectBox,xnew,ynew,originx,originy)
elif xnew < x:
canvas.coords(selectBox,xnew,originy,originx,ynew)
elif ynew < y:
canvas.coords(selectBox,originx,ynew,xnew,originy)
else:
canvas.coords(selectBox,originx,originy,xnew,ynew)
# binding for drag select
def __SelectRelease__(self, event):
x1,y1,x2,y2 = canvas.coords(selectBox)
canvas.delete(selectBox)
# find all objects within select box
selectedPointers = []
for i in canvas.find_withtag("tag"):
x3,y3,x4,y4 = canvas.coords(i)
if x3>x1 and x4<x2 and y3>y1 and y4<y2:
selectedPointers.append(i)
Callback(selectedPointers)
# function to receive IDs of selected items
def Callback(pointers):
print(pointers)

Resources