The case study doesn't seem too hard to explain, but I guess TextCtrl in wxPython aren't often used in this way. So here it is: I have a simple window with two TextCtrls. One is an input widget (the user is supposed to enter commands there), the second is an output widget (the system displays the result of the commands). The output field is a read-only TextCtrl, only the system can write in it.
So far so good. Now, I would like to intercept events in the output widget: If users type in this output field (a read-only widget), they should be redirected to the input field and the text they have begun typing should appear there. The first part isn't complicated: I intercept the EVT_KEY_DOWN on the output widget and can do something like self.input.SetFocus(). However, the key that has been pressed by the user is lost. If he/she began to type something, she has to start over again. This is supposed to be a shortcut feature (no matter in what field the user type, it should be written in the input widget).
A short note on why I do this, since it can still quite stupid: Sighted users don't often fool around with read-only widgets; they see them and leave them alone. This application is mostly designed for users with screen readers, who have to move around the output field. The cursor is therefore often there, and a key press doesn't have any effect (since it's a read-only widget). It would be great if, on typing in the output widget, the user was redirected to the input field, with the text he was typing already in this widget.
import wx
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.panel = MyPanel(self)
self.Show()
self.Maximize()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
# Input field
self.input = wx.TextCtrl(self, -1, "", size=(125, -5),
style=wx.TE_PROCESS_ENTER)
# Ouput
self.output = wx.TextCtrl(self, -1, "",
size=(600, 400), style=wx.TE_MULTILINE|wx.TE_READONLY)
# Add the output fields in the sizer
sizer.Add(self.input)
sizer.Add(self.output, proportion=8)
# Event handler
self.output.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
def OnKeyDown(self, e):
"""A key is pressed in the output widget."""
modifiers = e.GetModifiers()
key = e.GetUnicodeKey()
if not key:
key = e.GetKeyCode()
print "From there, we should redirect to the input"
self.input.SetFocus()
# Let's run that
app = wx.App()
MyFrame(None)
app.MainLoop()
Give self.input.EmulateKeyPress(e) a try. If you're on Windows it should work fine. On other platforms it is not perfect, but basically works there too.
Other options would be to use wx.UiActionSimulator, or simply to append the new character to the input textctrl in your code.
Related
I'm taking the first steps to move from .NET to Python but I'm already having a few headaches regarding the GUI design.
For some reason, passing the size attribute to a wx.Button seems to be kind of ignored. And I say "kind of" because the actual space seems to change but the actual button keeps occupying the same space:
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
def InitUI(self):
self.SetSize((800, 600))
self.SetTitle('Main Menu')
self.Centre()
self.Show(True)
''' Fill the form '''
self.lblUsername = wx.StaticText(self, size=(80, -1), pos=(20,20), label="Username:" )
self.txtUsername = wx.TextCtrl(self, size=(140, -1), pos=(100,20), style=wx.TE_PROCESS_ENTER)
self.lblPassword = wx.StaticText(self, size=(80, -1), pos=(20,50), label="Password:" )
self.txtPassword = wx.TextCtrl(self, size=(140, -1), pos=(100,50), style=wx.TE_PROCESS_ENTER)
self.btnOK = wx.Button( self, label="OK", pos=(260, 16), size=(50,50))
self.btnOK.Bind(wx.EVT_BUTTON, self.onClickOK)
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Ready')
def onClickOK(self, e):
print "Button triggered"
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
No matter what size I set, the Button won't stretch (it will be centered as if all the space was actually being used, but will still be small).
Can anyone spot what am I doing wrong?
This is a limit imposed by OSX. The way the native button widget is drawn only allows it to be stretched horizontally, and the vertical size is fixed. Or rather, as you've discovered, the widget itself can be larger than normal vertically, but it will only draw itself at a fixed height within that space. It seems less neccessary with modern versions of OSX, but if you look at buttons in OSX from a few years ago you can probably see why this is so. The esthetic graphical effect of the "tic-tack" or "capsule" buttons would be totally ruined if they were a non-standard vertical size, causing the images used to draw the buttons to be stretched. wxWidgets follows the native plaform look and feel standards where possible, in this case it happens that Apple's standard is imposed upon us and wx can't offer the same level of flexibility that it usually does.
You do have some options however if you really want taller than normal buttons. The native widgets have a few different standard sizes, which you can select using the SetWindowVariant method, although I don't think the variants would get as tall as you want. Or you could use a generic button widget instead of a native one, such as wx.lib.buttons.ThemedGenButton.
Same problem in my little Software EventSoundControl.
Just a workaround: Use a multiline label and sizes of wxButton will work as desired!
If you want the button to stretch when you resize the frame, then you cannot use static sizes and positioning. You will need to put your widgets in a sizer. Then the sizer will manage the position / size of the widget(s) as you change the size of the frame. There are many examples on the wxPython wiki that demonstrate how to use sizers. You might also find the following tutorial helpful:
http://zetcode.com/wxpython/layout/
I'm trying to fire a line of code when the user clicks on a textCtrl. The end goal is to highlight the contents of the box when it is clicked on. I am aware that this is possible with wx.EVT_SET_FOCUS, but it's either buggy or I'm implementing it wrong. Here's my code:
self.m_textCtrl1 = wx.TextCtrl(self.m_panel2, wx.ID_ANY, wx.EmptyString,
wx.DefaultPosition, wx.Size(100,-1), wx.TE_LEFT)
self.m_textCtrl1.SetMaxLength(8)
self.m_textCtrl1.SetMinSize(wx.Size(100,-1))
self.m_textCtrl1.SetMaxSize(wx.Size(100,-1))
self.m_textCtrl1.Bind(wx.EVT_SET_FOCUS, self.highlightText, self.m_textCtrl1)
This code is able to successfully fire highlightText when I want it to, but for some reason the cursor is removed from the textCtrl, leaving the user unable to pick his spot, highlight, or backspace. Any suggestions would be appreciated. As a side note, is there a way to do this in wxFormBuilder? I built my application using it but was unable to add a focus event. It seems the only focus events it offers are for the entire window.
EDIT 9/19/14:
Mike, here's my automatically generated wxFormBuilder code, in gui.py:
class OrderNumEntry ( wx.Frame ):
def __init__( self, parent ):
# there's a lot more stuff here, but it's irrelevant
self.m_textCtrl1.Bind( wx.EVT_SET_FOCUS, self.highlightText )
def __del__( self ):
pass
# Virtual event handlers, overide them in your derived class
def highlightText( self, event ):
event.Skip()
... and here's the event handler that I wrote
import wx, gui
class OrderFrame(gui.OrderNumEntry):
def __init__(self, parent):
gui.OrderNumEntry.__init__(self, parent)
# again, a lot more irrelevant stuff here
def highlightText(self, event):
print 'test'
The event works fine (as in test is printed when I want it), but I'm not able to highlight text and I can't see my cursor.
You don't show your event handler, but my guess would be that you need to call event.Skip() at the end of it. I want to also note that you binding the event incorrectly. It should be:
self.m_textCtrl1.Bind(wx.EVT_SET_FOCUS, self.highlightText)
or
self.Bind(wx.EVT_SET_FOCUS, self.highlightText, self.m_textCtrl1)
See the wxPython wiki for a complete explanation:
http://wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind
I have a simple wxPython application. It basically has a image and a text-entry field (a wx.TextCtrl).
I want to be able to immediately be able to start entering text as soon as the window opens. Right now, I have to first click in the text control, and then I can start entering text.
Here is a minimal app that demonstrates the issue:
import wx
class MyFrame(wx.Frame):
""" We simply derive a new class of Frame. """
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(200, 100))
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.control = wx.TextCtrl(self, style=wx.TE_MULTILINE)
mainSizer.Add(self.control, 1, wx.EXPAND)
self.SetSizer(mainSizer)
self.Show(True)
app = wx.App(False)
frame = MyFrame(None, 'Small editor')
app.MainLoop()
I've poked around with wx.SetInsertionPoint, but that does not seem to have any effect.
Ah, derp. I had to look further up the inheritance chain.
You can simply call SetFocus() on the control (in this case, self.control.SetFocus()).
SetFocus() is a member function of wxWindow. I was only looking at the docs for wxTextCtrl.
Of course, I didn't think to look up the inheritance chain until I had already asked the question.
I'm leaving this here, as this is a pretty hard to google issue. Hopefully this will help someone else.
I am developing a GUI with wxPython. I draw a square which represents a CD object, inside another square (also with wxPanel class), which represents CD Container Object.
I want to have "delete this CD" in the right click menu of CDWindow, which will remove the CDwindow.
Basically, my code looks like this (for simplicity, I keep the main parts):
class CDContainerWindow(wx.Panel):
def __init__(self):
wx.Panel.__init__(self, parent, id, pos, size)
cd_win=CDWindow()
class CDWindow(wx.Panel):
def __init__(self):
wx.Panel.__init__(self, parent, id, pos, size)
self.Bind(wx.EVT_MENU, self.OnDeleteCD, item_CD)
def OnDeleteCD(self, event):
self.destroy()
There is an error message "Segmentation fault"
What is wrong with my way? How can I delete this CD window from the CDContainer Window?
Maybe there's a sizer still using the destroyed panel? You should remove the panel from the sizer first.
import wx
class TestDraw(wx.Panel):
def __init__(self,parent=None,id=-1):
wx.Panel.__init__(self,parent,id)
self.SetBackgroundColour("#FFFFFF")
self.Bind(wx.EVT_PAINT,self.onPaint)
def onPaint(self, event):
event.Skip()
dc=wx.PaintDC(self)
dc.BeginDrawing()
width=dc.GetSize()[0]
height=dc.GetSize()[1]
if height<width:
self.drawTestRects(dc)
else:
dc.Clear()
dc.EndDrawing()
def drawTestRects(self,dc):
dc.SetBrush(wx.Brush("#000000",style=wx.SOLID))
dc.DrawRectangle(50,50,50,50)
dc.DrawRectangle(100,100,100,100)
class TestFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(640,480))
self.mainPanel=TestDraw(self,-1)
self.Show(True)
app = wx.App(False)
frame = TestFrame(None,"Test App")
app.MainLoop()
This code should draw the test rectangles only when the height is less than the width, and otherwise the window should remain clear. However, if you mess with resizing the window, the panel isn't actually redrawn unless it is moved off the window. What am I doing wrong?
You can bind a method to handle wx.EVT_SIZE or the panel and invalidate it there. Alternatively simply use the wx.FULL_REPAINT_ON_RESIZE for the panel.
The documentation for a SizeEvent claims that there may be some complications when drawing depends on the dimensions of the window. I do not know exactly what is going on behind the scenes. I followed the suggestion on the link and added the call self.Refresh() to the top of onPaint() and this seems to give the desired behavior. See mghie's answer for a more efficient example of working code.