Russian Qt Forum

Qt => Пользовательский интерфейс (GUI) => Тема начата: Rosster от Апрель 26, 2013, 14:59



Название: connect и динамический отправитель
Отправлено: Rosster от Апрель 26, 2013, 14:59
Всем привет. У меня в connect отправитель все время меняется. Вот пример:
Код:
Class Main
{
   public slots:
      virtual void add() = 0;
}
Class A : public Main
{
   public slots:
      void add() {cout << "A";}
}
Class B : public Main
{
   public slots:
      void add() {cout << "B";}
}

MainWindow::MainWindow(QWidget *parent)
{
   A *a = new A();
   B *b = new B();
   Main *main = a;
   // connect пишется после инициализации main, иначе это критическая ошибка.
   connect(sender, SIGNAL(signal()), main, SLOT(add()));
   emit sender->signal();
   main = b;
   emit sender->signal();
}
Вместо sender - виджет с его сигналом. Так вот: в первом случае вывод будет "А", и во втором тоже "А", а мне нужно понятное дело "В". Как быть? disconnect и снова connect не вариант. Так как A, B.... и виртуальных функций может быть много, в итоге неимоверный код. Сейчас приходится прибегать к тому, что создавать в MainWindow слот и уже там писать одну лишь строку {main->add()}. Но код то это не красит...
Есть ли решение?
Второй вопрос: почему даже если я поставлю в классе Main доступ private slots, то компилятор не ругнется и программа при запуске сработает нормально, хотя connect не имеет права уже этот метод использовать?
Спасибо.
P.S. Сорри за краткость и неполноту кода, главное что смысл понятен.


