Page 1 of 2

OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Sun Jan 15, 2012 8:12 am
by tuli
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?

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Sun Jan 15, 2012 10:19 am
by DavidHart
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

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Sun Jan 15, 2012 12:45 pm
by tuli
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);
}

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Mon Jan 16, 2012 7:16 am
by DerKleineNik
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

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Mon Jan 16, 2012 3:44 pm
by tuli
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?

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Mon Jan 16, 2012 7:31 pm
by tuli
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;
}

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Tue Jan 17, 2012 12:08 am
by Auria
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

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Tue Jan 17, 2012 12:42 am
by doublemax
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.

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Tue Jan 17, 2012 8:46 pm
by tuli
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

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Tue Jan 17, 2012 9:26 pm
by doublemax
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.

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Wed Jan 18, 2012 2:43 pm
by tuli
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()

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Thu Jan 19, 2012 3:26 pm
by doublemax
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.

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Thu Jan 19, 2012 3:54 pm
by doublemax
Also, when you're using a fixed-width font, you could use wxDC::DrawText instead of DrawLabel(). DrawText is probably faster.

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Thu Jan 19, 2012 5:30 pm
by tuli
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?

Re: OnPaint() of wxScrolledWindow in custom wxFrame

Posted: Thu Jan 19, 2012 5:42 pm
by doublemax
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.