Я пытаюсь выяснить, как решить эту проблему наиболее элегантно, используя c ++ 17 (без повышения!). Я работаю над библиотекой, предоставляющей элементы управления пользовательским интерфейсом. Элементы управления должны иметь возможность реагировать на события, а пользователь (используя библиотеку) должен иметь возможность добавлять свои обработчики для этих событий. Я хочу избежать шаблона Command , поскольку это слишком сложно для пользователя - он просто хочет определить одну функцию и назначить ее для некоторого элемента управления события и пользовательского интерфейса. Вот некоторый пример кода:
// My library
class Base; // Base class for all the UI controls
class ListBox : public Base {
// handlers reacting on clicking the item in listbox
std::vector<std::variant<std::function<void()>,
std::function<void(int)>>> click_handlers_;
// handlers reacting on changing the name of the item in listbox
std::vector<std::variant<std::function<void()>,
std::function<void(std::string, std::string)>>> edit_handlers_;
public:
void AddHandler(Event ev, const std::function< ? ? ? > handler);
};
// USER's code
int main() {
ListBox lb;
lb.AddHandler(Event::Click, [](int index) { DoSomethingWithItem(i); });
lb.AddHandler(Event::Edit, [](std::string old_name, std::string new_name)
{ DoSomethingWithItemNames(old_name, new_name); });
lb.AddHandler(Event::Edit, []() { DoSomeCleanup(); });
}
Когда происходит событие Event::Edit
(типа enum
), оно запускает DoSomethingWithItemNames()
, что требует обоих аргументов. Сразу после этого DoSomeCleanup()
выполняет свою работу, и событие обрабатывается (по крайней мере, с точки зрения пользователя).
- Проблема: для каждого события LisBox мне нужно определить отдельный вектор-обработчик .
- Проблема: каким должен быть тип второго аргумента метода
ListBox::AddHandler
?
Есть ли способ объединить эти обработчики, чтобы пользователь мог использовать только один функция AddHandler()
, а не отдельные функции, такие как AddClickHandler()
и AddEditHandler()
? Конечно, типы функций должны проверяться статически, и о любых несоответствиях типов следует сообщать пользователю во время компиляции. Есть ли стандартный способ лечения всей этой концепции? Имейте в виду, что каждый элемент управления имеет свои события, и я считаю, что каждому событию может быть назначена функция-обработчик без аргументов. Спасибо.
РЕДАКТИРОВАТЬ
После рассмотрения возникла другая проблема. дизайн библиотеки выглядит так: это . События генерируются системой, которая затем вызывает предопределенные каркасные методы, из которых мне нужно вызывать обработчики пользователя (например, в Qt сигнал itemDoubleClicked(QListWidgetItem *item)
испускается, когда элемент дважды щелкается внутри QListWidget). Следовательно, классу реализации (FrameWorkAListBox
) требуется доступ к сохраненным обработчикам (которые в настоящее время находятся в классе оболочки ListBox
). Существует больше платформ, предназначенных для использования, поэтому, где и как я должен их хранить, чтобы они не дублировались, а архитектура оставалась легко расширяемой?
РЕДАКТИРОВАТЬ 2
После нескольких попыток и дополнительного изучения я получил следующее решение:
// first define some aliases
template<class... Args>
using Handler = std::function<void(Args...)>; // event handler
template<class... Args>
using VariableHandler = std::variant<Args...>;
template<class T>
using Event = std::list<T>; // list of handler variants
// this is for easier std::visit use, taken from
https://en.cppreference.com/w/cpp/utility/variant/visit
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
// ListBox.cpp - interface class, used by the user
void ListBox::AddDoubleClickHandler(
const VariableHandler<Handler<>, Handler<int>>& handler) {
// forwards the handler to the implementation class (pushes the
// handler into the ev_double_click_ vector since wrapper class cannot
// have any other private fields aside from pointer to the
// implementation (according to pimpl idiom)
}
// ListBoxWindowsImpl.h - native Windows class representing UI control
class ListBoxImpl : public SomeNativeWindowsListBoxClass {
public:
// double-click event handler can have 0 or 1int argument
Event<VariableHandler<Handler<>, Handler<int>>> ev_double_click_;
// this gets called automatically by the system, when the user
// double-clicks an item within the listbox
void OnItemDoubleClick(int index) {
for (const auto& h : ev_double_click_) {
std::visit(overloaded {
[](Handler<> arg) { arg(); },
[index](Handler<int> arg) { arg(index); },
}, h);
}
}
Я хотел бы знать, есть ли способ добавить новые обработчики без необходимости добавления новой функции в класс ListBox , Было бы хорошо, если бы добавление обработчика двойного щелчка и новых обработчиков (например, изменение выделения, одиночный щелчок ...) могло быть выполнено одной функцией, например ListBox :: AddHandler. И лучше всего было бы, если бы при добавлении нового обработчика тело этого метода не нужно было изменять, чтобы интерфейс библиотеки (в данном случае класса ListBox) не нужно было перекомпилировать. Спасибо.