Gtk Python Callback - python

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...

Related

PyGObject page switching with buttons

I want to switch pages with the help of buttons in Gtk.Stack. There are 3 pages, and the title bar of the application has one forward and one back button. I want it to go to the next page when the forward button is pressed, and to go to the previous page when the back button is pressed. Its current state can only switch between page 1 and page 2.
import gi, os
gi.require_version("Gtk", "3.0")
gi.require_version("Handy", "1")
from gi.repository import Gtk, Handy
Handy.init()
class MyWindow(Handy.Window):
def __init__(self):
super().__init__(title="Hello World")
self.set_default_size(500, 300)
# WindowHandle
self.handle = Handy.WindowHandle()
self.add(self.handle)
# Box
self.winbox = Gtk.Box(spacing=6, orientation=Gtk.Orientation.VERTICAL)
self.handle.add(self.winbox)
# Headerbar
self.hb = Handy.HeaderBar()
self.hb.set_show_close_button(True)
self.hb.props.title = "Stack Example"
self.winbox.pack_start(self.hb, False, True, 0)
# Stack
self.stack = Gtk.Stack()
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.winbox.pack_start(self.stack, True, True, 0)
# Labels
self.label = Gtk.Label(label="Page 1")
self.stack.add_titled(self.label, "page0", "Label")
self.label = Gtk.Label(label="Page 2")
self.stack.add_titled(self.label, "page1", "Label")
self.label = Gtk.Label(label="Page 3")
self.stack.add_titled(self.label, "page2", "Label")
# Headerbar button 1
self.button = Gtk.Button()
self.button = Gtk.Button.new_from_icon_name("pan-start-symbolic", Gtk.IconSize.MENU)
self.hb.pack_start(self.button)
self.button.connect('clicked', self.on_button1_clicked)
# Headerbar button 2
self.button2 = Gtk.Button()
self.button2 = Gtk.Button.new_from_icon_name("pan-end-symbolic", Gtk.IconSize.MENU)
self.hb.pack_start(self.button2)
self.button2.connect("clicked", self.on_button2_clicked)
def on_button1_clicked(self, widget):
self.stack.set_visible_child_name("page1")
def on_button2_clicked(self, widget):
self.stack.set_visible_child_name("page2")
win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
I don't know if there's an easy way to make visible the next child in a GtkStack or if another container has this functionality apart from GtkAssistant.
Nevertheless there's multiple ways you can implement this yourself. Either like so:
def on_button1_clicked(self, widget):
pages = self.stack.get_children()
cur_page = self.stack.get_visible_child()
i = pages.index(cur_page)
if i == 0: return
self.stack.set_visible_child(pages[i-1])
def on_button2_clicked(self, widget):
pages = self.stack.get_children()
cur_page = self.stack.get_visible_child()
i = pages.index(cur_page)
if i == len(pages) - 1: return
self.stack.set_visible_child(pages[i+1])
where you get the stack's children with GtkContainer.get_children(), find the index of the current visible child and then plus/minus one to get the next/prev page.
Caveat: I'm not sure if get_children() always returns the child widgets in the order they are added.
Alternatively in your __init__() function, you can create a list to store your page names/widgets e.g. self.page_names = ['page0', 'page1', 'page2']. And then you can do:
def on_button1_clicked(self, widget):
cur_page_name = self.stack.get_visible_child_name()
i = self.page_names.index(cur_page_name)
if i == 0: return
self.stack.set_visible_child_name(self.page_names[i-1])
Or maybe you extract the page number from the child name (e.g. 0 from page0) using RegEx and generate the next page's name. There's many ways to accomplish this, I personally would keep a variable of all the pages and use that to determine which page is next/prev.

How do I implement a drag and drop system in a Gtk TreeView?

