Как реализовать универсальный класс button, который выполняет разные функции при нажатии?

#c #button #sdl

#c #кнопка #sdl

Вопрос:

Хорошо, я использую SDL с C и начал работать над простым графическим интерфейсом для моего редактора карт. На основе рабочего прототипа одной кнопки я написал универсальный класс GUIObject и универсальный класс Button. Но сейчас у меня проблема, чтобы класс можно было использовать повторно, мне нужно иметь возможность устанавливать для каждой кнопки, какую функцию она вызывает (т.Е. Помогите! открывает окно справки, Открыть открывает диалоговое окно file, это один и тот же класс Button, но они выполняют разные функции).

Некоторое время назад я использовал простую библиотеку для Ruby GUI, и в ее синтаксисе использовалось нечто, обозначенное там как «оператор блока»:

Пример:

 @login_button = Button.new(@window, 98, 131, Network_Data::LOGIN_BUTTON){execute_login}
  

Последняя часть {} была тем, что вы помещали свою собственную функцию, а класс каким-то образом извлекал ее и выполнял эту функцию при нажатии. Я также слышал, что в Java есть нечто подобное:

 onClick = new Function() { }
  

Итак, как бы мне пойти и реализовать это в программе на C (которая использует библиотеки SDL). Я не хочу использовать какие-либо библиотеки GUI, потому что они кажутся слишком сложными, и мне было бы трудно вставить эти библиотеки в мой уже созданный код.

Ответ №1:

Вы можете сделать это, используя указатели на функцию / функтор / метод. (функций должно быть достаточно и они должны быть относительно простыми)

вы можете передать указатель на функцию обратного вызова в вашем конструкторе button.

Вот базовый пример использования указателей на функции.

 class Button
{   
    typedef void(*eventFunction)();

    Button( eventFunction funcPtr ) : m_funcPtr( funcPtr ){}
    ///< A constructor.
    /// @param funcPtr The function pointer to register.

    ~Button(){ m_funcPtr = NULL; }
    ///< A destructor.          

    void operator() () const
    {
        (m_funcPtr)();
    }
    ///< Invokes the registered function.

    private:
        eventFunction m_funcPtr;
};
  

И вы можете использовать его таким образом:

 void myFunction1() { /* do something */ }
void myFunction2() { /* do something else */ }

Button myButton1( amp;myFunction1 );
myButton1(); // invoke the function registered

Button myButton2( amp;myFunction2 );
myButton2(); // invoke the function registered
  

Вот более сложный пример с использованием шаблонов переменных (C 0x)

 template < class Tr, class... Args >
class Button
{
    typedef Tr(*eventFunction)(Args...);

    Button( eventFunction funcPtr ) : m_funcPtr( funcPtr ){}
    ///< A constructor.
    /// @param funcPtr The function pointer to register.

    ~Button(){ m_funcPtr = NULL; }
    ///< A destructor.          

    void operator() ( Argsamp;... args ) const
    {
        (m_funcPtr)(args...);
    }
    ///< Invokes the registered function with the provided arguments.
    /// @param args The arguments to transmit to the invoked function.

    private:
        eventFunction m_funcPtr;
};
  

И вы можете использовать его таким образом:

 void myFunction1( int, double ) { /* do something */ }
void myFunction2() { /* do something */ }

Button<void, int, double> myButton1( amp;myFunction1 );
myButton1( 10, 20.5 );  // invoke the function registered with two arguments

Button<void> myButton2( amp;myFunction2 );
myButton1(); // invoke the function registered without argument
  

Tr Шаблон можно удалить и заменить на void , если вы никогда не планируете использовать другие функции, кроме функций, возвращающих void.

Таким образом, Button<void, int, double> myButton1( amp;myFunction1 ); изменилось бы на Button<int, double> myButton1( amp;myFunction1 );

и Button<void> myButton2( amp;myFunction2 ); изменился бы на Button<> myButton2( amp;myFunction2 );


