wxWidgets with AUI: OpenGL render loop method and wxPaintEvent - c++

The method I traditionally use of drawing to an OpenGL canvas with wxWidgets is to trigger a 30 hertz timed refresh() from the current OpenGL canvas, which then generates a "wxEVT_PAINT", which I can propogate out to the rest of the frame. I also bind to the wxEVT_PAINT and call refresh() to catch any frame resizes.
In my programs WITHOUT using wxWidgets AUI, this method has worked flawlessly.
If I try to use AUI however, every time I try binding to wxEVT_PAINT, my paint events never trigger. Sometimes binding to the paint event will even stop other events like the timer from triggering.
Is there some special way that AUI treats events, or the wxEVT_PAINT that I'm missing here? What is the best method to draw to a OpenGL window inside of a AUI managed frame? Can anyone provide hints or examples, as documentation seems to be non-existent on this topic concerning AUI.
Edit: For clarity, and I've added my source code below for anyone who would like to help track down the problem. I've removed the OpenGL portions, as I'm really just trying to get the wxEVT_PAINT to trigger my bound handler in the frame when I resize the window.
GeneratedFrame.h
#ifndef __GENERATEDFRAME_H__
#define __GENERATEDFRAME_H__
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/panel.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/menu.h>
#include <wx/frame.h>
#include <wx/aui/aui.h>
class MainFrame : public wxFrame
{
private:
protected:
wxPanel* m_panelMainView;
wxMenuBar* m_menubar2;
public:
MainFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("SimpleAui"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 646,516 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
wxAuiManager m_mgr;
~MainFrame();
};
#endif //__GENERATEDFRAME_H__
GeneratedFrame.cpp
#include "GeneratedFrame.h"
MainFrame::MainFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
m_mgr.SetManagedWindow(this);
m_mgr.SetFlags(wxAUI_MGR_LIVE_RESIZE);
m_panelMainView = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_panelMainView->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_ACTIVECAPTION ) );
m_panelMainView->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_INFOBK ) );
m_mgr.AddPane( m_panelMainView, wxAuiPaneInfo() .Name( wxT("MainView") ).Center() .Caption( wxT("Main View") ).MinimizeButton( true ).PinButton( true ).Dock().Resizable().FloatingSize( wxDefaultSize ).Floatable( false ) );
m_menubar2 = new wxMenuBar( 0 );
this->SetMenuBar( m_menubar2 );
m_mgr.Update();
this->Centre( wxBOTH );
}
MainFrame::~MainFrame()
{
m_mgr.UnInit();
}
AppFrame.h
#ifndef __AppFrame_h__
#define __AppFrame_h__
#include "GeneratedFrame.h"
#include "SimpleAuiApp.h"
class AppFrame : public MainFrame
{
public:
// Constructor/Descructor
AppFrame( wxWindow* parent, ApplicationData* pAppData );
~AppFrame( );
// display update (called from main App on an update event)
void UpdateDisplay( );
private:
// pointer to the main data structure
ApplicationData* m_pAppData;
void OnPaint( wxPaintEvent& event );
};
#endif //__AppFrame_h__
AppFrame.cpp
#include "AppFrame.h"
#include "SimpleAuiApp.h"
#include <iostream>
// Constructor for the frame
AppFrame::AppFrame( wxWindow* parent, ApplicationData* pAppData )
: MainFrame( parent )
{
// Pull in the app data pointer
m_pAppData = pAppData;
// Set the size of the inner drawing area of the frame
SetClientSize( 500, 500 );
// Show the frame
Show();
// Layout the frame
Layout();
// Bind to the wxEVT_PAINT event
Bind( wxEVT_PAINT, &AppFrame::OnPaint, this );
}
// Destructor for the frame
AppFrame::~AppFrame( )
{
// stop the update timer for the application, otherwise a timer update
// event can be generated while data is being deleted
if( m_pAppData->m_pTimer )
{
m_pAppData->m_pTimer->Stop( );
}
}
void AppFrame::OnPaint( wxPaintEvent& event )
{
std::cout << "Running AppFrame::OnPaint\n";
UpdateDisplay( );
}
// perform frame update for the display
void AppFrame::UpdateDisplay( )
{
std::cout << "Running AppFrame::UpdateDisplay\n";
}
SimpleAuiApp.h
#ifndef __SimpleAuiApp_h__
#define __SimpleAuiApp_h__
#include <wx/wx.h>
#define DEFAULT_UPDATE_RATE (10) // in Hz (set to 0 for OnIdle)
// Forward declarations
class AppFrame;
struct ApplicationData
{
// constructor
ApplicationData( )
{
m_pFrame = NULL;
m_pTimer = NULL;
m_nDisplayUpdateRate = DEFAULT_UPDATE_RATE;
}
// timer object for frame based updates
wxTimer* m_pTimer;
// rate of display update (in HZ) (0=update on idle)
int m_nDisplayUpdateRate;
// pointer to the main frame instance
AppFrame* m_pFrame;
};
// Main application class
// (derived from the wxWidget App class)
class SimpleAuiApp : public wxApp
{
public:
SimpleAuiApp( );
virtual ~SimpleAuiApp( );
// the main application data structure
ApplicationData m_AppData;
private:
// called by wxApp when starting up, program setup should be done here
bool OnInit( );
// called by wxApp when shutting down, program cleanup should be done here
int OnExit( );
// When running with "fixed" framerate, called for each timer event (frame)
void OnTimer( wxTimerEvent& event );
};
DECLARE_APP( SimpleAuiApp )
#endif //__SimpleAuiApp_h__
SimpleAuiApp.cpp
#include <wx/wx.h>
#include "SimpleAuiApp.h"
#include "AppFrame.h"
#include <iostream>
IMPLEMENT_APP( SimpleAuiApp )
SimpleAuiApp::SimpleAuiApp( )
{
}
SimpleAuiApp::~SimpleAuiApp( )
{
}
bool SimpleAuiApp::OnInit( )
{
// Open a console window for errors and standard output
AllocConsole( );
freopen( "CONOUT$", "wb", stdout );
freopen( "CONOUT$", "wb", stderr );
std::cout << "Initialization started...\n";
// Create the main application frame
m_AppData.m_pFrame = new AppFrame( (wxWindow*) NULL, &m_AppData );
// Bring the frame to the front
SetTopWindow( m_AppData.m_pFrame );
// See if a fixed frame rate is specified
if ( m_AppData.m_nDisplayUpdateRate > 0 )
{
// Start a timer to update the display at a fixed frame rate.
// Note that rate is increased by 10% to make up for wxWidget's inaccurate timers.
m_AppData.m_pTimer = new wxTimer( this );
float fMilliSeconds = 1000.0 / ( m_AppData.m_nDisplayUpdateRate * 1.1f );
m_AppData.m_pTimer->Start( fMilliSeconds );
Bind( wxEVT_TIMER, &SimpleAuiApp::OnTimer, this );
}
else
{
// capture the "on idle" event when not running at a fixed frame rate
Bind( wxEVT_IDLE, &SimpleAuiApp::OnIdle, this );
}
std::cout << "Initialization completed...\n";
return true;
}
// -------------------------------------------------------------
// Framerate - functions bound to framerate related events.
// Either OnTimer() or OnIdle() should be called here for each
// frame, but never both. They are two different refresh
// methods.
// -------------------------------------------------------------
// Called by widget app code on timer event
void SimpleAuiApp::OnTimer( wxTimerEvent& event )
{
std::cout << "Called SimpleAuiApp::OnTimer \n";
// update the frame's display
m_AppData.m_pFrame->UpdateDisplay( );
}
// -------------------------------------------------------------
// OnExit - Called by widget app code on shutdown
// -------------------------------------------------------------
int SimpleAuiApp::OnExit( )
{
// stop (and delete) the update timer if needed
if ( m_AppData.m_pTimer )
{
m_AppData.m_pTimer->Stop( );
delete m_AppData.m_pTimer;
m_AppData.m_pTimer = NULL;
}
// close the console window if needed
FreeConsole( );
// exit successful
return 0;
}

