I tried to get an event if the windows visibility is changed.
I found out that there is an event called "Visibility".
The Operating System is Windows 64bit.
So I implemented in the following way:
root.bind('<Visibility>', visibilityChanged)
But I always got the state "VisibilityUnobscured" no matter if there is a window over it or not. What is the normal behaviour of this event? How can I implement a feature like that?
Example Prog:
import tkinter as tk
class GUI:
def __init__(self, master):
self.master = master
master.title("Test GUI")
self.master.bind('<Visibility>', self.visibilityChanged)
self.label = tk.Label(master, text="GUI")
self.label.pack()
self.close_button = tk.Button(master, text="Close", command=master.quit)
self.close_button.pack()
def visibilityChanged(self, event):
if (str(event.type) == "Visibility"):
print(event.state)
root = tk.Tk()
my_gui = GUI(root)
root.mainloop()
What is the normal behaviour of this event?
It's well described in the docs:The X server generates VisibilityNotify event whenever the visibility changes state and for any window.
How can I implement a feature like that?
It depends on how far you are going to go in your wishes, since this isn't a trivial task. Thus, don't treat that answer as a complete solution, but as a problem overview and a set of suggestions.
The event problem
Windows OS uses a message-passing model - the system communicates with your application window via messages, where each message is a numeric code that designates a particular event. Application window has an associated window procedure — a function that processes (responds or ignores) all messages sent.
The most generic solution is to set a hook to catch certain events/messages and it's possible either via SetWindowsHookEx or pyHook.
The main problem is to get event, because the Windows WM has no such message as VisibilityNotify. As I said in comment section - one option, on which we can rely, is the z-order
(there's possibility to check Visibility of the window, whenever this window changes it's position in z-order).Therefore our target message is either WM_WINDOWPOSCHANGING or WM_WINDOWPOSCHANGED.
A naive implementation:
import ctypes
import ctypes.wintypes as wintypes
import tkinter as tk
class CWPRETSTRUCT(ctypes.Structure):
''' a class to represent CWPRETSTRUCT structure
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644963(v=vs.85).aspx '''
_fields_ = [('lResult', wintypes.LPARAM),
('lParam', wintypes.LPARAM),
('wParam', wintypes.WPARAM),
('message', wintypes.UINT),
('hwnd', wintypes.HWND)]
class WINDOWPOS(ctypes.Structure):
''' a class to represent WINDOWPOS structure
https://msdn.microsoft.com/en-gb/library/windows/desktop/ms632612(v=vs.85).aspx '''
_fields_ = [('hwnd', wintypes.HWND),
('hwndInsertAfter', wintypes.HWND),
('x', wintypes.INT),
('y', wintypes.INT),
('cx', wintypes.INT),
('cy', wintypes.INT),
('flags', wintypes.UINT)]
class App(tk.Tk):
''' generic tk app with win api interaction '''
wm_windowposschanged = 71
wh_callwndprocret = 12
swp_noownerzorder = 512
set_hook = ctypes.windll.user32.SetWindowsHookExW
call_next_hook = ctypes.windll.user32.CallNextHookEx
un_hook = ctypes.windll.user32.UnhookWindowsHookEx
get_thread = ctypes.windll.kernel32.GetCurrentThreadId
get_error = ctypes.windll.kernel32.GetLastError
get_parent = ctypes.windll.user32.GetParent
wnd_ret_proc = ctypes.WINFUNCTYPE(ctypes.c_long, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)
def __init__(self):
''' generic __init__ '''
super().__init__()
self.minsize(350, 200)
self.hook = self.setup_hook()
self.protocol('WM_DELETE_WINDOW', self.on_closing)
def setup_hook(self):
''' setting up the hook '''
thread = self.get_thread()
hook = self.set_hook(self.wh_callwndprocret, self.call_wnd_ret_proc, wintypes.HINSTANCE(0), thread)
if not hook:
raise ctypes.WinError(self.get_error())
return hook
def on_closing(self):
''' releasing the hook '''
if self.hook:
self.un_hook(self.hook)
self.destroy()
#staticmethod
#wnd_ret_proc
def call_wnd_ret_proc(nCode, wParam, lParam):
''' an implementation of the CallWndRetProc callback
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644976(v=vs.85).aspx'''
# get a message
msg = ctypes.cast(lParam, ctypes.POINTER(CWPRETSTRUCT)).contents
if msg.message == App.wm_windowposschanged and msg.hwnd == App.get_parent(app.winfo_id()):
# if message, which belongs to owner hwnd, is signaling that windows position is changed - check z-order
wnd_pos = ctypes.cast(msg.lParam, ctypes.POINTER(WINDOWPOS)).contents
print('z-order changed: %r' % ((wnd_pos.flags & App.swp_noownerzorder) != App.swp_noownerzorder))
return App.call_next_hook(None, nCode, wParam, lParam)
app = App()
app.mainloop()
As you can see, this implementation has a similar behavior as a "broken" Visibility event.
This problem stems from the fact, that you can catch only thread-specified messages, hence application doesn't know about changes in the stack. It's just my assumptions, but I think that the cause of the broken Visibility is same.
Of course, we can setup a global hook for all messages, regardless a thread, but this approach requires a DLL injection, which is an another story for sure.
The visibility problem
It's not a problem to determine obscuration of the window, since we can rely on Graphical Device Interface.
The logic is simple:
Represent window (and each visible window, which is higher in the z-order) as a rectangle.
Subtract from main rectangle each rectangle and store result.
If final geometrical subtraction is:
... an empty rectangle — return 'VisibilityFullyObscured'
... a set of rectangles — return 'VisibilityPartiallyObscured'
... a single rectangle:
if geometrical difference between result and original rectangle is:
... an empty rectangle — return 'VisibilityUnobscured'
... a single rectangle — return 'VisibilityPartiallyObscured'
A naive implementation (with self-scheduled loop):
import ctypes
import ctypes.wintypes as wintypes
import tkinter as tk
class App(tk.Tk):
''' generic tk app with win api interaction '''
enum_windows = ctypes.windll.user32.EnumWindows
is_window_visible = ctypes.windll.user32.IsWindowVisible
get_window_rect = ctypes.windll.user32.GetWindowRect
create_rect_rgn = ctypes.windll.gdi32.CreateRectRgn
combine_rgn = ctypes.windll.gdi32.CombineRgn
del_rgn = ctypes.windll.gdi32.DeleteObject
get_parent = ctypes.windll.user32.GetParent
enum_windows_proc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
def __init__(self):
''' generic __init__ '''
super().__init__()
self.minsize(350, 200)
self.status_label = tk.Label(self)
self.status_label.pack()
self.after(100, self.continuous_check)
self.state = ''
def continuous_check(self):
''' continuous (self-scheduled) check '''
state = self.determine_obscuration()
if self.state != state:
# mimic the event - fire only when state changes
print(state)
self.status_label.config(text=state)
self.state = state
self.after(100, self.continuous_check)
def enumerate_higher_windows(self, self_hwnd):
''' enumerate window, which has a higher position in z-order '''
#self.enum_windows_proc
def enum_func(hwnd, lParam):
''' clojure-callback for enumeration '''
rect = wintypes.RECT()
if hwnd == lParam:
# stop enumeration if hwnd is equal to lParam (self_hwnd)
return False
else:
# continue enumeration
if self.is_window_visible(hwnd):
self.get_window_rect(hwnd, ctypes.byref(rect))
rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)
# append region
rgns.append(rgn)
return True
rgns = []
self.enum_windows(enum_func, self_hwnd)
return rgns
def determine_obscuration(self):
''' determine obscuration via CombineRgn '''
hwnd = self.get_parent(self.winfo_id())
results = {1: 'VisibilityFullyObscured', 2: 'VisibilityUnobscured', 3: 'VisibilityPartiallyObscured'}
rgns = self.enumerate_higher_windows(hwnd)
result = 2
if len(rgns):
rect = wintypes.RECT()
self.get_window_rect(hwnd, ctypes.byref(rect))
# region of tk-window
reference_rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)
# temp region for storing diff and xor rgn-results
rgn = self.create_rect_rgn(0, 0, 0, 0)
# iterate over stored results
for _ in range(len(rgns)):
_rgn = rgn if _ != 0 else reference_rgn
result = self.combine_rgn(rgn, _rgn, rgns[_], 4)
self.del_rgn(rgns[_])
if result != 2:
# if result isn't a single rectangle
# (NULLREGION - 'VisibilityFullyObscured' or COMPLEXREGION - 'VisibilityPartiallyObscured')
pass
elif self.combine_rgn(rgn, reference_rgn, rgn, 3) == 1:
# if result of XOR is NULLREGION - 'VisibilityUnobscured'
result = 2
else:
# 'VisibilityPartiallyObscured'
result = 3
# clear up regions to prevent memory leaking
self.del_rgn(rgn)
self.del_rgn(reference_rgn)
return results[result]
app = App()
app.mainloop()
Unfortunately, this approach is far from a working solution, but it's tweakable in a perspective.
Related
I'm trying to put text into a QTextEdit in PyQT5, but every time I set the text for one text field it copies the same value into the other text fields on the same page. Even though I specified that it has to only change the contents of the QTextEdit field of which I have given the ID. Is this normal behavior, or is there a workaround? Any advice would be appreciated.
class NodeUIDScanner(QObject):
rfidTag = pyqtSignal(str)
scanEnabled = bool
def init(self):
self.scanEnabled = False
def run(self):
self.scanEnabled = True
dev = rfid_scanner.init('COM7') #change depending on usb port
while self.scanEnabled:
rfid = rfid_scanner.read(dev)
if rfid is None:
continue
if 'rfid' not in rfid:
continue
rfid = rfid['rfid']
self.scanEnabled = False
self.rfidTag.emit(rfid)
def stop(self):
self.scanEnabled = False
class Main:
def __init__(self):
self.main_win = QMainWindow()
self.ui = RFIDScannerDesign.Ui_MainWindow()
self.ui.setupUi(self.main_win)
#default page on load is the create page
self.ui.stackedWidget.setCurrentWidget(self.ui.pageCreate)
#nav buttons
self.ui.buttonCreate.clicked.connect(self.showCreate)
self.ui.buttonRead.clicked.connect(self.showRead)
self.ui.buttonUpdate.clicked.connect(self.showUpdate)
self.ui.buttonDelete.clicked.connect(self.showDelete)
self.ui.read_editButton.clicked.connect(self.showEdit)
#scan buttons
self.ui.create_buttonScan.clicked.connect(self.scanNodeUidCreate)
self.ui.read_buttonScan.clicked.connect(self.scanNodeUidRead)
self.ui.update_buttonScanOld.clicked.connect(self.scanNodeUidOld)
self.ui.update_buttonScanNew.clicked.connect(self.scanNodeUidNew)
self.ui.delete_buttonScan.clicked.connect(self.scanNodeUidDelete)
#submit button gives alert asking if you're sure you have the right input
self.ui.create_buttonSubmit.clicked.connect(self.alertCreate)
self.ui.edit_buttonSubmit.clicked.connect(self.alertEdit)
self.ui.update_buttonSubmit.clicked.connect(self.alertUpdate)
self.ui.delete_buttonSubmit.clicked.connect(self.alertDelete)
#yes or no buttons with alert. Yes sends info to db. No keeps you on the page
self.ui.create_buttonSubmitYes.clicked.connect(self.submitCreate)
self.ui.create_buttonSubmitNo.clicked.connect(self.hideCreateAlert)
self.ui.edit_buttonSubmitYes.clicked.connect(self.submitEdit)
self.ui.edit_buttonSubmitNo.clicked.connect(self.hideEditAlert)
self.ui.update_buttonSubmitYes.clicked.connect(self.submitUpdate)
self.ui.update_buttonSubmitNo.clicked.connect(self.hideUpdateAlert)
self.ui.delete_buttonSubmitYes.clicked.connect(self.submitDelete)
self.ui.delete_buttonSubmitNo.clicked.connect(self.hideDeleteAlert)
#define QThread here so it can be used
self.main_win.thread = QThread()
self.worker = NodeUIDScanner()
self.worker.moveToThread(self.main_win.thread)
self.main_win.thread.started.connect(self.worker.run)
def show(self):
self.main_win.show()
#Functions for nav buttons to show the right page
def showCreate(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageCreate)
self.ui.create_buttonSubmit.show()
self.ui.create_alert.hide()
self.ui.create_buttonSubmitYes.hide()
self.ui.create_buttonSubmitNo.hide()
def showRead(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageRead)
self.ui.read_editButton.show()
def showUpdate(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageUpdate)
self.ui.update_buttonSubmit.show()
self.ui.update_alert.hide()
self.ui.update_buttonSubmitYes.hide()
self.ui.update_buttonSubmitNo.hide()
def showDelete(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageDelete)
self.ui.delete_buttonSubmit.show()
self.ui.delete_alert.hide()
self.ui.delete_buttonSubmitYes.hide()
self.ui.delete_buttonSubmitNo.hide()
def showEdit(self):
self.ui.stackedWidget.setCurrentWidget(self.ui.pageEdit)
self.ui.edit_buttonSubmit.show()
self.ui.edit_alert.hide()
self.ui.edit_buttonSubmitYes.hide()
self.ui.edit_buttonSubmitNo.hide()
#Threads for scanning nodeUIDs asynchronously
def readRFID(self, rfid):
try:
response_rfid = database.query(IndexName='rfid-index',
KeyConditionExpression=Key('rfid').eq(rfid))
if response_rfid['Count'] > 0:
print('RFID already in database', rfid)
print('Node uid:', int(response_rfid['Items'][0]['node_uid']))
return int(response_rfid['Items'][0]['node_uid'])
except Exception as e:
print(e)
def scanNodeUidCreate(self):
if self.ui.create_buttonScan.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeCreate)
self.main_win.thread.start()
def scanNodeUidRead(self):
if self.ui.read_buttonScan.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeRead)
self.main_win.thread.start()
def scanNodeUidOld(self):
if self.ui.update_buttonScanOld.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeOld)
self.main_win.thread.start()
def scanNodeUidNew(self):
if self.ui.update_buttonScanNew.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeNew)
self.main_win.thread.start()
def scanNodeUidDelete(self):
if self.ui.delete_buttonScan.clicked:
self.worker.stop()
self.worker.rfidTag.connect(self.displayNodeDelete)
self.main_win.thread.start()
#Slot for passing signal from worker thread to keep it asynchronous
def displayNodeCreate(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageCreate:
self.ui.create_nodeUid.setText(str(self.readRFID(node)))
self.ui.create_buttonScan.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
def displayNodeRead(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageRead:
self.ui.read_nodeUid.setText(str(self.readRFID(node)))
self.ui.edit_nodeUid.setText(str(self.readRFID(node)))
self.ui.read_buttonScan.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
def displayNodeOld(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageUpdate:
self.ui.update_nodeUidOld.setText(str(self.readRFID(node)))
self.ui.update_buttonScanOld.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
def displayNodeNew(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageUpdate:
self.ui.update_nodeUidNew.setText(str(self.readRFID(node)))
self.ui.update_buttonScanNew.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
def displayNodeDelete(self, node):
if self.ui.stackedWidget.currentWidget() == self.ui.pageDelete:
self.ui.delete_nodeUid.setText(str(self.readRFID(node)))
self.ui.delete_buttonScan.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
#Submit button, when pressed gives alerts
def alertCreate(self):
self.ui.create_buttonSubmit.hide()
self.ui.create_alert.show()
self.ui.create_buttonSubmitYes.show()
self.ui.create_buttonSubmitNo.show()
def alertEdit(self):
self.ui.edit_buttonSubmit.hide()
self.ui.edit_alert.show()
self.ui.edit_buttonSubmitYes.show()
self.ui.edit_buttonSubmitNo.show()
def alertUpdate(self):
self.ui.update_buttonSubmit.hide()
self.ui.update_alert.show()
self.ui.update_buttonSubmitYes.show()
self.ui.update_buttonSubmitNo.show()
def alertDelete(self):
self.ui.delete_buttonSubmit.hide()
self.ui.delete_alert.show()
self.ui.delete_buttonSubmitYes.show()
self.ui.delete_buttonSubmitNo.show()
#TODO add db connection
def submitCreate(self):
self.showCreate()
self.ui.create_alertSucces.show()
self.fade(self.ui.create_alertSucces)
def submitEdit(self):
self.showRead()
self.ui.read_alertSucces.show()
self.fade(self.ui.read_alertSucces)
def submitUpdate(self):
self.showUpdate()
self.ui.update_alertSucces.show()
self.fade(self.ui.update_alertSucces)
def submitDelete(self):
self.showDelete()
self.ui.delete_alertSucces.show()
self.fade(self.ui.delete_alertSucces)
def hideCreateAlert(self):
self.ui.create_buttonSubmit.show()
self.ui.create_alert.hide()
self.ui.create_buttonSubmitYes.hide()
self.ui.create_buttonSubmitNo.hide()
def hideEditAlert(self):
self.ui.edit_buttonSubmit.show()
self.ui.edit_alert.hide()
self.ui.edit_buttonSubmitYes.hide()
self.ui.edit_buttonSubmitNo.hide()
def hideUpdateAlert(self):
self.ui.update_buttonSubmit.show()
self.ui.update_alert.hide()
self.ui.update_buttonSubmitYes.hide()
self.ui.update_buttonSubmitNo.hide()
def hideDeleteAlert(self):
self.ui.delete_buttonSubmit.show()
self.ui.delete_alert.hide()
self.ui.delete_buttonSubmitYes.hide()
self.ui.delete_buttonSubmitNo.hide()
#fade effect for animations on labels and alerts
def fade(self, widget):
self.effect = QGraphicsOpacityEffect()
widget.setGraphicsEffect(self.effect)
self.animation = QtCore.QPropertyAnimation(self.effect, b"opacity")
self.animation.setDuration(3000)
self.animation.setStartValue(1)
self.animation.setEndValue(0)
self.animation.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
main_win = Main()
main_win.show()
sys.exit(app.exec_())
The problem is that every time a "buttonScan" is clicked, you connect it to a slot. Signals can be connected to an indefinite number of functions, and it is also possible to connect a signal to the same function more than once.
Stopping the worker won't change anything, as the object still exists, and when the signal will be emitted every (previously) connected function will be called as well.
Since all functions do practically the same thing, a better choice is to connect the signal to a single function, and set the target widgets when switching between pages.
Note that there are other problems in your code, most importantly:
clicked is a signal, using if someObject.someSignal: is pointless as it will always be considered valid, and since the function is already connected to that signal there would be no point in checking it anyway;
calling wait() of an external thread in the main thread is wrong, since it is blocking; I won't address the issue here as it's off topic to the question;
Unfortunately, your code is quite convoluted, so I'm only providing modifications for a single page, then you have to implement the rest on your own.
class Main:
def __init__(self):
# ...
self.ui.create_buttonScan.clicked.connect(self.startScan)
# ...
self.worker.rfidTag.connect(self.rfidReceived)
def showCreate(self):
if self.ui.stackedWidget.currentWidget == self.ui.pageCreate:
return
# ...
self.worker.stop() # just to be sure...
self.targetField = self.ui.create_nodeUid
self.targetCheck = self.ui.create_buttonScan
def startScan(self):
self.worker.stop()
self.main_win.thread.start()
def rfidReceived(self, node):
self.targetField.setText(str(self.readRFID(node))
self.targetCheck.setChecked(False)
self.worker.stop()
self.main_win.thread.quit()
self.main_win.thread.wait()
Note that more than half of your code is boilerplate just like the functions that update the text edits: they always do the same thing, just for different targets. This makes your code unnecessarily long and convoluted, so, prone to errors and annoying editing whenever you need to change its behavior. Since it seems clear that almost all your pages have the same interface (except for the read page) it's pointless to have functions that do the same thing repeated 5 times, especially considering that you're using very similar names that might be easily confused; each function is repeated for its 5 "targets" (the page and its buttons), that makes 30 functions, while you could just have 4 or 5. A better modularization and usage of classes will certainly be a smarter choice, will cut down your code by at least half the size, and will make debugging and maintenance much easier.
I'm building a large, complicated program, one half of which involves a GUI, which I'm building using Tkinter.
Previous iterations of this GUI seemed to work as intended. However, in the latest version, when I try to run a demonstration (see the demo() function in the code below), my whole computer freezes, and my only option is to carry out a hard reset.
Does anyone have any ideas as to why this might be happening? Some points which might be useful:
If I run the code below with the line self.gui.mainloop() commented out, the desired window appears on the screen for long enough for the toaster message to be displayed, and then closes without any freezing.
The ArrivalsManagerDaughter and DeparturesManagerDaughter objects transmit data wirelessly to another device, but they shouldn't be doing anything, other than being initialised, in the code which is causing the freezing. I don't believe that these are the cause of the problem, although I could well be wrong.
Here's the whole Python file which I'm trying to run. I'm happy to post more code if requested.
"""
This code holds a class which manages transitions between the
"recommendations" and "custom placement" windows, and also oversees their
interactions with Erebus.
"""
# GUI imports.
from tkinter import *
# Non-standard imports.
import ptoaster
# Custom imports.
from erebus.arrivals_manager_daughter import ArrivalsManagerDaughter
from erebus.departures_manager_daughter import DeparturesManagerDaughter
# Local imports.
from charon.custom_placement_window import Custom_Placement_Window
from charon.recommendations_window import Recommendations_Window
# Local constants.
REFRESH_INTERVAL = 1000
# ^^^ in miliseconds ^^^
##############
# MAIN CLASS #
##############
class Comptroller:
""" The class in question. """
def __init__(self, welcome=False, delete_existing_ledger=False,
internal=False, diagnostics=False, path_to_icon=None):
self.close_requested = False
self.path_to_icon = path_to_icon
self.recommendations = dict()
self.arrivals_manager = ArrivalsManagerDaughter(self,
diagnostics=diagnostics)
self.departures_manager = DeparturesManagerDaughter(
delete_existing=delete_existing_ledger, internal=internal,
diagnostics=diagnostics)
self.gui = Tk()
self.top = Frame(self.gui)
self.window = Recommendations_Window(self)
self.is_on_recommendations_window = True
self.arrange()
if welcome:
print_welcome()
def add_recommendation(self, ticket, epc, column, row):
""" Add a recommendation to the dictionary. """
recommendation = dict()
recommendation["ticket"] = ticket
recommendation["epc"] = epc
recommendation["column"] = column
recommendation["row"] = row
self.recommendations[ticket] = recommendation
def remove_recommendation(self, ticket):
""" Delete a recommendation from the dictionary. """
del self.recommendations[ticket]
def get_top(self):
""" Return the top-level GUI object. """
return self.top
def arrange(self):
""" Arrange the widgets. """
self.window.get_top().pack()
self.top.pack()
def switch_to_custom_placement(self, ticket, epc):
""" Switch window from "Recommendations" to "Custom Placement". """
columns = self.arrivals_manager.columns
rows = self.arrivals_manager.rows
self.window.get_top().pack_forget()
self.window = Custom_Placement_Window(self, ticket, epc, columns,
rows)
self.window.get_top().pack()
self.is_on_recommendations_window = False
def switch_to_recommendations(self):
""" Switch window from "Custom Placement" to "Recommendations". """
self.window.get_top().pack_forget()
self.window = Recommendations_Window(self)
self.window.get_top().pack()
self.is_on_recommendations_window = True
def refresh(self):
""" Refresh the "recommendations" window, as necessary. """
if (self.is_on_recommendations_window and
self.arrivals_manager.clear_quanta()):
self.window.refresh_rec_table()
self.departures_manager.clear_backlog()
if self.close_requested:
self.kill_me()
else:
self.gui.after(REFRESH_INTERVAL, self.refresh)
def simulate_recommendation(self, ticket, epc, column, row):
""" Simulate receiving a transmission from the Pi. """
self.add_recommendation(ticket, epc, column, row)
self.window.refresh_rec_table()
def request_close(self):
self.close_requested = True
def run_me(self):
""" Run the "mainloop" method on the GUI object. """
self.gui.after(REFRESH_INTERVAL, self.refresh)
self.gui.title("Charon")
if self.path_to_icon:
self.gui.iconphoto(True, PhotoImage(file=self.path_to_icon))
self.gui.protocol("WM_DELETE_WINDOW", self.request_close)
self.gui.mainloop()
def kill_me(self):
""" Kill the mainloop process, and shut the window. """
self.gui.destroy()
####################
# HELPER FUNCTIONS #
####################
def print_welcome():
""" Print a welcome "toaster" message. """
message = ("Notifications about boxes leaving the coldstore will be "+
"posted here.")
ptoaster.notify("Welcome to Charon", message,
display_duration_in_ms=REFRESH_INTERVAL)
def print_exit(epc):
""" Print an exit "toaster" message. """
message = "Box with EPC "+epc+" has left the coldstore."
ptoaster.notify("Exit", message)
###########
# TESTING #
###########
def demo():
""" Run a demonstration. """
comptroller = Comptroller(welcome=True, delete_existing_ledger=True,
internal=True, diagnostics=True)
comptroller.simulate_recommendation(1, "rumpelstiltskin", 0, 0)
comptroller.simulate_recommendation(2, "beetlejuice", 0, 0)
comptroller.run_me()
###################
# RUN AND WRAP UP #
###################
def run():
demo()
if __name__ == "__main__":
run()
FOUND THE PROBLEM: the culprit was actually calling a ptoaster function from within a Tkinter GUI. Which leads me on to my next question: Is it possible to combine ptoaster and Tkinter in an elegant fashion, and, if so, how?
The problem ocurred when calling print_welcome(), which in turn calls one of the ptoaster functions. It seems that Tkinter and ptoaster do not play together nicely. Removing the reference to print_welcome() from the Comptroller class put a stop to any freezing.
(On a side note: I'd be very grateful to anyone who could suggest an elegant method of combing ptoaster with Tkinter.)
Try changing
def demo():
""" Run a demonstration. """
comptroller = Comptroller(welcome=True, delete_existing_ledger=True,
internal=True, diagnostics=True)
comptroller.simulate_recommendation(1, "rumpelstiltskin", 0, 0)
comptroller.simulate_recommendation(2, "beetlejuice", 0, 0)
comptroller.run_me()
###################
# RUN AND WRAP UP #
###################
def run():
demo()
if __name__ == "__main__":
run()
To simply
if __name__ == "__main__":
""" Run a demonstration. """
comptroller = Comptroller(welcome=True, delete_existing_ledger=True,
internal=True, diagnostics=True)
comptroller.simulate_recommendation(1, "rumpelstiltskin", 0, 0)
comptroller.simulate_recommendation(2, "beetlejuice", 0, 0)
comptroller.run_me()
As to why this might happening, since you're creating an object by instantiating Comptroller inside a regular function demo, the object is not being "retained" after the demo exits.
EDIT
If you would like to still maintain demo and run you could create a class Demo and store the instance globally.
Or maybe a simple global variable inside demo to retain a "reference" to the instance Comptroller.
EDIT: Made some changes based on feedback in comments.
I am trying to use a statusTextF function to show a WAIT/READY message on python GUI.
I have defined, at the beginning, when the GUI is drawn.
statusText = True
and then call the following when i want it to wait:
statusText = False
I call statusText as a global variable everywhere I use it, and I have the statusTextF function as shown below:
def statusTextF():
if statusText == True:
statusTitle = tk.Label(root,text="READY")
statusTitle.config(font=statusFont,bg="light green")
statusX = 500
statusY = 450
statusTitle.place(x=statusX,y=statusY)
separation = 45
else:
statusTitle = tk.Label(root,text="WAIT")
statusTitle.config(font=statusFont,bg="light red")
statusX = 500
statusY = 450
statusTitle.place(x=statusX,y=statusY)
separation = 45
I am seeing 'READY' all the time though.
What could be wrong?
Now, it doesn't display anything, and also I get a light red is unknown colour error.
Here's a demonstration of how to do something like you want. It doesn't use a Queue for communication between the main GUI thread and the status updating thread since the amount of information being exchanged between them is so minimal. It instead uses a threading.Lock to control access to a global variable shared between them. Note that it's also implicitly being used to protect updates to the separation global.
Using a queue.Queue to exchange the information ought be fairly easy to implement — should you need one for some reason — since they don't need a separate Lock because they implement "all the required locking semantics" interally.
Note: I've tried to (mostly) follow the PEP 8 - Style Guide for Python Code to make it fairly readable — which I strongly suggest you read (and also follow).
import random
import time
import tkinter as tk
import tkinter.font as tkFont
import threading
class StatusUpdater(threading.Thread):
# # Not really needed since it doesn't do anything except call superclass ctor here.
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs) # Initialize base class constructor.
def run(self):
global status_flag_lock, status_flag # Must declare these to change their values.
while True:
# Randomly update status_flag.
value = random.randint(0, 100)
with status_flag_lock:
status_flag = bool(value % 2) # True if odd number.
time.sleep(.5) # Pause updating for a little while.
def statusTextF():
global status_flag_lock, separation # Must declare these to change their values.
with status_flag_lock:
if status_flag:
statusTitle.config(text="READY", bg="light green")
separation = 45
else:
statusTitle.config(text="WAIT", bg="pink")
separation = 55
root.after(250, statusTextF) # Continue the polling.
status_flag_lock = threading.Lock() # To control concurrent access.
root = tk.Tk()
root.geometry('600x600')
STATUS_FONT = tkFont.Font(family='Courier', size=8)
STATUS_X, STATUS_Y = 500, 450
status_flag = True
separation = 45
statusTitle = tk.Label(root, text="UNKNOWN", font=STATUS_FONT, bg="gray50")
statusTitle.place(x=STATUS_X, y=STATUS_Y)
status_updater = StatusUpdater(daemon=True)
status_updater.start() # Start updating of status flag.
root.after(250, statusTextF) # Start polling status every 250 millisecs.
root.mainloop()
I want to use the bot_create function with a button but I keep getting (on line 20) the problem "bots not defined" so I moved the function down below the button but got the problem "bot_create not defined".
I didn't get this problem using C++ and I'm new to Python. How should I arrange the functions?
import tkinter as tk
import numpy as np
import multiprocessing as mp
bots_max = 1000 # Maximum number of bots
bot = []
bot_count = 0
# Menu functions
def save_field():
pass
# Field functions
def field_clear():
pass
# Bots functions
def bots_create():
bot[bot_count] = bots
bot_count += 1
main = tk.Tk()
field_sides = 600
ctrls_width = 200
main.geometry("800x600")
main.resizable(0, 0)
main.title("Swarm Simulator v1.0")
# Controls menu on left side
button1 = tk.Button(main, text = "Button 1").pack(side = "left", command = bots_create())
class environment:
def __init__():
pass
class wall:
def __init__():
pass
# Bots
class bots:
alive = True
def __init__():
alive = True
# Field where bots live
field = tk.Canvas(main, width = field_sides, height = field_sides, bg = "white").pack(side = "right")
for particle in bots:
print("|")
main.mainloop()
Here's a version of your code that fixes all the syntactic problems, and so compiles (what I really mean is that my IDE now thinks its ok). It also runs, but I don't know if it does what you intended. See my comments in the code:
import tkinter as tk
import numpy as np
import multiprocessing as mp
# moved your class defs up to fix problems with accessing them before they are defined
class environment:
def __init__(self): # need a self param here
pass
class wall:
def __init__(self): # need a self param here
pass
# Bots
class bots:
alive = True
def __init__(self): # need a self param here
alive = True
bots_max = 1000 # Maximum number of bots
bot = []
# bot_count = 0 # this no longer does anything. use `len(bot)` to get the number of objects in the 'bot' list
# Menu functions
def save_field():
pass
# Field functions
def field_clear():
pass
# Bots functions
def bots_create():
# bot[bot_count] = bots # this will crash as it is referring to a non-existent location in the list
# also, your use of "bots" here makes no sense
# bot_count += 1 # this makes 'bot_count' a local variable, which is not what you want
bot.append(bots()) # not sure this is what you want, but this creates a new 'bots' object and adds it to the 'bot' list
main = tk.Tk()
field_sides = 600
ctrls_width = 200
main.geometry("800x600")
main.resizable(0, 0)
main.title("Swarm Simulator v1.0")
# Controls menu on left side
button1 = tk.Button(main, text = "Button 1").pack(side = "left", command = bots_create())
# Field where bots live
field = tk.Canvas(main, width = field_sides, height = field_sides, bg = "white").pack(side = "right")
for particle in bot: # maybe you want to iterate over the 'bot' list instead of the 'bots' type?
print("|")
main.mainloop()
As #khelwood says, it seems that you should swap the use of the names bot and bots per the way you are using them
I have issue with GTK's TreeView with ListStore. Records are updated, but sometime only when I hover it.
Bigger problem are new records - It's like it stops to displaying new ones unless I scroll to bottom all the time - which is weird.
I use Glade.
My code (slightly simplified)
class UI(SampleUI):
gladefile = 'ui.glade'
iterdict = {}
def __init__(self, module):
super().__init__(module)
def start(self):
self.fetch_widgets()
self.connect_events()
self.window.show()
def fetch_widgets(self):
self.window = self.builder.get_object('window')
self.liststore = self.builder.get_object('store')
def connect_events(self):
handlers = {
"on_window_close" : Gtk.main_quit,
"on_start_working": self.start_working,
"on_stop_working": self.stop_working
}
self.builder.connect_signals(handlers)
self.module.url_pending_action = self.url_pending
self.module.url_succeed_action = self.url_update
def start_working(self, button):
self.module.start()
def stop_stop(self, button):
self.module.stop()
def url_pending(self, data):
self.iterdict[data['url']] = self.liststore.append([0, data['url'], 0, '', data['text']])
def url_update(self, data):
_iter = self.iterdict[data['url']]
self.liststore[_iter][1] = data['some_field1']
self.liststore[_iter][2] = data['some_field2']
Methods self.url_pending and self.url_update are called by threads (at most 30 running at the same time) created in self.module
I checked and new records are correctly appended into ListStore - I can read data from it. Window is working fine, but there are no new items at the bottom.
Ideas?
Ok, I made research and I figured out that GTK don't like being called from outside of main thread.
There was methods in Gdk to lock GTK calls
Gtk.threads_enter()
...
Gtk.threads_leave()
But now it's deprecated and GTK documentation says that every GTK call should be in main thread.
My workaround:
# [...]
def start(self):
# [...]
GObject.timeout_add(100, self.query_do)
# [...]
def query_do(self):
while len(self.gtk_calls):
self.gtk_calls.pop()()
GObject.timeout_add(100, self.query_do)
And I'm just adding into query
self.gtk_calls.insert(0, lambda: anything())
The code isn't as clear as it was before, but works perfect.