Hi everyone,
I just joined this mailing list. My name is Michal, and I work as a software engineer. Apologies if this personal introduction is out of place, but hey, the guide for contributing specifically asked for it.
Anyway, I'm posting here to discuss a suggestion I have for a small addition to the project. I've been recently working on a list/listbox like container for my project and one specific aspect of it led me to writing some questionable code that I feel like it shouldn't be necessary and I think I know how it can be improved. So please, humour me for a moment.
I have a class that holds some arbitrary data and, crucially, is unaware of wxWidgets. It's purely oriented on business logic. Let's call it "Foo". When selecting an item in said list, I'd like to fire off an event (ideally wxListEvent, but wxCommandEvent works too) and pass a pointer to "Foo" that matches the item in the list or nullptr if the item in the list doesn't represent "Foo". This allows the event handler to display the content of Foo in a separate view and decouples the list from other UI elements. I know of two ways to do it. One is to pass a void* to wxCommandEvent::SetClientData(void*) and then in the event handler use static_cast to cast it back to "Foo". However this is a very brittle design, and I don't like it. The other option (as I understand it) is to create a new class that inherits from wxClientData, then pass an instance of it to wxCommandEvent::SetClientObject(wxClientData*). Which is better, but requires creating (and naming) a class in question for each type or alternatively making "Foo" inherit from wxClientData. In my case, like I said earlier, Foo is intentionally not tied to wxWidgets, so inheritance is out of the question and creating a special wrapper class works quite well, but I think it can be made better.
I'm sure I'm not the first and won't be the last person who will have this problem, which is why I'd like to propose a solution to it, in the form of a templated wrapper for wxClientData of the following form.
template <typename T> class wxClientDataWrapper : public wxClientData {
public:
wxClientDataWrapper(T obj) : m_data(obj){}
T m_data;
};
This can then be used to pass arbitrary data in a single function call, possibly even in a single line, like so:
wxListEvent* e = new wxListEvent(wxEVT_LIST_ITEM_SELECTED, GetId());
e->SetClientObject(new wxClientDataWrapper<Foo*>(pointer_to_foo));
wxQueueEvent(this, e);
and then retrieved in the event handler like so:
void Bar::OnSelectionChanged(wxListEvent& e) {
Foo* f = dynamic_cast<wxClientDataWrapper<Foo*>*>(e.GetClientObject())->m_data;
}
Yes, this needs some extra care with object ownership and all, but with the magic of RTTI it allows for safely passing data around without tying it to wxWidgets too much.
I've tested this idea with MSVC and it behaves as expected. I can retrieve the m_data when the template is specialized the same way on both ends and it throws an exception if they are mismatched and I don't check them. I haven't tried it with GCC or LLVM, and I'm not sure how this will play with crossing boundaries of different binaries, so please share your wisdom with me.
Please note that m_data is of type T and not T*. I'd like for it to stay this way, on an off chance someone decides to quickly add it to the project, as it allows for passing simple things like std:vector or std:tuple or even std::shared_ptr without forcing the use of raw pointers.
In any case, I'm looking forward to hear what people here think of the idea and in the mean time I'd like to wish all of you a nice evening.
Sincerely,
Michal Kaczorowski