I'm trying to fade out an entire row of a GridLayout when a user clicks a button. So far I've been able to loop each of the widgets on the specific row which is working but I'm struggling with creating a parallel animation for them all...
idx = self.parent().findChild(QGridLayout).indexOf(self)
location = self.parent().findChild(QGridLayout).getItemPosition(idx)
row, col = location[:2]
self.parent_grid = self.parent().findChild(QGridLayout)
col_count = self.parent().findChild(QGridLayout).columnCount()
print(col_count)
self.effects = []
self.animations = []
anim_group = QParallelAnimationGroup()
for i in range(col_count-1):
effect = QGraphicsOpacityEffect(self)
self.effects.append(effect)
self.parent_grid.itemAtPosition(row,i).widget().setGraphicsEffect(effect)
new_anim = QPropertyAnimation(effect, b"opacity")
new_anim.setDuration(1000)
new_anim.setStartValue(1)
new_anim.setEndValue(0)
new_anim.finished.connect(lambda: self.hide_widget(col_count, row))
self.animations.append(new_anim)
anim_group.addAnimation(new_anim)
anim_group.start()
Since the count of widgets might be high, it's not a good idea to create an animation for each one of them.
Instead, you can use a "controller" that has its own unique animation, and updates the opacity of each widgets in its row.
While using a QPropertyAnimation can work, it would require adding a custom property for that, and it's not really needed for this purpose. Instead, use a basic QVariantAnimation and connect its valueChanged to a function that updates the opacity of each graphics effect.
Here is a possible implementation:
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class RowFadeController(QObject):
def __init__(self, parent, buttons):
super().__init__()
self.buttons = buttons
self.effects = []
for button in buttons:
effect = QGraphicsOpacityEffect(button, opacity=1.0)
button.setGraphicsEffect(effect)
self.effects.append(effect)
self.animation = QVariantAnimation(self)
self.animation.setStartValue(1.)
self.animation.setEndValue(0.)
self.animation.valueChanged.connect(self.setOpacity)
def toggle(self, hide):
self.animation.setDirection(
self.animation.Forward if hide else self.animation.Backward
)
self.animation.start()
def setOpacity(self, opacity):
for effect in self.effects:
effect.setOpacity(opacity)
class Window(QWidget):
def __init__(self):
super().__init__()
layout = QGridLayout(self)
self.fadeControllers = []
for row in range(10):
rowButtons = []
for col in range(10):
button = QPushButton(str(col + 1))
layout.addWidget(button, row, col)
rowButtons.append(button)
toggleButton = QPushButton('toggle', checkable=True)
layout.addWidget(toggleButton, row, layout.columnCount() - 1)
fadeController = RowFadeController(self, rowButtons)
self.fadeControllers.append(fadeController)
toggleButton.clicked.connect(fadeController.toggle)
app = QApplication([])
test = Window()
test.show()
app.exec()
Tweaked for PyQT6 and added a function to get rid of the row once it's faded out.
from PyQt6.QtCore import *
from PyQt6.QtWidgets import *
class RowFadeController(QObject):
def __init__(self, parent, buttons):
super().__init__(parent)
self.buttons = buttons
self.effects = []
for button in buttons:
effect = QGraphicsOpacityEffect(button, opacity=1.0)
button.setGraphicsEffect(effect)
self.effects.append(effect)
self.animation = QVariantAnimation(self)
self.animation.setStartValue(1.)
self.animation.setEndValue(0.)
self.animation.valueChanged.connect(self.setOpacity)
self.animation.finished.connect(self.hideRow)
def toggle(self, hide):
self.animation.setDirection(
self.animation.Direction.Forward if hide else self.animation.Direction.Backward
)
self.animation.start()
def setOpacity(self, opacity):
for effect in self.effects:
effect.setOpacity(opacity)
def hideRow(self):
for button in self.buttons:
button.hide()
button.setParent(None)
class Window(QWidget):
def __init__(self):
super().__init__()
layout = QGridLayout(self)
self.fadeControllers = []
for row in range(10):
rowButtons = []
for col in range(10):
button = QPushButton(str(col + 1))
layout.addWidget(button, row, col)
rowButtons.append(button)
toggleButton = QPushButton('toggle', checkable=True)
rowButtons.append(toggleButton)
layout.addWidget(toggleButton, row, layout.columnCount() - 1)
fadeController = RowFadeController(self, rowButtons)
self.fadeControllers.append(fadeController)
toggleButton.clicked.connect(fadeController.toggle)
app = QApplication([])
test = Window()
test.show()
app.exec()
Related
Is there any way by which I can resize a QDialog after removing a QWidget from its layout?
I'm still a beginner so bear with me if the code looks a bit silly.
The main dialog geometry is stored during the resizeEvent()
Before creating the widget:
https://imgur.com/a/vFORp4t
When the widget is created:
https://imgur.com/z7KUa3I
When the widget is removed:
https://imgur.com/KdzULUe
def resizeEvent(self, event):
QtWidgets.QMainWindow.resizeEvent(self, event)
window = self.window()
self.dialog_rect = window.geometry()
self.DialogSizeChanged.emit() # pylint: disable=E1101
Here's the "create widget" code:
def create_info_widget(self):
px = self.dialog_rect.x()
py = self.dialog_rect.y()
w = self.dialog_rect.width()
h = self.dialog_rect.height()
self.dialog_stored_rect = self.dialog_rect
self.info_wdg = abw.ThumbnailInfo("Asset Info")
self.asset_wdg_layout.addWidget(self.info_wdg)
data = self.list_wdg.get_list_thumbnails_data()
path = data['thumbnail']
self.info_wdg.set_info_thumbnail(path)
wdg_h = self.info_wdg.height()
self.setGeometry(px, py, w, h + wdg_h)
self.updateGeometry()
And the "remove widget" code:
def remove_info_widget(self):
wdg = self.findChild(QtWidgets.QFrame, "Asset Info")
if wdg:
self.asset_wdg_layout.removeWidget(wdg)
wdg.deleteLater()
self.info_wdg = None
self.setGeometry(self.dialog_stored_rect)
self.updateGeometry()
As shown on grabbed images, when the widget is removed, it doesn't get back to its size before it was created.
Thank you,
Jacques.
We will first resize it and then use adjustSize to let QApplication know.
We are using resize for just resizing, not for fixed values
Make sure not to apply this when maximized
from PySide2.QtWidgets import (
QApplication,
QPushButton,
QWidget,
QVBoxLayout
)
class Test(QWidget):
def __init__(self):
super().__init__()
self.setLayout(QVBoxLayout(self))
self.add = QPushButton(self, text="Toggle")
self.added = QPushButton(self, text="Hello There")
self.added.setMinimumHeight(600)
self.added.setVisible(False)
self.layout().addWidget(self.add)
self.layout().addWidget(self.added)
self.add.clicked.connect(
lambda: self.added.setVisible(not self.added.isVisible())
)
self.add.clicked.connect(self.toggle)
# trick is to use resize and self.adjustSize
def toggle(self):
if self.isMaximized():
return ...
self.resize(self.width(), 600 if self.added.isVisible() else 100)
self.adjustSize()
sample = QApplication([])
test = Test()
test.show()
sample.exec_()
I am trying to create a qTableWidget in PyQt5 that will allow me to make a selection with a checkbox that filters a Pandas DataFrame and then show that data in the GUI.
I can get it to run the code the first time, and it presents the data just as I would expect.
When you try and refresh it however it won't load the new data into the qTableWidget.
There isn't any error messages, and if you print the DataFrame it is loading the new data as I would expect, it just isn't showing up in the GUI.
I've searched for similar issues on here, but can't find anyone having the same problems I am getting.
I need it to refresh the table in the same GUI as in my real data, the DataFrame is generated from an API call which when selecting a new group will pull the most recent data and could be ran an indefinite number of times.
Here is what I've got up to, I've tried to add comments to explain as much as I can do
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import pandas as pd
class TableWidget(QtWidgets.QTableWidget):
def __init__(self, tab2_df, parent=None):
QtWidgets.QTableWidget.__init__(self,parent)
tab2_df = tab2_df
nRows = len(tab2_df.index)
nColumns = len(tab2_df.columns)
self.setRowCount(nRows)
self.setColumnCount(nColumns)
for i in range(self.rowCount()):
for j in range(self.columnCount()):
x = "{}".format(tab2_df.iloc[i, j])
self.setItem(i, j, QtWidgets.QTableWidgetItem(x))
print(tab2_df) #validating that the new df is received
class Window(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__()
self.title = "Reporting Test"
self.left = 400
self.top = 50
self.width = 1300
self.height = 700
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
#initialize window with tabs
self.table_widget = MyTableWidget(self)
self.setCentralWidget(self.table_widget)
self.show()
class MyTableWidget(QtWidgets.QWidget):
def __init__(self, parent):
super(QtWidgets.QWidget, self).__init__(parent)
self.layout = QtWidgets.QVBoxLayout(self)
self.tabs = QtWidgets.QTabWidget()
self.tab1 = QtWidgets.QWidget() #Group select Tab
self.tab2 = QtWidgets.QWidget() #DataFrame Tab
# Name and add first Tab to Layout
self.tabs.addTab(self.tab1,"Group Select")
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
# Sample DataFrame for demonstation
self.tab1.df = pd.DataFrame({'Name':['Tom', 'Jack', 'Steve', 'Ricky'],'Age':[28,34,29,42]})
# Filter dataframe into a list to show as checkboxes
self.tab1.group = self.tab1.df[["Name"]]
self.tab1.groupList = self.tab1.group['Name'].tolist()
self.tab1.listLabel = ["",] * len(self.tab1.groupList)
self.tab1.grid = QtWidgets.QGridLayout()
self.tab1.setLayout(self.tab1.grid)
# Populate the checkboxes with the list
for i, v in enumerate(self.tab1.groupList):
self.tab1.groupList[i] = QtWidgets.QCheckBox(v)
self.tab1.listLabel[i] = QtWidgets.QLabel()
self.tab1.grid.addWidget(self.tab1.groupList[i], i, 0)
# Add the checkboxes into the tab
self.tab1.button = QtWidgets.QPushButton("Select Group")
self.tab1.button.clicked.connect(self.checkboxChanged)
self.tab1.labelResult = QtWidgets.QLabel()
self.tab1.grid.addWidget(self.tab1.button, i+1, 0, 1,2)
self.tab1.setLayout(self.tab1.grid)
def checkboxChanged(self):
# Clear the previous tab
self.tabs.clear()
# Add a new tab for the loaded data
self.tabs.addTab(self.tab2,"Loaded Data")
# Match the ticked checkbox to the DataFrame and filter to a new DataFrame
self.tab1.labelResult.setText("")
for i, v in enumerate(self.tab1.groupList):
self.tab1.listLabel[i].setText("True" if v.checkState() else "False")
self.tab1.labelResult.setText("{}, {}".format(self.tab1.labelResult.text(),
self.tab1.listLabel[i].text()))
self.tab1.groupList2 = self.tab1.group['Name'].tolist()
checked2 = str(self.tab1.labelResult.text()).split(',')
result = list(filter(None, checked2))
checked_list = {"Name":self.tab1.groupList2, "checked":result}
checked_list_df = pd.DataFrame(checked_list)
checked_list_filtered_df = checked_list_df[checked_list_df.checked.str.contains("true", case=False)]
self.tab1.filteredGroup_df = checked_list_filtered_df
group_select_df = pd.merge(self.tab1.group, self.tab1.filteredGroup_df, on="Name", how="inner")
group_select_list = group_select_df["Name"].tolist()
tab2_df = self.tab1.df[self.tab1.df["Name"].isin(group_select_list)]
# Populate the filtered DataFrame onto a TableWidget and populate this into the tab
self.tab2.tableWidget = TableWidget(tab2_df, self)
# Set headings and style for TableWidget
self.tab2.tableWidget.setHorizontalHeaderLabels(("Name", "Age"))
stylesheet3 = "::section{font: bold 18px}"
self.tab2.tableWidget.horizontalHeader().setStyleSheet(stylesheet3)
# Add button to refresh the data with a new group filter
self.tab2.button = QtWidgets.QPushButton("Refresh", self.tab2)
self.tab2.button.clicked.connect(self.refresh_button)
self.tab2.layout = QtWidgets.QVBoxLayout()
self.tab2.layout.addWidget(self.tab2.tableWidget)
self.tab2.layout.addWidget(self.tab2.button)
self.tab2.setLayout(self.tab2.layout)
# Refresh button that will clear the previous TableWidget and load the Group Select Tab to generate new data
def refresh_button(self):
# Clear the previous tab
self.tabs.clear()
# Clear the TableWidget
self.tab2.tableWidget.setRowCount(0)
# Load the Group Select Tab to select the new group
self.tabs.addTab(self.tab1,"Group Select")
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I'm sure a lot of this could be done much more elegantly and I'm trying to learn as I go, but I just can't figure this bit out.
How can i control each button individually in tkinter?
Here is my code:
from tkinter import *
class Minesweeper:
def __init__(self,action):
L=[]
for i in range(15):
for j in range(15):
self.action = Button(win, text = " h ",command = self.clickMe)
self.action.grid(column = i, row = j)
def clickMe(self):
self.action.configure(foreground = "red")
def main():
global win
win = Tk()
win.title('Minesweeper')
game = Minesweeper(win)
win.mainloop()
main()
Better way is a binding some event to button:
from tkinter import *
class Minesweeper:
def __init__(self, action):
L=[]
for i in range(15):
for j in range(15):
self.action = Button(win, text="h")
# bind to button function 'clickMe' that runs while <Button-1> clicked on it
self.action.bind("<Button-1>", self.clickMe)
self.action.grid(column=i, row=j)
def clickMe(self, event):
"""changes the 'fg' color of the events widget"""
event.widget.configure(foreground="red")
def main():
global win
win = Tk()
win.title('Minesweeper')
game = Minesweeper(win)
win.mainloop()
main()
Having trouble with this horizontal scroll bar... I've pieced this much together but could be completely wrong. The scroll bar is there where I want it to be it's just unusable and wont scroll anywhere.
There are about 83 columns and I can only see 15 of them.
import databasefile
import sqlite3
from tkinter import *
from tkinter.ttk import *
conn = sqlite3.connect("test_db.db")
cursor = conn.cursor()
returns = cursor.execute("SELECT * FROM Order_Number ORDER BY id DESC")
variables = [1,2,3,4,5,6,7,8,9,0,11,12,13,14,15,16,17,18,19,20]
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.CreateUI()
self.grid(sticky = (N,S,W,E))
parent.grid_rowconfigure(1, weight = 1)
parent.grid_columnconfigure(1, weight = 1)
parent.wm_title("Database")
parent.maxsize(width=1200,height=600)
parent.resizable(width=0,height=0)
def CreateUI(self):
tv = Treeview(self)
tv['columns'] = variables
# Scroll Bar section
vsb = Scrollbar(root,orient="horizontal",command=tv.xview)
tv.configure(xscrollcommand=vsb.set)
vsb.place(x=0,y=210,height=20, width=1200)
tv.column("#0", anchor="w",width=25)
for item in variables:
tv.heading(str(item),text=str(item))
tv.column(str(item),anchor='center',width=75)
tv.grid(sticky = (N,S,W,E))
self.grid_rowconfigure(index=1, weight = 1)
self.grid_columnconfigure(index=1, weight = 1)
for row in returns:
tv.insert('','end',values=(row))
root = Tk()
App(root)
root.mainloop()
I have below code snippet from the help of stackoverflow followers.
I am able to filter the table now. but when i try to filter it sorts first as i enabled sort for the view.
I want to create the QTableview such a way that if i click on header it should sort. and should have a dropdown box (may be combox box style) at the right of every header. i am uploading a snap of how i want (which i made it in .NET)
Code Snippet
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtCore, QtGui
class myWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(myWindow, self).__init__(parent)
self.centralwidget = QtGui.QWidget(self)
self.view = QtGui.QTableView(self.centralwidget)
self.view.setSortingEnabled(True)
self.gridLayout = QtGui.QGridLayout(self.centralwidget)
self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
self.setCentralWidget(self.centralwidget)
self.model = QtGui.QStandardItemModel(self)
for rowName in range(3) * 5:
self.model.invisibleRootItem().appendRow(
[ QtGui.QStandardItem("row {0} col {1}".format(rowName, column))
for column in range(3)
]
)
self.proxy = QtGui.QSortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.horizontalHeader = self.view.horizontalHeader()
self.horizontalHeader.sectionClicked.connect(self.horizontalHeader_Clicked)
#QtCore.pyqtSlot(int)
def horizontalHeader_Clicked(self, logicalIndex):
self.logicalIndex = logicalIndex
# local variable, and no parent
menuValues = QtGui.QMenu()
# delete the previous one
try:
self.signalMapper.deleteLater()
except:
pass
self.signalMapper = QtCore.QSignalMapper(self)
valuesUnique = [
self.proxy.index(row, self.logicalIndex).data().toString()
for row in xrange(self.proxy.rowCount())
]
print 'printing col %d values' % self.logicalIndex
for row in range(self.proxy.rowCount()):
print 'row %d Item %s' % (row,self.model.item(row, self.logicalIndex).text())
actionAll = QtGui.QAction("All", self)
actionAll.triggered.connect(self.actionAll)
menuValues.addAction(actionAll)
menuValues.addSeparator()
for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))):
action = QtGui.QAction(actionName, self)
self.signalMapper.setMapping(action, actionNumber)
action.triggered.connect(self.signalMapper.map)
menuValues.addAction(action)
self.signalMapper.mapped.connect(self.signalMapper_mapped)
headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
posY = headerPos.y() + self.horizontalHeader.height()
posX = headerPos.x() + self.horizontalHeader.sectionPosition(self.logicalIndex)
menuValues.exec_(QtCore.QPoint(posX, posY))
#QtCore.pyqtSlot()
def actionAll(self):
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( "",
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
#QtCore.pyqtSlot(int)
def signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( stringAction,
QtCore.Qt.CaseSensitive,
QtCore.QRegExp.FixedString
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
main = myWindow()
main.show()
main.resize(400, 600)
sys.exit(app.exec_())
This i how i am trying to get (Sort and filter)
If possible I need the ability to set the filter for selected columns only like in above image.
There is a discussion location here about the same topic: Quick way for QWidget in QHeaderView's columns?
They suggest that you would need to ditch the stock QHeaderView in your view, and provide your own custom widget for the header functionality, in order to place custom widgets into the header sections.