Search code examples
c++wxwidgets

wxWidgets painting customization controls


I want to write an application, which must have the same appearance on any platform. I decide use wxWidgets

 (I never have a large amount of work with this library).

Start my customization from ScrollBar. This was a bad decision,

(inheritance from wxScrollBar = because all control badly flickering)

because under OS Windows default ScrollBar has very poor ability for customization, explanation here. Next my step was to make same work as in this post, but use wxWidgets instead MFC.

  1. Better paint all control on one window or replace each 4 part by own window: scroll background, thumb slider, 2 scroll arrows? need advice.

I chose first:

#include "wx/wx.h"
#include "wx/sizer.h"

#define ENABLE_ERASING 0
#define DISABLE_BORDER 1
#define UPDATE_ON_RESIZE 0

#if DISABLE_BORDER
long gBorder = wxBORDER_NONE;
#else
long gBorder = 0;
#endif

class ScrollBar : public wxControl
{
public:
    ScrollBar(wxWindow *parent, wxWindowID id);
protected:
    void on_paint(wxPaintEvent& ev);
#if ENABLE_ERASING
    void on_erace(wxEraseEvent& ev);
#endif
    void draw(wxDC& dc);
    wxSize DoGetBestSize() const override;
#if UPDATE_ON_RESIZE
    void on_size(wxSizeEvent& ev);
#endif
    wxDECLARE_EVENT_TABLE();
private:
};

wxBEGIN_EVENT_TABLE(ScrollBar, wxControl)
EVT_PAINT(ScrollBar::on_paint)
#if ENABLE_ERASING
EVT_ERASE_BACKGROUND(ScrollBar::on_erace)
#endif
#if UPDATE_ON_RESIZE
EVT_SIZE(ScrollBar::on_size)
#endif
wxEND_EVENT_TABLE()

ScrollBar::ScrollBar(wxWindow* parent, wxWindowID id)
    : wxControl(parent, id, wxDefaultPosition, wxDefaultSize, gBorder)
{}

void ScrollBar::on_paint(wxPaintEvent& ev)
{
    wxPaintDC dc(this);
    draw(dc);
    wxSize size = GetClientSize();
    wxString text = wxString::Format("%i; %i paint", size.GetWidth(),     
size.GetHeight());
    dc.DrawText(text, wxPoint(74, 8));
}

#if ENABLE_ERASING
void ScrollBar::on_erace(wxEraseEvent& ev)
{
    wxClientDC* clientDC = nullptr;
    if (!ev.GetDC())
        clientDC = new wxClientDC(this);
    wxDC* dc = clientDC ? clientDC : ev.GetDC() ;
    draw(*dc);
    wxSize size = GetClientSize();
    wxString text = wxString::Format("%i; %i erase", size.GetWidth(), 
size.GetHeight());
    dc->DrawText(text, wxPoint(74, 8));
    if (clientDC)
        delete clientDC;
}
#endif

void ScrollBar::draw(wxDC& dc)
{
    wxSize size = dc.GetSize();
    // draw scroll arrows
    dc.SetBrush(*wxBLUE_BRUSH);
    dc.SetPen( wxPen( wxColor(0,175,175), 2 ) );
    dc.DrawRectangle(0, 0, 20, size.GetHeight());
    dc.DrawRectangle(size.GetWidth() - 21, 0, 20, size.GetHeight());

    // draw background
    dc.SetBrush(*wxGREY_BRUSH);
    dc.SetPen( wxPen( wxColor(175,175, 0), 2 ) );
    dc.DrawRectangle(21, 0, size.GetWidth() - 43, size.GetHeight());

    // draw thumb slider
    dc.SetBrush(*wxCYAN_BRUSH);
    dc.SetPen( wxPen( wxColor(175, 0, 175), 2 ) );
    dc.DrawRectangle(70, 7, 150, 20);

    dc.SetPen(wxNullPen);
    dc.SetBrush(wxNullBrush);
}

wxSize ScrollBar::DoGetBestSize() const
{
    return wxSize(-1, 35);
}

#if UPDATE_ON_RESIZE
void ScrollBar::on_size(wxSizeEvent& ev)
{
    Refresh();
    ev.Skip();
}
#endif
// ********************************************** //

class MyApp: public wxApp
{
    wxFrame* frame_;
    ScrollBar* sc_bar_;
public:
    bool OnInit()
    {
        if ( !wxApp::OnInit() )
            return false;

        frame_ = new wxFrame(nullptr, wxID_ANY, "ScBar test App");
        sc_bar_ = new ScrollBar(frame_, wxID_ANY);

        wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
        sizer->Add(new wxButton(frame_, -1, "Tiny Button"), 0, 0, 0);
        sizer->Add(sc_bar_, 0, wxEXPAND, 0);

        frame_->SetSizer(sizer);
        frame_->Show();
        return true;
    }
};

IMPLEMENT_APP(MyApp)
  1. Why does statically painted (not change position, size) thumb slider always draw correct and resizing not make any effect on that rect?

And then the flicker wars began. Erasing background not help at all:

#define ENABLE_ERASING 1

Here i find solution need add correct handler for resizing:

#define ENABLE_ERASING 0
#define UPDATE_ON_RESIZE 1
  1. Is it the correct solution?
  2. I not understand why i must to do ev.Skip(); in resize event handler?
  3. And why here, here, here use wxPanel, wxWindow not wxControl maybe i must do the same?

OS Window 7 x64; MinGW 4.8.1; wxWidgets 3.1.0


Solution

  • wxWidgets is emphatically not a good choice for writing an application that must have the same appearance under all platforms because this goes straight against wxWidgets design goals which are to be indistinguishable from the native look and feel under each platform.

    The only way to use wxWidgets to do what you want to is to use its wxUniversal port which does implement the same appearance everywhere. Be warned though, that it has a number of bugs and missing features compared to the usual ports (wxMSW, wxGTK, wxOSX), so you should be ready to deal with them if you decide to use it. But it would still be a much better idea than using any native port as you're apparently trying to do.