Using Dbo::QueryModel to display enum fields as strings
Added by Roy Wiggins over 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 over 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 over 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