wxScrolledCanvas inside a sizer -- What am I doing wrong? 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
Schala
In need of some credit
In need of some credit
Posts: 9
Joined: Mon Jul 16, 2012 8:11 am

wxScrolledCanvas inside a sizer -- What am I doing wrong?

Post by Schala » Wed Jun 11, 2014 10:49 am

I'm trying to implement a chat program where a user can connect to a server and move around their avatar in a 2D environment. For this I've been trying to find a suitable widget to use as a canvas for a paint device context. I believe I found what I need with wxScrolledCanvas because it offers a scrollable area to paint on. However, no matter what I do, the sizer I've placed it in collapses/hides it, showing just the chat input textbox. Furthermore, the sizer layout doesn't seem obey my flag arguments for the chat input box: It's supposed to be at the bottom of the window frame, but instead it's right under the collapsed canvas with a large empty space below it.

Code: Select all

// main_win.hpp
#ifndef _MAIN_WIN_H
#define _MAIN_WIN_H

#include <wx/defs.h>
#include <wx/event.h>
#include <wx/frame.h>
#include <wx/panel.h>
#include <wx/scrolwin.h>

class MainWin final: public wxFrame {
public:
	MainWin(const wxString &title, const wxPoint &pos, const wxSize &size);
private:
	wxPanel *panel;
	wxScrolledCanvas *canvas;
	//void OnAbout(wxCommandEvent &event);
	void OnExit(wxCommandEvent &event);
	//void OnPreferences(wxCommandEvent &event);
	void OnPaint(wxPaintEvent &event);
	
	wxDECLARE_EVENT_TABLE();
};

#endif // _MAIN_WIN_H

// main_win.cpp

#include <wx/dcclient.h>
#include <wx/menu.h>
#include <wx/sizer.h>
#include <wx/textctrl.h>
#include "main_win.hpp"

wxBEGIN_EVENT_TABLE(MainWin, wxFrame)
	EVT_MENU(wxID_EXIT, MainWin::OnExit)
	EVT_PAINT(MainWin::OnPaint)
wxEND_EVENT_TABLE()

MainWin::MainWin(const wxString &title, const wxPoint &pos, const wxSize &size):
		wxFrame(nullptr, wxID_ANY, title, wxDefaultPosition, size) {
	wxMenu *file_menu = new wxMenu;
	file_menu->Append(wxID_EXIT);
	
	wxMenuBar *menubar = new wxMenuBar;
	menubar->Append(file_menu, "&File");
	SetMenuBar(menubar);
	
	wxToolBar *toolbar = CreateToolBar();
	
	panel = new wxPanel(this);
	wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
	canvas = new wxScrolledCanvas(panel);
	canvas->GetTargetWindow()->SetSize(800,600);
	//vbox->Add(canvas, 0, wxALL | wxEXPAND | wxRESERVE_SPACE_EVEN_IF_HIDDEN);
	wxTextCtrl *chatbox = new wxTextCtrl(panel, -1, wxEmptyString, wxDefaultPosition,
		wxDefaultSize, wxTE_PROCESS_ENTER);
	vbox->Add(chatbox, 0, wxBOTTOM | wxLEFT | wxRIGHT | wxEXPAND);
	panel->SetSizer(vbox);
	CreateStatusBar();
	SetStatusText("Not connected");
	Centre();
}

void MainWin::OnExit(wxCommandEvent &event) {
	Close(true);
}

void MainWin::OnPaint(wxPaintEvent &event) {
	wxPaintDC dc(canvas->GetTargetWindow());
	
	dc.SetBrush(wxBrush(wxColour("#ffffff")));
	dc.SetPen(wxPen(wxColour("#000000"), 1));
	dc.DrawRectangle(0, 0, 1276, 600);
}
The result should resemble this (except with a placeholder rectangle since I have yet to connect the GUI to the network code):
Image

Manolo
Ultimate wxWidgets Guru
Ultimate wxWidgets Guru
Posts: 696
Joined: Mon Apr 30, 2012 11:07 pm

Re: wxScrolledCanvas inside a sizer -- What am I doing wrong

Post by Manolo » Wed Jun 11, 2014 4:53 pm

The panel you use is the only child control of the frame. So it will always fill all the client area of the frame.
A sizer is not a rectangle inside a window. It is a size-manager, that handles the size of the window(s) it managers.
In a scrolled window there are two different sizes: the one available, scrolled area, virtual size; and the one currently displayed. A sizer will handle the virtual one.

