How to refresh listctrl simultaneously when using setstringitem function - python

I'm so troubled by this problem:
I've create a ListCtrl object, a TextCtrl object, and a button. First I fill some data into the ListCtrl object, when I press the button, it will append some strings into TextCtrl object and use SetStringItem to modify ListCtrl object.
As you can see in the button function, I've added time.sleep(2) in each loop. When I've got is when the button is pressed, TextCtrl will be refreshed every time the strings is inserted, but ListCtrl just freeze until the LOOP IS FINISHED, then it will display the correct strings.
I want to know how to refresh the ListCtrl object as soon as SetStringItem is called.
Any help is deeply appreciated.
Here is the code:
import wx
import sys
import time
class Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, size=(450, 450))
self.panel = wx.Panel(self)
self.dl = wx.ListCtrl(self,-1,size=(300,100),style=wx.LC_REPORT)
self.dl.InsertColumn(0, 'File')
self.dl.InsertColumn(1, 'Progress')
self.dl.InsertColumn(2, 'State')
for row in range(3):
labels = [l+str(row) for l in "FILE PERCENT STATE".split()]
# sys.maxint inserts at the end of the list
index = self.dl.InsertStringItem(sys.maxint, labels[0])
self.dl.SetStringItem(index, 1, labels[1])
self.dl.SetStringItem(index, 2, labels[2])
self.Show(True)
button2 = wx.Button(self, label=u"test", pos=(15, 200), size=(60, 25))
self.Bind(wx.EVT_BUTTON, self.test, button2)
self.text = wx.TextCtrl(self, -1, pos=(80, 200), size=(200, 175), style=wx.TE_MULTILINE)
def test(self,event):
for i in range(3):
self.dl.SetStringItem(i,1,"HELLO")
self.text.AppendText("HELLO")
time.sleep(2)
app = wx.App()
Frame(None)
app.MainLoop()

The problem is that time.sleep blocks your GUI, what you will need to do to get the effect that you are trying for is:
On your button press add the first item & start a 2 second wx.Timer with an event handler/
In the event handler add the next string or if there are no more to add cancel the timer.

I've changed my code to this, and it works, thanks steve
import wx
import sys
import time
class Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, size=(450, 450))
self.panel = wx.Panel(self)
self.dl = wx.ListCtrl(self,-1,size=(300,100),style=wx.LC_REPORT)
self.dl.InsertColumn(0, 'File')
self.dl.InsertColumn(1, 'Progress')
self.dl.InsertColumn(2, 'State')
for row in range(3):
labels = [l+str(row) for l in "FILE PERCENT STATE".split()]
# sys.maxint inserts at the end of the list
index = self.dl.InsertStringItem(sys.maxint, labels[0])
self.dl.SetStringItem(index, 1, labels[1])
self.dl.SetStringItem(index, 2, labels[2])
self.Show(True)
self.timer = wx.Timer(self,-1)
#self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.Bind(wx.EVT_TIMER, self.test1, self.timer)
button2 = wx.Button(self, label=u"test", pos=(15, 200), size=(60, 25))
self.Bind(wx.EVT_BUTTON, self.test, button2)
self.text = wx.TextCtrl(self, -1, pos=(80, 200), size=(200, 175), style=wx.TE_MULTILINE)
self.z=0
def test(self,event):
self.timer.Start(3000)
def test1(self,event):
for i in range(1):
self.dl.SetStringItem(self.z,1,"HELLO")
self.text.AppendText("HELLO")
self.z+=1
if self.z >2 :
self.timer.Stop()
app = wx.App()
Frame(None)
app.MainLoop()

Related

Wx.stc.StyledTextCtrl ScrollBar

