It doesn't look like it has that attribute, but it'd be really useful to me.
You have to change the state of the Text widget from NORMAL to DISABLED after entering text.insert() or text.bind() :
text.config(state=DISABLED)
text = Text(app, state='disabled', width=44, height=5)
Before and after inserting, change the state, otherwise it won't update
text.configure(state='normal')
text.insert('end', 'Some Text')
text.configure(state='disabled')
Very easy solution is just to bind any key press to a function that returns "break" like so:
import Tkinter
root = Tkinter.Tk()
readonly = Tkinter.Text(root)
readonly.bind("<Key>", lambda e: "break")
The tcl wiki describes this problem in detail, and lists three possible solutions:
The Disable/Enable trick described in other answers
Replace the bindings for the insert/delete events
Same as (2), but wrap it up in a separate widget.
(2) or (3) would be preferable, however, the solution isn't obvious. However, a worked solution is available on the unpythonic wiki:
from Tkinter import Text
from idlelib.WidgetRedirector import WidgetRedirector
class ReadOnlyText(Text):
def __init__(self, *args, **kwargs):
Text.__init__(self, *args, **kwargs)
self.redirector = WidgetRedirector(self)
self.insert = self.redirector.register("insert", lambda *args, **kw: "break")
self.delete = self.redirector.register("delete", lambda *args, **kw: "break")
If your use case is really simple, nbro's text.bind('<1>', lambda event: text.focus_set()) code solves the interactivity problem that Craig McQueen sees on OS X but that others don't see on Windows and Linux.
On the other hand, if your readonly data has any contextual structure, at some point you'll probably end up using Tkinter.Text.insert(position, text, taglist) to add it to your readonly Text box window under a tag. You'll do this because you want parts of the data to stand out based on context. Text that's been marked up with tags can be emphasized by calling .Text.tag_config() to change the font or colors, etc. Similarly, text that's been marked up with tags can have interactive bindings attached using .Text.tag_bind(). There's a good example of using these functions here. If a mark_for_paste() function is nice, a mark_for_paste() function that understands the context of your data is probably nicer.
This is how I did it. Making the state disabled at the end disallows the user to edit the text box but making the state normal before the text box is edited is necessary for text to be inserted.
from tkinter import *
text=Text(root)
text.pack()
text.config(state="normal")
text.insert(END, "Text goes here")
text.config(state="disabled")
from Tkinter import *
root = Tk()
text = Text(root)
text.insert(END,"Some Text")
text.configure(state='disabled')
Use this code in windows if you want to disable user edit and allow Ctrl+C for copy on screen text:
def txtEvent(event):
if(event.state==12 and event.keysym=='c' ):
return
else:
return "break"
txt.bind("<Key>", lambda e: txtEvent(e))
If selecting text is not something you need disabling the state is the simplest way to go. In order to support copying you can use an external entity - a Button - to do the job. Whenever the user presses the button the contents of Text will be copied to clipboard. Tk has an in-build support of handling the clipboard (see here) so emulating the behaviour of Ctrl-C is an easy task. If you are building let's say a console where log messages are written you can go further and add an Entry where the user can specify the number of log messages he wants to copy.
Many mentioned you can't copy from the text widget when the state is disabled. For me on Ubuntu Python 3.8.5 the copying issue turned out to be caused by the widget not having focus on Ubuntu (works on Windows).
I have been using the solution with setting the state to disabled and then switching the state, when I need to edit it programmatically using 1) text.config(state=tkinter.NORMAL) 2) editing the text and 3) text.config(state=tkinter.DISABLED).
On windows I was able to copy text from the widget normally, but on Ubuntu it would look like I had selected the text, but I wasn't able to copy it.
After some testing it turned out, that I could copy it as long as the text widget had focus. On Windows the text widget seems to get focus, when you click it regardless of the state, but on Ubuntu clicking the text widget doesn't focus it.
So I fixed this problem by binding the text.focus_set() to the mouse click event "<Button>":
import tkinter
root = tkinter.Tk()
text0 = tkinter.Text(root, state=tkinter.DISABLED)
text0.config(state=tkinter.NORMAL)
text0.insert(1.0, 'You can not copy or edit this text.')
text0.config(state=tkinter.DISABLED)
text0.pack()
text1 = tkinter.Text(root, state=tkinter.DISABLED)
text1.config(state=tkinter.NORMAL)
text1.insert(1.0, 'You can copy, but not edit this text.')
text1.config(state=tkinter.DISABLED)
text1.bind("<Button>", lambda event: text1.focus_set())
text1.pack()
For me at least, that turned out to be a simple but effective solution, hope someone else finds it useful.
Disabling the Text widget is not ideal, since you would then need to re-enable it in order to update it. An easier way is to catch the mouse button and any keystrokes. So:
textWidget.bind("<Button-1>", lambda e: "break")
textWidget.bind("<Key>", lambda e: "break")
seems to do the trick. This is how I disabled my "line numbers" Text widget in a text editor. The first line is the more powerful one. I'm not sure the second is needed, but it makes me feel better having it there. :)
This can also be done in Frames
from tkinter import *
root = Tk()
area = Frame(root)
T = (area, height=5, width=502)
T.pack()
T.insert(1.0, "lorem ipsum")
T.config(state=DISABLED)
area.pack()
root.mainloop()
You could use a Label instead. A Label can be edited programmatically and cannot be edited by the user.
I'm using wxPython DirDialog and it seems to have a bug.
When launching the dialog I specify a default path (defaultPath).
That path is being selected by the dialog but the dialog is not scrolled to the selected path.
Instead the dialog is scrolled to the top of the dialog.
This leaves the user to scroll A LOT down to reach the default path.
Very inconvenient.
Any way to correct this?
Using:
Python 2.6.5
wxPython 2.8.12.1
Windows 8.1
It may not be any consolation but with Python 2.7.12 Wx '3.0.2.0 gtk2 (classic)' on Linux, the following works as it should. Check if you are doing something different.
def OnSelect_dir(self,event):
dialog = wx.DirDialog (None,defaultPath=self.client_dir.GetValue(), message = 'Pick a directory.' )
if dialog.ShowModal() == wx.ID_OK:
self.client_dir.SetValue(dialog.GetPath())
else:
pass
dialog.Destroy()
It works is I hard code defaultPath='/home/rolf' as well.
Well apparently if I exclude the style "wx.DD_DEFAULT_STYLE" then it works just fine.
So this works:
style = wx.DD_DIR_MUST_EXIST
But this doesn't focus the dialog properly on the defaultPath:
style = wx.DD_DEFAULT_STYLE | wx.DD_DIR_MUST_EXIST
I guess it must be a bug somewhere
I'm implementing a custom widget to use it as a title bar on a dockable window. My problem arises only on Windows, namely, the window border disappears when the dock window is afloat.
Seems the problem is that, on Windows only, the window flags are changed. I.e. when I do this:
print dock_window.windowFlags()
dock_window.setTitleBarWidget(title_bar)
print dock_window.windowFlags()
it prints out different setting for the flags before and after.
However, it stays the same on linux and the borders remain unchanged.
My question is, how to restore the window border?
UPDATE: Since the custom title bar overrides the flags for the border when the dock window is floating, how can I edit the dock window so it has some kind of border?
(It is crucial for the dock window to have a custom title bar when floating.)
According to this answer this is expected behavior.
From the documentation of setTitleBarWidget:
If a title bar widget is set, QDockWidget will not use native window
decorations when it is floated.
So Linux does it the wrong way then?
Anyway as a workaround for Windows I implemented the idea (unsetting the title bar widget before floating) from the answer in PySide/PyQt.
from PySide import QtGui, QtCore
class MyDockWidget(QtGui.QDockWidget):
def __init__(self, title_widget):
super().__init__()
self.title_widget = title_widget
self.toggle_title_widget(False)
self.topLevelChanged.connect(self.toggle_title_widget)
def toggle_title_widget(self, off):
if off:
self.setTitleBarWidget(None)
else:
self.setTitleBarWidget(self.title_widget)
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
t = QtGui.QLabel('Title')
d = MyDockWidget(t)
w.addDockWidget(QtCore.Qt.LeftDockWidgetArea, d)
w.show()
app.exec_()
At least it keeps the standard decoration when floating.
I found this to be an unresolved bug in QT and I don't see this as expected behavior.
I found multiple cases of people stumbling on this issue eg1, eg2 and others.
Some recommend unsetting and setting the setTitleBarWidget as in Trilarion's answer.
This, however, removes the custom title bar and was not ok with me.
others recommend setting flags on topLevelChanged event: window.setWindowFlags(Qt::Window | Qt::FramelessWindowHint);. this adds the usual title bar to the dock widget, which again is not what I personally want.
the best solution I found is w->setWindowFlags(Qt::Tool|Qt::CustomizeWindowHint);. This uses Qt.CustomizeWindowHint instead of Qt.FramelessWindowHint and does not produce a huge title bar, merely a small bar.
Implementation
from PyQt5.QtCore import Qt
....
def dockfloatevent(isfloating):
if isfloating:
dock.setWindowFlags(Qt.Tool | Qt.CustomizeWindowHint)
dock.topLevelChanged.connect(dockfloatevent)
I am not using the most up to date Qt, but from what I can tell this is still an issue? If someone has a Qt account maybe post something to the above bug link? I have already wasted many hours on this and don't feel like pushing it further :|
Is there any reason why the position, pos, flag doesn't seem to work in the following example?
dlg = wx.MessageDialog(
parent=self,
message='You must enter a URL',
caption='Error',
style=wx.OK | wx.ICON_ERROR | wx.STAY_ON_TOP,
pos=(200,200)
)
dlg.ShowModal()
dlg.Destroy()
The documentation is here: http://www.wxpython.org/docs/api/wx.MessageDialog-class.html
'self' is a reference to the frame. I'm running in Windows Vista, python26, wxpython28. The message dialog always appears to be in the middle of the screen.
If for some reason it's not possible to position the dialog, is there anyway to at least restrict the dialog to be in the frame, rather than just the center of the screen?
It seems to be a bug and i think you should file the same. for time being you can user your own dervied dialog class to center it as you wish. Also instead of wx.MessageDialog you can use wx.MessageBox, it will save you few lines.
I'm designing an application that I want to run in the background. There isn't any user interaction necessary, so I want the app to run invisibly save for a systray icon. I want that icon to have a menu that just opens the config/help files in notepad. Could someone point me in the right direction or provide an example?
You can probably do this more cleanly but I have using some samples a while back I was able to create myself a class to handle the basic contruction of a taskbar icon.
TaskBarIcon.py
import wx
ID_SHOW_OPTION = wx.NewId()
ID_EDIT_OPTION = wx.NewId()
class Icon(wx.TaskBarIcon):
def __init__(self, parent, icon, tooltip):
wx.TaskBarIcon.__init__(self)
self.SetIcon(icon, tooltip)
self.parent = parent
self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnLeftDClick)
self.CreateMenu()
def CreateMenu(self):
self.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnPopup)
self.menu = wx.Menu()
self.menu.Append(ID_SHOW_OPTION, '&Show Option 1')
self.menu.Append(ID_EDIT_OPTION, '&Edit Option 2')
self.menu.AppendSeparator()
self.menu.Append(wx.ID_EXIT, 'E&xit')
def OnPopup(self, event):
self.PopupMenu(self.menu)
def OnLeftDClick(self, event):
if self.parent.IsIconized():
self.parent.Iconize(False)
if not self.parent.IsShown():
self.parent.Show(True)
self.parent.Raise()
Within your Frame's init(), add the two lines below:
self.TrayIcon = tbi.Icon(self, wx.Icon("C:\\YourIcon.png", wx.BITMAP_TYPE_PNG), "ToolTip Help Text Here")
self.Bind(wx.EVT_ICONIZE, self.OnIconify)
And now just add this function to your frame and you should be set:
def OnIconify(self, event):
self.Hide()
Just remember to edit the items in the Icon class to suit your needs.
Have you considered running this application as a windows service? Many users will consider a system tray icon with little to no functionality a nuisance. You could still provide links to help/config files as a start menu entry.
The python win32 extensions package should have support for python services.
Of course, there are still reasons why you may want to run this as a system tray icon. I'm sorry that I don't have any experience with that.
You want the wx.TaskBarIcon:
http://docs.wxwidgets.org/stable/wx_wxtaskbaricon.html
The wxPython Demo has example code you can look at.