Hi i have a borderless wxFrame but it is not resizable. I want to make the frame resizable just like when it is resizable with a border. I am using wxRESIZE_BORDER and wxBORDER_NONE style properties. I know i have to catch the mouse event manually and then implement it but am not able do that. I have also looked at the shaped sample directory but there also there is no "border-less resizable frame". I am using Ubuntu 18.04 and wxWidgets 3.1.5. Is this doable/possible in wxWidgets and is there any example of the same?
Here's an example of the basic machinery needed to allow a frame to be resized without using a resize border. It basically shows Igor's comments above.
#include "wx/wx.h"
#include <wx/timer.h>
#include <wx/dcbuffer.h>
#include <wx/version.h>
#if __WXGTK__
#define BORDERLESS_FRAME_STYLE (wxCAPTION | wxCLOSE_BOX)
#else
#define BORDERLESS_FRAME_STYLE (wxCAPTION | wxCLOSE_BOX | wxBORDER_NONE)
#endif // __WXGTK__
class MyFrame: public wxFrame
{
public:
MyFrame();
private:
enum ResizeMode
{
ResizeNone,
ResizeFromTop,
ResizeFromUpperLeft,
ResizeFromLeft,
ResizeFromLowerLeft,
ResizeFromBottom,
ResizeFromLowerRight,
ResizeFromRight,
ResizeFromUpperRight
};
// General event handlers
void OnBgPanelPaint(wxPaintEvent&);
// Event handlers for resizing
void OnLeftDownForResizeFromLowerRight(wxMouseEvent&);
void OnLeftDownForResizeFromLowerLeft(wxMouseEvent&);
void OnLeftUp(wxMouseEvent&);
void OnMouseCaptureLost(wxMouseCaptureLostEvent&);
void OnResizeTimer(wxTimerEvent&);
// Resizing helper functions
void DoDragBasedResize();
void StartResize(wxWindow*, const wxPoint&);
void CompleteResize(bool doFinalResize = false);
// Data and objects needed for resizing.
wxTimer m_resizeTimer;
int m_resizeFrequency;
int m_resizeAreaLength;
ResizeMode m_resizeMode;
wxPoint m_resizeStartPoint;
wxSize m_initialFrameSize;
wxPoint m_initialFramePosition;
wxWindow* m_clickToResizeFromLowerRightWindow;
wxWindow* m_clickToResizeFromLowerLeftWindow;
wxWindow* m_curResizeWindow;
// GUI controls
wxPanel* m_bgPanel;
};
MyFrame::MyFrame():wxFrame(NULL, wxID_ANY, "Resizing Demo", wxDefaultPosition,
wxSize(400, 300), BORDERLESS_FRAME_STYLE)
{
// Set up the UI.
m_bgPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxTAB_TRAVERSAL|wxFULL_REPAINT_ON_RESIZE);
m_bgPanel->SetBackgroundStyle(wxBG_STYLE_PAINT );
m_bgPanel->Bind(wxEVT_PAINT, &MyFrame::OnBgPanelPaint, this);
wxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
mainSizer->Add(m_bgPanel, wxSizerFlags(1).Expand());
SetSizer(mainSizer);
Layout();
// Initialize the data needed for resizing.
m_resizeMode = ResizeNone;
#if wxCHECK_VERSION(3,1,0)
m_resizeAreaLength = FromDIP(20);
#else
m_resizeAreaLength = 20;
#endif // wxCHECK_VERSION
m_resizeTimer.Bind(wxEVT_TIMER, &MyFrame::OnResizeTimer, this);
m_resizeFrequency = 50;
m_curResizeWindow = NULL;
// Set window and event handlers for resizing from lower right.
m_clickToResizeFromLowerRightWindow = m_bgPanel;
m_clickToResizeFromLowerRightWindow->Bind(wxEVT_LEFT_DOWN,
&MyFrame::OnLeftDownForResizeFromLowerRight, this);
m_clickToResizeFromLowerRightWindow->Bind(wxEVT_LEFT_UP,
&MyFrame::OnLeftUp, this);
m_clickToResizeFromLowerRightWindow->Bind(wxEVT_MOUSE_CAPTURE_LOST,
&MyFrame::OnMouseCaptureLost, this);
// Set window and event handlers for resizing from lower left.
m_clickToResizeFromLowerLeftWindow = m_bgPanel;
m_clickToResizeFromLowerLeftWindow->Bind(wxEVT_LEFT_DOWN,
&MyFrame::OnLeftDownForResizeFromLowerLeft, this);
m_clickToResizeFromLowerLeftWindow->Bind(wxEVT_LEFT_UP,
&MyFrame::OnLeftUp, this);
m_clickToResizeFromLowerLeftWindow->Bind(wxEVT_MOUSE_CAPTURE_LOST,
&MyFrame::OnMouseCaptureLost, this);
}
void MyFrame::OnLeftDownForResizeFromLowerLeft(wxMouseEvent& event)
{
wxPoint p = event.GetPosition();
wxSize sz = m_clickToResizeFromLowerLeftWindow->GetClientSize();
// Check if the click is in the lower left of the window.
if ( p.x < m_resizeAreaLength &&
sz.GetHeight() - p.y < m_resizeAreaLength )
{
StartResize(m_clickToResizeFromLowerLeftWindow, p);
m_resizeMode = ResizeFromLowerLeft;
SetTitle("Resize From lower left in progress...");
SetCursor(wxCursor(wxCURSOR_SIZENESW));
}
else
{
event.Skip();
}
}
void MyFrame::OnLeftDownForResizeFromLowerRight(wxMouseEvent& event)
{
wxPoint p = event.GetPosition();
wxSize sz = m_clickToResizeFromLowerRightWindow->GetClientSize();
// Check if the click is in the lower right of the window.
if ( sz.GetWidth() - p.x < m_resizeAreaLength &&
sz.GetHeight() - p.y < m_resizeAreaLength )
{
StartResize(m_clickToResizeFromLowerRightWindow, p);
m_resizeMode = ResizeFromLowerRight;
SetTitle("Resize from lower right in progress...");
SetCursor(wxCursor(wxCURSOR_SIZENWSE));
}
else
{
event.Skip();
}
}
void MyFrame::OnLeftUp(wxMouseEvent& event)
{
if ( m_resizeMode != ResizeNone )
{
CompleteResize(true);
}
else
{
event.Skip();
}
}
void MyFrame::OnMouseCaptureLost(wxMouseCaptureLostEvent&)
{
if ( m_resizeMode != ResizeNone )
{
CompleteResize(false);
}
}
void MyFrame::OnResizeTimer(wxTimerEvent&)
{
DoDragBasedResize();
}
void MyFrame::DoDragBasedResize()
{
wxMouseState ms = ::wxGetMouseState();
wxPoint curMousePsn = ms.GetPosition();
wxPoint dragVector = curMousePsn - m_resizeStartPoint;
wxSize newSize(m_initialFrameSize);
wxPoint newPsn(m_initialFramePosition);
if ( m_resizeMode == ResizeFromLowerRight )
{
newSize = wxSize(m_initialFrameSize.GetWidth() + dragVector.x,
m_initialFrameSize.GetHeight() + dragVector.y);
}
else if ( m_resizeMode == ResizeFromLowerLeft )
{
newSize = wxSize(m_initialFrameSize.GetWidth() - dragVector.x,
m_initialFrameSize.GetHeight() + dragVector.y);
newPsn = wxPoint(m_initialFramePosition.x + dragVector.x,
m_initialFramePosition.y);
}
SetSize(newPsn.x, newPsn.y, newSize.GetWidth(), newSize.GetHeight());
}
void MyFrame::StartResize(wxWindow* win, const wxPoint& p)
{
m_curResizeWindow = win;
m_resizeTimer.Start(m_resizeFrequency);
m_resizeStartPoint = m_curResizeWindow->ClientToScreen(p);
m_curResizeWindow->CaptureMouse();
m_initialFramePosition = GetPosition();
m_initialFrameSize = GetSize();
}
void MyFrame::CompleteResize(bool doFinalResize)
{
if ( doFinalResize )
{
DoDragBasedResize();
}
m_resizeTimer.Stop();
m_resizeMode = ResizeNone;
SetTitle("Resize complete");
SetCursor(wxCursor(wxCURSOR_ARROW));
if ( m_curResizeWindow && m_curResizeWindow->HasCapture() )
{
m_curResizeWindow->ReleaseMouse();
}
m_curResizeWindow = NULL;
}
void MyFrame::OnBgPanelPaint(wxPaintEvent&)
{
wxAutoBufferedPaintDC dc(m_bgPanel);
dc.Clear();
wxPen pen(*wxRED,5);
dc.SetPen(pen);
// Draw some red marks to indicate the lower right resize area
wxPoint lowerRight = m_bgPanel->GetClientRect().GetBottomRight();
dc.DrawLine(lowerRight - wxPoint(0,m_resizeAreaLength), lowerRight);
dc.DrawLine(lowerRight, lowerRight - wxPoint(m_resizeAreaLength,0));
// Draw some red marks to indicate the lower left resize area
wxPoint lowerLeft = m_bgPanel->GetClientRect().GetBottomLeft();
dc.DrawLine(lowerLeft - wxPoint(0,m_resizeAreaLength), lowerLeft);
dc.DrawLine(lowerLeft, lowerLeft + wxPoint(m_resizeAreaLength,0));
}
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
MyFrame* frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
This only shows resizing if the click is in the lower right corner of a panel covering the client area of the frame. I tried to add enough generality that if you have another control covering the lower right, you should be able to set that control to be m_clickToResizeWindow
in the code above.
There is one case where that might not work however. I could be wrong, but I think some native controls completely consume mouse clicks and won't even generate a wxMouseEvent. In this case, resizing won't be possible if such a control is in the lower right.
There are some parameters that can be changed to modify the resizing behavior. The m_resizeAreaLength
determines how close to the edge a click can be to start the resizing process. I've set this to 20 DIPs. The m_resizeFrequency
determines how frequently the size is updated during a resize operation. I've set this to 50ms. A smaller value will provide smoother updates. In this example, I drew some red marks on the lower right to indicate the resizing area. This is completely unnecessary and can be removed.
This example only shows resizing based on a click on the lower right. However it shouldn't be too hard to modify the code to allow only horizontal resizing based on a click on the right edge or vertical resizing based on a click on the bottom edge. However, it might get complicated if there are multiple controls covering the left or bottom edges. This system only works well if there is one control covering those edges.
edit: I've updated the code to show resizing from both the lower left and lower right while keeping the rest of the frame in the appropriate position.
This can be further expanded to allow for resizing from other edges or corners. To do so it will be necessary to:
DoDragBasedResize
method setting a new size and position for the frame appropriate for the type of resizing being done.