Trouble with painting on scrolled window Topic is solved

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
ZuLuuuuuu
Earned a small fee
Earned a small fee
Posts: 14
Joined: Sun Jun 21, 2009 8:48 am

Trouble with painting on scrolled window

Post by ZuLuuuuuu »

Hello,

I'm trying to create some kind of canvas for my project but got a behaviour that I cannot tell why, so I decided to prepare a minimal test case with the advise of a user at the IRC.

Here, I create a scrolled window inside a wxFrame. I try to draw some lines on it which should appear at exactly the same point even when the user is scrolling. But the case is that drawing messes up when scrolling, much like when you don't use DoPrepareDC().

Here is the -1 file- code:

Code: Select all

// main.cpp

#include <cstdio>

#include <wx/wx.h>

// Headers

class MyApp: public wxApp
{
  public:
    virtual bool OnInit();
};

class GUI: public wxFrame
{
  public:
    GUI();
};

class Canvas: public wxScrolledWindow
{
  public:
    Canvas(wxFrame *parent);
    void OnPaint(wxPaintEvent& paint_event);
    void OnSize(wxSizeEvent& size_event);
    
  private:
    DECLARE_EVENT_TABLE()
    
    int default_unit_width;
};

// Definitions

IMPLEMENT_APP(MyApp)

BEGIN_EVENT_TABLE(Canvas, wxScrolledWindow)
    EVT_PAINT(Canvas::OnPaint)
    EVT_SIZE(Canvas::OnSize)
END_EVENT_TABLE()

Canvas::Canvas(wxFrame *parent): wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
    this->default_unit_width = 10;
    
    SetScrollbars(this->default_unit_width, this->default_unit_width, 1000, 1000);
}

void Canvas::OnPaint(wxPaintEvent& paint_event)
{
    wxPaintDC canvas(this);
    DoPrepareDC(canvas);

    int x = 0;
    int y = 0;
    
    this->GetViewStart(&x, &y);
    
    printf("%d\n", x);
    
    canvas.DrawLine(x * this->default_unit_width + 100, 30, x * this->default_unit_width + 200, 70);
    canvas.DrawLine(x * this->default_unit_width + 500, 100, x * this->default_unit_width + 300, 200);
}

void Canvas::OnSize(wxSizeEvent& size_event)
{
    Refresh();
}

GUI::GUI(): wxFrame(NULL, wxID_ANY, wxT("Example"), wxDefaultPosition, wxSize(500, 350))
{
    wxBoxSizer *box_sizer_main = new wxBoxSizer(wxHORIZONTAL);
    
    Canvas *canvas_main = new Canvas(this);
    
    box_sizer_main->Add(canvas_main, 1, wxEXPAND);

    SetSizer(box_sizer_main);
    
    Centre();
}

bool MyApp::OnInit()
{
    GUI *gui = new GUI();
    
    gui->Show(true);

    return true;
}
You can compile it via "g++ main.cpp `wx-config --libs` `wx-config --cxxflags` -o main" or whatever method you use.

The interesting thing is that when I comment out the this->GetViewStart(&x, &y); line it works right but of course then it does not draw the lines at the same place when the user scrolls.

I put printf("%d\n", x); line so that you can see the values of x or y variables, which seem fine to me.

Another observation: After it messes up the drawings, if you resize the window, you see that they are drawn correctly.

I am on Ubuntu 9.04 with wxWidgets 2.8.9.1.

I'm new to both wxWidgets and C++ so maybe it might be pretty easy to solve, sorry if it is a very easy problem :)
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

The wxScrolledWindow docs seem to say that DoPrepareDC sets the offset of your DC for you. So you may not need to manually do +x and +y
"Keyboard not detected. Press F1 to continue"
-- Windows
ZuLuuuuuu
Earned a small fee
Earned a small fee
Posts: 14
Joined: Sun Jun 21, 2009 8:48 am

Post by ZuLuuuuuu »

Auria wrote:The wxScrolledWindow docs seem to say that DoPrepareDC sets the offset of your DC for you. So you may not need to manually do +x and +y
Hi Auria,

I want the shapes to stay on the visible area, like they are not moving.

