How to create PyGObject application with a menubar using Gtk.Builder? - python

There is no full documentation about how to use Gtk.Builder in PyGObject to create a menubar.
I don't use that Gtk.UIManager because it is deprecated.
The example code below is based on my experience with Gtk.UIManager.
In the example should appear a menubar with Foo as a top menu group having an clickable item Bar.
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio
class Window(Gtk.ApplicationWindow):
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(200, 100)
#
self.interface_info = """
<interface>
<menu id='TheMenu'>
<section>
<attribute name='foo'>Foo</attribute>
<item>
<attribute name='bar'>Bar</attribute>
</item>
</section>
</menu>
</interface>
"""
builder = Gtk.Builder.new_from_string(self.interface_info, -1)
action_bar = Gio.SimpleAction.new('bar', None)
action_bar.connect('activate', self.on_menu)
self.add_action(action_bar)
menubar = builder.get_object('TheMenu')
# layout
self.layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.layout.pack_start(menubar, True, True, 0)
self.add(self.layout)
self.connect('destroy', Gtk.main_quit)
self.show_all()
def on_menu(self, widget):
print(widget)
if __name__ == '__main__':
win = Window()
Gtk.main()
The current error is
Traceback (most recent call last):
File "./_menubar.py", line 46, in <module>
win = Window()
File "./_menubar.py", line 36, in __init__
self.layout.pack_start(menubar, True, True, 0)
TypeError: argument child: Expected Gtk.Widget, but got gi.repository.Gio.Menu
I am unsure about
How to create the XML string.
How to get the menubar-widget.
How to create Actions/Click-handlers for menu items.
Of course the question could be extended to toolbars but I wouldn't made it to complexe.
btw: I don't want to use Gtk.Application.set_menubar(). Because there is no Gtk.Application.set_toolbar() and currently I see no advantage on having a Gtk-based application object.
EDIT: I also tried this variant (without any success):
gio_menu = builder.get_object('TheMenu')
menubar = Gtk.Menubar.new_from_model(gio_menu)

My answer is based on a foreign answer on the gtk-dev-app mailinglist.
I prefere Variant 3.
Variant 1: with XML-String
Please be aware of the different naming of the action between the XML-string (win.bar) and the Gio.SimpleAction(bar).
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio
class Window(Gtk.ApplicationWindow):
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(200, 100)
#
self.interface_info = """
<interface>
<menu id='TheMenu'>
<submenu>
<attribute name='label'>Foo</attribute>
<item>
<attribute name='label'>Bar</attribute>
<attribute name='action'>win.bar</attribute>
</item>
</submenu>
</menu>
</interface>
"""
builder = Gtk.Builder.new_from_string(self.interface_info, -1)
action_bar = Gio.SimpleAction.new('bar', None)
action_bar.connect('activate', self.on_menu)
self.add_action(action_bar)
menumodel = builder.get_object('TheMenu')
menubar = Gtk.MenuBar.new_from_model(menumodel)
# layout
self.layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.layout.pack_start(menubar, False, False, 0)
self.add(self.layout)
self.connect('destroy', Gtk.main_quit)
self.show_all()
def on_menu(self, action, value):
print('Action: {}\nValue: {}'.format(action, value))
if __name__ == '__main__':
win = Window()
Gtk.main()
Variant 2: without XML but with Actions
I prefere this variant because it doesn't use (human unreadable XML) and Gtk.Builder.
Here you create the structure of your menu as a data structure based on Gio.Menu and connect a Action (which itself is connected to an event handler) to it's items. Out of that informations the widget for the menubar is kind of generated.
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gio
class Window(Gtk.ApplicationWindow):
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(200, 100)
action_bar = Gio.SimpleAction.new('bar', None)
action_bar.connect('activate', self.on_menu)
self.add_action(action_bar)
# root of the menu
menu_model = Gio.Menu.new()
# menu item "Bar"
menu_item = Gio.MenuItem.new('Bar', 'win.bar')
# sub-menu "Foo" with item "Bar"
menu_foo = Gio.Menu.new()
menu_foo.append_item(menu_item)
menu_model.append_submenu('Foo', menu_foo)
# create menubar widget from the model
menubar = Gtk.MenuBar.new_from_model(menu_model)
# layout
self.layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.layout.pack_start(menubar, False, False, 0)
self.add(self.layout)
self.connect('destroy', Gtk.main_quit)
self.show_all()
def on_menu(self, action, value):
print('Action: {}\nValue: {}'.format(action, value))
if __name__ == '__main__':
win = Window()
Gtk.main()
Variant 3: Old-school, easy without XML, Actions or Gio layer
This variant works kind of "old school" because you simply build your menu widgets together and connect signalls directly to them. This works without using a underlying and abstract data structure (e. g. Gio.MenuModel or an XML-string) and without a Application class.
#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class Window(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(200, 100)
# create menubar
menubar = self._create_menubar()
# create a toolbar
toolbar = self._create_toolbar()
# layout
self.layout = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.layout.pack_start(menubar, False, False, 0)
self.layout.pack_start(toolbar, False, False, 0)
self.add(self.layout)
self.connect('destroy', Gtk.main_quit)
self.show_all()
def _create_menubar(self):
# menu item 'Bar'
item_bar = Gtk.MenuItem.new_with_label('Bar')
item_bar.connect('activate', self.on_menu)
# sub menu for 'Bar'
menu_foo = Gtk.Menu.new()
menu_foo.append(item_bar)
# main menu 'Foo' with attached sub menu
item_foo = Gtk.MenuItem.new_with_label('Foo')
item_foo.set_submenu(menu_foo)
# the menubar itself
menubar = Gtk.MenuBar.new()
menubar.append(item_foo)
return menubar
def _create_toolbar(self):
toolbar = Gtk.Toolbar.new()
# button with label
bar_item = Gtk.ToolButton.new(None, 'Bar')
bar_item.connect('clicked', self.on_menu)
toolbar.insert(bar_item, -1)
# button with icon
bar_item = Gtk.ToolButton.new_from_stock(Gtk.STOCK_OK)
bar_item.connect('clicked', self.on_menu)
toolbar.insert(bar_item, -1)
return toolbar
def on_menu(self, caller):
print(caller)
if __name__ == '__main__':
win = Window()
Gtk.main()

