Russian Qt Forum

Программирование => Общий => Тема начата: Fregloin от Ноябрь 21, 2012, 18:56



Название: Как передавать событие QEvent через несколько объектов?
Отправлено: Fregloin от Ноябрь 21, 2012, 18:56
Привет всем. Возникла такая задачка:

Есть объект Controller, он может генерить некоторое событие.
Есть много объектов CustomAction, которые должны принимать это событие.
Есть обект Proxy, через которые Controller связан с CustomAction посредством агрегации. Т.е. Controller и CustomActionы хранят в себе указатель на Proxy.
Вот что представляет собой мое событие
Код:
class RAILCORESHARED_EXPORT CAbstractEvent : public QEvent
{
    QString         fsenderTag;        //кто отправляет (записывает отправитель свой tag)
    QStringList     fdestinationTag;   //кто должен получить (tag приемника, можеть быть больше одного)
    QVariantHash    fvarData;          //где хранятся параметры

public:

    static const int customType = 1003;
    CAbstractEvent(QEvent::Type InitType);

    const   QString         &   senderTag()     const;
    const   QStringList     &   destiantionTag()const;
    const   QVariantHash    &   varData()       const;

    void    setSenderTag(const QString & Value);
    void    setDestinationTag(const QStringList & Value);
    void    setVarData(const QVariantHash & Value);
};

Что нужно: при создании события указать в destination текстовую строку(или строки)-описатель, по которому можно найти нужный CSceneAction.

Как сейчас сделано:
Код:
bool CMVCEventProxy::event(QEvent *e)
{
    qDebug("type %d",e->type());
    if(e->type() == static_cast<QEvent::Type>(CAbstractEvent::customType))
    {
        CAbstractEvent  *   abstractEvent = static_cast<CAbstractEvent*>(e);
        {
            CAbstractSceneAction    *   targetSceneAction;
            CGenericRailController  *   targetRailController;

            foreach(const QString & currentDestinationTag, abstractEvent->destiantionTag())
            {
                //сначала смотрим по action в качестве адресата
                if((targetSceneAction = factionsHash.value(currentDestinationTag,NULL)))
                {
                    QApplication::postEvent(targetSceneAction,abstractEvent); //отправляем событие дальше на сцену
                    continue;
                }

                if((targetRailController = fcontrollersHash.value(currentDestinationTag,NULL)))
                {
                    QApplication::postEvent(targetRailController,abstractEvent); //отправляем контроллеру
                    continue;
                }
            }
        }
        return  false;
    }
    else
        return  QObject::event(e);
}

Т.е. я ловлю событие, и перенапрвляю его уже в нужный CSceneAction.
Как я и догадывался, такая фишка не прокатывает - сегментация.
Подскажите, как правильно такое сделать?


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Fregloin от Ноябрь 21, 2012, 18:58
Как генерится событие в контроллере
Код:
 ...
if(manevrWasChanged && !lightObject->manevrDestination().isEmpty())
            {
                CAbstractEvent  *   setupManevrRouteEvent = new CAbstractEvent(static_cast<QEvent::Type>(CAbstractEvent::customType));
                setupManevrRouteEvent->setSenderTag(tag());
                destinations<<"com.action.dsp.manevr_route"; //это кто должен словить это соыбтие
                destinations<<"com.action.dsp.manevr_route_auto";
                QVariantHash    params;
                params["begin_light"] = lightObject->objectId();
                params["end_light"] = lightObject->manevrDestination();
                setupManevrRouteEvent->setVarData(params);
                setupManevrRouteEvent->setDestinationTag(destinations);
                qDebug("postEvent");
                QApplication::postEvent(eventProxy(),setupManevrRouteEvent);
            }
            else ...


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Igors от Ноябрь 21, 2012, 19:33
Ну шансы на "конкретный" ответ здесь невелики - слишком много специфики проекта, которую никто кроме Вас не знает. Поэтому "взагалi"

- вызывающе выглядят static_cast как впрочем и выборки из хеша

- не смущает ли Вас что событийный механизм используется для целей далеких от UI? Конечно это не запрещено, но по-взрослому надо строить граф зависимостей, проверять его на зацикливание, а потом процессировать.

 


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Bepec от Ноябрь 21, 2012, 19:58
Я бы сказал проще - у вас сложный довольно проект для понимания.

Вы сыпете кодом, в котором всё понятно только (ну и тем, кто работает с вами) разработчикам сего чуда :)

Локализуйте проблему. Сделайте лёгкий пример без лишней мутотени с заглушками на ожидаемом результате. Выложите сие творение. (именно в такой последовательности)

Я вот лично не поручусь, что сегментация идёт в Qt, а не в ваших чудодействиях.


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Igors от Ноябрь 21, 2012, 20:19
Локализуйте проблему. Сделайте лёгкий пример без лишней мутотени с заглушками на ожидаемом результате. Выложите сие творение. (именно в такой последовательности)
Fregloin, не поймали ли Вы себя на мысли что это нереально? Впрочем как и для многих моих проектов :) Не все вещи должны быть просты и очевидны, это нормально. Но все же когда нельзя изложить примитивно - это плохо, тревожный "симптом". Мне кажется Вы увлеклись "логикой интерпретирования", ну максимальная гибкость и все такое. Как человек в свое время работавший на AutoLisp соглашусь - это красиво. Но в то же время мой личный опыт показал - только неск процентов из заложенных возможностей будут использованы.А вот отсутствие жесткой типизации колет довольно больно  :)