I am teaching myself Python Gtk+3 using the readthedocs tutorial. I'm currently working on drag and drop and want to create a TreeView where every line is dragable. To do this, my code reads in a list of telecommands and puts them in a TreeView. This TreeView works and I can drag the single lines around. When I drop them into the designated drop area however, nothing happens.
My current goal is for the program to just print the text "Received data", whenever I drop something. Later on, I want the program to print out the single commands, that have been dropped.
My code looks as follows(I followed this example):
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk
from csv import reader
# get data from file and format it
with open ("TC_list.csv", "r") as read_command_list:
csv_reader = reader(read_command_list)
list_of_commands = list(csv_reader)
list_header = list_of_commands.pop(0)
for command in list_of_commands:
command[0] = int(command[0])
command[1] = int(command[1])
DRAG_ACTION = Gdk.DragAction.COPY
class DragDropWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Drag and Drop Demo")
self.grid = Gtk.Grid()
self.grid.set_column_homogeneous(True)
self.grid.set_row_homogeneous(True)
self.add(self.grid)
self.grid.set_size_request(1200, 1000)
self.listview = DragSourceListView()
self.drop_area = DropArea()
self.grid.attach(self.listview, 0, 0, 1, 1)
self.grid.attach_next_to(self.drop_area, self.listview, \
Gtk.PositionType.BOTTOM, 1, 1)
class DragSourceListView(Gtk.Grid):
def __init__(self):
Gtk.Grid.__init__(self)
# Creating ListStore model
self.telecommand_liststore = Gtk.ListStore(int, int, str, str)
for telecommand_ref in list_of_commands:
self.telecommand_liststore.append(list(telecommand_ref))
self.current_filter_telecommand = None
# Creating the filter, feeding it with the liststore model
self.telecommand_filter = self.telecommand_liststore.filter_new()
# setting the filter function
self.telecommand_filter.set_visible_func(self.telecommand_filter_func)
# creating the treeview, making it use the filter a model, adding columns
self.treeview = Gtk.TreeView.new_with_model(Gtk.TreeModelSort(self.telecommand_filter))
for i, column_title in enumerate(list_header):
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(column_title, renderer, text=i)
column.set_sort_column_id(i)
self.treeview.append_column(column)
# set up drag-source
self.treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], DRAG_ACTION)
self.treeview.connect("drag-data-get", self.on_drag_data_get)
# setting up layout, treeview in scrollwindow
self.scrollable_treelist = Gtk.ScrolledWindow()
self.scrollable_treelist.set_vexpand(True)
self.scrollable_treelist.set_hexpand(True)
self.attach(self.scrollable_treelist, 0, 1, 8, 10)
self.scrollable_treelist.add(self.treeview)
def telecommand_filter_func(self, model, iter, data):
if (
self.current_filter_telecommand is None
or self.current_filter_telecommand == "None"
):
return True
else:
return model[iter][0] == self.current_filter_telecommand
def on_drag_data_get(self, widget, drag_context, dat, info, time):
selected_tc = self.get_selected_items()[0]
selected_iter = self.get_model().get_iter(selected_tc)
class DropArea(Gtk.Label):
def __init__(self):
Gtk.Label.__init__(self)
self.set_label("Drop Area")
self.drag_dest_set(Gtk.DestDefaults.ALL, [], DRAG_ACTION)
self.connect("drag-data-received", self.on_drag_data_received)
def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
print("Received data")
win = DragDropWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
What am I overlooking here? Why doesn't the drop-area do anything?
Try to set gtk_tree_view_set_reorderable to True.

Passing Information from a child to a parent on an event

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?

Python GTK+ 3: How to set row in TreeView active

