I want my program to show another window where it says that it's the break time when the determined requirements are met (in this case the exact same hour) but when I run it, the first window (not the login one) crashes or simply doesn't show up until it is the hour.
(horatime and min can be changed for testing it)
main and main2 are windows programed in PyCharm they are clocks.
I want to show main window until the time set in the code ("timecheck") is reached then "timecheck" will hide main and show main2
this is my code:
'''
from PyQt5 import QtWidgets, uic
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QTimer, QTime
from PyQt5.uic import loadUi
import datetime
app = QtWidgets.QApplication([])
login = uic.loadUi("ventana1.ui")
time = uic.loadUi("ventana2.ui")
error = uic.loadUi("ventana3.ui")
def gui_login():
name = login.lineEdit.text()
password = login.lineEdit_2.text()
if len(name)==0 or len(password)==0:
login.label_5.setText("Ingrese todos los datos")
elif name == "Pedro" and password == "1234":
gui_clock()
timecheck()
else:
gui_error()
def gui_clock():
login.hide()
main.show()
def timecheck():
horatime = int(19)
min = int(21)
while True:
if horatime == datetime.datetime.now().hour and min == datetime.datetime.now().minute:
main.hide()
main2.show()
break
#this is the clock1, it shows the time in red
class VentanaPrincipal(QMainWindow):
def __init__(self):
super(VentanaPrincipal, self).__init__()
loadUi('ventana2.ui', self)
timer = QTimer(self)
timer.timeout.connect(self.displayTime)
timer.start(1000)
def displayTime(self):
currentTime = QTime.currentTime()
displayText = currentTime.toString('hh:mm:ss')
self.reloj.setText(displayText)
self.lcdNumber.display(displayText)
#this is the clock2, it shows the time in green
class VentanaPrincipal2(QMainWindow):
def __init__(self2):
super(VentanaPrincipal2, self2).__init__()
loadUi('ventana4.ui', self2)
timer2 = QTimer(self2)
timer2.timeout.connect(self2.displayTime)
timer2.start(1000)
def displayTime(self):
currentTime2 = QTime.currentTime()
displayText2 = currentTime2.toString('hh:mm:ss')
self.reloj.setText(displayText2)
self.lcdNumber.display(displayText2)
def gui_error():
login.hide()
error.show()
def back_error():
error.hide()
login.show()
login.pushButton.clicked.connect(gui_login)
error.pushButton.clicked.connect(regresar_error)
login.show()
main = VentanaPrincipal()
main2 = VentanaPrincipal2()
app.exec()
i tried a lot of things to change the loop to show what i want, but im just an amateur on programming so i cant find a way to make my programm to do what i want
Related
code it's clean from bugs but when is running program will freezing
the program its countdown using button to start the countdown
the picture below discripe the layout
enter image description here
the probelm is im using loop to changing label text and that make program freezed
from time import sleep
import PyQt5.QtWidgets as Qtw
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
pushButton = Qtw.QPushButton("start",clicked = lambda: setting_label())
self.layout().addWidget(pushButton)
my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(my_label)
self.show()
def setting_label():
t = 1200
while t:
h = t // 3600
m = t // 60
s = t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
my_label.setText(timer)
sleep(1)
t -= 1
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()
The way the code is written in the OP, it doesn't really get stuck or frozen. But rather, the display fails to update. You can get around this by trying to force Qt to update the GUI with app.processEvents(). Put it in your setting_label function after the setText():
self.my_label.setText(timer)
app.processEvents()
The Preferred Way
Using app.processEvents() a lot is usually discouraged. Another way to make a countdown timer is to use a QTimer. There is a little more overhead in setting up all of the signals and slots. But it can be very powerful. Here is an example
from time import sleep
import PyQt5.QtWidgets as Qtw
from PyQt5.QtCore import QTimer
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
pushButton = Qtw.QPushButton("start",clicked = self.start_countdown)
self.layout().addWidget(pushButton)
self.my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(self.my_label)
self.timer = QTimer() # create a new QTimer instance
self.timer.setInterval(1000) # make it fire every 1000 msec
self.t = 1200
self.timer.timeout.connect(self.setting_label) # connect the timeout signal to self.setting_label
self.show()
def start_countdown(self):
self.t = 1200
self.timer.start()
def setting_label(self):
if self.t == 0:
self.timer.stop()
print('timer stopped')
h = self.t // 3600
m = self.t // 60
s = self.t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
self.my_label.setText(timer)
self.t -= 1
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()
Edit: This solution works for the OP, but others have pointed out that doing this using threading can cause unexpected behavior, crashes, etc. so it's better to do this using other methods (for example: this answer)
The interface freezes because the while loop you use to change the label blocks the event loop of the app (i.e. receiving inputs). You can fix this by moving the function to a thread like this:
import threading
from time import sleep
import PyQt5.QtWidgets as Qtw
class MainWindow(Qtw.QWidget):
def __init__(self):
super().__init__()
self.setLayout(Qtw.QVBoxLayout())
def setting_label():
t = 1200
while t:
h = t // 3600
m = t // 60
s = t % 60
timer = "{:02d}:{:02d}:{:02d}".format(h,m,s)
my_label.setText(timer)
sleep(1)
t -= 1
pushButton = Qtw.QPushButton(
"start",
clicked=lambda: thread.Thread(target=setting_label).start()
)
self.layout().addWidget(pushButton)
my_label = Qtw.QLabel("00:00:00")
self.layout().addWidget(my_label)
self.show()
app = Qtw.QApplication([])
window = MainWindow()
app.exec_()
The code below just displays a tree view of computer drives. Each time a new file/folder is selected, the view scrolls to make this new selection visible.
Question 1: While this works, the initial selection after the application is launched doesn't trigger the scroll. Why?
Question 2: If the instructions:
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
are inverted:
self.my_view.resizeColumnToContents(0)
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
the first column size is not adjusted either on the initial display, only after. Why?
import sys
from PyQt5.QtCore import Qt, QModelIndex, QDir
from PyQt5.QtWidgets import QApplication, QTreeView, QMainWindow, QFileSystemModel, QAbstractItemView
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
# Instance variables
self.my_view = QTreeView()
self.my_model = QFileSystemModel()
# Init FS model to show all computer drives
model_root_path = str(self.my_model.myComputer())
self.my_model.setRootPath(model_root_path)
# Init tree view
self.my_view.setModel(self.my_model)
self.my_view.setRootIndex(self.my_model.index(model_root_path))
self.my_view.setSelectionMode(QAbstractItemView.SingleSelection)
self.my_view.setSelectionBehavior(QAbstractItemView.SelectRows)
# Connect selection change events to custom slot
select_model = self.my_view.selectionModel()
select_model.currentRowChanged.connect(self.current_row_changed)
# Main window
self.setCentralWidget(self.my_view)
self.setGeometry(200, 200, 800, 600)
# Select initial row on view
focus_path = QDir.currentPath()
focus_index = self.my_model.index(focus_path)
self.my_view.setCurrentIndex(focus_index)
def current_row_changed(self):
"""Current row of the model has changed"""
# Scroll view to new row
index = self.my_view.selectionModel().currentIndex()
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
# Show path of current row in window title
absolute_path = self.my_model.filePath(index)
self.setWindowTitle(absolute_path)
def main():
a = QApplication(sys.argv)
mw = MyWindow()
mw.show()
sys.exit(a.exec_())
if __name__ == '__main__':
main()
`
Edit: After using the good solution provided by #ekhumoro, my sample code above worked. However this other piece of code still didn't:
import os
import sys
from PyQt5.QtCore import pyqtSignal, QTimer, QDir, Qt
from PyQt5.QtWidgets import QMainWindow, QGridLayout, QWidget, QTreeView, QAbstractItemView, QFileSystemModel, \
QApplication
class AppWindow(QMainWindow):
default_folder_path = "."
def __init__(self):
super().__init__()
self.folder_view = FolderTreeView()
self.folder_view.folder_has_changed.connect(self.folder_changed)
self.build_ui()
self.show()
# Select initial folder
self.select_initial_folder()
def build_ui(self):
main_widget = QWidget()
layout = QGridLayout(main_widget)
layout.addWidget(self.folder_view)
self.setCentralWidget(main_widget)
self.setGeometry(200, 100, 800, 600)
def select_initial_folder(self):
folder_index = self.folder_view.get_index(AppWindow.default_folder_path)
if folder_index.isValid():
self.folder_view.select_folder(folder_index)
def folder_changed(self, folder_path):
if not os.path.isdir(folder_path):
print("Non existing folder:", folder_path)
return
class FolderTreeView(QTreeView):
folder_has_changed = pyqtSignal(str)
def __init__(self):
super().__init__()
self.folder_tree_model = FolderTreeModel()
self.setModel(self.folder_tree_model)
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
def select_folder(self, folder_index):
self.setCurrentIndex(folder_index)
def currentChanged(self, current, previous):
super(FolderTreeView, self).currentChanged(current, previous)
# Scroll the view to current item and resize folder name column
QTimer.singleShot(50, lambda: self.delayed_scroll(current))
# Emit signal for other uses
self.folder_has_changed.emit(self.folder_tree_model.filePath(current))
def delayed_scroll(self, index):
self.scrollTo(index, QAbstractItemView.EnsureVisible)
self.resizeColumnToContents(0)
def get_index(self, folder_path):
return self.folder_tree_model.index(folder_path)
class FolderTreeModel(QFileSystemModel):
def __init__(self):
super().__init__()
self.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot)
self.setRootPath("")
def main():
app = QApplication(sys.argv)
window = AppWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The first problem may be caused if, by default, the model initialises its current index to the current directory. This would mean that if you set it again to the same index, the row-change signal will not be emitted (because nothing changed). This can be fixed by calling the row-change handler directly:
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
...
focus_path = QDir.currentPath()
focus_index = self.my_model.index(focus_path)
self.my_view.setCurrentIndex(focus_index)
self.current_row_changed()
def current_row_changed(self):
index = self.my_view.currentIndex()
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
...
As to the second problem: when you call scrollTo, it may have to expand several directories in order to select the required index. This could obviously change the width of the first column, so you should always call resizeColumnToContents afterwards in order to get the correct width.
UPDATE:
I think there is also another problem caused by timing issues. The QFileSystemModel must work asynchronously to some extent, because it has to request resources from the operating system and then wait for the response. Also, before it gets the response, it cannot know in advance exactly how much data it is going to receive, because the file-system may have been updated while it was waiting. Potentially, the response could include data from a huge directory containing thousands of files. So in order to keep the GUI responsive, the data is processed in batches which are of a sufficient size to fill the current view. If the current index is set before the window has been shown and all its widgets fully laid out, there is no guarantee that the view will be able to resize its columns correctly.
This can be fixed by explicitly re-calling the row-change handler via a one-shot timer with a small delay. This should allow the view to recalculate its column widths correctly:
...
focus_path = QDir.currentPath()
focus_index = self.my_model.index(focus_path)
self.my_view.setCurrentIndex(focus_index)
QTimer.singleShot(50, self.current_row_changed)
def current_row_changed(self):
index = self.my_view.currentIndex()
self.my_view.scrollTo(index, QAbstractItemView.EnsureVisible)
self.my_view.resizeColumnToContents(0)
I have a QToolButton with a menu. When the QToolButton is clicked, the menu appears. The default behavior is that when an action is clicked from the menu, the menu disappears. How can I make it so that the menu stays open until the user clicks elsewhere?
Here is minimal code that shows the behavior:
from PyQt4 import QtGui, QtCore
import sys, os
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
toolButton = QtGui.QToolButton()
toolButton.setText('Select')
toolMenu = QtGui.QMenu()
for i in range(3):
action = toolMenu.addAction(str(i))
action.setCheckable(True)
toolButton.setMenu(toolMenu)
toolButton.setPopupMode(QtGui.QToolButton.InstantPopup)
toolButton.show()
sys.exit(app.exec_())
Shamelessly porting this code from this c++ answer:
from PyQt4 import QtGui, QtCore
import sys, os
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
toolButton = QtGui.QToolButton()
toolButton.setText('Select')
toolMenu = QtGui.QMenu()
for i in range(3):
checkBox = QtGui.QCheckBox(str(i), toolMenu)
checkableAction = QtGui.QWidgetAction(toolMenu)
checkableAction.setDefaultWidget(checkBox)
toolMenu.addAction(checkableAction)
toolButton.setMenu(toolMenu)
toolButton.setPopupMode(QtGui.QToolButton.InstantPopup)
toolButton.show()
sys.exit(app.exec_())
I made a PyQt5 version based on #three_pineapples's answer and solved what #Space Hornet tried to solve--get the states of checkboxes.
According to the doc of QWidgetAction:
Note that it is up to the widget to activate the action, for example
by reimplementing mouse event handlers and calling QAction::trigger().
So I think one needs to connect the checkbox's stateChanged signal to the action's trigger method.
I also added the a text to the action therefore action.text() gives the same text label as the checkbox. May not be necessary though.
Complete code below:
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import pyqtSlot
#pyqtSlot(QtWidgets.QAction)
def menuTriggered(action):
print('state change=',action.text())
return
#pyqtSlot(QtWidgets.QMenu)
def buttonTriggered(menu):
actions=menu.findChildren(QtWidgets.QWidgetAction)
for actii in actions:
wii=actii.defaultWidget()
stateii=wii.isChecked()
print('action', actii.text(), 'is checked:',stateii)
return
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
toolButton = QtWidgets.QToolButton()
toolButton.setText('Select')
toolMenu = QtWidgets.QMenu()
for i in range(3):
checkBox = QtWidgets.QCheckBox(str(i), toolMenu)
checkableAction = QtWidgets.QWidgetAction(toolMenu)
checkableAction.setDefaultWidget(checkBox)
# Add a text to action, for easier handling in slot
checkableAction.setText(str(i))
# Connect the checkbox's stateChanged to QAction.trigger
checkBox.stateChanged.connect(checkableAction.trigger)
toolMenu.addAction(checkableAction)
toolMenu.triggered.connect(menuTriggered)
toolButton.setMenu(toolMenu)
toolButton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
# NOTE that toolButton.clicked work, toolButton.triggered not
toolButton.clicked.connect(lambda: buttonTriggered(toolMenu))
toolButton.show()
sys.exit(app.exec_())
The easiest solution I've managed to find is to make an addition to actionEvent:
class myMenu(QtGui.QMenu):
def actionEvent(self, event):
super().actionEvent(event)
self.show()
I was looking for the exact same thing and used the code from three_pineapples, but I had trouble connecting it the way I wanted. I thought I'd share my solution in case anyone else finds it useful.
The button function is very similar but my code includes my solution for connecting the checkboxes to a function. Also, since they are stored in a list one can connect them individually or in a loop if that's easier.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os
##### main window class #####
class main_window(QMainWindow):
def __init__(self):
super(main_window, self).__init__()
self.resize(300, 200)
wdgMain = QWidget()
self.setCentralWidget(wdgMain)
layMain = QGridLayout(wdgMain)
wdgMain.setLayout(layMain)
## checkable tool button ##
tlbToolButton1 = QToolButtonChx("Check Me Out!")
layMain.addWidget(tlbToolButton1, 0, 0)
tlbToolButton1.addItems(["Item" + str(n) for n in range(8)])
## connect tool button checkboxes ##
for i in range(tlbToolButton1.length()):
tlbToolButton1.index(i).stateChanged.connect(self.checkbox_tester)
def checkbox_tester(self, choice):
objSender = self.sender()
strObjectName = objSender.objectName()
print "Action Checker::", strObjectName, ":", choice
##### end of main window class #####
##### checkable tool button class #####
class QToolButtonChx(QToolButton):
def __init__(self, strText=""):
super(QToolButtonChx, self).__init__()
self.setText(strText)
tlbMenu = QMenu(self)
self.setMenu(tlbMenu)
self.setPopupMode(QToolButton.MenuButtonPopup)
self.lstchxItems = []
def addItem(self, strItem):
self.lstchxItems.append(QCheckBox(strItem, self.menu()))
actCheckItem = QWidgetAction(self.menu())
actCheckItem.setDefaultWidget(self.lstchxItems[-1])
self.lstchxItems[-1].setObjectName('chx' + strItem)
self.menu().addAction(actCheckItem)
def addItems(self, lstItems):
for strItem in lstItems:
self.lstchxItems.append(QCheckBox(strItem, self.menu()))
actCheckItem = QWidgetAction(self.menu())
actCheckItem.setDefaultWidget(self.lstchxItems[-1])
self.lstchxItems[-1].setObjectName('chx' + strItem)
self.menu().addAction(actCheckItem)
def index(self, intIndex):
return self.lstchxItems[intIndex]
def length(self):
return len(self.lstchxItems)
##### end of checkable tool button class #####
if __name__ == '__main__':
app = QApplication(sys.argv)
winMain = QMainWindow()
gui = main_window()
gui.show()
sys.exit(app.exec_())
I have the following code and am wondering if there is a way to make this more efficient. The setCurrentItem() and scrollToItem() functions seem to slow the process down considerable. Also, I would like to see the items show up in the list as they are added instead of all at once after the loop has completed. Any help or discussion would be greatly appreaciated.
import sys
from math import *
#from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import (Qt, SIGNAL, QSize)
from PyQt4.QtGui import (QApplication, QDialog, QLabel, QListWidget, QListWidgetItem, QPushButton, QScrollArea, QTextDocument, QVBoxLayout)
from time import localtime, strftime
import time
class LogDlg(QDialog):
def __init__(self, parent=None):
super(LogDlg, self).__init__(parent)
self.resize(450, 380)
self.log1 = QListWidget()
self.log2 = QListWidget()
lbl = QLabel()
lbl_2 = QLabel()
lbl.setText("Communications Log")
lbl_2.setText("Command/Data Log")
self.pushButton = QPushButton()
self.pushButton.setMaximumSize(QSize(110, 24))
self.pushButton.setObjectName("pushButton")
self.pushbutton = QPushButton
self.pushButton.setText("Start Log Loop")
layout = QVBoxLayout()
layout.addWidget(self.pushButton)
layout.addWidget(lbl)
layout.addWidget(self.log1)
layout.addWidget(lbl_2)
layout.addWidget(self.log2)
self.setLayout(layout)
self.setWindowTitle("Transaction Logs")
self.connect(self.pushButton,SIGNAL("clicked()"),self.logLoop)
self.time = time.time()
def logLoop(self):
for i in range(1000):
print i
self.addLog("This is a test","c",True)
def timeStamp(self):
now = time.time()
localtime = time.localtime(now)
milliseconds = '%02d' % int((now - int(now)) * 100)
val = time.strftime('%H:%M:%S.', localtime) + milliseconds
return val
def clearUi(self):
self.log1.clear()
self.log2.clear()
def addLog(self, data, type="c", ts=False):
# pass
t = self.timeStamp()
if ts == True:
data = t + " " + data
if type == "c":
self.listItem1 = QListWidgetItem()
self.listItem1.setText(data)
self.log1.addItem(self.listItem1)
# self.log1.scrollToItem(self.listItem1)
self.log1.setCurrentItem(self.listItem1)
elif type == "d":
self.listItem2 = QListWidgetItem()
self.listItem2.setText(data)
self.log2.addItem(self.listItem2)
# self.log2.scrollToItem(self.listItem2)
self.log2.setCurrentItem(self.listItem2)
app = QApplication(sys.argv)
form = LogDlg()
form.open()
app.exec_()
Your issue doesn't have anything to do with .scrollToItem or .setCurrentItem. The loop in logLoop method doesn't give any chance for the Qt event loop to update things. One way to solve it is, give the Qt a chance to update the GUI with QApplication.processEvents(). So if you modify the logLoop as following, you should see the items as they are added:
def logLoop(self):
for i in range(1000):
print i
self.addLog("This is a test","c",True)
QApplication.processEvents()
This is useful to some degree. If the time between processEvents is small enough you'll get a responsive UI. But once things get more complicated and the time to complete tasks for each segment increases, you'll need to delegate that piece of code to a separate thread (QThread) in order to keep the GUI responsive.
One other issue is, things will get slower once you have a large number of items in the QListWidget. You may notice that adding the 25th item is faster than 925th item. That's because QListWidget (or QTableWidget/QTreeWidget) doesn't scale well. If you are going to have large number of items, model/view framework (QListView/QTableView/QTreeView) should be your choice.
In my application, I have a call to an external module which spawns some threads, does some stuff, then returns a value. I'm trying to get a QMessageBox to show before and a QLabel to update after this is complete, but I'm stumped. The code goes something like this (called from QObject.connect on a button):
def _process(self):
self._message_box.show()
for i in range(3):
rv = external_module_function_with_threads() // blocking function call
label = getattr(self, "label%d" % (i + 1))
label.setText(rv)
When I click the button and the function is called, the message box only shows after the loop completes. The labels only update after the loop completes as well. I tried calling label.repaint() in the loop, but all that seems to do is make the message box show up earlier (but with no text in it).
I know I'm not violating the "GUI operations from outside the main thread" rule (...right?), so is there a way to force an update?
For your message box use self._message_box.exec_(). From my understanding of your question, I think this will do what you want.
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
class Main(QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QVBoxLayout(self)
button = QPushButton("Press me")
self.label = QLabel("Run #")
map(layout.addWidget, [button, self.label])
button.pressed.connect(self.buttonPressed)
self.messageBox = QMessageBox()
def buttonPressed(self):
self.messageBox.exec_()
Thread().run(self.label)
class Thread(QThread):
def run(self, label):
for x in range(5):
self.updateLabel(label)
app.processEvents()
time.sleep(.5)
def updateLabel(self, label):
try:
number = int(label.text().split(" ")[-1])
number += 1
except ValueError:
number = 0
label.setText("Run %i" % number)
app = QApplication([])
main = Main()
main.show()
sys.exit(app.exec_())