Dynamiczne tworzenie GUI do edycji struktur , jak zadeklarować walidacje danych ?

0

Mam stworzyć wiele okien do edycji struktur , w przykładzie struktura ma 4 pola , normalnie 20-30.
To naukowcy generują takie struktury :D
Naukowcy trochę znają c++ ale zupełnie nie kumają Qt, wiec pomyślałem że trochę ułatwię życie i zrobię mechanizm który
ułatwi tworzenie GUI za pomocą prostych konstrukcji

struct StructT1
{
  std::string name;  // "koza/krowa/owca"
  int   data1;          // "0-1; default 0"
  int16 data2;     //"0-319; default 0"
  int8 data3;     //"0-3; 3-internal mode"
}

Opisuje rekordy do edycji jako lista struktur

class Edit_StructT1 : public Edit_struct::Edit_struct_base
{
public:    
    virtual void init_rows() override
    {
        Edit_StructT1 &editObject =  ..... ;

    rows.emplace_back(RowEdit{"Jakis opis",row_separator});
    rows.emplace_back(RowEdit{"name",row_string, &editObject.name, "koza/krowa/owca"});
    rows.emplace_back(RowEdit{"data1",row_int, &editObject.data1, "0-1; default 0"});
    rows.emplace_back(RowEdit{"data2",row_int16, &editObject.data2, "0-319; default 0"});
    rows.emplace_back(RowEdit{"data3",row_int8, &editObject.data3, "0-3; 3-internal mode"});
      
   };
};
namespace Edit_struct
{

class Edit_struct_base : public QWidget
{
    Q_OBJECT

public:
    enum RowEditType {row_separator, row_int, row_int16, row_int8};


    struct RowEdit
    {
        std::string name;
        RowEditType type;
        void * ptr{nullptr};
        std::string desc{""};
        bool readOnly{false};

        // konstruktor domyslny
        RowEdit (std::string name_, RowEditType type_ ,  void * ptr_ = nullptr , std::string desc_ = "", bool readOnly_=false)
        {
            name = name_;
            type = type_;
            ptr = ptr_;
            desc = desc_;
            readOnly = readOnly_;
        }

        // RowEdit (std::string name_, RowEditType type_)
        // {
        //     name = name_;
        //     type = type_;
        // }
    };

W komentarzach struktury są opisane warunki
np. int data1; // "0-1; default 0"

jak dodać w mojej strukturze "RowEdit" walidacje danych ? Lambda to dobry trop ?

0

Moment ja wróciłbym do początku.
Interesuje cię jak zrozumiałem wizualizacja tej że struktury. Dla mnie to jest tabela z kolumnami Klucz-wartość. To ja ym olała robienie własnych sturktur a użył MV. W modelu miej itemy klucz-wartość a w np. qtableview wyświetlaj
https://doc.qt.io/qt-6/qtableview.html#details
https://doc.qt.io/qt-6/model-view-programming.html
Wtedy znika problem jakiś klas dziedziczących po qwidget.

co do walidacji zaraz się odezwę.
edit:
Co do walidacji nie wiem jak ten soft wygląda ale zakładam że skądś te dane przychodzą? Po jakimś tam protokole, to wtedy ja w skrócie widzę to tak -> przychodzą dane -> walidacja -> dodanie do modelu jak ok. Qt już załatwi wyświetlanie w widoku, nie trzeba żadnych cudów z dziedziczeniem po qwidget.

1

ja do walidacji proponuję QRegularExpression można zrobić fajne maski wprowadzania

1
Adamek Adam napisał(a):

, wiec pomyślałem że trochę ułatwię życie i zrobię mechanizm który
ułatwi tworzenie GUI za pomocą prostych konstrukcji

Dzięki.
Utwierdziłeś mnie (po raz kolejny mam takie doświadczenia) że już nigdy nie zrobię dużego obiektowego *) GUI w C++, a w językach posiadających refleksję.

(nic osobistego - konstatacja techniczna)

*) gdzie gridy/widgety są zasilane obiektami a nie kolumnami z bazy jak w Delhi

1

Źle do tego podchodzisz.