The problem ended up being that the AUI manager which you attach to the main Frame DOES consume the wxEVT_PAINT event when it propagates, and it never reaches the Frame's class. The Frame's children however, do receive the events.
Instead of just using Bind() from the Frame class, I called m_panelgl->Bind where the m_panelgl was my child container which held the OpenGL canvas.

Related

Resizing Borderless wxFrame in wxWidgets

Hi i have a borderless wxFrame but it is not resizable. I want to make the frame resizable just like when it is resizable with a border. I am using wxRESIZE_BORDER and wxBORDER_NONE style properties. I know i have to catch the mouse event manually and then implement it but am not able do that. I have also looked at the shaped sample directory but there also there is no "border-less resizable frame". I am using Ubuntu 18.04 and wxWidgets 3.1.5. Is this doable/possible in wxWidgets and is there any example of the same?
Here's an example of the basic machinery needed to allow a frame to be resized without using a resize border. It basically shows Igor's comments above.
#include "wx/wx.h"
#include <wx/timer.h>
#include <wx/dcbuffer.h>
#include <wx/version.h>
#if __WXGTK__
#define BORDERLESS_FRAME_STYLE (wxCAPTION | wxCLOSE_BOX)
#else
#define BORDERLESS_FRAME_STYLE (wxCAPTION | wxCLOSE_BOX | wxBORDER_NONE)
#endif // __WXGTK__
class MyFrame: public wxFrame
{
public:
MyFrame();
private:
enum ResizeMode
{
ResizeNone,
ResizeFromTop,
ResizeFromUpperLeft,
ResizeFromLeft,
ResizeFromLowerLeft,
ResizeFromBottom,
ResizeFromLowerRight,
ResizeFromRight,
ResizeFromUpperRight
};
// General event handlers
void OnBgPanelPaint(wxPaintEvent&);
// Event handlers for resizing
void OnLeftDownForResizeFromLowerRight(wxMouseEvent&);
void OnLeftDownForResizeFromLowerLeft(wxMouseEvent&);
void OnLeftUp(wxMouseEvent&);
void OnMouseCaptureLost(wxMouseCaptureLostEvent&);
void OnResizeTimer(wxTimerEvent&);
// Resizing helper functions
void DoDragBasedResize();
void StartResize(wxWindow*, const wxPoint&);
void CompleteResize(bool doFinalResize = false);
// Data and objects needed for resizing.
wxTimer m_resizeTimer;
int m_resizeFrequency;
int m_resizeAreaLength;
ResizeMode m_resizeMode;
wxPoint m_resizeStartPoint;
wxSize m_initialFrameSize;
wxPoint m_initialFramePosition;
wxWindow* m_clickToResizeFromLowerRightWindow;
wxWindow* m_clickToResizeFromLowerLeftWindow;
wxWindow* m_curResizeWindow;
// GUI controls
wxPanel* m_bgPanel;
};
MyFrame::MyFrame():wxFrame(NULL, wxID_ANY, "Resizing Demo", wxDefaultPosition,
wxSize(400, 300), BORDERLESS_FRAME_STYLE)
{
// Set up the UI.
m_bgPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxTAB_TRAVERSAL|wxFULL_REPAINT_ON_RESIZE);
m_bgPanel->SetBackgroundStyle(wxBG_STYLE_PAINT );
m_bgPanel->Bind(wxEVT_PAINT, &MyFrame::OnBgPanelPaint, this);
wxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
mainSizer->Add(m_bgPanel, wxSizerFlags(1).Expand());
SetSizer(mainSizer);
Layout();
// Initialize the data needed for resizing.
m_resizeMode = ResizeNone;
#if wxCHECK_VERSION(3,1,0)
m_resizeAreaLength = FromDIP(20);
#else
m_resizeAreaLength = 20;
#endif // wxCHECK_VERSION
m_resizeTimer.Bind(wxEVT_TIMER, &MyFrame::OnResizeTimer, this);
m_resizeFrequency = 50;
m_curResizeWindow = NULL;
// Set window and event handlers for resizing from lower right.
m_clickToResizeFromLowerRightWindow = m_bgPanel;
m_clickToResizeFromLowerRightWindow->Bind(wxEVT_LEFT_DOWN,
&MyFrame::OnLeftDownForResizeFromLowerRight, this);
m_clickToResizeFromLowerRightWindow->Bind(wxEVT_LEFT_UP,
&MyFrame::OnLeftUp, this);
m_clickToResizeFromLowerRightWindow->Bind(wxEVT_MOUSE_CAPTURE_LOST,
&MyFrame::OnMouseCaptureLost, this);
// Set window and event handlers for resizing from lower left.
m_clickToResizeFromLowerLeftWindow = m_bgPanel;
m_clickToResizeFromLowerLeftWindow->Bind(wxEVT_LEFT_DOWN,
&MyFrame::OnLeftDownForResizeFromLowerLeft, this);
m_clickToResizeFromLowerLeftWindow->Bind(wxEVT_LEFT_UP,
&MyFrame::OnLeftUp, this);
m_clickToResizeFromLowerLeftWindow->Bind(wxEVT_MOUSE_CAPTURE_LOST,
&MyFrame::OnMouseCaptureLost, this);
}
void MyFrame::OnLeftDownForResizeFromLowerLeft(wxMouseEvent& event)
{
wxPoint p = event.GetPosition();
wxSize sz = m_clickToResizeFromLowerLeftWindow->GetClientSize();
// Check if the click is in the lower left of the window.
if ( p.x < m_resizeAreaLength &&
sz.GetHeight() - p.y < m_resizeAreaLength )
{
StartResize(m_clickToResizeFromLowerLeftWindow, p);
m_resizeMode = ResizeFromLowerLeft;
SetTitle("Resize From lower left in progress...");
SetCursor(wxCursor(wxCURSOR_SIZENESW));
}
else
{
event.Skip();
}
}
void MyFrame::OnLeftDownForResizeFromLowerRight(wxMouseEvent& event)
{
wxPoint p = event.GetPosition();
wxSize sz = m_clickToResizeFromLowerRightWindow->GetClientSize();
// Check if the click is in the lower right of the window.
if ( sz.GetWidth() - p.x < m_resizeAreaLength &&
sz.GetHeight() - p.y < m_resizeAreaLength )
{
StartResize(m_clickToResizeFromLowerRightWindow, p);
m_resizeMode = ResizeFromLowerRight;
SetTitle("Resize from lower right in progress...");
SetCursor(wxCursor(wxCURSOR_SIZENWSE));
}
else
{
event.Skip();
}
}
void MyFrame::OnLeftUp(wxMouseEvent& event)
{
if ( m_resizeMode != ResizeNone )
{
CompleteResize(true);
}
else
{
event.Skip();
}
}
void MyFrame::OnMouseCaptureLost(wxMouseCaptureLostEvent&)
{
if ( m_resizeMode != ResizeNone )
{
CompleteResize(false);
}
}
void MyFrame::OnResizeTimer(wxTimerEvent&)
{
DoDragBasedResize();
}
void MyFrame::DoDragBasedResize()
{
wxMouseState ms = ::wxGetMouseState();
wxPoint curMousePsn = ms.GetPosition();
wxPoint dragVector = curMousePsn - m_resizeStartPoint;
wxSize newSize(m_initialFrameSize);
wxPoint newPsn(m_initialFramePosition);
if ( m_resizeMode == ResizeFromLowerRight )
{
newSize = wxSize(m_initialFrameSize.GetWidth() + dragVector.x,
m_initialFrameSize.GetHeight() + dragVector.y);
}
else if ( m_resizeMode == ResizeFromLowerLeft )
{
newSize = wxSize(m_initialFrameSize.GetWidth() - dragVector.x,
m_initialFrameSize.GetHeight() + dragVector.y);
newPsn = wxPoint(m_initialFramePosition.x + dragVector.x,
m_initialFramePosition.y);
}
SetSize(newPsn.x, newPsn.y, newSize.GetWidth(), newSize.GetHeight());
}
void MyFrame::StartResize(wxWindow* win, const wxPoint& p)
{
m_curResizeWindow = win;
m_resizeTimer.Start(m_resizeFrequency);
m_resizeStartPoint = m_curResizeWindow->ClientToScreen(p);
m_curResizeWindow->CaptureMouse();
m_initialFramePosition = GetPosition();
m_initialFrameSize = GetSize();
}
void MyFrame::CompleteResize(bool doFinalResize)
{
if ( doFinalResize )
{
DoDragBasedResize();
}
m_resizeTimer.Stop();
m_resizeMode = ResizeNone;
SetTitle("Resize complete");
SetCursor(wxCursor(wxCURSOR_ARROW));
if ( m_curResizeWindow && m_curResizeWindow->HasCapture() )
{
m_curResizeWindow->ReleaseMouse();
}
m_curResizeWindow = NULL;
}
void MyFrame::OnBgPanelPaint(wxPaintEvent&)
{
wxAutoBufferedPaintDC dc(m_bgPanel);
dc.Clear();
wxPen pen(*wxRED,5);
dc.SetPen(pen);
// Draw some red marks to indicate the lower right resize area
wxPoint lowerRight = m_bgPanel->GetClientRect().GetBottomRight();
dc.DrawLine(lowerRight - wxPoint(0,m_resizeAreaLength), lowerRight);
dc.DrawLine(lowerRight, lowerRight - wxPoint(m_resizeAreaLength,0));
// Draw some red marks to indicate the lower left resize area
wxPoint lowerLeft = m_bgPanel->GetClientRect().GetBottomLeft();
dc.DrawLine(lowerLeft - wxPoint(0,m_resizeAreaLength), lowerLeft);
dc.DrawLine(lowerLeft, lowerLeft + wxPoint(m_resizeAreaLength,0));
}
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
MyFrame* frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
This only shows resizing if the click is in the lower right corner of a panel covering the client area of the frame. I tried to add enough generality that if you have another control covering the lower right, you should be able to set that control to be m_clickToResizeWindow in the code above.
There is one case where that might not work however. I could be wrong, but I think some native controls completely consume mouse clicks and won't even generate a wxMouseEvent. In this case, resizing won't be possible if such a control is in the lower right.
There are some parameters that can be changed to modify the resizing behavior. The m_resizeAreaLength determines how close to the edge a click can be to start the resizing process. I've set this to 20 DIPs. The m_resizeFrequency determines how frequently the size is updated during a resize operation. I've set this to 50ms. A smaller value will provide smoother updates. In this example, I drew some red marks on the lower right to indicate the resizing area. This is completely unnecessary and can be removed.
This example only shows resizing based on a click on the lower right. However it shouldn't be too hard to modify the code to allow only horizontal resizing based on a click on the right edge or vertical resizing based on a click on the bottom edge. However, it might get complicated if there are multiple controls covering the left or bottom edges. This system only works well if there is one control covering those edges.
edit: I've updated the code to show resizing from both the lower left and lower right while keeping the rest of the frame in the appropriate position.
This can be further expanded to allow for resizing from other edges or corners. To do so it will be necessary to:
add a pointer for the window that will be used to receive the click starting the resize.
add a left down handler for that window to start the process.
Bind the new left down handler, the existing left up handler, and the existing capture lost handler for the window.
add to the DoDragBasedResize method setting a new size and position for the frame appropriate for the type of resizing being done.