Название: Re: connect и динамический отправитель
Отправлено: Serr500 от Апрель 26, 2013, 15:24
QMetaObject::invokeMethod (http://qt-project.org/doc/qt-4.8/qmetaobject.html#invokeMethod) не подойдёт?


Название: Re: connect и динамический отправитель
Отправлено: m_ax от Апрель 26, 2013, 15:44
Может и не нужны никакие сигналы?

Можно сделать так, как вариант:

Код
C++ (Qt)
#include <iostream>
#include <functional>
 
struct base  {
   virtual ~base() {}
   virtual void print() = 0;
};
 
struct derived1 : public base {
   virtual void print() { std::cout << "A" << std::endl; }
};
 
struct derived2 : public base {
   virtual void print() { std::cout << "B" << std::endl; }
};
 
 
int main()
{
   base *d1 = new derived1;
   base *d2 = new derived2;
 
   std::function<void(base*)> func(&base::print);
 
   func(d1);
   func(d2);
 
   return 0;
}
 


Название: Re: connect и динамический отправитель
Отправлено: mutineer от Апрель 26, 2013, 15:48
Для слота не принципиально private он или public, потому что при вызове через сигнал/слот вызывает слот сам объект-владелец слота, а не кто-то снаружи.

Твое решение не работает, потому что connect привязывается к конкретному объекту в момент коннекта, а не в момент испускания сигнала. Рассматривай варианты выше


Название: Re: connect и динамический отправитель
Отправлено: Rosster от Апрель 26, 2013, 16:22
QMetaObject::invokeMethod (http://qt-project.org/doc/qt-4.8/qmetaobject.html#invokeMethod) не подойдёт?
Можно поподробней? Почитал про него, он нужен при потоках и просто вызывает слот.Тоже самое я могу сделать в слоте
MainWindow::add() {QMetaObject::invokeMethod(main, "add");} вместо
MainWindow::add() {main->add();}
Или я чет не догоняю?

Может и не нужны никакие сигналы?
Не подойдет. У меня sender - это кнопка в интерфейсе, кнопка мультизадачная, на нее нужно повесить слот add(), который вызывается на другом виджете.
Короче это похоже на задачу: одна кнопка "добавить запись" и куча виджетов для которых эта кнопка пригодится.


Название: Re: connect и динамический отправитель
Отправлено: m_ax от Апрель 26, 2013, 16:34
Не подойдет. У меня sender - это кнопка в интерфейсе, кнопка мультизадачная, на нее нужно повесить слот add(), который вызывается на другом виджете.
Короче это похоже на задачу: одна кнопка "добавить запись" и куча виджетов для которых эта кнопка пригодится.

Если sender в курсе (в смысле знает) у какого именно объекта в данный момент должен быть вызван слот, то проблемы не вижу..
Сигнал кнопки можно повесить на слот (как некий посредник), в котором уже вызывать std::function с нужным указателем на виджет...


Название: Re: connect и динамический отправитель
Отправлено: mutineer от Апрель 26, 2013, 17:02
QMetaObject::invokeMethod (http://qt-project.org/doc/qt-4.8/qmetaobject.html#invokeMethod) не подойдёт?
Можно поподробней? Почитал про него, он нужен при потоках и просто вызывает слот.Тоже самое я могу сделать в слоте
MainWindow::add() {QMetaObject::invokeMethod(main, "add");} вместо
MainWindow::add() {main->add();}
Или я чет не догоняю?

1) Он нужен не только при потоках, а и например тогда, когда нужно вызвать метод по его строковому имени у неизвестного QObject
2) Сделать можешь, но если main указывает на QObject, то придется кастить


Название: Re: connect и динамический отправитель
Отправлено: Rosster от Апрель 26, 2013, 18:58
Сигнал кнопки можно повесить на слот (как некий посредник), в котором уже вызывать std::function с нужным указателем на виджет...
Сделать можешь, но если main указывает на QObject, то придется кастить
У меня уже есть посредник (смысл мне в std::function?), сейчас я и пишу:
Код:
MainWindow::MainWindow()
{
   connect(button, SIGNAL(clicked()), this, SLOT(slAdd()));
   // connect(button, SIGNAL(clicked()), main, SLOT(add())); // хочу так
}
MainWindow::slAdd() {main->add();}
Работает без проблем. Сам main наследован от QWidget. И если main будет указывать уже на другой виджет, то вызовется требуемый add().
Тока как я и говорил если у меня куча кнопок а также виртуальных функций,мне для каждого делать посредника? Не люблю лишних функций
Раз нельзя сделать динамический connect, то как это обойти даже с помощью QMetaObject::invokeMethod() я не понял.


Название: Re: connect и динамический отправитель
Отправлено: Bepec от Апрель 26, 2013, 20:42
Rosster, опиши пожалуйста в общих словах, что ты хочешь сделать. Если нехватает слов, нарисуй.

PS ваша проблема скорее всего происходит из плохой архитектуры. Расскажите свою идею, а мы (ну или только я) подскажу как её можно реализовать просто и наглядно.


Название: Re: connect и динамический отправитель
Отправлено: Rosster от Апрель 26, 2013, 21:20
Хорошо:) Есть QMainWindow. На нем штук 10 кнопок. В QTabWidget куча вкладок с виджетами. Их может быть и 3 и 10. И каждый виджет должен работать с этими кнопками. Поэтому было принято решение наследоваться этим виджетам от главного, в котором виртуальные функции для каждой из кнопок. Благодаря этому каждому виджету придется (и это правильно) работать с этими кнопками. В итоге такой код:
Код:
Class Manager : public QWidget
{
public slots:
   virtual void add() = 0;
   virtual void del() = 0;
   ...
}

Class ListWidget : public Manager
{
public slots:
   void add() {}
   void del() {};
   ...
}

Class TreeWidget : public Manager
{
public slots:
   void add() {}
   void del() {};
   ...
}

Class MainWindow : public QMainWindow
{
   MainWindow();
public slots:
   void slChange();
   void slAdd();
   Manager *_manager;
}
//.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    ui->setup(this); //допустим там 2 виджета ui->listWidget и ui->treeWidget

    manager = ui->listWidget;

    connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(slChange()));

    // connect(ui->button, SIGNAL(clicked()), manager, SLOT(add())); // не катит
    connect(ui->button, SIGNAL(clicked()), this, SLOT(slAdd())); //все отлично
}