W C++ nie ma meta danych, ale Qt ma na to mechanizmy:

struct StructT1
{
  Q_GADGET
  Q_PROPERTY(std::string name MEMBER name)
  Q_PROPERTY(int   data1 MEMBER data1)
  Q_PROPERTY(int16 data2 MEMBER data2)
  Q_PROPERTY(int8 data3 MEMBER data3)
  
  std::string name;
  int   data1; 
  int16 data2; 
  int8 data3;
};

Następnie posługując się danymi z QMetaObject można jednym kodem wydłubać dowolna informację o dancyh.
Niestety Qt nie udostępnia w prosty sposób edytora właściwości taki jaki jest dostępny w Qt Designer dla Qt Creator, ale z tego co widziałem są dostępne jakieś gotowe rozwiązania.

Inny sposób to skorzystać z boost::fusion albo boost::pfr (tu niestety dostęp do nazwy nie jest prosty, ale też możliwy).

0

To proszę podaj jakiegoś linka z przykładem tak żebym wiedział jakie rozwiązanie masz na myśli — Adamek Adam dziś, 10:55

Przypomniało mi się w sąsiednim wątku
Długo w C++ ze mną pozostał map<string,MyVariant> jako implementacja a'la Recordu (wiersza w gidzie na przykład)

W/w MyVariant (każde środowisko C++ GUI praktycznie taką wysokopoziomową unię ma, Qt prawie na pewno też) przynajmniej musi przynajmniej mieć zdolność: string, numeryczną, może datową.
A przez to, że indeksowane stringiem, znakomicie daje namiastkę (o ile można znakomicie dać namiastkę, he he) refleksji

0

@MarekR22:
Mam pytanie czym się różni boost::fusion od zwykłej klasy ? Nie wiem czy dobrze rozumiem kod podany w przytoczonym przez ciebie przykładzie - ale mnie to wygląda na inny zapis w odwoływaniu się do składowych tej struktury - czyli wynaleziono inaczej wyglądające koło i z innym sposobem użycia ale robi to samo co klasa...

0

A to może jest jakaś biblioteka Qt która potrafi JSON zamienić na GUI ?
Taki koncept: struct/class =nlohmann::json=> JSON ==> GUI ==> JSON =nlohmann::json=> struct/class
Wtedy wszystko co da się zamienić na JSON bedzie mozna edytować ;)
Tylko że serializacja do JSON powoduje utratę informacji o typie danych wiec są koszty , bo nie wiem czy da sie zrobić liste rozwijana dla pola enum (serializacja/deserializacja enum działa w nlohmann::json )

0

Taki koncept: struct/class =nlohmann::json=> JSON ==> GUI ==> JSON =nlohmann::json=> struct/class

Próbuję zrozumieć co chcesz osiągnąć więc zadam pytania:

  1. Te struktury przychodzą skądś czy dopiero mają zostać wypełnione przez naukowców? Bo z tego co rozumiem chcesz chyba jednak je tylko zwizualizować?
  2. Jeśli je chcesz wizualizować to czemu nie model MV?

Wyjście z jakimiś strukturami tekstowymi to bardziej xml niż json. xml dlatego że plik .ui z qtdesignera to nic innego jak plik xml. Mógłbyś teoretycznie napisać sobie konwerter struktura -> plik .ui -> załadować go w qt. Ale nadal usiłuję zrozumieć problem. Swoją drogą są narzędzia które robią .ui -> c++.

0

Zlecam na zewnątrz osobie z zacięciem badawczo naukowym stworzenie biblioteki
po stronie interfejsu kilka zmiennych (tego potem używa użytkownik w postaci suwaka)
w środku biblioteki kilkadziesiąt zmiennych i krzywe kalibracyjne (w procesie badawczym szukamy najlepszych ustawień)

Na etapie badawczym potrzebne są okna do wizualizacji danych i potem ewentualnie do strojenia
i na razie bylo LabView albo w Delphi dodawane były kolejne okna do edycji wewnętrznych struktur
Chciałbym ciężar przenieść na stronę badawczą: niech każdy robi jak chce pod warunkiem że dziedziczy z mojej klasy BAZOWEJ ;)

