wxBufferedPaintDC, wxGraphicContext, and flickering Topic is solved

Are you writing your own components and need help with how to set them up or have questions about the components you are deriving from ? Ask them here.
Post Reply
Glitch
Earned a small fee
Earned a small fee
Posts: 13
Joined: Fri Mar 27, 2020 9:54 am

wxBufferedPaintDC, wxGraphicContext, and flickering

Post by Glitch »

I thought that the wxBufferedPaintDC was the right thing to use to avoid flickering.
Instead my sample flickers.
What have i done wrong?

(the code should compile, despite it seems long, i think the only relevant part are the OnPaint and the DoDraw functions in the wxActiveArea.cpp file).

TEXT_wxAA.cpp

Code: Select all

#include "wx/wx.h"

#define WXC_FROM_DIP(ppp)   ppp
class TEST_wxAA_App : public wxApp
{
public:
    virtual bool OnInit() wxOVERRIDE;
};

// Define a new frame type: this is going to be our main frame
class MyFrame : public wxFrame
{
public:
    MyFrame(const wxString& title);
private:
};
//==============================================================
wxIMPLEMENT_APP(TEST_wxAA_App);
//==============================================================
bool TEST_wxAA_App::OnInit()
{
    if ( !wxApp::OnInit() )
        return false;

    MyFrame *frame = new MyFrame("wxActiveArea TEST");
    frame->Show(true);
    return true;
}

//==============================================================
#include "wxActiveArea.h"
//==============================================================
MyFrame::MyFrame(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title)
{
    wxBoxSizer* szr_main = new wxBoxSizer(wxHORIZONTAL);
    this->SetSizer(szr_main);

    wxActiveArea* m_ActiveArea = new wxActiveArea(this, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(this, wxSize(-1,-1)), wxTAB_TRAVERSAL);
    m_ActiveArea->SetBackgroundColour(wxColour(wxT("rgb(0,0,128)")));
    m_ActiveArea->SetForegroundColour(wxColour(wxT("rgb(255,255,255)")));

    szr_main->Add(m_ActiveArea, 1, wxALL|wxEXPAND, WXC_FROM_DIP(15));

    wxBoxSizer* szr_AA_V = new wxBoxSizer(wxVERTICAL);
    m_ActiveArea->SetSizer(szr_AA_V);

    wxBoxSizer* szr_AA_H = new wxBoxSizer(wxHORIZONTAL);

    szr_AA_V->Add(szr_AA_H, 1, wxALIGN_CENTER_HORIZONTAL, 5);

    wxStaticText* m_st_label = new wxStaticText(m_ActiveArea, wxID_ANY, _("Centered label"), wxDefaultPosition, wxDefaultSize, 0);
    m_st_label->SetBackgroundColour(wxColour(wxT("rgb(0,0,255)")));

    szr_AA_H->Add(m_st_label, 0, wxALIGN_CENTER_VERTICAL, 5);

    CentreOnScreen(wxBOTH);
}
wxActiveArea.cpp

Code: Select all

#include "wxActiveArea.h"
#include <algorithm>
#include <wx/graphics.h>

IMPLEMENT_DYNAMIC_CLASS( wxActiveArea, inherited )

BEGIN_EVENT_TABLE( wxActiveArea, inherited )
    EVT_SIZE( wxActiveArea::OnSize )
    EVT_PAINT( wxActiveArea::OnPaint )
END_EVENT_TABLE()
//---------------------------------------------------------------------------------------------------
int Step;
int Direction;
//---------------------------------------------------------------------------------------------------
wxActiveArea::~wxActiveArea()
{
    m_TimClick.Stop();
}
//---------------------------------------------------------------------------------------------------
wxActiveArea::wxActiveArea(
    wxWindow            *parent     ,
    wxWindowID          winid       ,
    const wxPoint&      pos         ,
    const wxSize&       size        ,
    long                style       ,
    const wxString&     name
)
{
	m_parent = parent;
    Create(
        m_parent	,
        winid		,
        pos			,
        size		,
        style       ,
        name
    );
    Step = 0;
    Direction = 1;
    m_TimClick.Start(50);
}
//---------------------------------------------------------------------------------------------------
bool wxActiveArea::Create(
    wxWindow            *parent     ,
    wxWindowID          winid       ,
    const wxPoint&      pos         ,
    const wxSize&       size        ,
    long                style       ,
    const wxString&     name
)
{
    inherited::Create(parent, winid, pos, size, style, name);

    this->SetBackgroundColour(wxColour(0,   0,  64));
    this->SetForegroundColour(wxColour(255, 0, 0));

    m_TimClick.Bind(wxEVT_TIMER, &wxActiveArea::OnTimClick, this);
    return true;
}
//---------------------------------------------------------------------------------------------------
void wxActiveArea::OnPaint( wxPaintEvent& event )
{
    wxBufferedPaintDC dc(this);
	doDraw(dc);
}
//---------------------------------------------------------------------------------------------------

