WxPython - How to hide the X and expand button on window - python

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.

Related

Why does the caret disappear from a TextCtrl widget after a focus event?

I'm writing a calculator application on MS Windows 8.1 using wxPython-Phoenix in which I'd like the calculation to be performed as soon as the user enters a value into one of the parameter fields. To achieve that I'm using wx.EVT_KILL_FOCUS generated by any parameter field to generate a command event which triggers the calculation method. Everything works fine but the appearance of the caret in the parameter fields (implemented by TextCtrl widgets).
After setting the focus either by the Tab key or the mouse on a certain field and moving it (again by the Tab key or the mouse) to a different field - the caret is gone from the TextCtrl widget never to return! when you call the GetCaret() method on that TextCtrl widget the return value is None.
See attached example. The widget still accepts input and displays it but without a caret.
How can I restore the caret in the right position? or not lose it in the first place?
I've tried setting a new caret in the TextCtrl but it does not follow the text input. Since the application is ment to be intensely interactive, I want the triggering event to be the lost focus and not a button (to minimize the number of clicks)
import wx
class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super(MyFrame, self).__init__(*args, **kwargs)
self.InitUI()
def InitUI(self):
# setting up the Input panel
panel = wx.Panel(self)
self.lbl1 = wx.StaticText(panel,wx.ID_ANY,'Some Text:')
self.txt1 = wx.TextCtrl(panel,wx.ID_ANY,style=wx.TE_RIGHT)
self.lbl2 = wx.StaticText(panel,wx.ID_ANY,'Some Other Text:')
self.txt2 = wx.TextCtrl(panel,wx.ID_ANY,style=wx.TE_RIGHT)
infgsz = wx.FlexGridSizer(2,2,15,15)
infgsz.AddMany([(self.lbl1,0,wx.ALIGN_LEFT),\
(self.txt1,0,wx.ALIGN_LEFT),\
(self.lbl2,0,wx.ALIGN_LEFT),\
(self.txt2,0,wx.ALIGN_LEFT)])
self.txt1.Bind(wx.EVT_KILL_FOCUS,self.OnInput)
self.txt2.Bind(wx.EVT_KILL_FOCUS,self.OnInput)
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(infgsz, flag= wx.EXPAND | wx.ALL, border=15)
panel.SetSizer(box)
self.SetSize((280, 140))
self.SetTitle('TextCtrl Demo')
self.Centre()
def OnInput(self, e):
if e.GetId() == self.txt1.GetId():
self.lbl2.SetForegroundColour(wx.ColourDatabase().Find('RED'))
self.lbl1.SetForegroundColour(wx.ColourDatabase().Find('BLACK'))
else:
self.lbl1.SetForegroundColour(wx.ColourDatabase().Find('BLUE'))
self.lbl2.SetForegroundColour(wx.ColourDatabase().Find('BLACK'))
self.Refresh()
def main():
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
if __name__ == '__main__':
main()
The above application displays 2 lines of text. In each line there is a label (wx.StaticText widget) on the left and a TextCtrl widget on the right. when you enter text into the TextCtrl widget of either line and move the focus to the other TextCtrl widget the corresponding label foreground color (the text color) changes to RED or BLUE and the other label changes to BLACK. However, without any seen reason, the caret disappears from the top TextCtrl widget and never returns to it! (On MS Windows 8.1 at least).
The documentation explicitly tells you
The focus event handlers should almost invariably call wxEvent::Skip() on their event argument to allow the default handling to take place.
and goes on to explain that not doing it may result in various problems -- such as the one you're observing.
Add e.Skip() to your event handler to fix it.

wxpython communication between notebook and main frame

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/

Always open wxPython GUI with scroll bar selected

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)

TextCtrl scrollbar unusable until window is resized