Alternative to `wxProcess::IsInputAvailable`

According to the documentation, the method wxProcess::IsInputAvailable "allows writing simple (and extremely inefficient) polling-based code waiting for a better mechanism in future wxWidgets versions." It also points to an example code dated to "15.01.00", I've checked on GitHub that the usage of this method dates back some 16 years or more.
Well, is this method of pooling still inefficient? Is there any better alternative?
I'm debugging an issue with a process's output not being read using something similar to the exec sample, and either way my application is sensitive to the timeliness of this polling and extracting of output.
I don't think the methods for reading the redirected output or the child process are inefficient. I think the document is stating that polling on the GUI thread as in the sample will be inefficient since it involves repeatedly generating pseudo idle events.
A possible alternative is to use a secondary thread to do the reading. Here is an example of using the lame mp3 encoder to encode a wav file:
// 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/filename.h>
#include <wx/filepicker.h>
#include <wx/msgqueue.h>
#include <wx/thread.h>
#include <wx/process.h>
const char* pathToLame = "d:\\temp\\lame.exe";
wxDEFINE_EVENT(wxEVT_THREAD_STDIN, wxThreadEvent);
wxDEFINE_EVENT(wxEVT_THREAD_STDERR, wxThreadEvent);
class LameThread : public wxThread
{
public:
enum ThreadMessage
{
ProcessComplete,
ExitThread,
MessageLast
};
LameThread(wxEvtHandler*, wxProcess*, wxMessageQueue<ThreadMessage>& q);
~LameThread();
private:
ExitCode Entry() wxOVERRIDE;
void DrainInput();
wxMessageQueue<ThreadMessage>& m_queue;
wxEvtHandler* m_handler;
wxProcess* m_process;
char* m_buffer;
size_t m_bufferSize;
};
LameThread::LameThread(wxEvtHandler* h, wxProcess* p,
wxMessageQueue<ThreadMessage>& q)
:wxThread(wxTHREAD_JOINABLE),m_queue(q)
{
m_process = p;
m_handler = h;
m_bufferSize = 1024*1024;
m_buffer = new char[m_bufferSize];
}
LameThread::~LameThread()
{
delete[] m_buffer;
delete m_process;
}
wxThread::ExitCode LameThread::Entry()
{
ExitCode c;
while ( 1 )
{
// Check if termination was requested.
if ( TestDestroy() )
{
wxProcess::Kill(m_process->GetPid());
c = reinterpret_cast<ExitCode>(1);
break;
}
ThreadMessage m = MessageLast;
wxMessageQueueError e = m_queue.ReceiveTimeout(10, m);
// Check if a message was received or we timed out.
if ( e == wxMSGQUEUE_NO_ERROR )
{
if ( m == ProcessComplete )
{
DrainInput();
c = reinterpret_cast<ExitCode>(0);
break;
}
else if ( m == ExitThread )
{
wxProcess::Kill(m_process->GetPid());
c = reinterpret_cast<ExitCode>(1);
break;
}
}
else if ( e == wxMSGQUEUE_TIMEOUT )
{
DrainInput();
}
}
return c;
}
void LameThread::DrainInput()
{
if ( !m_process->IsInputOpened() )
{
return;
}
wxString fromInputStream, fromErrorStream;
wxInputStream* stream;
while ( m_process->IsInputAvailable() )
{
stream = m_process->GetInputStream();
stream->Read(m_buffer, m_bufferSize);
fromInputStream << wxString(m_buffer, stream->LastRead());
}
while ( m_process->IsErrorAvailable() )
{
stream = m_process->GetErrorStream();
stream->Read(m_buffer, m_bufferSize);
fromErrorStream << wxString(m_buffer, stream->LastRead());
}
if ( !fromInputStream.IsEmpty() )
{
wxThreadEvent* event = new wxThreadEvent(wxEVT_THREAD_STDIN);
event->SetString(fromInputStream);
m_handler->QueueEvent(event);
}
if ( !fromErrorStream.IsEmpty() )
{
wxThreadEvent* event = new wxThreadEvent(wxEVT_THREAD_STDERR);
event->SetString(fromErrorStream);
m_handler->QueueEvent(event);
}
}
class MyFrame: public wxFrame
{
public:
MyFrame();
private:
void OnClose(wxCloseEvent& event);
void OnEncode(wxCommandEvent& event);
void OnProcessComplete(wxProcessEvent& event);
void OnThreadInput(wxThreadEvent&);
wxThread* m_lameThread;
wxMessageQueue<LameThread::ThreadMessage> m_msgQueue;
wxFilePickerCtrl* m_filePicker;
wxTextCtrl* m_textCtrl;
wxButton* m_encodeButton;
};
MyFrame::MyFrame()
:wxFrame(NULL, wxID_ANY, "Encode", wxDefaultPosition, wxSize(600, 400))
{
m_lameThread = NULL;
wxPanel* panel = new wxPanel(this, wxID_ANY);
m_filePicker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString,
"Select a file", "*.wav");
m_textCtrl = new wxTextCtrl(panel, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxDefaultSize,
wxTE_DONTWRAP|wxTE_MULTILINE|wxTE_READONLY);
m_encodeButton = new wxButton( panel, wxID_ANY, "Encode");
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_filePicker, wxSizerFlags().Expand().Border(wxALL) );
sizer->Add(m_textCtrl,
wxSizerFlags(1).Expand().Border(wxLEFT|wxRIGHT|wxBOTTOM));
sizer->Add(m_encodeButton,
wxSizerFlags().Border(wxLEFT|wxRIGHT|wxBOTTOM).Right());
panel->SetSizer( sizer );
panel->Layout();
m_encodeButton->Bind(wxEVT_BUTTON, &MyFrame::OnEncode, this);
Bind(wxEVT_CLOSE_WINDOW, &MyFrame::OnClose, this);
Bind(wxEVT_END_PROCESS, &MyFrame::OnProcessComplete, this);
Bind(wxEVT_THREAD_STDIN, &MyFrame::OnThreadInput, this);
Bind(wxEVT_THREAD_STDERR, &MyFrame::OnThreadInput, this);
}
void MyFrame::OnClose(wxCloseEvent& event)
{
if ( m_lameThread && m_lameThread->IsRunning() )
{
m_msgQueue.Post(LameThread::ExitThread);
m_lameThread->Wait();
delete m_lameThread;
}
Destroy();
}
void MyFrame::OnEncode(wxCommandEvent& event)
{
// Make sure the input file exists and is a wav file.
wxString file = m_filePicker->GetPath();
if ( !wxFileName::FileExists(file) )
{
m_textCtrl->AppendText("file does not exist.\n");
return;
}
wxFileName fn(file);
if ( fn.GetExt() != "wav" )
{
m_textCtrl->AppendText("File is not a wav file.\n");
return;
}
// Create a process and and encoder thread.
wxProcess* process = new wxProcess(this);
process->Redirect();
m_msgQueue.Clear();
m_lameThread = new LameThread(this, process, m_msgQueue);
m_lameThread->Run();
if ( !m_lameThread->IsRunning() )
{
m_textCtrl->AppendText("Unable to launch encoder thread.\n");
delete m_lameThread;
return;
}
// Execute the encoder command.
wxString cmd = pathToLame;
cmd << " \"" << fn.GetFullPath() << "\" \"";
cmd << fn.GetPathWithSep() << fn.GetName() << ".mp3\"";
wxExecute(cmd, wxEXEC_ASYNC, process);
m_encodeButton->Disable();
}
void MyFrame::OnProcessComplete(wxProcessEvent& event)
{
if ( m_lameThread && m_lameThread->IsRunning() )
{
m_msgQueue.Post(LameThread::ProcessComplete);
m_lameThread->Wait();
delete m_lameThread;
m_lameThread = NULL;
m_encodeButton->Enable();
}
}
void MyFrame::OnThreadInput(wxThreadEvent& event)
{
m_textCtrl->AppendText(event.GetString());
}
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
::wxInitAllImageHandlers();
MyFrame* frame = new MyFrame();
frame->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
On windows, this looks like this:
The basic idea is to run a tight loop in the thread's Entry method. In each iteration the thread waits for a message from a message queue, and if no message is received the thread tries to check the process for input or error messages.
That's essentially the same polling technique as used in the sample, but since it's in a secondary thread it won't clog the gui thread with idle events. If you need to do any processing of the input, that will also be done on the secondary thread.
If you're having trouble with timings, you can reduce the time spent waiting on the message queue.
Note that launching the secondary procss and receiving the event when it finishes can only be done on the main thread.
Finally I should mention that I'm not 100% sure this is completely thread safe. I wish there was a way to protect the reads and writes with the input and error streams with a mutex or critcal section; but since all the writing to those streams is done under the hood, I can't see any way to do that from user code. I can say I've used this technique on both windows and linux in the past without issue.

