How to use wxDCOverlay?

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
Big Muscle
Experienced Solver
Experienced Solver
Posts: 82
Joined: Sun Jun 27, 2010 6:18 pm

How to use wxDCOverlay?

Post by Big Muscle » Tue Jan 14, 2020 12:07 pm

Our application has a canvas that draws some plots on X-Y axis. We would like to draw pointer that shows the measurement time in the plot. The current approach is to do everything in wxEVT_PAINT event, but it is not good because changing the time pointer redraws whole canvas that can be slow.

Our ideas is to draw time pointer into some kind of layer that is put over the plot and only this layer is redrawn when time changes. There is some wxDCOverlay in wxWidgets. Is it suitable for this purpose? Application will run on Win and GTK+3.

I declare "wxOverlay m_overlay" in the main class and call the following code when time changes:

Code: Select all

CAnalysisPanel contructor:
	m_pPlot = new wxPanel(this);
	m_pPlot->SetBackgroundStyle(wxBG_STYLE_CUSTOM);
	m_pPlot->Bind(wxEVT_PAINT, &CAnalysisPanel::OnPaint, this);

void CAnalysisPanel::SetCurrentFrame(unsigned int i) {
        wxClientDC dc(m_pPlot) ; // m_pCanvas is wxPanel
        wxDCOverlay overlaydc( m_overlay, &dc );
        overlaydc.Clear();
        dc.DrawLine(...);	// code to draw the pointer
}
But it does not work. Although the time pointer is drawn, the wxEVT_PAINT event seems to be called immediately after, so it gets repainted.
Is something wrong with the code? Did I forget something? Why wxEVT_PAINT is called?

New Pagodi
Super wx Problem Solver
Super wx Problem Solver
Posts: 347
Joined: Tue Jun 20, 2006 6:47 pm
Contact:

Re: How to use wxDCOverlay?

Post by New Pagodi » Tue Jan 14, 2020 2:21 pm

I think a better approach would be to cache the plots into a bitmap. The paint handler would first draw the bitmap and then draw any extra lines over the bitmap. To generate the cached bitmap of the plot, you can use wxMemoryDC.

User avatar
doublemax
Moderator
Moderator
Posts: 14954
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to use wxDCOverlay?

Post by doublemax » Tue Jan 14, 2020 4:15 pm

I've never used wxDCOverlay, but it's used in the "drawing" sample to draw a rubber-band on the panel. Check it out.
Use the source, Luke!

Big Muscle
Experienced Solver
Experienced Solver
Posts: 82
Joined: Sun Jun 27, 2010 6:18 pm

Re: How to use wxDCOverlay?

Post by Big Muscle » Wed Jan 15, 2020 9:32 am

New Pagodi: yes, this seems the classical approach and I probably stay with it, it also allows me to cache separate bitmap for more plots and then just compose them when needed. Although the usage seems much more complicated, e.g. how to plot the lines into the transparent wxMemoryDC so I can compose them directly?

Code: Select all

	wxMemoryDC dc;
	dc.SelectObject(m_bmpCachedPlot); // m_bmpCachedPlot is 32-bit bitmap

	wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
	if (gc) {
		// this does nothing !!! why it does not clear the background???
		gc->ClearRectangle(rc.GetLeft(), rc.GetTop(), rc.GetWidth(), rc.GetHeight()); 
		...
		delete gc;
	}
I just wanted to know whether there is some native way in wxWidgets and I found wxDCOverlay. Sample works correctly, but my code does not although I just copied it from the sample and there is not much information in the documentation. I don't understand why wxEVT_PAINT handler is invoked in my application but it is not in the sample. Also, it seems weird that using wxDCOverlay behaves exactly in the same way like when it is not used, i.e.:

Code: Select all

void CAnalysisPanel::SetCurrentFrame(unsigned int i) {
        wxClientDC dc(m_pPlot) ; // m_pPlot is wxPanel
        wxDCOverlay overlaydc( m_overlay, &dc );
        overlaydc.Clear();
        dc.DrawLine(...);	// code to draw the pointer
}
behaves exactly like

Code: Select all

void CAnalysisPanel::SetCurrentFrame(unsigned int i) {
        wxClientDC dc(m_pPlot) ; // m_pPlot is wxPanel
        dc.DrawLine(...);	// code to draw the pointer
}
When drawing anything in "dc" then wxEVT_PAINT is invoked.

New Pagodi
Super wx Problem Solver
Super wx Problem Solver
Posts: 347
Joined: Tue Jun 20, 2006 6:47 pm
Contact:

Re: How to use wxDCOverlay?

Post by New Pagodi » Wed Jan 15, 2020 11:06 am

