wxPython StaticText in class not updating - python

I have a simple local network chat program with a chat log that is a wx.StaticText object and I'm having issues with it not updating when I set the label. I had it working previously with just self.chat_box.SetLabel(chatHistory_Display), but that was before I had to separate the elements into their own panels so I could have a scrolling panel for just the static text.
When running the program, you type a message in the bottom textctrl box, hit Enter or click the send button, then your message is supposed to appear in the black chat history box. The code runs fine with no errors, and when running the code step by step, the chatHistory and chatHistory_Display variables contains the correct value, but nothing happens when setting the label. The text box remains blank.
My code below. Apologies, I'm sure my code is a mess as I'm very new to wxPython and python in general and I'm trying to learn. The LogPanel class below contains my static text object, and the function i'm calling that sets the label is in append_chat.
import wx
import wx.lib.scrolledpanel as scrolled
chatHistory = []
class LogPanel(scrolled.ScrolledPanel):
def __init__(self, parent):
wx.ScrolledWindow.__init__(self, parent)
self.SetupScrolling()
# Chat log
self.chat_box = wx.StaticText(self, style=wx.TE_MULTILINE | wx.BORDER_THEME | wx.VSCROLL | wx.ST_NO_AUTORESIZE)
self.chat_box.SetBackgroundColour(wx.Colour(0, 0, 0, wx.ALPHA_OPAQUE))
self.chat_box.SetForegroundColour(wx.Colour(255, 255, 255, wx.ALPHA_OPAQUE))
# Sizer
sizer_log = wx.StaticBoxSizer(wx.VERTICAL, self)
sizer_log.Add(self.chat_box, 1, wx.EXPAND | wx.ALL, 5)
self.SetSizer(sizer_log)
def append_chat(self, message):
chatHistory.append(message)
print('Message to be added: ' + message) # Prints message to be appended to chat history
chatHistory_Display = ''
for message in chatHistory:
chatHistory_Display += message + '\n'
self.chat_box.SetLabel(chatHistory_Display)
print('Entire chat history: ' + str(chatHistory)) # Prints entire chat history
class SendPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# Send button
self.my_btn = wx.Button(self, label='Send')
self.my_btn.Bind(wx.EVT_BUTTON, self.send_message) # Even bind
# Message box
self.text_ctrl = wx.TextCtrl(style=wx.TE_PROCESS_ENTER | wx.TE_MULTILINE, parent=self)
self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.key_code)
# sizer
box_send = wx.BoxSizer(wx.HORIZONTAL)
box_send.Add(self.my_btn, 0, wx.ALL, 5)
box_send.Add(self.text_ctrl, 1, wx.EXPAND | wx.ALL, 5)
self.SetSizer(box_send)
self.function = LogPanel(self)
def send_message(self, event):
msg = self.text_ctrl.GetValue()
if msg:
self.function.append_chat(msg)
self.text_ctrl.Clear()
def key_code(self, event):
unicodeKey = event.GetUnicodeKey()
if event.GetModifiers() == wx.MOD_SHIFT and unicodeKey == wx.WXK_RETURN:
self.text_ctrl.WriteText('\n')
# print("Shift + Enter")
elif unicodeKey == wx.WXK_RETURN:
self.send_message(event)
# print("Just Enter")
else:
event.Skip()
# print("Any other character")
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='Chatting with Bob', name='Local Instant Messenger')
# Panels
main_panel = wx.Panel(self)
sub_panel_Log = LogPanel(main_panel)
sub_panel_Send = SendPanel(main_panel)
# Sizer
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(sub_panel_Log, 1, wx.EXPAND | wx.ALL, 5)
main_sizer.Add(sub_panel_Send, 0, wx.EXPAND | wx.ALL, 5)
main_panel.SetSizer(main_sizer)
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
frame.Show()
app.MainLoop()
I've look everywhere online and couldn't find any answer that worked for me. I've tried all kinds of combinations of Refresh() and Update() like self.Refresh(), LogPanel.Refresh(), MyFrame.Refresh(), just plain Refresh(), and others I can't remember (Pretty much any combination/placement PyCharm would be satisfied with. I tried wx.Yield as well.
I also tried using wx.TextCtrl with wx.TE_READONLY before splitting things up into separate classes, but I didn't like that it shows the cursor and even with wx.TE_MULTILINE the text wouldn't create new lines.
I have no idea what I'm doing, so any guidance would be helpful. Other tips not related to this specific issue would be appreciated as well.
Thanks.

The clue is that weird glyph attached to your frame.
It's a second occurrence of the LogPanel created in SendPanel by this line self.function = LogPanel(self).
Again, it's my bete noire, everything sitting in it's own class.
You need to point to it not recreate it and pointing to it, is either by directly creating a variable for the purpose or tracking it down through the hierarchy.
Here's a simple way, by getting it via the grandparent:
import wx
import wx.lib.scrolledpanel as scrolled
chatHistory = []
class LogPanel(scrolled.ScrolledPanel):
def __init__(self, parent):
wx.ScrolledWindow.__init__(self, parent)
self.SetupScrolling()
# Chat log
self.chat_box = wx.StaticText(self, style=wx.TE_MULTILINE | wx.BORDER_THEME | wx.VSCROLL | wx.ST_NO_AUTORESIZE)
self.chat_box.SetBackgroundColour(wx.Colour(0, 0, 0, wx.ALPHA_OPAQUE))
self.chat_box.SetForegroundColour(wx.Colour(255, 255, 255, wx.ALPHA_OPAQUE))
# Sizer
sizer_log = wx.StaticBoxSizer(wx.VERTICAL, self)
sizer_log.Add(self.chat_box, 1, wx.EXPAND | wx.ALL, 5)
self.SetSizer(sizer_log)
def append_chat(self, message):
chatHistory.append(message)
print('Message to be added: ' + message) # Prints message to be appended to chat history
chatHistory_Display = ''
for message in chatHistory:
chatHistory_Display += message + '\n'
self.chat_box.SetLabel(chatHistory_Display)
print('Entire chat history: ' + str(chatHistory)) # Prints entire chat history
class SendPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
gparent = parent.GetParent()
# Send button
self.my_btn = wx.Button(self, label='Send')
self.my_btn.Bind(wx.EVT_BUTTON, self.send_message) # Even bind
# Message box
self.text_ctrl = wx.TextCtrl(style=wx.TE_PROCESS_ENTER | wx.TE_MULTILINE, parent=self)
self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.key_code)
# sizer
box_send = wx.BoxSizer(wx.HORIZONTAL)
box_send.Add(self.my_btn, 0, wx.ALL, 5)
box_send.Add(self.text_ctrl, 1, wx.EXPAND | wx.ALL, 5)
self.SetSizer(box_send)
self.function = gparent.sub_panel_Log
def send_message(self, event):
msg = self.text_ctrl.GetValue()
if msg:
self.function.append_chat(msg)
self.text_ctrl.Clear()
def key_code(self, event):
unicodeKey = event.GetUnicodeKey()
if event.GetModifiers() == wx.MOD_SHIFT and unicodeKey == wx.WXK_RETURN:
self.text_ctrl.WriteText('\n')
# print("Shift + Enter")
elif unicodeKey == wx.WXK_RETURN:
self.send_message(event)
# print("Just Enter")
else:
event.Skip()
# print("Any other character")
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='Chatting with Bob', name='Local Instant Messenger')
# Panels
main_panel = wx.Panel(self)
self.sub_panel_Log = LogPanel(main_panel)
self.sub_panel_Send = SendPanel(main_panel)
# Sizer
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(self.sub_panel_Log, 1, wx.EXPAND | wx.ALL, 5)
main_sizer.Add(self.sub_panel_Send, 0, wx.EXPAND | wx.ALL, 5)
main_panel.SetSizer(main_sizer)
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
frame.Show()
app.MainLoop()
It's perhaps worth noting that using a multiline, readonly wx.TextCtrl might be better than a wx.StaticText for the log.
Then, you can simply write the message, rather than reconstruct the entire statictext.
Edit
This sort of code seems to be frowned upon these days (I genuinely don't see why for small projects like this) but since you asked:
import wx
import wx.lib.scrolledpanel as scrolled
chatHistory = []
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title='Chatting with Bob', name='Local Instant Messenger')
self.sub_panel_Log = scrolled.ScrolledPanel(self)
self.sub_panel_Log.SetupScrolling()
self.sub_panel_Send = wx.Panel(self)
# Chat log
self.chat_box = wx.StaticText(self.sub_panel_Log, style=wx.TE_MULTILINE | wx.BORDER_THEME | wx.VSCROLL)
self.chat_box.SetBackgroundColour(wx.Colour(0, 0, 0, wx.ALPHA_OPAQUE))
self.chat_box.SetForegroundColour(wx.Colour(255, 255, 255, wx.ALPHA_OPAQUE))
# Message box
self.my_btn = wx.Button(self.sub_panel_Send, label='Send')
self.my_btn.Bind(wx.EVT_BUTTON, self.send_message) # Even bind
self.text_ctrl = wx.TextCtrl(self.sub_panel_Send, -1, style=wx.TE_PROCESS_ENTER | wx.TE_MULTILINE)
self.text_ctrl.Bind(wx.EVT_KEY_DOWN, self.key_code)
# sub_panel_Log Sizer
sizer_log = wx.StaticBoxSizer(wx.VERTICAL, self.sub_panel_Log)
sizer_log.Add(self.chat_box, 1, wx.EXPAND | wx.ALL, 5)
self.sub_panel_Log.SetSizer(sizer_log)
# send panel sizer
box_send = wx.BoxSizer(wx.HORIZONTAL)
box_send.Add(self.my_btn, 0, wx.ALL, 5)
box_send.Add(self.text_ctrl, 1, wx.EXPAND | wx.ALL, 5)
self.sub_panel_Send.SetSizer(box_send)
# main Sizer
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(self.sub_panel_Log, 1, wx.EXPAND | wx.ALL, 5)
main_sizer.Add(self.sub_panel_Send, 0, wx.EXPAND | wx.ALL, 5)
self.SetSizer(main_sizer)
def send_message(self, event):
msg = self.text_ctrl.GetValue()
if msg:
self.append_chat(msg)
self.text_ctrl.Clear()
def key_code(self, event):
unicodeKey = event.GetUnicodeKey()
if event.GetModifiers() == wx.MOD_SHIFT and unicodeKey == wx.WXK_RETURN:
self.text_ctrl.WriteText('\n')
# print("Shift + Enter")
elif unicodeKey == wx.WXK_RETURN:
self.send_message(event)
# print("Just Enter")
else:
event.Skip()
# print("Any other character")
def append_chat(self, message):
chatHistory.append(message)
chatHistory_Display = ''
for message in chatHistory:
chatHistory_Display += message + '\n'
self.chat_box.SetLabel(chatHistory_Display)
self.Layout()
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
frame.Show()
app.MainLoop()

