OnPaint() of wxScrolledWindow in custom wxFrame

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.
tuli
Knows some wx things
Knows some wx things
Posts: 42
Joined: Sat Dec 03, 2011 3:56 pm

OnPaint() of wxScrolledWindow in custom wxFrame

Post by tuli » Sun Jan 15, 2012 8:12 am

I have a custom class derived from wxFrame, which has wxScrolledWindow member.
wxScrolledWindow is connected to a OnPaint() event handler:

Code: Select all

m_scrolledWindow1->Connect( wxEVT_PAINT, wxPaintEventHandler(xgui::dvPaint ), NULL, this );
The problem is, that i have to call DoPrepareDC() in the drawing event, but wxw wont let me: it`s a non-static member function.
Note that declare xgui::dvPaint() in my class derived from wxFrame.

Where else could i place it? Or do i have to derive from wxSW to use it?

DavidHart
Site Admin
Site Admin
Posts: 3881
Joined: Thu Jan 12, 2006 6:23 pm
Location: IoW, UK

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by DavidHart » Sun Jan 15, 2012 10:19 am

Hi,
The problem is, that i have to call DoPrepareDC() in the drawing event
(The event-handler, presumably...) You'll need a pointer to the instance; in this case presumably m_scrolledWindow1. So:
m_scrolledWindow1->DoPrepareDC(...);

But...
Or do i have to derive from wxSW to use it?
If you're doing something non-trivial, subclassing is the best solution, both for simplicity and encapsulation. IMO that's especially true when the alternative is to cram lots of stuff into the frame class.

Regards,

David

tuli
Knows some wx things
Knows some wx things
Posts: 42
Joined: Sat Dec 03, 2011 3:56 pm

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by tuli » Sun Jan 15, 2012 12:45 pm

yes, that was it. Thanks.

Now there is a flickering issue. :(
I am trying to draw a white rectangle and a circle, the rectangle should always color the entire visible background white, and the circle should always be in the upper-left corner, regardless of where the scrollbar is set.
This works fine, but when i scroll the window is not refreshed correctly (see pic). I have to min/max it manually to force a refresh, and then it works.

Calling refresh() in the paint() event handler causes permanent flickering and 100% cpu usage.

Interestingly, this is not the case in the scrolled-example that ships with wxw. The example is kinda complex, though, and i wasnt able to find out why...


code:

Code: Select all

void dbgui1dbgui1::dvPaint( wxPaintEvent& event )
{
	wxBufferedPaintDC dc(m_scrolledWindow1);
	const int lineh = 16;

	m_scrolledWindow1->DoPrepareDC(dc);

	dc.SetBrush(*wxWHITE_BRUSH);
	dc.SetPen(*wxBLACK_PEN);


	wxSize clsz = m_scrolledWindow1->GetClientSize();
	wxPoint views = m_scrolledWindow1->GetViewStart();


	dc.DrawRectangle(views.x *lineh, views.y *lineh, clsz.x, clsz.y);
	dc.DrawCircle(views.x *lineh, views.y *lineh, 20);
}
Attachments
refresh.PNG
refresh.PNG (12.76 KiB) Viewed 4192 times

DerKleineNik
Knows some wx things
Knows some wx things
Posts: 29
Joined: Fri Sep 09, 2011 9:59 am

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by DerKleineNik » Mon Jan 16, 2012 7:16 am

Calling Refresh() in the OnPaint means you have programmed a loop as Refresh() calles again the OnPaint event, in the OnPaint there is calles Refresh again etc.

But in the code snippet you provided there is no Refresh() called in your paint event handler.

If you got the flickering problem there are several solutions: http://wiki.wxwidgets.org/Flicker-Free_Drawing

Or you can just try to set Freeze() before your repainting actions and Thaw() afterwards, for example if you have many child windows and in everyone is repainted or resized something

tuli
Knows some wx things
Knows some wx things
Posts: 42
Joined: Sat Dec 03, 2011 3:56 pm

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by tuli » Mon Jan 16, 2012 3:44 pm

well, i wouldnt say it`s a flickering problem. More a when-to-refresh problem.

I just called Refresh() in a click event, which clears and repaints the screen as expected once i click.
Am i supposed to catch the scroll event and call refresh in its Handler? Is this the "official" way to do this?

tuli
Knows some wx things
Knows some wx things
Posts: 42
Joined: Sat Dec 03, 2011 3:56 pm

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by tuli » Mon Jan 16, 2012 7:31 pm

please, i need some advice... :)

