Difference between wx.TextCtrl .write / .WriteText / .AppendText - python

I am new to Python and so i am new to wxPython as well. I was just wondering if there is any difference between these wx.TextCtrl functions. This mini code shows three times the same output. If there is no difference, is there a historic reason for these functions?
import wx
class testUI(wx.Panel):
textCtrl = ''
def __init__(self, parent, name):
super(testUI, self).__init__(parent, name=name)
self.buildUI()
self.Show(True)
self.textCtrl.write('bli\n')
self.textCtrl.WriteText('bla\n')
self.textCtrl.AppendText('blub\n')
def buildUI(self):
self.textCtrl = wx.TextCtrl(self, style=wx.TE_MULTILINE|wx.TE_READONLY)
box = wx.BoxSizer(wx.VERTICAL)
box.Add(self.textCtrl, proportion=1, flag=wx.EXPAND)
def main():
app = wx.App(False)
root = wx.Frame(parent=None, title='testUI')
testUI(parent=root, name='testUI')
root.Show(True)
app.MainLoop()
# Standard boilerplate to call the main() function.
if __name__ == '__main__':
main()
thanks :)

Guessing people may hit this thread for the same reason I did, which is that the "new" version of wxPython (4.0.1) lists write() as a method on the wx.TextCtrl class - but it's not defined. I needed a write function to be able to use the logging.StreamHandler class; because these methods should all do the same thing, I recreated the missing write method with an assignment - this works:
import wx
import logging
lgr = logging.getLogger(__name__)
lgr.setLevel(logging.DEBUG)
fmt = logging.Formatter('%(asctime)s: %(name)s [%(levelname)s] %(message)s')
debug = lgr.debug
class LogTab(wx.Panel):
def __init__(self, *args, **kwds):
kwds["style"] = wx.TAB_TRAVERSAL
wx.Panel.__init__(self, *args, **kwds)
self.text_log = wx.TextCtrl(self, wx.ID_ANY, "",
style=wx.TE_MULTILINE | wx.TE_READONLY)
self.text_log.write = self.text_log.WriteText
self.__set_properties()
self.__do_layout()
self.log_handler = logging.StreamHandler(self.text_log)
self.log_handler.setFormatter(fmt)
lgr.addHandler(self.log_handler)

The 3 methods appear to be functionally the same. However, I would argue that you would use AppendText for adding additional text to a text control to make what you're doing very clear in the code itself. Most of the time, you will normally use SetValue. I have used WriteText when redirecting stdout, but that's it. You can read about that use case here:
http://www.blog.pythonlibrary.org/2009/01/01/wxpython-redirecting-stdout-stderr/

EDIT:
Ok, instead of reading my entire answer the difference between .AppendText() and .WriteText() appears to be that the first one adds text to the end of the textcontrol and the second one adds it at the current insertion point as explained here.
Old answer (may still be usefull as an example):
Hmm.. strange. I ended up here because using .WriteText() on my wx.stc.StyledTextCtrl() was giving strange results. I now changed it to .write() and it works.
I am using my wx.stc.StyledTextCtrl() as a drag and drop window that lists the dropped filepaths. However, since the window is also editable (I need it to be) the InsertionPoint can be located anywhere.
So, whenever someone drops a new filepath (or multiple filepaths) onto my wx.stc.StyledTextCtrl() I need it to check where the current InsertionPoint is located. If the InsertionPoint is located in a line that already holds text, it needs to be moved to the end of that specific line. From there it needs to print a newline and the received filepath(s).
My code looks like this:
def write_interactive(self, text):
curPos = self.interactivewindow.GetInsertionPoint()
lineVal,curCol,curRow = self.interactivewindow.PositionToXY(curPos)
lineNum = curRow
lineText = self.interactivewindow.GetLineText(lineNum)
if len(lineText) > 0:
endOfLine = self.interactivewindow.XYToPosition(len(lineText), curRow)
self.interactivewindow.SetInsertionPoint(endOfLine)
self.interactivewindow.write('\n' + text)
else:
self.interactivewindow.write(text)
Now, as to your question, I have noticed that when using self.interactivewindow.WriteText(text) the text that is located between the initial InsertionPoint and the end of that specific line vanishes. And the new text is written over it. However, when using self.interactivewindow.write(text), everything works fine.