Related

python Gtk3 - Set label of button to default value of None

I am trying to reset a label of a button to its initial (default) value of None, which does not work as expected. Here's the minimal example:
from gi import require_version
require_version('Gtk', '3.0')
from gi.repository import Gtk
class GUI(Gtk.Window):
def __init__(self):
super().__init__()
self.connect('destroy', Gtk.main_quit)
self.set_position(Gtk.WindowPosition.CENTER)
self.grid = Gtk.Grid(hexpand=True, vexpand=True)
self.add(self.grid)
button = Gtk.Button(hexpand=True, vexpand=True)
self.grid.attach(button, 0, 0, 1, 1)
button.connect('clicked', self.on_button_clicked)
def on_button_clicked(self, button: Gtk.Button) -> None:
print(label := button.get_label(), type(label))
button.set_label(label)
def main() -> None:
win = GUI()
win.show_all()
Gtk.main()
if __name__ == '__main__':
main()
Result:
$ python example.py
None <class 'NoneType'>
Traceback (most recent call last):
File "/home/neumann/example.py", line 21, in on_button_clicked
button.set_label(label)
TypeError: Argument 1 does not allow None as a value
What's the correct way to do this?
Note: Before somebody suggests it: I do not want to set the label to an empty string, since that will change the size of the button, which is noticeable on a larger grid of buttons:
from gi import require_version
require_version('Gtk', '3.0')
from gi.repository import Gtk
class GUI(Gtk.Window):
def __init__(self):
super().__init__()
self.connect('destroy', Gtk.main_quit)
self.set_position(Gtk.WindowPosition.CENTER)
self.grid = Gtk.Grid(hexpand=True, vexpand=True)
self.add(self.grid)
for x in range(3):
button = Gtk.Button(hexpand=True, vexpand=True)
button.connect('clicked', self.on_button_clicked)
self.grid.attach(button, x, 0, 1, 1)
def on_button_clicked(self, button: Gtk.Button) -> None:
print(label := button.get_label(), type(label))
button.set_label('')
def main() -> None:
win = GUI()
win.show_all()
Gtk.main()
if __name__ == '__main__':
main()
I'm not sure what your use case is, but you can try adding a GtkLabel child and set the string there:
from gi import require_version
require_version('Gtk', '3.0')
from gi.repository import Gtk
class GUI(Gtk.Window):
def __init__(self):
super().__init__()
self.connect('destroy', Gtk.main_quit)
self.set_position(Gtk.WindowPosition.CENTER)
self.grid = Gtk.Grid(hexpand=True, vexpand=True)
self.add(self.grid)
for x in range(3):
button = Gtk.Button(hexpand=True, vexpand=True)
label = Gtk.Label()
button.add(label)
button.connect('clicked', self.on_button_clicked)
self.grid.attach(button, x, 0, 1, 1)
def on_button_clicked(self, button: Gtk.Button) -> None:
label = button.get_child()
print(text := label.get_label(), type(text))
label.set_label('')
# or hide it if you want
# label.hide()
def main() -> None:
win = GUI()
win.show_all()
Gtk.main()
if __name__ == '__main__':
main()
GtkButton may be creating the internal GtkLabel child only when a label is set (which should be a valid string). And since the hexpand and vexpand for the GtkButton are set to True, they may be getting propagated to the internal GtkLabel.
If you simply want all the buttons to have same width and height, you may only need grid.set_row_homogeneous() and grid.set_column_homogeneous()