i derived from a wxScrolledWindow, here`s my new full code, whcih yields the same results as in the previously posted picture:

myscrolwin.h

Code: Select all

class myscrolwin :
	public wxScrolledWindow
{
public:
	myscrolwin(void);
	~myscrolwin(void);
	myscrolwin(wxWindow *parent,
		wxWindowID winid = wxID_ANY,
		const wxPoint& pos = wxDefaultPosition,
		const wxSize& size = wxDefaultSize,
		long style = wxScrolledWindowStyle,
		const wxString& name = wxPanelNameStr);

	void OnPaint(wxPaintEvent& event);
	void OnScroll(wxScrollEvent& event);
	void OnEraseBackground(wxEraseEvent& event);


private:
	//DECLARE_EVENT_TABLE()
};
myscrolwin.cpp

Code: Select all

#include "myscrolwin.h"

myscrolwin::myscrolwin(void)
{
}

myscrolwin::~myscrolwin(void)
{
}

myscrolwin::myscrolwin(wxWindow *parent,
				 wxWindowID winid ,
				 const wxPoint& pos ,
				 const wxSize& size ,
				 long style ,
				 const wxString& name )
				 :wxScrolledWindow(parent,
				  winid,
				  pos ,
				 size ,
				  style,
				 name )
{

	SetScrollbars(16,16,1,1000);
	this->Connect(wxEVT_PAINT, wxPaintEventHandler(myscrolwin::OnPaint),0,this);
	this->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(myscrolwin::OnScroll),0,this);
	this->Connect( wxEVT_ERASE_BACKGROUND, wxEraseEventHandler( myscrolwin::OnEraseBackground ), NULL, this );

}

void myscrolwin::OnPaint(wxPaintEvent& event)
{
	wxBufferedPaintDC dc(this);
	const int lineh = 16;

	DoPrepareDC(dc);



	dc.SetBrush(*wxWHITE_BRUSH);
	dc.SetPen(*wxBLACK_PEN);


	wxSize clsz = GetClientSize();
	wxPoint views = GetViewStart();
	wxSize vsz = GetVirtualSize();


	dc.DrawRectangle(views.x *lineh, views.y *lineh, clsz.x, clsz.y);
	dc.DrawCircle(views.x *lineh, views.y *lineh, 20);
}

void myscrolwin::OnScroll(wxScrollEvent& event)
{
//doesnt work as of now, is this the way to go?
	Refresh();
}

void myscrolwin::OnEraseBackground(wxEraseEvent& event)
{
	wxClientDC dc(this);
	dc.SetBackground(*wxWHITE_BRUSH);
	dc.Clear();
}

//BEGIN_EVENT_TABLE(myscrolwin, wxScrolledWindow)
//EVT_SCROLLWIN(myscrolwin::OnScroll)
//END_EVENT_TABLE()
the custom wxSW is then shown on a Frame:


main.cpp

Code: Select all

bool MyApp::OnInit()
{
	wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
	wxFrame* frame = new wxFrame(NULL, wxID_ANY, wxT("Hello wxDC"), wxPoint(50,50), wxSize(800,600));

	myscrolwin* msw = new myscrolwin(frame,wxID_ANY,wxPoint(0,0),wxSize(100,200));

	sizer->Add(msw,1,wxEXPAND|wxALL,5);

	frame->SetSizer(sizer);
	frame->Layout();
	frame->Show();
	return true;
}

Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by Auria » Tue Jan 17, 2012 12:08 am

It looks like you want some portion of the drawing to stay static no matter how much you scroll. As far as I know Windows tries to optimize scrolling by scrolling what had been already drawn and to ask you to draw only the part remaining.

Maybe you could just use a plain old wxPanel surrounded by plain wxScrollBars? You'd need to manually fake scrolling but you'd have full control
"Keyboard not detected. Press F1 to continue"
-- Windows

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

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by doublemax » Tue Jan 17, 2012 12:42 am

Am i supposed to catch the scroll event and call refresh in its Handler? Is this the "official" way to do this?
Yes, usually when scrolling, a part of the window content will be copied by the OS and you only receive a paint event for a small strip that needs a redraw. If you want some stationary content that's independent of the scroll position, this doesn't work. In this case you have to force a full refresh on each scroll event. (It's possible that setting the wxFULL_REPAINT_ON_RESIZE window style flag works here, too. But i'm not sure about this). You should call event.Skip() inside the event handler, so that the default scroll handling is executed.

To reduce flickering, leave the OnEraseBackground handler empty (*no* event.skip() inside) and clear the background in the paint event handler.
Use the source, Luke!

tuli
Knows some wx things
Knows some wx things
Posts: 42
Joined: Sat Dec 03, 2011 3:56 pm

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by tuli » Tue Jan 17, 2012 8:46 pm

let`s forget about the static drawing, it`s not the best example, i guess =P~

Let`s instead say i want to display the text of a large text file, line by line, and only draw the lines that are actually to be displayed.
So if i scroll up by, say, 3 lines, the event paramter tells me to just repaint the lowest 3 lines, as the rest is shited by the OS. Am i supposed to calculate how many lines are missing based on the scollbar position and ClientSize() of the scrolledwindow?
More importantly: shouldnt a wxBufferedPaintDC take care of this for me?