Related

Python modules and binding to functions

I am a self-taught in python and I have been programming for the last year or so. Most of my knowledge has been attained from googling and trial and error. I apologize in advance if I am not using the proper terms so please correct me.
I am at a roadblock with my program, so I will post a few bits of relevant code.
I have a button (that references a function) that I want to use in several different classes. My button is one class and the function is in a different class.
When I start my program, it runs my def_init(self) and that runs my universal_windows function and my main window references the class button_position and the function make_buttons and the sub function button_template.
(There are more functions, but this is a snippet)
class button_position:
def make_buttons(self,panel,sizer,v_box_size,v_rt,
v_delete,v_move_up,v_move_down):
def button_template():
delete_clip = wx.Button(panel, label="Delete\n Clip",pos = (975,v_delete))
delete_clip.Bind(wx.EVT_BUTTON, self.delete_clips)
delete_clip.SetSize((48,45))
delete_clip.SetBackgroundColour("Red")
delete_clip.SetForegroundColour("Black")
# print(type(delete_clip))
(PROGRAM CONTINUES)
delete_clips is another function (located in the the class button_functions) and all of my buttons work in my main window when called by the init function.
I want to reuse this function in another window, so I call button_position in another class. The buttons appear fine but my program cannot reference the functions that are associated with button_position function. I have to make new functions (with the same name) and a reference to the original function to have those buttons work. Since I have to "redefine" the functions in every class I use the functions in, it means a lot of bloating in my classes. I have tried to just call them without defining and that doesn't work. Ultimately, I don't want to have to redfine them at all.
class Stringout_window(wx.Frame):
""""""
Functions to delete clips, move clips up, move clips down
def delete_clips(self,event):
button_functions.delete_clips(self,event)
def move_clips_up(self,event):
button_functions.move_clips_up(self,event)
def move_clips_down(self,event):
button_functions.move_clips_down(self,event)
def accel(self):
# Edit_window.accelerator_commands(self)
print("this")
...
def __init__(self):
pub.subscribe(self.my_listener2, "bottom_data")
# print(bottom_data)
self.accel()
wx.Frame.__init__(self, None, wx.ID_ANY, "Full Screen",style= wx.MINIMIZE_BOX
| wx.CLOSE_BOX,pos = (10,20),size = (1075,1150))
self.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.NORMAL))
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.index = 0
#Sizer placed at the top because the sizer attributes are part of each object.
sizer = wx.BoxSizer(wx.VERTICAL)
h_sizer = wx.BoxSizer(wx.HORIZONTAL)
#Sizer to add space to top
sizer.Add(0,60,0)
button_position.make_buttons(self,panel,sizer,982,20,
85,200,350)
(PROGRAM CONTINUES)
I thought of trying to reference the function in the button_template but that doesn't work properly either
def button_template():
delete_clip = wx.Button(panel, label="Delete\n Clip",pos = (975,v_delete))
delete_clip.Bind(wx.EVT_BUTTON, self.button_function.delete_clips)
Please let me know if you need more information.
Can someone give me a bit of advice? I appreciate your help.
Thanks

QFileSystemModel not updating when files change

