views:

63

answers:

3

I am trying to write a non-webbased client for a chat service on a web site, it connects to it fine via socket and can communicate with it and everything. I am writing a GUI for it (I tried writing it in tkinter but I hit some walls I really wantd to get passed, so I switched to wxPython)

What I'm having a problem with:

This application uses an extended Notebook widget called NotebookCtrl. However the same problem appears with regular Notebook. First it creates a page in which things are logged to, which is successful, and then it connects, and it's supposed to add pages with each chatroom it joins on the service. However, when it adds a tab after it's mainloop has been started (I am communicating with the GUI and the sockets via queues and threading), the tab comes up completely blank. I have been stuck on this for hours and hours and got absolutely nowhere

The example that came with the NotebookCtrl download adds and deletes pages by itself perfectly fine. I am on the edge of completely giving up on this project. Here is what the code looks like (note this is a very small portion of the application, but this covers the wxPython stuff)

class Chatroom(Panel):
''' Frame for the notebook widget to tabulate a chatroom'''
def __init__(self, ns, parent):        
    Panel.__init__(self, parent, -1)
    self.msgs, self.typed, self.pcbuff = [], [], {}
    self.members, self._topic, self._title, self.pc = None, None, None, None
    self.name, self.tabsign, = ns, 0

    self.hSizer1 = wx.BoxSizer(wx.HORIZONTAL)
    self.vSizer  = wx.BoxSizer(wx.VERTICAL)

    self.Grid    = wx.GridBagSizer(5, 2)


    self.Title = TextCtrl(self, size=(-1, 50), style=wx.TE_MULTILINE | wx.TE_READONLY)
    self.Topic = TextCtrl(self, size=(-1, 50), style=wx.TE_MULTILINE | wx.TE_READONLY)
    self.Privclasses = TreeCtrl(self, size=(150, -1))
    self.Buffer = wx.html.HtmlWindow(self) 
    self.Buffer.SetStandardFonts(8) 
    self.templbl = StaticText(self, -1, 'This is where the formatting buttons will go!')
    # note to remember: self.templbl.SetLabel('string') sets the label
    self.Typer = TextCtrl(self, size=(-1, 50), style=wx.TE_MULTILINE)

    self.Grid.Add(self.Title, (0,0), (1,2), wx.EXPAND, 2)
    self.Grid.Add(self.Topic, (1,0), (1,1), wx.EXPAND, 2)
    self.Grid.Add(self.Privclasses, (1,1), (2,1), wx.EXPAND, 2)
    self.Grid.Add(self.Buffer, (2,0), (1,1), wx.EXPAND, 2)
    self.Grid.Add(self.templbl, (3,0), (1,1), wx.EXPAND | wx.ALIGN_LEFT, 2)
    self.Grid.Add(self.Typer, (4,0), (1,1), wx.EXPAND, 2)


    self.Grid.AddGrowableCol(0)
    self.Grid.AddGrowableRow(2)


    self.SetSizerAndFit(self.Grid)
    self.Show(True)

    self.Typer.Bind(EVT_CHAR, self.Typer_OnKeyDown)



def Typer_OnKeyDown(self, event):
    keycode = event.GetKeyCode()
    if event.ShiftDown():
        if keycode == WXK_RETURN:
            pass
        elif keycode == WXK_BACK:
            pass
        elif keycode == WXK_UP:
            pass
        elif keycode == WXK_DOWN:
            pass
    else:
        if keycode == WXK_RETURN:
            pass
    event.Skip()

def Write(self, msg, K):
    self.msgs.append(msg)
    if len(self.msgs) > 300: 
        self.msgs = self.msgs[50:]
    self.Buffer.SetPage('<br>'.join(self.msgs))

class Application(App):
def __init__(self, K):
    self.Queue = Queue.Queue()
    self.current = ''
    self.chatorder = []

    self.Window = App(0)
    self.frame = MainFrame(None, 0, "Komodo Dragon")
    self.Pages = NC.NotebookCtrl(self.frame, 9000)
    self.Channels = {}
    self.AddChatroom('~Komodo', K)


    self.frame.Show(True)
    self.Window.SetTopWindow(self.frame)

    self.Timer = _Timer(0.050, self.OnTimer)
    self.Timer.start()

    self.Pages.Bind(NC.EVT_NOTEBOOKCTRL_PAGE_CHANGED, self.onPageChanged)  
    self.Pages.Bind(NC.EVT_NOTEBOOKCTRL_PAGE_CHANGING, self.onPageChanging)   
    self.Pages.Bind(EVT_PAINT, self.onPaint)
    self.Pages.Bind(EVT_SIZE, self.onSize)

