I'm trying to get a scrolling panel where I can draw picture like on MS Paint
The problem is that, when I draw something to try the application:
I draw one rectangle (or more than one)
I scroll the panel to the right, the rectangles remain to the left
I scroll the panel to the left, the rectangles are disappeared
I'm a newbie in wx and I think I'm missing something important
Using wxWidget in Codelite (C++), Windows 7 32 bit
That's the code:
editorshp.h
class EditorShp : public wxFrame {
protected:
wxClientDC *dcPannelloDisegno;
wxScrolledWindow *scrollwin;
wxPanel *pannello;
public:
EditorShp( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = _("Editor SHP"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 800,500 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
void draw_temp(wxCommandEvent& WXUNUSED(event));
};
editorshp.cpp
PS: bDrawTemp is the button I use for drawing some rectangles (the first is at point 100,100; the second at 200,100 and so on)
EditorShp::EditorShp( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style ) {
scrollwin = new wxScrolledWindow(this, -1, wxPoint(0, 0), wxSize(700, 400));
scrollwin->SetScrollbars(1, 1, 1600, 1000, 0, 0);
pannello = new wxPanel(scrollwin, -1, wxPoint(0, 0), wxSize(700, 400), wxFULL_REPAINT_ON_RESIZE);
dcPannelloDisegno = new wxClientDC(pannello);
scrollwin->DoPrepareDC(*dcPannelloDisegno);
wxButton *bDrawTemp= new wxButton( this, wxID_ANY, _("Indietro"), wxPoint(0, 400), wxSize( 100, 24 ), 0 );
bDrawTemp->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(EditorShp::disegnaTemp), NULL, this);
scrollwin->Show(true);
this->Show(true);
}
void EditorShp::draw_temp(wxCommandEvent& WXUNUSED(event)){
static int x = 100, y = 100;
dcPannelloDisegno->DrawRectangle(wxPoint(x, y), wxSize(10, 10));
//pannello->Refresh(); it cancels everything
pannello->Update();
x += 100;
x = x % 1600;
}
EDIT 1: Sorry it's not wxScrollingPanel but wxScrolledWindow
First, catch wxEVT_PAINT and do the drawing in its hadler, using a wxPaintDC (or one of its variants - wx[Auto]BufferedPaintDC).
Second, you are now attempting to paint on pannello, and for that you do not need to prepare the wxDC. However, from your description you want to paint on scrollwin, and in that case you might not even need pannello.
So, for drawing on a scrolled window, see scroll sample that comes with wxW. What you probably need is the implementation of MySimpleCanvas (described as a scrolled window which draws a simple rectangle) - to see it press F1 when running the sample.
As a general rule, the samples should be the first place to search for a functionality; there are a lot of them exemplified there.
Related
I am creating an animation timeline widget using wxWidgets.
The widget consist of a wxFrame with a wxScrolledWindow child:
I want to support horizontal zooming by changing the timeline window virtual size when the user rolls mouse middle button.
Zooming without scrolling works well. But when i start scrolling(forward and backward) things get messed up.
So to troubleshoot I decided to do a simple test:
Draw a line from the window top left corner to the bottom right corner. And the test fails!
Without scrolling -> Test pass
Zoom then scroll to right -> Test fails
Scroll back to left -> Test also fails
I have no idea of whats going on. Here my CPP code:
In .H:
class AnimationFrame : public wxFrame
{
public:
AnimationFrame();
private:
void onTimelineMouseWheel(wxMouseEvent& ev);
void onTimelinePaint(wxPaintEvent& ev);
// The scrolled window.
wxScrolledWindow* m_timelineWindow;
};
In .CPP:
AnimationFrame::AnimationFrame()
{
assert(wxXmlResource::Get()->LoadFrame(this, NULL, "AnimationFrame"));
m_timelineWindow = (wxScrolledWindow*)wxWindow::FindWindowByName("TimelineScrolledWindow", this);
assert(m_timelineWindow);
wxPanel* headerPanel = (wxPanel*)wxWindow::FindWindowByName("HeaderPanel", this);
assert(headerPanel);
// Adjust panel sizes.
{
headerPanel->SetMaxSize(wxSize(headerPanel->GetMaxSize().x, 40));
m_timelineWindow->SetMaxSize(wxSize(m_timelineWindow->GetMaxSize().x, 100));
this->SetMinSize(wxSize(this->GetSize().x, 250));
this->SetSize(wxSize(this->GetSize().x, 250));
this->SetMaxSize(wxSize(this->GetSize().x, 250));
}
// Timeline events from the scrolled window.
m_timelineWindow->Bind(wxEVT_MOUSEWHEEL, &AnimationFrame::onTimelineMouseWheel, this);
m_timelineWindow->Bind(wxEVT_PAINT, std::bind(&AnimationFrame::onTimelinePaint, this, std::placeholders::_1));
}
void AnimationFrame::onTimelineMouseWheel(wxMouseEvent& ev)
{
static const nbFloat32 scrollIntensity = 1.20f;
const nbFloat32 delta = scrollIntensity * (nbFloat32)(ev.GetWheelRotation() / ev.GetWheelDelta());
const nbFloat32 sizeFactor = delta >= 0.0 ? delta : 1.0f / -delta;
const wxSize currentVirtualSize = m_timelineWindow->GetVirtualSize();
wxSize targetVirtualSize = wxSize((nbUint32)((nbFloat32)currentVirtualSize.x * sizeFactor), currentVirtualSize.y);
targetVirtualSize.x = std::min(targetVirtualSize.x, 5000);
targetVirtualSize.x = std::max(targetVirtualSize.x, m_timelineWindow->GetSize().x);
m_timelineWindow->SetVirtualSize(targetVirtualSize);
this->Refresh();
}
void AnimationFrame::onTimelinePaint(wxPaintEvent& ev)
{
// Init the DC.
wxPaintDC dc(m_timelineWindow);
PrepareDC(dc);// from the scrolling sample. Doesnt seems to do anything.
/*
wxFont font(12, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
dc.SetFont(font);
dc.SetBackgroundMode(wxTRANSPARENT);
dc.SetTextForeground(*wxBLACK);
dc.SetTextBackground(*wxWHITE);
*/
// Now draw line.
dc.DrawLine(wxPoint(0, 0), wxPoint(m_timelineWindow->GetSize().x, m_timelineWindow->GetSize().y));
}
Addional information:
As you certainly realised the widget is loaded from CPP using the XRC format. Here the generated code from wxFormBuilder for the widget.
XRC: https://1drv.ms/u/s!Ak_u4fVrMHMkrTe59Fuo4X2b-fRO?e=2Z0lw0
CPP: https://1drv.ms/u/s!Ak_u4fVrMHMkrTi_8w5UIkwFTl9t?e=BZBlLs
I'm new to wxWidgets since I never use GUI when programming in C++. Now I have to. I want to make the UI responsive using something like the DockStyle.Right/Bottom/Fill from WinForms. Since I have no code I tried before, the question is very short and not the best but I hope someone can help me.
EDIT:
That is what I currently have. When I uncomment SetMaxSize there is 30px space at the bottom but the panel (blue) is not visible:
namespace tms {
MainFrame::MainFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame(NULL, wxID_ANY, title, pos, size) {
this->InitialiseComponents();
}
void MainFrame::InitialiseComponents() {
this->panelChatLog = new wxPanel(this, wxID_ANY);
this->panelChatLog->SetBackgroundColour(wxColour(0, 255, 0));
this->panelChatInput = new wxPanel(this, wxID_ANY);
this->panelChatInput->SetBackgroundColour(wxColour(0, 0, 255));
this->panelChatInput->SetMinSize(wxSize(0, 30));
//this->panelChatInput->SetMaxSize(wxSize(0, 30));
this->sizerPanels = new wxBoxSizer(wxVERTICAL);
this->sizerPanels->Add(this->panelChatLog, 1, wxEXPAND | wxALL);
this->sizerPanels->Add(this->panelChatInput, 1, wxEXPAND | wxALL);
this->SetSizer(this->sizerPanels);
}
void MainFrame::OnExit(wxCommandEvent& event) {
this->Close(true);
}
}
Learning sizers can be a little tricky at first. But once you've absorbed the concept, they can be a very effective tool for laying out windows and controls for your application.
In the specific case above, when you uncomment the SetMaxSize line, you're setting both the min width and max width of the window to be 0 - which means the window has a width of 0. Instead you can use the special value of -1 which means the min width is and max width are unspecified. Consequently the sizer will pick the best width for the window:
this->panelChatInput->SetMinSize(wxSize(-1, 30));
this->panelChatInput->SetMaxSize(wxSize(-1, 30));
Alternately, you can use the proportion property when adding the lower panel to sizer. If you set the proportion to 0, the window will take it's minimum size (in the vertical direction). It will still expand in the horizontal direction because of the wxEXPAND flag is set.
...
this->panelChatInput->SetMinSize(wxSize(-1, 30));
this->sizerPanels = new wxBoxSizer(wxVERTICAL);
this->sizerPanels->Add(this->panelChatLog, 1, wxEXPAND | wxALL);
this->sizerPanels->Add(this->panelChatInput, 0, wxEXPAND | wxALL);
...
Either way, you'll get a layout that looks like this:
I think that's what you're trying to accomplish. Sorry if I'm misunderstanding.
Also as I mentioned above, visual tools like wxFormbuilder, wxCrafter, and wxGlade can be of help to see how all these numbers and properties translate to the actual layout of the windows in your application's forms. I know they really helped me.
how to show another frame after button click event?
like this
My code here show window OnInit. but what to do next?
I did not find how to do this. little experience with this.
I comment the window that should be.
enum
{
wxID_First_Load = 5000,
wxID_First_Frame,
wxID_First_Panel
};
class MyFrame : public wxFrame
{
public:
MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
void fileLoad(wxCommandEvent& event);
private:
int file_count = 0;
wxDECLARE_EVENT_TABLE();
};
wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_BUTTON(wxID_First_Load, MyFrame::fileLoad)
wxEND_EVENT_TABLE()
wxIMPLEMENT_APP(MyApp);
bool MyApp::OnInit()
{
MyFrame *frame = new MyFrame("Hello World", wxDefaultPosition, wxSize(450, 250));
frame->SetWindowStyle(wxCAPTION | wxSYSTEM_MENU );
frame->Show(true);
return true;
}
MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size)
: wxFrame(NULL, wxID_First_Frame, title, pos, size)
{
wxBoxSizer *first_sizer = new wxBoxSizer(wxVERTICAL);
wxPanel *firstPanel = new wxPanel(this, wxID_First_Panel);
wxButton *firstButton_Load = new wxButton(firstPanel, wxID_First_Load, "Choose file");
firstPanel->SetSizer(first_sizer);
first_sizer->Add(firstButton_Load, 1, wxEXPAND | wxALL, 10);
firstPanel->SetSizer(first_sizer);
}
void MyFrame::fileLoad(wxCommandEvent& WXUNUSED(event))
{
file_count = 2;
}
Second Frame or window:
wxPanel *firstPanel = new wxPanel(this, wxID_First_Panel);
wxBoxSizer *second_sizer = new wxBoxSizer(wxVERTICAL);
for (int i = 0; i < file_count; i++)
{
second_sizer->Add(new wxTextCtrl(firstPanel, wxWindowID(i), "Hello", wxDefaultPosition, wxSize(235, 60)), wxSizerFlags(0).Border(wxALL, 5));
}
firstPanel->SetSizer(second_sizer);
To create a new frame you need to create a new object of wxFrame class or a class deriving from it. Typically, you want to put some data and logic into your new frame, so you would create some class, e.g. MySecondaryFrame (but hopefully with a better name) , inheriting from wxFrame in a similar way to your existing MyFrame class.
Then to show it you would do the same thing that you do in MyApp::OnInit(), i.e. create a new object of this class and call Show() to actually show it.
P.S. Note that your SetWindowStyle(wxCAPTION | wxSYSTEM_MENU ) call is unnecessary, these styles are already on by default. Also, hard coding the frame size in pixels is a bad idea, consider using sizers to determine the best size suitable for the frame contents or just leave it unspecified if it really doesn't matter.
I have written code for MyFrame. I want MyFrame CONTENTS to be part of MyPageInWizard1 in wxWizard. Is there any way that I can create an object of MyFrame and use it in the MyPageInWizard1 of wxWizard? Or I have to copy code of MyFrame in that page?
MyFrame::MyFrame(wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style) : wxFrame(parent, id, title, pos, size, style)
I am doing this: but that MyFrame contents are not displayed.
MyPageInWizard1::MyPageInWizard1(wxWizard *parent, wxWizardPage *prev, wxWizardPage *next)MyWizardPage(1, parent, prev, next)
{
SetBackgroundColour(wxColour(255, 255, 255));
wxBoxSizer* bSizer3;
bSizer3 = new wxBoxSizer(wxVERTICAL);
MyHeading = new wxStaticText(this, wxID_ANY, wxT("Some Heading"), wxDefaultPosition, wxDefaultSize, 0);
MyHeading->Wrap(-1);
MyHeading->SetFont(wxFont(14, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxT("Arial")));
bSizer3->Add(MyHeading, 0, wxALIGN_CENTER | wxALL, 5);
frame = new MyFrame(NULL);
bSizer3->Add(frame, 0, wxALIGN_CENTER | wxALL, 5);
this->SetSizer(bSizer3);
}
wxFrame is always a top level window, meaning that you can't put it inside any other window, including wxWizard.
To do what you want, you need to refactor your code and put the frame contents in some new MyPanel class deriving from wxPanel. Then your frame would contain just a MyPanel and you could also create (another) MyPanel inside the wizard.
What am trying to accomplish is having two Panels , one to draw on , and the other one will hold the tools , so I'm using the default Panel with size of the whole screen for drawing shapes on drawPanel , and a custom Panel on top of it for the tools , so I can add a background to it toolsPanel:
#ifndef WXIMAGEPANEL_H
#define WXIMAGEPANEL_H
#include <wx/wx.h>
#include <wx/custombgwin.h>
#include <wx/dcbuffer.h>
class wxImagePanel : public wxCustomBackgroundWindow<wxPanel>
{
public:
wxImagePanel();
wxImagePanel (wxWindow *parent,
wxWindowID winid = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& sizee = wxDefaultSize,
long style = wxTAB_TRAVERSAL | wxNO_BORDER,
const wxString& name = wxPanelNameStr);
void SetBackgroundImage(const wxBitmap & background);
virtual ~wxImagePanel();
protected:
private:
const wxBitmap * ptr_backgorund;
void paintEvent(wxPaintEvent & evt);
void OnEraseBackground(wxEraseEvent& event);
DECLARE_EVENT_TABLE()
};
#endif // WXIMAGEPANEL_H
----------------------------------------------------
#include "wxImagePanel.h"
wxImagePanel::wxImagePanel()
{
//ctor
//SetBackgroundStyle(wxBG_STYLE_PAINT);
}
wxImagePanel::wxImagePanel (wxWindow *parent,
wxWindowID winid ,
const wxPoint& pos ,
const wxSize& sizee ,
long style ,
const wxString& name)
{
Create(parent,winid,pos,sizee,style,name);
//SetBackgroundStyle(wxBG_STYLE_PAINT);
}
void wxImagePanel::SetBackgroundImage(const wxBitmap & background)
{
this->ptr_backgorund = &background;
SetBackgroundBitmap(background);
}
wxImagePanel::~wxImagePanel()
{
//dtor
if(ptr_backgorund)
delete ptr_backgorund;
}
BEGIN_EVENT_TABLE(wxImagePanel, wxPanel)
//EVT_PAINT(wxImagePanel::paintEvent)
EVT_ERASE_BACKGROUND(wxImagePanel::OnEraseBackground)
END_EVENT_TABLE()
void wxImagePanel::OnEraseBackground(wxEraseEvent& event)
{
}
void wxImagePanel::paintEvent(wxPaintEvent & evt)
{
wxAutoBufferedPaintDC dc(this);
PrepareDC(dc);
if(ptr_backgorund)
dc.DrawBitmap( *ptr_backgorund, 0, 0);
}
I have tried both ways (drawing the background myself, and using SetBackgroundBitmap method) , both ways are flickering when am calling drawPanel->Refresh() on MouseMove event , so what am missing here , that causing the toolsPanel to flicker?
The flicker is unavoidable if you call Refresh() on the entire window on each mouse move, this simply shouldn't be done. At the very least, you need to refresh just the small area which really needs to be repainted and not the entire window, which would reduce the flicker significantly but might still be not enough. The best solution is to use the (unfortunately still undocumented) wxOverlay class to overlay whatever you're drawing when the mouse moves on top of the window.
try refreshing like this:
window->Refresh(false)
Here's a similar thread containing a very useful piece of advice: on Refresh, "the option eraseBackground = FALSE is very important to avoid flickering"
how to animate picture without making it flicker using wxWidgets in erlang?
The documentation says Refresh repaints "window, and all of its children recursively (except under wxGTK1 where this is not implemented)". Although I don't understand the behaviour, I'm suspecting the recursive nature of Refresh causing the flicker, since I'm usually experiencing it under Windows 7 but not under Linux GTK (with the exact same code).
However, under Windows 10 I don't see any flicker even with eraseBackground = TRUE.