Big Muscle wrote:
Wed Jan 15, 2020 9:32 am
New Pagodi: yes, this seems the classical approach and I probably stay with it, it also allows me to cache separate bitmap for more plots and then just compose them when needed. Although the usage seems much more complicated, e.g. how to plot the lines into the transparent wxMemoryDC so I can compose them directly?

Code: Select all

	wxMemoryDC dc;
	dc.SelectObject(m_bmpCachedPlot); // m_bmpCachedPlot is 32-bit bitmap

	wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
	if (gc) {
		// this does nothing !!! why it does not clear the background???
		gc->ClearRectangle(rc.GetLeft(), rc.GetTop(), rc.GetWidth(), rc.GetHeight()); 
		...
		delete gc;
	}
Do you mean "gc->DrawRectangle"? I don't think there is a method named "ClearRectangle" in the graphics context class. If you do mean DrawRectangle, you need to set a pen first. ie something like:

Code: Select all

	if (gc) {
		gc->SetPen(*wxBLACK_PEN);
		gc->DrawRectangle(rc.GetLeft(), rc.GetTop(), rc.GetWidth(), rc.GetHeight()); 

Big Muscle
Experienced Solver
Experienced Solver
Posts: 82
Joined: Sun Jun 27, 2010 6:18 pm

Re: How to use wxDCOverlay?

Post by Big Muscle » Wed Jan 15, 2020 8:10 pm

There is ClearRectangle function in wxGraphicsContext but its implementation is empty so it does nothing. DrawRectangle is not suitable because it will draw rectangle together with the existing content.

My current multi-platform (GTK + MSW) solution that seems to be working is to re-create wxImage every time the plot needs to be repainted:

Code: Select all

wxImage img(rc.GetSize());
img.SetMaskColour(0, 0, 0);	// InitAlpha() initializes to opaque when mask colour is not set
img.InitAlpha();	// initializes image to be fully transparent

m_bmpCachedPlot = img;

wxMemoryDC dc;
dc.SelectObject(m_bmpCachedPlot);

wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
if (gc) {
	...
It is not cool, re-creating m_bmpCachedPlot only when window size changes would be better, but I have not found any other way to clear wxGraphicsContext with transparent background before painting.

New Pagodi
Super wx Problem Solver
Super wx Problem Solver
Posts: 347
Joined: Tue Jun 20, 2006 6:47 pm
Contact:

Re: How to use wxDCOverlay?

Post by New Pagodi » Wed Jan 15, 2020 10:57 pm

If you want to draw with transparency with wxGraphicsContext while caching the result in a bitmap, I would try adding a function like this:

Code: Select all

void CAnalysisPanel::BuildPlot(int width, int height)
{
    wxImage img(width,height);

    // Create an alpha channel and set all pixels transparent.
    img.InitAlpha();
    memset(img.GetAlpha(), 0, width * height);

    // Create a graphics context from the image.
    wxGraphicsContext* gc = wxGraphicsContext::Create(img);

    if ( gc )
    {
        gc->SetPen(*wxBLACK_PEN);
        gc->StrokeLine(0,height-6,width-1,height-6);
        gc->StrokeLine(5,0,5,height-1);
        gc->StrokeLine(0,5,5,0);
        gc->StrokeLine(5,0,10,5);
        gc->StrokeLine(width-6,height-11,width-1,height-6);
        gc->StrokeLine(width-1,height-6,width-6,height-1);

        delete gc;
    }

    // Convert the image to a bitmap for drawing.
    m_bmpCachedPlot = wxBitmap(img);
}

Big Muscle
Experienced Solver
Experienced Solver
Posts: 82
Joined: Sun Jun 27, 2010 6:18 pm

Re: How to use wxDCOverlay?

Post by Big Muscle » Thu Jan 16, 2020 11:07 am

Thank you, it works!

I just modified it a but not to recreate wxImage everytime, wxBitmap m_bmpCachedPlot replaced with wxImage m_imgCachedPlot:

Code: Select all

	if (!m_imgCachedPlot.IsOk() || rc.GetSize() != m_imgCachedPlot.GetSize()) {
		m_imgCachedPlot.Create(rc.GetSize());
		m_imgCachedPlot.InitAlpha();
	}

	memset(m_imgCachedPlot.GetAlpha(), 0, rc.GetWidth() * rc.GetHeight());
	wxGraphicsContext* gc = wxGraphicsContext::Create(img);
	...
And in wxEVT_PAINT handler:

Code: Select all

	gc->DrawBitmap(gc->CreateBitmapFromImage(m_imgCachedPlot), 0, 0, rc.GetWidth(), rc.GetHeight());
I guess there is no way to use wxGraphicsBitmap directly to avoid image data copying as much as possible.

Post Reply