PyQt stacked widget not moving to next page until function ends - widget

Hi i have a program that when a button is pressed it should move to the next stacked widget replace some text in some labels and then execute some functions but this is not working and moves to the next page when the functions completes
The code is :
QtCore.QObject.connect(self.StartBtn, QtCore.SIGNAL(_fromUtf8("clicked()")), self.start) #Start
def nextPage(self):
current_page = self.stackedWidget.currentIndex()
i = int(current_page) + 1
self.stackedWidget.setCurrentIndex(i)
def start(self):
self.nextPage()
self.animation()
self.runFunctions()
def runFunctions(self):
try:
self.DbLabel.setText(_translate("MainWindow", "Checking Database", None))
if checkDb == True:
self.DbLabel.setText(_translate("MainWindow", "Checking Database ", None))
self.checkDbFun()
self.DbLabel.setText(_translate("MainWindow", "Database checked", None))
else:
self.checkedDbImg.setPixmap(QtGui.QPixmap(_fromUtf8("Files\\x.png")))
self.DbLabel.setText(_translate("MainWindow", "Database not checked", None))
except Exception as e:
self.AlertMessage(e)
def animation(self):
self.LoadingGif = QtGui.QLabel(MainWindow)
movie = QtGui.QMovie("Files\\loading.png")
self.LoadingGif.setMovie(movie)
self.LoadingGif.setAlignment(QtCore.Qt.AlignCenter)
self.gridLayout_2.addWidget(self.LoadingGif, 4, 1, 1, 1)
movie.start()
So what i want is to press StartBtn then move to next stacked widget page load the animation image and then run the functions

You probably need to let Qt process events in order for the tab change to take effect. You could do that two ways:
insert a qApp.processEvents() between the animation() and runFunctions() (qApp is in PyQt5.QtWidgets)
call runFunctions() via a single-shot timer: QTimer.singleShot(0, runFunctions), which will schuedule runFunctions via the event loop, so any pending events will first be processed (because runFunctions() is the latest added), then runFunctions() will get called. If you actually have params for runFunctions(), use a lambda.
I favor the first approach because I find it more clearly indicates what is happening (events need to be processed), but I recommend also adding a comment on that line that "so stack tab can change".
BTW you should be use the new-style notation for signals-slot connections, much cleaner, of the form "signal.connect(slot)":
self.StartBtn.clicked.connect(self.start)
So for approach #1 your code would look like this:
from PyQt5.QtWidgets import qApp
...
self.StartBtn.clicked.connect(self.start)
...
def start(self):
self.nextPage()
self.animation()
qApp.processEvents()
self.runFunctions()
...

Related

Intercept QTabWidget tab change [duplicate]