def onPaint(self, event):
    event.Skip()
def onSize(self, event):
    event.Skip()

def Run(self):
    self.Window.MainLoop()


def onPageChanged(self, event):
    event.Skip()

def onPageChanging(self, event):
    event.Skip()




# Timer and Queue functions
def OnTimer(self):
    self.CheckQueue()
    self.Timer = _Timer(0.050, self.OnTimer)
    self.Timer.start()
def CheckQueue(self): # the Application needs to use a queue to do things in order to prevent
    try:             # overlaps from happening, such as runtime errors and widgets changing
        while not self.Queue.empty(): # suddenly. The most common error seems to be size 
            func = self.Queue.get_nowait()  # changes during iterations. Everything from 
            func()   # packet processing to adding widgets needs to wait in line this way
    except Queue.Empty: 
        pass
def AddQueue(self, func): 
    self.Queue.put(func)

# Channel controls
def AddChatroom(self, ns, K):
    if ns in self.Channels: return

    #self.typedindex = 0
    c = K.format_ns(ns)
    self.chatorder.append(ns)
    self.current = ns

    self.Channels[ns] = Chatroom(ns, self.Pages)
    self.Pages.AddPage(self.Channels[ns], ns, True)

def DeleteChatroom(self, ns, bot): # Delete a channel, it's properties, and buttons
    ind = self.chatorder.index(ns)
    del self.chatorder[ind]
    for each in self.chatorder[ind:]:
        x = self.channels[each].tab.grid_info()
        if x['column'] == '0': r, c = int(x['row'])-1, 9
        else:                  r, c = int(x['row']), int(x['column'])-1
        self.channels[each].tab.grid_configure(row=r, column=c)
        x = self.channels[each].tab.grid_info()
    self.channels[ns].Tab.destroy()
    self.channels[ns].tab.destroy()
    self.channels[self.chatorder[-1]].tab.select()
    self.switchtab(bot, self.chatorder[-1])
    x = self.tabids_reverse[ns]
    del self.tabids_reverse[ns], self.tabids[x], self.channels[ns]

The Chatroom class covers what each tab should have in it. the first tab that is added in class Application's init function is perfectly fine, and even prints messages it receives from the chat service when it attempts to connect. Once the service sends a join packet to me, it calls the same exact function used to add that tab, AddChatroom(), and should create the exact same thing, only printing messages specifically for that chatroom. It creates the tab, but the Chatroom page is completely empty and grey. I am very sad :C

Thanks in advance if you can help me.

EDIT

I have written a working example of this script you can run yourself (if you have wxPython). It uses regular Notebook instead of NotebookCtrl so you don't have to download that widget as well, but the bug happens for both. The example sets up the window, and sets up the main debug tab and then a chatroom tab before entering mainloop. After mainloop, any chatrooms added afterwords are completely blank

from wx import *
import Queue, time
from threading import Timer as _Timer
from threading import Thread
# import System._NotebookCtrl.NotebookCtrl as NC  ## Using regular notebook for this example

