Reputation: 39
I am in the process of writing a chat program that integrates with skype. I have most of the legwork done, but I am having issues with the Notebook control in wxPython. I want to create a new tab on the notebook when a user sends a message, which I have working, but the issue I have is how do I reference the TextCtrl on the panel on the tab? The following is code from a different project I pulled from online:
import wx
class Page(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
t = wx.StaticText(self, -1, "THIS IS A PAGE OBJECT", (20,20))
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Notebook Remove Pages Example")
pannel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
self.buttonRemove = wx.Button(pannel, id=wx.ID_ANY, label="DELETE", size=(80, 25))
self.buttonRemove.Bind(wx.EVT_BUTTON, self.onButtonRemove)
hbox.Add(self.buttonRemove)
self.buttonInsert = wx.Button(pannel, id=wx.ID_ANY, label="CREATE", size=(80, 25))
self.buttonInsert.Bind(wx.EVT_BUTTON, self.onButtonInsert)
hbox.Add(self.buttonInsert)
self.buttonMessage = wx.Button(pannel, id=wx.ID_ANY, label="Message", size=(80, 25))
self.buttonMessage.Bind(wx.EVT_BUTTON, self.onButtonMessage)
hbox.Add(self.buttonList)
vbox.Add(hbox)
self.Notebook3 = wx.Notebook(pannel)
vbox.Add(self.Notebook3, 2, flag=wx.EXPAND)
pannel.SetSizer(vbox)
self.pageCounter = 0
self.addPage()
def addPage(self):
self.pageCounter += 1
page = Page(self.Notebook3)
pageTitle = "Page: {0}".format(str(self.pageCounter))
self.Notebook3.AddPage(page, pageTitle)
def onButtonRemove(self, event):
page_to_delete = self.Notebook3.GetSelection()
self.Notebook3.DeletePage(page_to_delete)
def onButtonInsert(self, event):
self.addPage()
def onButtonMessage(self, event):
self.Notebook3.StaticText(0).AppendText("Yeah right. Like this works")
if __name__ == "__main__":
app = wx.App()
MainFrame().Show()
app.MainLoop()
I just can't seem to get it together quite right. Any help is appreciated.
Upvotes: 1
Views: 2652
Reputation: 3113
Yoriz's answer is good but you need to be very careful in these situations to not violate encapsulation and abstraction.
When it comes to encapsulation, my personal rule-of-thumb is that any of my frames/panels/controls/dialogs/etc. are only allowed to call functions one level away. That is, a panel can call functions from its parent and from its immediate children. In their solution, Yoriz calls a function two levels away; AppendText()
is called on page.textCtrl
:
def onButtonMessage(self, event):
page = self.notebook3.GetCurrentPage()
page.textCtrl.AppendText("Yeah this works ")
The problem is that MainFrame
is calling functions on objects it does not have immediate control of. This can be easily solved by creating some wrapper function in Page
like this:
class Page(wx.Panel):
...
def AppendText(text):
self.textCtrl.AppendText(text)
And calling it in MainFrame
like this:
class MainFrame(wx.Frame):
def onButtonMessage(self, event):
page = self.notebook3.GetCurrentPage()
page.AppendText("Yeah this works ")
This too has a problem. In order to promote code-reuse we want our design to be as abstracted as possible with the fewest possible dependencies. MainFrame
is dependent on notebook3
existing and having a textCtrl
object (in Yoriz's answer) or an AppendText()
function (in my suggestion above) and will create an error if this isn't the case. This creates difficulties if you attempt to reuse MainFrame
in a different project.
There are several ways to reduce these types of dependencies but my personal favourite is wxPython's pubsub library (you can find a more detailed tutorial here). When the button is pressed in one panel you can send out a message using pub.sendMessage()
which can then be received by the other panel which can deal with it appropriately. You can even include arguments (in your case, like what text to set textCtrl
to). This is conceptually very similar to how wxPython already handles button presses, just substitute "message" for "event".
Using pubsub, MainFrame
doesn't need to know anything about workbook3
. Heck, pannel
andworkbook3
can communicate without even knowing each other exist; they only need to know what messages to send/subscribe to (I suggest declaring your messages as constants). This makes all of your components more flexible and reusable.
In code, it looks like this:
import wx
from wx.lib.pubsub import setupkwargs #this line not required in wxPython2.9.
#See documentation for more detail
from wx.lib.pubsub import pub
#This message requires the argument "text"
MSG_CHANGE_TEXT = "change.text"
class Page(wx.Panel):
def __init__(self, parent):
self.textCtrl = wx.TextCtrl(self, -1, "THIS IS A PAGE OBJECT ",
style=wx.TE_MULTILINE | wx.BORDER_NONE)
...
pub.subscribe(self.onChangeText, MSG_CHANGE_TEXT)
def onChangeText(self, text):
self.textCtrl.AppendText(text)
class MainFrame(wx.Frame):
...
def onButtonMessage(self, event):
pub.sendMessage(MSG_CHANGE_TEXT, text="Yeah this works ")
Obviously, the sample code you gave us is pretty simple so in this particular case you could easily say that the advantages gained by using pubsub are not worth the effort. However, what if pannel
and workbook3
didn't share the same parent? In that case it would be very difficult to pass an instance of workbook3
to pannel
and would require a lot of dependencies over a large number of classes. In situations like this pubsub offers a simple, easy, and clean solution.
Upvotes: 1
Reputation: 3625
In your class Page you created a control but only stored it in a local variable, you need to use self to store it as an instance variable that you can access from the page instance.
In your method onButtonMessage you are asking the notebook for its statictext which it doesn't have one, note book contains pages, also you are calling AppendText which statictext doesn't have this method.
To fix this code you need to Change the class Page to have a textctrl and store it as an instance variable. Change method onButtonMessage to find the current page and then access its textcrtl to append the text.
Here is your modified code, i also done some layout adjustments
import wx
class Page(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.textCtrl = wx.TextCtrl(self, -1, "THIS IS A PAGE OBJECT ",
style=wx.TE_MULTILINE | wx.BORDER_NONE)
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add(self.textCtrl, 1, wx.EXPAND)
self.SetSizer(vbox)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Notebook Remove Pages Example")
pannel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
self.buttonRemove = wx.Button(pannel, id=-1, label="DELETE")
self.buttonRemove.Bind(wx.EVT_BUTTON, self.onButtonRemove)
hbox.Add(self.buttonRemove)
self.buttonInsert = wx.Button(pannel, id=-1, label="CREATE")
self.buttonInsert.Bind(wx.EVT_BUTTON, self.onButtonInsert)
hbox.Add(self.buttonInsert)
self.buttonMessage = wx.Button(pannel, id=-1, label="Message")
self.buttonMessage.Bind(wx.EVT_BUTTON, self.onButtonMessage)
hbox.Add(self.buttonMessage)
vbox.Add(hbox, 0, wx.ALL, 7)
self.notebook3 = wx.Notebook(pannel)
vbox.Add(self.notebook3, 1, wx.EXPAND | wx.ALL, 7)
pannel.SetSizer(vbox)
self.pageCounter = 0
self.addPage()
def addPage(self):
self.pageCounter += 1
page = Page(self.notebook3)
pageTitle = "Page: {0}".format(str(self.pageCounter))
self.notebook3.AddPage(page, pageTitle)
def onButtonRemove(self, event):
page_to_delete = self.notebook3.GetSelection()
self.notebook3.DeletePage(page_to_delete)
def onButtonInsert(self, event):
self.addPage()
def onButtonMessage(self, event):
page = self.notebook3.GetCurrentPage()
page.textCtrl.AppendText("Yeah this works ")
if __name__ == "__main__":
app = wx.App()
MainFrame().Show()
app.MainLoop()
Upvotes: 1