I'm trying to create something like the categories panel in Wordpress, with wxPython.
What I'm trying to figure out, is how to add a widget when the user clicks a button (like "Add New Category")
Here is my code:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(300,200))
self.panel = wx.Panel(self, -1)
button = wx.Button(self.panel,-1,"Button")
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(button)
add_btn = wx.Button(self.panel,-1,"Add")
add_btn.Bind(wx.EVT_BUTTON, self.add)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(add_btn)
main_vbox = wx.BoxSizer(wx.VERTICAL)
main_vbox.Add(self.vbox)
main_vbox.Add(hbox)
self.panel.SetSizer(main_vbox)
self.Centre()
self.Show(True)
def add(self,event):
self.vbox.Add((wx.Button(self.panel,-1,"Button")))
if __name__ == "__main__":
app = wx.App()
MainWindow(None, -1, 'Add a Button')
app.MainLoop()
My problem is, the button gets added on top of the previous button. I'm rather mystified by this, because if I delete the event argument of the add() function, and then call it in the __init__ method, self.add(), it works fine. But that doesn't help me any because I need to add the widgets when the user clicks the button.
Any help is much appreciated.
Call self.panel.Layout() after adding the button. This function is called automatically when you resize a window with children (try it with your current code), but not when you add widgets to it.
Related
In Python 3.10.5 on Windows 11 employing the wxPython 4.2.0 package, I am using the wx.html2.WebView widget to display HTML content in a help dialog. Now, I want the dialog to close based on the Escape key press event. However, if I bind the wx.EVT_CHAR_HOOK event handlerr to the dialog object, the event does not get propagated and thus the handler is not executed when the keyboard focus is on the WebView widget, but if the focus is on other widget in the dialog, such as a close button I added there next to the WebView widget, the event is received properly. To better demonstrate it, here is the code I am using:
class HelpHTMLDialog(wx.Dialog):
def __init__(self, title, parent=None):
super(HelpHTMLDialog, self).__init__(parent=parent, title=title, size=(1000, 800))
self.Bind(wx.EVT_CHAR_HOOK, self.charHook)
self.addWidgets()
self.Centre()
self.ShowModal()
def addWidgets(self):
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
# HTML browser containing the help page
self.browser = wx.html2.WebView.New(self.panel)
html = "<p>Hello there! <a href='#'>Click me</a>.</p>"
self.browser.SetPage(html, "")
vbox.Add(self.browser, 1, wx.EXPAND | wx.ALL, 5)
# Close button
self.closeButton = wx.Button(self.panel, label="Close")
self.closeButton.Bind(wx.EVT_BUTTON, self.onCloseButtonClick)
vbox.Add(self.closeButton, 1, wx.EXPAND | wx.ALL, 5)
self.panel.SetSizer(vbox)
def close(self):
self.Destroy()
def charHook(self, event):
key = event.GetKeyCode()
if key == wx.WXK_ESCAPE:
self.close()
else:
event.Skip()
def onCloseButtonClick(self, event):
self.close()
The question therefore is - How can the key press or charhook event be propagated from the WebView widget to the dialog so that I can handle it? I also tried binding the event to the WebView widget instead of the dialog object, but that had the same effect.
I'm implementing the help menu of an App done in wxPython. By now, I'm using a txt file opened in a frame. I would like to have hyperlinks in the help text in order to open other txt files in the same frame. However, I don't know how to do this. I don't even know if this is the most elegant way to implement a help menu. Any suggestion will be very useful.
Below you can find part of the code I'm using (you will need a txt file called "Help_Main_App.txt"):
import wx
class Help_Frame(wx.Frame):
title = "Help, I need somebody, help..."
def __init__(self):
wx.Frame.__init__(self, wx.GetApp().TopWindow, title=self.title, size=(450,500))
self.CreateStatusBar()
panel = wx.Panel(self, wx.ID_ANY)
panel.SetBackgroundColour('#ededed')
self.Centre()
vBox = wx.BoxSizer(wx.VERTICAL)
hBox = wx.BoxSizer(wx.HORIZONTAL)
self.textbox = wx.TextCtrl(panel, style=wx.TE_MULTILINE, size=(-1, 295))
hBox.Add(self.textbox, 1, flag=wx.EXPAND)
vBox.Add(hBox, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
panel.SetSizer(hBox)
defaultdir, filename = './', 'Help_Main_App.txt'
self.filePath = '/'.join((defaultdir, filename))
self.textbox.LoadFile(self.filePath)
self.textbox.Disable()
class Main_Window(wx.Frame):
def __init__(self, parent, title):
#wx.Frame.__init__(self, parent, title = title, pos = (0, 0), size = wx.DisplaySize())
wx.Frame.__init__(self, parent, title=title, size=(1000,780))
self.Center()
# Setting up the menu.
filemenu = wx.Menu()
helpmenu = wx.Menu()
menuExit = filemenu.Append(wx.ID_EXIT,"&Exit"," Close window and exit program")
menuHelp = helpmenu.Append(wx.ID_HELP, "&Help"," Help of this program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
menuBar.Append(helpmenu,"&Help") # Adding the "helpmenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
# Set event handlers
self.Bind(wx.EVT_MENU, self.OnHelp, menuHelp)
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
def OnHelp(self,e):
Help_Frame().Show()
def OnExit(self,e):
self.Close(True) # Close the frame.
def main():
app = wx.App(False)
frame = Main_Window(None, "Main App")
frame.Show()
app.MainLoop()
if __name__ == "__main__" :
main()
I recommend using an HTMLWindow for something simple like that. It can handle only simple HTML, so don't try to make a website with it as HTMLWindow doesn't support CSS or javascript.
I wrote a simple About box using it. You can read about it here:
http://www.blog.pythonlibrary.org/2008/06/11/wxpython-creating-an-about-box/
The basic idea is to subclass HTMLWindow and override its OnLinkClicked method. Then you can use Python's webbrowser to open the user's default browser. Or you can try using subprocess, although that will be a lot less likely to work unless you always know what is installed on your target machines.
Further to Mikes answer if you are able to use wxPython 2.9.4 or above you can consider using the more advanced html2 webview which does support CSS and javascript. Using this you could make the help as a simple website that can be viewed in program.
http://wxpython.org/Phoenix/docs/html/html2.WebView.html
Its also worth mentioning that if (for some strange reason) you don't want to work with you could achieve a similar outcome with a StyledTxtCtrl.
Late to the party but just for the sake of completeness (seeing that the OP's code was using wx.TextCtrl to show the help text), here is an example on how to add and launch hyperlinks using wx.TextCtrl (I have attached any explanations on the code comments):
class HelpDialog(wx.Dialog):
"""Help Dialog."""
def __init__(self, parent, title, style):
"""Init."""
wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
title=title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=style)
# We need the 'wx.TE_AUTO_URL' style set.
self.help = wx.TextCtrl(self, wx.ID_ANY, '', DPOS, DSIZE,
wx.TE_AUTO_URL|wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH2|wx.TE_WORDWRAP)
# Events - this is the interesting part,
# we catch the mouse on hovering the hyperlink:
self.help.Bind(wx.EVT_TEXT_URL, self.openHlpUrl)
# Show dialog
self.ShowModal()
def openHlpUrl(self, event):
"""Open help URL."""
# We get the starting and ending points on
# the text stored in our ctrl from this event
# and we slice it:
url = self.help.GetValue()[event.GetURLStart():event.GetURLEnd()]
# We want to capture the left up mouse event
# when hovering on the hyperlink:
if event.MouseEvent.LeftDown():
# Let's be wxpythion native and launch the browser this way:
wx.LaunchDefaultBrowser(url)
I'm in the process of learning to use wxWidgets and Python, but I'm having some trouble figuring out how to size widgets within a frame.
I am under the impression that I can set the size of various widgets by giving them a custom size=(x,y) value when calling the constructor. This code is copied and pasted out of the examples for wxPython and I have added the value="example",pos=(0,0) and size=(100,100) values to the wx.TextCtrl() constructor, but when I run this program, the text control takes up the entirety of the 500x500 frame. I'm not sure why, and I'd appreciate any help you could give me to get it to work.
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(500,500))
self.control = wx.TextCtrl(self,-1,value="example",pos=(0,0),size=(100,100))
self.CreateStatusBar() # A Statusbar in the bottom of the window
# Setting up the menu.
filemenu= wx.Menu()
# wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
filemenu.AppendSeparator()
filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
self.Show(True)
app = wx.App(False)
frame = MainWindow(None, "Sample editor")
app.MainLoop()
Please read the sizers overview in the manual to know about how to size widgets correctly.
As for your particular example, it's an exception due to the fact that wxFrame always resizes its only window to fill its entire client area -- just because this is what you almost always want. However typically this only window is a wxPanel which, in turn, contains other controls and uses sizers to position them.
TL;DR: You should never use absolute positioning, i.e. specifying positions in pixels.
For sure you need to read from this book: wxPython 2.8 Application Development Cookbook
Read -> Chapter 7: Window Layout and Design
In your code, add widgets holder: panel.
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(500,500))
panel = wx.Panel(self)
self.control = wx.TextCtrl(panel,-1,value="example",pos=(0,0),size=(100,100))
self.CreateStatusBar() # A Statusbar in the bottom of the window
# Setting up the menu.
filemenu= wx.Menu()
# wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program")
filemenu.AppendSeparator()
filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
self.Show(True)
app = wx.App(False)
frame = MainWindow(None, "Sample editor")
app.MainLoop()
I am fairly new to programming and to python and wxpython. I have looked over this code for literally HOURS and I tried finding an answer everywhere online. I am having trouble getting a new window to show up after a menu item is clicked. Here is my code so far...
import wx
class MainWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Python Test App',size=(600,400))
panel=wx.Panel(self)
wx.Frame.CenterOnScreen(self)
##wx.Frame.Maximize(self)
status=self.CreateStatusBar()
menubar=wx.MenuBar()
file_menu=wx.Menu()
edit_menu=wx.Menu()
ID_FILE_NEW = 1
ID_FILE_OPEN = 2
ID_EDIT_UNDO = 3
ID_EDIT_REDO = 4
file_menu.Append(ID_FILE_NEW,"New Window","This is a new window")
file_menu.Append(ID_FILE_OPEN,"Open...","This will open a new window")
edit_menu.Append(ID_EDIT_UNDO,"Undo","This will undo your last action")
edit_menu.Append(ID_EDIT_REDO,"Redo","This will redo your last undo")
menubar.Append(file_menu,"File")
menubar.Append(edit_menu,"Edit")
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, NewWindow.new_frame, None, 1)
class NewWindow(wx.Frame):
def __init__(self,MainWindow,id):
wx.Frame.__init__(self, None, id, 'New Window', size=(600,400))
wx.Frame.CenterOnScreen(self)
self.Show(False)
def new_frame(self, event):
NewWindow.Show(True)
if __name__=='__main__':
app=wx.PySimpleApp()
frame=MainWindow(parent=None,id=-1)
frame.Show()
app.MainLoop()
When I try to run this code, I get this error message once I click on the menu item "New Window"
TypeError: unbound method new_frame() must be called with NewWindow instance as first argument (got CommandEvent instance instead)
Again, I am fairly new to programming. Any help is greatly appreciated and also, I know my code may not be the "cleanest" looking code around. Thanks in advance!
You don't seem to understand how classes work in Python. You try to call NewWindow.new_frame, but you never actually create an instance of that class.
The error message is because you are calling the method on the class instead of on an instance of the class. What you want to do is something like:
newWin = NewWindow(...) # replace ... with the appropriate parameters
newWin.Show(True)
You don't provide enough information in your example to know what the appropriate parameters are for the NewWindow call (e.g., you don't show where you create the main window), but the MainWindow and id parameters in NewWindow.__init__ aren't just there for looks: wxPython needs to know the parent window. You should look into the wxPython documentation to understand how to create a wxFrame.
Modifying your code to some extent i was able to show a new window when user clicks a New Window option,
Do check the stuff that i have modified a let me know if this is what you want??
import wx
class MainWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Python Test App',size=(600,400))
panel=wx.Panel(self)
wx.Frame.CenterOnScreen(self)
status=self.CreateStatusBar()
menubar=wx.MenuBar()
file_menu=wx.Menu()
edit_menu=wx.Menu()
ID_FILE_NEW = 1
ID_FILE_OPEN = 2
ID_EDIT_UNDO = 3
ID_EDIT_REDO = 4
file_menu.Append(ID_FILE_NEW,"New Window","This is a new window")
file_menu.Append(ID_FILE_OPEN,"Open...","This will open a new window")
edit_menu.Append(ID_EDIT_UNDO,"Undo","This will undo your last action")
edit_menu.Append(ID_EDIT_REDO,"Redo","This will redo your last undo")
menubar.Append(file_menu,"File")
menubar.Append(edit_menu,"Edit")
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.test, None, 1)
def test(self, event):
self.new = NewWindow(parent=None, id=-1)
self.new.Show()
class NewWindow(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self, parent, id, 'New Window', size=(400,300))
wx.Frame.CenterOnScreen(self)
#self.new.Show(False)
if __name__=='__main__':
app=wx.PySimpleApp()
frame=MainWindow(parent=None,id=-1)
frame.Show()
app.MainLoop()
I have a wxPython notebook, in this case a wx.aui.AuiNotebook. (but this problem has happened with other kinds of notebooks as well.) In my notebook I have a widget, in this case a subclass of ScrolledPanel, for which I am trying to do some custom event handling (for wx.EVT_KEY_DOWN). However, the events are not being handled. I checked my code outside of the notebook, and the event handling works, but when I put my widget in the notebook, the event handler doesn't seem to get invoked when the event happens.
Does the notebook somehow block the event? How do I solve this?
I tried reproducing your problem but it worked fine for me. The only thing I can think of is that there is one of your classes that also binds to wx.EVT_KEY_DOWN and doesn't call wx.Event.Skip() in its callback. That would prevent further handling of the event. If your scrolled panel happens to be downstream of such an object in the sequence of event handlers it will never see the event.
For reference, here's an example that worked for me (on Windows). Is what you're doing much different than this?
import wx
import wx.aui, wx.lib.scrolledpanel
class AppFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
# The notebook
self.nb = wx.aui.AuiNotebook(self)
# Create a scrolled panel
panel = wx.lib.scrolledpanel.ScrolledPanel(self, -1)
panel.SetupScrolling()
self.add_panel(panel, 'Scrolled Panel')
# Create a normal panel
panel = wx.Panel(self, -1)
self.add_panel(panel, 'Simple Panel')
# Set the notebook on the frame
self.sizer = wx.BoxSizer()
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.SetSizer(self.sizer)
# Status bar to display the key code of what was typed
self.sb = self.CreateStatusBar()
def add_panel(self, panel, name):
panel.Bind(wx.EVT_KEY_DOWN, self.on_key)
self.nb.AddPage(panel, name)
def on_key(self, event):
self.sb.SetStatusText("key: %d [%d]" % (event.GetKeyCode(), event.GetTimestamp()))
event.Skip()
class TestApp(wx.App):
def OnInit(self):
frame = AppFrame(None, -1, 'Click on a panel and hit a key')
frame.Show()
self.SetTopWindow(frame)
return 1
if __name__ == "__main__":
app = TestApp(0)
app.MainLoop()