I have some shapes which move when the user scrolls and got some other shapes which I want to stay still even when the user is scrolling. The first part is easy because DoPrepareDC arranges the origin for me like you said but I need x and y to make some shapes stay still and couldn't achive that.
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

Your sample works just fine for me (on wxMac 2.8.10) when scrolling horizontally (lines do scroll vertically but from your code I think it's expected)
"Keyboard not detected. Press F1 to continue"
-- Windows
ZuLuuuuuu
Earned a small fee
Earned a small fee
Posts: 14
Joined: Sun Jun 21, 2009 8:48 am

Post by ZuLuuuuuu »

Hmmm, I just tested it with my Windows installation (2.8.10), but it doesn't work there, either. The look after a little scrolling is at the attachement.

I'll try it with another version of wxWidgets...
Attachments
Untitled 1.png
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

Ah, maybe your system is trying to optimize some drawing by only asking you to redraw new parts. Maybe you could try marking the full area as dirty (never did this before but that could perhaps help)
"Keyboard not detected. Press F1 to continue"
-- Windows
ZuLuuuuuu
Earned a small fee
Earned a small fee
Posts: 14
Joined: Sun Jun 21, 2009 8:48 am

Post by ZuLuuuuuu »

How can I mark an area to be redrawn?

I tried inserting these lines to the OnPaint event:

Code: Select all

this->Refresh();
this->Update();
Now it draws correctly as I scroll (with a little flicker) but this time the program does not close when I click X on upper right. Also I didn't understand why it cannot calculate correctly which area should be redrawn :(
Auria
Site Admin
Site Admin
Posts: 6695
Joined: Thu Sep 28, 2006 12:23 am
Contact:

Post by Auria »

flicker : try using buffered DCs, and look at the wiki for more tips (bottom of article http://wiki.wxwidgets.org/Drawing_on_a_panel_with_a_DC)

About closing, I honestly don't see how it could be related to drawing
Also I didn't understand why it cannot calculate correctly which area should be redrawn
Because when you use a scrolled panel, wx and/or your system expect that drawings in it will scroll; it can't tell you want some lines not to scroll.
"Keyboard not detected. Press F1 to continue"
-- Windows
leiradella
I live to help wx-kind
I live to help wx-kind
Posts: 172
Joined: Sun Sep 07, 2008 9:49 pm
Location: Rio de Janeiro, Brazil

Post by leiradella »

I didn't try it, but perhaps you could draw the shapes that don't scroll in a wxClientDC?

Maybe a wxPaintDC will do it if you draw these shapes before you call DoPrepareDC?

Cheers,

Andre
ZuLuuuuuu
Earned a small fee
Earned a small fee
Posts: 14
Joined: Sun Jun 21, 2009 8:48 am

Post by ZuLuuuuuu »

leiradella wrote:Maybe a wxPaintDC will do it if you draw these shapes before you call DoPrepareDC?
Unfortunately, that didn't work, either.
ZuLuuuuuu
Earned a small fee
Earned a small fee
Posts: 14
Joined: Sun Jun 21, 2009 8:48 am

Post by ZuLuuuuuu »

I have some news, I now understand why it is not working and why it is working when I use Refresh() and why it is not closing when I use Refresh().

The first diagnosis Auria said was true: "maybe your system is trying to optimize some drawing by only asking you to redraw new parts."

The old shape is not erased while I scroll to the right because it does not mark the middle of the canvas as invalid so it does not redraw there to put the background color. The shape is completely erased when I scroll to the left because there is no shape there anymore and the newly drawn area is empty.

It is working well when I do Refresh() because it makes the whole area as invalid and enforces ALL of the background to be refilled before the shapes are drawn.

But when I put Refresh() there occurs an infinite loop: Refresh() calls OnPaint(), OnPaint() calls Refresh() so the application gets stuck and I cannot close it.

The thing is I couldn't find a way to prevent this recursion yet :) Any suggestions?
leiradella
I live to help wx-kind
I live to help wx-kind
Posts: 172
Joined: Sun Sep 07, 2008 9:49 pm
Location: Rio de Janeiro, Brazil

Post by leiradella »

Well, take Refresh out of OnPaint, and put it in an event handler that is called when you use the scroll bars to scroll the canvas.

This way Refresh will only be called when needed.

Cheers,

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

Post by Auria »

Maybe try calling Refresh from the scroll event (though that may still make two paint events; maybe not so bad).

For such cases, maybe you can just use a non-scrolling panel surrounded by scrollbar objects, and manage the scroll effect yourself (my app - ariamaestosa.sf.net - uses a scroll area with some areas that "float over"; I did it this way and it works well)
"Keyboard not detected. Press F1 to continue"
-- Windows
ZuLuuuuuu
Earned a small fee
Earned a small fee
Posts: 14
Joined: Sun Jun 21, 2009 8:48 am

Post by ZuLuuuuuu »

Hmmm, I created an OnScroll method and bind it to the canvas with EVT_SCROLLWIN(Canvas::OnScroll), though this time the scrolled window does not scroll at all although I scroll the scrollbars. I guess, I've overridden the default behaviour.
ZuLuuuuuu
Earned a small fee
Earned a small fee
Posts: 14
Joined: Sun Jun 21, 2009 8:48 am

Post by ZuLuuuuuu »

Ok, finally I found the solution for overriding problem, too. Looking at the scrolwin.cpp I saw that it calls a method named HandleOnScroll() whenever a scroll event happens, so I called it, too on my method. Here is the final code (I draw 3 lines, 2 of them is standing and one of them is scrolled):

Code: Select all

// main.cpp

#include <cstdio>

#include <wx/wx.h>

// Headers

class MyApp: public wxApp
{
  public:
    virtual bool OnInit();
};

class GUI: public wxFrame
{
  public:
    GUI();
};

class Canvas: public wxScrolledWindow
{
  public:
    Canvas(wxFrame *parent);
    void OnPaint(wxPaintEvent& paint_event);
    void OnSize(wxSizeEvent& size_event);
    void OnScroll(wxScrollWinEvent& scroll_event);
   
  private:
    DECLARE_EVENT_TABLE()
   
    int default_unit_width;
};

// Definitions

IMPLEMENT_APP(MyApp)

BEGIN_EVENT_TABLE(Canvas, wxScrolledWindow)
    EVT_PAINT(Canvas::OnPaint)
    EVT_SIZE(Canvas::OnSize)
    EVT_SCROLLWIN(Canvas::OnScroll)
END_EVENT_TABLE()

Canvas::Canvas(wxFrame *parent): wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
    this->default_unit_width = 10;
   
    SetScrollbars(this->default_unit_width, this->default_unit_width, 1000, 1000);
}

void Canvas::OnPaint(wxPaintEvent& paint_event)
{
    wxPaintDC canvas(this);
    DoPrepareDC(canvas);

    int x = 0;
    int y = 0;
   
    this->GetViewStart(&x, &y);
   
    printf("%d\n", x);
   
    canvas.DrawLine(10, 30, 20, 70);
    canvas.DrawLine(x * this->default_unit_width + 100, 30, x * this->default_unit_width + 200, 70);
    canvas.DrawLine(x * this->default_unit_width + 500, 100, x * this->default_unit_width + 300, 200);
}

void Canvas::OnSize(wxSizeEvent& size_event)
{
    Refresh();
}

void Canvas::OnScroll(wxScrollWinEvent& scroll_event)
{
    wxScrolledWindow::HandleOnScroll(scroll_event);
    
    Refresh();
}

GUI::GUI(): wxFrame(NULL, wxID_ANY, wxT("Example"), wxDefaultPosition, wxSize(500, 350))
{
    wxBoxSizer *box_sizer_main = new wxBoxSizer(wxHORIZONTAL);
   
    Canvas *canvas_main = new Canvas(this);
   
    box_sizer_main->Add(canvas_main, 1, wxEXPAND);

    SetSizer(box_sizer_main);
   
    Centre();
}

bool MyApp::OnInit()
{
    GUI *gui = new GUI();
   
    gui->Show(true);

    return true;
}
 
Is there an easier method to fix this overriding problem than looking at the source code of wxWidgets? I couldn't see any warning, for example, saying that HandleOnScroll() is called by default when scrolling event occurs... In this case, it was pretty apparent that it wasn't doing the job of scrolling, so I was able to guess I've caused the default method not to be called but there may be cases that slightly incorrect behaviour might occur by overriding the default method and I want to avoid that.
Post Reply