Search code examples
c++c++11eventswxwidgetsdraggable

Create a Titleless/Borderless draggable wxFrame in wxWidgets


Hi i am trying to create a wxApp that will have not have a titlebar(including the minimize, maximize and close) icons that are by default provided. The code that i have is as follows: main.h

class MyApp : public wxApp
{
  public:
    virtual bool OnInit();
};

main.cpp

bool MyApp::OnInit()
{

    MyFrame *prop = new MyFrame(wxT("MyFrame"));
    prop->Show(true);

    return true;
}

myframe.h

class MyFrame : public wxFrame
{
public:
  MyFrame(const wxString& title);
  
  void OnClick(wxMouseEvent& event);

};

myframe.cpp

MyFrame::MyFrame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
  MyPanel *panel = new MyPanel(this, wxID_ANY);
  panel->SetBackgroundColour(wxColour(255,255,255));
  MyButton *button = new MyButton(panel, wxID_ANY, wxT("Ok"));

  Connect( wxEVT_LEFT_UP, 
      wxMouseEventHandler(MyFrame::OnClick));

  panel->Centre();
}


void MyFrame::OnClick(wxMouseEvent& event) 
{
  std::cout << "event reached frame class" << std::endl;
  
}

mypanel.h

class MyPanel : public wxPanel
{
public: 
  MyPanel(wxFrame *frame, int id);

  void OnClick(wxMouseEvent& event);
};

mypanel.cpp

MyPanel::MyPanel(wxFrame *frame, int id)
    : wxPanel(frame, id)
{
  Connect( wxEVT_LEFT_UP, 
      wxMouseEventHandler(MyPanel::OnClick));
} 

void MyPanel::OnClick(wxMouseEvent& event) 
{
  std::cout << "event reached panel class" << std::endl;
  
}

mybutton.h

class MyButton : public wxPanel
{
public:
  MyButton(wxPanel *panel, int id, const wxString &label);

  void OnClick(wxMouseEvent& event);

};

mybutton.cpp

void MyButton::onClick(wxMouseEvent &event)
{

}

What i want is:

  1. There should be no title bar(including the 3 maximize, minimize & close buttons) at the top.
  2. Now since there is no titlebar at the top of the frame, there is no way to drag or close or maximize or minimize the window. For that i want to create a custom titlebar at the top which should have the three customized maximized,minimized and close button and also i should be able to drag the frame by double clicking and holding and dragging the topmost part of the newly created frame.

Is this possible in wxWidgets? If yes, how can i achieve this?

I am not proposing any new way of dragging. The new frame/window that we will have should also be dragged only by its own customized title bar. It's exactly like dragging the old frame by double clicking and dragging the frame in the native case. I just want to customize the native title bar. Like increase its height, change it's colour, change how to three buttons(minimize, maximize and close) look.


Solution

  • Here's the simplest example I can think of how to create a frame with a pseudo titlebar that can be clicked to drag the frame around. This example shows which mouse events need to be handled to drag the window around and how to do the calculations needed in those event handlers.

    Note that moving the frame needs to be done in screen coordinates, but the coordinates received in the event handlers will be in client coordinates for the title bar. This example also shows how to do those coordinate conversions.

    #include "wx/wx.h"
    
    class CustomTitleBar:public wxWindow
    {
    public:
        CustomTitleBar(wxWindow* p) : wxWindow(p,wxID_ANY)
        {
            m_dragging = false;
    
            SetBackgroundColour(*wxGREEN);
            Bind(wxEVT_LEFT_DOWN,&CustomTitleBar::OnMouseLeftDown,this);
            Bind(wxEVT_MOUSE_CAPTURE_LOST, &CustomTitleBar::OnMouseCaptureLost,
                 this);
        }
    
        wxSize DoGetBestClientSize() const override
        {
            return wxSize(-1,20);
        }
    
    private:
        void OnMouseLeftDown(wxMouseEvent& event)
        {
            if ( !m_dragging )
            {
                Bind(wxEVT_LEFT_UP,&CustomTitleBar::OnMouseLeftUp,this);
                Bind(wxEVT_MOTION,&CustomTitleBar::OnMouseMotion,this);
                m_dragging = true;
    
                wxPoint clientStart = event.GetPosition();
                m_dragStartMouse = ClientToScreen(clientStart);
                m_dragStartWindow = GetParent()->GetPosition();
                CaptureMouse();
            }
        }
    
        void OnMouseLeftUp(wxMouseEvent&)
        {
            FinishDrag();
        }
    
        void OnMouseMotion(wxMouseEvent& event)
        {
            wxPoint curClientPsn = event.GetPosition();
            wxPoint curScreenPsn = ClientToScreen(curClientPsn);
            wxPoint movementVector = curScreenPsn - m_dragStartMouse;
    
            GetParent()->SetPosition(m_dragStartWindow + movementVector);
        }
    
        void OnMouseCaptureLost(wxMouseCaptureLostEvent&)
        {
            FinishDrag();
        }
    
        void FinishDrag()
        {
            if ( m_dragging )
            {
                Unbind(wxEVT_LEFT_UP,&CustomTitleBar::OnMouseLeftUp,this);
                Unbind(wxEVT_MOTION,&CustomTitleBar::OnMouseMotion,this);
                m_dragging = false;
            }
    
            if ( HasCapture() )
            {
                ReleaseMouse();
            }
        }
    
        wxPoint m_dragStartMouse;
        wxPoint m_dragStartWindow;
        bool m_dragging;
    };
    
    class Customframe : public wxFrame
    {
    public:
        Customframe(wxWindow* p)
            :wxFrame(p, wxID_ANY, wxString(), wxDefaultPosition, wxSize(150,100),
                     wxBORDER_NONE)
        {
            CustomTitleBar* t = new CustomTitleBar(this);
            SetBackgroundColour(*wxBLUE);
            wxBoxSizer* szr = new wxBoxSizer(wxVERTICAL);
            szr->Add(t,wxSizerFlags(0).Expand());
            SetSizer(szr);
            Layout();
        }
    };
    
    class MyFrame: public wxFrame
    {
    public:
        MyFrame():wxFrame(NULL, wxID_ANY, "Custom frame Demo", wxDefaultPosition,
                 wxSize(400, 300))
         {
             wxPanel* bg = new wxPanel(this, wxID_ANY);
             wxButton* btn = new wxButton(bg, wxID_ANY, "Custom frame");
             wxBoxSizer* szr = new wxBoxSizer(wxVERTICAL);
             szr->Add(btn,wxSizerFlags(0).Border(wxALL));
             bg->SetSizer(szr);
    
             Layout();
    
             btn->Bind(wxEVT_BUTTON, &MyFrame::OnButton, this);
             m_customFrame = NULL;
         }
    private:
        void OnButton(wxCommandEvent&)
        {
            if ( m_customFrame )
            {
                m_customFrame->Close();
                m_customFrame = NULL;
            }
            else
            {
                m_customFrame = new Customframe(this);
                m_customFrame->CenterOnParent();
                m_customFrame->Show();
            }
        }
    
        wxFrame* m_customFrame;
    };
    
    class MyApp : public wxApp
    {
        public:
            virtual bool OnInit()
            {
                MyFrame* frame = new MyFrame();
                frame->Show();
                return true;
            }
    };
    
    wxIMPLEMENT_APP(MyApp);
    
    

    On windows, it looks like this.

    A custom frame with a custom title bar

    You should be able to add whatever buttons you want to the custom title bar exactly as you would add buttons to any other window.