Mausgesteuerte Widgets. Ziehen und Ablegen innerhalb des Fensters

Das übliche Verständnis von Drag & Drop (D & D) setzt voraus, dass beispielsweise ein Link zu einer Datei von einem Widget genommen und mit der Maus in ein anderes Fenster oder Widget verschoben wird. Als nächstes werden wir nicht über die Bibliotheksfunktionen von D & D sprechen, sondern über unsere eigene Implementierung des Verschiebens des Widgets innerhalb des Fensters und die damit verbundenen Funktionen. Der Code ist mehr als eine konkrete praktische Anwendung, die im C-Stil mit Klassen geschrieben ist. Der Editor ist CodeBlocks 17.12, der unter Ubuntu x64 im Vergleich zur 16. Version nicht mehr abstürzt.

Bild

Es gibt einen Widget-Container GtkFixed, in dem andere Widgets an bestimmten Koordinaten gespeichert werden können. Das klassische Konzept zum Erstellen von Anwendungen in GTK umfasst die Verwendung von Widget-Containern GtkBox (und anderen), um das Fenster korrekt zu dehnen und den Raum zu füllen. Der Container wird entweder auf die Größe des Fensters (und auf die Ränder anderer Widgets) gestreckt oder in der Regel auf die Größe des untergeordneten Widgets reduziert.

Der Code ist in main.cpp, main.hpp, movable_widgets.hpp unterteilt. Ich habe die Implementierungsdatei nicht separat ausgewählt. Der Inhalt von main.cpp ist ziemlich typisch:

#include "main.hpp" #include "movable_widgets.hpp" void builder_init(gpointer user_data) { appdata *data=(appdata*) user_data; GError *error = NULL; GtkBuilder *builder = gtk_builder_new(); if (!gtk_builder_add_from_file (builder, "window.glade", &error)) { //     g_critical ("   : %s", error->message); g_error_free (error); } data->win=GTK_WIDGET(gtk_builder_get_object(builder, "window1")); data->notebook=GTK_NOTEBOOK(gtk_builder_get_object(builder, "notebook1")); gtk_notebook_remove_page(data->notebook,0); ///    gtk_builder_connect_signals (builder,data); g_clear_object(&builder); } void application_activate(GtkApplication *application, gpointer user_data) { appdata *data=(appdata*) user_data; builder_init(data); gtk_widget_set_size_request(data->win,320,240); gtk_application_add_window(data->app,GTK_WINDOW(data->win)); page_body *page=new page_body(data, G_OBJECT(data->notebook)); const gchar *text ="<span foreground=\"blue\" size=\"x-large\">Blue text</span>" ; GtkWidget *label = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (label), text); GtkWidget *image=gtk_image_new_from_file("opennet2.gif"); GtkWidget *image2=gtk_image_new_from_file("n_temp.png"); page->add_widget(label,label_t,10,10); page->add_widget(image,image_t,20,20); page->add_widget(image2,image_t,40,40); gtk_widget_show_all(data->win); } void application_shutdown(const GtkApplication *application, gpointer user_data) {} int main (int argc, char *argv[]) { appdata data; gtk_init (&argc, &argv); gint res; data.app = gtk_application_new("gtk3.org", G_APPLICATION_FLAGS_NONE); g_signal_connect(data.app, "activate", G_CALLBACK(application_activate), &data); g_signal_connect(data.app, "shutdown", G_CALLBACK(application_shutdown), &data); res = g_application_run(G_APPLICATION(data.app), 0, NULL); return 0; } 

Einige Widgets werden aus einer XML-Beschreibung (Funktion builder_init) erstellt, ein anderer Teil programmgesteuert (page-> add_widget). Gtk_widget_show_all Funktion (Daten-> Win); wird für die rekursive Anzeige von Widgets und deren Inhalten benötigt. GTK löscht unabhängig den Inhalt von Widgets, wenn diese gelöscht werden, insbesondere von anderen untergeordneten Widgets. Zum Zeitpunkt der Ausführung der Rückruffunktion application_shutdown sind das Hauptfenster und alle Inhalts-Widgets bereits gelöscht.

 #ifndef MAIN_H #define MAIN_H #include <gtk/gtk.h> #include <stdbool.h> #include <stdlib.h> #define restrict __restrict__ class appdata { public: char *glade_name=(char*)"window.glade"; GtkApplication *restrict app; GtkWidget *restrict win; GtkNotebook *restrict notebook; GArray *restrict pages; }; #endif 

