bugblatterbeast
Anmeldungsdatum: 30. Januar 2008
Beiträge: 455
|
Ich vermeide UI/UX-Programmierung normaler Weise. Ich bräuchte aber gerade dringend eine Test-Applikation mit grafischer Oberfläche um die Funktionen einer dynamischen Bibliothek zu überprüfen, die ich entwickle. Auf den ersten Blick bin ich von GTK+ und Glade begeistert. Es ist mir sofort gelungen eine grafische Oberfläche zu erstellen und diese von meiner Test-Applikation zu starten. Ich sehe auch, dass ich in glade Signale an Handler zuweisen und optional mit zusätzlichen Daten versehen kann, die dann anscheinend mit dem Befehl gtk_builder_connect_signals(GtkBuilder*, NULL); verbunden werden. Gibt es irgendwo ein Beispiel oder eine übersichtliche Dokumentation darüber, wie die Funktionsrümpfe für die verschiedenen Handler in C aussehen müssen? Dieses Tutorial hat mir bisher am meisten geholfen: http://www.peteronion.org.uk/GtkExamples/GladeTutorials.html Ich habe jedoch leider nicht herausgefunden, wie der Verfasser an die Information gekommen ist, welche unterschiedlichen Handler es gibt und welche Argumente sie benötigen. Die Links auf Objekt-Dokumentationen in diesem Tutorial scheinen kaputt zu sein und werden auf die Startseite der Gtk3 Dokumentation umgeleitet. Vielleicht habe ich ja einen falschen Denkansatz und suche nach den falschen Begriffen. Was die Sache für mich etwas anstrengender macht als üblich ist die Tatsache, dass ich zwar mit $(pkg-config gtk+-3.0 --cflags --libs) super kompilieren kann, meine IDE aber nicht alle Header findet und ich deswegen nicht so einfach wie sonst zu den Definitionen der Klassen und Funktionen navigieren kann.
|
bugblatterbeast
(Themenstarter)
Anmeldungsdatum: 30. Januar 2008
Beiträge: 455
|
bugblatterbeast schrieb: Was die Sache für mich etwas anstrengender macht als üblich ist die Tatsache, dass ich zwar mit $(pkg-config gtk+-3.0 --cflags --libs) super kompilieren kann, meine IDE aber nicht alle Header findet und ich deswegen nicht so einfach wie sonst zu den Definitionen der Klassen und Funktionen navigieren kann.
Kompiliert zwar, funktioniert aber nicht für meine IDE | #include <gtk-3.0/gtk/gtk.h>
|
Kompiliert und funktioniert für meine IDE
|
ChickenLipsRfun2eat
Anmeldungsdatum: 6. Dezember 2009
Beiträge: 12067
|
Hallo! Zu Glade kann ich dir leider nicht viel sagen, ich habe damit nur sehr rudimentäre Versuche vor Äonen gestartet. Generell steht dir eine doxygen-ähnliche Hilfe zur Verfügung, die in den meisten Fällen auch ausreicht, bspw.: QObject.signal_connect in deinem Fall. Signal & Slot ist in Qt sehr ausführlich erklärt (die haben das mal verzapft), ist prinzipiell nur Funktionsaufruf mit Argumenten. Da du aber auf Kubuntu bist, könntest du dir deine GUI auch einfach gleich im Qt Creator zusammenklicken. Das reicht für quick and dirty aus und: Die Dokumentation ist klasse. Es gibt auch speziell für deinen Zweck ein komplexes Test-Framework mit dem du deine Bibliothek nach Herzenslust zerlegen kannst. Einziges Manko bei Qt: Verschiedene Lizenzmodelle. Um bei freier Software zu bleiben, musst du ein wenig aufpassen welche Pakete du dann nutzen kannst, bei OpenSource ist es Wurscht.
|
bugblatterbeast
(Themenstarter)
Anmeldungsdatum: 30. Januar 2008
Beiträge: 455
|
Es funktioniert für mich leider bisher nicht, sich nur auf die Funktion gtk_builder_connect_signals(); zu verlassen. Anders als in den Beispielen die ich bisher gefunden habe, muss ich bis jetzt immer auch die Funktion gtk_builder_add_callback_symbol(); aufrufen, um die callback Funktionen bekannt zu machen. Ich hatte mich zunächst darauf verlassen, dass der GtkBuilder die Callbacks direkt in der Symboltabelle findet.
|
bugblatterbeast
(Themenstarter)
Anmeldungsdatum: 30. Januar 2008
Beiträge: 455
|
Hallo ChickenLipsRfun2eat, sehr vielen Dank für den Hinweis. Mit Qt habe ich bisher zwar wenig aber dafür sehr gute Erfahrungen gemacht und natürlich auch darüber nachgedacht. Der Grund aus dem ich mich gegen Qt entschieden habe war der, dass Qt so gute OpenGL-Unterstützung hat und man so komfortabel an einen bereits initialisierten Kontext heran kommt. Es wäre aber mein Wunsch zu testen, ob meine Bibliothek selbstständig in der Lage ist, mit einem _XDisplay und einer XID einen geeigneten GLXContext zu initialisieren. Ich werde noch etwas Zeit investieren, es mit GTK+ zu versuchen, bin aber sehr dankbar für Deinen Vorschlag und werde vermutlich darauf zurückgreifen, wenn ich mit meinem bisherigen Vorhaben nicht weiter komme.
|
ChickenLipsRfun2eat
Anmeldungsdatum: 6. Dezember 2009
Beiträge: 12067
|
Also ich habe tatsächlich in meinem Sammelsurium nur noch ein Beispielprojekt im GTK-Ordner und in diesem habe ich (warum steht leider nicht dabei) separate Funktionen definiert und direkt g_signal_connect verwendet. Beispiel anhand meines Beispiels: | static void print_button_click( GtkWidget *widget, gpointer data ) {
(void) data;
g_print( "%s\n", gtk_widget_get_name(widget) );
}
// …
GtkWidget *button = …
// …
g_signal_connect( button, "clicked", G_CALLBACK( print_button_click ), NULL );
|
Ich erinnere mich allerdings, das man in GTK viel mehr selbst machen musste. Vielleicht hatte ich den Weg gewählt, weil ich mit dem builder auch nicht weiterkam. Bin leider oftmals ne Schlampe was Dokumentation angeht…
|
bugblatterbeast
(Themenstarter)
Anmeldungsdatum: 30. Januar 2008
Beiträge: 455
|
Danke für den Beispiel Code. Ich hatte allerdings die Verknüpfung direkt in Glade angelegt. | <object class="GtkButton" id="RunButton">
...
<signal name="clicked" handler="run_button_clicked" object="ArgumentInput" swapped="no"/>
</object>
|
und dann folgendermaßen benutzt: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | #include <gtk-3.0/gtk/gtk.h>
#include <gtk-3.0/gtk/gtkbutton.h>
#include "debug.h"
G_MODULE_EXPORT void run_button_clicked(GtkButton* self, gpointer user_data) {
dbg("run button clicked");
}
int main(int argc, char **argv) {
dbg("TestApplication started");
GtkWidget *window;
GtkBuilder *builder = NULL;
gtk_init (&argc , &argv);
builder = gtk_builder_new();
if(gtk_builder_add_from_file(builder, "TestApplication.glade" , NULL) == 0) {
printf("Could not read UI layout\n");
return(0);
}
window = GTK_WIDGET(gtk_builder_get_object (builder,"ApplicationWindow"));
gtk_builder_add_callback_symbol(builder, "run_button_clicked", G_CALLBACK(run_button_clicked));
gtk_builder_connect_signals(builder, NULL);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
|
Bei mir funktioniert es nicht ohne die Zeile
| gtk_builder_add_callback_symbol(builder, "run_button_clicked", G_CALLBACK(run_button_clicked));
|
obwohl die in den Tutorials die ich gefunden habe nicht nötig war. Nicht dass das dramatisch wäre, hat mich aber etwas Zeit gekostet, darauf zu kommen. Bin leider oftmals ne Schlampe was Dokumentation angeht…
Ist das nicht normal? Ich persönlich kenne keinen Programmierer, der Spaß daran hat, Dokumentationen zu schreiben und keinen Programmierer, der gerne lange Dokumentationen liest.
|
ChickenLipsRfun2eat
Anmeldungsdatum: 6. Dezember 2009
Beiträge: 12067
|
→ https://docs.gtk.org/gtk3/method.Builder.add_callback_symbol.html Scheint also so, als würde da irgendwas im globalen Namespace fehlen. Sonst müsste die Zeile gtk_builder_connect_signals(builder, NULL); ausreichen. Die kannst du dann aber theoretisch weglassen.
Ist das nicht normal? Ich persönlich kenne keinen Programmierer, der Spaß daran hat, Dokumentationen zu schreiben und keinen Programmierer, der gerne lange Dokumentationen liest.
Keine Ahnung. Ich bin nur Hobby-Täter und frickle hauptsächlich an Kleinkram für ganz persönliche Zwecke und habe nichts in der Richtung „offiziell gelernt“ oder beruflich damit zu tun gehabt. Ich schreibe aber oftmals vieles mit, warum ich was gemacht habe. Allerdings nicht in dem Fall, da mein Mitschreiben erst nach den ersten paar Feldversuchen startet, wenn ich das Thema so weit verstanden habe, das ich es mir erklären kann. Und ich lese gerne ausführliche Dokumentationen, damit ich nicht beim Nachschlagen einer Funktion 700 Tabs mit dependencies offen haben muss 😉
|
bugblatterbeast
(Themenstarter)
Anmeldungsdatum: 30. Januar 2008
Beiträge: 455
|
Ich habe zwar leider kein kompaktes Einstiegs-Tutorial oder passende Beispiele gefunden aber ich konnte mir zusammen suchen, was ich für mein Projekt gebraucht habe. Für den Fall dass es in Zukunft mal jemanden interessieren könnte, schreibe ich kurz was ich herausgefunden habe: Es handelt sich bei meinem Projekt um reinen C Code. In C++ müsste es auch funktionieren, jedoch gibt es da möglicher Weise für das ein oder andere bequemere Wege. Wie ich bereits gestern gestern schon geschrieben habe, hat es für mich nicht gereicht, die Signale nur in Glade zu verknüpfen und davon auszugehen, dass der GtkBuilder die Callbacks in der Symboltabelle findet. Für jedes Callback das ich angelegt und in Glade mit einem Signal verknüpft habe, musste ich den Befehl "gtk_builder_add_callback_symbol" ausführen:
| gtk_builder_add_callback_symbol(builder, "OutputArea_render_cb", G_CALLBACK(OutputArea_render_cb));
gtk_builder_add_callback_symbol(builder, "OutputArea_resize_cb", G_CALLBACK(OutputArea_resize_cb));
gtk_builder_add_callback_symbol(builder, "FileMenuItemNew_activate_cb", G_CALLBACK(FileMenuItemNew_activate_cb));
gtk_builder_add_callback_symbol(builder, "FileMenuItemOpen_activate_cb", G_CALLBACK(FileMenuItemOpen_activate_cb));
gtk_builder_add_callback_symbol(builder, "FileMenuItemSave_activate_cb", G_CALLBACK(FileMenuItemSave_activate_cb));
gtk_builder_add_callback_symbol(builder, "FileMenuItemSaveAs_activate_cb", G_CALLBACK(FileMenuItemSaveAs_activate_cb));
gtk_builder_add_callback_symbol(builder, "FileMenuItemQuit_activate_cb", G_CALLBACK(FileMenuItemQuit_activate_cb));
gtk_builder_add_callback_symbol(builder, "HelpMenuItemAbout_activate_cb", G_CALLBACK(HelpMenuItemAbout_activate_cb));
gtk_builder_add_callback_symbol(builder, "FunctionSelect_changed_cb", G_CALLBACK(FunctionSelect_changed_cb));
gtk_builder_add_callback_symbol(builder, "RunButton_clicked_cb", G_CALLBACK(RunButton_clicked_cb));
gtk_builder_connect_signals(builder, NULL);
|
Die Deklarationen für die Callback Funktionen findet man in der Gtk-Dokumentation, wenn man jeweils nach dem Element-Typ sucht und dann ganz nach unten zu den Signalen scrollt. Es ist mir nicht gelungen, Callbacks für Pointer-Events (Maus-Ereignisse) über dem OpenGL-Ausgabebereich mit Glade zu konfigurieren. Statt dessen habe ich das folgendermaßen hinbekommen:
| GtkWidget* OutputArea = (GtkWidget*) gtk_builder_get_object(builder, "OutputArea");
if(OutputArea) {
gtk_widget_add_events(OutputArea, GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL_MASK);
g_signal_connect(G_OBJECT(OutputArea), "motion-notify-event", G_CALLBACK(OutputArea_event_cb), NULL);
g_signal_connect(G_OBJECT(OutputArea), "button-press-event", G_CALLBACK(OutputArea_event_cb), NULL);
g_signal_connect(G_OBJECT(OutputArea), "button-release-event", G_CALLBACK(OutputArea_event_cb), NULL);
g_signal_connect(G_OBJECT(OutputArea), "scroll-event", G_CALLBACK(OutputArea_event_cb), NULL);
}
|
Ich habe leider keine Dokumentation zu den verwendbaren Werten für das zweite Argument ("detailed_signal") der Funktion "g_signal_connect" finden können. Durch raten und ausprobieren, habe ich zumindest herausgefunden, dass man die verwendbaren Werte anscheinend von den Werten von GdkEventType ableiten kann ( https://docs.gtk.org/gdk4/enum.EventType.html ).
GDK_MOTION_NOTIFY > motion-notify-event GDK_BUTTON_PRESS > button-press-event GDK_BUTTON_RELEASE > button-release-event GDK_SCROLL > scroll-event
Diese Events arbeite ich hier testweise alle in einer einzigen Funktion ab:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 | gboolean OutputArea_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) {
GdkEventButton* eventButton = (GdkEventButton*) event;
switch(event->type) {
case GDK_MOTION_NOTIFY:
dbg("Mouse moved to <%.3f, %.3f>", ((GdkEventMotion*) event)->x, ((GdkEventMotion*) event)->y);
break;
case GDK_BUTTON_PRESS:
dbg("Key %d pressed at <%.3f, %.3f>", ((GdkEventButton*) event)->button, ((GdkEventButton*) event)->x, ((GdkEventButton*) event)->y);
break;
case GDK_BUTTON_RELEASE:
dbg("Key %d released at <%.3f, %.3f>", ((GdkEventButton*) event)->button, ((GdkEventButton*) event)->x, ((GdkEventButton*) event)->y);
break;
case GDK_SCROLL:
switch(((GdkEventScroll*) event)->direction) {
case GDK_SCROLL_UP:
dbg("Scroll wheel used at <%.3f, %.3f>, direction = UP", ((GdkEventScroll*) event)->x, ((GdkEventScroll*) event)->y);
break;
case GDK_SCROLL_DOWN:
dbg("Scroll wheel used at <%.3f, %.3f>, direction = DOWN", ((GdkEventScroll*) event)->x, ((GdkEventScroll*) event)->y);
break;
default:
break;
}
break;
default:
break;
}
return FALSE;
}
|
Es war auch nicht ganz leicht herauszufinden, wie man eine mit Glade erstellte Selectbox im Quellcode dynamisch mit Werten initialisiert. Ich habe mich für die Einfachere Variante GtkComboBoxText entschieden (GtkComboBox und GtkComboBoxText sind aber laut Doku ab Version 4.10 deprecated).
| GtkComboBoxText* ComboBox = (GtkComboBoxText*) gtk_builder_get_object(builder, "FunctionSelect");
if(ComboBox) {
gtk_combo_box_text_insert(ComboBox, 1, "TestID1", "Test Label 1");
gtk_combo_box_text_insert(ComboBox, 2, "TestID2", "Test Label 2");
gtk_combo_box_text_insert(ComboBox, 3, "TestID3", "Test Label 3");
}
|
Im Test ist das jetzt noch statisch. Da kann man dann aber leicht über ein Array oder so iterieren um das mit dynamischen Werten zu füllen. So sieht das Callback für das Change-Signal im Test aus:
| void FunctionSelect_changed_cb(GtkComboBoxText* self, gpointer user_data) {
dbg("ID \"%s\" selected, Text = \"%s\"", gtk_combo_box_get_active_id((GtkComboBox*) self), gtk_combo_box_text_get_active_text(self));
}
|
Das Ganze funktioniert für mich jetzt wie es soll (die Mausbewegungen generieren aber natürlich sehr viele Ereignisse):
[./src/main.c, 91]: Mouse moved to <5.041, 125.536>
[./src/main.c, 91]: Mouse moved to <11.766, 124.695>
[./src/main.c, 91]: Mouse moved to <18.440, 123.861>
...
[./src/main.c, 94]: Key 1 pressed at <75.457, 100.590>
[./src/main.c, 91]: Mouse moved to <75.457, 100.899>
...
[./src/main.c, 97]: Key 1 released at <139.083, 141.702>
[./src/main.c, 91]: Mouse moved to <138.640, 141.702>
...
[./src/main.c, 94]: Key 2 pressed at <99.009, 193.515>
[./src/main.c, 91]: Mouse moved to <99.009, 193.325>
...
[./src/main.c, 97]: Key 2 released at <247.497, 187.710>
[./src/main.c, 91]: Mouse moved to <247.497, 187.233>
...
[./src/main.c, 94]: Key 3 pressed at <265.898, 129.071>
[./src/main.c, 91]: Mouse moved to <266.319, 129.071>
...
[./src/main.c, 97]: Key 3 released at <401.349, 205.070>
...
[./src/main.c, 68]: ID "TestID1" selected, Text = "Test Label 1"
[./src/main.c, 68]: ID "TestID2" selected, Text = "Test Label 2"
[./src/main.c, 68]: ID "TestID3" selected, Text = "Test Label 3"
|
bugblatterbeast
(Themenstarter)
Anmeldungsdatum: 30. Januar 2008
Beiträge: 455
|
Hallo ChickenLipsRfun2eat sehr vielen Dank noch mal für Deine Hinweise
→ https://docs.gtk.org/gtk3/method.Builder.add_callback_symbol.html
Scheint also so, als würde da irgendwas im globalen Namespace fehlen. Sonst müsste die Zeile gtk_builder_connect_signals(builder, NULL); ausreichen. Die kannst du dann aber theoretisch weglassen.
Obwohl ich für jedes einzelne Callback die Funktion gtk_builder_add_callback_symbol aufrufe, klappt es nicht wenn ich die Zeile gtk_builder_connect_signals(builder, NULL); weg lasse.
Da wäre Qt wirklich kontraproduktiv. GTK aber vermutlich auch.
Du hattest anscheinend absolut recht. Ich habe es nicht geschafft, mit GTK irgendwie an ein _XDisplay und eine XID zu kommen um selbst einen OpenGL-Kontext zu initialisieren. Statt dessen bekomme ich von GTK nur eine GtkGLArea mit bereits initialisiertem Kontext. Für diesen einen Test muss ich mir also noch etwas anderes überlegen.
ich lese gerne ausführliche Dokumentationen, damit ich nicht beim Nachschlagen einer Funktion 700 Tabs mit dependencies offen haben muss
Ich mag es lieber, wenn die Entwickler gut kommentierten Beispiel-Quellcode veröffentlichen und Doxygen-Kommentare verwenden. Es ist nicht nur so, dass ich mich an die automatisch generierten Doxygen Dokus gewöhnt habe. Meine IDE erkennt die Kommentare auch und blendet sie mir ein, wenn ich eine Funktion verwende. EDIT: wie sicher dem ein oder anderen aufgefallen ist, ist in meinem letzten Post in der Funktion "OutputArea_event_cb" die 2. Zeile "GdkEventButton* eventButton = (GdkEventButton*) event;" unnötig.
|