I'm assuming this is possible with a multiline text box, but not sure how to do it. What I'm looking to do is make a log box in my wxPython program, where I can write messages to it when certain actions happen. Also, i need to write the messages not only when an event happens, but certain times in the code. How would i get it to redraw the window so the messages appear at that instant?
I wrote an article on this sort of thing a couple years ago:
http://www.blog.pythonlibrary.org/2009/01/01/wxpython-redirecting-stdout-stderr/
If you would like just a log dialog in wxpython, use wx.LogWindow:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent=None):
wx.Frame.__init__(self, parent, wx.NewId(), 'Logging')
self.log_window = wx.LogWindow(self, 'Log Window', bShow=True)
box_sizer = wx.BoxSizer(orient=wx.VERTICAL)
show_log_button = wx.Button(self, wx.NewId(), 'Show Log')
show_log_button.Bind(wx.EVT_BUTTON, self._show_log)
log_message_button = wx.Button(self, wx.NewId(), 'Log Message')
log_message_button.Bind(wx.EVT_BUTTON, self._log_message)
box_sizer.AddMany((show_log_button, log_message_button))
self.SetSizer(box_sizer)
self.Fit()
self.Bind(wx.EVT_CLOSE, self._on_close)
def _show_log(self, event):
self.log_window.Show()
def _log_message(self, event):
wx.LogError('New error message')
def _on_close(self, event):
self.log_window.this.disown()
wx.Log.SetActiveTarget(None)
event.Skip()
if __name__ == '__main__':
app = wx.PySimpleApp()
dlg = MainWindow()
dlg.Show()
app.MainLoop()
Where bShow in wx.LogWindow is if it's initially shown or not. This will log nicely all your wx.LogX messages that you can trigger, and it still passes it on to any other handlers.
Another method you could use would be to log with python and then, upon opening a frame/window with a text control in it, use LoadFile to open the log file:
import logging
LOG_FILENAME = 'example.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG)
logging.debug('This message should go to the log file')
Then, when creating a wx.TextCtrl somewhere:
log_control = wx.TextCtrl(self, wx.NewId(), style=wx.TE_MULTILINE|wx.TE_READONLY)
log_control.LoadFile('example.log')
EDIT:
This now works with the _on_close event! Thanks Fenikso
Related
I'm running wxPython 4.0.1 msw (phoenix) with Python 3.6.5 on a Windows7 machine, as well as wxPython 2.9.4 with Python 2.7.
I'm observing an issue with a modal dialog, which doesn't block the access to its parent window behind. This only occurs if I run a progress dialog followed by a modal dialog. This behavior is somehow related to custom dialogs. Integrated dialogs like wx.MessageDialog doesn't seem to have this issue.
To isolate the issue, I've written an example. The first two buttons open either the progress or the modal dialog and work properly. The third button opens both dialogs in sequence. In this case the modal functionality of the custom dialog doesn't work and I'm able to access and close the mainframe. Which causes multiple issues.
Dialog is not modal, the main window can be accessed and closed
import wx
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, title='SomeDialog',
style=wx.DEFAULT_DIALOG_STYLE)
self.button_ok = wx.Button(self, wx.ID_OK, size=(120,-1))
hsizer = wx.BoxSizer(wx.HORIZONTAL)
hsizer.Add(self.button_ok, 0, wx.ALL|wx.ALIGN_CENTER, 10)
self.SetSizer(hsizer)
self.SetSize(self.BestSize)
self.Layout()
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, size=(400, 400))
self.button_progress = wx.Button(self, -1, 'Show Progress')
self.button_modal = wx.Button(self, -1, 'Show Modal')
self.button_both = wx.Button(self, -1, 'Show Both')
self.Bind(wx.EVT_BUTTON, self.on_button, self.button_progress)
self.Bind(wx.EVT_BUTTON, self.on_button, self.button_modal)
self.Bind(wx.EVT_BUTTON, self.on_button, self.button_both)
sizer = wx.BoxSizer()
sizer.Add(self.button_progress)
sizer.Add(self.button_modal)
sizer.Add(self.button_both)
self.SetSizer(sizer)
def on_button(self, event):
if event.EventObject is self.button_progress:
self._show_progress_dialog()
elif event.EventObject is self.button_modal:
self._show_modal_dialog()
else:
self._show_progress_dialog()
self._show_modal_dialog()
def _show_progress_dialog(self):
max = 10
dlg = wx.ProgressDialog('Progress dialog example', 'Some message',
maximum=max, parent=self,
style=wx.PD_APP_MODAL|wx.PD_AUTO_HIDE)
keepGoing = True
count = 0
while keepGoing and count < max:
count += 1
wx.MilliSleep(250)
wx.Yield()
(keepGoing, skip) = dlg.Update(count)
dlg.Destroy()
def _show_modal_dialog(self):
with SomeDialog(self) as dlg:
dlg.CenterOnParent()
dlg.ShowModal()
if __name__ == '__main__':
app = wx.App()
frame = TestFrame()
frame.Show()
app.MainLoop()
In case this is an issue in the wxpython framework and not an issue with my implementation, it would be great if someone could provide me a workaround to show such dialogs in sequence.
This looks like a bug to me. I'm not sure why its happening, but one workaround would be to use wx.CallLater
changing _show_modal_dialog to:
def _show_modal_dialog(self):
def _make_dialog():
with SomeDialog(self) as dlg:
dlg.CenterOnParent()
dlg.ShowModal()
wx.CallLater(50, _make_dialog) # 50 mils is arbitrary
Seems to resolve the issue of the dialog not acting modalish. The problem with this workaround is that it will be non-blocking, meaning that any code that has to wait for the dialog to return has to be moved into the dialog class or passed to the dialog as a callback.
In the meantime I found a workaround myself which I like to share.
I added a handler catching the windows close event.
class TestFrame(wx.Frame):
def __init__(self):
#...
self.Bind(wx.EVT_CLOSE, self.on_close)
This event function checks if some child dialog is open and modal and performs a veto.
def on_close(self, event):
# In case any modal dialog is open, prevent the frame from closing.
for children in (c for c in self.Children if isinstance(c, wx.Dialog)):
if children.IsModal():
event.Veto()
return
event.Skip()
This is also only a workaround, but I seems to work for my use cases.
I have a wxpython desktop application and I am using python 2.7 and wxpython 2.8.
I know how to add an accelerator table to a menuitem but I would like to fire an event when a user press a certain combination of keys without having a menuitem.
The user could have the focus on any field in my UI but when he press (for instance) CTRL-L an event should be fired. How to do this ?
Thanks for any help
You always need to bind your accelerator table items to wx.EVT_MENU, but wxPython doesn't require that you use a menu item object. Here's a simple example:
import wx
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial", size=(500,500))
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
randomId = wx.NewId()
self.Bind(wx.EVT_MENU, self.onKeyCombo, id=randomId)
accel_tbl = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('Q'), randomId )])
self.SetAcceleratorTable(accel_tbl)
text = wx.TextCtrl(panel)
text.SetFocus()
#----------------------------------------------------------------------
def onKeyCombo(self, event):
""""""
print "You pressed CTRL+Q!"
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
In this example, we just create a random id, bind that id to an event handler and then create an accelerator that will fire that handler, which in this case is CTRL+Q. To make things more interesting, I added a text control widget and set the focus to that. Then if you press CTRL+Q, you should see the event handler fire and some text appear in your console window.
You can read more about accelerators here:
http://www.blog.pythonlibrary.org/2010/12/02/wxpython-keyboard-shortcuts-accelerators/
So I am a complete beginner at python and usually code in C/C++ or Java. In the process of developing a GUI my events keep getting called at the start of the program running instead of when I click the button. (i know my indents are wrong because I needed to put it into a code block). How do I make my events only get called when I left click the button?
def __init__(self, parent, title):
super(QuadDash, self).__init__(parent, title=title, size=(1024, 780))
self.SetBackgroundColour("white")
pnl = wx.Panel(self)
cbtn = wx.Button(pnl, label='Start Quad', pos=(20,30))
self.Bind(wx.EVT_LEFT_DOWN, self.start_quad_event(), cbtn)
self.Show(True)
def start_quad_event(self):
dlg = wx.MessageDialog(self, "Test", "ABC", wx.YES_NO | wx.ICON_QUESTION)
dlg.ShowModal()
if __name__ == '__main__':
app = wx.App()
qd = QuadDash(None, title='QuadCopter Dashboard')
app.MainLoop()
The offending line is:
self.Bind(wx.EVT_LEFT_DOWN, self.start_quad_event(), cbtn)
You actually call start_quad_event() and pass the result to bind().
The correct line is:
self.Bind(wx.EVT_LEFT_DOWN, self.start_quad_event, cbtn)
Note: No parentheses. Here you actually pass the function to bind().
I have been looking around the web since early morning and I can't seem to figure out how to get wxPython to show a dialogue box on my main frame.
import wx
class MainWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Window',size=(400,300))
panel = wx.Panel(self)
test = wx.TextEntryDialog(panel, 'Enter your word:',"New word","",
style=wx.OK|wx.CANCEL|wx.CENTRE,pos=(100,200))
def main():
pass
if __name__ == '__main__':
app = wx.App()
frame=MainWindow(parent=None,id=1)
frame.Show()
app.MainLoop()
It just opens a window without a text dialogue.
Use:
Dlg = wx.TextEntryDialog(panel, 'Enter your word:',"New word","",
style=wx.OK|wx.CANCEL|wx.CENTRE,pos=(100,200))
if Dlg.ShowModal() == wx.OK:
test = Dlg.GetValue()
del Dlg
As wx.TextEntryDialog is a dialogue class not one of the convenience dialogue functions you need to show it and get the value rather than just getting a reply.
I am building a Python program that searches things on a remote website.
Sometimes the operation takes many seconds and I believe that the user will not notice the status bar message "Search Operation in progress".
Therefore, I would like to change the mouse cursor to highlight that the program is still waiting for a result.
This is the method I am using:
def OnButtonSearchClick( self, event ):
"""
If there is text in the search text, launch a SearchOperation.
"""
searched_value = self.m_search_text.GetValue()
if not searched_value:
return
# clean eventual previous results
self.EnableButtons(False)
self.CleanSearchResults()
operations.SearchOperation(self.m_frame, searched_value)
I tried two different approaches, both before the last line:
wx.BeginBusyCursor()
self.m_frame.SetCursor(wx.StockCursor(wx.CURSOR_WAIT))
None of them are working.
I am using KDE under GNU/Linux. This does not work under Gnome, too
Any hints? Thank you
I asked Robin Dunn, the maker of wxPython about this, and it looks like this should work, but doesn't. However, if you call the panel's SetCursor(), it DOES work or so I'm told. Here's an example you can try:
import wx
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a self.panel so it looks the correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
btn = wx.Button(self.panel, label="Change Cursor")
btn.Bind(wx.EVT_BUTTON, self.changeCursor)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(btn)
self.panel.SetSizer(sizer)
#----------------------------------------------------------------------
def changeCursor(self, event):
""""""
myCursor= wx.StockCursor(wx.CURSOR_WAIT)
self.panel.SetCursor(myCursor)
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()