How to update widget dynamically in GTK3 (PyGObject)?

In this example, I'm trying to add another button (or any widget) every time a button is pressed.
from gi.repository import Gtk
class ButtonWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Button Demo")
self.hbox = Gtk.HBox()
self.add(self.hbox)
button = Gtk.Button.new_with_label("Click Me")
button.connect("clicked", self.on_clicked)
self.hbox.pack_start(button, False, True, 0)
def on_clicked(self, button):
print("This prints...")
button = Gtk.Button.new_with_label("Another button")
self.hbox.pack_start(button, False, True, 0) # ... but the new button doesn't appear
win = ButtonWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
I have tried queue_draw() and other hacks, but nothing has worked so far.
Calling the show_all() method works to update widgets' children. Here is the code with show_all() used, and the added line marked accordingly:
from gi.repository import Gtk
class ButtonWindow(Gtk.Window):
def __init__(self):
super().__init__(title="Button Demo")
self.hbox = Gtk.HBox()
self.add(self.hbox)
button = Gtk.Button.new_with_label("Click Me")
button.connect("clicked", self.on_clicked)
self.hbox.pack_start(button, False, True, 0)
def on_clicked(self, button):
print("This prints...")
button = Gtk.Button.new_with_label("Another button")
self.hbox.pack_start(button, False, True, 0)
self.hbox.show_all() ### ADDED LINE
win = ButtonWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
So, calling self.hbox.show_all() shows all the children of self.hbox.

How to print only blue characters in textview

I want to get the characters printed only in blue.
How to do it?
Here is the sample program code, which is a fragment of most of the program.
I would be very grateful for your help.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
class TextViewWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="awesome gui")
self.set_resizable(True)
self.set_default_size(700, 550)
self.grid = Gtk.Grid()
self.add(self.grid)
self.create_textview()
self.buffer = []
def create_textview(self):
scrolledwindow = Gtk.ScrolledWindow()
scrolledwindow.set_hexpand(True)
scrolledwindow.set_vexpand(True)
self.grid.attach(scrolledwindow, 0, 2, 80, 1)
self.textview = Gtk.TextView()
scrolledwindow.add(self.textview)
self.textbuffer = self.textview.get_buffer()
self.textview.set_editable(False)
self.textview.set_cursor_visible(False)
self.textview.connect("key-press-event", self.on_key_down)
def on_key_down(self, widget, event, data=None):
znak_p = event.string
end_iter_m = self.textbuffer.get_iter_at_line_offset(1, 1)
qwerty_tag = self.textbuffer.create_tag(None, editable=True, foreground="blue")
self.textbuffer.insert_with_tags(end_iter_m, znak_p, qwerty_tag)
win = TextViewWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
Your on_key_down handler is wrong:
you are creating an anonymous tag every time you're pressing a key
you are using an invalid string for the GtkTextTag:foreground property
you are not returning a value from the callback telling GTK whether you handled the event (and thus should stop the event propagation) or not.
The GtkTextTag:foreground property uses the same format as the gdk_rgba_parse() function; if you want a blue color, you should use rgba(0.0, 0.0, 1.0, 1.0) instead of "blue".
A correct handler is:
def on_key_down(self, widget, event, data=None):
znak_p = event.string
end_iter_m = self.textbuffer.get
self.textbuffer.insert_with_tags(end_iter_m, znak_p, self.qwerty_tag)
return True

How to pass/return data from Gtk dialog to main application class