I have a Gtk.ComboBoxText that should be used as a trigger to activate a row in a Gtk.TreeView. I know how to trigger the combo box by activating the respective row in the tree view. But vice versa is beyond my scope. I learned that I need to pass Gtk.TreePath and Gtk.TreeViewColumn to the function row_activated(), but I don't know how to implement these correctly and where to put the row ID in my function self.combo_changed(). This is an example of the issue:
from gi.repository import Gtk
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(200, 100)
self.set_border_width(10)
self.grid = Gtk.Grid(row_spacing=10)
self.add(self.grid)
self.combo = Gtk.ComboBoxText()
self.combo.connect("changed", self.combo_changed)
self.grid.attach(self.combo, 0, 0, 1, 1)
self.liststore = Gtk.ListStore()
self.liststore.set_column_types([int, str])
self.treeview = Gtk.TreeView(self.liststore)
self.treeview.set_activate_on_single_click(True)
self.treeview.connect('row-activated', self.list_changed)
self.grid.attach(self.treeview, 0, 1, 1, 1)
cols = ["ID", "Animal"]
self.treeviewcolumn = []
self.cellrenderertext = []
for i in range(len(cols)):
self.cellrenderertext.append(Gtk.CellRendererText())
self.treeviewcolumn.append(Gtk.TreeViewColumn(cols[i]))
self.treeviewcolumn[i].pack_start(self.cellrenderertext[i], True)
self.treeviewcolumn[i].add_attribute(self.cellrenderertext[i], "text", i)
self.treeview.append_column(self.treeviewcolumn[i])
animals = ["Dog", "Cat", "Mouse"]
self.rowiter = []
for i in range(len(animals)):
self.combo.append_text(animals[i])
self.rowiter.append([self.liststore.append([i, animals[i]])])
self.combo.set_active(0)
def list_changed(self, widget, row, data2):
self.combo.set_active(int(row.to_string()))
def combo_changed(self, widget):
print(widget.get_active()) # the ID of the requested row
#self.treeview.row_activated(Gtk.TreePath(), Gtk.TreeViewColumn())
def quit_window(self, widget, data=None):
Gtk.main_quit()
win = MainWindow()
win.show_all()
win.connect("delete-event", win.quit_window)
Gtk.main()
I discovered that I also need Gtk.TreeView.set_cursor() to achieve my goal:
def combo_changed(self, widget):
row = widget.get_active() # the ID of the requested row
print(row)
self.treeview.row_activated(Gtk.TreePath(row), Gtk.TreeViewColumn(None))
self.treeview.set_cursor(Gtk.TreePath(row))

Best way to get the name of a button that called an event?