this is what i am working with right now. It`s supposed to print lines onto the scrollledwindow, depending on your scoll position. Needless to say it doesnt work and draw over itself...
This is now using an EvventTable, The paint-code is taken directly from the wxw example.
Calling Refresh() or Update() in the EVT_WINSCROLL handler causes flickering.

Code: Select all


myscrolwin::myscrolwin(wxWindow *parent,
				 wxWindowID winid ,
				 const wxPoint& pos ,
				 const wxSize& size ,
				 long style ,
				 const wxString& name )
				 :wxScrolledWindow(parent,
				  winid,
				  pos ,
				 size ,
				  style,
				 name )
{

	SetScrollbars(16,16,100,100);
	//this->Connect(wxEVT_PAINT, wxPaintEventHandler(myscrolwin::OnPaint),0,this);
	//this->Connect(wxEVT_SCROLL_CHANGED, wxScrollEventHandler(myscrolwin::OnScroll),0,this);
	//this->Connect( wxEVT_ERASE_BACKGROUND, wxEraseEventHandler( myscrolwin::OnEraseBackground ), NULL, this );

}

void myscrolwin::OnPaint(wxPaintEvent& event)
{
	wxBufferedPaintDC dc(this);
	const int lineh = 16;


	DoPrepareDC(dc);


	dc.SetBrush(*wxWHITE_BRUSH);
	dc.SetPen(*wxBLACK_PEN);


	wxSize clsz = GetClientSize();
	wxPoint views = GetViewStart();
	wxSize vsz = GetVirtualSize();



	//taken from the example
	int m_nLines = 100;
	int m_hLine = lineh;
	static size_t s_redrawCount = 0;
	dc.SetTextForeground(s_redrawCount++ % 2 ? *wxRED : *wxBLUE);

	// update region is always in device coords, translate to logical ones
	wxRect rectUpdate = GetUpdateRegion().GetBox();
	CalcUnscrolledPosition(rectUpdate.x, rectUpdate.y,
		&rectUpdate.x, &rectUpdate.y);

	size_t lineFrom = rectUpdate.y / m_hLine,
		lineTo = rectUpdate.GetBottom() / m_hLine;

	if ( lineTo > m_nLines - 1)
		lineTo = m_nLines - 1;

	int y = lineFrom*m_hLine;
	for ( size_t line = lineFrom; line <= lineTo; line++ )
	{
		int yPhys;
		CalcScrolledPosition(0, y, NULL, &yPhys);

		dc.DrawText(wxString::Format("Line %u (logical %d, physical %d)",
			unsigned(line), y, yPhys), 0, y);
		y += m_hLine;
	}
}

void myscrolwin::OnScroll(wxScrollWinEvent& event)
{
	//Refresh();//??? causes flickering and doesnt help
	//Update();//same
	event.Skip();
}

void myscrolwin::OnEraseBackground(wxEraseEvent& event)
{

}

BEGIN_EVENT_TABLE(myscrolwin, wxScrolledWindow)
	EVT_SCROLLWIN(myscrolwin::OnScroll)
	EVT_PAINT(myscrolwin::OnPaint)
	EVT_ERASE_BACKGROUND(myscrolwin::OnEraseBackground)
END_EVENT_TABLE()


p.s. i cant make too much of the exe scroll-example. Maybe one class using some of its features would be more helpful than more than 20 spread over 1500 lines of code. :o

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

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by doublemax » Tue Jan 17, 2012 9:26 pm

Without a stationary background, you don't need most of your code.

Check the "splitter" sample, which uses two wxScrolledWindows and is simpler:

Code: Select all

MyCanvas::MyCanvas(wxWindow* parent, bool mirror)
        : wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
                           wxHSCROLL | wxVSCROLL | wxNO_FULL_REPAINT_ON_RESIZE)
{
    m_mirror = mirror;
}

void MyCanvas::OnDraw(wxDC& dc)
{
	// the original code uses a wxMirrorDC, but commented out for simplicity
  // wxMirrorDC dc(dcOrig, m_mirror);

    dc.SetPen(*wxBLACK_PEN);
    dc.DrawLine(0, 0, 100, 200);

    dc.SetBackgroundMode(wxBRUSHSTYLE_TRANSPARENT);
    dc.DrawText(wxT("Testing"), 50, 50);

    dc.SetPen(*wxRED_PEN);
    dc.SetBrush(*wxGREEN_BRUSH);
    dc.DrawRectangle(120, 120, 100, 80);
}
From wxScrolledWindow docs:
You have the option of handling the OnPaint handler or overriding the OnDraw function, which is passed a pre-scrolled device context (prepared by DoPrepareDC).

If you don't wish to calculate your own scrolling, you must call DoPrepareDC when not drawing from within OnDraw, to set the device origin for the device context according to the current scroll position.
Summing up:
- you only need to call Refresh() on each scroll event if you want to force a redraw of the whole visible area
- wxScrolledWindow has a special feature, you don't have to catch the paint event, you can also override the virtual method wxScrolledWindow::OnDraw(). It gets a wxDC with pre-scrolled coordinates, so you don't need any additional coordinate conversion code.
Use the source, Luke!

tuli
Knows some wx things
Knows some wx things
Posts: 42
Joined: Sat Dec 03, 2011 3:56 pm

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by tuli » Wed Jan 18, 2012 2:43 pm

Thanks for the answer, spiltter seems to be an easier example indeed. :)

In the meantime, i crafted out a new try, this time a little hex-dispaly on a wxScrolledWindow. Only the part of the file that is actually displayed is read.
Three is, however, a wired problem:

if i click on the scrollbar and drag it around, it`s all good, but if i scroll down using the mouse-wheel, the window is not correctly redrawn - see attached pic.
Scrolling UP using the mouse-wheel works fine... :S