Название: Re: Как передавать событие QEvent через несколько &
Отправлено: _OLEGator_ от Ноябрь 21, 2012, 20:50
Помоему, если я не ошибаюсь, надо создавать новое событие, а точнее для каждого объекта свое событие. Так действует стандартный механизм Qt - он удаляет объект QEvent.


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Bepec от Ноябрь 21, 2012, 21:14
to Igors - поправьте своё сообщение и смените объект претензии на меня :)

Нет, не поймал себя на мысли. Насколько я вижу, тут используются самописные классы в размере 2 штук, которые хзчто делают и являются чёрными ящиками.

Проблема же в передаче сообщения дальше. Убираем чёрные ящики, убираем разделение, получаем простейший пример.



Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Fregloin от Ноябрь 22, 2012, 12:28
Перефразирую первый пост по другому:

Есть несколько классов объектов, которые должны посылать друг другу сообщения.
Объекты этих классов напрмямую не связаны между собой. При том, при смене режима работы, одни объекты могут выгружаться, другие загружаться посредством dll.
У каждого из этих объектов есть свой tag - просто строка определенного формата. По сути что нужно: "подписать" по тегам эти объекты в некоторый общий диспетчер своих событий.
В определенный момент времени (смена состояния например) один из объектов говорит, что нужно по такому то предполагаемому тегу отправить сообщение. Сообщение шлётся в диспетчер.
Диспетчер смотрит, если у него объект с таким тегом, и если есть, перенаправялет это событие адресату (адресат и отправитель могут быть как одного класса, так и разных, не связанных между собой). Надеюсь доходчиво написал.

Что то подобное есть в iOS, когда можно слать нотификации объектам с определенным тегом.


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Bepec от Ноябрь 22, 2012, 13:05
Вы описали простейший диспетчер. Который в качестве идентификаторы использует tag.

Помоему гораздо проще писать функции с процедурой обработки своих евентов (1 функция с обработкой), чем издеваться над QEvent.  Или у вас есть какие то аргументы за и против?


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Fregloin от Ноябрь 22, 2012, 13:49
Я уже понял что QEvent здесь не особо применим, поэтому решил написать свой диспетчер, чем и занялся.


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Fregloin от Ноябрь 22, 2012, 14:12
Вот что набросал:

Интерфейс моего кастомного события

Код:
#include    <QString>

class   CAbstractEvent;
class   CMVCEventProxy;

class CAbstractEventInterface
{
    CMVCEventProxy      *   feventProxy;

protected:

    virtual void    attachEventProxy();                             //обработчик привяки диспетчера событий
    virtual void    detachEventProxy();                             //обработчик отвязки
    virtual void    receiveMVCEvent(CAbstractEvent * event);        //обработчки приёма события
    void    sendEvent(CAbstractEvent * event);                      //отправка события
    virtual    void    acceptEvent(CAbstractEvent * event);         //принять событие

    friend  class   CMVCEventProxy;

public:

    CAbstractEventInterface();
    virtual const   QString    &    tag()   const = 0;  //тег объекта (может выступать адресом применика, и адресом отправителя)

    void    setEventProxy(CMVCEventProxy    *   EventProxyObject);  //установка проки
    const   CMVCEventProxy  *   eventProxyObject()  const;          //получение прокси
};
реализация
Код:
#include "cabstracteventinterface.h"
#include "cabstractevent.h"
#include "cmvceventproxy.h"

CAbstractEventInterface::CAbstractEventInterface()
{
    feventProxy = NULL;
}


void CAbstractEventInterface::setEventProxy(CMVCEventProxy *EventProxyObject)
{
    if(feventProxy!=EventProxyObject)
    {
        if(feventProxy)
            detachEventProxy();
        feventProxy = EventProxyObject;
        if(feventProxy)
            attachEventProxy();
    }
}

const CMVCEventProxy *CAbstractEventInterface::eventProxyObject() const
{
    return  feventProxy;
}

void CAbstractEventInterface::sendEvent(CAbstractEvent *event)
{
    if(feventProxy)
    {
        feventProxy->sendEvent(event);
    }
}

void CAbstractEventInterface::acceptEvent(CAbstractEvent *event)
{
    Q_UNUSED(event)
}

void CAbstractEventInterface::attachEventProxy()
{
}

void CAbstractEventInterface::detachEventProxy()
{
}

void CAbstractEventInterface::receiveMVCEvent(CAbstractEvent *event)
{
    Q_UNUSED(event)
}

вот сам прокси
Код:
#include <QObject>
#include <QHash>
#include "cabstracteventinterface.h"