In my project, I use wx.stc.StyledTextCtrl(). I bind the events of key down and key up. When I want to add a letter into the TextCtrl, I don’t skip the event, in some reasons, I use the method AddText() in order to add a text. When the text is long and the ScrollBar (of the width of the screen) is opened I want that ScrollBar will be at the position where I can see the letter that is added (will move automatically as it should be). currently the ScrollBar always stays at the left side of the screen.
I'm searching for a function that can do that.
when the letters are over the width of the TextCtrl (over pos 300) the ScrollBar is still doesn't move. I want that it will be like the messageTxt in the right side of the frame.
Here is a basic code that present my problem:
import wx
import wx.stc
def on_key_down(event):
pass
def on_key_up(event):
key_code = event.GetKeyCode()
messageTxt.AddText(chr(key_code))
app = wx.App()
frame = wx.Frame(None, -1, title='2', pos=(0, 0), size=(500, 500))
frame.Show(True)
messageTxt = wx.stc.StyledTextCtrl(frame, id=wx.ID_ANY, pos=(0, 0), size=(300, 300),
style=wx.TE_MULTILINE, name="File")
messageTxt.Bind(wx.EVT_KEY_DOWN, on_key_down)
messageTxt.Bind(wx.EVT_KEY_UP, on_key_up)
messageTxt2 = wx.stc.StyledTextCtrl(frame, id=wx.ID_ANY, pos=(320, 0), size=(150, 150),
style=wx.TE_MULTILINE, name="File")
app.SetTopWindow(frame)
app.MainLoop()
Clearly another event is ocurring after the key event, which is being missed.
Use event.Skip() in the functions bound to a key event.
Skip(self, skip=True)
This method can be used inside an event handler to control whether further event handlers bound to this event will be called after the current one returns.
Without Skip (or equivalently if Skip(false) is used), the event will not be processed any more. If Skip(true) is called, the event processing system continues searching for a further handler function for this event, even though it has been processed already in the current handler.
In general, it is recommended to skip all non-command events to allow the default handling to take place. The command events are, however, normally not skipped as usually a single command such as a button click or menu item selection must only be processed by one handler.
import wx
import wx.stc
def on_key_down(event):
event.Skip()
pass
def on_key_up(event):
key_code = event.GetKeyCode()
messageTxt.AddText(chr(key_code))
event.Skip()
app = wx.App()
frame = wx.Frame(None, -1, title='2', pos=(0, 0), size=(500, 500))
frame.Show(True)
messageTxt = wx.stc.StyledTextCtrl(frame, id=wx.ID_ANY, pos=(0, 0), size=(300, 300),
style=wx.TE_MULTILINE, name="File")
messageTxt.Bind(wx.EVT_KEY_DOWN, on_key_down)
messageTxt.Bind(wx.EVT_KEY_UP, on_key_up)
messageTxt2 = wx.stc.StyledTextCtrl(frame, id=wx.ID_ANY, pos=(320, 0), size=(150, 150),
style=wx.TE_MULTILINE, name="File")
app.SetTopWindow(frame)
app.MainLoop()
I've added a different answer because the first issue is still valid, just not for your question, as I interpret it.
I assume this is a mock-up of your problem, if it isn't, let me know and I'll delete it.
The "server updates" are simulated by a timer, the character is added, then we simply move the cursor 1 character to the right.
import wx
import wx.stc
server_sends=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','T','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','T']
class MyFrame(wx.Frame):
def __init__(self, parent, id=wx.ID_ANY, title=""):
super(MyFrame, self).__init__(parent, id, title)
self.SetSize((500,500))
self.panel = wx.Panel(self, -1 , size=(500,500))
self.messageTxt = wx.stc.StyledTextCtrl(self.panel, id=wx.ID_ANY, pos=(0, 0), size=(300, 300),
style=wx.TE_MULTILINE, name="File")
self.messageTxt2 = wx.stc.StyledTextCtrl(self.panel, id=wx.ID_ANY, pos=(320, 0), size=(150, 150), style=wx.TE_MULTILINE, name="File")
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(500)
self.cnt = 0
self.Show()
def OnTimer(self, event):
print (server_sends[self.cnt])
self.Server_Update(server_sends[self.cnt])
self.cnt += 1
if self.cnt > len(server_sends) - 1 :
self.timer.Stop()
def Server_Update(self,char):
self.messageTxt.AddText(char)
self.messageTxt.CharRight()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame(None,title="The Main Frame")
app.MainLoop()

