I am getting an error trying to attach a procedure within a module to a TextChanged event.
Attach event code:
import NewMortgage
txtNewMortgageValue = LogicalTreeHelper.FindLogicalNode(_tikitSender,'txtLoanAmount')
txtNewMortgageValue.TextChanged += NewMortgage.evtMortgage_RecalcNet
Module NewMortgage.py:
def evtMortgage_RecalcNet(sender, e):
MessageBox.Show('test')
I moved the LogicalTreeHelper functionality into the module, then the event code was accessible.
I also created a Global module to hold frequently used vars.
I first include the modules
import leanGlobal
import leanNewMortgage
Below is the leanNewMortgage module, the instance of class created at the bottom of file.
Notice this module also has 'import leanGlobal' so I can get access to the global vars and functions.
###### NEW MORTGAGE FUNCTIONS & VARS ######
from System.Windows import MessageBox
from System.Windows import LogicalTreeHelper
import leanGlobal
class NewMortgage(object):
def Init(self):
self.txtLoanAmount = LogicalTreeHelper.FindLogicalNode(leanGlobal.oGlobal.Sender, 'txtLoanAmount')
self.txtLenderChaps = LogicalTreeHelper.FindLogicalNode(leanGlobal.oGlobal.Sender, 'txtLenderChaps')
self.txtCashBack = LogicalTreeHelper.FindLogicalNode(leanGlobal.oGlobal.Sender, 'txtCashBack')
self.txtRetention = LogicalTreeHelper.FindLogicalNode(leanGlobal.oGlobal.Sender, 'txtRetention')
self.txtNETAdvance = LogicalTreeHelper.FindLogicalNode(leanGlobal.oGlobal.Sender, 'txtNETAdvance')
self.AddEvents()
def AddEvents(self):
self.RemoveEvents()
self.txtLoanAmount.TextChanged += self.evtMortgage_RecalcNet
self.txtLenderChaps.TextChanged += self.evtMortgage_RecalcNet
self.txtCashBack.TextChanged += self.evtMortgage_RecalcNet
self.txtRetention.TextChanged += self.evtMortgage_RecalcNet
def RemoveEvents(self):
self.txtLoanAmount.TextChanged -= self.evtMortgage_RecalcNet
self.txtLenderChaps.TextChanged -= self.evtMortgage_RecalcNet
self.txtCashBack.TextChanged -= self.evtMortgage_RecalcNet
self.txtRetention.TextChanged -= self.evtMortgage_RecalcNet
def evtMortgage_RecalcNet(self,sender, e):
self.RemoveEvents()
if (self.txtLoanAmount.Text==""):
self.txtLoanAmount.Text="0"
if (self.txtLenderChaps.Text==""):
self.txtLenderChaps.Text="0"
if (self.txtCashBack.Text==""):
self.txtCashBack.Text="0"
if (self.txtRetention.Text==""):
self.txtRetention.Text="0"
self.txtNETAdvance.Text=(float(self.txtLoanAmount.Text.replace(',', ''))
-float(self.txtLenderChaps.Text.replace(',', ''))
+float(self.txtCashBack.Text.replace(',', ''))
-float(self.txtRetention.Text.replace(',', ''))).ToString()
self.AddEvents()
NewMortgage.txtLoanAmount=object
NewMortgage.txtLenderChaps=object
NewMortgage.txtCashBack=object
NewMortgage.txtRetention=object
NewMortgage.txtNETAdvance=object
oNewMortgage=NewMortgage()
I create the Global class first:
leanGlobal.oGlobal.Init(_tikitEntity,_tikitMatter.ToString(),_tikitDbAccess,_tikitSender)
then I call the calc module(leanNewMortgage.py), and all works OK
leanNewMortgage.oNewMortgage.Init()
Notice I also add events, and also temporarily remove them when fired.
This is because I wanted a central event rather than creating an event for every textbox. As the event modifies the textboxes the events need to be temporarily disabled, otherwise an infinite loop will occur!
The main lesson for me, relating to the question was how to create a tidy program structure.
Whilst I an new to IronPython I am happy with the structure but would love to hear any comments.
Related
I am currently working on a GUI using qt designer. I am wondering how I should go about printing strings on the GUI that acts like a logger window. I am using pyqt5.
Adapted from Todd Vanyo's example for PyQt5:
import sys
from PyQt5 import QtWidgets
import logging
# Uncomment below for terminal log messages
# logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(name)s - %(levelname)s - %(message)s')
class QTextEditLogger(logging.Handler):
def __init__(self, parent):
super().__init__()
self.widget = QtWidgets.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
class MyDialog(QtWidgets.QDialog, QtWidgets.QPlainTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
logTextBox = QTextEditLogger(self)
# You can format what is printed to text box
logTextBox.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logging.getLogger().addHandler(logTextBox)
# You can control the logging level
logging.getLogger().setLevel(logging.DEBUG)
self._button = QtWidgets.QPushButton(self)
self._button.setText('Test Me')
layout = QtWidgets.QVBoxLayout()
# Add the new logging box widget to the layout
layout.addWidget(logTextBox.widget)
layout.addWidget(self._button)
self.setLayout(layout)
# Connect signal to slot
self._button.clicked.connect(self.test)
def test(self):
logging.debug('damn, a bug')
logging.info('something to remember')
logging.warning('that\'s not right')
logging.error('foobar')
app = QtWidgets.QApplication(sys.argv)
dlg = MyDialog()
dlg.show()
dlg.raise_()
sys.exit(app.exec_())
Threading issues
Note: as of late 2022, this is still the highest ranked answer. The OP doesn't seem to be active anymore, so I took the liberty of editing it because the lack of explanation of the implications of its usage is a common cause of closely related questions.
Be aware that the above code will only work for single threaded programs. If you are sure that your program doesn't use threading, then it's fine. Be aware, though: you have to be completely sure about that, also considering external modules. Qt included.
Don't underestimate this aspect: even some Qt classes that are thread safe are actually not single threaded; for instance, QFileSystemModel is a thread safe object, but it does use threading for file system crawling. If it faces a problem for any reason (ie. access permissions), it will output some debug messages from those threads, and you'll probably still get issues.
Possible results of underestimated threading issues while accessing UI elements include:
unexpected behavior;
drawing artifacts;
lots of "unrelated" debug messages;
possible fatal crash of the program;
So, as a rule of thumb, the above has to be considered as unsafe and should not be used, since it attempts to access the QPlainTextEdit widget even from external threads: widgets are not thread-safe, and can only be accessed from the main thread.
Since there is no immediate way to know if the logging source is in the same thread or not without pointlessly affecting performance, it's better to always use a thread safe solution in any case (see the other answers that consider this aspect).
If you are using the Python logging module to can easily create a custom logging handler that passes the log messages through to a QPlainTextEdit instance (as described by Christopher).
To do this you first subclass logging.Handler. In this __init__ we create the QPlainTextEdit that will contain the logs. The key bit here is that the handle will be receiving messages via the emit() function. So we overload this function and pass the message text into the QPlainTextEdit.
import logging
class QPlainTextEditLogger(logging.Handler):
def __init__(self, parent):
super(QPlainTextEditLogger, self).__init__()
self.widget = QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
def write(self, m):
pass
Create an object from this class, passing it the parent for the QPlainTextEdit (e.g. the main window, or a layout). You can then add this handler for the current logger.
# Set up logging to use your widget as a handler
log_handler = QPlainTextEditLogger(<parent widget>)
logging.getLogger().addHandler(log_handler)
Here's a complete working example based on mfitzp's answer:
import sys
from PyQt4 import QtCore, QtGui
import logging
# Uncomment below for terminal log messages
# logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(name)s - %(levelname)s - %(message)s')
class QPlainTextEditLogger(logging.Handler):
def __init__(self, parent):
super().__init__()
self.widget = QtGui.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
class MyDialog(QtGui.QDialog, QPlainTextEditLogger):
def __init__(self, parent=None):
super().__init__(parent)
logTextBox = QPlainTextEditLogger(self)
# You can format what is printed to text box
logTextBox.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logging.getLogger().addHandler(logTextBox)
# You can control the logging level
logging.getLogger().setLevel(logging.DEBUG)
self._button = QtGui.QPushButton(self)
self._button.setText('Test Me')
layout = QtGui.QVBoxLayout()
# Add the new logging box widget to the layout
layout.addWidget(logTextBox.widget)
layout.addWidget(self._button)
self.setLayout(layout)
# Connect signal to slot
self._button.clicked.connect(self.test)
def test(self):
logging.debug('damn, a bug')
logging.info('something to remember')
logging.warning('that\'s not right')
logging.error('foobar')
if (__name__ == '__main__'):
app = None
if (not QtGui.QApplication.instance()):
app = QtGui.QApplication([])
dlg = MyDialog()
dlg.show()
dlg.raise_()
if (app):
app.exec_()
Thread-safe version
class QTextEditLogger(logging.Handler, QtCore.QObject):
appendPlainText = QtCore.pyqtSignal(str)
def __init__(self, parent):
super().__init__()
QtCore.QObject.__init__(self)
self.widget = QtWidgets.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
self.appendPlainText.connect(self.widget.appendPlainText)
def emit(self, record):
msg = self.format(record)
self.appendPlainText.emit(msg)
Usage
logTextBox = QTextEditLogger(self)
# log to text box
logTextBox.setFormatter(
logging.Formatter(
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s'))
logging.getLogger().addHandler(logTextBox)
logging.getLogger().setLevel(logging.DEBUG)
# log to file
fh = logging.FileHandler('my-log.log')
fh.setLevel(logging.DEBUG)
fh.setFormatter(
logging.Formatter(
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s'))
logging.getLogger().addHandler(fh)
Alex's answer should be ok in a single thread scenario, but if you are logging in another thread (QThread) you may get the following warning:
QObject::connect: Cannot queue arguments of type 'QTextCursor'
(Make sure 'QTextCursor' is registered using qRegisterMetaType().)
This is because you are modifying the GUI (self.widget.appendPlainText(msg)) from a thread other than the main thread without using the Qt Signal/Slot mechanism.
Here is my solution:
# my_logger.py
import logging
from PyQt5.QtCore import pyqtSignal, QObject
class Handler(QObject, logging.Handler):
new_record = pyqtSignal(object)
def __init__(self, parent):
super().__init__(parent)
super(logging.Handler).__init__()
formatter = Formatter('%(asctime)s|%(levelname)s|%(message)s|', '%d/%m/%Y %H:%M:%S')
self.setFormatter(formatter)
def emit(self, record):
msg = self.format(record)
self.new_record.emit(msg) # <---- emit signal here
class Formatter(logging.Formatter):
def formatException(self, ei):
result = super(Formatter, self).formatException(ei)
return result
def format(self, record):
s = super(Formatter, self).format(record)
if record.exc_text:
s = s.replace('\n', '')
return s
# gui.py
... # GUI code
...
def setup_logger(self)
handler = Handler(self)
log_text_box = QPlainTextEdit(self)
self.main_layout.addWidget(log_text_box)
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
handler.new_record.connect(log_text_box.appendPlainText) # <---- connect QPlainTextEdit.appendPlainText slot
...
Sounds like you'll want to use a QPlainTextEdit widget set to read-only.
Consider changing the background color to gray to give the user a hint that it is not editable. It is also up to you if you want it to be scrollable or the text selectable.
This answer can get you started subclassing QPlainTextEdit to scroll with output, save to a file, whatever.
Sifferman’s answer appears to be the most elegant to me. Regardless, I've tried all in this post. They all work, but notice that if you try, e.g., to write a test and create a log entry on it you might get a nasty error like
RuntimeError: wrapped C/C++ object of type QPlainTextEdit has been deleted
After loosing my mind for a couple hours, I noticed that it's quite important to delete the handler manually when closing the window,
def closeEvent(self, event):
...
root_logger = logging.getLogger()
root_logger.removeHandler(self.logger)
...
super().closeEvent(event)
I am very new to this PyQt, so please I need some help. I created a MainWindow which is imported from another file. collect_host_status also imported from another py file. Basicly the GUI works, but freezing obviously, so therefore I need to use threading for long running process. So far I have changed my code to be like this, but when I click on the button which suppose to check the hosts, happens nothing. :( I dont really get it how to connect the textEdit from MainWindow class to Worker class. As how it is now, it seems like Worker class has no clue what is really self.ui.textEdit.
class Worker(QObject):
finished = Signal()
def __init__(self):
super(Worker, self).__init__()
def run(self):
hostname = self.ui.textEdit.toPlainText()
output_text = collect_host_status(hostname)
for i in output_text:
if "not found" in i:
w = i.replace(" not found", "")
self.ui.textEdit_3.append(w)
else:
self.ui.textEdit_2.append(i)
self.finished.emit()
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.exitbutton.clicked.connect(self.close)
self.ui.actionExit_2.triggered.connect(self.close)
self.ui.actionOpen_2.triggered.connect(self.openfiles)
self.ui.pushButton.clicked.connect(self.ui.textEdit.clear)
self.ui.pushButton.clicked.connect(self.ui.textEdit_2.clear)
self.ui.pushButton.clicked.connect(self.ui.textEdit_3.clear)
self.ui.pushButton_2.clicked.connect(self.ui.textEdit_2.clear)
self.ui.pushButton_2.clicked.connect(self.ui.textEdit_3.clear)
self.connect(self.ui.pushButton_2, SIGNAL("clicked()",), self.buttonclicked)
def buttonclicked(self):
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
Qt, as with most UI frameworks, does not allow any kind of access to objects from outside its main thread, which means that external threads cannot create widgets, reading properties (like toPlainText()) is unreliable and writing (such as using append()) might even lead to crash.
Even assuming that that was possible, as you pointed out the worker has no clue about self.ui.textEdit and other objects, and that's pretty obvious: self.ui is an attribute created on the main window instance, the thread has no ui object. I suggest you to do some research about how classes and instances work, and how their attributes are accessible.
The only safe and correct way to do so is to use custom signals that are emitted from the thread and connected to the slots (functions) that will actually manipulate the UI.
In the following code I made some adjustments to make it working:
the worker thread directly subclasses from QThread;
only one worker thread is created, and it uses a Queue to get requests from the main thread;
two specialized signals are used to notify whether the request is valid or not, and directly connected to the append() function of the QTextEdits;
I removed the finished signal, which is unnecessary since the thread is going to be reused (and, in any case, QThread already provides such a signal);
changed the "old style" self.connect as it's considered obsolete and will not be supported in newer versions of Qt;
class Worker(QThread):
found = Signal(str)
notFound = Signal(str)
def __init__(self):
QThread.__init__(self)
self.queue = Queue()
def run(self):
while True:
hostname = self.queue.get()
output_text = collect_host_status(hostname)
for i in output_text:
if "not found" in i:
self.notFound.emit(i.replace(" not found", ""))
else:
self.found.emit(i)
def lookUp(self, hostname):
self.queue.put(hostname)
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.ui.pushButton_2.clicked.connect(self.buttonclicked)
self.thread = Worker()
self.thread.found.connect(self.ui.textEdit_2.append)
self.thread.notFound.connect(self.ui.textEdit_3.append)
self.thread.start()
def buttonclicked(self):
if self.ui.textEdit.toPlainText():
self.thread.lookUp(self.ui.textEdit.toPlainText())
I want to make a ansible callback plugin, that hides sensitive data in the ansible output. There is a suggestion on how to do it here:
from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
import os, collections
class CallbackModule(CallbackModule_default):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'protect_data'
def __init__(self, display=None):
super(CallbackModule, self).__init__(display)
def hide_password(self, result):
ret = {}
for key, value in result.iteritems():
if isinstance(value, collections.Mapping):
ret[key] = self.hide_password(value)
else:
if "password" in key:
ret[key] = "********"
else:
ret[key] = value
return ret
def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False):
return super(CallbackModule, self)._dump_results(self.hide_password(result), indent, sort_keys, keep_invocation)
Now this example hides "password". I now want to make the word, that are hidden configurable at runtime of the playbook.
Can I somehow give the plugin a state (a list of words to hide) and modify it at the runtime of the playbook?
You can set self.words_list inside __init__ to some default value.
Then inside ...on_task_start and ...on_handler_task_start check for some specific variable and modify your self.words_list accordingly.
You can take a look at how persistent properties to collect statistics are used in profile_tasks callback plugin.
I'm trying to make a function that runs a ttk progressbar until a file is created. It seems that Widget.after is causing the APPCRASH but I don't know why. Please help!
def FilePgBar(title, file):
if root:
root.withdraw()
boxRoot = Toplevel(master=root)
boxRoot.withdraw()
else:
boxRoot = Tk()
boxRoot.withdraw()
boxRoot.protocol('WM_DELETE_WINDOW', denyWindowManagerClose )
boxRoot.title(title)
boxRoot.iconname('Dialog')
boxRoot.geometry(rootWindowPosition)
boxRoot.minsize(400, 100)
pgBar = ttk.Progressbar(boxRoot, orient=HORIZONTAL, length=300, mode='indeterminate')
pgBar.grid(row=1, column=0)
pgBar.pack()
pgBar.start()
def checkfile():
if os.path.exists(file):
pgBar.stop()
pgBar.destroy()
boxRoot.deiconify()
boxRoot.mainloop()
boxRoot.destroy()
if root: root.deiconify()
else:
boxRoot.after(100, checkfile)
checkfile()
I want to call this function from others scripts, so I'm not sure about using a class
EDIT: I edited the code. I no longer get an APPCRASH, but nothing happens when I run the program.
Python evaluates the arguments before passing them to a function. So when it encounters
boxRoot.after(100, checkfile(file))
it evaluates checkfile(file) -- calling checkfile -- and replaces checkfile(file) with the value returned by the function before calling boxRoot.after.
Since checkfile(file) has no return statement, None is returned by default. Thus
boxRoot.after(100, None)
gets called. This raises an error since the second argument to boxRoot.after should be a callable.
Instead, pass the function object checkfile itself:
def checkfile():
if os.path.exists(file):
pgBar.stop()
pgBar.destroy()
boxRoot.destroy()
if root: root.deiconify()
else:
# You need to update the progress bar
boxRoot.after(100, checkfile)
This allows the boxRoot.after function to call the function from within boxRoot.after instead of before after is called.
You might do something like this:
import os
import Tkinter as tk
import ttk
class App(object):
def __init__(self, master, *args, **kwargs):
self.master = master
self.button = tk.Button(master, text='Stop', command=self.stop)
self.button.pack()
self.progress = ttk.Progressbar(master, orient="horizontal",
length=200, mode="determinate")
self.progress.pack()
self.progress["value"] = 0
self.progress["maximum"] = 100
self.filename = '/tmp/out'
if os.path.exists(self.filename):
os.unlink(self.filename)
self.checkfile()
def checkfile(self):
self.progress["value"] += 1
if (not os.path.exists(self.filename)
and self.progress["value"] < self.progress["maximum"]):
self.master.after(100, self.checkfile)
def stop(self):
with open(self.filename, 'w') as f: pass
root = tk.Tk()
app = App(root)
root.mainloop()
The SWT-Gui looks very nice.
Is there an easy way to use it in Jython ?
Given that you can use all Java classes from within Jython, it is also possible to use SWT.
For the example, adapted from an SWT snippet, make sure you have the SWT jar on your CLASSPATH:
import org.eclipse.swt as swt
import org.eclipse.swt.widgets as widgets
import org.eclipse.swt.layout as layout
result = None
display = widgets.Display()
shell = widgets.Shell(display)
shell.pack()
shell.open()
dialog = widgets.Shell(shell, swt.SWT.DIALOG_TRIM | swt.SWT.APPLICATION_MODAL)
dialog.setLayout(layout.RowLayout())
ok = widgets.Button(dialog, swt.SWT.PUSH)
ok.setText ("OK")
cancel = widgets.Button(dialog, swt.SWT.PUSH);
cancel.setText("Cancel");
class MyListener(widgets.Listener):
def handleEvent(self, event):
global result
result = event.widget == ok
dialog.close()
listener = MyListener()
ok.addListener(swt.SWT.Selection, listener)
cancel.addListener(swt.SWT.Selection, listener)
dialog.pack()
dialog.open()
while not dialog.isDisposed():
if not display.readAndDispatch():
display.sleep ()
print "Result:", result
display.dispose()
Jython has a few other niceties that makes the code cleaner.
Jython automagically translates getters & setters into public properties so that
ok.setText ("OK")
becomes simply
ok.text = 'OK'
You can then supply them as named arguments to the constructor. Jython also handles creating listener objects for your event handlers:
def handleEvent(self, event):
global result
result = event.widget == ok
dialog.close()
ok = widgets.Button(dialog, swt.SWT.PUSH
text='OK',
widgetSelected=handleEvent)
cancel = widgets.Button(dialog, swt.SWT.PUSH
text='Cancel',
widgetSelected=handleEvent)
Actually, there is no need for a special module. This talk by Sean McGrath contains a simple example of a Jython/SWT GUI.
Slide 11 of the talk begins with:
"""
Simple SWT Example
Sean McGrath
"""
from org.eclipse.swt.events import *
from org.eclipse.swt.graphics import *
from org.eclipse.swt.layout import *
from org.eclipse.swt.widgets import *
from org.eclipse.swt.layout.GridData import *
from org.eclipse.swt import *
It shows that SWT is directly usable from Jython.
The full example is right there at Sean's site.