MainWindow::slChange()
{
   if (ui->tabWidget->currentWidget() == ui->listWidget)
       manager = ui->listWidget;
   
   if (ui->tabWidget->currentWidget() == ui->treeWidget)
       manager = ui->treeWidget;
}

MainWindow::slAdd() {manager->add();}

Получается для add(), del() и т.д. нужно писать отдельный слот. Для типичной реализации нужно для трех классов писать кучу слотов. Причем MainWindow - посредник. Получается как минимум некрасивый код.


Название: Re: connect и динамический отправитель
Отправлено: thechicho от Апрель 28, 2013, 17:34
Manager *_manager;
manager = ui->listWidget;

чот я нифига не понял.
есть QMainWindow. на нем QTabWidget. на какой-то вкладке есть QListWidget
так же на QMainWindow есть кнопка добавить pushButtonAdd
MainWindow::on_pushButtonAdd_clicked()
{
    if (ui->tabWidget->currentIndex() == 0) {
    ....
    } else if (ui->tabWidget->currentIndex() == 1) {
        ui->listWidgetN->addItem(ui->lineEditN->text());    
    } else if (ui->tabWidget->currentIndex() == N) {
    ...
    }
}


Название: Re: connect и динамический отправитель
Отправлено: Bepec от Апрель 28, 2013, 21:16
Кхм. Правильно я всё таки спросил :D

У вас вот какая ситуация (если неправ, поправьте).

Имеются кнопки, которые должны работать с одним виджетом единовременно и со всеми, скажем так, параллельно.

Зачем вам выносить этот код в каждый виджет? Сделайте проще - 3 слота (add, delete, insert, допустим). И в каждом не жёстко задаёте свой виджет, а используете лишь текущий видимый.

Т.е. примерно так (псевдокод)

Код:
void add()
{
listWidget* point = ui.tabWidget->currentWidget();
// получаем текущий виджет и присваиваем указателю.

// и далее реализуем всё что угодно.
}

PS если кнопки одинаковы, то и реакция на них должна быть одинаковая? Если для каждого виджета реакция должна быть разная, тут никуда не уйти от виртуальных функций.


Название: Re: connect и динамический отправитель
Отправлено: Rosster от Апрель 29, 2013, 09:53
MainWindow::on_pushButtonAdd_clicked()
{
    if (ui->tabWidget->currentIndex() == 0) {
    ....
    } else if (ui->tabWidget->currentIndex() == 1) {
        ui->listWidgetN->addItem(ui->lineEditN->text());    
    } else if (ui->tabWidget->currentIndex() == N) {
    ...
    }
}
И вот так для каждой кнопки расписывать?зачем?Виртуальная функция позволяет это все проделать одной строкой. Но все равно мой код считаю не есть гуд.

PS если кнопки одинаковы, то и реакция на них должна быть одинаковая? Если для каждого виджета реакция должна быть разная, тут никуда не уйти от виртуальных функций.
Именно) Для каждого виджета разная. Нажатие кнопки влияет только на текущую вкладку, и этот add() для каждого виджета разный и этот метод обязателен в классе виджета.


Название: Re: connect и динамический отправитель
Отправлено: Bepec от Апрель 29, 2013, 10:29
Тогда от виртуальных методов никуда не деться.

Совмещайте - получаем текущий виджет и вызываем у него метод кнопочный.

Не связывая слоты.

Типа код:
Код:
// текущий виджет получаем.
QWidget * point = ui.tabWidget->currentWidget();
// и слот такой вот вызываем
QMetaObject::invokeMethod(point, "add");
// строку меняем "add" на "del" или другое любое наименование слота.


Название: Re: connect и динамический отправитель
Отправлено: thechicho от Апрель 29, 2013, 11:37
//И вот так для каждой кнопки расписывать?зачем?Виртуальная функция позволяет это все проделать одной строкой.
как?

зачем городить классы-наследники, если к виджетам уже есть доступ.
можно же сразу все описать в 1 месте в зависимости от текущего индекса таба.
так же будет понятнее и по "затрам" дешевле.
или я не так понял задачу?

if (ui->tabWidget->currentWidget() == ui->listWidget)
       manager = ui->listWidget;