wxSuperToolTip changes position on SetMessage

I'm creating a wx.agw.SuperToolTip. I'm updating the message in the tip every few seconds, and if the tip is showing when the message updates the tip is redrawn in a different position.
The new position seems to be relative to the original position's relation to the top left corner of the screen, but that could just be coincidence.
Also, if I modify wx.lib.agw.supertooltip.ToolTipWindowBase.Invalidate() by commenting out the call to self.CalculateBestSize() the problem goes away. Of course then the window won't resize, so that's no solution.
I'm using wxPython 2.8.12.1.
Here's an app that demonstrates the problem:
class MyFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title,
pos=(150, 150), size=(350, 225))
panel = wx.Panel(self)
btn = wx.Button(panel, -1, "Hover over this")
self._superTip = SuperToolTip("")
self._superTip.SetHeader("Heyo!")
self._superTip.SetTarget(btn)
self._superTip.EnableTip(True)
self._superTip.SetDrawHeaderLine(True)
self._superTip.SetDrawFooterLine(True)
self._superTip.SetStartDelay(1)
self._superTip.SetEndDelay(60)
currentFooterFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
currentFooterFont.SetPointSize(6)
currentFooterFont.SetWeight(wx.NORMAL)
self._superTip.SetFooterFont(currentFooterFont)
self._superTip.SetFooter('(Click to close)')
self._superTip.ApplyStyle("Blue Glass")
self._superTip.SetDropShadow(True)
self.ttTimer = wx.Timer(self)
self.ttText = 'What the?'
self.Bind(wx.EVT_TIMER, self.onTimer, self.ttTimer)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL, 10)
panel.SetSizer(sizer)
self.ttTimer.Start(2000)
panel.Layout()
def onTimer(self, evt):
self._superTip.SetMessage(self.ttText)
self.ttText += '?'
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, "STT error demo")
self.SetTopWindow(frame)
frame.Show(True)
return True
app = MyApp(redirect=True)
app.MainLoop()
Any thoughts on how I can update a visible tooltip without its location changing?
Thanks a lot.

wxPython, Getting input from TextCtrl box to send to Notepad