//°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°
wxRect wxActiveArea::ShrinkToSize(
                  const wxRect &Container,
                  const int Margin,
                  const int Thickness
                )
{
	wxRect retRect;
	// final margin must take care of border thickness.
	// even thickness is drawn up-left, odd thickness is drawn down-right
    int TLOffset= Margin + Thickness/2;        // Offset of the TopLeft corner
    int BROffset= Thickness%2 + TLOffset*2;    // Offset of the BottomRight corner

    retRect.x      = Container.x      + TLOffset;
    retRect.y      = Container.y      + TLOffset;
	retRect.width  = Container.width  - BROffset;
	retRect.height = Container.height - BROffset;
    return retRect;
}
//°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°



//===================================================================================================
//===================================================================================================
//===================================================================================================
void wxActiveArea::doDraw(wxBufferedPaintDC& dc)
{
    wxGraphicsContext *gc;
	wxRect clientRect = GetClientRect();

    // Draw the background
	dc.SetPen			(wxPen		(GetBackgroundColour()) );
	dc.SetBrush			(wxBrush	(GetBackgroundColour()) );
	dc.DrawRectangle	(clientRect);

    gc = wxGraphicsContext::Create( dc );
    if(!gc) {
        // error?!?
        return;
    }
    gc->SetBrush(*wxTRANSPARENT_BRUSH);


    // Draw the rounded border, 5px thick, 2 pixels inside
    wxRect drawRect = ShrinkToSize (clientRect,2,5);
    wxPen bPen(wxColour(100, 0, 0));
    bPen.SetWidth(5);
    gc->SetPen(bPen);
    gc->DrawRoundedRectangle(drawRect.GetLeft(), drawRect.GetTop(), drawRect.GetWidth(),drawRect.GetHeight(),10);

    // Draw the glow over the border

    // First, set the color with alpha=50....
    wxColour cCol = GetForegroundColour();  // Get the forecolor
    uint32_t cColLong= cCol.GetRGB();       // get it as long
    size_t alpha = 50;                      // compute the alpha (simplified, here)
    uint32_t rgba = (alpha<<24) | cColLong; // set the alpha value
    cCol.SetRGBA(rgba);                     // set the color to the new value
    bPen.SetColour(cCol);                   // set the pen color.
    //.... phew.

    // Pulsing glow
    int tthick;
    for (int i=1; i<=Step; i++) {
        tthick = 5 + i; // width of the glow part
        drawRect = ShrinkToSize (clientRect, 2, tthick); // get the right rectangle
        bPen.SetWidth(tthick);
        gc->SetPen(bPen);
        gc->DrawRoundedRectangle(drawRect.GetLeft(), drawRect.GetTop(), drawRect.GetWidth(),drawRect.GetHeight(),10);
    }
    delete gc;
}
//===================================================================================================
//===================================================================================================
//===================================================================================================

//---------------------------------------------------------------------------------------------------
void wxActiveArea::OnSize( wxSizeEvent& event )
{
	event.Skip();
	Refresh();
}
//---------------------------------------------------------------------------------------------------
void wxActiveArea::OnTimClick(wxTimerEvent& event)
{
    Step+=Direction; // Glow expansion/reduction
    if (Step<=0 || Step>5) {
        Direction *= -1;
    }
    Refresh();
}
//---------------------------------------------------------------------------------------------------
wxActiveArea.h

Code: Select all

#ifndef WXACTIVEAREA_H
#define WXACTIVEAREA_H

#include <wx/panel.h>
#include <wx/dcbuffer.h>
#include <wx/timer.h>

// forward declarations
class wxActiveArea;

#define ID_WXACTIVEAREA 10101

