wxpython run when textctrl changes - text

I am making a simple text editor in wxpython. I would like it to be able to edit code such as python, and as such I would like to have it highlight the text in a similar manner to IDLE or Notepad++. I know how I would highlight it, but I would like the best way of running it. I don't know if it is possible but what I would really like is to run whenever a key is pressed, and not on a loop checking if it is pressed, so as to save on processing.
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(500,600))
style = wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_RICH2
self.status_area = wx.TextCtrl(self, -1,
pos=(10, 270),style=style,
size=(380,150))
self.status_area.AppendText("Type in your wonderfull code here.")
fg = wx.Colour(200,80,100)
at = wx.TextAttr(fg)
self.status_area.SetStyle(3, 5, at)
self.CreateStatusBar() # A Statusbar in the bottom of the window
# Setting up the menu.
filemenu= wx.Menu()
filemenu.Append(wx.ID_ABOUT, "&About","Use to edit python code")
filemenu.AppendSeparator()
filemenu.Append(wx.ID_EXIT,"&Exit"," 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, "Python Coder")
app.MainLoop()
If a loop is needed what would be the best way to make it loop, with a while loop, or a
def Loop():
<code>
Loop()
My new code with the added bind:
import wx
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(500,600))
style = wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_RICH2
self.status_area = wx.TextCtrl(self, -1,
pos=(10, 270),style=style,
size=(380,150))
#settup the syntax highlighting to run on a key press
self.Bind(wx.EVT_CHAR, self.onKeyPress, self.status_area)
self.status_area.AppendText("Type in your wonderfull code here.")
fg = wx.Colour(200,80,100)
at = wx.TextAttr(fg)
self.status_area.SetStyle(3, 5, at)
self.CreateStatusBar() # A Statusbar in the bottom of the window
# Setting up the menu.
filemenu= wx.Menu()
filemenu.Append(wx.ID_ABOUT, "&About","Use to edit python code")
filemenu.AppendSeparator()
filemenu.Append(wx.ID_EXIT,"&Exit"," 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)
def onKeyPress (self, event):
print "KEY PRESSED"
kc = event.GetKeyCode()
if kc == WXK_SPACE or kc == WXK_RETURN:
Line = self.status_area.GetValue()
print Line
app = wx.App(False)
frame = MainWindow(None, "Python Coder")
app.MainLoop()

In your MainWindow __init__ function add this
self.Bind(wx.EVT_CHAR, self.onKeyPress, self.status_area)
then define onKeyPress in MainWindow
def onKeyPress (self, event):
kc = event.GetKeyCode()
if kc == WXK_SPACE or kc == WXK_RETURN:
#Run your highlighting code here
Come to think of it, this might not be the most efficient way of doing code highlighting. Let me look this up. But in the meantime you can try this.
Edit:
Take a look at this - StyledTextCtrl . I think its more along the lines of what you need.

I solved this when I faced the same issue by creating a custom event.
First, I created a subclass of the TextCtrl, so I had a place in code to raise/post the custom event from:
import wx.lib.newevent
(OnChangeEvent, EVT_VALUE_CHANGED) = wx.lib.newevent.NewEvent()
class TextBox(wx.TextCtrl):
old_value = u''
def __init__(self,*args,**kwargs):
wx.TextCtrl.__init__(self,*args,**kwargs)
self.Bind(wx.EVT_SET_FOCUS, self.gotFocus) # used to set old value
self.Bind(wx.EVT_KILL_FOCUS, self.lostFocus) # used to get new value
def gotFocus(self, evt):
evt.Skip()
self.old_value = self.GetValue()
def lostFocus(self, evt):
evt.Skip()
if self.GetValue() != self.old_value:
evt = OnChangeEvent(oldValue=self.old_value, newValue=self.GetValue())
wx.PostEvent(self, evt)
Now, in my frame's code, here is a snippet of me Binding the event, and using it.
summ_text_ctrl = TextBox(self, -1, size=(400, -1))
summ_text_ctrl.Bind(EVT_VALUE_CHANGED, self.onTextChanged)
def OnTextChanged(self, evt):
evt.Skip()
print('old Value: %s' % evt.oldValue )
print('new Value: %s' % evt.newValue )

Related

TkInter - Can't Get Frames to work correctly and resize

TkInter's frames are driving me crazy. My goal is to have an options frame where I can select some options, then press "Archive" and the TkInter window changes to showing the output from the rest of my script.
I cannot get this to size correctly - there appears to be some additional frame taking up space in the window.
import string
from tkinter import *
import tkinter as tk
import threading
def main(argv):
print("In Main")
for arg in argv:
print(arg)
class TextOut(tk.Text):
def write(self, s):
self.insert(tk.CURRENT, s)
self.see(tk.END)
def flush(self):
pass
class Mainframe(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = OptionsFrame(self)
self._frame.pack(expand=True)
def change(self, frameClass):
# make new frame - for archive output
self._frame = frameClass(self)
self._frame.pack(fill="both", expand=True)
return self._frame
class Mainframe(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = OptionsFrame(self)
self._frame.pack(expand=True)
def change(self, newFrameClass):
# make new frame - for archive output
self._frame = newFrameClass(self)
self._frame.pack(fill="both", expand=True)
return self._frame
class OptionsFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
master.title("Test")
master.geometry("325x180")
self.selectedProject = None
self.initUI(master)
def initUI(self, master):
frame1 = Frame(master)
frame1.pack(fill=BOTH, expand=True)
self.label1 = Label(frame1, text="Select Project to Archive, then click Archive")
self.projectListbox = tk.Listbox(frame1, width=60, height=100)
self.projectListbox.bind("<<ProjectSelected>>", self.changeProject)
# create a vertical scrollbar for the listbox to the right of the listbox
self.yscroll = tk.Scrollbar(self.projectListbox,command=self.projectListbox.yview,orient=tk.VERTICAL)
self.projectListbox.configure(yscrollcommand=self.yscroll.set)
# Archive button
self.archiveBtn=tk.Button(frame1,text="Archive",command=self.ArchiveButtonClick)
# Do layout
self.label1.pack()
self.projectListbox.pack(fill="both", expand=True)
self.yscroll.pack(side="right", fill="y")
self.archiveBtn.pack(side="bottom", pady=10, expand=False)
choices = ["test 1", "test 2", "test 3", "test 4", "test 5", "test 6"]
# load listbox with sorted data
for item in choices:
self.projectListbox.insert(tk.END, item)
def getSelectedProject(self):
# get selected line index
index = self.projectListbox.curselection()[0]
# get the line's text
return self.projectListbox.get(index)
# on change dropdown value
def changeProject(self,*args):
self.selectedProject = self.getSelectedProject()
def ArchiveButtonClick(self):
# Switch to second frame - for running the archive
self.changeProject(None)
# Hide existing controls
self.label1.pack_forget()
self.projectListbox.pack_forget()
self.yscroll.pack_forget()
self.archiveBtn.pack_forget()
newFrame = self.master.change(ArchivingOutputFrame)
newFrame.args = [ "-n", self.selectedProject]
newFrame.start()
# Frame shown while archive task is running
class ArchivingOutputFrame(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
master.title("Test Frame 2")
master.geometry("1000x600")
# Set up for standard output in window
self.var = tk.StringVar(self)
lbl = tk.Label(self, textvariable=self.var)
#lbl.grid(row=0, column=0)
lbl.pack(anchor="nw")
def start(self):
t = threading.Thread(target=self.process)
t.start()
def process(self):
main(self.args)
if __name__=="__main__":
# If command line options passed - skip the UI
if len(sys.argv) > 1:
main(sys.argv[1:])
else:
app=Mainframe()
text = TextOut(app)
sys.stdout = text
sys.stderr = text
text.pack(expand=True, fill=tk.BOTH)
app.mainloop()
Here is what I get in the UI; note this is showing the UI hierachy from Microsoft's Spy++ - there is a frame I didn't create (at least I don't think I did) that is at the bottom of the window and taking up half of the UI area; this is the yellow highlight. My options pane is thus squeezed into the top half.
Resize also doesn't work - if I resize the window, I get this:
When I click the button and the code to remove the options frame and put in the frame that is capturing stdout/stderr from the main script runs, I get this:
Now the extra space appears to be at the top!
Thanks for any ideas - I know I could switch to using the "Grid" UI layout engine, but this seems so simple - I'm not doing anything sophisticated here that shouldn't work with pack.
That was a lot of complicated code. It would be easier to debug if you provide a Minimal, Complete, and Verifiable example.
However; the bottom Frame is the TextOut() widget that you pack after Mainframe():
if __name__=="__main__":
app = Mainframe()
text = TextOut(app) # This one
sys.stdout = text
sys.stderr = text
text.pack(expand=True, fill=tk.BOTH)
app.mainloop()
You'll have an easier time debugging if you give each widget a bg colour and then give them all some padding so you can easier identify which widget is inside which widget.

How can I execute a child python scriptby clicking on a button on a parent script?

I am currently training OOP for GUI. I am using the wxPython library to create my windows and customize them.
Right now, I am trying to launch a python script by clicking on a button from an other script.
For that, I have 2 programs, wx_Practicing.py and wx_Practicing_child.py which are in the same folder.
wx_Practicing.py
import wx
import time
import wx_Practicing_child
import threading
import os
import sys
class MainWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Test",
wx.DefaultPosition,(1000,850), wx.DEFAULT_FRAME_STYLE, wx.FrameNameStr)
# Click counter and flag variable for the new frame opened
self.click = 0
self.OpenButtonFlag = 0
# Sizer to definit the position of elements
sizer_hori = wx.BoxSizer(wx.HORIZONTAL)
sizer_verti = wx.BoxSizer(wx.VERTICAL)
# Panel
test_panel = PanelMainWindow(self)
test_panel.SetSizer(sizer_verti)
# Button to close the main frame and end the program
btn_quit = wx.Button(test_panel, label ="Quit")
btn_quit.Bind(wx.EVT_BUTTON, self.OnQuit)
sizer_verti.Add(btn_quit)
# Button which displays the number of click done on it since the
# frame is opened
btn_counter = wx.Button(test_panel, label="Click counter")
sizer_verti.Add(btn_counter)
btn_counter.Bind(wx.EVT_LEFT_DOWN, self.OnCount)
# Button which open the child frame from wx_Practicing_child.py
btn_new_frame = wx.Button(test_panel, label = "Open new frame")
sizer_verti.Add(btn_new_frame)
btn_new_frame.Bind(wx.EVT_LEFT_DOWN, self.OnNewFrame)
self.Show()
# Method to quit the frame and close it
def OnQuit(self, event):
self.Close()
#Method to count clicks
def OnCount(self, event):
self.click +=1
print(self.click)
# MEthod which open the child frame
def OnNewFrame(self, event):
if self.OpenButtonFlag == 0 :
print('aaaaaaaa')
os.system('wx_Practicing_child.py')
self.Show()
print("New frame opened")
self.OpenButtonFlag = 1
else :
print("Frame already launched, close it before opening a new one")
class PanelMainWindow(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
test = wx.App(False)
frame = MainWindow()
test.MainLoop()
wx_Practicing_child.py
import wx
class MainWindow_child(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Test",
wx.DefaultPosition, (1000,850), wx.DEFAULT_FRAME_STYLE, wx.FrameNameStr)
self.OpenButtonFlag = 0
# Sizer
sizer_hori = wx.BoxSizer(wx.HORIZONTAL)
sizer_verti = wx.BoxSizer(wx.VERTICAL)
# Panel
test_panel_child = PanelMainWindow_child(self)
test_panel_child.SetSizer(sizer_verti)
# Button to quit the child frame
btn_quit = wx.Button(test_panel_child, label ="Quit")
btn_quit.Bind(wx.EVT_BUTTON, self.OnQuit)
sizer_verti.Add(btn_quit)
self.Show()
# Method used to close the child frame
def OnQuit(self, event):
self.OpenButtonFlag = 0
self.Close()
class PanelMainWindow_child(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
So basically, the functionning is simple. I launch wx_Practicing.py to open the parent window, then I click on the "Open new frame" button and the child frame from wx_Practicing_child.py appears. If i trigger the button again, it does nothing until I closed the previous child window.
But when I try it, the buttonFlag is set to 1, so it enters in the loop, but the child frame does not appear.
So I would like to know how could I make it work. I am out of option right now.
Thank you.
Welcome to StackOverflow.
The problem is that you are creating the child frame in the wrong way.
You only need to change the line:
os.system('wx_Practicing_child.py')
for:
child = wx_Practicing_child.MainWindow_child()

Python 3 Wx Change tray icon

I'm using WxPython (phoenix new version) to have an icon on the tray bar while the programs runs, but I would some events to change the icon used.
I found this great example to get things started: Quick and easy: trayicon with python?
But it doesn't have the example to cycle through icons.
Just after the imports it has this line:
TRAY_ICON = 'icon.png'
And I've tried to use that as sort of a variable, and adding the following line to an event (it has some mock events like hellow world on a right click
TRAY_ICON = 'icon2.png'
but it didn't work =//
I only found examples in c, and one in python but using a very complex win32 alternative that I can't figure out
This should provide you with enough to solve your problem.
import wx
import wx.adv
ICON = 'toggle1.png'
ICONS = ["toggle1.png", "toggle2.png"]
X=[1,0]
class TaskBarIcon(wx.adv.TaskBarIcon):
def __init__(self, frame):
self.frame = frame
self.toggle = 0
wx.adv.TaskBarIcon.__init__(self)
self.Bind(wx.adv.EVT_TASKBAR_LEFT_DOWN, self.OnToggle)
self.OnSetIcon(ICON)
def CreatePopupMenu(self):
menu = wx.Menu()
togglem = wx.MenuItem(menu, wx.NewId(), 'Toggle Icon')
menu.Bind(wx.EVT_MENU, self.OnToggle, id=togglem.GetId())
menu.Append(togglem)
menu.AppendSeparator()
flashm = wx.MenuItem(menu, wx.NewId(), 'Flash Icon')
menu.Bind(wx.EVT_MENU, self.OnTimer, id=flashm.GetId())
menu.Append(flashm)
menu.AppendSeparator()
quitm = wx.MenuItem(menu, wx.NewId(), 'Quit')
menu.Bind(wx.EVT_MENU, self.OnQuit, id=quitm.GetId())
menu.Append(quitm)
return menu
def OnSetIcon(self, path):
icon = wx.Icon(path)
self.SetIcon(icon, path)
def OnToggle(self, event):
self.toggle=X[self.toggle]
use_icon = ICONS[self.toggle]
self.OnSetIcon(use_icon)
def OnTimer(self,event):
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnInUseTimer)
self.timer.Start(1000)
def OnInUseTimer(self,event):
self.OnToggle(None)
def OnQuit(self, event):
self.RemoveIcon()
wx.CallAfter(self.Destroy)
self.frame.Close()
if __name__ == '__main__':
app = wx.App()
frame=wx.Frame(None)
TaskBarIcon(frame)
app.MainLoop()
and the images:
In action:

How to interactively add widgets using QAction in Pyside/Pyqt without using layout management?

I'm trying to make a tool window for Maya, in which I can right-click anywhere, and if I click 'add', a rectangle widget shows up at my cursor position.
Now my right-click functionality works. I can also get my cursor position in addPicker() function. But I am having problem with placing newly-created widgets. If I add a layout and add the newly-created widgets to it, they actually show up. However, if I didn't create a layout for those widgets, no matter what position I tested, nothing shows up in my window.
Hopefully someone has some ideas. Thank you all in advance.
A right-click screenshot:
class RightClickMenu(QtGui.QMenu):
def __init__(self, *args, **kwargs):
super(RightClickMenu, self).__init__(*args)
self.parentWidget().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.parentWidget().customContextMenuRequested.connect(self.menuPos)
def menuPos(self, *args):
self.exec_(QtGui.QCursor.pos())
class Ui_window(object):
def setupUi(self, window):
window.setObjectName("window")
window.resize(555, 900)
self.widget_base = QtGui.QWidget()
self.verticalLayout_window = QtGui.QVBoxLayout(window)
self.verticalLayout_window.addWidget(self.widget_base)
self.menu_popup = RightClickMenu(self.widget_base)
self.menu_popup.setObjectName("popupMenu")
self.verticalLayout_widget = QtGui.QVBoxLayout(self.widget_base)
# Action - add picker
addAction = QtGui.QAction('Add Picker', self.widget_base)
addAction.setShortcut('Ctrl+A')
addAction.setStatusTip('Add Picker')
addAction.triggered.connect(self.addPicker)
self.menu_popup.addAction(addAction)
# Action - delete picker
deleteAction = QtGui.QAction('Delete Picker', self.widget_base)
deleteAction.setShortcut('Ctrl+D')
deleteAction.setStatusTip('Delete Picker')
deleteAction.triggered.connect(self.deletePicker)
self.menu_popup.addAction(deleteAction)
def addPicker(self):
cursorPos = QtGui.QCursor.pos()
localPos = self.widget_base.mapFromGlobal(cursorPos)
######################################################################
# how??? below doesn't work.
self.pushButton = QtGui.QPushButton(self.widget_base)
self.pushButton.setGeometry(QtCore.QRect(220, 50, 75, 23))
self.pushButton.setObjectName("pushButton")
def deletePicker(self):
print 'delete'
def run():
import sys
try:
Ui_window.close()
except:
pass
pickerWindow = QtGui.QDialog()
ui = Ui_window()
ui.setupUi(pickerWindow)
pickerWindow.show()
pickerWindow.exec_()
Surprising solution (see this question):
self.pushButton.show()

wxpython - Menu interaction

I'm having trouble with a menu system - I have a basic example here (below) that shows a basic menu example I've followed; specifically, my issue is how may I make decisions from use menu choices, I'm not sure how to interact user choice with a menu choice?
Could someone point me in the right direction, or ideally give a brief example on this - say input data from a menu and display this?
Thanks
import wx
class myFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id,'Menu', size=(300,200))
panel = wx.Panel(self)
status = self.CreateStatusBar()
menubar = wx.MenuBar()
firstMenu = wx.Menu()
secondMenu = wx.Menu()
# create files
firstMenu.Append(wx.NewId(), 'Save Data' , 'Save data')
firstMenu.Append(wx.NewId(), 'Open Data..', 'Open a new window')
secondMenu.Append(wx.NewId(),'Configure..', 'Input Data here')
# append to menu
menubar.Append(firstMenu, 'File')
menubar.Append(secondMenu,'Options')
#
self.SetMenuBar(menubar)
if( __name__ == '__main__' ):
app = wx.PySimpleApp()
frame = myFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
You have to bind wx.EVT_MENU event. See wxPython demo for more examples. In your case that would be somethink like:
import wx
SAVE_DATA = wx.NewId()
OPEN_DATA = wx.NewId()
CONFIGURE = wx.NewId()
class myFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id,'Menu', size=(300,200))
panel = wx.Panel(self)
status = self.CreateStatusBar()
menubar = wx.MenuBar()
firstMenu = wx.Menu()
secondMenu = wx.Menu()
# create files
firstMenu.Append(SAVE_DATA, 'Save Data' , 'Save data')
firstMenu.Append(OPEN_DATA, 'Open Data..', 'Open a new window')
secondMenu.Append(CONFIGURE,'Configure..', 'Input Data here')
# append to menu
menubar.Append(firstMenu, 'File')
menubar.Append(secondMenu,'Options')
#
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.SaveData, id=SAVE_DATA)
self.Bind(wx.EVT_MENU, self.OpenData, id=OPEN_DATA)
self.Bind(wx.EVT_MENU, self.Configure, id=CONFIGURE)
def SaveData(self, e):
print("Save")
def OpenData(self, e):
print("Open")
def Configure(self, e):
print("Config")
if( __name__ == '__main__' ):
app = wx.PySimpleApp()
frame = myFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()

Resources