I am trying to get a searchctrl in wxPython. However I am not getting exactly what I want.
I get this:
But I would like to get the SearchCtrl like:
I know that there isn't a big difference, it is just for visual reasons.
I declare my SearchCtrl as:
self.searchControl = wx.SearchCtrl(panel, -1, style=wx.TE_PROCESS_ENTER)
Does anybody know how can I declare it in order to get the SearchCtrl as I want?
There are three differences that I can see between what you have and what you want:
The cancel button (X in a circle)
The menu indicator (the arrow next to the magnifying glass).
The location of the word "Search"
None of these three differences are affected by the declaration.
To get the cancel button to show up, call:
self.searchControl.ShowCancelButton(True)
To get the menu indicator to show up, call:
self.SetMenu(menu)
To get the text to appear in the right place, prevent the sizer from vertically resizing your control.
For example:
#!/usr/bin/env python
import wx
app = wx.App(False)
frame = wx.Frame(None, wx.ID_ANY, "Hello World")
sizer = wx.BoxSizer(wx.HORIZONTAL)
menu = wx.Menu()
menu.Append(wx.ID_ABOUT, 'About')
search = wx.SearchCtrl(frame)
search.ShowCancelButton(True)
search.SetMenu(menu)
sizer.Add(search, 0)
frame.SetSizer(sizer)
frame.SetAutoLayout(1)
sizer.Fit(frame)
frame.Show()
app.MainLoop()
yields this:
Related
Im making a python program and in some functions it needs to hide the X and expand window buttons, how would i do it? Im using WxPython, how would I put this in?
The widgets in the window frame are defined as part of the window's style: CLOSE_BOX, MINIMIZE_BOX, and MAXIMIZE_BOX.
So, when you create the window, just leave those styles out.
If you're using a wx.Frame subclass, note that DEFAULT_FRAME_STYLE includes these values, so you will have to mask them out:
style = wx.DEFAULT_FRAME_STYLE & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX)
super().__init__(whatever, args, you, use, style=style)
If you want to change them after creation, you use SetWindowStyle:
style = self.GetWindowStyle()
self.SetWindowStyle(style & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX))
self.Refresh()
However, notice that the documentation of that function says:
Please note that some styles cannot be changed after the window creation and that Refresh() might need to be called after changing the others for the change to take place immediately.
And, from what I can tell, on Windows, if you create a window with a close box and then remove it later in this way, it doesn't actually go away. It does disable, which may be good enough. But if not, there's probably no way to do what you want without either reaching underneath wx to the native Windows API (which gets very tricky), or drawing the widgets on the frame manually (which gets even more tricky, especially if you care about looking right on different versions of Windows—not to mention porting to other platforms).
I wrote about Frame styles a while ago on my blog. To remove all the buttons, you could do this:
import wx
########################################################################
class NoSystemMenuFrame(wx.Frame):
"""
There is no system menu, which means the title bar is there, but
no buttons and no menu when clicking the top left hand corner
of the frame
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
no_sys_menu = wx.CAPTION
wx.Frame.__init__(self, None, title="No System Menu", style=no_sys_menu)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = NoSystemMenuFrame()
app.MainLoop()
I tried setting the style to wx.DEFAULT_FRAME_STYLE & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX) and to wx.DEFAULT_FRAME_STYLE^(wx.CLOSE_BOX|wx.MAXIMIZE_BOX), but both of those seem to only remove the Close box. For some reason, the Maximize button is still there on my Xubuntu machine.
I have a notebook whose parent is the main frame of the application. The main frame also has a panel showing a chart to the side of the notebook, a menu bar and a status bar.
The notebook has a couple of pages and each page has some nested panels.
I'd like the callbacks for buttons in those panels to be able to talk to the main frame.
At the moment, that means a ridiculous chain of 'parents'. For example, to get to status bar from a panel on a notebook page I would do:
stat = self.parent.parent.parent.status_bar
The first parent is the notebook page, the second parent is the notebook and finally the last parent is the main frame.
This leads to very obtuse code...
Naturally you can see how this might get worse if I wanted to talk between elements on the panel adjacent to the notebook or nest the notebook in it's own panel..
Any tips?
There is a simple way to get your Main Frame.
Since you can get your app instance anywhere in your code with "wx.GetApp()", then you can set your Mainframe into your app instance, it would be easy to fecth.
Please try following simple sample:
import wx
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
btn = wx.Button(wx.Panel(self), label = "test")
self.Bind(wx.EVT_BUTTON, self.onButton)
def onButton(self, evt):
print "onButton"
app = wx.GetApp()
print app.Myframe
app = wx.App()
frame = TestFrame()
frame.Center()
frame.Show()
app.Myframe = frame
app.MainLoop()
If you need to get access to the top frame, you should be able to use wx.GetTopLevelParent(). Personally, I think pubsub is probably the easiest way to call various classes in wxPython and it's pretty clean too. Plus if you need to call multiple frames or panels or whatever, you can have them all "subscribe" to the same message name and then publish a message for all of them to pick up.
Here's a tutorial for pubsub: http://www.blog.pythonlibrary.org/2013/09/05/wxpython-2-9-and-the-newer-pubsub-api-a-simple-tutorial/
Is it possible to create a message box (with wx.MessageDialog or anything else) without parent window ?
For example, I sometimes may want to display an error message before the GUI has really started. Then I would need to be able to display a message box before having a parent window :
With parent = None, this doesn't work :
wx.MessageDialog(parent, 'This is a message box.', 'Test', wx.OK | wx.ICON_INFORMATION).ShowModal()
How to display a message box without a parent window ?
Just saw this old question and wanted to answer it, better late than never:
By default, the main application window is used as the dialog parent even if no parent is specified explicitly, because this is what you want in 99% of cases -- modal dialogs without parent/owner window are quite unusual. If you really, really need to prevent the dialog from having a parent, you must use wx.DIALOG_NO_PARENT style explicitly.
It should work, try this:
import wx
app = wx.App()
wx.MessageDialog(None, 'This is a message box.', 'Test', wx.OK | wx.ICON_INFORMATION).ShowModal()
frame = wx.Frame(None)
frame.Center()
frame.Show()
app.MainLoop()
I know this is an old question, but I believe that parent=None does not work as one might expect. Consider the example above, but with the wx.Frame shown first, and the wx.MessageDialog after, like so:
import wx
app = wx.App()
frame = wx.Frame(None)
frame.Center()
frame.Show()
wx.MessageDialog(None, 'This is a message box.', 'Test', wx.OK | wx.ICON_INFORMATION).ShowModal()
app.MainLoop()
The result is a wx.Frame with the wx.MessageDialog shown on top of it (as expected), but the wx.Frame cannot be resized or draged around the screen (not expected). The wx.MessageDialog can be draged around the screen, but the wx.MessageDialog moves with it (not expected). The two frames clearly do not work independent of each other, and it seems the wx.MessageDialog is owned by the wx.Frame. I therefore think that wxPython applies some magic that is not obvious; at least I couldn't see anything in the docs.
Without parent frame.
There seems no need to use a frame for showing a 'stand alone' dialog. This works fine. (Tested only on Win10.)
Apparently wxpython takes the dialog, which is of course also just a window, as the 'frame' to display.
import wx
# -------------------------------------------------------
def wx_ask_question_windowed(question, caption):
app = wx.App()
dlg = wx.MessageDialog(None, question, caption, wx.YES_NO | wx.ICON_INFORMATION)
dlg.Center()
dlg_result = dlg.ShowModal()
result = dlg_result == wx.ID_YES
dlg.Destroy()
app.MainLoop()
app.Destroy()
return result
# ==============================================================
def main():
if wx_ask_question_windowed('Do you like this?', 'A windowed question'):
print('You like it')
else:
print("You don't like it")
# ==============================================================
if __name__ == '__main__':
main()
I have several GUIs which open progressively that are all experiencing the same issue. When I open them, the first object which is selected is a TextCtrl object. These GUIs are rather large and have scroll bars. Since a TextCtrl object is selected, scrolling with the mouse wheel does nothing and makes it appear as if the scroll bars are broken. To demonstrate this, I made the following code:
import wx
class Tester(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Window", size=(500, 500))
self.panel = wx.ScrolledWindow(self,wx.ID_ANY)
self.panel.SetScrollbars(30,30,600,400)
textBox = wx.TextCtrl(panel, -1, "", size=(200, 150), style=wx.TE_MULTILINE|wx.TE_LEFT)
textStuff = wx.StaticText(panel, -1, "A\nbunch\nof\nlines\nto\nmake\nthis\nlong\nenough\nto\nhave\nscroll\nbars\n\n\n\n\n\n\n\n\n\nIts lonely down here\n\n\n\n:(")
lonelyBtn = wx.Button(panel, -1, "So Lonely")
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add(textBox, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)
vbox.Add(textStuff, flag=wx.LEFT|wx.RIGHT|wx.TOP, border=10)
vbox.Add(lonelyBtn, flag=wx.LEFT|wx.RIGHT|wx.TOP, border=10)
panel.SetSizer(vbox)
panel.Layout()
app = wx.PySimpleApp()
Tester().Show()
app.MainLoop()
When you run this and try to scroll down, you'll notice you cannot scroll with the mouse wheel.
So far, this all makes sense. Here's where it gets a little weird. If the selected object is a Button, using the mouse wheel engages the scroll bars. You can test this by pressing the button, then using the mouse wheel. Also, clicking on the panel, or even the scroll bar itself, doesn't allow the mouse wheel to work.
What I'm looking for is a way to make sure that if there are scroll bars, they can be used with the mouse wheel upon displaying the GUI (they are selected by default). I can accept that the mouse wheel will not function once a user clicks into the text control.
Additionally, if you have an explanation for why the mouse wheel works for buttons and not text controls, I'd love to hear it
EDIT: I know I can add a listener (thanks to Mr. Joran Beasley), but this means that the scroll bars within a multi-line text control can never be used with the mouse wheel. The ideal solution (which I'm not sure is possible), is to have clicking on anywhere outside the text control (panel or scroll bar) allows the mouse wheel to scroll the panel. Is this possible?
Additionally, I've switched over to using ScrolledWindow instead of ScrolledPanel
EDIT 2: The fix was to use the following:
self.panel.Bind(wx.EVT_MOTION, self.onMouseMove)
def onMouseMove(self, event):
self.panel.SetFocusIgnoringChildren()
EDIT 3: The actual fix was to do something a little tricky. Using the code below I bound only multiline text controls to EVT_ENTER_WINDOW and EVT_LEAVE_WINDOW as well as binding every item (and the panel itself) to EVT_MOUSEWHEEL. Then a logical self.inMLTxtCtrl tracks if the mouse if over any of the multiline text controls
self.panel.Bind(wx.EVT_MOUSEWHEEL, self.onWheel)
for sizerItem in self.panel.GetSizer().GetChildren():
try:
if sizerItem.GetWindow().IsMultiLine():
sizerItem.GetWindow().Bind(wx.EVT_ENTER_WINDOW, self.onMouseEnter)
sizerItem.GetWindow().Bind(wx.EVT_LEAVE_WINDOW, self.onMouseLeave)
sizerItem.GetWindow().Bind(wx.EVT_MOUSEWHEEL, self.onWheel)
except:
sizerItem.GetWindow().Bind(wx.EVT_MOUSEWHEEL, self.onWheel)
Then a logical self.inMLTxtCtrl tracks if the mouse if over any of the multiline text controls as shown below.
def onMouseEnter(self, event):
print "entering"
self.inMLTxtCtrl = True
def onMouseLeave(self, event):
print "leaving"
self.inMLTxtCtrl = False
Finally, the onWheel() function uses this logical flag to determine where to scroll. If the mouse is in a multiline text control when the scroll wheel is turned, it attempts to scroll in that text control. Othewise, the SetFocusIgnoringChildren() function is called and the panel is scrolled. Since the panel and text control use different scrolling methods, a try...except is needed.
def onWheel(self, event):
if self.inMLTxtCtrl:
print "in", event.GetWheelRotation()
else:
print "out", event.GetWheelRotation()
self.panel.SetFocusIgnoringChildren()
try:
currScroll = self.panel.FindFocus().GetViewStart()
newScroll = (currScroll[0],currScroll[1]- event.GetWheelRotation()/60)
self.panel.FindFocus().Scroll(*newScroll)
except:
self.panel.FindFocus().ScrollLines(event.GetWheelRotation()/-60)
class Tester(wx.Frame):
def OnTxtScroll(self,e):
currScroll = self.panel.GetViewStart()
newScroll = (currScroll[0],currScroll[1]- e.GetWheelRotation()/120)
self.panel.Scroll(*newScroll)
def __init__(self):
....
#your code
....
self.panel = panel
textBox.Bind(wx.EVT_MOUSEWHEEL,self.OnTxtScroll)
after your clarification ... I think that this would work (its a bit hacky and doesnt do exactly what you describe ... but it might work)
def OnTxtScroll(self,e):
print dir(e)
target = e.GetEventObject()
p1 = target.GetScrollPos(wx.VERTICAL)
e.Skip()
self.Update()
def updateScroll(p1,target,scroll_amt):
p2 = target.GetScrollPos(wx.VERTICAL)
if p1 ==p2:#scroll did not effect target object so lets scroll our main panel
currScroll = self.panel.GetViewStart()
newScroll = (currScroll[0],currScroll[1]- scroll_amt)
self.panel.Scroll(*newScroll)
wx.CallAfter(updateScroll,p1,target,e.GetWheelRotation()/120)
I'm trying to send colored text to a TextCtrl widget, but don't know how
style = wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_READONLY|wx.TE_RICH2
self.status_area = wx.TextCtrl(self.panel, -1,
pos=(10, 270),style=style,
size=(380,150))
basically that snippet defines a status box in my window, and I want to write colored log messages to it. If I just do self.status_area.AppendText("blah") it will append text like I want, but it will always be black. I can't find the documentation on how to do this.
You need to call SetStyle to change the text behavior.
import wx
class F(wx.Frame):
def __init__(self, *args, **kw):
wx.Frame.__init__(self, None)
style = wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_READONLY|wx.TE_RICH2
self.status_area = wx.TextCtrl(self, -1,
pos=(10, 270),style=style,
size=(380,150))
self.status_area.AppendText("blahblahhblah")
fg = wx.Colour(200,80,100)
at = wx.TextAttr(fg)
self.status_area.SetStyle(3, 5, at)
app = wx.PySimpleApp()
f = F()
f.Show()
app.MainLoop()
documentation of wxwidgets has this to say (you can also look up wxPython docs, but it points to wxwidgets anyway):
either use SetDefaultStyle before you append text to your textctrl, or after inserting text use SetStyle.
According to docs, the first solution is more efficient (and sounds easier to me.)