Project

General

Profile

Using Dbo::QueryModel to display enum fields as strings

Added by Roy Wiggins about 7 years ago

So, I'm trying to display some data from a database using Dbo::QueryModel. It uses enums for categorical data.

By default, QueryModel displays enums as the backing integer type. I had hoped to register the enum type with Wt::registerType and have Wt::QueryModel use the operator<< defined on the type, but it doesn't seem to be working completely.

I can register the type just fine:

    EventInfo::Type foo = EventInfo::Type::Generic;
    boost::any bar = boost::any(foo);
    cout << Wt::asString(bar) << std::endl; // prints "Generic", from the associated operator overload

But, when I use that type as a field in a Dbo object, Dbo::QueryModel displays the underlying int.

The QueryModel is as simple as possible ("Event" has a field

EventInfo::Type type

)

    dbo::QueryModel< dbo::ptr<Event> > *model = new dbo::QueryModel< dbo::ptr<Event> >();
    model->setQuery(app->db.find<Event>());
    model->addAllFieldsAsColumns();
    WTableView *view = new WTableView();
    view->setModel(model);

Should this work? Or do I need to write my own delegate?


Replies (2)

RE: Using Dbo::QueryModel to display enum fields as strings - Added by Roy Wiggins about 7 years ago

Aha: insofar as sql_value_traits supports enums, by the time the item delegate sees them, they've become ints. So I would have to specialize sql_value_traits to do this like I wanted.

I'm not sure how to do that properly, so in the meantime, I've come up with a dumb-as-rocks solution that uses a Delegate to cast these fields back to their enum types and display them (there's no logic here for edits- this table is meant to be read-only):

template<class E,class F>
class EnumDelegate : public Wt::WItemDelegate {
public:
    EnumDelegate(Wt::WAbstractItemModel* items)
    { }

    virtual WWidget *update(WWidget *widget, const WModelIndex& index,
                WFlags<ViewItemRenderFlag> flags) {
        WText* text;
        if (!widget) {
            text = new WText();
        } else {
            text = dynamic_cast<WText *>(widget);
        }
        E val = static_cast<E>(boost::any_cast<int>(index.data()));
         WString label = Wt::asString(val, "");
         text->setText(label);
         return text;
    }

    static void register_(dbo::QueryModel<dbo::ptr<F>>* model, Wt::WTableView* view) {
        Wt::registerType<E>();
        for (int i=0; i<model->columnCount(); i++) {
            Wt::Dbo::FieldInfo info = model->fieldInfo(i);
            if (*(info.type()) == typeid(E)) {
                view->setItemDelegateForColumn(i,new EnumDelegate<E,F>(model));
            }
        }
    }
};

It's doing unnecessary loops, but it's very simple to use once you've defined operator<< on your enum:

    EnumDelegate<EventInfo::Type,Event>::register_(model, view);

This works okay.

RE: Using Dbo::QueryModel to display enum fields as strings - Added by Maximilian Kleinert about 5 years ago

You have to specialize the Wt::Dbo::ToAny and Wt::Dbo::FromAny structs in order to avoid the integer conversion:

#include <Wt/Dbo/Dbo.h>
#include <Wt/WAny.h>

namespace Wt {
    namespace Dbo {
        template<>
        struct ToAny<MyEnumClass> {
            static cpp17::any convert(const MyEnumClass &v)
            { return v; }
        };

        template<>
        struct FromAny<MyEnumClass> {
            static MyEnumClass convert(const cpp17::any &v)
            { return (cpp17::any_cast<MyEnumClass>(v)); }
        };
    }
}

To get it displayed correctly in a model (now they are stored as the enum type and not as an integer) you have to specialize the Wt::any_traits struct (see https://webtoolkit.eu/wt/doc/reference/html/structWt_1_1any__traits.html):

#include <Wt/WAny.h>

namespace Wt {
    template<>
    struct any_traits<MyEnumClass> {
        static WString asString(const MyEnumClass &value, const WString &)
        {
            return EnumtoString(value);
        }

        static double asNumber(const MyEnumClass &v)
        {
            return static_cast<double>(static_cast<std::underlying_type_t<MyEnumClass>>(v));
        }

        static int compare(const MyEnumClass &v1, const MyEnumClass &v2)
        {
            return v1 == v2 ? 0 : (v1 < v2 ? -1 : 1);
        }
    };
}

Finally you have to call the types via Wt::register (see https://webtoolkit.eu/wt/doc/reference/html/group__modelview.html#gaeb2f9c583490833afd55d65402b4fea9) in you Application:

Wt::registerType<MyEnumClass>();

If you have multiple (scoped) enums you can use this helper (adopt is_scoped_enum_v by is_enum_v if you like to support them):

#include <Wt/WAny.h>

// see https://stackoverflow.com/questions/15586163/c11-type-trait-to-differentiate-between-enum-class-and-regular-enum
#include <type_traits>

namespace Wt::ScopedEnumHelper {
    template<typename E>
    using is_scoped_enum = std::integral_constant<
            bool, std::is_enum_v<E> && !std::is_convertible_v<E, int>>;
    template<typename E>
    inline constexpr bool is_scoped_enum_v = is_scoped_enum<E>::value;

    template<typename V, class Enable = void>
    struct ToAny;
    template<typename V, class Enable = void>
    struct FromAny;

    template<typename Enum>
    struct ToAny<Enum, std::enable_if_t<is_scoped_enum_v<Enum>>> {
        static cpp17::any convert(const Enum &v)
        { return v; }
    };

    template<typename Enum>
    struct FromAny<Enum, std::enable_if_t<is_scoped_enum_v<Enum>>> {
        static Enum convert(const cpp17::any &v)
        { return (cpp17::any_cast<Enum>(v)); }
    };

    template<typename V, class Enable = void>
    struct any_traits;

    template<typename Enum>
    struct any_traits<Enum, std::enable_if_t<is_scoped_enum_v<Enum>>> {
        static WString asString(const Enum& value, const WString&)
        {
            return EnumtoString(value);
        }

        static double asNumber(const Enum& v)
        {
            return static_cast<double>(static_cast<std::underlying_type_t<Enum>>(v));
        }

        static int compare(const Enum& v1, const Enum& v2)
        {
            return v1 == v2 ? 0 : (v1 < v2 ? -1 : 1);
        }
    };
}

#define SCOPEDENUMHELPER_SETUP_ANY_CONVERSION(type)                         \
namespace Wt {                                                              \
    namespace Dbo {                                                         \
        template<>                                                          \
        struct ToAny<type> : public ScopedEnumHelper::ToAny<type> {};       \
        template<>                                                          \
        struct FromAny<type> : public ScopedEnumHelper::FromAny<type> {};   \
    }                                                                       \
    template<>                                                              \
    struct any_traits<type> : public ScopedEnumHelper::any_traits<type> {}; \
}

and add these with

SCOPEDENUMHELPER_SETUP_ANY_CONVERSION(MyEnumClass)

You have to adopt the EnumToString function template to your need.

Best regards

Max

    (1-2/2)