Project

General

Profile

How to use Signals? // How to communicate between Objects?

Added by Quirin Schwanse over 5 years ago

Hello, still exploring Wt here as a C newbie.

The Question is: How do I communicate from a Widget as a Member to the Widget which Contains that Member?

For Example:

I have a Navigation bar with X Buttons.

The Navigation bar is part of my Template "TLayout".

Now I want to modify Content of the TLayout by pressing buttons, for example:

There is a Section "NEWS", and a Section "DEV". Depending on the Button which is clicked by the user,

the content of the TLayout shall change.

I've put the files as an attachment:


Replies (7)

RE: How to use Signals? // How to communicate between Objects? - Added by lm at over 5 years ago

I can think of several things to say about your code. Some C, some Wt. Let's start with C.

TLayout has a member int * Section;. Make that just int. When you initialize the Section member, do it more like this:

TLayout::TLayout() 
    : WTemplate { tr("tpl.LAYOUT") }
    , Section{nullptr}
{ /* Constructor Body */ }

By the way, the way you have it now, it looks like TLayout::switchSection(int) will be dereferencing a null pointer when you do *Section = aSec; (Section is still null at that point).

In your WNavigation constructor, it looks like your std::bind s are incorrect. You need to pass an instance of type TLayout, but it looks like you don't have one.

By the way, also in WNavigator constructor b_NEWS = addWidget(make_unique<WPushButton>(tr("str.NEWS"))); can be bNEWS = addNew<WPushButton>(tr("STR.NEWS")); which is identical but easier to type and read.

Okay, about communicating properly between WNavigation and TLayout...I see that you're passing an instance of TLayout to WNavigation in the TLayout constructor (mNavigation = bindWidget("NAVIGATION", make_unique<WNavigation>(this));). That looks great, but there doesn't appear to be such a constructor in WNavigation, so this code can't compile at all. This can work: having WNavigation have a pointer to TLayout and calling TLayout::switchSection, but it is annoying to have two classes that mutually know about each other. Another way is directly apropos to your question: "How to use Signals?". To use Wt::Signal, try something like this:

class WNavigation
{
public:
    Wt::Signal<int> & sectionSelected();
private:
    Wt::Signal<int> _sectionSelected;

    void selectSection(int);
};

Wt::Signal<int> & WNavigation::sectionSelected()
{
    return _sectionSelected;
}

void WNavigation::selectSection(int selection)
{
    _sectionSelected(selection);
}

TLayout::TLayout()
    : /* initialization... */
{
    mNavigation = bindWidget("NAVIGATION", make_unique<WNavigation>(this));
    mNavigation->selectionSelected().connect(std::bind(&TLayout::switchSelection, this, std::placeholder::_1));
}

I didn't try compiling this, so I'm not sure if the syntax is perfect. I won't bother explaining this...hopefully it explains itself, but feel free to ask if anything here seems mysterious. Good luck!

RE: How to use Signals? // How to communicate between Objects? - Added by Quirin Schwanse over 5 years ago

Well, the missing object parameter in the WNavigation Constructor was a result of experimenting with the arguments of the functions and the bidirectional association between TLayout-WNavigation Objects. I had to figure out the stuff about pre-processor definitions to achieve this. So you're right, the shown code wouldn't be compilable. Anyways and again you helped me a lot, you answered my question without any mysteries left. But since this is like my 4th post about small problems I wanted to ask if there is any way to contact somebody about small questions? Cause I don't want to fill the Forum with more little fishes :D

RE: How to use Signals? // How to communicate between Objects? - Added by lm at over 5 years ago

By the way, I am in no way affiliated with emweb or wt except as an enthusiastic user of the latter.

IRC is a great place to get little questions answered. Freenode #wt is slow so you would have to wait for a response. If you have a simple question about c, ##c-basic, or if you want to try to ask a wt question to a "general" c crowd, ##c-general.

Of course, stack overflow is a great place to have anything answered, but you'll want to pose the question very well with a (working ;-) ) minimum example, etc.

In my opinion (holding in mind the caveat of the first line of this response), this is a fine place to ask such questions. It also leaves archived, searchable breadcrumbs for the next guy.

RE: How to use Signals? // How to communicate between Objects? - Added by Quirin Schwanse over 5 years ago

Okay, thanks a lot for the suggestions!

I implemented your solution into the Project, but Im still struggling on how to pass arguments with a Button-click.

Right now the Files look like this:

TLayout.h:

class WNavigation;

class TLayout : public WTemplate
{

public:
    TLayout();

    //To-Learn: How to pass this correctly to WNavigation?
    enum SECTION {NEWS, DEV, PROJ, MEDIA, SHOP, CONTACT};
    //void setSection(SECTION aSec);