а как тут может быть равенство?

QWidget * QTabWidget::currentWidget () const
Returns a pointer to the page currently being displayed by the tab dialog. The tab dialog does its best to make sure that this value is never 0 (but if you try hard enough, it can be).

currentWidget() возвращает же указатель на вкладку, а не на виджет, расположенный на этой вкладке (тем более их может быть несколько).
или я опять не так понял?


Название: Re: connect и динамический отправитель
Отправлено: Bepec от Апрель 30, 2013, 08:20
Ичика, если создавать табы при помощи метода addTab(QWidget*, QString), то этот метод вернёт установленный виджет (просто при создании в дизайнере вкладки, текущим становится виджет вкладки). Так же присутствует метод setCurrentWidget.

А разбивать логику на классы необходимо. Для того, чтобы при добавлении нового виджета не надо было менять уже существующие классы. Таким образом получается система плагинов. - Можно подставить любой класс с этими методами.



Название: Re: connect и динамический отправитель
Отправлено: Rosster от Апрель 30, 2013, 12:28
зачем городить классы-наследники, если к виджетам уже есть доступ.
можно же сразу все описать в 1 месте в зависимости от текущего индекса таба.
так же будет понятнее и по "затрам" дешевле.
или я не так понял задачу?
Верес правильно написал: меньше ошибок, больше мобильности.

Типа код:
Код:
// текущий виджет получаем.
QWidget * point = ui.tabWidget->currentWidget();
// и слот такой вот вызываем
QObject::invokeMethod(point, "add");
// строку меняем "add" на "del" или другое любое наименование слота.
Это куда вставлять? В MainWindow::slAdd() {}? А смысл? Там стоит более логичное manager->add(); которое работает на ура.


Название: Re: connect и динамический отправитель
Отправлено: Bepec от Апрель 30, 2013, 12:41
Эммм... У вас присутствует менеджер. А я предлагаю без менеджера как бы.

Напрямую. Без левых наследований и прочего. Т.е.

Код:
// запихиваем ваши классы в табвиджет
ui.tabWidget->addTab(вашиКлассы, "именаВашихКлассов");

//и слот нажатия кнопки у главного виджета
void on_pushButton_add_clicked()
{
    // текущий виджет получаем.
    QWidget * point = ui.tabWidget->currentWidget();
    // и слот такой вот вызываем
    QMetaObject::invokeMethod(point, "add");
    // строку меняем "add" на "del" или другое любое наименование слота.
}



Название: Re: connect и динамический отправитель
Отправлено: Rosster от Апрель 30, 2013, 12:53
Верес, в моем случае не подойдет, т.к. в мэнеджере еще много методов общих для всех виджетов,поэтому без него никак. Но ваш способ понадобится в более простых случаях, мне это пригодится.
Спасибо всем)


Название: Re: connect и динамический отправитель
Отправлено: Bepec от Апрель 30, 2013, 14:05
Кхм. Что мешает вызывать все ваши методы через способ, предложенный мной? :)

Этот метод позволяет вызвать любой слот у любого класса, наследника QWidget. С любыми параметрами.

PS недоумение.


Название: Re: connect и динамический отправитель
Отправлено: Rosster от Апрель 30, 2013, 14:59
Кхм. Что мешает вызывать все ваши методы через способ, предложенный мной? :)

Этот метод позволяет вызвать любой слот у любого класса, наследника QWidget. С любыми параметрами.

PS недоумение.

Верес, в моем случае не подойдет, т.к. в мэнеджере еще много методов общих для всех виджетов,поэтому без него никак.
Я же написал...Мне полюбому создавать родит. класс,т.к. много ОБЩИХ методов для всех виджетов, которые я тут не расписывал. Ну а вирт. функции позволяют не забыть в каком-нибудь виджете обязательный метод.


Название: Re: connect и динамический отправитель
Отправлено: Bepec от Апрель 30, 2013, 15:02
Кхм... кхм.... кхм..... Ладно, не буду настаивать, делай как хочешь :D

Как грится - я дал тебе знание, теперь обрети силу :D