Why Does Gtk::Frame Force A Redraw & Resize?

In the included code I've created an application where I periodically update a label. When the application first starts up, updating the timeLabel results in redrawing the entire contents of the application. This can be observed by running the application with the --gtk-debug=updates argument.
When the button on the right side is clicked, the frame that encloses the contents of the window is removed from the widget hierarchy. This results in further updates to the timeLabel only redrawing the label, and not redrawing swapButton.
Why does a frame seem to want to redraw itself even if it doesn't need to?
#include <gtkmm.h>
class MyWindow
: public Gtk::Window
{
public:
MyWindow();
private:
bool timeout();
void toggleUseOfFrame();
Gtk::Frame frame;
Gtk::Label timeLabel;
Gtk::Button swapButton;
Gtk::Box box;
};
MyWindow::MyWindow()
{
// Layout widgets in initial configuration.
box.pack_start( timeLabel, true, true );
box.pack_start( swapButton, true, true );
box.set_homogeneous();
frame.add( box );
add( frame );
show_all();
set_size_request( 100, 50 );
// Setup signal handlers.
Glib::MainContext::get_default()->signal_timeout().connect(
sigc::mem_fun( *this, &MyWindow::timeout ), 1000 );
swapButton.signal_clicked().connect(
sigc::mem_fun( *this, &MyWindow::toggleUseOfFrame ) );
}
// Periodically update the label to force it to redraw.
bool MyWindow::timeout()
{
Glib::DateTime now = Glib::DateTime::create_now_local();
timeLabel.set_text( now.format( "%S" ) );
return true;
}
// If the frame is currently in use remove it. Otherwise add it back.
void MyWindow::toggleUseOfFrame()
{
if( frame.get_parent() ) {
remove();
box.reparent( *this );
}
else {
box.reparent( frame );
add( frame );
}
}
int main( int argc, char* argv[]) {
Glib::RefPtr<Gtk::Application> app =
Gtk::Application::create( argc, argv, "test" );
MyWindow myWindow;
return app->run( myWindow );
}