Chodziło by też o zbudowanie jakiegoś wewnętrznego standardu

0

A naukowcy koniecznie chcą oglądać ten kod w QT/C++ ?

Bo może lepszym rozwiązaniem byłby frontend w JS i użycie czegoś w stylu https://github.com/json-editor/json-editor ?

A tak w ogóle to naucz ich używać Jupyter Lab-a... ;)

3
MarekR22 napisał(a):

Źle do tego podchodzisz.

W C++ nie ma meta danych, ale Qt ma na to mechanizmy:

struct StructT1
{
  Q_GADGET
  Q_PROPERTY(std::string name MEMBER name)
  Q_PROPERTY(int   data1 MEMBER data1)
  Q_PROPERTY(int16 data2 MEMBER data2)
  Q_PROPERTY(int8 data3 MEMBER data3)
  
  std::string name;
  int   data1; 
  int16 data2; 
  int8 data3;
};

Następnie posługując się danymi z QMetaObject można jednym kodem wydłubać dowolna informację o dancyh.
Niestety Qt nie udostępnia w prosty sposób edytora właściwości taki jaki jest dostępny w Qt Designer dla Qt Creator, ale z tego co widziałem są dostępne jakieś gotowe rozwiązania.

Inny sposób to skorzystać z boost::fusion albo boost::pfr (tu niestety dostęp do nazwy nie jest prosty, ale też możliwy).

Zamiast gadać lepiej pokazać działajacy kod.

Okazuje się, że nie jest to skomplikowane:

#ifndef GADGETEDITOR_H
#define GADGETEDITOR_H

#include <QAbstractTableModel>

#include "GadgetEditor_global.h"

class GADGETEDITOR_EXPORT GadgetDataModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    explicit GadgetDataModel(QObject* parent);

    template<typename T>
    void setGadget(T *gadget)
    {
        beginResetModel();
        mGadgetValue = gadget;
        mGadgetMetaData = T::staticMetaObject;
        endResetModel();
    }

    // QAbstractItemModel interface
public:
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
    bool setData(const QModelIndex &index, const QVariant &value, int role);
    Qt::ItemFlags flags(const QModelIndex &index) const;

private:
    void* mGadgetValue = nullptr;
    QMetaObject mGadgetMetaData;
};

#endif // GADGETEDITOR_H
#include "GadgetDataModel.h"
#include <QMetaProperty>

GadgetDataModel::GadgetDataModel(QObject* parent)
    : QAbstractTableModel{parent}
{
}

int GadgetDataModel::rowCount(const QModelIndex &parent) const
{
    return parent.isValid() || !mGadgetValue ? 0 : mGadgetMetaData.propertyCount();
}

int GadgetDataModel::columnCount(const QModelIndex &parent) const
{
    return parent.isValid() || !mGadgetValue ? 0 : 2;
}

QVariant GadgetDataModel::data(const QModelIndex &index, int role) const
{
    switch (role) {
    case Qt::DisplayRole:
    case Qt::EditRole:
        if (index.column() == 0) {
            return QString{mGadgetMetaData.property(index.row()).name()};
        } else {
            return mGadgetMetaData.property(index.row()).readOnGadget(mGadgetValue);
        }
    default:
        return {};
    }
}

bool GadgetDataModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    switch (role) {
    case Qt::DisplayRole:
    case Qt::EditRole:
        if (index.column() == 1) {
            if (mGadgetMetaData.property(index.row()).writeOnGadget(mGadgetValue, value)) {
                emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
                return true;
            }
        }
    }
    return false;
}

Qt::ItemFlags GadgetDataModel::flags(const QModelIndex &index) const
{
    constexpr auto BasicFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    if (index.column() == 0) {
        return BasicFlags;
    }
    return BasicFlags | Qt::ItemIsEditable;
}

Repo z apką testową.

0

Bardzo edukacyjny przykład !

Co mnie zastanawia w dalszym rozwoju tego komponentu:
Czy da się w strukturze gadget::Foo jakoś dodać wiecej informacji o danym polu struktury ? np. zmodyfikować "in place editor" , czy zamiast tekstu zrobić combo box dla wybranego pola ?

