Eine einfache EditDialog-Vorlage

Eine einfache EditDialog-Vorlage

Bisher habe ich die Grundlagen zum Verbinden von Boost-Fusion-angepassten Strukturen mit der Qts Model/View-Architektur behandelt. Heute ist der nächste Schritt:ein einfacher Dialog zum Bearbeiten einer einzelnen Instanz eines solchen fusionsfähigen Typs.

Dies ist nur ein einfacher Formulardialog, bei dem jede Zeile eine Beschriftung und ein Widget für die Dateneingabe ist. Aber es umfasst den notwendigen Code, um aus den Informationen, die Fusion und die Tags liefern, genau dieses Eingabeformular zu generieren.

Dialoggrundlagen

Dieser Dialog hat zwei Rollen:eine zum Bearbeiten einer bestehenden Instanz, er könnte sehr nützlich sein, um einen Optionsdialog anzuzeigen. Wobei die Optionsklasse eine fusionsangepasste Struktur ist. Oder um neue Daten einzugeben und diese Daten dann in eine Instanz des Fusion-aktivierten Typs umzuwandeln. Das grundlegende Layout des EditDialogs sieht folgendermaßen aus:

template< class Seq, typename ...Tags>
class EditDialog : public QDialog
{
    W_OBJECT(EditDialog) //Q_OBJECT for templates from verdigris
    using taglist = boost::mp11::mp_list< Tags...>;
    const size_t colnumber = uitags::count_editable_tags< Tags...>();
    std::array<int, uitags::count_editable_tags< Tags...>()> index_array = uitags::make_edit_index_array< Tags...>();
    std::array< const char*,boost::fusion::result_of::size< Seq>::value> membernames = tagtype::get_member_names< Seq>();
    std::array< QWidget*,boost::fusion::result_of::size< Seq>::value> index2widget;
    std::array< QLabel*,boost::fusion::result_of::size< Seq>::value> index2label;

Viele std::array-Mitglieder:

  • index_array dient wiederum dazu, den lokalen Index zur Bearbeitung in den Index des Typs zu übersetzen (z. B. NoEdit/NoDisplay-Tags zu überspringen)
  • membernames wird für die Standardkennzeichnung verwendet
  • index2widget übersetzt einen bestimmten Zeilenindex in den Edit-Widget-Zeiger.
  • index2label ist ähnlich wie index2widget, nur dass es den Zeiger auf das Label speichert

Da diese Vorlage zwei Konstruktoren hat, gibt es eine private Funktion zum Erstellen der Benutzeroberfläche:

void makeDialog()
{
    QVBoxLayout* vbox = new QVBoxLayout(this);
    setLayout(vbox);
    QFormLayout* form_layout = new QFormLayout();
    form_layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
    boost::mp11::mp_for_each< boost::mp11::mp_iota_c<uitags::count_editable_tags< Tags...>()>>(
                [&]( auto I ){
                if(index_array[I]!= -1)
                {
                  QWidget* w = make_widget(this,boost::mp11::mp_at_c< taglist,I>{});
                  index2widget[I]=w;
                  QLabel *lbl = new QLabel(QString("Enter ")+ membernames[I]);
                  index2label[I] = lbl;
                  form_layout->addRow(lbl,w);
                }
                } );
    vbox->addLayout(form_layout);

    auto buttonBox = new QDialogButtonBox(this);

    buttonBox->setOrientation(Qt::Horizontal);
    buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
    connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
    connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
    vbox->addWidget(buttonBox);
}

Ich bin es gewohnt, meine UIs in QtCreator zusammenzuklicken, daher schreibe ich nicht sehr oft Code zum Erstellen manueller UIs in Qt. Dies ist also normaler UI-Code, den Sie normalerweise nicht selbst in Qt schreiben müssten. Aber da dies eine Vorlage ist, muss man diesen Code nur einmal schreiben.

Der einzige Nicht-Qt-Teil dieses Codes ist, wo die eigentlichen Widgets und Labels erstellt werden, indem mp_for_each aufgerufen wird, das dann das generische Lambda einmal für jeden Index aufruft. Die Funktion make_widget verwendet den Tag-Versand, um den richtigen Widget-Typ für jedes Tag zu erstellen. Boosts mp11::mp_at_c wird verwendet, um auf den richtigen Tag-Typ zuzugreifen und ihn zu erstellen.

Es besteht eine 1:1-Korrelation zwischen Tag und Widgettyp:

QWidget* make_widget(QWidget* parent,uitags::SingleLine)
{
    return new QLineEdit(parent);
}

Ähnliche Funktionen behandeln das Abrufen und Setzen von Werten für diese Widgets. QWidget hat dafür keine allgemeine API. Jedes Widget hat seine eigene Methode namens value, text, toPlainText (,...), um den tatsächlichen Wert innerhalb dessen zu erhalten, was angezeigt/bearbeitet wird. Daher benötigt auch jedes Tag eine Funktion zum Setzen/Abrufen des Werts von seinem Widget:

QVariant get_value_as_variant(const QWidget* w,uitags::DoubleSpinBox)
{
    return qobject_cast< const QDoubleSpinBox*>(w)->value();
}

void set_value(QWidget* w,const std::string& s,uitags::SingleLine)
{
    qobject_cast< QLineEdit*>(w)->setText(QString::fromStdString(s));
}

void set_value(QWidget* w,const QString& s,uitags::SingleLine)
{
    qobject_cast< QLineEdit*>(w)->setText(s);
}

Für den Wertabruf wird QVariant verwendet, da Qt-Typen bereits leicht dazu konvertiert werden können und es bereits Code gibt, mit dem eine QVariant einer fusionsfähigen Struktur zugewiesen werden kann. Für die set_value-Funktionen werden mehrere Überladungen bereitgestellt, da diese möglicherweise mit dem tatsächlichen Typ aufgerufen werden.

Die Logik eines EditDialogs ist, dass nichts geändert wird, bis Ok gedrückt wird, Cancel behält die alten Werte. Ich habe mich entschieden, jetzt eine automatische Zuweisung zu den Werten bereitzustellen, sobald OK gedrückt wird, der Handhabungscode muss immer noch transferValues:

aufrufen
void transferValues(Seq& s)
{
    boost::mp11::mp_for_each< boost::mp11::mp_iota_c< uitags::count_editable_tags()>>(
                [&]( auto I ){
                    if(index_array[I]!= -1)
                    {
                     QWidget* w = index2widget[I];
                     qt_assign(boost::fusion::get< I>(s),get_value_as_variant(w,boost::mp11::mp_at_c< taglist,I>{}));
                    }
                } );
}

Dies verwendet erneut mp11::mp_for_each, um über den Index der Strukturmitglieder von Fusion zu iterieren. Die im vorigen Beitrag vorgestellte Funktion qt_assign übernimmt das Setzen des Wertes.

Und das ist so ziemlich der Code für den Bearbeitungsdialog. Aber auf diese Weise hat mein Code auch diese neue Art des Schreibens von Qt-UI-Code für C++-Domänenklassen untersucht. Im Moment ist der Code ein guter Prototyp, um prototypische UIs zu schreiben. Wohin dieser Weg über den einfachen Ansatz hinaus führt, werde ich in den nächsten Wochen erkunden...