    void setSection(int aSec);
private:

    WPageHead* mPageHead;
    WNavigation* mNavigation;
};

TLayout.cpp:

TLayout::TLayout() : WTemplate { tr("tpl.LAYOUT") }
{

    //Binding Widgets to Page:
    mPageHead = bindWidget("PAGEHEAD", make_unique<WPageHead>());
    mNavigation = bindWidget("NAVIGATION", make_unique<WNavigation>(this));

    mNavigation->sectionSelected().connect(std::bind(&TLayout::setSection, this, std::placeholders::_1));

}

void TLayout::setSection(int aSec) {
    std::cout << "Button was clicked:" << aSec << std::endl;
}

WNavigation.h:

class WNavigation : public WContainerWidget
{

public:
    WNavigation(TLayout* aLayout);
    ~WNavigation();

    Wt::Signal<int>& sectionSelected();
    void selectSection(int aSec);

private:
    TLayout* theLayout;

    Wt::Signal<int> _selectedSection;

    WPushButton* b_NEWS;
    WPushButton* b_DEV;
    WPushButton* b_PROJ;
    WPushButton* b_MEDIA;
    WPushButton* b_SHOP;
    WPushButton* b_CONTACT;
};

WNavigation.cpp:

WNavigation::WNavigation(TLayout* aLayout)
{
    theLayout = aLayout;

    //Init Buttons
    b_NEWS = addWidget(make_unique<WPushButton>(tr("str.NEWS")));

    b_DEV = addWidget(make_unique<WPushButton>(tr("str.DEV")));

    b_PROJ = addWidget(make_unique<WPushButton>(tr("str.PROJ")));

    b_MEDIA = addWidget(make_unique<WPushButton>(tr("str.MEDIA")));

    b_SHOP = addWidget(make_unique<WPushButton>(tr("str.SHOP")));

    b_CONTACT = addWidget(make_unique<WPushButton>(tr("str.CONTACT")));

    //How do I pass a parameter? <<HERE?
    b_NEWS->clicked().connect(this, &WNavigation::selectSection(0));
}


void WNavigation::selectSection(int aSec){ 
    _selectedSection(aSec);
}

Wt::Signal<int>& WNavigation::sectionSelected(){ 
    return _selectedSection; 
}

I highlighted the code I'm struggling with with a comment :>

RE: How to use Signals? // How to communicate between Objects? - Added by lm at over 5 years ago

This doesn't compile:

b_NEWS->clicked().connect(this, &WNavigation::selectSection(0));

& means "address of" and WNavigation::selectSection(0) calls the function, WNavigation::selectSection passing 0 as a parameter. You're passing the address of the return value of WNavigation::selectSection(0) to the clicked handler on your button. Of course this is nonsensical; you want to pass the address of the function, not the address of the return value (which is void?).

There are four overloads to connect: https://www.webtoolkit.eu/wt/doc/reference/html/classWt_1_1EventSignal.html. The first one registers a function that takes no arguments and returns void. The second one passes no parameters to the handler function (WNavigation::selectSection in this case), but we need to pass a special value so it won't do. The third and fourth pass an event object which won't have the enum you're wanting to pass. You'll use the first. You need to create a function that takes no parameters and returns void and as a side effect, does what you want it to do. Since you already have WNavigation::selectSection, let's create a new function that calls selectSection passing it the correct value:

b_NEWS->clicked().connect(std::bind(&WNavigation::selectSection, this, NEWS));

(NEWS is defined in TLayout.h, so you'll need to include that file or something.) std::bind here creates a function that takes no parameters and returns void. (std::bind can do more, but this is what we want now.) The created function will call WNavigation::selectSection passing this and NEWS. Since WNavigation::selectSection is a pointer to a member function, the first parameter is the object on which the function is invoked (this in this case).

Let me know if you want to see the lambda (anonymous function) version of this invocation.

RE: How to use Signals? // How to communicate between Objects? - Added by Quirin Schwanse over 5 years ago

Well, with some research I found a similar solution using the bindSafe-function with a lambda:

b_NEWS->clicked().connect(bindSafe([this] { selectSection(SECTION::NEWS); }));

my problem was that the enumeration was declared as a member of TLayout. When I declared it outside of the class-definition

in TLayout.h and forward-declared the enumeration in WNavigation.h, it worked. Thank you again for your help! I really want

to learn hard to become a good C-Developer, and this was some helpful new stuff right now. By the way, since u are a professional

developer u could probably make decent income on a udemy-course about Wt, I would buy yours! ;>

RE: How to use Signals? // How to communicate between Objects? - Added by lm at over 5 years ago

Yup, that looks perfect. Glad you figured out the declaration problem. Always happy to help answer a well-formed question, and yours are asked well.

    (1-7/7)