Skip to content

Creating Inline Widgets for Listbox

Jinhao edited this page Jul 28, 2018 · 10 revisions

An inline widget is a child of a specified widget. It is associated with the data which is represented in the other widget. The listbox inline widgets are associated with the column of certain item.

The following example demonstrates the use of listbox inline widgets to create 2 widgets that represent a column of a listbox item.

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("Delete");
		btn_.events().click([this]
		{
			//Delete the item when button is clicked
			auto & lsbox = dynamic_cast<listbox&>(indicator_->host());
			lsbox.erase(lsbox.at(pos_));
		});

		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 -= (sz.width < 50 ? 0 : 50); //Check for avoiding underflow.
		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_;
};

Use this inline_widget

int main()
{
	using namespace nana;

	form fm;
	listbox lsbox(fm, rectangle{ 10, 10, 300, 200 });

	//Create two columns
	lsbox.append_header("column 0");
	lsbox.append_header("column 1");

	//Then append items
	lsbox.at(0).append({ "Hello0", "World0" });
	lsbox.at(0).append({ "Hello1", "World1" });
	lsbox.at(0).append({ "Hello2", "World2" });
	lsbox.at(0).append({ "Hello3", "World3" });

	//Create a new category
	lsbox.append("Category 1");

	//Append items for category 1
	lsbox.at(1).append({ "Hello4", "World4" });
	lsbox.at(1).append({ "Hello5", "World5" });
	lsbox.at(1).append({ "Hello6", "World6" });
	lsbox.at(1).append({ "Hello7", "World7" });

	//Set the inline_widget, the first column of category 0, the second column of category 1
	lsbox.at(0).inline_factory(0, pat::make_factory<inline_widget>());
	lsbox.at(1).inline_factory(1, pat::make_factory<inline_widget>());

	fm.show();
	exec();
}

The Screenshot

Inline Widget Screenshot

whether_to_draw() method determines whether the listbox have to draw the text of the sub item. Another case of use of whether_to_draw is to make UI good look.

Whether to draw

The listbox internally manages these inline_widget objects, and each inline_widget object is temporarily bound to an item. The listbox creates at most the number of inline_widget objects for the number of displaying items. If a listbox has 1000 items but only displays 10 items on the screen due to its height, the listbox only creates 10 instances of the inline_widgets. When the listbox's scrollbar scrolls, these 10 inline_widgets objects will be rebound to other items by calling the inline_notifier_interface::activate() method. For these reason, the inline_widget should not keep the data associated with certain listbox item.