Qt QAbstractButton setDown interferes with grabMouse

I have some weird behaviour in Qt that seems like a defect. I'd like to know if anybody has a good workaround.
I have a popup widget that contains many buttons in it. The user activates the popup by pressing the mouse button down. The popup widget calls grabMouse when shown. It gets all the mouse events. As it rolls over a button it calls setDown(true) on the button. Now however, when the mouse button is released the popup widget does not get the mouseReleaseEvent, that goes to the button.
That is, calling setDown(true) on a button causes the button to steal mouse events, bypassing the grabMouse in the popup widget.
I've looked at the source code for setDown but I can't see anything there that would do it directly. I also notice however that sometimes a button gets a hover event, sometimes not. I would assume it would never get those events when the mouse is grabbed.
//g++ -o grab_lost grab_lost.cpp -lQtCore -lQtGui -I /usr/include/qt4/ -I /usr/include/qt4/QtCore -I /usr/include/qt4/QtGui
/**
Demonstrates the defect of losing the mouse. Run the program and:
1. Press mouse anywhere
2. release in purple block (not on X)
3. Release message written (GrabLost receives the mouseReleaseEvent)
For defect:
1. Pree mouse anywhere
2. Release inside the X button
3. button is clicked, no release message (GrabLost does not get the mouseReleaseEvent)
*/
#include <QWidget>
#include <QPushButton>
#include <QApplication>
#include <QMouseEvent>
#include <QPainter>
class GrabLost : public QWidget
{
QPushButton * btn;
public:
GrabLost( QWidget * parent = 0)
: QWidget( parent, Qt::Popup )
{
btn = new QPushButton( "X", this );
setMouseTracking( true );
}
protected:
void showEvent( QShowEvent * ev )
{
QWidget::showEvent( ev );
grabMouse();
}
void closeEvent( QCloseEvent * ev )
{
releaseMouse();
QWidget::closeEvent( ev );
}
void hideEvent( QHideEvent * ev )
{
releaseMouse();
QWidget::hideEvent( ev );
}
void mouseReleaseEvent( QMouseEvent * ev )
{
qDebug( "mouseRelease" );
close();
}
void mouseMoveEvent( QMouseEvent * ev )
{
QWidget * w = childAt( ev->pos() );
bool ours = dynamic_cast<QPushButton*>( w ) == btn;
btn->setDown( ours );
}
void paintEvent( QPaintEvent * ev )
{
//just to show where the widget is
QPainter pt( this );
pt.setPen( QColor( 0,0,0 ) );
pt.setBrush( QColor( 128,0,128) );
pt.drawRect( 0, 0, size().width(), size().height() );
}
};
class GrabMe : public QWidget
{
protected:
void mousePressEvent( QMouseEvent * ev )
{
GrabLost * gl = new GrabLost();
gl->resize( 100, 100 );
QPoint at( mapToGlobal( ev->pos() ) );
gl->move( at.x() - 50, at.y() - 50 );
gl->show();
}
};
int main( int argc, char** argv )
{
QApplication app( argc, argv );
GrabMe * gm = new GrabMe();
gm->move( 100, 100 );
gm->resize( 300, 300 );
gm->show();
app.exec();
return 0;
}
I've entered the defect at the Nokia DB. I'm giving it about a 95% chance that they close it as "works as intended".
For those of you that need a solution nonetheless you'll have to use event filters and create your own grabbing. Basically install an event filter for every child widget and propagate the mouse events to the parent.
Note in the above code that the right mouse button doesn't work even if you don't call setDown.