Related

Do not bind a file entry in Bind to wx. Button buttons in the loop

Greetings to the respected community!
I have the following task: I need to create a panel with buttons whose names are taken from the file (all_classes). When clicking on each label, the buttons must be recorded in another file (chosen_classes). I managed to create buttons in the loop and arrange them on the panel, but the recording event to the button is not tied and I do not understand why.
all_classes = open('data/yolo2/yolo2.names', 'r').read().split()
chosen_classes = open('chosen_classes', 'w')
deltaxSize, deltaySize, c = 0, 0, 0
for k, obj_class in enumerate(all_classes):
self.buttons.append(wx.Button(self.panel, label=f'{obj_class}', pos=(50 + deltaxSize, 20 + deltaySize),
size=(100, 20)))
self.Bind(wx.EVT_BUTTON, lambda event: chosen_classes.write(f'{obj_class}\n'), self.buttons[k])
deltaySize += 20
c += 1
if c == 30:
deltaxSize += 100
deltaySize, c = 0, 0
I tried instead of recording in the lambda just prints to check what was going on, but got a strange result: when you press any button, only the last label is displayed:
deltaxSize, deltaySize, c = 0, 0, 0
self.buttons = []
for k, obj_class in enumerate(all_classes):
self.buttons.append(wx.Button(self.panel, label=f'{obj_class}', pos=(50 + deltaxSize, 20 + deltaySize),
size=(100, 20)))
self.Bind(wx.EVT_BUTTON, lambda event: print(f'{obj_class}\n'), self.buttons[k])
deltaySize += 20
c += 1
if c == 30:
deltaxSize += 100
deltaySize, c = 0, 0
The same happens if you replace obj_class in the f-line with self.buttons [k]. GetLabelText () At the same time, if you turn to each button separately outside the loop, you can print the label, but the file still does not record. I am completely, admittedly, perplexed, if anyone can suggest anything, I would be infinitely grateful. Thanks.
You have managed to over complicate your solution a bit.
The use of a sizer will make this easier and because the only event being fired is a button event and they all do the same thing, we only need to bind it once.
Pick the bones out of the following, it should help.
import wx
all_classes = ["abc","def","ghi","jkl","mno","pqr","stu","vwx","yz"]
chosen_classes = open('chosen_classes.txt', 'w')
class ButtonPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.sizer)
self.parent = parent
for k, obj_class in enumerate(all_classes):
self.add_button(obj_class)
self.Bind(wx.EVT_BUTTON, self.OnButton)
self.parent.Layout()
def add_button(self, obj_class):
self.sizer.Add(wx.Button(self, label=f'{obj_class}'), 0, wx.EXPAND, 0)
def OnButton(self,event):
obj = event.GetEventObject()
label = obj.GetLabel()
chosen_classes.write(label+" Pressed\n")
print(label)
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.button_panel = ButtonPanel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.button_panel, 0, wx.EXPAND, 0)
self.SetSizer(sizer)
class MyFrame(wx.Frame):
def __init__(self, *args):
super().__init__(*args)
panel = MyPanel(self)
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()