I'm having trouble with QFileSystemModel not showing changes to files. When a file is first created it immediately shows up. But when the file itself changes, the size and timestamp don't update. I've made multiple attempts at trying to force the model to update with no real success. The best I've achieved is to completely replace the model. Although that results in this error:
QSortFilterProxyModel: index from wrong model passed to mapToSource
The test code below creates a table view of an empty directory. The left button creates a file (foo.txt) when clicked. Successive clicks append data to the file. It was my understanding that the QFileSystemModel didn't need a refresh, but the second button is my attempt at that.
Any help as to what I'm doing wrong would be greatly appreciated!
# Testing with python3.6.3 and pip installed pyqt5 5.9.2 in virtualenv on Ubuntu
import os, sys, tempfile
from PyQt5 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
self._view = QtWidgets.QTableView()
layout.addWidget(self._view)
self._modify_button = QtWidgets.QPushButton('Create')
layout.addWidget(self._modify_button)
self._refresh_button = QtWidgets.QPushButton('Refresh')
layout.addWidget(self._refresh_button)
self._modify_button.clicked.connect(self._modify)
self._refresh_button.clicked.connect(self._refresh)
self._model, self._proxy = None, None
self.temp_dir = tempfile.TemporaryDirectory(dir=os.path.dirname(os.path.abspath(__file__)))
self.init_model(self.temp_dir.name)
def init_model(self, path):
self._model = QtWidgets.QFileSystemModel()
self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)
self._proxy = QtCore.QSortFilterProxyModel(self)
self._proxy.setSourceModel(self._model)
self._view.setModel(self._proxy)
# self._view.setModel(self._model)
self._model.directoryLoaded.connect(self._loaded)
self._model.setRootPath(path)
def _loaded(self):
path = self._model.rootPath()
source_index = self._model.index(path)
index = self._proxy.mapFromSource(source_index)
self._view.setRootIndex(index)
# self._view.setRootIndex(source_index)
def _modify(self):
"""Create or modify foo.txt..model should see and update"""
self._modify_button.setText('Modify')
file_name = os.path.join(self.temp_dir.name, 'foo.txt')
with open(file_name, 'a') as txt_file:
print('foo', file=txt_file)
# def _refresh(self):
# # This only seems to work once..and its a flawed approach since it requires permission to write
# temp = tempfile.NamedTemporaryFile(dir=self.temp_dir.name)
# def _refresh(self):
# self._model.beginResetModel()
# self._model.endResetModel()
# def _refresh(self):
# self._proxy.setFilterRegExp('foo')
# self._proxy.setFilterRegExp(None)
# self._proxy.invalidate()
# self._proxy.invalidateFilter()
# self._proxy.reset()
#
# root_index = self._model.index(self._model.rootPath())
# rows = self._model.rowCount(root_index)
# proxy_root_index = self._proxy.mapFromSource(root_index)
# topLeft = self._proxy.index(0, 0, proxy_root_index)
# bottomRight = self._proxy.index(rows - 1, self._model.columnCount(proxy_root_index) - 1, proxy_root_index)
# # self._proxy.dataChanged.emit(topLeft, bottomRight)
# self._model.dataChanged.emit(topLeft, bottomRight)
# def _refresh(self):
# # This only seems to work once
# self._model.setRootPath('')
# self._model.setRootPath(self.temp_dir.name)
def _refresh(self):
# This seems heavy handed..but seems to work
# ..though generates "QSortFilterProxyModel: index from wrong model passed to mapToSource" spam in console
self.init_model(self.temp_dir.name)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = Widget()
widget.show()
sys.exit(app.exec_())
UPDATE:
As of Qt-5.9.4, the QT_FILESYSTEMMODEL_WATCH_FILES envrionment variable can be used to switch on per-file watching (see QTBUG-46684). This needs to be set once to a non-empty value before the model starts caching information about files. But note that this will add a file-watcher to every file that is encountered, so this may make it an expensive solution on some systems.
The original answer is left below as an explanation of the problem.
This problem is caused by a long-standing Qt bug: QTBUG-2276. Unfortunately, at present, it does not look likely that it will be fixed any time soon. As indicated in the bug report comments, the core of the issue seems to be this:
It's an OS limitation. A change to a file does not mean the directory
is modified.
The only real work-around for this would be to attach a QFileSystemWatcher to every single file, which could obviously be prohibitively expensive (on some platforms, anyway).
In addition to this problem, the QFileSystemModel class doesn't currently provide an API for forcing a refresh, and, as you have discovered, there does not seem to be any reliable work-around for that. Most "solutions" offered on SO and elsewhere, suggest some variant of this:
root = fsmodel.rootPath()
fsmodel.setRootPath('')
fsmodel.setRootPath(root)
But as you know, this seems to work only once - probably due to some quirk in the way file-info caching is currently implemented.
At present it appears the only way to force an update is to replace the entire model. The error messages produced by your current implementation of this can be prevented by refactoring your init_model method like this:
def init_model(self, path):
if self._proxy is None:
self._proxy = QtCore.QSortFilterProxyModel(self)
else:
# remove the current source model
self._proxy.setSourceModel(None)
self._model = QtWidgets.QFileSystemModel()
self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)
self._proxy.setSourceModel(self._model)
self._view.setModel(self._proxy)
self._model.directoryLoaded.connect(self._loaded)
self._model.setRootPath(path)
This is a very unsatisfactory situation, but there just doesn't seem to be any obvious way around it at the moment.
Since Qt v5.9.4 you can set the environment variable QT_FILESYSTEMMODEL_WATCH_FILES, you can read more about it in the changelog:
[QTBUG-46684] It is now possible to enable per-file watching by
setting the environment variable QT_FILESYSTEMMODEL_WATCH_FILES,
allowing to track for example changes in file size.
Couple of things:
For the time being you need to set it before initializing the model, after that you can set it to another folder without any problem.
Be aware this feature comes at the cost of potentially heavy load, though.

