Search code examples
c++wxwidgets

Image corruption in wxScrolledWindow while scrolling


I want to view an image in one of wxSplitterWindow using wxScrolledWindow. I derived class from wxPanel, which I add into wxScrolledWindow. But the result is not what i want.(

class ImagePanel : public wxPanel
{
public:
    //ImagePanel(wxFrame* parent);
    ImagePanel(wxWindow* parent);
    void paintEvent(wxPaintEvent& event);
    void paintNow();

    void setBitmap(wxBitmap& imBitmap);
    void setBitmap(wxImage& img);

    int GetHeight();
    int GetWidth();

    void render(wxDC& dc);

    DECLARE_EVENT_TABLE()
private:
    wxBitmap curBitmap;
};

Paint event:

void ImagePanel::paintEvent(wxPaintEvent& event)
{
    wxBufferedPaintDC dc(this);
    render(dc);
}

This is how I have written the interface into main frame constructor:

topSizer = new wxBoxSizer(wxVERTICAL);

    topSplitter = new wxSplitterWindow(this,ID_TOPSPLITTER,wxDefaultPosition,wxDefaultSize,wxSP_LIVE_UPDATE|wxSP_3D);
    topSizer->Add(topSplitter,1,wxALL|wxEXPAND,0);

    imagePanel = new wxPanel(topSplitter,ID_IMAGEPANEL);
    imageSizer = new wxBoxSizer(wxVERTICAL);
    imagePanel->SetSizer(imageSizer);
    optionPanel = new wxPanel(topSplitter,ID_OPTIONPANEL);
    optionSizer = new wxBoxSizer(wxVERTICAL);
    optionPanel->SetSizer(optionSizer);
    topSplitter->SplitVertically(imagePanel, optionPanel);
    topSplitter->SetMinimumPaneSize(200);

    //===========================================
    //image panel interface
    imageScWindow=new wxScrolledWindow(imagePanel,ID_IMAGESCWINDOW,wxDefaultPosition,wxDefaultSize,
                                       wxVSCROLL|wxHSCROLL|wxALL|wxALWAYS_SHOW_SB|wxFULL_REPAINT_ON_RESIZE);
    imgArea = new ImagePanel(imageScWindow);
    imageSizer->Add(imageScWindow,1,wxEXPAND,0);

    scrollSizer = new wxBoxSizer(wxVERTICAL);
    imageScWindow->SetSizer(scrollSizer);
    scrollSizer->Add(imgArea,1,wxEXPAND,0);
    imageScWindow->SetTargetWindow(imgArea);

And this is the function that loads image:

void MainFrame::Setpic(wxCommandEvent& event)

{
    wxString path = GetCurrentWorkingDir()+wxT("\\")+tmptxt1->GetValue();
    wxImage img(path);
    wxBitmap imBitmap(img,-1);
    imgArea->setBitmap(imBitmap);
    imageScWindow->SetScrollbars(1,1,imgArea->GetWidth(),imgArea->GetHeight());
    imageScWindow->Refresh();
}

Thats what i get: https://i.sstatic.net/M0d7U.jpg

I have also tried to do like this in the constructor, without SetTargetArea:

//===========================================
    //image panel interface
    imageScWindow=new wxScrolledWindow(imagePanel,ID_IMAGESCWINDOW,wxDefaultPosition,wxDefaultSize,
                                       wxVSCROLL|wxHSCROLL|wxALL|wxALWAYS_SHOW_SB|wxFULL_REPAINT_ON_RESIZE);
    imgArea = new ImagePanel(imageScWindow);
    imageSizer->Add(imageScWindow,1,wxEXPAND,0);

    scrollSizer = new wxBoxSizer(wxVERTICAL);
    imageScWindow->SetSizer(scrollSizer);
    scrollSizer->Add(imgArea,1,wxEXPAND,0);

Then image shows properly, but if I change the frame size or move splitter, scrollbars disappear, and I have to set them again somwhere. Then i tried to do like this:

int prvx,prvy;
void ImagePanel::paintEvent(wxPaintEvent& event)
{
    reinterpret_cast<wxScrolledWindow*>(this->GetParent())->GetViewStart(&prvx,&prvy);
    reinterpret_cast<wxScrolledWindow*>(this->GetParent())->SetScrollbars(1,1,curBitmap.GetWidth(),curBitmap.GetHeight());
    reinterpret_cast<wxScrolledWindow*>(this->GetParent())->Scroll(wxPoint(prvx,prvy));
    wxBufferedPaintDC dc(this);
    render(dc);
}

It shows properly, but lags while scrolling, even without wxFULL_REPAINT_ON_RESIZE in wxScrolledWindow. The reason why it is in paint event: if I add this into OnSize event, program crashes. What to do?


Solution

  • I'm not sure where to begin answering your question. Here's a minimal sample of a frame that has a splitter with a bitmap on the left and a panel on the right. I hope you can adapt this sample to do what you need.

    // For compilers that support precompilation, includes "wx/wx.h".
    #include "wx/wxprec.h"
    
    #ifdef __BORLANDC__
        #pragma hdrstop
    #endif
    
    // for all others, include the necessary headers (this file is usually all you
    // need because it includes almost all "standard" wxWidgets headers)
    #ifndef WX_PRECOMP
        #include "wx/wx.h"
    #endif
    
    #include <wx/scrolwin.h>
    #include <wx/splitter.h>
    #include <wx/dcbuffer.h>
    
    class MyFrame : public wxFrame
    {
        public:
            MyFrame( wxWindow* parent, int id = wxID_ANY, wxString title = "Demo",
                     wxPoint pos = wxDefaultPosition, wxSize size = wxDefaultSize,
                     int style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
        private:
            void OnScrollPaint( wxPaintEvent& event );
    
            wxScrolledCanvas* m_canvas;
            wxBitmap m_bitmap;
    };
    
    MyFrame::MyFrame( wxWindow* parent, int id, wxString title, wxPoint pos,
                      wxSize size, int style )
            :wxFrame( parent, id, title, pos, size, style )
    {
        m_bitmap=wxBitmap ("test.png", wxBITMAP_TYPE_PNG   );
    
        wxSplitterWindow* m_splitter1
            = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition,
                                    wxDefaultSize, wxSP_LIVE_UPDATE );
    
        m_canvas = new wxScrolledCanvas( m_splitter1, wxID_ANY, wxDefaultPosition,
             wxDefaultSize, wxSTATIC_BORDER|wxHSCROLL|wxVSCROLL );
    
        m_canvas->SetScrollRate( 5, 5 );
        m_canvas->SetVirtualSize(m_bitmap.GetWidth(), m_bitmap.GetHeight());
        m_canvas->SetBackgroundStyle(wxBG_STYLE_PAINT);
        m_canvas->Bind( wxEVT_PAINT, &MyFrame::OnScrollPaint , this );
    
        wxPanel* m_panel2 = new wxPanel( m_splitter1, wxID_ANY, wxDefaultPosition,
            wxDefaultSize, wxSTATIC_BORDER|wxTAB_TRAVERSAL );
    
        m_splitter1->SplitVertically( m_canvas, m_panel2, GetSize().x/2 );
    }
    
    void MyFrame::OnScrollPaint( wxPaintEvent& event )
    {
        wxAutoBufferedPaintDC dc(m_canvas);
        m_canvas->DoPrepareDC(dc);
        dc.Clear();
        dc.DrawBitmap(m_bitmap,0,0);
    }
    
    class MyApp : public wxApp
    {
        public:
            virtual bool OnInit()
            {
                wxInitAllImageHandlers();
                MyFrame* frame = new MyFrame(NULL);
                frame->Show();
                return true;
            }
    };
    
    wxIMPLEMENT_APP(MyApp);
    

    To run this, be sure to change "test.png" in line 35 to the name of an actual image file on your computer (and change wxBITMAP_TYPE_PNG if the image file isn't a png).

    The important parts are:

    1. Set the background style of the canvas to wxBG_STYLE_PAINT as shown in line 46.
    2. In the paint handler, call the DoPrepareDC method on the paintdc.

    By the way, the paint handler shown in my sample isn't very good. It draws the entire bitmap every time. A better approach would be to get the update region and only redraw the parts that are needed, but I wanted to keep this simple.