In the following code (inspired by this snippet), I use a single event handler buttonClick to change the title of the window. Currently, I need to evaluate if the Id of the event corresponds to the Id of the button. If I decide to add 50 buttons instead of 2, this method could become cumbersome. Is there a better way to do this?
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, 'wxBitmapButton',
pos=(300, 150), size=(300, 350))
self.panel1 = wx.Panel(self, -1)
self.button1 = wx.Button(self.panel1, id=-1,
pos=(10, 20), size = (20,20))
self.button1.Bind(wx.EVT_BUTTON, self.buttonClick)
self.button2 = wx.Button(self.panel1, id=-1,
pos=(40, 20), size = (20,20))
self.button2.Bind(wx.EVT_BUTTON, self.buttonClick)
self.Show(True)
def buttonClick(self,event):
if event.Id == self.button1.Id:
self.SetTitle("Button 1 clicked")
elif event.Id == self.button2.Id:
self.SetTitle("Button 2 clicked")
application = wx.PySimpleApp()
window = MyFrame()
application.MainLoop()
You could give the button a name, and then look at the name in the event handler.
When you make the button
b = wx.Button(self, 10, "Default Button", (20, 20))
b.myname = "default button"
self.Bind(wx.EVT_BUTTON, self.OnClick, b)
When the button is clicked:
def OnClick(self, event):
name = event.GetEventObject().myname
Take advantage of what you can do in a language like Python. You can pass extra arguments to your event callback function, like so.
import functools
def __init__(self):
# ...
for i in range(10):
name = 'Button %d' % i
button = wx.Button(parent, -1, name)
func = functools.partial(self.on_button, name=name)
button.Bind(wx.EVT_BUTTON, func)
# ...
def on_button(self, event, name):
print '%s clicked' % name
Of course, the arguments can be anything you want.
I recommend that you use different event handlers to handle events from each button. If there is a lot of commonality, you can combine that into a function which returns a function with the specific behavior you want, for instance:
def goingTo(self, where):
def goingToHandler(event):
self.SetTitle("I'm going to " + where)
return goingToHandler
def __init__(self):
buttonA.Bind(wx.EVT_BUTTON, self.goingTo("work"))
# clicking will say "I'm going to work"
buttonB.Bind(wx.EVT_BUTTON, self.goingTo("home"))
# clicking will say "I'm going to home"
Keep a dict with keys that are the .Id of the buttons and values that are the button names or whatever, so instead of a long if/elif chain you do a single dict lookup in buttonClick.
Code snippets: in __init__, add creation and update of the dict:
self.panel1 = wx.Panel(self, -1)
self.thebuttons = dict()
self.button1 = wx.Button(self.panel1, id=-1,
pos=(10, 20), size = (20,20))
self.thebuttons[self.button1.Id] = 'Button 1'
self.button1.Bind(wx.EVT_BUTTON, self.buttonClick)
and so on for 50 buttons (or whatever) [they might be better created in a loop, btw;-)].
So buttonClick becomes:
def buttonClick(self,event):
button_name = self.thebuttons.get(event.Id, '?No button?')
self.setTitle(button_name + ' clicked')
You could create a dictionary of buttons, and do the look based on the id ... something like this:
class MyFrame(wx.Frame):
def _add_button (self, *args):
btn = wx.Button (*args)
btn.Bind (wx.EVT_BUTTON, self.buttonClick)
self.buttons[btn.id] = btn
def __init__ (self):
self.button = dict ()
self._add_button (self.panel1, id=-1,
pos=(10, 20), size = (20,20))
self._add_button = (self.panel1, id=-1,
pos=(40, 20), size = (20,20))
self.Show (True)
def buttonClick(self,event):
self.SetTitle (self.buttons[event.Id].label)
I ran into a similar problem: I was generating buttons based on user-supplied data, and I needed the buttons to affect another class, so I needed to pass along information about the buttonclick. What I did was explicitly assign button IDs to each button I generated, then stored information about them in a dictionary to lookup later.
I would have thought there would be a prettier way to do this, constructing a custom event passing along more information, but all I've seen is the dictionary-lookup method. Also, I keep around a list of the buttons so I can erase all of them when needed.
Here's a slightly scrubbed code sample of something similar:
self.buttonDefs = {}
self.buttons = []
id_increment = 800
if (row, col) in self.items:
for ev in self.items[(row, col)]:
id_increment += 1
#### Populate a dict with the event information
self.buttonDefs[id_increment ] = (row, col, ev['user'])
####
tempBtn = wx.Button(self.sidebar, id_increment , "Choose",
(0,50+len(self.buttons)*40), (50,20) )
self.sidebar.Bind(wx.EVT_BUTTON, self.OnShiftClick, tempBtn)
self.buttons.append(tempBtn)
def OnShiftClick(self, evt):
### Lookup the information from the dict
row, col, user = self.buttonDefs[evt.GetId()]
self.WriteToCell(row, col, user)
self.DrawShiftPicker(row, col)
I needed to do the same thing to keep track of button-presses . I used a lambda function to bind to the event . That way I could pass in the entire button object to the event handler function to manipulate accordingly.
class PlatGridderTop(wx.Frame):
numbuttons = 0
buttonlist = []
def create_another_button(self, event): # wxGlade: PlateGridderTop.<event_handler>
buttoncreator_id = wx.ID_ANY
butonname = "button" + str(buttoncreator_id)
PlateGridderTop.numbuttons = PlateGridderTop.numbuttons + 1
thisbutton_number = PlateGridderTop.numbuttons
self.buttonname = wx.Button(self,buttoncreator_id ,"ChildButton %s" % thisbutton_number )
self.Bind(wx.EVT_BUTTON,lambda event, buttonpressed=self.buttonname: self.print_button_press(event,buttonpressed),self.buttonname)
self.buttonlist.append(self.buttonname)
self.__do_layout()
print "Clicked plate button %s" % butonname
event.Skip()
def print_button_press(self,event,clickerbutton):
"""Just a dummy method that responds to a button press"""
print "Clicked a created button named %s with wxpython ID %s" % (clickerbutton.GetLabel(),event.GetId())
Disclaimer : This is my first post to stackoverflow

Categories

Resources