class MainFrame(Frame):
    def __init__(self, parent, ID, title):
        ID_FILE_LOGIN   = 100
        ID_FILE_RESTART = 101
        ID_FILE_EXIT    = 102
        ID_HELP_ABOUT   = 200

        Frame.__init__(self, parent, ID, title,
                         DefaultPosition, Size(1000, 600))
        self.CreateStatusBar()

        self.SetStatusText("This is the statusbar")
        menu_File   = Menu()
        menu_Help   = Menu()
        menu_Edit   = Menu()
        menu_Config = Menu()

        menu_File.Append(ID_FILE_LOGIN, "&Login Info", 
                    "Enter your deviantArt Login information")
        menu_File.AppendSeparator()
        menu_File.Append(ID_FILE_RESTART, "&Restart", 
                    "Restart the program")
        menu_File.Append(ID_FILE_EXIT, "E&xit", 
                    "Terminate the program")

        menu_Help.Append(ID_HELP_ABOUT, "&About",
                    "More information about this program")

        menuBar = MenuBar()
        menuBar.Append(menu_File, "&File");
        menuBar.Append(menu_Edit, "&Edit");
        menuBar.Append(menu_Config, "&Config");
        menuBar.Append(menu_Help, "&Help");
        self.SetMenuBar(menuBar)

        EVT_MENU(self, ID_FILE_LOGIN, self.OnLogin)
        EVT_MENU(self, ID_FILE_RESTART,  self.OnRestart)
        EVT_MENU(self, ID_FILE_EXIT,  self.OnQuit)

        EVT_MENU(self, ID_HELP_ABOUT, self.OnAbout)

    def OnAbout(self, event):
        dlg = MessageDialog(self, "Hi! I am Komodo Dragon! I am an application\n"
                              "that communicates with deviantArt's Messaging Network (dAmn)",
                              "Komodo", OK | ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def OnQuit(self, event):
        self.Close(True)

    def OnRestart(self, event):
        pass

    def OnLogin(self, event):
        dlg = MessageDialog(self, "Enter your Login information here:\n"
                              "Work in progress, LOL",
                              "Login", OK | ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

class Chatroom(Panel):
    ''' Frame for the notebook widget to tabulate a chatroom'''
    def __init__(self, parent, ns):        
        Panel.__init__(self, parent, -1)
        self.msgs, self.typed, self.pcbuff = [], [], {}
        self.members, self._topic, self._title, self.pc = None, None, None, None
        self.name, self.tabsign, = ns, 0

        self.hSizer1 = wx.BoxSizer(wx.HORIZONTAL)
        self.vSizer  = wx.BoxSizer(wx.VERTICAL)

        self.Grid    = wx.GridBagSizer(5, 2)

        self.Title = TextCtrl(self, size=(-1, 50), style=wx.TE_MULTILINE | wx.TE_READONLY)
        self.Topic = TextCtrl(self, size=(-1, 50), style=wx.TE_MULTILINE | wx.TE_READONLY)
        self.Privclasses = TreeCtrl(self, size=(150, -1))
        self.Buffer = wx.html.HtmlWindow(self) 
        self.Buffer.SetStandardFonts(8) 
        self.templbl = StaticText(self, -1, 'This is where the formatting buttons will go!')
        self.Typer = TextCtrl(self, size=(-1, 50), style=wx.TE_MULTILINE)

        self.Grid.Add(self.Title, (0,0), (1,2), wx.EXPAND, 2)
        self.Grid.Add(self.Topic, (1,0), (1,1), wx.EXPAND, 2)
        self.Grid.Add(self.Privclasses, (1,1), (2,1), wx.EXPAND, 2)
        self.Grid.Add(self.Buffer, (2,0), (1,1), wx.EXPAND, 2)
        self.Grid.Add(self.templbl, (3,0), (1,1), wx.EXPAND | wx.ALIGN_LEFT, 2)
        self.Grid.Add(self.Typer, (4,0), (1,1), wx.EXPAND, 2)

        self.Grid.AddGrowableCol(0)
        self.Grid.AddGrowableRow(2)

        self.SetSizerAndFit(self.Grid)
        self.Show(True)

        self.Typer.Bind(EVT_CHAR, self.Typer_OnKeyDown)

    def Typer_OnKeyDown(self, event):
        keycode = event.GetKeyCode()
        if event.ShiftDown():
            if keycode == WXK_RETURN:
                pass
            elif keycode == WXK_BACK:
                pass
            elif keycode == WXK_UP:
                pass
            elif keycode == WXK_DOWN:
                pass
        else:
            if keycode == WXK_RETURN:
                pass
        event.Skip()

    def Write(self, msg):
        self.msgs.append(msg)
        if len(self.msgs) > 300: 
            self.msgs = self.msgs[50:]
        self.Buffer.SetPage('<br>'.join(self.msgs))



class Application(App):
    def __init__(self, K):
        self.Queue = Queue.Queue()
        self.current = ''
        self.chatorder = []

        self.Window = App(0)
        self.frame = MainFrame(None, 0, "Komodo Dragon")
        self.Pages = Notebook(self.frame, 9000)
        self.Channels = {}
        self.AddChatroom('~Komodo', K)

        self.frame.Show(True)
        self.Window.SetTopWindow(self.frame)

        self.Timer = _Timer(0.050, self.OnTimer)

        self.Pages.Bind(EVT_NOTEBOOK_PAGE_CHANGED, self.onPageChanged)  
        self.Pages.Bind(EVT_NOTEBOOK_PAGE_CHANGING, self.onPageChanging)   
        self.Pages.Bind(EVT_PAINT, self.onPaint)
        self.Pages.Bind(EVT_SIZE, self.onSize)

    def onPaint(self, event):
        event.Skip()
    def onSize(self, event):
        event.Skip()
    def onPageChanged(self, event):
        event.Skip()
    def onPageChanging(self, event):
        event.Skip()        

    def Run(self):
        self.Window.MainLoop()

    # Timer and Queue functions
    def OnTimer(self):
        self.CheckQueue()
        self.Timer = _Timer(0.050, self.OnTimer)
        self.Timer.start()
    def CheckQueue(self): # the Application needs to use a queue to do things in order to prevent
        try:             # overlaps from happening, such as runtime errors and widgets changing
            while not self.Queue.empty(): # suddenly. The most common error seems to be size 
                func = self.Queue.get_nowait()  # changes during iterations. Everything from 
                if func is not None:
                    func()   # packet processing to adding widgets needs to wait in line this way
        except Queue.Empty: 
            pass
    def AddQueue(self, func): 
        self.Queue.put(func)

    # Channel controls
    def AddChatroom(self, ns, K):
        if ns in self.Channels: return

        self.chatorder.append(ns)
        self.current = ns

        self.Channels[ns] = Chatroom(self.Pages, ns)
        self.Pages.AddPage(self.Channels[ns], ns, True)

class _Thread(Thread):
    def __init__(self, K):
        Thread.__init__(self)
        self.K = K

    def run(self):
        self.K.Mainloop()        

class K:
    def __init__(self): 
        self.App = Application(self)
        self.App.AddQueue(self.App.Channels['~Komodo'].Write('>> Welcome!') )
        self.App.AddQueue(self.App.Channels['~Komodo'].Write('>> Entering mainloop...') )
        self.App.AddChatroom('#TestChatroom1', self)

        self.roomcount = 1
        self.timer = time.time() + 3

        self.thread = _Thread(self)
        self.thread.start()
        self.App.Timer.start()
        self.App.Run()



    def Mainloop(self):
        while True:
            if time.time() > self.timer:
                self.App.AddQueue(
                    lambda: self.App.Channels['~Komodo'].Write('>> Testing') )
                if self.roomcount < 5:
                    self.roomcount += 1
                    self.App.AddQueue(
                        lambda: self.App.AddChatroom('#TestChatroom{0}'.format(self.roomcount), self) )
                self.timer = time.time() + 3


if __name__ == '__main__':
    test = K()
A: 

It doesn't look like you're creating your Chatroom with its parent as the Notebook. What is "K" in Application.__init__? (you didn't post a fully runnable sample)

Steven Sproat
the fully runnable sample is too big to paste on here. K refers to the program's main class (the program is called Komodo) it is there because the application interface needs to communicate with the chat service using methods in the K class (not fully implemented, but I'll be binding things to do certain things. Enter sends a message, buttons for kicking people, etc)
Blazer
'self.Pages' is the notebook, and the parent of the tab is stated in the second argument of the Chatroom class in this line: 'self.Channels[ns] = Chatroom(ns, self.Pages)'
Blazer
and for reference, ns (namespace) refers to the chatroom's name
Blazer
I will see if I can recreate a small example without inclduing the rest of the program (it is in many many files) using what you see here
Blazer
I have edited the question with a working example
Blazer
A: 

Here is your problem:

lambda: self.App.AddChatroom('#TestChatroom{0}'.format(self.roomcount), self) )

Fixed by using wx.CallAfter (tested on win xp sp3):

lambda: wx.CallAfter(self.App.AddChatroom, '#TestChatroom{0}'.format(self.roomcount), self)

You were probably tying up the GUI by calling wx objects from thread code. See this wxPython wiki article.

Steven Sproat
thanks alot! Who knew such a big problem had such an easy fix. You have no idea how grateful I am. I would vote up if I had enough reputation lol
Blazer
A: 

When adding or deleting tabs, you probably need to call Layout() right after you're done. One easy way to find out is to grab an edge of the frame and resize it slightly. If you see widgets magically appear, then it's because Layout() was called and your page re-drawn.

Mike Driscoll