Προσαρμοσμένοι περιέκτες

When deriving from Gtk::Container, you should override the following virtual methods:

  • get_request_mode_vfunc(): Return what Gtk::SizeRequestMode is preferred by the container.
  • get_preferred_width_vfunc(): Calculate the minimum and natural width of the container.
  • get_preferred_height_vfunc(): Calculate the minimum and natural height of the container.
  • get_preferred_width_for_height_vfunc(): Calculate the minimum and natural width of the container, if it would be given the specified height.
  • get_preferred_height_for_width_vfunc(): Calculate the minimum and natural height of the container, if it would be given the specified width.
  • on_size_allocate(): Position the child widgets, given the height and width that the container has actually been given.
  • forall_vfunc(): Call the same callback for each of the children.
  • on_add(): Add a child widget to the container.
  • on_remove(): Remove a child widget from the container.
  • child_type_vfunc(): Return what type of child can be added.

Οι εικονικές μέθοδοι get_request_mode_vfunc(), get_preferred_width_vfunc(), get_preferred_height_vfunc(), get_preferred_width_for_height_vfunc(), get_preferred_height_for_width_vfunc() και on_size_allocate() ελέγχουν τη διάταξη των θυγατρικών γραφικών συστατικών. Για παράδειγμα, αν ο περιέκτης σας έχει 2 θυγατρικά γραφικά συστατικά, με το ένα κάτω από το άλλο, η get_request_mode_vfunc() μπορεί να ζητήσει διάταξη ύψους προς πλάτος. Έπειτα η get_preferred_width_vfunc() σας μπορεί να αναφέρει το μέγιστο των πλατών των θυγατρικών γραφικών συστατικών και η get_preferred_height_for_width_vfunc() μπορεί να αναφέρει το άθροισμα των υψών τους. Αν θέλετε συμπλήρωση μεταξύ των θυγατρικών γραφικών συστατικών, τότε θα πρέπει να το προσθέσετε στο πλάτος και στο ύψος επίσης. Ο περιέκτης του γραφικού συστατικού θα χρησιμοποιήσει αυτό το αποτέλεσμα για να εξασφαλίσει ότι το γραφικό συστατικό σας έχει αρκετό χώρο και όχι μικρότερο. Εξετάζοντας κάθε γονικό του θυγατρικού συστατικού και το γονικό του, θα αποφασίσει τελικά η λογική για το μέγεθος του παραθύρου ανωτάτου επιπέδου.

Δεν υπάρχει εγγύηση ότι θα πάρετε το Gtk::SizeRequestMode που ζητήσατε. Συνεπώς και οι τέσσερις μέθοδοι της get_preferred_xxx_vfunc() πρέπει να επιστρέψουν λογικές τιμές.

Η on_size_allocate() δέχεται το ενεργό ύψος και πλάτος που ο γονικός περιέκτης έχει αποφασίσει να δώσσει στο γραφικό συστατικό του. Αυτό μπορεί να είναι περισσότερο από το ελάχιστο, ή ακόμα περισσότερο από το φυσικό μέγεθος, για παράδειγμα αν το παράθυρο ανωτάτου επιπέδου έχει επεκταθεί. Μπορεί να επιλέξετε να αγνοήσετε τον πρόσθετο χώρο και να αφήσετε μια κενή περιοχή, ή μπορείτε να επιλέξετε να επεκτείνετε τα θυγατρικά γραφικά συστατικά σας για να γεμίσετε τον χώρο, ή να επεκτείνετε τη συμπλήρωση μεταξύ των γραφικών συστατικών σας. Είναι ο περιέκτης σας, έτσι αποφασίζετε. Να μην ξεχάσετε να καλέσετε την set_allocation() μέσα στην υλοποίησή σας on_size_allocate() για να χρησιμοποιήσετε στην πραγματικότητα τον κατανεμημένο χώρο που έχει προσφερθεί από τον γονικό περιέκτη.

Εκτός και ο περιέκτης σας είναι ένα παράθυρο ανωτάτου επιπέδου που παράγεται από την Gtk::Window, θα πρέπει προφανώς να καλέσετε επίσης την Gtk::Widget::set_has_window(false) στον κατασκευαστή σας. Αυτό σημαίνει ότι ο περιέκτης σας δεν δημιουργεί την δικιά του Gdk::Window, αλλά χρησιμοποιεί το γονικό του παράθυρο. (Σημειώστε τη διαφορά μεταξύ Gtk::Window και Gdk::Window.) Αν ο περιέκτης σας δεν χρειάζεται τη δικιά του Gdk::Window και δεν παράγεται από την Gtk::Window, θα πρέπει επίσης να αντικαταστήσετε τη μέθοδο on_realize() όπως περιγράφηκε στην ενότητα προσαρμοσμένα γραφικά συστατικά. Και εκτός και ο περιέκτης σας σχεδιάζει άμεσα στο υποκείμενο Gdk::Window, θα πρέπει προφανώς να καλέσετε την set_redraw_on_allocate(false) για να βελτιώσετε την απόδοση.