Additionally, this is using quiet some CPU power, probably because i am not handling the re-drawing in an efficient way.
When using windows GDI, i can call InvalidateRect() followed by UpdateWindow(), which re-draws the window very efficiently. Is there such a mechanism in wxw?

Code: Select all

#include "MyWindow.h"

#define MAX_LINES 100


MyWindow::MyWindow(const wxString& title)
: wxFrame(NULL, ID_TEST, title, wxDefaultPosition, wxSize(500, 500) )
{

	CreateStatusBar(1);


	MyScrolledWindow* wnd = new MyScrolledWindow(this, wxID_ANY, 0,0,-1,-1,wxVSCROLL|wxHORIZONTAL  |wxRETAINED		
		, 16, 16 , 500, 500, true);
}



void MyWindow::OnQuit(wxCommandEvent &event)
{
	Close(true);
}

#define offsz 75
#define datasz (16*16)
#define btnheigth 15

HANDLE hfile;
int row = 0;

MyScrolledWindow::MyScrolledWindow(wxWindow* parent, wxWindowID id,
								   int xpos, int ypos,
								   int width, int height,
								   long style, int pixperunitX, int pixperunitY,
								   int nounitsX, int nounitsY, bool refresh)
				:wxScrolledWindow(parent,id,wxPoint(xpos,ypos),wxSize(width, height), style)
{

	hfile = CreateFile(L"E:\\torr2\\crulesdec28.mp4", GENERIC_READ, 0, 0, OPEN_EXISTING, 0,0);

	int sz = GetFileSize(hfile,0) / 16;

	SetScrollbars(16,16,1,sz,0,0,true);

}

void MyScrolledWindow::OnBack(wxEraseEvent &event)
{
	return;
}

