Qt setColumnWidth does not work - c++

Have written the following code:
m_selectCategoryTableWidget = new QTableWidget;
m_selectCategoryTableWidget->setRowCount(0);
m_selectCategoryTableWidget->setColumnCount(2);
m_selectCategoryTableWidget->setHorizontalHeaderLabels(QStringList()<<tr("Category")<<tr("Number of items"));
m_selectCategoryTableWidget->verticalHeader()->setVisible(false);
m_selectCategoryTableWidget->horizontalHeader()->setStretchLastSection(true);
//m_selectCategoryTableWidget->setColumnWidth(0,400);
m_selectCategoryTableWidget->resizeColumnsToContents();
m_selectCategoryTableWidget->setColumnWidth(1,100); //this does not take effect
Please help.

Well, Qt's logic is so, that after column resize, scroll bar area checks how columns fit into it. And if the sum of all columns' widths is less than the widget's visible width, then the last column gets resized to fill up the space leading to no visible result of calling setColumnWidth(). Actually two resizes happen - to shrink and reverse to enlarge.
So, the lesson is - get control's visible width, recalculate sizes as you want, and resize all but the last column. For two column case it's really simple:
int secondColumnWidth = 100;
int firstColumnWidth = m_selectCategoryTableWidget->width() - secondColumnWidth;
if (firstColumnWidth > 0)
{
m_selectCategoryTableWidget->setColumnWidth(0, firstColumnWidth);
}
else
{
m_selectCategoryTableWidget->resizeColumnsToContents();
}
Good luck!

It is also possible to specify that you want the first column to fill the remaining space instead of the last column. Unfortunately this does seem to prevent the user from being able to manually resize the columns.
int secondColumnWidth = 100;
m_selectCategoryTableWidget->header()->setStretchLastSection(false);
m_selectCategoryTableWidget->header()->setResizeMode(0, QHeaderView::Stretch);
m_selectCategoryTableWidget->setColumnWidth(1, secondColumnWidth);

This will automatically resize the columns to fit ("view" is an QTableView* and model is a QSqlQueryModel*).
static_cast<QTableView*>(view)->horizontalHeader()
->resizeSections(QHeaderView::ResizeToContents);
QFontMetrics fm(view->font());
for (int i = 0 ; i < model->record().count(); ++i)
{
int maxLength = 0;
for (int j = 0; j < model->rowCount(); ++j)
{
QString cell = model->record(j).value(i).toString();
if (fm.width(cell) > maxLength)
{
maxLength = fm.width(cell);
}
}
QHeaderView& hv = *static_cast<QTableView*>(view)->horizontalHeader();
if (maxLength > hv.sectionSize(i))
{
hv.resizeSection(i, maxLength * 1.5);
}
}

Related

Drag and Drop Item list not working properly on ImGUI

Im using ImGUI and I want to implement a layer menu for the images and to move them im using
Drag to reorder items in a vector.
Sometimes it works just fine but others the images just jumps from the current position to a random one.
for (int i = 0; i < this->Images->size(); i++) {
ImGui::Image((void*)(intptr_t)this->Images->at(i).texture, ImVec2(100 * temp_percentage, 100 * temp_percentage));
ImGui::SameLine();
ImGui::Selectable(this->Images->at(i).name.c_str());
if (ImGui::IsItemActive() && !ImGui::IsItemHovered())
{
int n_next = i + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1);
if (n_next >= 0 && n_next < this->Images->size())
{
std::swap(this->Images->at(i), this->Images->at(n_next));
*this->CurrentImage = this->Images->front();
centerImage();
ImGui::ResetMouseDragDelta();
}
}
ImGui::Separator();
}
The problem lies at !ImGui::IsItemHovered(), there is small spacing between the lines (cell, selectable,... ), so when the mouse hovers over that spacing, the item isn't hovered but still is actived, and therefore will execute the swap and reset mouse delta multiple times making it goes to the top or bottom of the list. This will also happen if the mouse goes out of the table/window bounds.
To make the problem more visible, you can make the spacing bigger using ImGui::GetStyle().ItemSpacing.y = 50.f;.
To actually fix the problem, you'll have to calculate the item index using the mouse position, here is a way to do it, tho not perfect but it works.
ImGuiStyle& style = ImGui::GetStyle();
ImVec2 windowPosition = ImGui::GetWindowPos();
ImVec2 cursorPosition = ImGui::GetCursorPos();
// this is not a pixel perfect position
// you can try to make it more accurate by adding some offset
ImVec2 itemPosition (
windowPosition.x + cursorPosition.x,
windowPosition.y + cursorPosition.y - style.ItemSpacing.y
);
for (int i = 0; i < this->Images->size(); i++) {
ImGui::Image((void*)(intptr_t)this->Images->at(i).texture, ImVec2(100 * temp_percentage, 100 * temp_percentage));
ImGui::SameLine();
ImGui::Selectable(this->Images->at(i).name.c_str());
if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0))
{
int n_next = floorf((ImGui::GetMousePos().y - itemPosition.y) / itemHeight);
if (n_next != i && n_next >= 0 && n_next < this->Images->size())
{
std::swap(this->Images->at(i), this->Images->at(n_next));
*this->CurrentImage = this->Images->front();
centerImage();
}
}
ImGui::Separator();
}
There is also another problem in your code, if there are multiple items with the same name, ImGui::IsItemActive() will return true for all of them if one is actived.
You can fix this easily by adding ##some_unique_string after the name, for example ImGui::Selectable("Image#image_1") will just display Image.