I am trying to create a simple invoice program for a school project. I have the basic layout of my program.
The large text boxes on the left are for the invoice, and the ones on the right are for the price of that input.
I want the text boxes to return the input into them, and assign it to say JobOne. The next step is that I need these values to be send to a file in Notepad when the 'Send To Invoice' button is clicked.
I am really stuck here, I've tried so many different combinations of things, and I'm forever getting "TextCtrl" object is not callable.
Any help would be greatly appreciated.
I've taken out my messy attempts to get the problem working and stripped it down to its barebones.
import wx
class windowClass(wx.Frame):
def __init__(self, *args, **kwargs):
super(windowClass, self).__init__(*args, **kwargs)
self.basicGUI()
def basicGUI(self):
panel = wx.Panel(self)
self.SetSizeWH(1200, 800)
menuBar = wx.MenuBar()
fileButton = wx.Menu()
exitItem = fileButton.Append(wx.ID_EXIT, 'Exit', 'status msg...')
menuBar.Append(fileButton, 'File')
self.SetMenuBar(menuBar)
self.Bind(wx.EVT_MENU, self.Quit, exitItem)
yesNoBox = wx.MessageDialog(None, 'Do you wish to create a new invoice?',
'Create New Invoice?', wx.YES_NO)
yesNoAnswer = yesNoBox.ShowModal()
yesNoBox.Destroy()
nameBox = wx.TextEntryDialog(None, 'What is the name of the customer?', 'Customer Name'
, 'Customer Name')
if nameBox.ShowModal() ==wx.ID_OK:
CustomerName = nameBox.GetValue()
wx.TextCtrl(panel, pos=(10, 10), size=(500,100))
wx.TextCtrl(panel, pos=(550, 10), size=(60,20))
wx.TextCtrl(panel, pos=(10, 200), size=(500,100))
wx.TextCtrl(panel, pos=(550, 200), size=(60,20))
wx.TextCtrl(panel, pos=(10, 400), size=(500,100))
wx.TextCtrl(panel, pos=(550, 400), size=(60,20))
self.SetTitle('Invoice For ' +CustomerName)
SendToNotepadButton = wx.Button(panel, label='Convert to invoice',pos=(650, 600), size=(120, 80))
def SendToNotepad(e):
f = open("Notepad.exe", 'w')
f.write(())
call(["Notepad.exe", "CustomerInvoice"])
self.Bind(wx.EVT_BUTTON, SendToNotepad)
self.Show(True)
def Quit(self, e):
self.Close()
def main():
app = wx.App()
windowClass(None)
app.MainLoop()
main()
If you manage to help me, I thank you!
This is actually fairly easy. I skipped the MessageDialog stuff and just put together a proof of concept. This worked for me on my Windows 7 box with Python 2.7 and wxPython 3.0.2:
import subprocess
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.txt = wx.TextCtrl(self)
notepad_btn = wx.Button(self, label="Send to Invoice")
notepad_btn.Bind(wx.EVT_BUTTON, self.onSendInvoice)
my_sizer = wx.BoxSizer(wx.VERTICAL)
my_sizer.Add(self.txt, 0, wx.EXPAND|wx.ALL, 5)
my_sizer.Add(notepad_btn, 0, wx.CENTER|wx.ALL, 5)
self.SetSizer(my_sizer)
#----------------------------------------------------------------------
def onSendInvoice(self, event):
""""""
txt = self.txt.GetValue()
print txt
# write to file
with open("invoice.txt", 'w') as fobj:
fobj.write(txt)
# open Notepad
subprocess.Popen(['notepad.exe', 'invoice.txt'])
########################################################################
class MainWindow(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Jobs")
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainWindow()
app.MainLoop()
Hopefully this code will help you see how to put it all together.

WxPython using Listbox and other UserInput with a Button

I am trying to create a web crawler based on specific user input. For example, the User Input I am trying to receive is from a ListBox and a text field. Once I have that information, I would like the user to click a button to start the search with the information collected.
This is where I have been getting problems. The EVT function doesn't recognize the listbox since its been linked to the Button evt. Is there a way to solve the problem? Can EVT information be shared with other EVTs?
Here is what I have so far:
import wx # for gui
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
tournLevel = ['$10,000', '$15,000', '$20,000', '$50,000','$75,000','$100,000']
levelBox = wx.ListBox(panel, -1, (40, 50), (90, 90), tournLevel)
levelBox.SetSelection(1) # default selection
checkButton = wx.Button(panel, label= "Check Now", pos = (150, 50), size = (90, 40))
self.Bind(wx.EVT_BUTTON, self.OnClick, checkButton)
def OnClick(self, event):
currLevel = event.GetSelection()
print(currLevel) # to test if GetSelection is working
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
I would be very happy if I could just get the button to recognize the ListBox results.
Thank you for your time!
You can just grab it from the listbox, you don't need it from the event. See below:
import wx # for gui
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
tournLevel = ['$10,000', '$15,000', '$20,000', '$50,000','$75,000','$100,000']
self.levelBox = wx.ListBox(panel, -1, (40, 50), (90, 90), tournLevel)
self.levelBox.SetSelection(1) # default selection
self.checkButton = wx.Button(panel, label= "Check Now", pos = (150, 50), size = (90, 40))
self.Bind(wx.EVT_BUTTON, self.OnClick, self.checkButton)
def OnClick(self, event):
currLevel = self.levelBox.GetSelection()
print(currLevel) # to test if GetSelection is working
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
More specifically, if you store levelBox as self.levelBox, it will be accessible inside the OnClick method as a MyFrame attribute. You can then use the GetSelection method for this object (not the event), which will get the current selection.
You can make levelBox into a property of the class by turning it into self.levelBox and accessing it that way as #brettb mentioned. However you can get a bit sneakier and do it using a lambda for your callback to pass the Listbox widget to the event handler:
import wx # for gui
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
panel = wx.Panel(self)
tournLevel = ['$10,000', '$15,000', '$20,000', '$50,000','$75,000','$100,000']
levelBox = wx.ListBox(panel, -1, (40, 50), (90, 90), tournLevel)
levelBox.SetSelection(1) # default selection
checkButton = wx.Button(panel, label= "Check Now", pos = (150, 50), size = (90, 40))
evt = lambda caller, widget=levelBox: self.OnClick(caller, widget)
checkButton.Bind(wx.EVT_BUTTON, evt)
def OnClick(self, event, widget):
currLevel = widget.GetSelection()
print(currLevel) # to test if GetSelection is working
print widget.GetString(currLevel)
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
Also note that you didn't have panel defined, so your original code doesn't work. See the following for more information:
http://wiki.wxpython.org/Passing%20Arguments%20to%20Callbacks

wxpython textctrl line and column

I am writing a text editor using wxpython. I want to display the line & column numbers based on the position of the text pointer(when changed using keyboard or mouse), in the statusbar.
I tried to do it using the below code:
import wx
class TextFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Text Editor', size=(300, 250))
self.panel = wx.Panel(self, -1)
self.multiText = wx.TextCtrl(self.panel, -1,"",size=(200, 100), style=wx.TE_MULTILINE|wx.EXPAND)
sizer = wx.BoxSizer()
sizer.Add(self.multiText, proportion=1, flag=wx.CENTER|wx.EXPAND)
self.panel.SetSizer(sizer)
self.CreateStatusBar()
self.multiText.Bind(wx.EVT_KEY_DOWN, self.updateLineCol)
self.multiText.Bind(wx.EVT_LEFT_DOWN, self.updateLineCol)
def updateLineCol(self, event):
#lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
self.StatusBar.SetStatusText(str(l)+","+str(c), number=0)
event.Skip()
app = wx.App(False)
frame = TextFrame()
frame.Show()
app.MainLoop()
I tried the below 2 ways to achieve that:
1.) lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
2.) l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
But, both, always give the line & column values of previous position of the pointer instead of current.
Is there a way to display the current line & column values?
I think you just need to bind to EVT_KEY_UP instead of EVT_KEY_DOWN. That way the event handler isn't called until after you have finished typing. I also updated the content written to the statusbar to make it a bit clearer which value was which:
import wx
class TextFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Text Editor', size=(300, 250))
self.panel = wx.Panel(self, -1)
self.multiText = wx.TextCtrl(self.panel, -1,"",size=(200, 100), style=wx.TE_MULTILINE|wx.EXPAND)
sizer = wx.BoxSizer()
sizer.Add(self.multiText, proportion=1, flag=wx.CENTER|wx.EXPAND)
self.panel.SetSizer(sizer)
self.CreateStatusBar()
self.multiText.Bind(wx.EVT_KEY_UP, self.updateLineCol)
self.multiText.Bind(wx.EVT_LEFT_DOWN, self.updateLineCol)
def updateLineCol(self, event):
#lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
stat = "col=%s, row=%s" % (l,c)
self.StatusBar.SetStatusText(stat, number=0)
event.Skip()
app = wx.App(False)
frame = TextFrame()
frame.Show()
app.MainLoop()

Categories