void MyScrolledWindow::OnPaint(wxPaintEvent &event)
{
	//wxPaintDC dc(this);
	wxBufferedPaintDC dc(this);


	//////////////////////////////////////////////////////////////////////////
	int gvx,gvy;
	GetViewStart(&gvx, &gvy);
	int ppux,ppuy;
	GetScrollPixelsPerUnit(&ppux,&ppuy);
	gvy *= ppuy;


	int offset = gvy;

	int clrx,clry;
	GetClientSize(&clrx, &clry);
	int linecount = clry /16;


	byte* buff = (byte*)malloc(linecount * 16);
	ZeroMemory(buff,linecount*16);

	DWORD rd;
	SetFilePointer(hfile,offset,0,FILE_BEGIN);
	ReadFile(hfile,buff,linecount*16,&rd,0);

	//////////////////////////////////////////////////////////////////////////

	wxPen pn = *wxBLACK_PEN;
	pn.SetWidth(0);
	pn.SetStyle(wxTRANSPARENT);

	const int lineh = 16;
	const int charw = 16;

	dc.SetPen(pn);
	dc.SetBrush(*wxWHITE_BRUSH);
	dc.DrawRectangle(0,0,clrx,clry);


	wxString f;

	dc.SetPen(*wxBLACK_PEN);


	for(int i=0; i<linecount; i++)
	{
			int idx = i*16 + 0;
			f.Printf("%.2hX %.2hX %.2hX %.2hX %.2hX %.2hX %.2hX %.2hX "
					"%.2hX %.2hX %.2hX %.2hX %.2hX %.2hX %.2hX %.2hX",
					buff[idx],buff[idx+1],buff[idx+2],buff[idx+3],buff[idx+4],buff[idx+5],buff[idx+6],buff[idx+7],
					buff[idx+8],buff[idx+9],buff[idx+10],buff[idx+11],buff[idx+12],buff[idx+13],buff[idx+14],buff[idx+15]);
			dc.DrawLabel(f,wxRect(0*charw+offsz,i*lineh,16*charw,lineh),wxALIGN_CENTER);
			f.Printf("%.8X",offset+i*16);
			dc.DrawLabel(f,wxRect(0,i*16,offsz,16),wxALIGN_CENTER);
	}

	free(buff);
}

void MyScrolledWindow::OnMouse(wxMouseEvent &event)
{

	//...

	//Refresh(true);
	event.Skip();
	
}

BEGIN_EVENT_TABLE(MyScrolledWindow, wxScrolledWindow)
	//EVT_SCROLLWIN(MyScrolledWindow::OnScroll)
	EVT_PAINT(MyScrolledWindow::OnPaint)
	EVT_ERASE_BACKGROUND(MyScrolledWindow::OnBack)
	//EVT_SIZING(MyScrolledWindow::OnSizeing)
	EVT_MOUSE_EVENTS(MyScrolledWindow::OnMouse)
END_EVENT_TABLE()
Attachments
wxw.PNG
wxw.PNG (14.87 KiB) Viewed 4128 times

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

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by doublemax » Thu Jan 19, 2012 3:26 pm

I think you're not drawing the last line.
Try this:

Code: Select all

// change:
 int linecount = clry / 16;
 // to:
 int linecount = (clry+15) / 16;
When using windows GDI, i can call InvalidateRect() followed by UpdateWindow(), which re-draws the window very efficiently. Is there such a mechanism in wxw?
For further optimization, check class wxRegionIterator.

Example usage is here:
http://docs.wxwidgets.org/stable/wx_wxp ... paintevent

You could use this to skip redraw of lines outside the "dirty" window area.
Use the source, Luke!

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

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by doublemax » Thu Jan 19, 2012 3:54 pm

Also, when you're using a fixed-width font, you could use wxDC::DrawText instead of DrawLabel(). DrawText is probably faster.
Use the source, Luke!

tuli
Knows some wx things
Knows some wx things
Posts: 42
Joined: Sat Dec 03, 2011 3:56 pm

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by tuli » Thu Jan 19, 2012 5:30 pm

thanks for the tip. :)

My first idea would be now to draw everything into a bitmap first, then loop through a wxRegionIterator and blit the parts that need to be updated to the screen.
However, i thought that this is precisely what a wxBufferedPaintDC does. But then I just read somewhere, that the buffered DC still draws the entire bitmap to the screen, just all at once (when it`s destroyed).
What`s correct?

I dont think the re-draw problem is the linecount, as it is drawn correctly when i force a refresh via min+max.

btw,

Code: Select all

the application must always create a wxPaintDC object, even if you do not use it
i`m assuming a DC derived from that, such as wxBufferedPaintDC is ok, too?

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

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Post by doublemax » Thu Jan 19, 2012 5:42 pm

My first idea would be now to draw everything into a bitmap first, then loop through a wxRegionIterator and blit the parts that need to be updated to the screen.
Why should that be faster?
that the buffered DC still draws the entire bitmap to the screen
Yes, but blitting is usually hardware-accelerated and therefore fast, this is not really a bottleneck.
I dont think the re-draw problem is the linecount, as it is drawn correctly when i force a refresh via min+max
Did you try it? If the whole window is redrawn, a few missing pixel-lines at the end are hardly noticable. But if the window is scrolled up and only the last few lines need a redraw, you can get pixel garbage.
i`m assuming a DC derived from that, such as wxBufferedPaintDC is ok, too?
Yes

In general, try to get a bug-free display first, then you can start optimizing.

I think caching the file content would be more important that trying to optimize the display. I'm almost certain that's already fast enough.
Use the source, Luke!

Post Reply