class   CAbstractEvent;
class   CAbstractArmLogic;

class CMVCEventProxy : public QObject
{
Q_OBJECT

    QHash<QString, CAbstractEventInterface*>    fsubscribedObjects; //подписчики на события

protected:

public:

    explicit CMVCEventProxy(QObject *parent = 0);
    ~CMVCEventProxy();

    bool            sendEvent(CAbstractEvent * event);  //отправка события

    void    subscribeObjectForEventReceiving(const QString & address, CAbstractEventInterface    *   InterfaceObject);  //подписывает объект на приём событий
    void    unsubscribeObjectFromEventReceiving(const QString & address);   //отписывает объект по адерсу
    void    unsubscribeObjectFromEventReceiving(CAbstractEventInterface    *   InterfaceObject);    //отписывает объект по интерфейсу

};

Код:
#include "cmvceventproxy.h"
#include "cabstractevent.h"

CMVCEventProxy::CMVCEventProxy(QObject *parent) :
    QObject(parent)
{

}

CMVCEventProxy::~CMVCEventProxy()
{

}

bool CMVCEventProxy::sendEvent(CAbstractEvent *event)
{
    if(!event || event->destiantionTag().isEmpty()) return  false;

    qDebug("CMVCEventProxy::sendEvent");

    CAbstractEventInterface *   interface;
    foreach(const QString & destination, event->destiantionTag())
    {
        if((interface = fsubscribedObjects.value(destination,NULL)))
        {
            interface->acceptEvent(event);
        }
    }
    delete  event;
    return  true;
}

void CMVCEventProxy::subscribeObjectForEventReceiving(const QString &address, CAbstractEventInterface *InterfaceObject)
{
    if(address.isEmpty() || !InterfaceObject)   return;

    qDebug("subscribeObjectForEventReceiving %s",qPrintable(address));

    CAbstractEventInterface *   oldInterface = fsubscribedObjects.value(address,NULL);
    if(oldInterface)
        oldInterface->setEventProxy(NULL);

    InterfaceObject->setEventProxy(this);
    fsubscribedObjects[address] = InterfaceObject;
}

void CMVCEventProxy::unsubscribeObjectFromEventReceiving(const QString &address)
{
    qDebug("unsubscribeObjectFromEventReceiving %s",qPrintable(address));

    CAbstractEventInterface *   oldInterface = fsubscribedObjects.value(address,NULL);
    if(oldInterface)
        oldInterface->setEventProxy(NULL);
    fsubscribedObjects.remove(address);
}

void CMVCEventProxy::unsubscribeObjectFromEventReceiving(CAbstractEventInterface *InterfaceObject)
{
    for(QHash<QString, CAbstractEventInterface*>::iterator it = fsubscribedObjects.begin(); it!= fsubscribedObjects.end(); it++)
    {
        if(it.value() == InterfaceObject)
        {
            unsubscribeObjectFromEventReceiving(it.key());
            break;
        }
    }
}

вот как шлю событие
Код:
if(manevrWasChanged && !lightObject->manevrDestination().isEmpty())
            {
                CAbstractEvent  *   setupManevrRouteEvent = new CAbstractEvent("com.event.route.setup.manevr");
                setupManevrRouteEvent->setSenderTag(tag());
                destinations<<"com.action.dsp.manevr_route";
                destinations<<"com.action.dsp.manevr_route_auto";
                QVariantHash    params;
                params["begin_light"] = lightObject->objectId();
                params["end_light"] = lightObject->manevrDestination();
                setupManevrRouteEvent->setVarData(params);
                setupManevrRouteEvent->setDestinationTag(destinations);
                sendEvent(setupManevrRouteEvent);
            }

и принимаю в нужном месте
Код:
void CSetupRouteAction::acceptEvent(CAbstractEvent *event)
{
//на самом деле тут данные из соыбтия обрабатываются, но для простоты просто подвтерждаем что сделано
    doneAction();
}

Это набросал по быстрому, но думаю что есть что усовершенствовать. Моежт будут какие то советы?


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Fregloin от Ноябрь 22, 2012, 14:16
Такой механизм работает, но во первых, только в одном потоке, и есть смущения по поводу удаления event.
Может его обернуть в QSharedPointer? что бы был подсчёт ссылок, ну и сделать передачу событий асинхронной (пока получается блокирование на acceptEvent)...


Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Bepec от Ноябрь 22, 2012, 14:24
Я бы на вашем месте не городил кучу кода и прочего. (я специфики конечно вашей незнаю)

Сделал бы структурку одну штуку. Сделал бы диспетчер с функциями (подписаться, отписаться, переслать событие).

Эм... ну и всё в принципе.

PS вы выкладываете много кода. Это конечно хорошо. Но вы его обрезаете, названия файлов не даёте, комментариев по коду не видно... Лучше не выкладывайте) Редкий форумчанин будет разбираться в каше :)



Название: Re: Как передавать событие QEvent через несколько объектов?
Отправлено: Fregloin от Ноябрь 23, 2012, 10:42
Да пожалуй Вы правы. Писалось на эмоциях ).