I have an application in Python Gtk. I have my main Application class in my main file. I then have all my dialogs in a different file. I need to be able to pass/return custom data from the dialog to the main application class other than the standard Gtk response codes, here is some basic example code as my own code is very long:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
self.add(button)
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
print("The OK button was clicked")
elif response == Gtk.ResponseType.CANCEL:
print("The Cancel button was clicked")
dialog.destroy()
win = DialogWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
The dialog window in a separate file:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DialogExample(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.set_default_size(150, 100)
label = Gtk.Label("This is a dialog to display additional information")
box = self.get_content_area()
box.add(label)
self.show_all()
As standard we apply the Gtk.ResponseType to the buttons. But what if we want to return some custom data - not just a simple response code - as a further code example:
class DialogExample(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0)
self.set_default_size(150, 100)
label = Gtk.Label("This is a dialog to display additional information")
button = Gtk.Button("Return something")
button.connect("clicked", self.on_button_clicked)
box = self.get_content_area()
box.add(label)
self.show_all()
def on_button_clicked(self, widget):
if SOME_CONDITION:
return <CUSTOM_RESPONSE>
else:
return <ALT_CUSTOM_RESPONSE>
When I do the last example the dialog does not return anything, I would like to do something like:
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
self.add(button)
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == <CUSTOM_RESPONSE>:
#do something with <CUSTOM_RESPONSE>
elif response == <ALT_CUSTOM_RESPONSE>:
#do something different with <ALT_CUSTOM_RESPONSE>
dialog.destroy()
win = DialogWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
The DialogExample window does not destroy/close and nothing is returned and the application basically just pauses as it thinks there are no more methods to run - although there is much to be done after the return of the custom data (I need to then start adding records to a database).
[UPDATE]
I have now tried so many various things to solve this issue I could not possibly list them all here. I have searched endlessly for some kind of answer and it would seem this is not something that is done by anyone on the Internet.
The C version of gtk_dialog_run is limited to return integers, you can set custom values but nothing like strings or objects. You can workaround this by setting a value on the "response" signal and then get it after the run function returns.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DialogExample(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.result = ""
self.set_default_size(150, 100)
self.connect("response", self.on_response)
label = Gtk.Label(label="Type something")
self.entry = Gtk.Entry()
box = self.get_content_area()
box.add(label)
box.add(self.entry)
self.show_all()
def on_response(self, widget, response_id):
self.result = self.entry.get_text ()
def get_result(self):
return self.result
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add(box)
button = Gtk.Button(label="Open dialog")
button.connect("clicked", self.on_button_clicked)
box.add(button)
self.label = Gtk.Label()
box.add(self.label)
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
self.label.set_text(dialog.get_result())
print("The OK button was clicked")
elif response == Gtk.ResponseType.CANCEL:
print("The Cancel button was clicked")
dialog.destroy()
win = DialogWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

Adding widget in callback in GTK

How can I add a new widget from a callback function within a class? For example, I have a Gtk.Box and Gtk.Button and I want to add Gtk.Label to the Gtk.Box from callback function connected to button click. (this code doesn't work)
import gi
import os
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk, GObject, Gio
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Delete Screenshots")
self.main_grid = Gtk.Grid()
self.main_grid.set_row_homogeneous(True)
self.add(self.main_grid)
self.screen_label = Gtk.Label()
self.screen_label.set_text("Test Label")
self.screen_label2 = Gtk.Label()
self.screen_label2.set_text("Test Label2")
self.label_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.label_box.homogeneous = True
self.button_search = Gtk.Button(label="Search")
self.button_search.connect("clicked", self.on_button_search_clicked)
self.button_delete = Gtk.Button(label="Delete")
self.button_delete.connect("clicked", self.on_button_delete_clicked)
self.main_grid.add(self.button_search);
self.main_grid.attach(self.button_delete, 1, 0, 1, 1);
self.main_grid.attach(self.label_box, 0, 1, 1, 1)
def on_button_search_clicked(self, widget):
self.label_box.pack_start(self.screen_label, True, True, 0)
def on_button_delete_clicked(self, widget):
print("Delete")
win = MainWindow()
win.set_default_size(50, 30)
win.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
How to add something to label_box from on_button_search_clicked?
Your code is basically correct. However all widgets need to be shown by either calling show() on them or show_all() on a parent widget. In your code, show_all() is called on the MainWindow instance. At that time, the widget you are adding in your callback is not attached to the window or any of its children. It will thus not be included in the show_all() call.
To fix this, simply call show() on your label in the callback:
...
def on_button_search_clicked(self, widget):
self.label_box.pack_start(self.screen_label, True, True, 0)
self.screen_label.show()
...

Categories