I have a shell-like console for my app, which prompts with ">>>" after every command. The problem is every time I have my shell WriteText(">>> "), it also appends a new line. The user can backspace up to the correct line, but this just looks terrible. Any way to fix it?
I suspect you are declaring your TextCtrl to be of style wx.TE_PROCESS_ENTER and then binding the EVT_TEXT_ENTER event - only because I ran into the same problem when I tried that.
My first instinct was to write a method to process wx.EVT_TEXT_ENTER that would then use the TextCtrl:Remove() method. This method only seems to remove visible characters, unfortunately. My next thought was to use the EmulateKeyPress() method along with backspace (WKX_BACK) to erase the newline character. This might be doable though I couldn't come up with a good way to spoof a wx.KeyEvent (can't just use event.m_keyCode since EVT_TEXT_ENTER sends a CommandEvent, and not a KeyEvent) so I gave up on that approach... err, I mean this solution is left as an exercise to the reader.
wx.EVT_TEXT_ENTER being a CommandEvent finally led to a third angle which did work. Instead of binding wx.EVT_TEXT_ENTER, I bound wx.EVT_CHAR and put in logic with special processing for the Return key (ASCII key code 13). I then planned to implement the EmulateKeyPress() bit I talked about earlier but when I removed wx.TE_PROCESS_ENTER from the TextCtrl style, I discovered that the \n was no longer being surreptitiously added. Code follows:
import wx
class TestRun(wx.Frame):
def __init__(self,parent):
wx.Frame.__init__(self, parent, title="StackO Test", size=(400,400))
self.control = wx.TextCtrl(self, id=wx.ID_ANY, style=wx.TE_MULTILINE)
self.control.Bind(wx.EVT_CHAR, self.OnPress)
self.Show(True)
def OnPress(self, event):
if event.GetKeyCode() == 13:
self.control.WriteText('\n>>>')
else:
event.Skip()
if __name__ == '__main__':
app = wx.App(False)
TestRun(None)
app.MainLoop()
The event.Skip() line is crucial; during my research into this, I learned that a KeyEvent is usually followed by a CharEvent. The CharEvent is the part where the character is written to the TextCtrl. When you intercept the KeyEvent, the CharEvent is only called if you do so explicitly - event.skip() is therefore vital if you want your TextCtrl to otherwise behave as normal. Without it, any keyboard input not equal to ASCII keycode 13 would do nothing.
From my tests, it appears that there is something about declaring the TextCtrl to have style wx.TE_PROCESS_ENTER that makes it print a newline after each call to WriteText(). My way gets around this though you will have more work to do with regard to making sure the insertion point is always in the right place, etc.
Best of luck!
Related
I have an element, editorBox which is of the PyQt5 element type QPlainTextEdit. My target goal is to call a function when the hotkey Shift + Return is pressed, and my goal with this function is that it will also insert text into the editorBox element (this isn't the part I'm stressed about, it's fairly easy to do with the .insertPlainText() method).
I've done searching, and the closest result I could find was to use QShortcut & QKeySequence paired together like so:
# Initialize the QShortcut class
self.keybindShiftEnter = QShortcut(QKeySequence("Shift+Return"), self)
# Connect the shortcut event to a lambda which executes my function
self.keybindShiftEnter.activated.connect(lambda: self.editorBox.insertPlainText("my text to insert"))
For clarification, I have tried using other characters in the QKeySequence constructor, such as Ctrl+b, and I've had success with it. Oddly enough, only the combination Shift+Return doesn't work for me.
I've analyzed a problem with it in relation to my bug. Some of the posts I've viewed:
This is for triggering a button, not a QPlainTextEdit.
Best thing I've tried, almost worked up until I tried Shift+Return
Any solution with keyPressEvent wouldn't work, because keys other than Shift+Enter wouldn't be typed into the editorBox
Solved my own problem:
# ... editorBox Initialization code ...
self.editorBox.installEventFilter(self)
# Within App class
def eventFilter(self, obj, event):
if obj is self.editorBox and event.type() == QEvent.KeyPress:
if isKeyPressed("return") and isKeyPressed("shift"):
self.editorBox.insertPlainText("my text to insert")
return True
return super(App, self).eventFilter(obj, event)
What I'm doing here is setting a filter function- basically, every time a key is pressed (any key, including space/backspace/etc) it will call the eventFilter function. The first if statement makes sure that the filter will only pass through if it is a key stroke (not entirely sure if this part is necessary, I don't think clicks trigger the function). Afterwards, I utilize the isKeyPressed function (a renamed version of the keyboard module's is_pressed function) to detect if the current key is held down. With the and operator, I can use this to make keybind combos.
I am beginning to work on a program in which i want multilingual support, but since it is pretty modular (and i want it to be even more in the future), a language change means "destroy what you had of interface and build again with the content which language modules have". (You can see the source as of now on GitHub)
This full-modular approach may give many problems, but i still want it, and so the problem is: Whenever i destroy the widgets i had, until i am alone with the raw Gtk.Window itself, i am not able to assign once again widgets to it. They won't get displayed at all, sometimes silently, sometimes with errors depending on my approach.
Lets suppose the class window, which inherits from a Gtk.Window.
This class is the raw window, and i assign to it a Gtk.Box -
self.interface.
self.interface itself, has two Gtk.Box's, one sidebar and one stack of contents.
To rebuild i tried to:
Change the language variable
Use the method destroy on self.interface, which removes the widget and its child's.
Reuse the function to build the widgets stack on top of self.interface
Re-add self.interface to self (window).
This approach returns:
g_object_unref: assertion 'G_IS_OBJECT (object)' failed
Gtk.main()
Pointing to the .show_all() method in this file.
I've already tried to leave interface without using .destroy on it, applying only on its child's, and then creating them again over it, but didn't worked. The window was left blank with no error at all.
The code i am trying right now is:
#Remember, self is a Gtk.Window()
def __init__(self):
[...]
self.interface = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.stack = None
self.add(interface)
self.build_interface()
def build_interface(self):
self.interface.pack_start(
self.create_side_navigation(
self.interface_data["menu"][self.language]["name"])
, False, False, 0
)
self.stack = self.create_content_stack(self.interface_data["menu"][self.language])
self.interface.pack_start(self.stack, True, True, 0)
###Code to rebuild(this is a question dialog):
if response == Gtk.ResponseType.OK:
self.language = self.new_language["Index"]
self.new_language = None
self.stack.destroy()
self.interface.destroy()
self.interface = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.build_interface()
self.add(self.interface)
This code will cause the previously told "g_object_unref" error.
What is causing this? Why can't i add anything once deleted?
Feel free to clone the repo and try, the code is well commented(yet i am not sure if poorly written, i am a python newbie) and its quite easy to understand where is the problematic part. Its not too big.
PS: It should need GTK+3.12 because of the popovers.
As a GTK Dev showed to me, GTK has by default all the widgets invisible.
The error was caused in the line which declared the whole interface visibility (windowclass.show_all()), but since the interface changed since when it was applied, it threw that warning.
He pointed me to .remove() instead of .destroy(), and to set .show_all() to the new elements after set up.
The next commit(or the following) on that git, has the solution.
The best way to be multilingual is to keep your widgets the same and merely change the text of labels and titles. This can be done without disturbing the overall setup. For example:
s='Stop'
if lang=='fr': s='Arret'
...
somelabel.set_label(s)
I need some help tag-related!
I am writing a simple editor, supporting basic formatting.
By using a Text widget (named text), I put a tag 'b' to set the text where this tag applies to bold.
This is not a problem when I apply the bold to a selection:
text.tag_add('b', SEL_FIRST,SEL_LAST)
I have instead two problems when I just want to switch on/off the bold while typing.
To switch it on the only way I found is this:
text.insert(INSERT, ' ', 'b' )
text.mark_set("insert", INSERT+'-1c')
notice that I have to insert TWO spaces. If I insert one, the bold doesnt apply. If I insert '', my cursor goes back one char!
My second problem is how to switch it off, when I'm writing within a bolded region - and for this I havent the slightest idea...
Thanks for any help!
You may not realize it, but you're trying to do something that's very hard in Tkinter. While the text widget tags are a powerful concept, they have some weaknesses when it comes to creating something like a wysywig editor.
You need to change your approach a bit. Instead of inserting spaces, I think the better solution is to add bindings that apply (or remove) your tags each time a character is inserted. This has it's own set of problems, but with enough attention to detail you can overcome them. We can do that though a custom key binding.
When you insert a character into a text widget, this is handled by a binding on the <Key> event on the text widget class. So, we could bind to <Key> to add the tag. However, if we add a binding to <Key> on the widget, this will fire before the class binding, meaning our code will execute before the character inserted rather than after. We'll be trying to modify something that hasn't actually been inserted into the widget yet.
One way to solve that is to bind to a key release rather than a key press, since the character is inserted on the press. However, think of the scenario where a user presses and holds a key down -- multiple characters will be entered but you may only get a single key-up event. So this solution isn't particularly good.
Another solution is to somehow arrange for our custom binding to happen after the default binding. To do that we need to do two things: 1) adjust the "bind tags" for the widget to have an additional tag after the class tag, and 2) add a binding to this new bind tag.
There are downsides to this approach too. Not because of the bind tags, but because there are a lot more events you need to handle besides <Key> (for example, control-v to paste isn't handled by the <Key> binding, so you'll have to add a special case for pasting).
That being said, this solution might be good enough for you, or at least good enough to help you better understand the problem, and understanding the problem is often the biggest obstacle to finding a solution.
In the following example I have a text widget that has an additional bindtag named "CustomText" that we place after the standard "Text" bind tag. I put a binding on this tag for the <Key> event, and in the handler I simply apply the appropriate tags to the just-inserted character.
You'll have to add your own code to handle a clipboard paste, and the problem of dealing with conflicting tags (such as two tags each with their own font). Hopefully, though, this example will act as inspiration
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.tag_vars = {
"underline": tk.IntVar(),
"red": tk.IntVar(),
}
self.text = MyText(self, width=40, height=8)
self.text.tag_configure("red", foreground="red")
self.text.tag_configure("underline", underline=True)
toolbar = tk.Frame(self)
self.underline = tk.Checkbutton(self, text="Underline",
onvalue = True, offvalue=False,
variable = self.tag_vars["underline"]
)
self.red = tk.Checkbutton(self, text="Red",
onvalue = True, offvalue=False,
variable = self.tag_vars["red"]
)
self.underline.pack(in_=toolbar, side="left")
self.red.pack(in_=toolbar, side="left")
toolbar.pack(side="top", fill="x")
self.text.pack(side="top", fill="both", expand=True)
class MyText(tk.Text):
def __init__(self, parent, *args, **kwargs):
tk.Text.__init__(self, *args, **kwargs)
self.parent = parent
# add a new bind tag, "CustomText" so we
# can have code run after the class binding
# has done it's work
bindtags = list(self.bindtags())
i = bindtags.index("Text")
bindtags.insert(i+1, "CustomText")
self.bindtags(tuple(bindtags))
# set a binding that will fire whenever a
# self-inserting key is pressed
self.bind_class("CustomText", "<Key>", self.OnKey)
def OnKey(self, event):
# we are assuming this is called whenever
# a character is inserted. Apply or remove
# each tag depending on the state of the checkbutton
for tag in self.parent.tag_vars.keys():
use_tag = self.parent.tag_vars[tag].get()
if use_tag:
self.tag_add(tag, "insert-1c", "insert")
else:
self.tag_remove(tag, "insert-1c", "insert")
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Thank you Bryan, but your approach seems to me too much complicated, and I'm not so eager to start writing bindings for what's missing - paste as an example!
I simply wrote down the following, and it seems to work
l=text.tag_names('insert')
if l==() or l[0]!='b': # select bold
text.insert(INSERT, ' ', 'b' )
text.mark_set('insert', 'insert-1c')
else: # deselect bold
text.insert(INSERT, ' ' )
text.tag_remove ('b','insert-1c')
text.mark_set('insert', 'insert-1c')
My only remaining problem is that I didnt find yet a way not to insert an additional space when selecting bold - but I can live with it...
alessandro
I am trying to bind events from a GUI file to use code from another file (effectively a "front end" and a "back end"). I can get the back end and front end working within the same file, but when I try to move them into separate files, I have issues getting the back end to see parts (labels, buttons, etc.) of the front end.
I. E. I need the back end code to change labels and do math and such, and it would need to affect the GUI.
I have provided a simple version of my program. Everything works with the exception of the error I get when I try to make the back end see the parts of the GUI.
mainfile.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
import label_changer
class foopanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, id=wx.ID_ANY)
box = wx.BoxSizer()
btn = wx.Button(self,1,"Press")
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
box.Add(btn)
self.lbl = wx.StaticText(self,1,"Foobar")
box.Add(self.lbl)
self.SetSizerAndFit(box)
class main_frame(wx.Frame):
"""Main Frame holding the main panel."""
def __init__(self,*args,**kwargs):
wx.Frame.__init__(self,*args,**kwargs)
sizer = wx.BoxSizer()
self.p = foopanel(self)
sizer.Add(self.p,1)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
frame = main_frame(None,-1,)
app.MainLoop()
label_changer.py
def change_label(self):
self.p.lbl.SetLabel("barfoo")
All I want it to do is change the label of the GUI, but use an external file.
I am doing this mostly to keep my code separate and just as a learning experience.
Thanks in advance!
One solution is to modify change_label to accept an argument that identifies the label to change. For example:
def change_label(event, label):
label.SetLabel("barfoo")
Then, use lambda to create a callback that passes that argument in:
btn.Bind(wx.EVT_BUTTON, label_changer,
lambda event, label=self.p.lbl: label_changer.change_label(event, label))
Make sure you define self.lbl before you do the binding.
For more on passing arguments to callbacks see Passing Arguments to Callbacks on WxPyWiki
A common way to do this is the MVC Pattern and pubsub. See this Example.
This
btn.Bind(wx.EVT_BUTTON,label_changer.change_label(self))
needs to be
btn.Bind(wx.EVT_BUTTON,label_changer.change_label)
and this
def change_label(self):
self.p.lbl.SetLabel("barfoo")
needs to be
def change_label(event):
panel = event.GetEventObject().GetParent()
panel.lbl.SetLabel("barfoo")
To clarify, you need to pass a reference to a function to Bind that is to be called when the event occurs. wx will always pass one argument to these functions - the event. The self that you usually see in the callbacks is a byproduct of them being bound methods. Every bound method (to oversimplify, a function defined in a class) gets implicitly passed a first argument when called that is a reference to a class instance. So since you can't get to this instance the traditional way in an "external" function you have to get to it through the event object.
One more thing, you are not realy separating the gui from the logic this way. This is because the logic (label_changer in this case) needs to know about the gui and to manipulate it directly. There are ways to achieve much stronger separation (st2053 hinted at one of them) but for a relatively small program you don't need to bother if you don't want to right now, simply splitting the code in multiple files and focusing on getting the thing done is fine. You can worry about architecture later.
I have a RichTextCtrl in my application, that has a handler for EVT_KEY_DOWN. The code that is executed is the following :
def move_caret(self):
pdb.set_trace()
self.rich.GetCaret().Move((0,0))
self.Refresh()
def onClick(self,event):
self.move_caret()
event.Skip()
rich is my RichTextCtrl.
Here is what I would like it to do :
on each key press, add the key to the control ( which is default behaviour )
move the cursor at the beginning of the control, first position
Here's what it actually does :
it adds the key to the control
I inspected the caret position, and the debugger reports it's located at 0,0 but on the control, it blinks at the current position ( which is position before I pressed a key + 1 )
Do you see anything wrong here? There must be something I'm doing wrong.
Apparently, there are two problems with your code:
You listen on EVT_KEY_DOWN, which is probably handled before EVT_TEXT, whose default handler sets the cursor position.
You modify the Caret object instead of using SetInsertionPoint method, which both moves the caret and makes the next character appear in given place.
So the working example (I tested it and it works as you would like it to) would be:
# Somewhere in __init__:
self.rich.Bind(wx.EVT_TEXT, self.onClick)
def onClick(self, event):
self.rich.SetInsertionPoint(0) # No refresh necessary.
event.Skip()
EDIT: if you want the text to be added at the end, but the cursor to remain at the beginning (see comments), you can take advantage of the fact that EVT_KEY_DOWN is handled before EVT_TEXT (which in turn is handled after character addition). So the order of events is:
handle EVT_KEY_DOWN
add character at current insertion point
handle EVT_TEXT
Adding a handler of EVT_KEY_DOWN that moves the insertion point to the end just before actually adding the character does the job quite nicely. So, in addition to the code mentioned earlier, write:
# Somewhere in __init__:
self.rich.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
def onKeyDown(self, event):
self.rich.SetInsertionPointEnd()
event.Skip()
By the way, event.Skip() does not immediately invoke next event handler, it justs sets a flag in the event object, so that event processor knows whether to stop propagating the event after this handler.