TextCtrl scrollbar unusable until window is resized

Solved:
Thanks to Aya's answer below I now know that the issue was caused by self.panel = wx.Panel(self, -1) on line 18. I created a panel and didn't attach anything to it. The original issue description is still below for reference.
My Google-fu has failed me. I'm building the text editor that you can find here, written in Python with wxPython:
https://github.com/joshsaintjacque/py-ed/blob/master/pyed.py
The issue that I'm running into is this: when I open a text file (the only functionality built in at this point) that's larger than the viewable area in the TextCtrl the scroll bar remains disabled until the window is re-sized, then it works fine.
I know that the act of re-sizing the window is running some command that I'm neglecting to include in my OpenFile function (or perhaps in init), but I can't figure out what.
Any thoughts anyone has that could lead me in the right direction would be greatly appreciated.
Thanks!
+1 for including a link to the full source code - makes it so much easier to test.
I couldn't reproduce the fault you describe on wxPython 2.8.12 on Win32, but upon running your code, I found a seemingly extraneous wx.Panel object being created on pyed.py line 18...
self.panel = wx.Panel(self, -1)
...which seems to be interfering with the correct operation of the program. After commenting out that line, it seems to work fine.
A couple of other things I noticed: line 56...
self.SetTitle("PyEd - Editing ... " + filename)
...should probably be put in the preceding if-block, otherwise you'll get an error if the user clicks "Cancel" on the wx.FileDialog, and on line 16...
wx.Frame.__init__(self, parent, id, 'PyEd', (-1, -1), wx.Size(640, 480))
...if you use keyword args rather than positional args...
wx.Frame.__init__(self, parent=parent, id=id, title='PyEd', size=wx.Size(640, 480))
...you needn't bother re-specifying the default value for the window position, which is also slightly safer, in case the wxPython developers decide to change the defaults in a future version.
You can also factor out constant values, and the optional creation of the wx.Size object to reduce that line to...
wx.Frame.__init__(self, parent=None, title='PyEd', size=(640, 480))
Finally, with regards to IDs: in most cases you'll probably find they're of little use. Where they come in handy is where you want many similar controls, and it makes more sense to have them handled by a single event handler function.
Consider this example...
def create_buttons(parent):
parent.button1 = wx.Button(label='Button 1')
parent.button2 = wx.Button(label='Button 2')
parent.button3 = wx.Button(label='Button 3')
parent.button1.Bind(wx.EVT_BUTTON, on_button_1)
parent.button2.Bind(wx.EVT_BUTTON, on_button_2)
parent.button3.Bind(wx.EVT_BUTTON, on_button_3)
def on_button_1(event):
print 'You clicked button 1'
def on_button_2(event):
print 'You clicked button 2'
def on_button_3(event):
print 'You clicked button 3'
...which is fine, but if you need, say, 100 buttons, you may prefer to implement it like this...
def create_buttons(parent):
parent.buttons = [wx.Button(id=i, label='Button %d' % i) for i in range(100)]
parent.Bind(wx.EVT_BUTTON, on_button)
def on_button(event):
button_id = event.GetId()
print 'You clicked button %d' % button_id
Oh, and be careful using id as a variable name, because it's also a Python built-in function name.
It looks as if you're not setting the min or max size hints for the window, nor are you calling Self.Fit() to fit the box sizer to the window size (or is it the other way round? I'm rusty on my wxPython...)
Right where you call self.SetSizer(sizer), you should be able to fix this by adding:
self.Fit()
self.SetSizeHintSz(minSize=wx.Size(640, 480))
You may be able to get around the separate call to self.Fit() by using self.SetSizerAndFit()
(edited for spelling.)

