在 wxWidgets 中调整无边界 wxFrame 的大小

Resizing Borderless wxFrame in wxWidgets

您好,我有一个无边界的 wxFrame,但它不能调整大小。我想让框架可以调整大小,就像它可以用边框调整大小一样。我正在使用 wxRESIZE_BORDER 和 wxBORDER_NONE 样式属性。我知道我必须手动捕获鼠标事件然后实现它,但我做不到。我也查看了形状示例目录,但也没有“无边框可调整大小的框架”。我正在使用 Ubuntu 18.04 和 wxWidgets 3.1.5。这是 wxWidgets 中的 doable/possible 吗?有没有相同的例子?

这里是允许在不使用调整大小边框的情况下调整框架大小所需的基本机制的示例。它基本上显示了 Igor 上面的评论。

#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);

只有在覆盖框架客户区的面板右下角单击时,才会显示调整大小。我试图添加足够的通用性,如果你有另一个控件覆盖右下角,你应该能够在上面的代码中将该控件设置为 m_clickToResizeWindow

但在一种情况下这可能不起作用。我可能是错的,但我认为一些原生控件完全消耗鼠标点击,甚至不会生成 wxMouseEvent。在这种情况下,如果此类控件位于右下方,则无法调整大小。

可以更改一些参数来修改调整大小行为。 m_resizeAreaLength 确定单击可以开始调整大小过程的边缘距离。我已将其设置为 20 DIP。 m_resizeFrequency 确定在调整大小操作期间更新大小的频率。我已将其设置为 50 毫秒。较小的值将提供更平滑的更新。在这个例子中,我在右下角画了一些红色标记来指示调整大小的区域。这完全没有必要,可以删除。

此示例仅显示基于点击右下角的调整大小。但是,修改代码以仅允许基于单击右边缘的水平调整大小或基于单击底部边缘的垂直调整大小应该不会太难。但是,如果有多个控件覆盖左侧或底部边缘,它可能会变得复杂。该系统只有在有一个控件覆盖这些边缘时才能正常工作。


编辑:我更新了代码以显示从左下角和右下角调整大小,同时将框架的其余部分保持在适当的位置。

这可以进一步扩展以允许从其他边缘或角落调整大小。为此,有必要:

  1. 为 window 添加一个指针,用于接收开始调整大小的点击。
  2. 为该 window 添加一个左下处理程序以启动该过程。
  3. 为 window.
  4. 绑定新的左下处理程序、现有的左上处理程序和现有的捕获丢失处理程序
  5. 添加到 DoDragBasedResize 方法中,为框架设置新的大小和位置,以适应正在进行的调整大小类型。