I am trying to make the user not switch to the next TAB where "Form 2" is located until they fill in Form 1.
I tried the "currentChange" event but it doesn't work the way I want as it shows the alert when it was already changed from TAB.
Is there a way to leave the current TAB fixed until the task is complete?
I attach the code and an image
import sys
from PyQt5.QtCore import Qt
from PyQt5 import QtWidgets
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.setGeometry(0, 0, 800, 500)
self.setLayout(QtWidgets.QVBoxLayout())
#flag to not show the alert when starting the program
self.flag = True
#changes to True when the form is completed
self.form_completed = False
#WIDGET TAB 1
self.widget_form1 = QtWidgets.QWidget()
self.widget_form1.setLayout(QtWidgets.QVBoxLayout())
self.widget_form1.layout().setAlignment(Qt.AlignHCenter)
label_form1 = QtWidgets.QLabel("FORM 1")
self.widget_form1.layout().addWidget(label_form1)
#WIDGET TAB 2
self.widget_form2 = QtWidgets.QWidget()
self.widget_form2.setLayout(QtWidgets.QVBoxLayout())
self.widget_form2.layout().setAlignment(Qt.AlignHCenter)
label_form2 = QtWidgets.QLabel("FORM 2")
self.widget_form2.layout().addWidget(label_form2)
#QTABWIDGET
self.tab_widget = QtWidgets.QTabWidget()
self.tab_widget.currentChanged.connect(self.changed)
self.tab_widget.addTab(self.widget_form1,"Form 1")
self.tab_widget.addTab(self.widget_form2, "Form 2")
self.layout().addWidget(self.tab_widget)
def changed(self,index):
if self.flag:
self.flag = False
return
if not self.form_completed:
QtWidgets.QMessageBox.about(self, "Warning", "You must complete the form")
return
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = MyWidget()
mw.show()
sys.exit(app.exec_())
The currentChanged signal is emitted when the index is already changed (the verb is in past tense: Changed), so if you want to prevent the change, you have to detect any user attempt to switch tab.
In order to do so, you must check both mouse and keyboard events:
left mouse clicks on the tab bar;
Ctrl+Tab and Ctrl+Shift+Tab on the tab widget;
Since you have to control that behavior from the main window, the only solution is to install an event filter on both the tab widget and its tabBar(), then if the action would change the index but the form is not completed, you must return True so that the event won't be handled by the widget.
Please consider that the following assumes that the tab that has to be kept active is the current (the first added tab, or the one set using setCurrentIndex()).
class MyWidget(QtWidgets.QWidget):
def __init__(self):
# ...
self.tab_widget.installEventFilter(self)
self.tab_widget.tabBar().installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == event.KeyPress and \
event.key() in (Qt.Key_Left, Qt.Key_Right):
return not self.form_completed
elif source == self.tab_widget.tabBar() and \
event.type() == event.MouseButtonPress and \
event.button() == Qt.LeftButton:
tab = self.tab_widget.tabBar().tabAt(event.pos())
if tab >= 0 and tab != self.tab_widget.currentIndex():
return self.isInvalid()
elif source == self.tab_widget and \
event.type() == event.KeyPress and \
event.key() in (Qt.Key_Tab, Qt.Key_Backtab) and \
event.modifiers() & Qt.ControlModifier:
return self.isInvalid()
return super().eventFilter(source, event)
def isInvalid(self):
if not self.form_completed:
QTimer.singleShot(0, lambda: QtWidgets.QMessageBox.about(
self, "Warning", "You must complete the form"))
return True
return False
Note that I showed the message box using a QTimer in order to properly return the event filter immediately.
Also consider that it's good practice to connect signals at the end of an object creation and configuration, and this is much more important for signals that notify property changes: you should not connect it before setting the property that could trigger it.
Since an empty QTabWidget has a -1 index, as soon as you add the first tab the index is changed to 0, thus triggering the signal. Just move the currentChanged signal connection after adding the tabs, and you can get rid of the self.flag check.

Cannot delete Room Calendar item with exchagelib code