Das Seitenfeld ist ein Array von Zeigern auf Klassen mit dem Inhalt der Seiten. In diesem Beispiel wird es nicht verwendet, da nur eine Registerkarte verwendet wird. Verwenden von Restrict ist ein Amateur. Theoretisch ergibt sich eine gewisse Leistungssteigerung. In diesem Fall besteht keine Notwendigkeit zur Verwendung.

Das eingefügte Widget selbst wird in einem Container vom Typ GtkEventBox abgelegt. Er sammelt Ereignisse
Knopfdruck. Auch ein optionaler Container vom Typ GtkFrame zum Anzeigen eines Widgets in einem Frame beim Klicken mit der linken Maustaste. Der Containerwechsel ist schnell genug. Die Registerkarte selbst, in die Widgets eingefügt werden, weist die folgende Hierarchie von Anhängen auf: GtkScrolledWindow-> GtkViewport-> GtkFixed. Anfangs sind Widgets vom Typ GtkWidget, das von Makros in die Typen GtkViewport, GtkFixed konvertiert wird. Ich würde betonen
Aufmerksamkeit auf die Makroansicht
InsertedWidgetWithProperty * widget_with_property = & g_array_index (Widgets, InsertedWidgetWithProperty, i);
da es am einfachsten ist, hier einen Fehler zu machen. Parameter x_correction, y_correction - Koordinaten des Mausklicks relativ zum eingefügten Widget GtkEvent. Das Flag button_not_pressed wird verwendet, um den Frame-Container korrekt anzuzeigen. Unter Logik versteht es sich, dass eine der Maustasten, die auf das eingefügte Widget geklickt wird, in einem Rahmen platziert werden sollte. Das heißt, die Buttonclick- und Buttonrelease-Ereignisse sind im Gegensatz zu den Enter-Notify-Event- und Leave-Notify-Event-Ereignissen, die mit einer Änderung der Cursorform verbunden sind, nicht gepaart. Wenn die Parameter x_correction, button_not_pressed Dienstparameter sind, dh sie sollten im privaten Bereich platziert werden, wird das Flag click_order verwendet, um das aktuelle Widget über dem Rest anzuzeigen.

 typedef struct { GtkWidget *restrict widget_ptr; GtkWidget *restrict eventbox; GtkWidget *restrict frame; //   GtkWidget *restrict pmenu; widget_type type; bool button_not_pressed; } InsertedWidgetWithProperty; class page_body { public: GtkWidget *restrict scrolledwindow; GtkWidget *restrict viewport; GtkWidget *restrict fixed; GArray *restrict widgets=g_array_new(FALSE, TRUE, sizeof(InsertedWidgetWithProperty)); GtkAdjustment *restrict h_adj; GtkAdjustment *restrict v_adj; int num_of_current_widget=0; double x_correction=0; double y_correction=0; GtkWidget *restrict window; ///      int widget_count=0; bool click_order=FALSE; //TRUE -  page_body(appdata *data, GObject *container) { window=data->win; h_adj=gtk_adjustment_new(0.0,4.0,900.0,1.0,5.0,10.0); v_adj=gtk_adjustment_new(0.0,4.0,900.0,1.0,5.0,10.0); scrolledwindow=gtk_scrolled_window_new(h_adj, v_adj); viewport=gtk_viewport_new(h_adj, v_adj); fixed=gtk_fixed_new(); gtk_container_add(GTK_CONTAINER(scrolledwindow),GTK_WIDGET(viewport)); gtk_container_add(GTK_CONTAINER(viewport),GTK_WIDGET(fixed)); if(GTK_IS_NOTEBOOK(container)) { gtk_notebook_append_page ((GtkNotebook*)container,scrolledwindow,NULL); } else if(GTK_IS_WIDGET(container)) { gtk_container_add(GTK_CONTAINER(container),scrolledwindow); } g_signal_connect(fixed,"motion-notify-event",G_CALLBACK(fixed_motion_notify), this); g_signal_connect(scrolledwindow,"destroy",G_CALLBACK(scrolled_window_destroy_cb), this); } ~page_body() { int i=widgets->len; if(widget_count>0) { for(i; i>=0; i--) { InsertedWidgetWithProperty *widget_with_property; widget_with_property=&g_array_index(widgets,InsertedWidgetWithProperty,i); } } g_array_free(widgets,TRUE); } void add_widget(GtkWidget *widget, widget_type type, int x, int y) { ++widget_count; InsertedWidgetWithProperty *widget_with_property=(InsertedWidgetWithProperty*) g_malloc0(sizeof(InsertedWidgetWithProperty)); widget_with_property->eventbox=gtk_event_box_new(); widget_with_property->type=type; widget_with_property->widget_ptr=widget; gtk_container_add(GTK_CONTAINER(widget_with_property->eventbox),widget); gtk_fixed_put(GTK_FIXED(fixed),widget_with_property->eventbox,x,y); widget_with_property->pmenu=gtk_menu_new(); GtkWidget *menu_items = gtk_menu_item_new_with_label (""); gtk_widget_show(menu_items); gtk_menu_shell_append (GTK_MENU_SHELL (widget_with_property->pmenu), menu_items); g_signal_connect(widget_with_property->eventbox,"button-press-event",G_CALLBACK(eventbox_press_cb),this); g_signal_connect(widget_with_property->eventbox,"button-release-event",G_CALLBACK(eventbox_release_cb),this); g_signal_connect(menu_items,"activate",G_CALLBACK(menu_delete_activate),this); g_signal_connect(widget_with_property->eventbox,"leave-notify-event",G_CALLBACK(eventbox_leave_cb),this); g_signal_connect(widget_with_property->eventbox,"enter-notify-event",G_CALLBACK(eventbox_enter_cb),this); gtk_widget_set_events(widget_with_property->eventbox,GDK_LEAVE_NOTIFY_MASK|GDK_ENTER_NOTIFY_MASK|GDK_STRUCTURE_MASK); g_array_append_val(widgets, *widget_with_property); } inline void change_cursor(char *cursor_name) { GdkDisplay *display; GdkCursor *cursor; display = gtk_widget_get_display (window); if(cursor_name) cursor = gdk_cursor_new_from_name (display, cursor_name); else cursor = gdk_cursor_new_from_name (display, "default"); GdkWindow *gdkwindow=gtk_widget_get_window (window); gdk_window_set_cursor (gdkwindow, cursor); } inline void delete_widget(int i) { InsertedWidgetWithProperty *widget_with_property= &g_array_index(this->widgets,InsertedWidgetWithProperty,i); GtkWidget *eventbox=widget_with_property->eventbox; g_object_ref(eventbox); gtk_container_remove(GTK_CONTAINER(this->fixed),eventbox); if(widget_with_property->frame!=NULL) { gtk_widget_destroy(widget_with_property->frame); } gtk_widget_destroy(widget_with_property->eventbox); this->widgets=g_array_remove_index_fast(this->widgets,i); --this->widget_count; } }; 

Die Formel zum Berechnen der Koordinaten des Widgets, damit es sich beim Klicken mit der Maus nicht in eine Richtung bewegt. Es beinhaltet die Koordinaten des Klicks relativ zum Widget,
GtkFixed-Koordinaten relativ zum Anwendungsfenster, Fensterkoordinaten relativ zum Bildschirm.
Der Korrekturfaktor +25 verwirrt mich etwas, aber ich weiß nicht, wie einfach das Schreiben ist. Ich habe die Arbeit in Versionen von Ubuntu 15.10, 16.04, 18.04 überprüft. Der Vergleich mit 0 und -1 wird durchgeführt, damit das eingefügte Widget nicht aus dem scrollbaren Bereich entfernt wird. Das Scrollen selbst wird vom GtkScrolledWindow-Widget bereitgestellt.

 gboolean fixed_motion_notify (GtkWidget *widget, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; int x_win, y_win, x_fixed, y_fixed; gtk_window_get_position(GTK_WINDOW(page->window),&x_win,&y_win); gtk_widget_translate_coordinates(page->window,page->fixed,x_win,y_win,&x_fixed,&y_fixed); double correction_y=(-y_fixed+y_win)*2+25; double correction_x=(-x_fixed+x_win); double x_corr=page->x_correction; double y_corr=page->y_correction; int position_x=event->motion.x_root-x_corr-x_win-correction_x; int position_y=event->motion.y_root-y_corr-y_fixed-correction_y; InsertedWidgetWithProperty *widget_with_property=&g_array_index(page->widgets,InsertedWidgetWithProperty,page->num_of_current_widget); GtkWidget *fixed=page->fixed; GtkWidget *eventbox=widget_with_property->eventbox; if(position_x<-1) position_x=0; if(position_y<-1) position_y=0; gtk_fixed_move(GTK_FIXED(fixed), eventbox, position_x, position_y); return FALSE; } 

Der Rest der Rückruffunktionen. Das Entfernen einzelner Widgets über das Kontextmenü mit der rechten Maustaste wurde implementiert. Das Entfernen der page_body-Klasse wird vom Löschereignis GtkScrolledWindow angehalten.

 void scrolled_window_destroy_cb (GtkWidget *object, gpointer user_data) { page_body *page=(page_body*) user_data; delete page; } void menu_delete_activate (GtkMenuItem *menuitem, gpointer user_data) { page_body *page=(page_body*) user_data; page->delete_widget(page->num_of_current_widget); } gboolean eventbox_leave_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; page->change_cursor(NULL); } gboolean eventbox_enter_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; page->change_cursor("pointer"); } gboolean eventbox_press_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; page->x_correction=event->button.x; page->y_correction=event->button.y; int i=0; InsertedWidgetWithProperty *widget_compare; for(i; i<=page->widgets->len; i++) { widget_compare=(InsertedWidgetWithProperty*) page->widgets->data+i; if(widget==widget_compare->eventbox) { page->num_of_current_widget=i; break; } } if(widget_compare->button_not_pressed==FALSE) { GtkWidget *eventbox=widget_compare->eventbox; if(page->click_order) { int x, y; gtk_widget_translate_coordinates(page->fixed, eventbox,0,0,&x, &y); gtk_container_remove(GTK_CONTAINER(page->fixed),eventbox); gtk_fixed_put(GTK_FIXED(page->fixed),eventbox,-x,-y); } g_object_ref(widget_compare->widget_ptr); gtk_container_remove(GTK_CONTAINER(eventbox),widget_compare->widget_ptr); if(widget_compare->frame==NULL) widget_compare->frame=gtk_frame_new(NULL); gtk_container_add(GTK_CONTAINER(widget_compare->frame),widget_compare->widget_ptr); gtk_container_add(GTK_CONTAINER(eventbox),widget_compare->frame); gtk_widget_show_all(eventbox); widget_compare->button_not_pressed=TRUE; } ///   const gint RIGHT_CLICK = 3; if (event->type == GDK_BUTTON_PRESS) { GdkEventButton *bevent = (GdkEventButton *) event; if (bevent->button == RIGHT_CLICK) { gtk_menu_popup(GTK_MENU(widget_compare->pmenu), NULL, NULL, NULL, NULL, bevent->button, bevent->time); } } return FALSE; } gboolean eventbox_release_cb (GtkWidget *eventbox, GdkEvent *event, gpointer user_data) { page_body *page=(page_body*) user_data; InsertedWidgetWithProperty *widget_with_property= &g_array_index(page->widgets,InsertedWidgetWithProperty,page->num_of_current_widget); ///      ,    if(widget_with_property->button_not_pressed==TRUE) { widget_with_property->frame=(GtkWidget*) g_object_ref(widget_with_property->frame); widget_with_property->widget_ptr=(GtkWidget*) g_object_ref(widget_with_property->widget_ptr); GtkWidget *frame=widget_with_property->frame; GtkWidget *widget=widget_with_property->widget_ptr; gtk_container_remove(GTK_CONTAINER(eventbox), frame); gtk_container_remove(GTK_CONTAINER(frame), widget); gtk_container_add(GTK_CONTAINER(eventbox), widget); widget_with_property->button_not_pressed=FALSE; } } 

Vielen Dank für Ihre Aufmerksamkeit.

github.com/SanyaZ7/movable_widgets_on_GtkFixed-

Source: https://habr.com/ru/post/de473716/


All Articles