Вы можете сделать почти то же самое с методами (вам нужно зарегистрировать указатель на экземпляр вызываемого класса И указатель на метод) и с функторами (вам нужно зарегистрировать указатель на экземпляр вызываемого функтора).


По вашему требованию вот тот же класс, который выполняет это с помощью методов. Вам решать, как заставить ваш класс Button принимать функции, функторы и методы одновременно (подумайте о virtual ^^). Это версия C 0x

 template < class C, class Tr, class... Args >
class Button
{
    typedef Tr(C::*eventMethod)(Args...);
    ~Button(){}

    Button( Camp; target, eventMethod method ) : m_target(target), m_method(method) {}
    ///< A constructor.
    /// @param target The class member instance used to call the method.
    /// @param method The class member method pointer to register.

    bool operator () ( Argsamp;... args ) const
    {
        (m_target.*(m_method))(args...);
    }
    ///< Invokes the registered method with the provided arguments.
    /// @param args The arguments to transmit to the invoked method.

    private:
        eventMethod m_method;
        Camp; m_target;
};
  

И вы можете использовать его таким образом:

 class MyClass
{
    void method1(){};
    void method2( int, double ){};
};

MyClass myC1;

Button<MyClass, void> myButton1( myC1, amp;MyClass::method1 );
myButton1(); // invoke the registered method using the registered class instance.

Button<MyClass, void, int, double> myButton2( myC1, amp;MyClass::method2 );
myButton2( 15, 170.5 ); // invoke the registered method using the registered class instance.
  

Если вам нужно вызвать его изнутри класса, к которому принадлежит метод, используйте this в качестве целевого параметра вместо myC1 .

Комментарии:

1. Ах, это выглядит красиво, и я могу в основном понять ваши примеры, поэтому я попытаюсь реализовать это в своем коде, и я выбираю это в качестве предпочтительного ответа. Только еще один вопрос, зачем вам нужно было добавлять void в качестве первого аргумента в сложном примере?

2. Это шаблонное значение «Tr», возвращаемый тип функции, на которую вы указываете. В моем примере я ничего не возвращаю, но operator () может вернуть значение, возвращаемое функциями, на которые вы указываете… Я изменил свой ответ, чтобы объяснить, как удалить его при необходимости. Обязательно учтите, что для второго класса требуется C 0x.

3. Еще раз огромное вам спасибо, вы действительно нашли время написать более длинный пример!

4. Как бы выглядел этот пример с методом, и если я инициализирую кнопку внутри того же класса? У меня есть класс с именем Engine, кнопка инициализирована как: Button<void> Button_Test( amp;ChangePassability()); а ChangePassability является общедоступной функцией-членом Engine.

5. Добавлено, и второй ответ — «это». Теперь я возвращаюсь к своей ошибке 😉

Ответ №2:

Существует два подхода. Вы можете использовать полиморфизм во время выполнения ( boost::function или std::tr1::function или std::function ) или вы можете использовать шаблон.

Ответ №3:

Вам понадобится ваш класс button для вызова функций, указанных его пользователями. Я рекомендую вам взглянуть на библиотеку Boost Signals. Эта библиотека реализует подход сигналов и слотов, имеет очень хорошую документацию и очень хорошо поддерживается. Кроме того, эти ссылки должны быть полезны для получения общей идеи:

Надеюсь, это поможет. Удачи!

Ответ №4:

В c это работает почти так же, как вы использовали бы в java:

 onClick = new ClickFunction;
  

но тогда вам, очевидно, потребуется следующее, чтобы определить необходимые классы:

 template<class A, class B>
class Function { public: virtual B Map(A a) const=0; };

Function<int, int> *onClick;

class ClickFunction : public Function<int,int>
{
public:
    ClickFunction(MyObject amp;o) : o(o) { }
    int Map(int a) const { return 10; }
private:
    MyObject amp;o;
};
  

Комментарии:

1. Итак, с этим я бы просто добавил ваш код, установил onClick и затем передал его в качестве аргумента (т. Е. Button(x, y, width,height, function)?