I'm creating a C++ wxWidgets calculator application. I'm implementing trigonometric functions, and to save on space I've reunited all of them in a single button. If you right click on the button then, a popup is created, which contains buttons for all the functions. I'm using a derived wxPopupTransientWindow class for this job, the problem is, the buttons aren't displaying correctly.
This is my code:
expandMenu.h
#include "wx/wx.h"
#include "wx/popupwin.h"
struct expandMenuInfo
{
const wxString& label;
wxWindowID id;
expandMenuInfo(const wxString& l, wxWindowID i)
: label{l}
, id{i}
{}
};
class expandMenu : public wxPopupTransientWindow
{
wxWindow* panel;
wxBoxSizer* sizer;
public:
expandMenu(wxWindow* parent, wxPoint pos, std::vector<expandMenuInfo> buttons);
~expandMenu();
};
expandMenu.cpp
#include "expandMenu.h"
// PANNELLO ESTENSIONE
expandMenu::expandMenu(wxWindow* parent, wxPoint pos, std::vector<expandMenuInfo> buttons)
: wxPopupTransientWindow(parent, wxBORDER_NONE | wxPU_CONTAINS_CONTROLS)
{
this->SetPosition(pos);
this->SetSize(50 * buttons.size(), 50);
this->SetBackgroundColour(wxColour(90, 93, 121));
panel = new wxWindow(this, wxID_ANY);
sizer = new wxBoxSizer(wxHORIZONTAL);
// costruisci struttura
for (unsigned int i = 0; i < buttons.size(); i++)
{
wxButton* btn = new wxButton(this, buttons.at(i).id, buttons.at(i).label);
sizer->Add(btn, 1, wxEXPAND);
}
panel->SetSizer(sizer);
}
expandMenu::~expandMenu()
{
}
And this is the code I use for my custom wxButton's to actually create the popup (it's temporary):
void ikeButton::rightClick(wxMouseEvent& evt) // CREA PANNELLO ESTENSIONE
{
if (flags & EXPANDABLE)
{
std::vector<expandMenuInfo> buttons;
buttons.push_back(expandMenuInfo(L"sin", 3001));
buttons.push_back(expandMenuInfo(L"cos", 3002));
buttons.push_back(expandMenuInfo(L"tan", 3003));
expandMenu* menu = new expandMenu(this, wxGetMousePosition(), buttons);
menu->Popup();
}
}
Thank you in advance for any help, as I'm pretty new to this framework.
To get the buttons to layout in the popup window, in the constuctor for expandMenu I think you just need to change panel->SetSizer(sizer); to
SetSizerAndFit(sizer);
Layout();
From a UI perspective, I think a split button might be a better way to implement the functionality you are describing. wxWidgets doesn't currently have such a control, but this post shows 2 different ways you can create one.
I believe the issue is that you're specifying this for the parent of the buttons, i.e. the popup window. I think you wanted their parent to be the panel instead.
Related
I am having trouble capturing and handling mouse events within a panel.
Binding mouse events to my main window frame works as expected. However, when I bind events to a child panel, they successfully don't go my frame, but are not correctly handled by my panel. Any help would be appreciated.
I am using wxWidgets v3.1.5
Below is my simplest example: a single panel inside a parent frame.
Clicking the panel should turn itself yellow. Clicking the surrounding frame area should turn the panel green.
// wxWidgets in full of strcpy
#pragma warning(disable : 4996)
#include <wx/wx.h>
class cPanel : public wxPanel {
public:
cPanel(wxWindow* parent, wxSize size)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, size) {
this->Bind(wxEVT_LEFT_DOWN, &cPanel::OnLeftClick, this);
}
void OnLeftClick(wxMouseEvent& event) {
SetBackgroundColour(wxColour("yellow"));
Refresh();
};
};
class cFrame : public wxFrame {
public:
wxPanel* child;
cFrame()
: wxFrame(nullptr, wxID_ANY, "Example Title", wxPoint(200, 200),
wxSize(800, 500)) {
child = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(300, 300));
auto top = new wxBoxSizer(wxHORIZONTAL);
top->Add(child, 0, wxALL, 20);
SetSizer(top);
this->Bind(wxEVT_LEFT_DOWN, &cFrame::OnLeftClick, this);
};
void OnLeftClick(wxMouseEvent& event) {
child->SetBackgroundColour(wxColour("green"));
child->Refresh();
}
};
class cApp : public wxApp {
public:
cFrame* frame = nullptr;
cApp(){};
~cApp(){};
virtual bool OnInit() {
frame = new cFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(cApp);
Only wxCommandEvents (and classes derived from it) will filter up to parent windows. wxMouseEvent does not derive from wxCommandEvent, so a mouse event on the panel that is not handled by the panel will not filter up to the frame.
Consequently, you'll need to Bind the mouse event handler to the panel instead of the frame. Here's an example of how to change the last few lines of your cFrame class to do that.
this->Bind(wxEVT_LEFT_DOWN, &cFrame::OnLeftClick, this);
child->Bind(wxEVT_LEFT_DOWN, &cFrame::OnChildLeftClick, this);
};
void OnLeftClick(wxMouseEvent& event) {
child->SetBackgroundColour(wxColour("green"));
child->Refresh();
}
void OnChildLeftClick(wxMouseEvent& event) {
child->SetBackgroundColour(wxColour("yellow"));
child->Refresh();
}
};
There are many ways of accomplishing the same thing, but I think something like this is probably the simplest.
Hi i am trying to create a wxApp that will have not have a titlebar(including the minimize, maximize and close) icons that are by default provided. The code that i have is as follows:
main.h
class MyApp : public wxApp
{
public:
virtual bool OnInit();
};
main.cpp
bool MyApp::OnInit()
{
MyFrame *prop = new MyFrame(wxT("MyFrame"));
prop->Show(true);
return true;
}
myframe.h
class MyFrame : public wxFrame
{
public:
MyFrame(const wxString& title);
void OnClick(wxMouseEvent& event);
};
myframe.cpp
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(250, 130))
{
MyPanel *panel = new MyPanel(this, wxID_ANY);
panel->SetBackgroundColour(wxColour(255,255,255));
MyButton *button = new MyButton(panel, wxID_ANY, wxT("Ok"));
Connect( wxEVT_LEFT_UP,
wxMouseEventHandler(MyFrame::OnClick));
panel->Centre();
}
void MyFrame::OnClick(wxMouseEvent& event)
{
std::cout << "event reached frame class" << std::endl;
}
mypanel.h
class MyPanel : public wxPanel
{
public:
MyPanel(wxFrame *frame, int id);
void OnClick(wxMouseEvent& event);
};
mypanel.cpp
MyPanel::MyPanel(wxFrame *frame, int id)
: wxPanel(frame, id)
{
Connect( wxEVT_LEFT_UP,
wxMouseEventHandler(MyPanel::OnClick));
}
void MyPanel::OnClick(wxMouseEvent& event)
{
std::cout << "event reached panel class" << std::endl;
}
mybutton.h
class MyButton : public wxPanel
{
public:
MyButton(wxPanel *panel, int id, const wxString &label);
void OnClick(wxMouseEvent& event);
};
mybutton.cpp
void MyButton::onClick(wxMouseEvent &event)
{
}
What i want is:
There should be no title bar(including the 3 maximize, minimize & close buttons) at the top.
Now since there is no titlebar at the top of the frame, there is no way to drag or close or maximize or minimize the window. For that i want to create a custom titlebar at the top which should have the three customized maximized,minimized and close button and also i should be able to drag the frame by double clicking and holding and dragging the topmost part of the newly created frame.
Is this possible in wxWidgets? If yes, how can i achieve this?
I am not proposing any new way of dragging. The new frame/window that we will have should also be dragged only by its own customized title bar. It's exactly like dragging the old frame by double clicking and dragging the frame in the native case. I just want to customize the native title bar. Like increase its height, change it's colour, change how to three buttons(minimize, maximize and close) look.
Here's the simplest example I can think of how to create a frame with a pseudo titlebar that can be clicked to drag the frame around. This example shows which mouse events need to be handled to drag the window around and how to do the calculations needed in those event handlers.
Note that moving the frame needs to be done in screen coordinates, but the coordinates received in the event handlers will be in client coordinates for the title bar. This example also shows how to do those coordinate conversions.
#include "wx/wx.h"
class CustomTitleBar:public wxWindow
{
public:
CustomTitleBar(wxWindow* p) : wxWindow(p,wxID_ANY)
{
m_dragging = false;
SetBackgroundColour(*wxGREEN);
Bind(wxEVT_LEFT_DOWN,&CustomTitleBar::OnMouseLeftDown,this);
Bind(wxEVT_MOUSE_CAPTURE_LOST, &CustomTitleBar::OnMouseCaptureLost,
this);
}
wxSize DoGetBestClientSize() const override
{
return wxSize(-1,20);
}
private:
void OnMouseLeftDown(wxMouseEvent& event)
{
if ( !m_dragging )
{
Bind(wxEVT_LEFT_UP,&CustomTitleBar::OnMouseLeftUp,this);
Bind(wxEVT_MOTION,&CustomTitleBar::OnMouseMotion,this);
m_dragging = true;
wxPoint clientStart = event.GetPosition();
m_dragStartMouse = ClientToScreen(clientStart);
m_dragStartWindow = GetParent()->GetPosition();
CaptureMouse();
}
}
void OnMouseLeftUp(wxMouseEvent&)
{
FinishDrag();
}
void OnMouseMotion(wxMouseEvent& event)
{
wxPoint curClientPsn = event.GetPosition();
wxPoint curScreenPsn = ClientToScreen(curClientPsn);
wxPoint movementVector = curScreenPsn - m_dragStartMouse;
GetParent()->SetPosition(m_dragStartWindow + movementVector);
}
void OnMouseCaptureLost(wxMouseCaptureLostEvent&)
{
FinishDrag();
}
void FinishDrag()
{
if ( m_dragging )
{
Unbind(wxEVT_LEFT_UP,&CustomTitleBar::OnMouseLeftUp,this);
Unbind(wxEVT_MOTION,&CustomTitleBar::OnMouseMotion,this);
m_dragging = false;
}
if ( HasCapture() )
{
ReleaseMouse();
}
}
wxPoint m_dragStartMouse;
wxPoint m_dragStartWindow;
bool m_dragging;
};
class Customframe : public wxFrame
{
public:
Customframe(wxWindow* p)
:wxFrame(p, wxID_ANY, wxString(), wxDefaultPosition, wxSize(150,100),
wxBORDER_NONE)
{
CustomTitleBar* t = new CustomTitleBar(this);
SetBackgroundColour(*wxBLUE);
wxBoxSizer* szr = new wxBoxSizer(wxVERTICAL);
szr->Add(t,wxSizerFlags(0).Expand());
SetSizer(szr);
Layout();
}
};
class MyFrame: public wxFrame
{
public:
MyFrame():wxFrame(NULL, wxID_ANY, "Custom frame Demo", wxDefaultPosition,
wxSize(400, 300))
{
wxPanel* bg = new wxPanel(this, wxID_ANY);
wxButton* btn = new wxButton(bg, wxID_ANY, "Custom frame");
wxBoxSizer* szr = new wxBoxSizer(wxVERTICAL);
szr->Add(btn,wxSizerFlags(0).Border(wxALL));
bg->SetSizer(szr);
Layout();
btn->Bind(wxEVT_BUTTON, &MyFrame::OnButton, this);
m_customFrame = NULL;
}
private:
void OnButton(wxCommandEvent&)
{
if ( m_customFrame )
{
m_customFrame->Close();
m_customFrame = NULL;
}
else
{
m_customFrame = new Customframe(this);
m_customFrame->CenterOnParent();
m_customFrame->Show();
}
}
wxFrame* m_customFrame;
};
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
MyFrame* frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
On windows, it looks like this.
You should be able to add whatever buttons you want to the custom title bar exactly as you would add buttons to any other window.
"Is this possible in wxWidgets?"
Yes. You need to use a wxWindow instead of a wxFrame and set some style for it. like wxBORDER_NONE. But you will have to implement many things that wxFrame already provides.
And your proposed way of dragging seems wrong/confusing to me. 99.99% of users prefer a UI they are used to it, avoiding to learn a new way of doing simple things they already know.
If you just want to avoid resizing then you have two ways:
a) Catch the size-event and do nothing. Calling event.Skip(false)
(to prevent handling in parent) is not neccessary.
b) Once you have created your window and correctly sized, get its size and set it as max and min.
In both cases the user will see a "resize" mouse pointer when hovering the mouse at any border, but nothing else, no resizing, will be done.
#JasonLiam,
Don't forget to place the application icon in the top left corner of you title bar and handle the right/left mouse clicks appropriately (as in native application) (if you want to go this route and get rid of the native frame window).
Thank you.
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.
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.
When you save a file on a Mac, a panel kinda descends down from the top bar in a really cool way. I want to create a class that does a similar thing using the Qt framework. There are a number of things that I'm confused about:
When the panel descends, input to the parent window should be blocked. This is easy with QDialog as it has the setModal() method, however - QDialogs, by default pop-out. I'm not sure how to get around this.
In a QMainProject, there is a QMenua new instance of the DescendingPanel class is created. How would you do that, supposing there are other widgets below the menubar. The DescendingPanel should appear above them.
I would really appreciate any help with this.
EDIT
I had an idea that instead of pegging the dialog under the menubar, just make it appear under there and remove the window frame. That way, it would give an illusion that it popped out from under there. Ofcourse, Move events would also have to be handled so that the Dialog is always under the menubar but that's for later. Here's the code I used to get the DescendingDialog to appear under the menubar.
class DescendingDialog : public QWidget
{
QMainWindow* Window;
QWidget* Menu;
QPoint GlobalLocationOfMenu;
int DialogWidth;
int DialogHeight;
int X()
{
int XDistanceOfPanel = GlobalLocationOfMenu.x() + ((Menu->width()/2) - (this->DialogWidth/2));
//GlobalLocationOfMenu.x() returns 0;
return XDistanceOfPanel;
}
int Y()
{
int YDistanceOfPanel = GlobalLocationOfMenu.y()+Menu->height();
//GlobalLocationOfMenu.y() returns 0;
return YDistanceOfPanel;
}
void SetGeometry()
{
this->setGeometry(this->X(),this->Y(),this->DialogWidth,this->DialogHeight);
}
public:
DescendingDialog(QMainWindow* Window,int DialogWidth,int DialogHeight):QWidget(NULL)
{
this->Window = Window;
this->Menu = this->Window->menuWidget();
this->DialogWidth = DialogWidth;
this->DialogHeight = DialogHeight;
QPoint RelativeLocationOfMenu = this->Menu->pos();
this->GlobalLocationOfMenu = QWidget::mapToGlobal(RelativeLocationOfMenu);
this->SetGeometry();
}
};
It didn't work because the GlobalLocationOfMenu.x() and .y() returned 0 so the dialog doesn't appear where I want it to.
You can let a dialog "slide in" by using a function similar to that:
#include <QDialog>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
void makeAppear(QDialog * dialog, QRect geometryEnd)
{
static QParallelAnimationGroup *animationGroup = 0;
if (animationGroup)
{
for(int i = 0, ie = animationGroup->animationCount(); i != ie; ++i)
delete animationGroup->animationAt(i);
delete animationGroup;
}
// Set up start and end geometry for 'dialog'.
QPoint parentTopLeft = dialog->parentWidget()->geometry().topLeft();
geometryEnd.translate(dialog->parentWidget()->mapToGlobal(parentTopLeft));
QRect geometryBegin = geometryEnd;
geometryBegin.setHeight(0);
// Set up start and end geometry for the only child widget of 'dialog'.
QWidget * dialogChildWidget = dynamic_cast< QWidget * >(dialog->children().first());
if ( !dialogChildWidget )
return;
QRect childGeometryEnd = dialogChildWidget->geometry();
QRect childGeometryBegin = childGeometryEnd;
childGeometryBegin.translate(0, geometryEnd.height() * (-1));
// Set up animation for 'dialog'.
QPropertyAnimation *dialogAnimation = new QPropertyAnimation(dialog, "geometry");
dialogAnimation->setDuration(400);
dialogAnimation->setStartValue(geometryBegin);
dialogAnimation->setEndValue(geometryEnd);
// Set up animation for the only child widget of 'dialog'.
QPropertyAnimation *childAnimation = new QPropertyAnimation(dialogChildWidget, "geometry");
childAnimation->setDuration(400);
childAnimation->setStartValue(childGeometryBegin);
childAnimation->setEndValue(childGeometryEnd);
// Set up (and start) a parallel animation group
animationGroup = new QParallelAnimationGroup;
animationGroup->addAnimation(dialogAnimation);
animationGroup->addAnimation(childAnimation);
animationGroup->start();
// Make 'dialog' visible, borderless, modal.
dialog->setModal(true);
dialog->setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
dialog->show();
}
The dialog argument shall point to a (hidden/not visible) QDialog instance with one single child widget, that contains all other widgets that belong to the dialog.
The geometryEnd argument shall specify position and size of dialog after it has appeared (relative to it's parent widget).
The result looks like this.