Stopping the VerticalHeader to update on QTableview

I am implementing a QTableview to display a huge amount of data. Instead of showing all at once, I am showing 10000 rows and dynamically adding and subtracting the data from the table. I am facing a problem when deleting the upper rows; the vertical header gets updated, and row numbers get changed. Any way to stop the vertical header from updating.
bool flag = 0;
int last_row = 1000;
int top_row = 0;
int margin = 30;
void DataTable::verticalScrollbarAction(int action) {
if(((this->rowAt(0)) - margin) < top_row && flag == 1)
{
this->getTableModel()->GetData(top_row-101, top_row-1, 0);
this->getTableModel()->removeRows(last_row -101, last_row -1);
last_row -= 100;
top_row -= 100;
}
else if ((this->rowAt(this->height()) + margin) > last_row)
{
flag = 1;
this->getTableModel()->GetData(last_row, last_row+100, 1);
this->getTableModel()->removeRows(0, 100);
last_row += 100;
top_row += 100;
}
}
The below answer is just my guess.
May be you can try a bit on it.
Get the vertical header object
QHeaderView *pHeaderView = yourTableView->verticalHeader();
Now go through the below documentation defaultSectionSize of QHeaderView
https://doc.qt.io/qt-6/qheaderview.html#defaultSectionSize-prop
By default, the value of this property is style dependent. Thus, when
the style changes, this property updates from it. Calling
setDefaultSectionSize() stops the updates, calling
resetDefaultSectionSize() will restore default behavior.
May be what you can do is first call
pHeaderView->setDefaultSectionSize(QHeaderView::Fixed);
After you are done with your business, you can call below to restore default behavior.
pHeaderView->resetDefaultSectionSize();

Impossible to delete the last column in a ListView?

I am having a bit of a trouble here. It seems I can't remove all the columns or (reset) a listview. Here is the relevant code:
HWND resultListView = GetDlgItem(hwnd, IDC_RESULTCONTROL);
SendMessage(resultListView, LVM_DELETEALLITEMS, 0, 0); //All items are deleted
//Get numebr of columns
HWND hWndHdr = (HWND)::SendMessage(resultListView, LVM_GETHEADER, 0, 0);
int count = (int)::SendMessage(hWndHdr, HDM_GETITEMCOUNT, 0, 0L);
for (int colIndex = 0; colIndex < count; colIndex++) {
ListView_DeleteColumn(resultListView, colIndex);
}
///... Irrelevant code
HWND listbox = GetDlgItem(hwnd, IDC_SELECTEDLISTBOX);
int numberOfItemsSelected = SendMessage(listbox, LB_GETLISTBOXINFO, 0, 0);
vector<string> selectedItemsStringsVector;
char buf[250];
LVCOLUMN buffer;
//Add Selected Columns
for (int i = 0; i < numberOfItemsSelected; i++) { //In this case always 2 "Date" and "Time" for testing.
SendMessage(listbox, LB_GETTEXT, i, (LPARAM)buf);
selectedItemsStringsVector.push_back(buf);
buffer.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;
buffer.fmt = LVCFMT_LEFT;
buffer.cx = 100;
buffer.pszText = buf;
buffer.cchTextMax = lstrlen(buf);
buffer.iSubItem = i;
buffer.iImage = 0;
buffer.iOrder = 0;
ListView_InsertColumn(resultListView, i, &buffer); //Works fine. Maybe I add an extra column here??
}
In debug mode, all my variables have the expected values.
FirstButtonClick column count in debug mode (variable count = 2) all good.
SecondButtonClick Column count = 3. But it does not delete them?
English is not my main language and I am kind of stumped right now...
The indices of columns in a header control are always consecutively numbered, starting at 0. User code does not control the index of a column. When deleting a column, the indices of columns towards higher indices are shifted down by 1. This is the reason, why the call to ListView_DeleteColumn eventually fails.
There are two options to solve this:
Delete columns starting at the final index down to 0. Deleting the last column does not change the index of other columns, so they stable throughout the deletion operation.
Always delete the first column (at index 0).
The second option is both easier to implement as well as read. The following loop will delete all columns:
for (int colIndex = 0; colIndex < count; ++colIndex) {
ListView_DeleteColumn(resultListView, 0);
}

