C++ Nana How can I access a button on index 0 that is inside a listbox, from a global listbox* pointer?

625 views Asked by At

I have a listbox, where on the first column it contains a text field with a button.

I have this global pointer to a nana listBox:

listbox*                listBox = nullptr;

I have this class retrieved from here:

#include <nana/gui.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/widgets/button.hpp>
#include <nana/gui/widgets/listbox.hpp>
#include <nana/gui/widgets/textbox.hpp>

#include <nana/gui/wvl.hpp> 
#include <nana/system/platform.hpp>

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <thread>
#include <Windows.h>

using namespace nana;

//Creates a textbox and button
//textbox shows the value of the sub item
//button is used to delete the item.
class inline_widget : public listbox::inline_notifier_interface
{
private:
    //Creates inline widget
    //listbox calls this method to create the widget
    //The position and size of widget can be ignored in this process
    virtual void create(window wd) override
    {
        //Create listbox
        txt_.create(wd);
        txt_.events().click([this]
        {
            //Select the item when clicks the textbox
            indicator_->selected(pos_);
        });

        txt_.events().mouse_move([this]
        {
            //Highlight the item when hovers the textbox
            indicator_->hovered(pos_);
        });

        txt_.events().key_char([this](const arg_keyboard& arg)
        {
            if (arg.key == keyboard::enter)
            {
                //Modify the item when enter is pressed
                arg.ignore = true;
                indicator_->modify(pos_, txt_.caption());
            }
        });
        //Or modify the item when typing
        txt_.events().text_changed([this]()
        {
            indicator_->modify(pos_, txt_.caption());
        });
        //Create button
        btn_.create(wd);
        btn_.caption("Start");
        btn_.enabled(false);
        btn_.events().click([this]
        {
            //Delete the item when button is clicked
            auto & lsbox = dynamic_cast<listbox&>(indicator_->host());
            //buttonPressed(btn_, pos_.item);
        });

        btn_.events().mouse_move([this]
        {
            //Highlight the item when hovers the button
            indicator_->hovered(pos_);
        });
    }

    //Activates the inline widget, bound to a certain item of the listbox
    //The inline_indicator is an object to operate the listbox item,
    //pos is an index object refers to the item of listbox
    virtual void activate(inline_indicator& ind, index_type pos)
    {
        indicator_ = &ind;
        pos_ = pos;
    }

    void notify_status(status_type status, bool status_on) override
    {
        //Sets focus for the textbox when the item is selected
        if ((status_type::selecting == status) && status_on)
            txt_.focus();
    }

    //Sets the inline widget size
    //dimension represents the max size can be set
    //The coordinate of inline widget is a logical coordinate to the sub item of listbox
    void resize(const size& dimension) override
    {
        auto sz = dimension;
        sz.width -= 50;
        txt_.size(sz);

        rectangle r(sz.width + 5, 0, 45, sz.height);
        btn_.move(r);
    }

    //Sets the value of inline widget with the value of the sub item
    virtual void set(const value_type& value)
    {
        //Avoid emitting text_changed to set caption again, otherwise it
        //causes infinite recursion.
        if (txt_.caption() != value)
            txt_.caption(value);
    }

    //Determines whether to draw the value of sub item
    //e.g, when the inline widgets covers the whole background of the sub item,
    //it should return false to avoid listbox useless drawing
    bool whether_to_draw() const override
    {
        return false;
    }

private:
    inline_indicator * indicator_{ nullptr };
    index_type pos_;
    textbox txt_;
    button btn_;
};

This is my thread:

void theThread()
{
    while (1)
    {
        //How can I access a button on index 0 from my listbox?
        button* mybutton = listBox.at(0). ... ? -->HOW CAN I DO THIS?
        mybutton->caption("I did it!!");
        Sleep(1000);
    }
}

And my main() function:

void main()
{
    using namespace nana;
    form fm(nana::API::make_center(750, 500), appear::decorate<appear::taskbar>());


    listbox lsbox(fm, rectangle{ 10, 50, 700, 200 });

    listBox = &lsbox;

    //Create columns
    lsbox.append_header("Col1");
    lsbox.append_header("Col2");
    lsbox.append_header("Col3");
    lsbox.append_header("Col4");
    lsbox.append_header("Col5");

    lsbox.at(0).append({ "text1", "text2", "text3", "text4", "text5" });
    lsbox.at(0).append({ "text1", "text2", "text3", "text4", "text5" });
    lsbox.at(0).append({ "text1", "text2", "text3", "text4", "text5" });

    lsbox.column_at(0).width(200);

    lsbox.at(0).inline_factory(0, pat::make_factory<inline_widget>());

    fm.show();


    std::thread myThread(theThread);

    exec();

    myThread.join();

}

Question:

How can I access a button on index 0 that is inside a listbox, from my global listbox* pointer?

2

There are 2 answers

0
qPCR4vir On BEST ANSWER

You are trying to make a complicated use of the most complicated feature of nana::listbox (or in the Wiki), the most complicated widget in Nana.

As I understand it, you do not directly "attach" an inline widget to each item in the list. Instead, the list itself generates a limited number of widgets and in each moment "inlines" one of those widgets to each and only to each of the visible items. In order to do that the list need to know how to create the widgets and how to "update" it for each item, as well as how the widget responds to the different possibles events, and how to update back the corresponding item.

This is achieved by attaching to one column of one of the categories of the list a "factory" of objects that implement the virtual interface listbox::inline_notifier_interface. This is where you use the function of the listbox category:

void nana::listbox::cat_proxy::inline_factory (size_type column,                    
               pat::cloneable<pat::abstract_factory<inline_notifier_interface>> factory);

This explain why there is not a direct way to access the inline widgets. Maybe it is possible to implement a new interface function in the listbox for that - we will try. I have seem there is a private function inline_pane * _m_get_inline_pane(const category_t& cat, std::size_t column_pos) const that return a inline_pane that in turn contains a std::unique_ptr<inline_notifier_interface> inline_ptr; that one can test if empty and which one can use to refer your button. Until now, the expected way to affect the inlined widget is to change the conten of the corresponding items and to use inline_notifier_interface to reflect that change.

As for your workaround of saving a reference to the button at the moment of creation into a global vector, you may prefer to have a global vector of inline_widget because it includes an index_type pos_; that you could use to search that vector for an element that is currently linked to the item of interest. Of course, you will have a hard time to ensure that the vector points to live objects.

You may need to note, that the way to access an item_proxy is by using lsbox.at(0).at(item_number), or lsbox.at(index_pair(0,item_number)).

0
waas1919 On

I know it's not the best practice, but here it goes:

Created a vector with pointers to buttons.

std::vector<button*>    mButtons;

On the creation of the button, I push_back() the reference to the vector:

    //Create button
    btn_.create(wd);
    btn_.caption("Start");
    btn_.enabled(false);
    btn_.events().click([this]
    {
        //Delete the item when button is clicked
        auto & lsbox = dynamic_cast<listbox&>(indicator_->host());
        //lsbox.erase(lsbox.at(pos_));
        buttonPressed(btn_, pos_.item);
    });

    btn_.events().mouse_move([this]
    {
        //Highlight the item when hovers the button
        indicator_->hovered(pos_);
    });

    mButtons.push_back(&btn_); // <--------- here

And now I can control the buttons from the outside.

button* btn = mButtons[index];
if (nullptr != btn)
{
    ...
}