Αντικαθιστώντας την forall_vfunc() μπορείτε να επιτρέψετε στις εφαρμογές να λειτουργούν σε όλα τα θυγατρικά γραφικά συστατικά του περιέκτη. Για παράδειγμα, η show_all_children() χρησιμοποιεί αυτό για να βρεί όλα τα θυγατρικά γραφικά συστατικά και να τα εμφανίσει.

Αν και ο περιέκτης σας μπορεί να έχει τη δική του μέθοδο ορισμού των θυγατρικών γραφικών συστατικών, θα πρέπει ακόμα να δώσετε μια υλοποίηση για τις εικονικές μεθόδους on_add() και on_remove() από τη βασική κλάση, έτσι ώστε οι μέθοδοι add() και remove() να κάνουν κάτι κατάλληλο αν κληθούν.

Η υλοποίηση της μεθόδου child_type_vfunc() πρέπει να αναφέρει τον τύπο του γραφικού συστατικού που μπορεί να προστεθεί στον περιέκτη σας, αν δεν είναι ακόμα πλήρης. Αυτή είναι συνήθως η Gtk::Widget::get_type() που δείχνει ότι ο περιέκτης μπορεί να περιέχει οποιαδήποτε κλάση παράγεται από την Gtk::Widget. Αν ο περιέκτης μπορεί να μην περιέχει οποιαδήποτε περισσότερα γραφικά συστατικά, τότε αυτή η μέθοδος πρέπει να επιστρέψει G_TYPE_NONE.

28.1.1. Παράδειγμα

Αυτό το παράδειγμα υλοποιεί έναν περιέκτη με δύο θυγατρικά γραφικά συστατικά, το ένα πάνω από το άλλο. Φυσικά, σε αυτήν την περίπτωση μπορεί να είναι πολύ πιο απλά να χρησιμοποιήσετε απλά μια κάθετη Gtk::Box.

Φιγούρα 28-1Προσαρμοσμένος περιέκτης

Source Code

File: examplewindow.h (For use with gtkmm 3, not gtkmm 2)

#ifndef GTKMM_EXAMPLEWINDOW_H
#define GTKMM_EXAMPLEWINDOW_H

#include <gtkmm.h>
#include "mycontainer.h"

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow();
  virtual ~ExampleWindow();

protected:
  //Signal handlers:
  void on_button_quit();

  //Child widgets:
  Gtk::Box m_VBox;
  MyContainer m_MyContainer;
  Gtk::Button m_Button_One;
  Gtk::Label m_Label_Two;
  Gtk::ButtonBox m_ButtonBox;
  Gtk::Button m_Button_Quit;
};

#endif //GTKMM_EXAMPLEWINDOW_H

File: mycontainer.h (For use with gtkmm 3, not gtkmm 2)

#ifndef GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H
#define GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H

#include <gtkmm/container.h>

class MyContainer : public Gtk::Container
{
public:
  MyContainer();
  virtual ~MyContainer();

  void set_child_widgets(Gtk::Widget& child_one, Gtk::Widget& child_two);

protected:

  //Overrides:
  virtual Gtk::SizeRequestMode get_request_mode_vfunc() const;
  virtual void get_preferred_width_vfunc(int& minimum_width, int& natural_width) const;
  virtual void get_preferred_height_for_width_vfunc(int width, int& minimum_height, int& natural_height) const;
  virtual void get_preferred_height_vfunc(int& minimum_height, int& natural_height) const;
  virtual void get_preferred_width_for_height_vfunc(int height, int& minimum_width, int& natural_width) const;
  virtual void on_size_allocate(Gtk::Allocation& allocation);

  virtual void forall_vfunc(gboolean include_internals, GtkCallback callback, gpointer callback_data);

  virtual void on_add(Gtk::Widget* child);
  virtual void on_remove(Gtk::Widget* child);
  virtual GType child_type_vfunc() const;

  Gtk::Widget* m_child_one;
  Gtk::Widget* m_child_two;
};

#endif //GTKMM_CUSTOM_CONTAINER_MYCONTAINER_H

File: examplewindow.cc (For use with gtkmm 3, not gtkmm 2)

#include <iostream>
#include "examplewindow.h"