TixComboBox subwidget not selecting/being set to specified value on first use

I have a TixExFileSelectDialog that is associated with several Entry objects in my program; the dialog is dynamically configured so that the selected file matches the text in the Entry the dialog is being used for. However, the first time the dialog is opened, regardless of which Entry is used, it only displays the pattern string, even when the Entry already has a default value. However, if I cancel out of the dialog and then reopen it, it displays the proper string. This happens when I set any combination of the combo box's selection and value options (one, the other, and both), as well as when I set the combo box's variable option to a StringVar. Is there something I'm missing in how TixComboBoxs function?
The code I'm currently using (with some reformatting/etc. for posting):
from tkinter.tix import *
opts = {'path': 'C:\\'}
class ImportGUI(Frame):
def _setfsd(self, directory='', pattern='*.xls', variable='', selection=None):
"Reconfigures the ExFileSelectDialog to enable reusability."
self.fsd.fsbox.config(directory=directory or opts['path'], # can't use opts['path'] as a default argument, because it could change.
pattern=pattern)
self.fsd.fsbox.file['variable'] = variable
if not variable:
self.fsd.fsbox.file['value'] = selection or pattern # Defaults to the pattern, which is the behavior of a fresh ExFileSelectionBox.
elif selection is not None: # variable exists, but setting selection manually as well
self.fsd.fsbox.file['value'] = selection
def _selectdatafile(self):
self._setfsd(selection='apple1.xls')
self.fsd.popup()
def _selectcodefile(self):
self._setfsd(selection='apple2.xls')
self.fsd.popup()
def createWidgets(self):
self.fsd = ExFileSelectDialog(self.master) # a top-level widget, so giving it the default root as master
self.dfrow = Frame(self)
self.cfrow = Frame(self)
self.dfentry = Entry(self.dfrow)
self.dfentry.pack(side='left')
self.cfentry = Entry(self.cfrow)
self.cfentry.pack(side='left')
self.dfbtn = Button(self.dfrow, text='...', command=self._selectdatafile)
self.dfbtn.pack(side='left')
self.cfbtn = Button(self.cfrow, text='...', command=self._selectcodefile)
self.cfbtn.pack(side='left')
self.dfrow.pack()
self.cfrow.pack()
self.pack()
def __init__(self, master=None):
Frame.__init__(self, master)
self.master.tk.eval('package require Tix')
self.createWidgets()
if __name__ == '__main__': # for testing
gui = ImportGUI()
gui.mainloop()
As it turns out, I had no need to do any of this in the first place because I can just use askopenfilename() from tkinter.filedialog to get exactly the functionality I desire, using the look-and-feel of the current OS. So much for Tix.
(Well, it wasn't exactly what I wanted, as the look was still a bit dated on Windows, but it was close enough. [IDLE seems to use it too, for that matter.])

sending colored text to a TextCtrl in wxpython

I'm trying to send colored text to a TextCtrl widget, but don't know how
style = wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_READONLY|wx.TE_RICH2
self.status_area = wx.TextCtrl(self.panel, -1,
pos=(10, 270),style=style,
size=(380,150))
basically that snippet defines a status box in my window, and I want to write colored log messages to it. If I just do self.status_area.AppendText("blah") it will append text like I want, but it will always be black. I can't find the documentation on how to do this.
You need to call SetStyle to change the text behavior.
import wx
class F(wx.Frame):
def __init__(self, *args, **kw):
wx.Frame.__init__(self, None)
style = wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_READONLY|wx.TE_RICH2
self.status_area = wx.TextCtrl(self, -1,
pos=(10, 270),style=style,
size=(380,150))
self.status_area.AppendText("blahblahhblah")
fg = wx.Colour(200,80,100)
at = wx.TextAttr(fg)
self.status_area.SetStyle(3, 5, at)
app = wx.PySimpleApp()
f = F()
f.Show()
app.MainLoop()
documentation of wxwidgets has this to say (you can also look up wxPython docs, but it points to wxwidgets anyway):
either use SetDefaultStyle before you append text to your textctrl, or after inserting text use SetStyle.
According to docs, the first solution is more efficient (and sounds easier to me.)

Categories