draw in a QFrame on clicking a button.

Say there is a QPushButton named "Draw", a QLineEdit and a QFrame. On clicking the button I want to take a number from QLineEdit and draw a circle in a QFrame. How can I do this? Please provide me with the code.
P.S. The problem is that draw methods of the QPainter should be called in drawEvent method.
If #Kaleb Pederson's answer is not enough for you then here's a complete solution for a simple set-up matching what you describe. Tested with Qt 4.5.2 on Linux. I had some spare time... ;)
main.cpp:
#include <QApplication>
#include "window.h"
int main( int argc, char** argv )
{
QApplication qapp( argc, argv );
Window w;
w.show();
return qapp.exec();
}
window.h
#pragma once
class QLineEdit;
class QPushButton;
#include <QWidget>
class Frame;
class Window : public QWidget
{
Q_OBJECT
public:
Window();
private slots:
void onButtonClicked();
private:
QLineEdit* m_lineEdit;
QPushButton* m_pushButton;
Frame* m_frame;
};
window.cpp:
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include "frame.h"
#include "window.h"
Window::Window()
: m_lineEdit ( new QLineEdit( this ) )
, m_pushButton( new QPushButton( tr( "Draw" ), this ) )
, m_frame ( new Frame( this ) )
{
connect( m_pushButton, SIGNAL( clicked() )
, SLOT( onButtonClicked() ) );
QHBoxLayout*const hLayout = new QHBoxLayout;
hLayout->addWidget( m_lineEdit );
hLayout->addWidget( m_pushButton );
QVBoxLayout*const vLayout = new QVBoxLayout( this );
vLayout->addLayout( hLayout );
m_frame->setFixedSize( 300, 400 );
vLayout->addWidget( m_frame );
setLayout( vLayout );
}
void Window::onButtonClicked()
{
const int r = m_lineEdit->text().toInt(); // r == 0 if invalid
m_frame->setCircleRadius( r );
m_frame->update();
}
frame.h:
#pragma once
#include <QFrame>
class Frame : public QFrame
{
Q_OBJECT
public:
Frame( QWidget* );
void setCircleRadius( int );
protected:
void paintEvent( QPaintEvent* );
private:
int m_radius;
};
frame.cpp:
#include <QPainter>
#include "frame.h"
Frame::Frame( QWidget* parent )
: QFrame( parent )
, m_radius( 0 )
{
setFrameStyle( QFrame::Box );
}
void Frame::setCircleRadius( int radius )
{
m_radius = radius;
}
void Frame::paintEvent( QPaintEvent* pe )
{
QFrame::paintEvent( pe );
if ( m_radius > 0 )
{
QPainter p( this );
p.drawEllipse( rect().center(), m_radius, m_radius );
}
}
If you want your frame to do the drawing, then it needs a way to know that it should draw something, so create a slot that will receive notification:
/* slot */ void drawCircle(QPoint origin, int radius) {
addCircle(origin, radius);
update(); // update the UI
}
void addCircle(QPoint origin, int radius) {
circleList.add(new Circle(origin,radius));
}
Then, your frame subclass you need to override paintEvent() to draw the circle:
void paintEvent(QPaintEvent *event) {
QFrame::paintEvent(event);
QPainter painter(this);
foreach (Circle c, circleList) { // understand foreach requirements
painter.drawEllipse(c.origin(), c.radius(), c.radius());
}
}
As long as the slot responding to the button's clicked() signal emits a signal that calls the drawCircle slot with the correct arguments everything should work correctly.
You don't draw diectly onto a frame.
Start here graphicsview, it looks complicated at first - but GUI program is a big leap when you first encounter it
In most GUIs (Qt, OpenGL etc) you build up a list of elements you want to draw in your program and store them somehow - then there is a draw() function that gets called when the computer needs to draw your picture - eg when it is moved or another window is moved in front of it. The OnDraw or OnRepaint etc function then gets called and you have to draw the list of objects.
Another way to do this is to draw them all to an image (QOimage or QPixmap) and copy that to the screen in OnDraw or OnRepaint - you might do this for a graphics package for example.