wxPython: pause main script and wait for button press

I am working on a GUI using wxPython. This GUI should dynamically ask to the user several sets of inputs in the same window, updating the window with the new set of inputs after a button "ok" is pressed.
To do this I have a for loop which calls a function that prompts the input controls on the window. I have tried to use the threading.Event class, letting the script wait for the button to be pressed, but python crashes.
Here below is the interested part of the code.
# Iterate through parameters
self.cv = threading.Event()
for key in parameters:
self.input_sizer = [None]*len(parameters[key])
self.cv.clear()
self.make_widgets(key, parameters[key])
self.cv.wait()
# Panel final settings
self.panel.SetSizer(self.main_sizer)
self.main_sizer.SetSizeHints(self)
def make_widgets(self, request, parameters):
# Function for widget prompting on the panel
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.controls = {"request": wx.StaticText(self.panel, label="Please type the new alias' " + request),
"txt": [None]*len(parameters),
"tc": [None]*len(parameters)}
self.main_sizer.Add(self.controls["request"], 1, wx.ALL, 10)
for i in range(len(parameters)):
self.input_sizer[i] = wx.BoxSizer(wx.HORIZONTAL)
self.main_sizer.Add(self.input_sizer[i], 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Text
self.controls['txt'][i] = wx.StaticText(self.panel, label=parameters[i])
self.input_sizer[i].Add(self.controls["txt"][i], 0, wx.ALIGN_CENTER | wx.LEFT, 10)
# Input
self.controls['tc'][i] = wx.TextCtrl(self.panel)
self.input_sizer[i].Add(self.controls["tc"][i], 0, wx.ALIGN_CENTER)
# Ok button
self.button_ok = wx.Button(self.panel, label="Ok")
self.main_sizer.Add(self.button_ok, 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
self.button_ok.Bind(wx.EVT_BUTTON, self.carry_on)
def carry_on(self, event):
self.cv.set()
Does anyone has any idea?
Thanks, here below there's the complete code.
import wx
from collections import OrderedDict
import threading
############################################################################
class AddMainName(wx.Frame):
# Class definition for main Name addition
def __init__(self, parent, title):
# Constructor
super().__init__(parent, title=title)
# Run the name addition
self.set_alias_name()
self.Centre()
self.Show()
def set_alias_name(self):
# Function for definition of alias name
# Panel
self.panel = wx.Panel(self)
# Lists of parameters to be typed
parameters = OrderedDict([("name parts", [
"Prefix", "Measurement", "Direction", "Item", "Location", "Descriptor", "Frame", "RTorigin"
]),
("SI units", [
"A", "cd", "K", "kg", "m", "mol", "Offset", "rad", "s", "ScaleFactor"
]),
("normal display unit", [
"A", "cd", "K", "kg", "m", "mol", "Offset", "rad", "s", "ScaleFactor"
]),
("other parameters", [
"Meaning", "Orientation Convention", "Contact Person", "Note",
])])
# Iterate through parameters
self.cv = threading.Event()
for key in parameters:
self.input_sizer = [None]*len(parameters[key])
self.cv.clear()
self.make_widgets(key, parameters[key])
self.cv.wait()
# Panel final settings
self.panel.SetSizer(self.main_sizer)
self.main_sizer.SetSizeHints(self)
def make_widgets(self, request, parameters):
# Function for widget prompting on the panel
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.controls = {"request": wx.StaticText(self.panel, label="Please type the new alias' " + request),
"txt": [None]*len(parameters),
"tc": [None]*len(parameters)}
self.main_sizer.Add(self.controls["request"], 1, wx.ALL, 10)
for i in range(len(parameters)):
self.input_sizer[i] = wx.BoxSizer(wx.HORIZONTAL)
self.main_sizer.Add(self.input_sizer[i], 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Text
self.controls['txt'][i] = wx.StaticText(self.panel, label=parameters[i])
self.input_sizer[i].Add(self.controls["txt"][i], 0, wx.ALIGN_CENTER | wx.LEFT, 10)
# Input
self.controls['tc'][i] = wx.TextCtrl(self.panel)
self.input_sizer[i].Add(self.controls["tc"][i], 0, wx.ALIGN_CENTER)
# Ok button
self.button_ok = wx.Button(self.panel, label="Ok")
self.main_sizer.Add(self.button_ok, 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
self.button_ok.Bind(wx.EVT_BUTTON, self.carry_on)
def carry_on(self, event):
self.cv.set()
############################################################################
class AddCommonName(wx.Frame):
# Class definition for common name addition
def __init__(self, parent, title):
# Constructor
super().__init__(parent, title=title,
size=(400, 150))
# Run the name addition
self.set_alias()
self.Centre()
self.Show()
def set_alias(self):
panel = wx.Panel(self)
sizer = wx.GridBagSizer(5, 5)
text1 = wx.StaticText(panel, label="Please type the new alias name.")
sizer.Add(text1, pos=(0, 0), flag=wx.TOP | wx.LEFT | wx.BOTTOM,
border=15)
panel.SetSizer(sizer)
############################################################################
class AliasGUI(wx.Frame):
def __init__(self, parent, title):
# Constructor
super().__init__(parent, title=title)
# Run first dialog of the GUI
self.begin()
self.Centre()
self.Show()
def begin(self):
# Panel initial settings
panel_start = wx.Panel(self)
main_sizer = wx.BoxSizer(wx.VERTICAL)
# Alias type selection
text_start = wx.StaticText(panel_start, label="Please select the type of the new alias.")
main_sizer.Add(text_start, 1, wx.ALL, 10)
# Buttons
buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
main_sizer.Add(buttons_sizer, 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
# Main name button
button_main_name = wx.Button(panel_start, label="Main Name")
buttons_sizer.Add(button_main_name, 0, wx.ALIGN_CENTER | wx.RIGHT, 10)
button_main_name.Bind(wx.EVT_BUTTON, self.main_name)
# Common name button
button_common_name = wx.Button(panel_start, label="Common Name")
buttons_sizer.Add(button_common_name, 0, wx.ALIGN_CENTER)
button_common_name.Bind(wx.EVT_BUTTON, self.common_name)
# Panel final settings
panel_start.SetSizer(main_sizer)
main_sizer.SetSizeHints(self)
#staticmethod
def main_name(event):
# Function for main name addition
frame = AddMainName(None, title="New Main Name")
frame.Centre()
frame.Show()
#staticmethod
def common_name(event):
# Function for common name addition
frame = AddCommonName(None, title="New Common Name")
frame.Centre()
frame.Show()
# GUI execution
if __name__ == '__main__':
app = wx.App()
AliasGUI(None, title="Aliases Management GUI")
app.MainLoop()
It's not really clear what you intend to do here. Your Script doesnt crash, it just waits indefinatly on the event at line 40. You did not start any additional thread which could set the event, so the other script would continue.
Also be aware that wxPython is not thread safe - so if you start adding widgets from inside another thread, you will (really) crash there.
The setup of the actual frame is not started until your __ init __ function is done. Inside this call you wait for a button press which cannot happen, as the window is yet to be created. So your script waits.
I would have posted an actual solution to your problem, but as stated above, I do not see what you intend to do here.
Edit:
As stated in the comment, I would use a dialog to ask the user for the items.
Create a Dialog Subclass:
class getDataDlg(wx.Dialog):
def __init__(self, *args, **kwargs):
self.parameters = parameters = kwargs.pop('parameters', None)
request = kwargs.pop('request', None)
assert parameters is not None
assert request is not None
wx.Dialog.__init__(self, *args, **kwargs)
self.data = {}
vsizer = wx.BoxSizer(wx.VERTICAL)
reqLbl = wx.StaticText(self, label="Please type new alias {}".format(request))
vsizer.Add(reqLbl, 1, wx.ALL, 10)
self.controls = controls = {}
for item in parameters:
hsizer = wx.BoxSizer(wx.HORIZONTAL)
parLbl = wx.StaticText(self, label=item)
hsizer.Add(parLbl, 0, wx.ALIGN_CENTER | wx.LEFT, 10)
ctrl = controls[item] = wx.TextCtrl(self)
hsizer.Add(ctrl, 0, wx.ALIGN_CENTER)
vsizer.Add(hsizer, 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
okBtn = wx.Button(self, id=wx.ID_OK)
vsizer.Add(okBtn, 1, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
self.SetSizer(vsizer)
self.Fit()
self.Layout()
okBtn.Bind(wx.EVT_BUTTON, self.saveData)
def saveData(self, event):
for item in self.parameters:
self.data[item] = self.controls[item].GetValue()
event.Skip()
Change main_name function to the following:
def main_name(self, event):
parameters = OrderedDict([("name parts", ["Prefix", "Measurement", "Direction", "Item", "Location", "Descriptor", "Frame", "RTorigin"]),
("SI units", ["A", "cd", "K", "kg", "m", "mol", "Offset", "rad", "s", "ScaleFactor"]),
("normal display unit", ["A", "cd", "K", "kg", "m", "mol", "Offset", "rad", "s", "ScaleFactor"]),
("other parameters", ["Meaning", "Orientation Convention", "Contact Person", "Note",])])
# Iterate through parameters
self.cv = threading.Event()
for itemKey in parameters:
item = parameters[itemKey]
dlg = getDataDlg(self, parameters=item, request=itemKey)
result = dlg.ShowModal()
if result == wx.ID_OK:
print("Got Result from Dialog:")
print(dlg.data)
dlg.Destroy()
Michael

wxpython HtmlWindow: show full content?

How can I display the full content of a HtmlWindow? I have some HtmlWindows in a scrollable panel and I want to see the full text in these windows. I have tried setting the proportion to 1 and the style to wx.EXPAND, but that doesn't work.
Currently it looks like this:
But I want to see in the windows the full text:
some long text
with multiple lines
and another line
Sample code:
import wx
from wx import html
from wx.lib.scrolledpanel import ScrolledPanel
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.notebook_1 = wx.Notebook(self, -1, style=0)
self.notebook_1_pane_1 = ScrolledPanel(self.notebook_1, -1)
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2 = wx.BoxSizer(wx.VERTICAL)
for _ in xrange(10):
self.html = html.HtmlWindow(self.notebook_1_pane_1)
self.html.SetPage('some long text<br />with multiple lines<br />' \
'and another line')
self.html.SetBorders(0)
self.sizer_3_staticbox = wx.StaticBox(self.notebook_1_pane_1, -1,
'a')
sizer_3 = wx.StaticBoxSizer(self.sizer_3_staticbox, wx.VERTICAL)
sizer_3.Add(self.html, 1, wx.EXPAND, 0)
sizer_2.Add(sizer_3, 0, wx.EXPAND, 0)
self.notebook_1_pane_1.SetSizer(sizer_2)
self.notebook_1.AddPage(self.notebook_1_pane_1, "tab1")
sizer_1.Add(self.notebook_1, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.notebook_1_pane_1.SetScrollRate(20, 20)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame_1 = MyFrame(None, -1, size=(400, 300))
app.SetTopWindow(frame_1)
frame_1.Show()
app.MainLoop()
You need to give sizer_2 a proportion of 1 or more as well.
sizer_2.Add(sizer_3, 1, wx.EXPAND, 0)
This makes the sizer_3 element stretch appropriately too. Otherwise, it only stretches in one direction. I would reduce the number of HTMLWindows you're putting in too unless you're using a high resolution monitor. Expanding this out so that all the text was visible in all the Windows is difficult on these low res wide screens.

how to make multiline wx.ListBox

I need a ListBox to show a phone book.Then i need to show name in top and number in bottom in each list item like phone.how to bind the datas into listbox.
now i made a listbox with singleline as shown below
cur.execute("select fname from tblsample1 order by fname")
names = [str(item[0]) for item in cur.fetchall()]
lvnames=wx.ListBox(panel,-1,(10,40),(210,180),names, wx.LB_SINGLE)
how to bind sqlite3 cursor with two columns to the listview
i need a wx.ListBox mouse click event(not EVT_LISTBOX
because i need only mouse click event)
Use the HtmlListBox, here is a little example to get you started.
import wx
class PhoneNumbers(wx.HtmlListBox):
def __init__(self, parent):
wx.HtmlListBox.__init__(self, parent)
self.data = [
("Foo", "3452-453"),
("Bar", "5672-346"),
]
self.SetItemCount(len(self.data))
def OnGetItem(self, n):
return "<b>%s</b><br>%s" % self.data[n]
def add_number(self, name, number):
self.data.append((name, number))
self.SetItemCount(len(self.data))
self.Refresh()
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size=(200, 400))
self.numbers = PhoneNumbers(self)
self.contact_name = wx.TextCtrl(self)
self.contact_number = wx.TextCtrl(self)
self.add_btn = wx.Button(self, label="Add contact")
self.Sizer = wx.BoxSizer(wx.VERTICAL)
self.Sizer.Add(self.numbers, 1, wx.EXPAND)
self.Sizer.Add(wx.SearchCtrl(self), 0, wx.EXPAND)
self.Sizer.Add(wx.StaticText(self, label="Name"), 0, wx.TOP, 10)
self.Sizer.Add(self.contact_name)
self.Sizer.Add(wx.StaticText(self, label="Number"), 0, wx.TOP, 5)
self.Sizer.Add(self.contact_number)
self.Sizer.Add(self.add_btn, 0, wx.ALL, 10)
self.numbers.Bind(wx.EVT_LISTBOX, self.OnSelectNumber)
self.add_btn.Bind(wx.EVT_BUTTON, self.OnAddNumber)
def OnSelectNumber(self, event):
name, number = self.numbers.data[event.Selection]
self.contact_name.Value = name
self.contact_number.Value = number
def OnAddNumber(self, event):
self.numbers.add_number(
self.contact_name.Value,
self.contact_number.Value
)
app = wx.PySimpleApp()
app.TopWindow = f = Frame()
f.Show()
app.MainLoop()
You should rephrase your question, I don't know if I got this right.
If you only need to display the two lines in your ListBox, you could simply use a \n:
cur.execute("select fname,number from tblsample1 order by fname")
entries = [str(item[0])+'\n'+str(item[1]) for item in cur.fetchall()]
To get a 'click' Event, you cant set the style of your wx.ListBox to wx.LC_SINGLE_SEL and catch the selection event wx.EVT_LIST_ITEM_SELECTED

Need help with wxPython, NotebookCtrl in particular

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()
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.
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)
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.

Categories

Resources