ExampleWindow::ExampleWindow()
: m_VBox(Gtk::ORIENTATION_VERTICAL),
  m_Button_One("Child One"),
  m_Label_Two("Child 2"),
  m_Button_Quit("Quit")
{
  set_title("Custom Container example");
  set_border_width(6);
  set_default_size(400, 200);

  add(m_VBox);

  //Add the child widgets to the custom container:
  m_MyContainer.set_child_widgets(m_Button_One, m_Label_Two);

  m_Label_Two.set_alignment(1.0, 0.5);

  m_VBox.pack_start(m_MyContainer, Gtk::PACK_EXPAND_WIDGET);
  m_VBox.pack_start(m_ButtonBox, Gtk::PACK_SHRINK);

  m_ButtonBox.pack_start(m_Button_Quit, Gtk::PACK_SHRINK);
  m_ButtonBox.set_border_width(6);
  m_ButtonBox.set_layout(Gtk::BUTTONBOX_END);
  m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this,
              &ExampleWindow::on_button_quit) );

  show_all_children();
}

ExampleWindow::~ExampleWindow()
{
}

void ExampleWindow::on_button_quit()
{
  hide();
}

File: main.cc (For use with gtkmm 3, not gtkmm 2)

#include "examplewindow.h"
#include <gtkmm/application.h>

int main(int argc, char *argv[])
{
  Glib::RefPtr<Gtk::Application> app = Gtk::Application::create(argc, argv, "org.gtkmm.example");

  ExampleWindow window;

  //Shows the window and returns when it is closed.
  return app->run(window);
}

File: mycontainer.cc (For use with gtkmm 3, not gtkmm 2)

#include <iostream>
#include <algorithm> // std::max
#include "mycontainer.h"

MyContainer::MyContainer()
: m_child_one(0), m_child_two(0)
{
  set_has_window(false);
  set_redraw_on_allocate(false);
}

MyContainer::~MyContainer()
{
/*
  // These calls to Gtk::Widget::unparent() are necessary if MyContainer is
  // deleted before its children. But if you use a version of gtkmm where bug
  // https://bugzilla.gnome.org/show_bug.cgi?id=605728
  // has not been fixed (gtkmm 3.7.10 or earlier) and the children are deleted
  // before the container, these calls can make the program crash.
  // That's because on_remove() is not called, when the children are deleted.
  if (m_child_one)
    m_child_one->unparent();

  if (m_child_two)
    m_child_two->unparent();
*/
}

void MyContainer::set_child_widgets(Gtk::Widget& child_one,
        Gtk::Widget& child_two)
{
  m_child_one = &child_one;
  m_child_two = &child_two;

  m_child_one->set_parent(*this);
  m_child_two->set_parent(*this);
}

//This example container is a simplified VBox with at most two children.
Gtk::SizeRequestMode MyContainer::get_request_mode_vfunc() const
{
  return Gtk::SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}

//Discover the total amount of minimum space and natural space needed by
//this container and its children.
void MyContainer::get_preferred_width_vfunc(int& minimum_width, int& natural_width) const
{
  int child_minimum_width[2] = {0, 0};
  int child_natural_width[2] = {0, 0};

  if(m_child_one && m_child_one->get_visible())
    m_child_one->get_preferred_width(child_minimum_width[0], child_natural_width[0]);

  if(m_child_two && m_child_two->get_visible())
    m_child_two->get_preferred_width(child_minimum_width[1], child_natural_width[1]);

  //Request a width equal to the width of the widest visible child.
  minimum_width = std::max(child_minimum_width[0], child_minimum_width[1]);
  natural_width = std::max(child_natural_width[0], child_natural_width[1]);
}

void MyContainer::get_preferred_height_for_width_vfunc(int width,
   int& minimum_height, int& natural_height) const
{
  int child_minimum_height[2] = {0, 0};
  int child_natural_height[2] = {0, 0};
  int nvis_children = 0;

  if(m_child_one && m_child_one->get_visible())
  {
    ++nvis_children;
    m_child_one->get_preferred_height_for_width(width, child_minimum_height[0],
                                                child_natural_height[0]);
  }

  if(m_child_two && m_child_two->get_visible())
  {
    ++nvis_children;
    m_child_two->get_preferred_height_for_width(width, child_minimum_height[1],
                                                child_natural_height[1]);
  }

  //The allocated height will be divided equally among the visible children.
  //Request a height equal to the number of visible children times the height
  //of the highest child.
  minimum_height = nvis_children * std::max(child_minimum_height[0],
                                            child_minimum_height[1]);
  natural_height = nvis_children * std::max(child_natural_height[0],
                                            child_natural_height[1]);
}