class wxActiveArea: public wxPanel //wxWindow //wxControl
{
    typedef wxPanel inherited;

    DECLARE_DYNAMIC_CLASS( wxActiveArea )
    DECLARE_EVENT_TABLE()

public:
    // Class functions
    wxActiveArea(){};
    wxActiveArea(
            wxWindow *parent,
            wxWindowID winid = ID_WXACTIVEAREA,
            const wxPoint& pos = wxDefaultPosition,
            const wxSize& size = wxDefaultSize,
            long style = wxTAB_TRAVERSAL | wxSIMPLE_BORDER,
            const wxString& name = wxPanelNameStr
    );
    virtual ~wxActiveArea();
    wxActiveArea(const wxActiveArea& other);

protected:
    // Pseudo ctor
    bool Create(wxWindow *parent,
                wxWindowID winid = ID_WXACTIVEAREA,
                const wxPoint& pos = wxDefaultPosition,
                const wxSize& size = wxDefaultSize,
                long style = wxTAB_TRAVERSAL | wxSIMPLE_BORDER,
                const wxString& name = wxPanelNameStr);

    void Init();

    void doDraw(wxBufferedPaintDC& dc);
    virtual bool AcceptsFocus() const {return false;};

    wxTimer m_TimClick;
    void OnTimClick(wxTimerEvent& event);

private:
    virtual void		OnSize( wxSizeEvent& event );
    virtual void		OnPaint( wxPaintEvent& event );

private:
    wxRect      ShrinkToSize(
                  const wxRect &Container,
                  const int Margin,
                  const int Thickness
                );
};
#endif // WXACTIVEAREA_H
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxBufferedPaintDC, wxGraphicContext, and flickering

Post by PB »

There is probably more issues but what I spotted while skimming the code was that you let the OS erase the window background but you are drawing it again by yourself.

It probably won't help much but I would not do that, i.e., I would change the wxActiveArea background style to wxBG_STYLE_PAINT, see
https://docs.wxwidgets.org/trunk/classw ... f9fd6594ec

Anyway, generally depending on the drawing and refresh rate, getting rid of flicker may not be easy...

EDIT
Actually, just adding

Code: Select all

SetBackgroundStyle(wxBG_STYLE_PAINT);
to wxActiveArea::Create() seems to get rid of the flicker for me even when the frame is maximized (screen resolution 2560x1440).

BTW, I understand that it is subjective, but the glow seems to go too fast for my taste.
Glitch
Earned a small fee
Earned a small fee
Posts: 13
Joined: Fri Mar 27, 2020 9:54 am

Re: wxBufferedPaintDC, wxGraphicContext, and flickering

Post by Glitch »

PB wrote: Wed Apr 22, 2020 5:54 pm I would change the wxActiveArea background style to wxBG_STYLE_PAINT, see
https://docs.wxwidgets.org/trunk/classw ... f9fd6594ec
Thanks! I missed this. :oops:
PB wrote: Wed Apr 22, 2020 5:54 pm There is probably more issues ...
pls tell me any issue or possible optimization you see. I'm willing to learn!
PB wrote: Wed Apr 22, 2020 5:54 pm BTW, I understand that it is subjective, but the glow seems to go too fast for my taste.
Yep.
This is a heavy stripped version of my component 'cause it has to be posted here.
The "glow" effect is a "feedback" for the user when using the "long-press" feature, and its speed is proportional to the time before firing the long-press event, which is a property of the wxActiveArea.
PB
Part Of The Furniture
Part Of The Furniture
Posts: 4193
Joined: Sun Jan 03, 2010 5:45 pm

Re: wxBufferedPaintDC, wxGraphicContext, and flickering

Post by PB »

So, did using wxBG_STYLE_PAINT eliminated the flicker for you as well?
Glitch wrote: Thu Apr 23, 2020 8:34 am pls tell me any issue or possible optimization you see.
After skimming the code the second time I actually do not see anything important.

The code could be probably simplified a bit by creating the control with wxFULL_REPAINT_ON_RESIZE style instead of handling the size event...

I think you do not need to stop the timer in the dtor nor save the control parent (always available via GetParent()).

At least in theory, you should check the result of the base class Create() and handle when it returns false.

You may also consider using FromDIP() instead of absolute pixel values, there could be a noticeable difference on higher DPI scalings.
Post Reply