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

Posted

技术标签:

【中文标题】在 wxWidgets 中调整无边框 wxFrame 的大小【英文标题】:Resizing Borderless wxFrame in wxWidgets 【发布时间】:2021-10-19 11:58:19 【问题描述】:

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

【问题讨论】:

为什么要使用无边框窗口?另外,你说I know i have to catch the mouse event manually and then implement it but am not able do that. 为什么?你能展示一些代码并解释它有什么问题吗? 应用程序的设计/外观(由 ui 设计师给出)是没有边框的,我们有一个自定义框架。 这是因为我不知道我应该按什么顺序做事。就像应该有多少步骤一样。如果我知道这些步骤,那么我可以先尝试自己实施。例如,在第 1 步中,我应该检查当前光标位置在哪里。然后在第二步中,如果光标距离框架仅 5 或 10 像素(向内或向外),那么我应该更改光标的外观。在第三步中,如果用户按住鼠标左键,则相应地更改帧大小。你能告诉我我必须遵循的确切步骤吗?然后我可以尝试实现它。 为此,您最好还是使用 wxEVT_ENTER_WINDOW。但这很奇怪。您能否确认它实际上是一个无边框窗口,而不仅仅是根本不可见的基本边框?您将不得不手动做很多事情,这些事情已经在操作系统/工具包级别为您完成了。感觉就像你要做的就是重新发明***。 但步骤几乎相同 - 获取光标坐标,相应地更改光标,然后在计时器上检查是否有鼠标点击然后移动。如果是 - 计算新大小并调用 event.Skip()。然而,所有这些都是不可靠的,因为鼠标进入/离开事件可以在进入窗口子级时触发。 【参考方案1】:

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

#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 毫秒。较小的值将提供更平滑的更新。在这个例子中,我在右下角画了一些红色标记来表示调整大小的区域。这是完全没有必要的,可以删除。

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


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

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

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

【讨论】:

我相信你是对的。我认为一个示例是 wxComboBox,您将在其中获得基于操作系统/工具包的不同行为。还有一张关于它的旧票。因此,如果这样的控制将在框架的边界 - 你会很不走运。否则您必须非常精确地使用坐标。 @NewPagodi 在为左下角添加相同的功能时,我注意到当我使用 SetWidth() 和 SetHeight() 方法时,框架的大小从最右边开始增加或减少.有没有办法从框架的另一(左)侧增加宽度和高度?比如改变原点什么的。现在我只在最右边使用这个调整大小。谢谢你的例子。 我已经更新了答案以显示如何根据从左下角和右下角拖动来调整大小。我知道这很复杂,但它最终比我预期的还要复杂。

以上是关于在 wxWidgets 中调整无边框 wxFrame 的大小的主要内容,如果未能解决你的问题,请参考以下文章

易语言利用窗口消息实现无边框调整和移动窗口

mxgraph进阶---如何创建无边框的文本标签,该标签会自动调整为文本

wxWidgets源码分析 - 窗口尺寸

创建自定义窗口大小调整功能

Qt-软件开发-自定义无边框UI界面 Frameless

MUI如何实现识别身份证调用相机带边框