I have a very primitive GUI built with Tkinter. This is my first attempt at a GUI so I am struggling to understand what is going on (and with syntax). Here is what I have:
from __future__ import division, print_function
import os, ttk, tkFileDialog, Tkconstants
import Tkinter as tk
import datetime as dt
Tkinter = tk
# define OS version
version = '0.0.2'
class TestGUI(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
# try building list of instruments and sites
if os.path.isfile('config'):
with open(''config','r') as config:
config = dict( [(r.split('=')[0].strip(), r.split('=')[1].strip()) for r in config.read().split('\n') if r[0]<>'#'] )
self.datapath = config['datapath']
else:
self.datapath = '../'
def initialize(self):
self.grid()
# set up tabs
self.geometry( "%dx%d+%d+%d" % (1500, 900, 200, 50) )
nb = ttk.Notebook(self)
nb.pack(fill='both',expand='yes')
f1 = tk.Frame(bg='green')
f2 = tk.Frame(bg='blue')
f3 = tk.Frame(bg='red')
f1.grid()
f2.grid()
f3.grid()
nb.add(f1, text='General'.ljust(12,' '))
nb.add(f2, text='Plot'.ljust(12,' '))
nb.add(f3, text='Analysis'.ljust(12,' '))
button = tk.Button(f2,text='I AM A BUTTON!')
button.pack(side='left', anchor='nw', padx=3, pady=5)
# insert button and text box for specifying data location
path_button = tk.Button(f1,text='Browse',command=self.askdirectory).pack(side='left', anchor='nw', padx=10, pady=15)
self.path_entry = tk.StringVar()
self.entry = tk.Entry(f1,textvariable=self.path_entry)
self.entry.grid(column=12,row=8,columnspan=10)
self.entry.bind("<Return>", self.OnPressEnter)
self.path_entry.set(u"Sites directory path...")
def OnButtonClick(self):
print("You clicked the button !")
def OnPressEnter(self,event):
print("You pressed enter !")
def askdirectory(self):
"""Returns a selected directoryname."""
self.datapath = tkFileDialog.askdirectory()
self.path_entry.set(self.datapath)
if __name__ == "__main__":
app = TestGUI(None)
app.title(version)
app.mainloop()
My problem is centered around the addition of an entry box here:
self.path_entry = tk.StringVar()
self.entry = tk.Entry(f1,textvariable=self.path_entry)
self.entry.grid(column=12,row=8,columnspan=10)
self.entry.bind("<Return>", self.OnPressEnter)
self.path_entry.set(u"Sites directory path...")
If I run the code as-is, it just hangs (it also hangs if I use "f2"; I suspect it is getting caught in the infinite loop without actually doing anything). However, if I change the parent from "f1" to "f3" or it works (the entry box is now in frame 3 instead of frame 1, but it at least does not hang on me). There is another issue even when f3 is used: the entry box's width/position never change despite my changing of column/row/columnspan values.
Does anyone know why the code is hanging when I specify "f1" or "f2" and how to fix it?
Does anyone know why my entry box position/size is not changing?
You have put widgets in f1 and f2 using the pack geometry manager:
button = tk.Button(f2,text='I AM A BUTTON!')
button.pack(side='left', anchor='nw', padx=3, pady=5)
#and
path_button = tk.Button(f1,text='Browse',command=self.askdirectory).pack(side='left', anchor='nw', padx=10, pady=15)
Mixing geometry managers can lead to your program hanging, so using grid to put in the Entry does exactly that.
From effbot.org:
Warning: Never mix grid and pack in the same master window. Tkinter will happily spend the rest of your lifetime trying to negotiate a solution that both managers are happy with. Instead of waiting, kill the application, and take another look at your code. A common mistake is to use the wrong parent for some of the widgets.
The problem you describe of the Entry not changing position is because it is the only widget there, so the row(s) and column(s) in which the entry is are the only ones which do not have a width and height of 0. To make rows and columns without widgets take up space, use grid_rowconfigure(index, weight=x) where x is non-zero. An example is given in this answer.
Again from effbot.org:
weight=
A relative weight used to distribute additional space between rows. A row with the weight 2 will grow twice as fast as a row with weight 1. The default is 0, which means that the row will not grow at all.
Related
I have a Treeview table that calls records from an external MongoDB collection, which changes frequently, so I need to use an update loop to reflect the changes in my table. Whenever I highlight a row with the cursor, it displays the highlighted row with the selected background colour.
The problem is when the loop updates, the highlight disappears. I was wondering if someone could show me a method for keeping the highlight in place, regardless of the table updates?
If you run this code, you'll see what I mean:
from Tkinter import *
import Tkinter
import ttk as TTK
root = Tk()
style = TTK.Style()
tv = TTK.Treeview()
def highlight_row(event):
tree = event.widget
item = tree.identify_row(event.y)
tree.tk.call(tree, "tag", "remove", "highlight")
tree.tk.call(tree, "tag", "add", "highlight", item)
tv.grid(row=1, column=0)
tv.tag_configure('highlight', background='orange')
tv.bind("<Motion>", highlight_row)
style.configure("Treeview", font=(None, 12))
style.configure("Treeview", foreground= '#3F3F3F',
background= '#3F3F3F',
fieldbackground='#3F3F3F')
tv['columns'] = ('a','b','c')
tv.heading('a', text='a')
tv.column('a', anchor='center', width=100)
tv.heading('b', text='b')
tv.column('b', anchor='center', width=175)
tv.heading('c', text='c')
tv.column( 'c', anchor='center', width=90)
external_db = [{'x':1,'y':2, 'z':3},
{'x':4,'y':5, 'z':6},
{'x':7,'y':8, 'z':9}]
def update():
try:
for j in tv.get_children():
tv.delete(j)
for j, k in enumerate(external_db):
tv.insert('', 'end', text=str(j), values=(k['x'],k['y'],k['z']) ,tags = ('col'))
tv.tag_configure('col', foreground = 'white')
except Exception, e:
print e, 'Update Exception'
pass
root.after(1500,update)
update()
root.mainloop()
i.e. there is an orange background highlight that disappears every time root.after(1500,update) is called.
Any suggestions? Thanks.
#_____________________
Here is what root.configure(background="#3F3F3F") does for me:
I am facing some issue with the display style of Qcombobox items. Currently am hardcoding the data to be shown in the combobox.
here is the code :
self.Dummy = QComboBox(self)
self.Dummy.setGeometry(200,600, 350, 50)
self.Dummy.setStyleSheet("QComboBox {background-color: white;border-style: outset;" border-width: 2px;border-radius: 5px;border-color: #448aff; font: 12px; min-width: 10em; padding: 3px;}")
self.Dummy.addItems(["-Select-", "2", "3","4","5","6","7","8","9","0","11",])
The issue is that the dropdown "list" postion keeps changing after each selection. Here is the image of the issue am facing.
Below is my combobox
The list contains items <-Select->,2,3,4,5,6,7,8,9,0,11 , where <-Select-> will be the first element shown.
Now when I click the box, the box list "down" the elements and suppose I selected '2'. Then, if I try to select another item, the list will be dropped in a "downwards" direction. see below
Now, say if selected the last element from the items, '11'. Now if I try to select a new item by clicking on the box, the list will be popped "up" instead of down. see below
What should be done to fix it ? I don't think its an issue with stylesheet, without it also, this issue is happening. The reason I need this to be fixed is that when the list is popping up, its covering the label above it
What you see is a behavior that is OS and style dependent.
To avoid it, the best way is to subclass QComboBox and overwrite showPopup(), then we call the base class implementation (which is responsible of showing, resizing and positioning the popup view) and move it if necessary.
class Combo(QtWidgets.QComboBox):
def showPopup(self):
super().showPopup()
# find the widget that contains the list; note that this is *not* the view
# that QComboBox.view() returns, but what is used to show it.
popup = self.view().window()
rect = popup.geometry()
if not rect.contains(self.mapToGlobal(self.rect().center())):
# the popup is not over the combo, there's no need to move it
return
# move the popup at the bottom left of the combo
rect.moveTopLeft(self.mapToGlobal(self.rect().bottomLeft()))
# ensure that the popup is always inside the edges of the screen
# we use the center of the popup as a reference, since with multiple
# screens the combo might be between two screens, but that position
# could also be completely outside the screen, so the cursor position
# is used as a fallback to decide on what screen we'll show it
done = False
for i, pos in enumerate((rect.center(), QtGui.QCursor.pos())):
for screen in QtWidgets.QApplication.screens():
if pos in screen.geometry():
screen = screen.geometry()
if rect.x() < screen.x():
rect.moveLeft(screen.x())
elif rect.right() > screen.right():
rect.moveRight(screen.right())
if rect.y() < screen.y():
rect.moveTop(screen.y())
elif rect.bottom() > screen.bottom():
# if the popup goes below the screen, move its bottom
# *over* the combo, so that the its current selected
# item will always be visible
rect.moveBottom(self.mapToGlobal(QtCore.QPoint()).y())
done = True
break
if done:
break
popup.move(rect.topLeft())
This can also be done without subclassing (for example if you have many combos, you created the UI from Designer and don't want to use promoted widgets), but you'll have to remember to change all referencies to the combo.
class MyWindow(QtWidgets.QWidget):
def __init__(self):
# ...
self.combo = QtWidgets.QComboBox()
self.combo.showPopup = self.showPopupAndCheck
def showPopupAndCheck(self):
QtWidgets.QComboBox.showPopup(self.combo)
popup = self.view().window()
rect = popup.geometry()
if not rect.contains(self.combo.mapToGlobal(self.combo.rect().center())):
# the popup is not over the combo, there's no need to move it
return
# change everything from self to self.combo
Alternatively, if you want to keep this behavior consistent through all your program without always using the subclass, you can use some sort of monkey patching hack.
The advantage is that any QComboBox you create (even when loading UI files or creating a combo at runtime) will always use the new behavior.
Important: this MUST be at the very beginning of the main file of your program, possibly just after the import section.
from PyQt5 import QtCore, QtGui, QtWidgets
def customShowPopup(self):
# we can't use super(), because we're not in the class definition, but
# calling the class method with "self" as first argument is practically the
# same thing; note the underscore!
QtWidgets.QComboBox._showPopup(self)
popup = self.view().window()
# ... go on, exactly as above
# create a new reference to the showPopup method, which is the one we've used
# in the function above
QtWidgets.QComboBox._showPopup = QtWidgets.QComboBox.showPopup
# overwrite the original reference with the new function
QtWidgets.QComboBox.showPopup = customShowPopup
I'm using QtableView and QStandardItemModel to display logs on GUI to maintain proper spacing and filter logs. I created model and inserted data into it. Used QSortFilterProxyModel for filter strings.
self.tableView = QtGui.QTableView(self)
self.model = QtGui.QStandardItemModel(self)
self.proxy = QtGui.QSortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.tableView.setModel(self.proxy)
In a sec, nearly 100 logs are expected and should be shown on GUI. When new logs are appended, the view isn't auto scrolling and the slider stays only at the top. It doesn't give live feel for logging and user need to scroll manually to the end. So to overcome this, i used following syntax,
self.model.rowsInserted.connect(lambda: QtCore.QTimer.singleShot(5, self.tableView.scrollToBottom))
It gives live feel for logs, but the slider remains always in bottom and i'm not able to scroll up to see previous logs. Whenever i try to move the slider, it immediately comes down to bottom again. So this syntax doesn't meet my requirement. In QTextEdit, auto scrolling is proper and user friendly. I want the same scenario here on QtableView. Is there any alternative for auto scrolling which resembles like QTextEdit ?
To get the required behaviour, you can auto-scroll only when the previous scroll position is at the bottom. That way, whenever the user scrolls away from the bottom, auto-scrolling will be disabled; but when they scroll back to the bottom, auto-scrolling will be re-enabled. (NB: to quickly re-enable auto-scroll, right-click the scrollbar and select "Bottom" from the context menu).
Here is a simple demo:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.table = QtGui.QTableView(self)
self.model = QtGui.QStandardItemModel(self)
self.table.setModel(self.model)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table)
self._scroll = True
self.model.rowsAboutToBeInserted.connect(self.beforeInsert)
self.model.rowsInserted.connect(self.afterInsert)
def beforeInsert(self):
vbar = self.table.verticalScrollBar()
self._scroll = vbar.value() == vbar.maximum()
def afterInsert(self):
if self._scroll:
self.table.scrollToBottom()
def addRow(self):
self.model.appendRow([QtGui.QStandardItem(c) for c in 'ABC'])
if __name__ == '__main__':
app = QtGui.QApplication([''])
window = Window()
window.setGeometry(500, 50, 400, 300)
window.show()
timer = QtCore.QTimer()
timer.timeout.connect(window.addRow)
timer.start(200)
app.exec_()
I am trying to update canvas background at the same time running a binding event.
(from code)In do_popup popup menu will be implemented and conti will continuously change the canvas background color. how can i use popup option while canvas is updating continuously.
Sample code:
from Tkinter import *
root = Tk()
def do_popup(event,w2):
print w2 # inplace of print some popupmenu will be implemented
def conti():
idt=1
while idt==1:
w.config(bg="red") # in place of red it will be a random color
w.update_idletasks()
w= Canvas(root, width=600, height=600)
w.grid(row=0, column=0)
line1 = w.create_line(200,200,300,300, width=10, tags="line1", fill="black")
w.tag_bind(line1, "<Button-3>", lambda e, w2="test1" :do_popup(e,w2))
f = Frame(root)
f.grid(row=0, column=1, sticky=N)
f1=Button(f, text='visual', command=lambda :conti())
f1.grid(row=0, column=1,columnspan=1, sticky=W+N+S+E)
mainloop()
will multiprocessing work?
I am using windows 7 32 bit with python 2.7.3
Thanks in advance
When your script enters the mainloop then the events are executed.
To make reoccurring updates I like to do this:
def conti():
try:
w.config(bg="red") # in place of red it will be a random color
finally:
# start conti after 10 milliseconds,
root.after(10, conti)
# could also be 0ms to handle events
root.after(0, conti)
You can see root.mainloop as
while not (root.quit was called):
root.update()
This way wou can do:
root.quit()
and conti automatically stops.
There is no concurrency as with threads in mainloops.
But you can put a mainloop() somewhere when you create an own dialog box and conti will go on.
If you use the modules tkMessageBox(Python2) or tkinter.messagebox(Python3) then you conti should run while the dialog is open.
Does this answer your questions?
PS: do root.protocol("WM_DELETE_WINDOW", root.quit) to make the mainloop end when you close the window.
I'm trying to write a simple 'Simon' game, but I have hit a road block here, and honestly have no idea how to get around it.
So here, I made a class for the four buttons in the GUI:
class button:
def buttonclicked(self):
self.butclicked= True
def checkIfClicked(self):
if self.butclicked== True:
global pressed
pressed.append(self.color)
self.butclicked= False
def __init__(self, color1):
self.color= color1
self.button= tk.Button(root, text=' '*10, bg= self.color, command= self.buttonclicked)
self.button.pack(side='left')
self.butclicked=False
I then created four instances of this class in blue, red, yellow, and green as bb, rb, yb, and gb.
Once everything is packed into the Tk() module, it enters a while loop that appends a random color to a list activecolors. I try to use the following loop to wait until the list pressed is at least as long as the list activecolors before comparing the two to see if the user was correct:
while len(pressed)<len(activecolors):
sleep(.25)
print('In the check loop')
bb.checkIfClicked()
rb.checkIfClicked()
yb.checkIfClicked()
gb.checkIfClicked()
However, since it is stuck inside the while loop, the program can't tell that the button has been clicked. I thought adding the sleep method into the loop would allow the code to have time to do other things (such as process button clicks), but this is not the case. Any help is appreciated.
Here is the link to the full code, if you would like to see it. A warning though: it's not pretty.
Edit:
I ended up just changing the code to check the list only after a new button was clicked, telling the computer the code was ready. I've updated the Google Document if you'd like to see it.
You are making it too complicated. This program uses partial from functiontools to allow a variable to be passed to the function so one function handles all clicks (Python 2.7).
from Tkinter import *
from functools import partial
class ButtonsTest:
def __init__(self):
self.top = Tk()
self.top.title('Buttons Test')
self.top_frame = Frame(self.top, width =400, height=400)
self.colors = ("red", "green", "blue", "yellow")
self.colors_selected = []
self.num_clicks = 0
self.wait_for_number = 5
self.buttons()
self.top_frame.grid(row=0, column=1)
Button(self.top_frame, text='Exit',
command=self.top.quit).grid(row=2,column=1, columnspan=5)
self.top.mainloop()
##-------------------------------------------------------------------
def buttons(self):
for but_num in range(4):
b = Button(self.top_frame, text = str(but_num+1),
command=partial(self.cb_handler, but_num))
b.grid(row=1, column=but_num+1)
##----------------------------------------------------------------
def cb_handler( self, cb_number ):
print "\ncb_handler", cb_number
self.num_clicks += 1
this_color = self.colors[cb_number]
if (self.num_clicks > self.wait_for_number) \
and (this_color in self.colors_selected):
print "%s already selected" % (this_color)
self.colors_selected.append(this_color)
##===================================================================
BT=ButtonsTest()