Solved:
Thanks to Aya's answer below I now know that the issue was caused by self.panel = wx.Panel(self, -1) on line 18. I created a panel and didn't attach anything to it. The original issue description is still below for reference.
My Google-fu has failed me. I'm building the text editor that you can find here, written in Python with wxPython:
https://github.com/joshsaintjacque/py-ed/blob/master/pyed.py
The issue that I'm running into is this: when I open a text file (the only functionality built in at this point) that's larger than the viewable area in the TextCtrl the scroll bar remains disabled until the window is re-sized, then it works fine.
I know that the act of re-sizing the window is running some command that I'm neglecting to include in my OpenFile function (or perhaps in init), but I can't figure out what.
Any thoughts anyone has that could lead me in the right direction would be greatly appreciated.
Thanks!
+1 for including a link to the full source code - makes it so much easier to test.
I couldn't reproduce the fault you describe on wxPython 2.8.12 on Win32, but upon running your code, I found a seemingly extraneous wx.Panel object being created on pyed.py line 18...
self.panel = wx.Panel(self, -1)
...which seems to be interfering with the correct operation of the program. After commenting out that line, it seems to work fine.
A couple of other things I noticed: line 56...
self.SetTitle("PyEd - Editing ... " + filename)
...should probably be put in the preceding if-block, otherwise you'll get an error if the user clicks "Cancel" on the wx.FileDialog, and on line 16...
wx.Frame.__init__(self, parent, id, 'PyEd', (-1, -1), wx.Size(640, 480))
...if you use keyword args rather than positional args...
wx.Frame.__init__(self, parent=parent, id=id, title='PyEd', size=wx.Size(640, 480))
...you needn't bother re-specifying the default value for the window position, which is also slightly safer, in case the wxPython developers decide to change the defaults in a future version.
You can also factor out constant values, and the optional creation of the wx.Size object to reduce that line to...
wx.Frame.__init__(self, parent=None, title='PyEd', size=(640, 480))
Finally, with regards to IDs: in most cases you'll probably find they're of little use. Where they come in handy is where you want many similar controls, and it makes more sense to have them handled by a single event handler function.
Consider this example...
def create_buttons(parent):
parent.button1 = wx.Button(label='Button 1')
parent.button2 = wx.Button(label='Button 2')
parent.button3 = wx.Button(label='Button 3')
parent.button1.Bind(wx.EVT_BUTTON, on_button_1)
parent.button2.Bind(wx.EVT_BUTTON, on_button_2)
parent.button3.Bind(wx.EVT_BUTTON, on_button_3)
def on_button_1(event):
print 'You clicked button 1'
def on_button_2(event):
print 'You clicked button 2'
def on_button_3(event):
print 'You clicked button 3'
...which is fine, but if you need, say, 100 buttons, you may prefer to implement it like this...
def create_buttons(parent):
parent.buttons = [wx.Button(id=i, label='Button %d' % i) for i in range(100)]
parent.Bind(wx.EVT_BUTTON, on_button)
def on_button(event):
button_id = event.GetId()
print 'You clicked button %d' % button_id
Oh, and be careful using id as a variable name, because it's also a Python built-in function name.
It looks as if you're not setting the min or max size hints for the window, nor are you calling Self.Fit() to fit the box sizer to the window size (or is it the other way round? I'm rusty on my wxPython...)
Right where you call self.SetSizer(sizer), you should be able to fix this by adding:
self.Fit()
self.SetSizeHintSz(minSize=wx.Size(640, 480))
You may be able to get around the separate call to self.Fit() by using self.SetSizerAndFit()
(edited for spelling.)

wxPython: how to make taskbar icon respond to left-click

Using wxPython, I created a taskbar icon and menu.
Everything works fine (in Windows at least) upon right-click of the icon: i.e., the menu is displayed, and automatically hidden when you click somewhere else, like on Windows' taskbar.
Now I do want to have the menu appear when the icon is left-clicked as well.
So I inserted a Bind() to a left-click in the Frame class wrapper, calling the CreatePopupMenu() of the taskbar icon:
import wx
class BibTaskBarIcon(wx.TaskBarIcon):
def __init__(self, frame):
wx.TaskBarIcon.__init__(self)
self.frame = frame
icon = wx.Icon('test_icon.ico', wx.BITMAP_TYPE_ICO)
self.SetIcon(icon, "title")
def CreatePopupMenu(self):
self.menu = wx.Menu()
self.menu.Append(wx.NewId(), "dummy menu ")
self.menu.Append(wx.NewId(), "dummy menu 2")
return self.menu
class TaskBarFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, style=wx.FRAME_NO_TASKBAR)
...
self.tbicon = BibTaskBarIcon(self)
wx.EVT_TASKBAR_LEFT_UP(self.tbicon, self.OnTaskBarLeftClick)
...
def OnTaskBarLeftClick(self, evt):
self.PopupMenu(self.tbicon.CreatePopupMenu())
...
def main(argv=None):
app = wx.App(False)
TaskBarFrame(None, "testing frame")
app.MainLoop()
This works fine, except that the menu does not disappear automatically when you click somewhere else on your screen. In fact, left-clicking multiple times on the icon creates multiple menus. The only way to hide the menu(s) is to click on one of its items (which you don't always want). I've looked at the available methods of TaskbarIcon, but I failed to be clear about which one to use to hide the menu (.Destroy() didn't work). Moreover, I don't know which event to bind it to (there is a EVT_SET_FOCUS, but I couldn't find any EVT_LOOSE_FOCUS or similar).
So, how to hide the menu upon losing focus?
EDIT: I've inserted a bit more code, to make it more clear
Ah, I've discovered what went wrong. In the statement
self.PopupMenu(self.tbicon.CreatePopupMenu())
I had bound the popup menu to the frame, instead of to the taskbar icon.
By changing it to:
self.tbicon.PopupMenu(self.tbicon.CreatePopupMenu())
all is working well now.
Thanks for all remarks
I think the problem here is that the PopupMenu is usually used in a program's context, not a little icon in the system tray. What that means is that in a normal frame, the popup menu would detect the click the you clicked off of it. Here, you are clicking outside of the wxPython program. Also, the PopupMenu is usually used with EVT_CONTEXT_MENU, not this taskbar event.
You can try wx.EVT_KILL_FOCUS and see if that works since it should theoretically fire when you click off the menu. Or you could ask on the official wxPython forum here: http://groups.google.com/group/wxpython-users/topics
Mike Driscoll
Blog: http://blog.pythonlibrary.org

Categories