void MyContainer::get_preferred_height_vfunc(int& minimum_height, int& natural_height) const
{
  int child_minimum_height[2] = {0, 0};
  int child_natural_height[2] = {0, 0};
  int nvis_children = 0;

  if(m_child_one && m_child_one->get_visible())
  {
    ++nvis_children;
    m_child_one->get_preferred_height(child_minimum_height[0], child_natural_height[0]);
  }

  if(m_child_two && m_child_two->get_visible())
  {
    ++nvis_children;
    m_child_two->get_preferred_height(child_minimum_height[1], child_natural_height[1]);
  }

  //The allocated height will be divided equally among the visible children.
  //Request a height equal to the number of visible children times the height
  //of the highest child.
  minimum_height = nvis_children * std::max(child_minimum_height[0],
                                            child_minimum_height[1]);
  natural_height = nvis_children * std::max(child_natural_height[0],
                                            child_natural_height[1]);
}

void MyContainer::get_preferred_width_for_height_vfunc(int height,
   int& minimum_width, int& natural_width) const
{
  int child_minimum_width[2] = {0, 0};
  int child_natural_width[2] = {0, 0};
  int nvis_children = 0;

  //Get number of visible children.
  if(m_child_one && m_child_one->get_visible())
    ++nvis_children;
  if(m_child_two && m_child_two->get_visible())
    ++nvis_children;

  if(nvis_children > 0)
  {
    //Divide the height equally among the visible children.
    const int height_per_child = height / nvis_children;

    if(m_child_one && m_child_one->get_visible())
      m_child_one->get_preferred_width_for_height(height_per_child,
                   child_minimum_width[0], child_natural_width[0]);

    if(m_child_two && m_child_two->get_visible())
      m_child_two->get_preferred_width_for_height(height_per_child,
                   child_minimum_width[1], child_natural_width[1]);
  }

  //Request a width equal to the width of the widest child.
  minimum_width = std::max(child_minimum_width[0], child_minimum_width[1]);
  natural_width = std::max(child_natural_width[0], child_natural_width[1]);
}

void MyContainer::on_size_allocate(Gtk::Allocation& allocation)
{
  //Do something with the space that we have actually been given:
  //(We will not be given heights or widths less than we have requested, though
  //we might get more.)

  //Use the offered allocation for this container:
  set_allocation(allocation);

  //Get number of visible children.
  int nvis_children = 0;
  if(m_child_one && m_child_one->get_visible())
    ++nvis_children;
  if(m_child_two && m_child_two->get_visible())
    ++nvis_children;

  if(nvis_children <= 0)
    return;

  //Assign space to the children:
  Gtk::Allocation child_allocation_one;
  Gtk::Allocation child_allocation_two;

  //Place the first child at the top-left:
  child_allocation_one.set_x( allocation.get_x() );
  child_allocation_one.set_y( allocation.get_y() );

  //Make it take up the full width available:
  child_allocation_one.set_width( allocation.get_width() );

  if(m_child_one && m_child_one->get_visible())
  {
    //Divide the height equally among the visible children.
    child_allocation_one.set_height( allocation.get_height() / nvis_children);
    m_child_one->size_allocate(child_allocation_one);
  }
  else
    child_allocation_one.set_height(0);

  //Place the second child below the first child:
  child_allocation_two.set_x( allocation.get_x() );
  child_allocation_two.set_y( allocation.get_y() +
          child_allocation_one.get_height());

  //Make it take up the full width available:
  child_allocation_two.set_width( allocation.get_width() );

  //Make it take up the remaining height:
  child_allocation_two.set_height( allocation.get_height() -
          child_allocation_one.get_height());

  if(m_child_two && m_child_two->get_visible())
    m_child_two->size_allocate(child_allocation_two);
}

void MyContainer::forall_vfunc(gboolean, GtkCallback callback, gpointer callback_data)
{
  if(m_child_one)
    callback(m_child_one->gobj(), callback_data);

  if(m_child_two)
    callback(m_child_two->gobj(), callback_data);
}

void MyContainer::on_add(Gtk::Widget* child)
{
  if(!m_child_one)
  {
    m_child_one = child;
    m_child_one->set_parent(*this);
  }
  else if(!m_child_two)
  {
    m_child_two = child;
    m_child_two->set_parent(*this);
  }
}

void MyContainer::on_remove(Gtk::Widget* child)
{
  if(child)
  {
    const bool visible = child->get_visible();
    bool found = false;

    if(child == m_child_one)
    {
      m_child_one = 0;
      found = true;
    }
    else if(child == m_child_two)
    {
      m_child_two = 0;
      found = true;
    }

    if(found)
    {
      child->unparent();

      if(visible)
        queue_resize();
    }
  }
}

GType MyContainer::child_type_vfunc() const
{
  //If there is still space for one widget, then report the type of widget that
  //may be added.
  if(!m_child_one || !m_child_two)
    return Gtk::Widget::get_type();
  else
  {
    //No more widgets may be added.
    return G_TYPE_NONE;
  }
}