My problem looks weird.
Exchange 2010 SP1. Code looks for conflicting record in Room Calendar and suposed to delete it.
Code looks like:
#DELEGATE also doesn't work
roomAccount = Account(primary_smtp_address=room_mailbox, config=config,
autodiscover=False, access_type=IMPERSONATION)
items = roomAccount.calendar.filter(start__gt=now_with_past)
for item in items:
if (start_dt_ews != start_dt_remote or item.subject != remote_record_subject or duration != remote_record_duration):
has_conflicts = detect_conflicts(roomAccount, item)
if has_conflicts:
process_conflict(item, 'update_conflict')
remove_meeting_data(item, item.organizer)
continue
def detect_conflicts(roomAccount, item):
try:
has_conflicts = False
if item.conflicting_meeting_count > 0:
if item.start_dt != item.start:
has_conflicts = True
return has_conflicts
except Exception as e:
trace_back = traceback.format_exc()
log_str = "Error in process_service " + str(e) + " " + str(trace_back)
Logger.error(log_str)
return False
def process_conflict(item, category):
if item.recurrence:
conflict_notify_and_delete(item, category, True)
else:
conflict_notify_and_delete(item, category, False)
Logger.error(item.subject + " meeting conflict error.")
def conflict_notify_and_delete(item, category, serial):
send_email(item.organizer, category, (item.subject))
try:
if serial:
item.delete(affected_task_occurrences=ALL_OCCURRENCIES)
item.save()
else:
# doesn't work
item.delete(send_meeting_cancellations=SEND_TO_NONE,
affected_task_occurrences=ALL_OCCURRENCIES)
# or also doesn't work
item.delete()
# or raise trash folder absense error.
item.move_to_trash()
# or raise trash folder absense error again.
item.soft_delete()
#abrakadabra atempts with rescheduling and subsequent deletion raise
# "Set action is invalid for property. (field: FieldURI(field_uri='calendar:StartTimeZone'))" error
item.start = UTC_NOW() + timedelta(days=6000)
item.save(update_fields=['start'])
item.delete()
The strangest fact about all this - any of delete() processing simply silent - no errors, no exceptions, everything looks like to be just fine while actually nothing is deleted of modified.. Second strangest fact - sometimes but not every time i'm trying to item.save() after item.delete() , it raise " AttributeError("Folder must be supplied when in save-only mode")", but item may be deleted at once. And may be not :((
This weird things happen only in part of code that process conflicting calendar items. Notconflicting items processing is fine - deleting and modifiying are ok.
Does anybody has any idea, what is going on and how to finally delete conflicting record from Room calendar without canceling a meeting from organizers's calendar - we do not want user to loose his item and information inside it? I've tried to google EWS setiings that can cause such weirdness but with no luck :(

when there are two inputs, how to get the function input, instead of the input from the main body

Here is my code, it is copyrighted cited here
Al Sweigart. Invent Your Own Computer Games with Python. August 28, 2015. https://inventwithpython.com/invent4thed/chapter5.html. Accessed December 4, 2020.
#import modules
import random
import time
#def functions
def intro():
guess = int(input("There are two caves, in one is magic treasure and in the other is a
terrible monster. Hit 1 or 2 to guess. "))
return guess
def check_cave(guess):
print("the cave is dank")
time.sleep(2)
print("you hear a sound")
time.sleep(2)
print("a monster jumps out and opens its jaws and...")
time.sleep(2)
friendly_cave = random.randint(1,2)
if str(guess) == str(friendly_cave):
print("leaves so you can take the treasure, congratulations!")
else:
print("kills you bahahaha")
print(guess)
#main body of code
play_again = 'no'
guess = int(input("please enter a guess of one or two "))
while play_again == 'yes' or 'y':
intro()
check_cave(guess)
play_again = input("want to play again? yes or no...")
if play_again == 'yes':
continue
else:
break
I expect the code to ask the user for input from the main body of the code, then ask for same input from inside a function later (the function returns the input), I expect the function's returned value to overwrite the original input. but it doesnt. Instead it keeps the original input from outside the function what can I do about this?
You must assign value returned from function intro() to a variable:
while play_again == 'yes' or 'y':
guess = intro() # modified this line
check_cave(guess)
play_again = input("want to play again? yes or no...")
if play_again == 'yes':
continue
else:
break

A function I created can only be called once, second time it shows an error

I created a function to draw a bargraph with turtle. The first call works fine but fails when calling it for the second time:
"File "C:\Users\NTC\AppData\Local\Programs\Python\Python37\lib\turtle.py", line 1292, in _incrementudc raise Terminator turtle.Terminator"
only thing i tried is using t.terminator at the end, same results
def bar_chart():
t = turtle.Turtle()
screen = turtle.Screen()
##snip # lines detailing the lines drawing
for value in range(1,11): # the upper limit has to be more thanhighest value in the "count"
t.forward(20)
t.write((" " + str(value)), align="left",font=("Arial", 8, "normal"))
screen.exitonclick()
just expect it to be called multiple times in a looped program.
The screen.exitonclick() function has no business being in a function that you plan to call multiple times. It's logically the last thing you do in your program. You also shouldn't keep allocating turtles to do the same thing, allocate one and reuse it. Something like:
from turtle import Screen, Turtle
def bar_chart(t):
for value in range(1, 11): # the upper limit has to be more than highest value in the "count"
t.forward(20)
t.write((" " + str(value)), align="left", font=("Arial", 8, "normal"))
screen = Screen()
turtle = Turtle()
bar_chart(turtle)
screen.exitonclick()

Detect automatically opened tabs with Pyppeteer

With pyppeteer it is possible to get all open tabs via the .pages function. This is working fine until a website opens a new tab by itself (e.g. after a click on a button). In this case, the new tab isn’t listed in the return of **.pages*.
Is there a way to detect this new tab, so that I can work with it like I can do with the other tabs/pages?
(I didn't test it with puppeteer, but I think it'll behave the same.)
Code Example (Sadly I have to use Python 2.7., so I have to use yield from):
self.browser = yield from launch(appMode=True, closeAtExit=False)
pages = yield from self.browser.pages()
self.page = pages[len(pages) - 1] # Open w3schools in the init tab
yield from self.page.goto("https://www.w3schools.com/tags/att_a_target.asp")
link = yield from self.page.waitForSelector('a.w3-btn:nth-child(4)')
yield from link.click()
yield from asyncio.sleep(5) # Just to give some extra time...
pages1 = yield from self.browser.pages()
self.log.info("Count: " + str(len(pages1))) # Should be 2 now
for mpage in pages1:
self.log.info("URL: " + str(mpage.url))
Output:
TARGETS: {'246562630E35EEAD0384B80658C827F8': <pyppeteer.target.Target object at 0x03482F10>}
TARGETS: {'246562630E35EEAD0384B80658C827F8': <pyppeteer.target.Target object at 0x03482F10>}
INFO:__main__:Count: 1
INFO:__main__:URL: https://www.w3schools.com/tags/att_a_target.asp
INFO:__main__:Done!