Normally a sizer handles several windows, dealing with the sizes for each one. I don't get your idea of using a sizer with an unique window.

Schala
In need of some credit
In need of some credit
Posts: 9
Joined: Mon Jul 16, 2012 8:11 am

Re: wxScrolledCanvas inside a sizer -- What am I doing wrong

Post by Schala » Wed Jun 11, 2014 8:58 pm

Well, imagine the above screenshot but with a scrollable canvas. That's what I'm trying to accomplish. I tweaked the code to remove panel and reassign the other components parented to it to `this` instead, but no luck.

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

Re: wxScrolledCanvas inside a sizer -- What am I doing wrong

Post by doublemax » Wed Jun 11, 2014 9:24 pm

The original sizer code was correct except for one thing: The out-commented line that put the canvas into the sizer. That is needed.

There is another problem though: You're catching the paint event for the frame, but in the paint event handler you're drawing onto the canvas. That doesn't work. In a paint event handler you're only allowed to create a wxPaintDC for the window that "owns" the event.

Read the comments above the changes i made to the code.

Code: Select all

// needed for wxAutoBufferedPaintDC
#include "wx/dcbuffer.h"

// no entry for EVT_PAINT in static event table
wxBEGIN_EVENT_TABLE(MainWin, wxFrame)
    EVT_MENU(wxID_EXIT, MainWin::OnExit)
wxEND_EVENT_TABLE()

MainWin::MainWin(const wxString &title, const wxPoint &pos, const wxSize &size):
      wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, size)
{
  wxMenu *file_menu = new wxMenu;
  file_menu->Append(wxID_EXIT);
  
  wxMenuBar *menubar = new wxMenuBar;
  menubar->Append(file_menu, "&File");
  SetMenuBar(menubar);
  
  wxToolBar *toolbar = CreateToolBar();
  
  panel = new wxPanel(this);
  wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);

  canvas = new wxScrolledCanvas(panel);

  // this is needed for using wxAutoBufferedPaintDC in the paint event handler
  canvas->SetBackgroundStyle(wxBG_STYLE_PAINT);

  // set a bigger virtual size, so we can see scrollbars
  canvas->SetVirtualSize(-1, 3000);
  canvas->SetScrollRate(16,16);

  // connect the paint event handler from the panel to a method from MainWin
  canvas->Connect(wxEVT_PAINT, wxPaintEventHandler(MainWin::OnPaint), NULL, this);

  // add canvas with a proportion of "1", so it will occupy all remaining space
  vbox->Add(canvas, 1, wxALL | wxEXPAND | wxRESERVE_SPACE_EVEN_IF_HIDDEN);

  wxTextCtrl *chatbox = new wxTextCtrl(panel, -1, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
  vbox->Add(chatbox, 0, wxBOTTOM | wxLEFT | wxRIGHT | wxEXPAND);
  panel->SetSizer(vbox);
  panel->SetAutoLayout(true);
  CreateStatusBar();
  SetStatusText("Not connected");
  Centre();
}

void MainWin::OnExit(wxCommandEvent &event) {
	Close(true);
}

void MainWin::OnPaint(wxPaintEvent &event)
{
       // avoid flicker
	wxAutoBufferedPaintDC dc(canvas);

        // clear the whole virtual area
	dc.SetBrush(*wxWHITE_BRUSH);
	dc.SetPen(*wxWHITE_PEN);
	dc.DrawRectangle(0,0, canvas->GetVirtualSize().x, canvas->GetVirtualSize().y); 

        // this adjusts the coordinate system in case the window is scrolled
	canvas->DoPrepareDC(dc);

	dc.SetBrush(wxBrush(wxColour("#ffffff")));
	dc.SetPen(wxPen(wxColour("#000000"), 1));
	dc.DrawRectangle(0, 0, 1276, 600);
}
Use the source, Luke!

Schala
In need of some credit
In need of some credit
Posts: 9
Joined: Mon Jul 16, 2012 8:11 am

Re: wxScrolledCanvas inside a sizer -- What am I doing wrong

Post by Schala » Wed Jun 11, 2014 10:49 pm

Many thanks Max. I got the proportion right before seeing your post but you helped me with the flicker problem.

Post Reply