How to animate window output from 2D array of x and y screen co-ordinates

I have a set of 2D array(IntMatrix to be exact) that possess values in certain index (if value is 1, it will print to screen).
The following is how I populated the 2D array when i press a button.
case IDC_SOLVE_BUTTON:
trysolve = 1;
solve2D.resize(inRowCount);
for (int i = 0; i < inRowCount; i++){
solve2D[i].resize(inColCount);
}
solve2D[0][3] = 1;
solve2D[1][3] = 1;
solve2D[1][4] = 1;
solve2D[1][5] = 1;
solve2D[2][5] = 1;
Now that I have values in some of the array indices, I know how to print them onto my window as shown below :
for (int rowId = 0; rowId < inRowCount; rowId++){
for (int colId = 0; colId < inColCount; colId++){
check = (colId*inColCount) + (rowId);
if (solve2D[rowId][colId] == 1){
solvecheck = (colId*inColCount) + (rowId);
startCol = colId * 10;
startRow = rowId * 10;
RECT cell = { startCol, startRow, startCol + dCol, startRow + dRow };
FillRect(hdc, &cell, tBrush);
}
}
Now important question is how do i exactly animate the printing ? Such that it will print one RECTANGLE, then print the subsequent RECTANGLE one second later. Any way i can set a timer and update the window at the printing segment of the code ??
Appreiciate some guidiance in this. Please help !
You can use the Sleep function to make the program wait for 1 second before continuing. As follows:
Sleep(1000);
Notice that the parameter is in milli-seconds, so you need 1000 to represent one second.
Place this function call in the suitable place of your code and you'll be done.

QTableView issue with resizeEvent()

I have an object that inherits QTableView and overrides the resizeEvent() method to set the widths of the table columns to a percantage of the available space if the table as a whole is resized.
Code is as follows:
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
// Call our base resizeEvent to handle the vertical resizing which
// we don't bother with here
QTableView::resizeEvent(ev);
}
This all works fine until the user manually resizes one of the columns and stretches it beyond the viewport (bringing up the horizontal scrollbar). This then triggers my resizeEvent() call which resets the column widths to the percentage defaults.
I can overcome this issue by connecting to the sectionResized() signal on the table header and setting a timer running. If the resizeEvent() is called whilst the timer is active then I don't recalculate the table widths.
Code below:
connect(horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(slotSectionResized(int, int, int)));
void DDUTableView::slotSectionResized(int /*logicalIndex*/, int /*oldSize*/, int /*newSize*/)
{
timer_->start(500);
}
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
if (timer_->isActive()) {
return;
}
// etc
This works but is messy. Anyway the simple question is can I prevent the resizeEvent() from being called if the user manually adjusts the column headers beyond the scope of the viewport? Alternatively, if not, is it possible to identify in the resizeEvent() whether this particular situation has occurred without having to set timers and the like?
Checking if the scrollbar is visible worked for my scenario.
void DDUTableView::resizeEvent(QResizeEvent* ev)
{
if (!horizontalScrollBar()->isVisible()) {
int num_columns = NUM_ELEMENTS(COLUMN_WIDTHS);
if (num_columns > 0) {
int width = ev->size().width();
int used_width = 0;
// Set our widths to be a percentage of the available width
for (int i = 0; i < num_columns - 1; i++) {
int column_width = (width * COLUMN_WIDTHS[i]) / 100;
this->setColumnWidth(i, column_width);
used_width += column_width;
}
// Set our last column to the remaining width
this->setColumnWidth(num_columns - 1, width - used_width);
}
}
// Call our base resizeEvent to handle the vertical resizing which
// we don't bother with here
QTableView::resizeEvent(ev);
}