📜 ⬆️ ⬇️

Qt widget for adding and deleting rows in a QTableView table


In this article, I would like to share my experience in developing a single widget (graphical user interface element), highlighting some Qt technologies and techniques.
It is often necessary to give the user the ability to insert rows and columns into the table or remove them from it. As a rule, this is implemented as follows: select the line by clicking on the header and select the item in the menu: select> menu> insert | delete. This is not entirely obvious and intuitive, as is the fact that the line is inserted before the current and not, for example, after it. Therefore, I wrote a widget that removes this problem.
The widget looks like four buttons that follow the cursor on the table border (haha, it's just like those bees that ran after the cursor on sites in the web 1.0 era!). It would be possible to overload QTableView, but then you have to change all instances; instead, I wrote a separate widget which is like a socket attached to an existing QTableView.
The project consists of four classes, the main ones: the InsertRemove :: Button button , the InsertRemove :: Panel panel and two auxiliary ones to demonstrate the possibilities: a container for storing a data matrix based on the InsertRemove :: Matrix vector of vectors and a model - the interface of this InsertRemove :: Model container .
The button calculates the position on the widget that is adequate to the current coordinate and current policy value ( InsertRemove :: Policy ), calculating it from the column width and row height. One coordinate is the sum of these dimensions, the other is the nearest value of the border or the middle of the line.
int coord1; int coord2 = 0; int sizes[m]; if (_orientation == Qt::Horizontal) { for (int i=0;i<m;i++) sizes[i] = table->columnWidth(i); for (int i=0;i<n;i++) coord2 += table->rowHeight(i); } else { for (int i=0;i<m;i++) sizes[i] = table->rowHeight(i); for (int i=0;i<n;i++) coord2 += table->columnWidth(i); } if (_type == InsertRemove::Insert) nearestBorder(_policy,point1+offset1,sizes,m,&_modelIndex,&coord1); else // _type == InsertRemove::Remove nearestMiddle(_policy,point1+offset1,sizes,m,&_modelIndex,&coord1); 

Policies are: insert, delete, insert only at the beginning, insert only at the end, because not all models can be allowed all these manipulations.
For the button, I applied the parent concept widely used in Qt, for objects, this means that deleting the parent removes all child memory objects, and for widgets, moreover, that the child widgets are displayed within the parent (if are not dialog boxes) and in its coordinate system.
There is nothing better to describe a style than css, and for data storage, it would be nice to use the Qt resource system, which allows embedding resources into a binary and accessing them as files, using ':' as the root directory.
  QString plus_css = "* {image: url(':/plus-icon.png'); border: 0;}" "*:hover {image: url(':/plus-icon-hover.png');}" "*:pressed {image: url(':/plus-icon-pressed.png');} "; QString minus_css = "* {image: url(':/minus-icon.png'); border: 0;}" "*:hover {image: url(':/minus-icon-hover.png');}" "*:pressed {image: url(':/minus-icon-pressed.png');} "; if (_type == Insert) setStyleSheet(plus_css); else setStyleSheet(minus_css); 

When the button is pressed, the actual insertion or deletion in the model is called.
 void Button::on_clicked() { QTableView* table = dynamic_cast<QTableView*>(this->parent()); if (!table) return; QAbstractItemModel* model = table->model(); if (!model) return; if (_type == InsertRemove::Insert) { if (_orientation == Qt::Horizontal) model->insertColumn(_modelIndex); else model->insertRow(_modelIndex); } else // _type == InsertRemove::Remove { if (_orientation == Qt::Horizontal) model->removeColumn(_modelIndex); else model->removeRow(_modelIndex); } } 

Now that everything is clear with the button, go to the panel. She is responsible for the creation and storage of these buttons and the transfer of coordinates and policies to them, as well as the adoption of buttons by a table.
 void Panel::attach(QTableView* table) { for (int i=0;i<4;i++) _buttons[i]->setParent(table); _table = table; table->setMouseTracking(true); table->viewport()->installEventFilter(this); connect(table->horizontalHeader(),SIGNAL(sectionResized(int,int,int)),this,SLOT(placeButtons())); connect(table->verticalHeader(),SIGNAL(sectionResized(int,int,int)),this,SLOT(placeButtons())); placeButtons(); } 

My favorite feature of Qt is the event filter (QEventFilter), it allows you to pray invading the inner life of the object, thereby violating the encapsulation. When I think about it, I mentally go back to the days of bare WinApi, krippi macros, structures and curses like lpcwstr, when the desktop programmer was harder. With this filter, the panel tracks the mouse movement in QTableView. The filter in my case is the panel itself. At first, I wanted to make the filter a separate class and put an event into a signal, but then I thought that there must be a good reason why events are events, and signals are signals and everything is not piled up (as done, if my memory serves me, in .Net). Firstly, the events are the inner life of the widget, and the signals are its interface, and secondly, it is obvious that events are faster due to the smaller number of layers (layers of interaction), which can be critical in our case, when the event will be triggered hundreds of times per second. Although I'm not sure at all 100 in these calculations. So, all we need to track the event is the mouse movement and transfer the coordinates to the buttons.
 bool Panel::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::MouseMove && object == _table->viewport()) { QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(event); if (!mouseEvent) return false; for (int i=0;i<4;i++) _buttons[i]->setPoint(mouseEvent->pos()); } return false; } 

The container and the demonstration model seem self-evident, so we go directly to the application: create a model, create a table, create a panel, attach a panel to the table.
  QTableView view; view.setModel(&model); Panel panel(EverythingAllowed,EverythingAllowed); panel.setPolicy(Qt::Horizontal, (PolicyFlags) RemoveAllowed | AppendAllowed ); panel.attach(&view); 

One of the disadvantages of this widget is that it is tied to QTableView (this is because it was for this purpose that I needed it), although it could be used for other types of views. If there is time and desire, I will decide this question.
Perhaps my work can be useful to you, it can be taken on a githaba or from my server. Everything you need is in the insertremove folder in the InsertRemove namespace, it is connected via the profile profile. For the library is not enough and damp for now. Fell free to use and contribute.
references:
 git clone git://github.com/overloop/insertremovepanel.git git clone git://mugiseyebrows.ru/insertremovepanel.git 

look at: github.com | mugiseyebrows.ru
download: github.com | mugiseyebrows.ru

')

Source: https://habr.com/ru/post/152933/


All Articles