Zastanawia mnie też uzycie :

#if defined(GADGETEDITOR_LIBRARY)
#  define GADGETEDITOR_EXPORT Q_DECL_EXPORT
#else
#  define GADGETEDITOR_EXPORT Q_DECL_IMPORT
#endif

bo bez tego też zadziała , tyle że w DLL bedzie wiecej wyeksportowanych funkcji
Dłuższa lista eksportowanych funkcji to jakiś problem ?
Albo moze to ma znaczenie dla kompilatora Microsoft ?

Jak deklaracja jest tak

class GADGETEDITOR_EXPORT GadgetDataModel : public QAbstractTableModel

to nie można zbudować STATIC tego modułu GadgetEditor

Za uwarzyłem tez subtelną różnice pomiędzy uruchomieniem przykładu na QtCreator oraz "Visual Studio Code"
Podczas uruchamiania QtCreator dodał do PATH katalog gdzie zbudowany był DLL , w przypadku "Visual Studio Code" musiałem sobie skopiować plik DLL do folderu EXE

0
Adamek Adam napisał(a):

Zastanawia mnie też uzycie :

#if defined(GADGETEDITOR_LIBRARY)
#  define GADGETEDITOR_EXPORT Q_DECL_EXPORT
#else
#  define GADGETEDITOR_EXPORT Q_DECL_IMPORT
#endif

To mi wygenerował Qt Creator podczas tworzenia dll-ki.
Generalnie chodzi o to, że na niektórych platformach (Windows) deklarując funkcję eksportowaną z dll-ki trzeba dodać jeden prefiks, a w momencie importowania tej deklaracji do użytkownika biblioteki prefiks jest inny. To makro właśnie pokrywa ten problem.

Adamek Adam napisał(a):

Dłuższa lista eksportowanych funkcji to jakiś problem ?

Nie.

Adamek Adam napisał(a):

Za uwarzyłem tez subtelną różnice pomiędzy uruchomieniem przykładu na QtCreator oraz "Visual Studio Code"> Podczas uruchamiania QtCreator dodał do PATH katalog gdzie zbudowany był DLL , w przypadku "Visual Studio Code" musiałem sobie skopiować plik DLL do folderu EXE

Nie testowałem na innych platformach, tylko na MacOS.
To musi być jakaś właściwość generatora dla MSVS. Qt Creator używa chyba Ninja Multi Config (nie sprawdzałem, to mój domysł).
Zawsze można pokombinować z ustawieniami cmake, żeby zapisywał wszystko w jednym określonym katalogu.
W każdym razie, nie to jest tematem tego wątku.

0

Mój koncept zarysowany na początku wątku i "wymęczony" przez praktykanta:
https://github.com/mariuszmaximus/qt_gadget_edit_model

Przykład @MarekR22 byłby jeszcze lepszy jakby dało się "gdzieś" opisać jak dane pole w widoku ma być wizualizowane

0
Adamek Adam napisał(a):

Przykład @MarekR22 byłby jeszcze lepszy jakby dało się "gdzieś" opisać jak dane pole w widoku ma być wizualizowane

The Property System | Qt Core 6.3.2

Requirements for Declaring Properties

To declare a property, use the Q_PROPERTY() macro in a class that inherits QObject.

Q_PROPERTY(type name
           (READ getFunction [WRITE setFunction] |
            MEMBER memberName [(READ getFunction | WRITE setFunction)])
           [RESET resetFunction]
           [NOTIFY notifySignal]
           [REVISION int | REVISION(int[, int])]
           [DESIGNABLE bool]
           [SCRIPTABLE bool]
           [STORED bool]
           [USER bool]
           [BINDABLE bindableProperty]
           [CONSTANT]
           [FINAL]
           [REQUIRED])

....

  • The DESIGNABLE attribute indicates whether the property should be visible in the property editor of GUI design tool (e.g., Qt Designer). Most properties are DESIGNABLE (default true). Valid values are true and false.

Mój kod trzeba poprawić by to uwzględniał.

1 użytkowników online, w tym zalogowanych: 0, gości: 1