I have read many questions about this topic but all I have found are either unanswered, do not do what I want, or simply do not work.
My problem is that of maintaining a height to width ratio while resizing my main window. So far I have seen propositions to override 'QLayout' and use 'heightForWidth' to perform the deed, to use the 'resizeEvent' call back to change the size hint, and to use the 'resizeEvent' call back to resize the window.
The first two options I mentioned only work in the examples for dragging width; I want a user to be able to drag width or height. The second of the two I attempted to adapt for my purposes but it causes my code to crash (the first relies on the 'heightForWidth' function and I cannot find a 'widthForHeight' function {The comments in this thread support my conclusion: How to maintain widgets aspect ratio in Qt?}). The third option was what I originally attempted but has a lot of bugs: most of the time the window snaps back to its original size, having rapidly transitioned between that and where it should be while dragging the border with the mouse.
The attempt which causes a crash:
void window::resizeEvent(QResizeEvent *event)
{
//window::resizeEvent(event); causes crash
if ((this->width() * 5 / 8) > (this->height() + 1)
|| (this->width() * 5 / 8) < (this->height() - 1))
{
updateGeometry();
}
}
QSize window::sizeHint() const
{
QSize s = size();
if (lastWidth != s.width())
{
s.setHeight((s.width()*5)/8);
//s.setWidth(window::sizeHint().width());
}
else if (lastHeight != s.height())
{
s.setWidth((s.height()*8)/5);
//s.setHeight(window::sizeHint().height());
}
return s;
}
The attempt which has bugs:
void window::resizeEvent(QResizeEvent *)
{
//If the window does not have the correct aspect ratio
//This should be false if this function caused the resize event,
// preventing a loop
if ((this->width() * 5 / 8) > (this->height() + 1)
|| (this->width() * 5 / 8) < (this->height() - 1))
{
int currentHeight = this->height();
int currentWidth = this->width();
//Change the dimension the user did not to match the user's change.
if (lastWidth != currentWidth)
{
lastWidth = currentWidth;
currentHeight = currentWidth * 5/8;
lastHeight = currentHeight;
}
else
{
lastHeight = currentHeight;
currentWidth = currentHeight * 8/5;
lastWidth = currentWidth;
}
//update the change
this->resize(currentWidth,currentHeight);
}
}
Related
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.
I have a window Mat gestures containing an image, I want to zoom in every pixel in the window but keep the border the same size. I have tried resize() but it's resizing the border as well.
For better explanation, I don't want the border that is in the green box to be resized as well as the whole border, but I need the image inside the border to be resized. How can I achieve this?
Set a ROI of the image excluding the border. If you already know the thickness, simply assign a new img from it. Then you can resize and draw cv::rectangle with the thickness of original image.
Following code snippet may not compile since I don't see a reproducible code.
cv::Mat img = cv::imread(...);
const int thick = 3;
const cv::Rect roi(thick, thick, img.width()-2*thick, img.height()-2*thick);
cv::Mat img_roi = img(roi);
cv::resize(...); // resize img_roi
cv::rectangle(...); // draw new border on img_roi, you need to pass a cv::Scalar value from img.at(0, 0) for the color of it.
However, I'm expecting a better idea from someone else.
The basic idea is deciding the scale changed every time on mouse wheel. After you get the current scale (v.s. origin image), you then can get the position and length of rectangle on scaled image.
In my github,checking OnMouseWheel () and RefreshSrcView () in Fastest_Image_Pattern_Matching/ELCVMatchTool/ELCVMatchToolDlg.cpp may give what you want.
Besides, if you only want to use opencv window without MFC framework or other frameworks, check this (pure OpenCV version)
Effect:
Part of the code:
BOOL CELCVMatchToolDlg::OnMouseWheel (UINT nFlags, short zDelta, CPoint pt)
{
POINT pointCursor;
GetCursorPos (&pointCursor);
ScreenToClient (&pointCursor);
// TODO: 在此加入您的訊息處理常式程式碼和 (或) 呼叫預設值
if (zDelta > 0)
{
if (m_iScaleTimes == MAX_SCALE_TIMES)
return TRUE;
else
m_iScaleTimes++;
}
if (zDelta < 0)
{
if (m_iScaleTimes == MIN_SCALE_TIMES)
return TRUE;
else
m_iScaleTimes--;
}
CRect rect;
//GetWindowRect (rect);
GetDlgItem (IDC_STATIC_SRC_VIEW)->GetWindowRect (rect);//重要
if (m_iScaleTimes == 0)
g_dCompensationX = g_dCompensationY = 0;
int iMouseOffsetX = pt.x - (rect.left + 1);
int iMouseOffsetY = pt.y - (rect.top + 1);
double dPixelX = (m_hScrollBar.GetScrollPos () + iMouseOffsetX + g_dCompensationX) / m_dNewScale;
double dPixelY = (m_vScrollBar.GetScrollPos () + iMouseOffsetY + g_dCompensationY) / m_dNewScale;
m_dNewScale = m_dSrcScale * pow (SCALE_RATIO, m_iScaleTimes);
if (m_iScaleTimes != 0)
{
int iWidth = m_matSrc.cols;
int iHeight = m_matSrc.rows;
m_hScrollBar.SetScrollRange (0, int (m_dNewScale * iWidth - m_dSrcScale * iWidth) - 1 + BAR_SIZE);
m_vScrollBar.SetScrollRange (0, int (m_dNewScale * iHeight - m_dSrcScale * iHeight) - 1 + BAR_SIZE);
int iBarPosX = int (dPixelX * m_dNewScale - iMouseOffsetX + 0.5);
m_hScrollBar.SetScrollPos (iBarPosX);
m_hScrollBar.ShowWindow (SW_SHOW);
g_dCompensationX = -iBarPosX + (dPixelX * m_dNewScale - iMouseOffsetX);
int iBarPosY = int (dPixelY * m_dNewScale - iMouseOffsetY + 0.5);
m_vScrollBar.SetScrollPos (iBarPosY);
m_vScrollBar.ShowWindow (SW_SHOW);
g_dCompensationY = -iBarPosY + (dPixelY * m_dNewScale - iMouseOffsetY);
//滑塊大小
SCROLLINFO infoH;
infoH.cbSize = sizeof (SCROLLINFO);
infoH.fMask = SIF_PAGE;
infoH.nPage = BAR_SIZE;
m_hScrollBar.SetScrollInfo (&infoH);
SCROLLINFO infoV;
infoV.cbSize = sizeof (SCROLLINFO);
infoV.fMask = SIF_PAGE;
infoV.nPage = BAR_SIZE;
m_vScrollBar.SetScrollInfo (&infoV);
//滑塊大小
}
else
{
m_hScrollBar.SetScrollPos (0);
m_hScrollBar.ShowWindow (SW_HIDE);
m_vScrollBar.SetScrollPos (0);
m_vScrollBar.ShowWindow (SW_HIDE);
}
RefreshSrcView ();
return CDialogEx::OnMouseWheel (nFlags, zDelta, pt);
}
I'm creating a screen where users can add certain tiles to use in an editor, but when adding a tile the window does not correctly resize to fit the content. Except that when I drag the window or resize it even just a little then it snaps to the correct size immediately.
And when just dragging the window it snaps to the correct size.
I tried using resize(sizeHint()); which gave me an incorrect size and the following error, but the snapping to correct size still happens when resizing/dragging.
QWindowsWindow::setGeometry: Unable to set geometry 299x329+991+536 on QWidgetWindow/'TileSetterWindow'. Resulting geometry: 299x399+991+536 (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 259x329, maximum size: 16777215x16777215).
I also tried using updateGeometry() and update(), but it didn't seem to do much if anything.
When setting the window to fixedSize it will immediately resize, but then the user cannot resize the window anymore. What am I doing wrong here and where do I start to solve it?
Edit
Minimal verifiable example and the .ui file.
selected_layout is of type Flowlayout
The flowlayout_placeholder_1 is only there because I can't place a flowlayout directly into the designer.
Edit2
Here is a minimal Visual Studio example. I use Visual Studio for Qt development. I tried creating a project in Qt Creator, but I didn't get that to work.
Edit3
Added a little video (80 KB).
Edit4
Here is the updated Visual Studio example. It has the new changes proposed by jpo38. It fixes the issue of the bad resizing. Though now trying to downsize the windows causes issues. They don't correctly fill up vertical space anymore if you try to reduce the horizontal space even though there is room for more rows.
Great MCVE, exactly what's needed to easily investigate the issue.
Looks like this FlowLayout class was not designed to have it's minimum size change on user action. Layout gets updated 'by chance' by QWidget kernel when the window is moved.
I could make it work smartly by modifying FlowLayout::minimumSize() behaviour, here are the changes I did:
Added QSize minSize; attribute to FlowLayout class
Modifed FlowLayout::minimumSize() to simply return this attribute
Added a third parameter QSize* pMinSize to doLayout function. This will be used to update this minSize attribute
Modified doLayout to save computed size to pMinSize parameter if specified
Had FlowLayout::setGeometry pass minSize attribute to doLayout and invalidate the layout if min size changed
The layout then behaves as expected.
int FlowLayout::heightForWidth(int width) const {
const int height = doLayout(QRect(0, 0, width, 0), true,NULL); // jpo38: set added parameter to NULL here
return height;
}
void FlowLayout::setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
// jpo38: update minSize from here, force layout to consider it if it changed
QSize oldSize = minSize;
doLayout(rect, false,&minSize);
if ( oldSize != minSize )
{
// force layout to consider new minimum size!
invalidate();
}
}
QSize FlowLayout::minimumSize() const {
// jpo38: Simply return computed min size
return minSize;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly,QSize* pMinSize) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
// jpo38: store max X
int maxX = 0;
for (auto&& item : itemList) {
QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
// jpo38: update max X based on current position
maxX = qMax( maxX, x + item->sizeHint().width() - rect.x() + left );
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
// jpo38: save height/width as max height/xidth in pMinSize is specified
int height = y + lineHeight - rect.y() + bottom;
if ( pMinSize )
{
pMinSize->setHeight( height );
pMinSize->setWidth( maxX );
}
return height;
}
I was having the same exact issue (albeit on PySide2 rather than C++).
#jpo38's answer above did not work directly, but it un-stuck me by giving me a new approach.
What worked was storing the last geometry, and using that geometry's width to calculate the minimum height.
Here is an untested C++ implementation based on the code in jpo38's answer (I don't code much in C++ so apologies in advance if some syntax is wrong):
int FlowLayout::heightForWidth(int width) const {
const int height = doLayout(QRect(0, 0, width, 0), true);
return height;
}
void FlowLayout::setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
// e-l: update lastSize from here
lastSize = rect.size();
doLayout(rect, false);
}
QSize FlowLayout::minimumSize() const {
// e-l: Call heightForWidth from here, my doLayout is doing things a bit differently with regards to margins, so might have to add or not add the margins here to the height
QSize size;
for (const QLayoutItem *item : qAsConst(itemList))
size = size.expandedTo(item->minimumSize());
const QMargins margins = contentsMargins();
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
size.setHeight(heightForWidth(qMax(lastSize.width(), size.width())));
return size;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
for (auto&& item : itemList) {
QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
int height = y + lineHeight - rect.y() + bottom;
return height;
}
My work Environment : Qt 5.8 MSVC2015 64bit, QT GraphicsView, Windows 7 64 bit
When GraphicsView vertical scroll bar is goes away, zoom out should stop.
So I tried with below code, but it failed to work :
void GraphicsView::scale(qreal scaleFactor)
{
QRectF r(0, 0, 1, 1); // A reference
int pos_x = this->horizontalScrollBar()->value();
int pos_y = this->verticalScrollBar()->value();
qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(r).width(); // absolute zoom factor
if ( factor > 7) { // Check zoom out limit
return;
}
//Failed, this code failed If zoom out again.**
if(pos_x <= 0 && pos_y <= 0 )
{
return;
}
Any suggestion How I can do to fix the above code ?
No reply for my question. Here is work around solution from me,
Check from wheelEvent are we doing zoom in or zoom out. I scale check vertical & horizontal scroll bar.
here _steps is private data member of my class GraphicsView. GraphicsView derived from QGraphicsView.
void GraphicsView::wheelEvent(QWheelEvent * event)
{
// Typical Calculations (Ref Qt Doc)
const int degrees = event->delta() / 8;
_steps = degrees / 15; // _steps = 1 for Zoom in, _steps = -1 for Zoom out.
}
void GraphicsView::scale(qreal scaleFactor)
{
QRectF r(0, 0, 1, 1); // A reference
int pos_x = this->horizontalScrollBar()->value();
int pos_y = this->verticalScrollBar()->value();
qreal factor = transform().scale(scaleFactor, scaleFactor).mapRect(r).width(); // absolute zoom factor
if ( factor > 7) { // Calculate your zoom in limit from factor
return;
}
//When there is no scroll bar, am I still I am zooming, stop it using _steps
if(pos_x <= 0 && pos_y <= 0 && _steps == -1)
{
return;
}
QGraphicsView::scale(scaleFactor, scaleFactor);
}
I know there is better solution then this, But I found this only :(
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);
}