I am trying to control some valves with a GUI and update the display when the button is pressed. The following code produces the correct initial display, but when I click one of the checkboxes I always get the value of i=2 and when unchecked I get i=0. I also have no idea how to update the docked window so that the message will toggle from close to open and vise versa. I do see the resultant value in self.states change in the array, but am always left with ['open', 'closed', 'open', 'closed', 'closed', 'closed'] Though this is changed the red closed on the right of the screen does not change to `open
class ApplicationWindow(gui.QMainWindow):
def __init__(self):
self.nCheckBoxes=6
self.states = ['closed']*self.nCheckBoxes
gui.QMainWindow.__init__(self)
self.setAttribute(core.Qt.WA_DeleteOnClose)
self.setWindowTitle("PiView")
self.file_menu = gui.QMenu('&File', self)
self.file_menu.addAction('&Quit', self.fileQuit, core.Qt.CTRL + core.Qt.Key_Q)
self.menuBar().addMenu(self.file_menu)
self.help_menu = gui.QMenu('&Help', self)
self.menuBar().addSeparator()
self.menuBar().addMenu(self.help_menu)
self.help_menu.addAction('&About', self.about)
self.help_menu.addAction('&Docs', self.doc)
self.main_widget = gui.QWidget(self)
l = gui.QVBoxLayout(self.main_widget)
self.dc = MyMplCanvas(self.main_widget, width=5, height=4, dpi=100)
l.addWidget(self.dc)
self.main_widget.setFocus()
self.setCentralWidget(self.main_widget)
self.dw = self.createDockWindows(self.dc)
self.statusBar().showMessage("Initialized", 2000)
def createDockWindows(self, MyMplCanvas):
"""
Create all the dock widgets
"""
self.drawValves()
self.drawGraphAdjustments()
self.drawStartAndStop()
def drawValves(self):
cboxes = gui.QDockWidget("Controls", self)
cboxes.setAllowedAreas(core.Qt.LeftDockWidgetArea)
w = gui.QWidget()
#layout = gui.QVBoxLayout()
layout = gui.QGridLayout()
w.setLayout(layout)
self.c = [0]*self.nCheckBoxes
# Create self.nCheckBoxes
msgBox = gui.QLabel()
msgBox.setText("States")
font = gui.QFont()
font.setBold(True)
msgBox.setFont(font)
msgBox.setStyleSheet("color: rgb(255,0,0)")
layout.addWidget(msgBox,0,1)
for i in range(self.nCheckBoxes):
self.c[i] = gui.QCheckBox("Valve " + str(i))
self.c[i].setChecked(False)
#self.c[i].stateChanged.connect(lambda:self.btnstate(self.c[i]))
#self.c[i].stateChanged.connect(self.checkedBox)
self.c[i].stateChanged.connect(lambda i: self.checkedBox(i))
layout.addWidget(self.c[i],i+1,0)
# Messages
msgBox = gui.QLabel()
msgBox.setText(self.states[i])
if self.states[i] == 'closed':
msgBox.setStyleSheet("color: rgb(255,0,0)")
else:
msgBox.setStyleSheet("color: rgb(0,255,0)")
layout.addWidget(msgBox,i+1,1)
spacerItem = gui.QSpacerItem(20,40, gui.QSizePolicy.Minimum, gui.QSizePolicy.Expanding)
layout.addItem(spacerItem)
cboxes.setWidget(w)
self.addDockWidget(core.Qt.LeftDockWidgetArea, cboxes)
def checkedBox(self,i):
sender = self.sender()
self.states[i] = 'open' if 'close' else 'close'
# For debugging
print i
print self.states
If I understand correctly I can just actuate the valve like I would normally in the function checkedBox.
This is what is displayed
I want to be able to click a checkbox and the message just to the right to change from closed to open, and vise versa.
The problem arises because the default value of i takes the value sent by the signal (ie state: Unchecked = 0, PartiallyChecked = 1, Checked = 2), to solve this we will add a further value to the function: Index of the checkbox. Also if you want to change the text of QLabel you must be able to access them so I have created a new list with labels. To make the change we will use the states list, for this implement the function updateLabels.
class ApplicationWindow(gui.QMainWindow):
def __init__(self):
self.nCheckBoxes=6
self.states = ['closed']*self.nCheckBoxes
gui.QMainWindow.__init__(self)
self.setAttribute(core.Qt.WA_DeleteOnClose)
self.setWindowTitle("PiView")
self.file_menu = gui.QMenu('&File', self)
#self.file_menu.addAction('&Quit', self.fileQuit, core.Qt.CTRL + core.Qt.Key_Q)
self.menuBar().addMenu(self.file_menu)
self.help_menu = gui.QMenu('&Help', self)
self.menuBar().addSeparator()
self.menuBar().addMenu(self.help_menu)
#self.help_menu.addAction('&About', self.about)
#self.help_menu.addAction('&Docs', self.doc)
self.main_widget = gui.QWidget(self)
l = gui.QVBoxLayout(self.main_widget)
self.dc = MyMplCanvas(self.main_widget, width=5, height=4, dpi=100)
l.addWidget(self.dc)
self.main_widget.setFocus()
self.setCentralWidget(self.main_widget)
self.dw = self.createDockWindows(self.dc)
self.statusBar().showMessage("Initialized", 2000)
def createDockWindows(self, MyMplCanvas):
"""
Create all the dock widgets
"""
self.drawValves()
#self.drawGraphAdjustments()
#self.drawStartAndStop()
def drawValves(self):
cboxes = gui.QDockWidget("Controls", self)
cboxes.setAllowedAreas(core.Qt.LeftDockWidgetArea)
w = gui.QWidget()
#layout = gui.QVBoxLayout()
layout = gui.QGridLayout()
w.setLayout(layout)
self.c = []
self.l = []
# Create self.nCheckBoxes
msgBox = gui.QLabel()
msgBox.setText("States")
font = gui.QFont()
font.setBold(True)
msgBox.setFont(font)
msgBox.setStyleSheet("color: rgb(255,0,0)")
layout.addWidget(msgBox,0,1)
for i in range(self.nCheckBoxes):
checkBox = gui.QCheckBox("Valve " + str(i))
checkBox.setChecked(False)
checkBox.stateChanged.connect(lambda state, p=i: self.checkedBox(state, p))
self.c.append(checkBox)
layout.addWidget(self.c[i],i+1,0)
msgBox = gui.QLabel()
msgBox.setText(self.states[i])
self.l.append(msgBox)
layout.addWidget(msgBox,i+1,1)
self.update_labels()
spacerItem = gui.QSpacerItem(20,40, gui.QSizePolicy.Minimum, gui.QSizePolicy.Expanding)
layout.addItem(spacerItem)
cboxes.setWidget(w)
self.addDockWidget(core.Qt.LeftDockWidgetArea, cboxes)
def checkedBox(self, state, p):
if state == core.Qt.Unchecked:
self.states[p] = 'closed'
else:
self.states[p] = 'open'
self.update_labels()
def update_labels(self):
for i in range(self.nCheckBoxes):
if self.states[i] == 'closed':
text = "closed"
styleSheet = "color: rgb(255,0,0)"
else:
text = "open"
styleSheet = "color: rgb(0,255,0)"
self.l[i].setText(text)
self.l[i].setStyleSheet(styleSheet)
Related
Forgive me in advance for my code, but I'm simply making this for friend of mine to automatically populate a GUI interface with song information, channel information for each song, and things such as images attached to the songs. Right now I'm only scraping from a playlist on Youtube and a playlist on Soundcloud. I have all of that properly working, but me being new to frontend development left me in a horrible spot to make a decent application for him. I had a lot in mind that I could have done, but now I'm simply creating buttons with each song title as the text. Here is an image of my progress. I still have to find a way to attach each image to each button for the on_enter event, but that is for later on. As you can see, I have a on_leave function commented out. I was using that to delete the self.image_window each time I left a button. Problem is even a minuscule amount of mouse movement would cause the window to be delete and recreated dozens of times. How do I make it static so when I am hovering over a button it doesn't spam create/delete the window?
Thanks!
from Tkinter import *
import json
import os
import webbrowser
class GUIPopulator(Frame):
def __init__(self, parent):
Frame.__init__(self)
self.parent = parent
self.configure(bg='PeachPuff2')
self.columnconfigure(20, weight=1)
self.rowconfigure(30, weight=1)
self.curtab = None
self.tabs = {}
self.pack(fill=BOTH, expand=1, padx=5, pady=5)
self.column = 0
self.row = 0
def on_enter(self, event):
self.image_window = Toplevel(self)
self.img_path = os.getcwd() + '/Rotating_earth_(large).gif'
self.img = PhotoImage(file=self.img_path)
#self.image_window.minsize(width=200, height=250)
self.image_window.title("Preview")
canvas = Canvas(self.image_window, width=200, height=200)
canvas.pack()
canvas.create_image(100, 100, image=self.img)
#def on_leave(self, enter):
def addTab(self, id):
tabslen = len(self.tabs)
tab = {}
if self.row < 30:
btn = Button(self, text=id,highlightbackground='PeachPuff2' ,command=lambda: self.raiseTab(id))
btn.grid(row=self.row, column=self.column, sticky=W+E)
btn.bind("<Enter>", self.on_enter)
#btn.bind("<Leave>", self.on_leave)
tab['id']=id
tab['btn']=btn
self.tabs[tabslen] = tab
self.raiseTab(id)
self.row +=1
else:
self.row = 0
self.column +=1
btn = Button(self, text=id,highlightbackground='PeachPuff2' ,command=lambda: self.raiseTab(id))
btn.grid(row=self.row, column=self.column, sticky=W+E)
tab['id']=id
tab['btn']=btn
self.tabs[tabslen] = tab
self.raiseTab(id)
def raiseTab(self, tabid):
with open(os.getcwd() + '/../PlaylistListener/CurrentSongs.json') as current_songs:
c_songs = json.load(current_songs)
print(tabid)
if self.curtab!= None and self.curtab != tabid and len(self.tabs)>1:
try:
#webbrowser.open(c_songs[tabid]['link'])
webbrowser.open_new('http://youtube.com')
except:
pass
def main():
root = Tk()
root.title('Playlist Scraper')
root.geometry("1920x1080+300+300")
t = GUIPopulator(root)
with open(os.getcwd() + '/../PlaylistListener/CurrentSongs.json') as current_songs:
c_songs = json.load(current_songs)
for song in c_songs:
t.addTab(song)
root.mainloop()
if __name__ == '__main__':
main()
Example of JSON file provided:
{
"F\u00d8RD - Shadows (feat. Samsaruh)": {
"page_title": "youtube",
"link": "youtube.com/watch?v=CNiV6Pne50U&index=32&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"id": "CNiV6Pne50U",
"channel": "youtube.com/watch?v=CNiV6Pne50U&index=32&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"image_path": [
"http://i4.ytimg.com/vi/CNiV6Pne50U/hqdefault.jpg",
"CNiV6Pne50U.jpg"
]
},
"Katelyn Tarver - You Don't Know (tof\u00fb remix)": {
"page_title": "youtube",
"link": "youtube.com/watch?v=7pPNv38JzD4&index=43&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"id": "7pPNv38JzD4",
"channel": "youtube.com/watch?v=7pPNv38JzD4&index=43&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"image_path": [
"http://i4.ytimg.com/vi/7pPNv38JzD4/hqdefault.jpg",
"7pPNv38JzD4.jpg"
]
},
"Illenium - Crawl Outta Love (feat. Annika Wells)": {
"page_title": "youtube",
"link": "youtube.com/watch?v=GprXUDZrdT4&index=7&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"id": "GprXUDZrdT4",
"channel": "youtube.com/watch?v=GprXUDZrdT4&index=7&list=PLkx04k4VGz1tH_pnRl_5xBU1BLE3PYuzd",
"image_path": [
"http://i4.ytimg.com/vi/GprXUDZrdT4/hqdefault.jpg",
"GprXUDZrdT4.jpg"
]
}
}
After some testing I have come up with some code I think you can use or is what you are looking for.
I have changes a few things and add some others.
1st we needed to create a place holder for the top window that we can use later in the code. So in the __init__ section of GUIPopulatior add self.image_window = None. We will talk about this part soon.
next I created the image path as a class attribute on init this can be changed later with update if need be.
next I added a bind() to the init that will help us keep the self.image_window placed in the correct location even if we move the root window. so we add self.parent.bind("<Configure>", self.move_me) to the __init__ section as well.
next we create the method move_me that we just created a bind for.
the way it is written it will only take effect if self.image_window is not equal to None this should prevent any errors while self.image_window is being used however I have not created the error handling to deal with what happens after the toplevel window is closed by the user. Its not difficult but I wanted to answer for the main issue at hand.
Here is the move_me method:
def move_me(self, event):
if self.image_window != None:
h = self.parent.winfo_height() # gets the height of the window in pixels
w = self.parent.winfo_width() # gets the width of the window in pixels
# gets the placement of the root window then uses height and width to
# calculate where to place the window to keep it at the bottom right.
self.image_window.geometry('+{}+{}'.format(self.parent.winfo_x() + w - 250,
self.parent.winfo_y() + h - 250))
next we need to modify the on_enter method to create the toplevel window if out class attribute self.image_window is equal to None if it is not equal to None then we can use the else portion of the if statement to just update the image.
Here is the modified on_enter method:
def on_enter(self, event):
if self.image_window == None:
self.image_window = Toplevel(self)
#this keeps the toplevel window on top of the program
self.image_window.attributes("-topmost", True)
h = self.parent.winfo_height()
w = self.parent.winfo_width()
self.image_window.geometry('+{}+{}'.format(self.parent.winfo_x() + w - 250,
self.parent.winfo_y() + h - 250))
self.img = PhotoImage(file=self.img_path)
self.image_window.title("Preview")
self.canvas = Canvas(self.image_window, width=200, height=200)
self.canvas.pack()
self.canv_image = self.canvas.create_image(100, 100, image=self.img)
else:
self.img = PhotoImage(file= self.img_path)
self.canvas.itemconfig(self.canv_image, image = self.img)
all that being said there are some other issues with your code that need to be addressed however this answer should point you in the right direction.
Below is a section of your code you need to replace:
class GUIPopulator(Frame):
def __init__(self, parent):
Frame.__init__(self)
self.parent = parent
self.configure(bg='PeachPuff2')
self.columnconfigure(20, weight=1)
self.rowconfigure(30, weight=1)
self.curtab = None
self.tabs = {}
self.pack(fill=BOTH, expand=1, padx=5, pady=5)
self.column = 0
self.row = 0
def on_enter(self, event):
self.image_window = Toplevel(self)
self.img_path = os.getcwd() + '/Rotating_earth_(large).gif'
self.img = PhotoImage(file=self.img_path)
#self.image_window.minsize(width=200, height=250)
self.image_window.title("Preview")
canvas = Canvas(self.image_window, width=200, height=200)
canvas.pack()
canvas.create_image(100, 100, image=self.img)
#def on_leave(self, enter):
With this:
class GUIPopulator(Frame):
def __init__(self, parent):
Frame.__init__(self)
self.parent = parent
self.configure(bg='PeachPuff2')
self.columnconfigure(20, weight=1)
self.rowconfigure(30, weight=1)
self.curtab = None
self.image_window = None
self.img_path = os.getcwd() + '/Rotating_earth_(large).gif'
self.tabs = {}
self.pack(fill=BOTH, expand=1, padx=5, pady=5)
self.parent.bind("<Configure>", self.move_me)
self.column = 0
self.row = 0
def move_me(self, event):
if self.image_window != None:
h = self.parent.winfo_height()
w = self.parent.winfo_width()
self.image_window.geometry('+{}+{}'.format(self.parent.winfo_x() + w - 250,
self.parent.winfo_y() + h - 250))
def on_enter(self, event):
if self.image_window == None:
self.image_window = Toplevel(self)
self.image_window.attributes("-topmost", True)
h = self.parent.winfo_height()
w = self.parent.winfo_width()
self.image_window.geometry('+{}+{}'.format(self.parent.winfo_x() + w - 250,
self.parent.winfo_y() + h - 250))
self.img = PhotoImage(file=self.img_path)
self.image_window.title("Preview")
self.canvas = Canvas(self.image_window, width=200, height=200)
self.canvas.pack()
self.canv_image = self.canvas.create_image(100, 100, image=self.img)
else:
self.img = PhotoImage(file= self.img_path)
self.canvas.itemconfig(self.canv_image, image = self.img)
I have created a custom matplotlib toolbar, and I'm working on the functions associated with the custom toolbar's buttons. One of the buttons functions will (eventually) return a list position that best represents a user's selected position from a plot. I was having a bit of difficulty making this work in my mind, so I made a simple example (trying to avoid using globals) where a label not associated with the toolbar is updated when the toolbar button is pressed.
class TrackPlotToolbar(NavigationToolbar2TkAgg):
toolitems = [t for t in NavigationToolbar2TkAgg.toolitems if
t[0] in ('Home', 'Pan', 'Zoom', 'Save')]
toolitems.append(('Trace', 'Trace Track Position', 'Trace', 'Trace_old'))
def __init__(self, plotCanvas, frame):
self.TraceListOld = []
self.TraceListNew = []
NavigationToolbar2TkAgg.__init__(self, plotCanvas, frame)
def set_message(self, msg):
pass
def Trace_old(self):
gui.infoLabel.text = "abrakadabra"
gui.infoLabel.update()
return 1
class gui(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.grid()
self.parent = parent
self.infoLabel = Label(master = self, text = 'magic')
self.initUI()
def initUI(self):
TestPlot = FigureCanvasTkAgg(TestFig, self)
TestPlot.get_tk_widget().grid(column = 0,\
row = 0, columnspan = 3, rowspan = 5)
TestFrame = Frame(self)
TestFrame.grid(column = 2, row =6, columnspan = 3)
shockToolbar = TrackPlotToolbar(TestPlot,TestFrame)
shockToolbar.update()
self.infoLabel.grid(column = 2, row = 7)
def main():
root = Tk()
app = gui(root)
root.mainloop()
if __name__ == '__main__':
main()
Am I taking the wrong approach? Is it possible to inquire for new data on an event associated with a class inside of the parent class?
I have a window which has two dropdown lists (country and club) which should never be visible at the same time.
When the app loads, neither should be visible. A third dropdown list (tournament type), with nothing selected by default, decides which of the club or country lists should display. Clicking on a button at the top of the screen populates the dropdown lists, selecting the appropriate tournament type and the associated club/or country.
What actually happens: When it loads, both dropdown lists are visible. When the tournament type is selected, both lists are visible. When I click the button at the top the club list is available and the country list is not. The latter is exactly as it should be.
I'm using the same function (set_visible()) to show or hide the lists in each case so I'm at a loss as to why it works in one case but not in the other two.
The code below should run. The bit after #Add Tournaments Tab creates the widgets and unsuccessfully tries to hide the combo boxes. The function on_type_combo changed unsuccessfully tries to hide one of the combo boxes. The function on_tournament_details successfully hides the appropriate box.
#!/usr/bin/python
# coding=utf-8
from gi.repository import Gtk
import wikipedia
class NotebookWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Rugby Database")
#Initialise data
self.country_store = Gtk.ListStore(int, str, str)
self.club_store = Gtk.ListStore(int, str, str, int, int)
self.tournament_store = Gtk.ListStore(int, str, int, str, int)
self.cur_id = 0
self.cur_club = ''
self.cur_club_id = 0
self.update_club_id = 0
self.update_tournament_id = 0
self.initialise_lists()
#Create Application Window
self.set_border_width(10)
self.set_default_size(800, 600)
self.set_position(Gtk.WindowPosition.CENTER)
#Add external container (box)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add(vbox)
#Add tabbed window
self.nbook = Gtk.Notebook()
vbox.pack_start(self.nbook, True, True, 0)
self.nbook.show()
#Add Tournaments tab
frame = Gtk.Frame()
frame.show()
self.t_type_id = -1
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.tournament_box = Gtk.FlowBox()
self.tournament_box.set_valign(Gtk.Align.START)
self.tournament_box.set_max_children_per_line(4)
self.tournament_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.tournament_box.set_activate_on_single_click(True)
vbox.pack_start(self.tournament_box, True, True, 0)
self.wiki_label = Gtk.Label()
self.wiki_label.set_line_wrap(True)
vbox.add(self.wiki_label)
tournament_grid = Gtk.Grid()
tournament_grid.set_column_spacing(5)
tournament_label = Gtk.Label(" Tournament Name: ")
tournament_logo_label = Gtk.Label("Logo:")
self.tournament_logo_entry = Gtk.Entry()
self.tournament_entry = Gtk.Entry()
self.t_type_combo = Gtk.ComboBoxText()
self.t_holder_combo_country = Gtk.ComboBoxText()
self.t_holder_combo_club = Gtk.ComboBoxText()
self.t_holder_combo_country = Gtk.ComboBox.new_with_model(self.country_store)
renderer_text = Gtk.CellRendererText()
self.t_holder_combo_country.pack_start(renderer_text, True)
self.t_holder_combo_country.add_attribute(renderer_text, "text", 1)
self.t_holder_combo_club = Gtk.ComboBox.new_with_model(self.club_store)
renderer_text = Gtk.CellRendererText()
self.t_holder_combo_club.pack_start(renderer_text, True)
self.t_holder_combo_club.add_attribute(renderer_text, "text", 1)
self.t_type_combo.append_text("Club")
self.t_type_combo.append_text("International")
self.t_type_combo.connect("changed", self.on_type_combo_changed)
renderer_text = Gtk.CellRendererText()
self.t_type_combo.pack_start(renderer_text, True)
self.t_type_combo.add_attribute(renderer_text, "text", 1)
type_label = Gtk.Label(" Type: ")
holder_label = Gtk.Label(" Holder: ")
tournament_add = Gtk.Button(" Save ")
tournament_grid.add(tournament_label)
tournament_grid.add(self.tournament_entry)
tournament_grid.add(type_label)
tournament_grid.add(self.t_type_combo)
tournament_grid.add(tournament_logo_label)
tournament_grid.add(self.tournament_logo_entry)
tournament_grid.add(holder_label)
tournament_grid.add(self.t_holder_combo_club)
tournament_grid.add(self.t_holder_combo_country)
tournament_grid.add(tournament_add)
vbox.add(tournament_grid)
self.t_holder_combo_club.set_visible(False)
self.t_holder_combo_country.set_visible(False)
self.tournament_message = Gtk.Label("\n")
vbox.add(self.tournament_message)
label = Gtk.Label()
label.set_markup("<b><big>Tournaments</big></b>")
frame.add(vbox)
self.nbook.append_page(frame, label)
self.load_boxes()
##### Function definitions #####
def initialise_lists(self):
self.country_store.clear()
self.country_store.append([1, 'Ireland', ''])
self.club_store.clear()
self.club_store.append([1, 'Leinster', '',1,1])
self.tournament_store.clear()
self.tournament_store.append([1, 'Pro 12', 1,'',1])
def reset_forms(self):
self.tournament_entry.set_text('')
self.update_club_id = 0
self.update_tournament_id = 0
self.tournament_entry.set_text('')
self.tournament_logo_entry.set_text('')
self.wiki_label.set_text('\n')
def load_boxes(self):
self.tournament_box.foreach(lambda widget: self.tournament_box.remove(widget))
for tournament in range(0, len(self.tournament_store)):
button = Gtk.Button(self.tournament_store[tournament][1])
self.tournament_box.add(button)
button.connect('clicked', self.on_tournament_details, tournament)
self.tournament_box.show_all()
def on_club_combo_changed(self, combo):
tree_iter = combo.get_active_iter()
if tree_iter is not None:
model = combo.get_model()
self.cur_club_id = model[tree_iter][0]
def on_type_combo_changed(self, combo):
tree_iter = combo.get_active_iter()
if tree_iter is not None:
model = combo.get_model()
self.t_type_id = model[tree_iter][0]
if self.t_type_id is "Club":
self.t_holder_combo_country.set_visible(False)
self.t_holder_combo_club.set_visible(True)
elif self.t_type_id is "International":
self.t_holder_combo_club.set_visible(False)
self.t_holder_combo_country.set_visible(True)
def on_tournament_details(self, button, tournament):
self.tournament_entry.set_text(self.tournament_store[tournament][1])
self.t_type_combo.set_active(self.tournament_store[tournament][2]-1)
self.tournament_logo_entry.set_text(self.tournament_store[tournament][3])
self.update_tournament_id = self.tournament_store[tournament][0]
self.tournament_message.set_text('\n')
self.wiki_label.set_text(wikipedia.summary(self.tournament_store[tournament][1], sentences=2))
if self.t_type_id == "Club":
self.t_holder_combo_country.set_visible(False)
self.t_holder_combo_club.set_visible(True)
self.t_holder_combo_club.set_active(self.tournament_store[tournament][4]-1)
elif self.t_type_id == "International":
self.t_holder_combo_club.set_visible(False)
self.t_holder_combo_country.set_visible(True)
self.t_holder_combo_country.set_active(self.tournament_store[tournament][4]-1)
win = NotebookWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
Tell the dropdown lists not to show, when you call win.show_all(). This can be accomblished by calling set_no_show_all on the widget, that should not be shown.
probably I'm less than a newbie in both python and GTK. Here my problem:
I've created a class to display a windows with a search bar and a treeview.
I fill the tree reading from an SQLITEDB.
Fine.
I'd like to use the search bar in order to select an item in the treeview (so performing, a db search and fill the treeview with the result)
I'm blocked on the callback since I cannot understand how to pass the user inserted data to a specific function.
Below some lines from my (STUPID) code.
class SecondWin:
def __init__(self):
self.win = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.win.connect("delete_event", self.delete_event)
self.win.set_title("Gmail Phonebook")
self.win.set_size_request(600, 300) #Larghez1za, Altezza
self.win.set_border_width(8)
self.win.set_position(gtk.WIN_POS_CENTER)
self.win.connect("destroy", self.destroy)
self.table = gtk.Table(8, 8,True)
self.cerca_bottone = gtk.Button("Cerca")
self.cerca_bottone.set_size_request(70, 30) #Larghezza, Altezza
self.cerca_bottone.connect( "clicked", self.cercainrubrica) #Call back sul bottone
self.table.attach(self.cerca_bottone, 0, 1, 0, 1,gtk.SHRINK,gtk.SHRINK)
# Search BAR
self.hbox = gtk.HBox()
self.entry = gtk.Entry(30)
self.entry.connect("activate", self.cercainrubrica) #call back su enter
self.hbox.pack_end(self.entry)
self.table.attach(self.hbox, 1, 8, 0, 1,gtk.EXPAND|gtk.FILL|gtk.SHRINK,
gtk.EXPAND|gtk.FILL|gtk.SHRINK,0,0)
self.vbox = gtk.VBox(False, 8)
self.sw = gtk.ScrolledWindow()
self.sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.vbox.pack_start(self.sw, True, True, 0)
self.table.attach(self.vbox, 1, 8, 1, 9,gtk.EXPAND|gtk.FILL|gtk.SHRINK,
gtk.EXPAND|gtk.FILL|gtk.SHRINK,10,10)
rubrica=mydb.find_record_complex("%", colonne_test[0])
store = self.create_model(rubrica) #THIS WORK FINE DISPLAYING THE SELECTION
HERE MY FUNCION
self.cercainrubrica ??
treeView=gtk.TreeView(store)
treeView.connect("row-activated", self.on_activated)
treeView.set_rules_hint(True)
self.sw.add(treeView)
self.create_columns(treeView,nomi_colonne)
self.statusbar = gtk.Statusbar()
self.win.add(self.table)
self.win.show_all()
THIS IS MY CALL BACK
def cercainrubrica( self, w, data=None):
name = self.entry.get_text()
capperi=mydb.find_record_complex(name, colonne_test[0])
store = self.create_model(capperi)
return store
def delete_event(self, widget, event, data=None):
return gtk.FALSE
def destroy(self, widget, data=None):
return gtk.main_quit()
def main(self):
gtk.main()
def create_model(self,records):
store = gtk.ListStore(str, str, str,str)
for record in records:
store.append([record[1], record[2], record[3],record[4]])
return store
def create_columns(self, treeView,intestazione_colonne):
i = 0
for nomecolonna in intestazione_colonne:
rendererText = gtk.CellRendererText()
column = gtk.TreeViewColumn(intestazione_colonne[i], rendererText, text=i)
column.set_sort_column_id(i)
treeView.append_column(column)
i += 1
def on_activated(self, widget, row, col):
model = widget.get_model()
text = model[row][0] + ", " + model[row][1] + ", " + model[row][2]
self.statusbar.push(0, text)
if __name__ == "__main__":
second = SecondWin()
second.main()
ANY HELP IS REALLY APPRECIATED
Not 100% sure what you want to do but if you want to filter what the treeview is showing based on the entered text, you should take a look at GtkTreeModelFilter. You can use it as a TreeModel and set your own VisibleFunc that decides if a row should be visible or not based on the entered text. When the text changes, just call refilter(): that will call VisibleFunc() for every row.
Sorry for not having a python example or docs for you, I hope that still helps...
Is there any way to use ttk Treeview with editable rows?
I mean it should work more like a table. For example on double click on the item make the #0 column 'editable'.
If this isn't possible, any way to allow mouse selecting on the item would be just fine. I haven't found any mention of this in tkdocs or other documents.
After long research I haven't found such feature so I guess there's any. Tk is very simple interface, which allows programmer to build 'high-level' features from the basics. So my desired behaviour this way.
def onDoubleClick(self, event):
''' Executed, when a row is double-clicked. Opens
read-only EntryPopup above the item's column, so it is possible
to select text '''
# close previous popups
# self.destroyPopups()
# what row and column was clicked on
rowid = self._tree.identify_row(event.y)
column = self._tree.identify_column(event.x)
# get column position info
x,y,width,height = self._tree.bbox(rowid, column)
# y-axis offset
# pady = height // 2
pady = 0
# place Entry popup properly
text = self._tree.item(rowid, 'text')
self.entryPopup = EntryPopup(self._tree, rowid, text)
self.entryPopup.place( x=0, y=y+pady, anchor=W, relwidth=1)
This is method within a class which composes ttk.Treeview as self._tree
And EntryPopup is then very simple sub-class of Entry:
class EntryPopup(Entry):
def __init__(self, parent, iid, text, **kw):
''' If relwidth is set, then width is ignored '''
super().__init__(parent, **kw)
self.tv = parent
self.iid = iid
self.insert(0, text)
# self['state'] = 'readonly'
# self['readonlybackground'] = 'white'
# self['selectbackground'] = '#1BA1E2'
self['exportselection'] = False
self.focus_force()
self.bind("<Return>", self.on_return)
self.bind("<Control-a>", self.select_all)
self.bind("<Escape>", lambda *ignore: self.destroy())
def on_return(self, event):
self.tv.item(self.iid, text=self.get())
self.destroy()
def select_all(self, *ignore):
''' Set selection on the whole text '''
self.selection_range(0, 'end')
# returns 'break' to interrupt default key-bindings
return 'break'
You could also pop up a tool window with the editable fields listed with Entries to update the values. This example has a treeview with three columns, and does not use subclasses.
Bind your double click to this:
def OnDoubleClick(self, treeView):
# First check if a blank space was selected
entryIndex = treeView.focus()
if '' == entryIndex: return
# Set up window
win = Toplevel()
win.title("Edit Entry")
win.attributes("-toolwindow", True)
####
# Set up the window's other attributes and geometry
####
# Grab the entry's values
for child in treeView.get_children():
if child == entryIndex:
values = treeView.item(child)["values"]
break
col1Lbl = Label(win, text = "Value 1: ")
col1Ent = Entry(win)
col1Ent.insert(0, values[0]) # Default is column 1's current value
col1Lbl.grid(row = 0, column = 0)
col1Ent.grid(row = 0, column = 1)
col2Lbl = Label(win, text = "Value 2: ")
col2Ent = Entry(win)
col2Ent.insert(0, values[1]) # Default is column 2's current value
col2Lbl.grid(row = 0, column = 2)
col2Ent.grid(row = 0, column = 3)
col3Lbl = Label(win, text = "Value 3: ")
col3Ent = Entry(win)
col3Ent.insert(0, values[2]) # Default is column 3's current value
col3Lbl.grid(row = 0, column = 4)
col3Ent.grid(row = 0, column = 5)
def UpdateThenDestroy():
if ConfirmEntry(treeView, col1Ent.get(), col2Ent.get(), col3Ent.get()):
win.destroy()
okButt = Button(win, text = "Ok")
okButt.bind("<Button-1>", lambda e: UpdateThenDestroy())
okButt.grid(row = 1, column = 4)
canButt = Button(win, text = "Cancel")
canButt.bind("<Button-1>", lambda c: win.destroy())
canButt.grid(row = 1, column = 5)
Then confirm the changes:
def ConfirmEntry(self, treeView, entry1, entry2, entry3):
####
# Whatever validation you need
####
# Grab the current index in the tree
currInd = treeView.index(treeView.focus())
# Remove it from the tree
DeleteCurrentEntry(treeView)
# Put it back in with the upated values
treeView.insert('', currInd, values = (entry1, entry2, entry3))
return True
Here's how to delete an entry:
def DeleteCurrentEntry(self, treeView):
curr = treeView.focus()
if '' == curr: return
treeView.delete(curr)
I have tried #dakov solution but it did not work for me since my treeView has multiple columns and for few more reasons. I made some changes that enhanced it so here is my version
class Tableview(ttk.Treeview):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
tv.bind("<Double-1>", lambda event: self.onDoubleClick(event))
def onDoubleClick(self, event):
''' Executed, when a row is double-clicked. Opens
read-only EntryPopup above the item's column, so it is possible
to select text '''
# close previous popups
try: # in case there was no previous popup
self.entryPopup.destroy()
except AttributeError:
pass
# what row and column was clicked on
rowid = self.identify_row(event.y)
column = self.identify_column(event.x)
# handle exception when header is double click
if not rowid:
return
# get column position info
x,y,width,height = self.bbox(rowid, column)
# y-axis offset
pady = height // 2
# place Entry popup properly
text = self.item(rowid, 'values')[int(column[1:])-1]
self.entryPopup = EntryPopup(self, rowid, int(column[1:])-1, text)
self.entryPopup.place(x=x, y=y+pady, width=width, height=height, anchor='w')
The EntryPopup class
class EntryPopup(ttk.Entry):
def __init__(self, parent, iid, column, text, **kw):
ttk.Style().configure('pad.TEntry', padding='1 1 1 1')
super().__init__(parent, style='pad.TEntry', **kw)
self.tv = parent
self.iid = iid
self.column = column
self.insert(0, text)
# self['state'] = 'readonly'
# self['readonlybackground'] = 'white'
# self['selectbackground'] = '#1BA1E2'
self['exportselection'] = False
self.focus_force()
self.select_all()
self.bind("<Return>", self.on_return)
self.bind("<Control-a>", self.select_all)
self.bind("<Escape>", lambda *ignore: self.destroy())
def on_return(self, event):
rowid = self.tv.focus()
vals = self.tv.item(rowid, 'values')
vals = list(vals)
vals[self.column] = self.get()
self.tv.item(rowid, values=vals)
self.destroy()
def select_all(self, *ignore):
''' Set selection on the whole text '''
self.selection_range(0, 'end')
# returns 'break' to interrupt default key-bindings
return 'break'
from tkinter import ttk
from tkinter import *
root = Tk()
columns = ("Items", "Values")
Treeview = ttk.Treeview(root, height=18, show="headings", columns=columns) #
Treeview.column("Items", width=200, anchor='center')
Treeview.column("Values", width=200, anchor='center')
Treeview.heading("Items", text="Items")
Treeview.heading("Values", text="Values")
Treeview.pack(side=LEFT, fill=BOTH)
name = ['Item1', 'Item2', 'Item3']
ipcode = ['10', '25', '163']
for i in range(min(len(name), len(ipcode))):
Treeview.insert('', i, values=(name[i], ipcode[i]))
def treeview_sort_column(tv, col, reverse):
l = [(tv.set(k, col), k) for k in tv.get_children('')]
l.sort(reverse=reverse)
for index, (val, k) in enumerate(l):
tv.move(k, '', index)
tv.heading(col, command=lambda: treeview_sort_column(tv, col, not reverse))
def set_cell_value(event):
for item in Treeview.selection():
item_text = Treeview.item(item, "values")
column = Treeview.identify_column(event.x)
row = Treeview.identify_row(event.y)
cn = int(str(column).replace('#', ''))
rn = int(str(row).replace('I', ''))
entryedit = Text(root, width=10 + (cn - 1) * 16, height=1)
entryedit.place(x=16 + (cn - 1) * 130, y=6 + rn * 20)
def saveedit():
Treeview.set(item, column=column, value=entryedit.get(0.0, "end"))
entryedit.destroy()
okb.destroy()
okb = ttk.Button(root, text='OK', width=4, command=saveedit)
okb.place(x=90 + (cn - 1) * 242, y=2 + rn * 20)
def newrow():
name.append('to be named')
ipcode.append('value')
Treeview.insert('', len(name) - 1, values=(name[len(name) - 1], ipcode[len(name) - 1]))
Treeview.update()
newb.place(x=120, y=(len(name) - 1) * 20 + 45)
newb.update()
Treeview.bind('<Double-1>', set_cell_value)
newb = ttk.Button(root, text='new item', width=20, command=newrow)
newb.place(x=120, y=(len(name) - 1) * 20 + 45)
for col in columns:
Treeview.heading(col, text=col, command=lambda _col=col: treeview_sort_column(Treeview, _col, False))
root.mainloop()
After so much research while doing my project got this code, it helped me a lot.
Double click on the element you want to edit, make the required change and click 'OK' button
I think this is what exactly you wanted
#python #tkinter #treeview #editablerow
New row
Editable row
This is just for creating a tree for the specified path that is set in the constructor. you can bind your event to your item on that tree. The event function is left in a way that the item could be used in many ways. In this case, it will show the name of the item when double clicked on it. Hope this helps somebody.
import ttk
from Tkinter import*
import os*
class Tree(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
path = "/home/...."
self.initUI(path)
def initUI(self, path):
self.parent.title("Tree")
self.tree = ttk.Treeview(self.parent)
self.tree.bind("<Double-1>", self.itemEvent)
yScr = ttk.Scrollbar(self.tree, orient = "vertical", command = self.tree.yview)
xScr = ttk.Scrollbar(self.tree, orient = "horizontal", command = self.tree.xview)
self.tree.configure(yscroll = yScr.set, xScroll = xScr.set)
self.tree.heading("#0", text = "My Tree", anchor = 'w')
yScr.pack(side = RIGHT, fill = Y)
pathy = os.path.abspath(path)
rootNode = self.tree.insert('', 'end', text = pathy, open = True)
self.createTree(rootNode, pathy)
self.tree.pack(side = LEFT, fill = BOTH, expand = 1, padx = 2, pady = 2)
self.pack(fill= BOTH, expand = 1)
def createTree(self, parent, path)
for p in os.listdir(path)
pathy = os.path.join(path, p)
isdir = os.path.isdir(pathy)
oid = self.tree.insert(parent, 'end' text = p, open = False)
if isdir:
self.createTree(oid, pathy)
def itemEvent(self, event):
item = self.tree.selection()[0] # now you got the item on that tree
print "you clicked on", self.tree.item(item,"text")
def main():
root = Tk.Tk()
app = Tree(root)
root.mainloop()
if __name__ == '__main__'
main()
You should not do this manually
there are ready to use pack that have this Feature and many more such as
tkintertable
it have some insane features
there is also pygubu-editable-treeview
if you are intrested in pygubu,
as for the the reason you shouldnt code your own ,
in order to do a good treeview you will need to build more Feature that make your gui easier to use
however such Feature takes hundred lines of code to create.(takes a long time to get right)
unless you are making a custom TREE-View-widget,it doesnot worth the effort.
I don't know about making the row editable, but to capture clicking on a row, you use the <<TreeviewSelect>> virtual event. This gets bound to a routine with the bind() method, then you use the selection() method to get the ids of the items selected.
These are snippets from an existing program, but show the basic sequence of calls:
# in Treeview setup routine
self.tview.tree.bind("<<TreeviewSelect>>", self.TableItemClick)
# in TableItemClick()
selitems = self.tview.tree.selection()
if selitems:
selitem = selitems[0]
text = self.tview.tree.item(selitem, "text") # get value in col #0