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.
Related
Here is my code :
/// Get the Gtk2+ window ID of wxPanel pnlview for GStreamer output
GtkWidget* video_window = pnlView->GetHandle();
// Without this line, GStreamer will create its own new window for the video stream.
gtk_widget_realize(video_window);
GdkWindow *videoareaXwindow = gtk_widget_get_window(video_window);
data.xid = GDK_WINDOW_XID(videoareaXwindow) // Get xid for setting overlay later
were pnlView is define as wxPanel* pnlView;
But the consol give me this error : Impossible to convert 'WXWidget' in 'GtkWidget * at the line where I initialize video_window
Can someone has any idea how to fix it ?
I just want to add my gstreamer window in my wxWindow
Thank you
I've never used gstreamer, but I do sometimes use libvlc which is probably very similar. When using libvlc to render into a wxWindow, I need to wait until the wxWindow is fully created before I can set vlc to use it. This is done by adding an event handler for the window creation event.
The process of declaring and binding the event handler looks like this:
class MainWindow : public wxFrame
{
...
// Event handlers
void OnRendererWinCreated(wxWindowCreateEvent& event);
}
MainWindow::MainWindow(...)
{
...
#ifdef __WXGTK__
// On GTK+, we have to wait until the window is actually created before we
// can tell VLC to use it for output. So wait for the window create event.
pnlView->Bind(wxEVT_CREATE, &MainWindow::OnRendererWinCreated, this);
#elif defined(__WXMSW__)
m_player.setHwnd(pnlView->GetHandle());
#endif
...
}
For libvlc, my window creation event handler looks like this:
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent& event)
{
#ifdef __WXGTK__
m_player.setXwindow(
gdk_x11_window_get_xid(gtk_widget_get_window(pnlView->GetHandle()))
);
pnlView->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
#endif
}
Based on the code you posted, I think the body of an event handler that will work for you should look something like this:
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent& event)
{
#ifdef __WXGTK__
/// Get the Gtk2+ window ID of wxPanel pnlview for GStreamer output
GtkWidget* video_window = pnlView->GetHandle();
GdkWindow *videoareaXwindow = gtk_widget_get_window(video_window);
data.xid = GDK_WINDOW_XID(videoareaXwindow) // Get xid for setting overlay later
pnlView->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
#endif
}
Edit:
Here's a simple example of using GStreamer to draw onto a wxWindow on GTK. This shows how to use the wxEVT_CREATE to get the XID for a window and how to use GStreamer's bus sync handler callback to pass that XID to GStreamer at the correct time.
This is basically a mashup of the 2nd tutorial and the code snippet from the GstVideoOverlay page adjusted for wxWidgets. Since this is based on the 2nd tutorial, it just shows a test pattern. The source variable can be changed to show other videos.
Obviously this assumes GTK is using X11. Some adjustments would be needed if Wayland is used instead, but I don't have a running distro that uses Wayland to test on, so I don't know what changes are needed there.
#include "wx/wx.h"
#ifdef __WXGTK__
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#endif
#include <gst/gst.h>
#include <gst/video/videooverlay.h>
class MainWindow : public wxFrame
{
public:
MainWindow(const wxString& title);
~MainWindow();
private:
// Event handlers
void OnRendererWinCreated(wxWindowCreateEvent&);
void OnPlay(wxCommandEvent&);
void OnStop(wxCommandEvent&);
// Helper function
void LoadVideo();
void PlayHelper();
// wx controls
wxWindow* m_renderWindow;
wxButton* m_playButton;
wxButton* m_stopButton;
// GStreamer data
GstElement* m_pipeline;
guintptr m_xid;
};
MainWindow::MainWindow(const wxString& title) : wxFrame(NULL, wxID_ANY, title)
{
// Create the UI widgets.
wxPanel* bg = new wxPanel(this,wxID_ANY);
m_renderWindow = new wxWindow(bg,wxID_ANY);
m_playButton = new wxButton(bg,wxID_ANY,"Play");
m_stopButton = new wxButton(bg,wxID_ANY,"Stop");
m_renderWindow->SetBackgroundColour(*wxBLACK);
m_playButton->Enable(true);
m_stopButton->Enable(false);
// Layout the UI.
wxBoxSizer* szr1 = new wxBoxSizer(wxVERTICAL);
wxBoxSizer* szr2 = new wxBoxSizer(wxHORIZONTAL);
szr2->Add(m_playButton, wxSizerFlags(0).Border(wxLEFT|wxRIGHT|wxBOTTOM));
szr2->Add(m_stopButton, wxSizerFlags(0).Border(wxRIGHT|wxBOTTOM));
szr1->Add(m_renderWindow, wxSizerFlags(1).Expand().Border(wxBOTTOM));
szr1->Add(szr2, wxSizerFlags(0));
bg->SetSizer(szr1);
Layout();
// Set up the event handlers.
#ifdef __WXGTK__
m_renderWindow->Bind(wxEVT_CREATE, &MainWindow::OnRendererWinCreated, this);
m_playButton->Enable(false);
#endif
m_playButton->Bind(wxEVT_BUTTON, &MainWindow::OnPlay, this);
m_stopButton->Bind(wxEVT_BUTTON, &MainWindow::OnStop, this);
// Initialize GStreamer.
m_xid = 0;
m_pipeline = NULL;
gst_init(NULL, NULL);
}
MainWindow::~MainWindow()
{
if ( m_pipeline )
{
gst_element_set_state(m_pipeline, GST_STATE_NULL);
gst_object_unref(m_pipeline);
}
}
void MainWindow::OnRendererWinCreated(wxWindowCreateEvent&)
{
#ifdef __WXGTK__
// This event is no longer needed.
m_renderWindow->Unbind(wxEVT_CREATE,&MainWindow::OnRendererWinCreated,this);
// Get the XID for this window.
m_xid = GDK_WINDOW_XID(gtk_widget_get_window(m_renderWindow->GetHandle()));
// We can now load and play the video, so enable the play button.
m_playButton->Enable(true);
#endif
}
void MainWindow::OnPlay(wxCommandEvent&)
{
if ( m_pipeline )
{
PlayHelper();
}
else
{
LoadVideo();
}
}
void MainWindow::OnStop(wxCommandEvent&)
{
if ( m_pipeline )
{
GstStateChangeReturn ret =
gst_element_set_state(m_pipeline, GST_STATE_PAUSED);
if ( ret == GST_STATE_CHANGE_FAILURE )
{
wxLogWarning("Unable to set the pipeline to the paused state.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
else
{
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
}
}
void MainWindow::LoadVideo()
{
// Create the elements
GstElement *source = gst_element_factory_make("videotestsrc", "source");
#ifdef __WXGTK__
GstElement *sink = gst_element_factory_make("xvimagesink", "sink");
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink), m_xid);
#elif defined __WXMSW__
GstElement *sink = gst_element_factory_make("d3dvideosink", "sink");
WXWidget hwnd = m_renderWindow->GetHandle();
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(sink),
reinterpret_cast<guintptr>(hwnd));
#endif
//Create the empty pipeline
m_pipeline = gst_pipeline_new ("test-pipeline");
if ( !m_pipeline || !source || !sink )
{
wxLogError("Not all elements could be created.");
return;
}
// Build the pipeline
gst_bin_add_many(GST_BIN(m_pipeline), source, sink, NULL);
if ( gst_element_link(source, sink) != TRUE )
{
wxLogWarning("Elements could not be linked.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
return;
}
// Modify the source's properties
g_object_set(source, "pattern", 0, NULL);
PlayHelper();
}
void MainWindow::PlayHelper()
{
GstStateChangeReturn ret =
gst_element_set_state(m_pipeline, GST_STATE_PLAYING);
if ( ret == GST_STATE_CHANGE_FAILURE )
{
wxLogWarning("Unable to set the pipeline to the playing state.");
gst_object_unref(m_pipeline);
m_pipeline = NULL;
m_playButton->Enable(true);
m_stopButton->Enable(false);
}
else
{
m_playButton->Enable(false);
m_stopButton->Enable(true);
}
}
class MyApp : public wxApp
{
public:
bool OnInit() override
{
MainWindow* mainWindow = new MainWindow("wxWidgets GStreamer demo");
mainWindow->Show();
return true;
}
};
wxIMPLEMENT_APP(MyApp);
On mint it looks like this:
On windows it looks like this:
I'm junior programmer
recently, I have implemented grabbing of Image using Halcon library.
when I press live button, Timer start to grab image. it works but main screen freezes to the timer cycle.
so, I am improving performance grabbing of Image using Thread
first I implemented thread like this
[ImageUpdateWorker.h]
class ImageUpdateWorker : public QObject
{
Q_OBJECT
public:
explicit ImageUpdateWorker(QObject* parent = 0, QString strThreadName = "ImageUpdateWorker");
~ImageUpdateWorker();
signals:
void finished();
void grab();
public slots:
void run();
private:
bool m_bStop{ false };
};
[ImageUpdateWorker.cpp]
ImageUpdateWorker::ImageUpdateWorker(QObject* parent, QString strThreadName)
: QObject(parent)
{
setObjectName(strThreadName);
}
ImageUpdateWorker::~ImageUpdateWorker()
{
}
void ImageUpdateWorker::run()
{
while (m_bStop == false)
{
emit grab();
}
emit finished();
}
second I implemented inherited QWidget UI Widget with output Screen like this
m_pThread = new QThread();
m_pUpdateWorker = new ImageUpdateWorker(nullptr, strName);
m_pUpdateWorker->moveToThread(m_pThread); // UpdateWorker move to Thread
connect(m_pThread, SIGNAL(started()), m_pUpdateWorker, SLOT(run()));
connect(m_pThread, SIGNAL(finished()), m_pThread, SLOT(deleteLater()));
connect(m_pUpdateWorker, SIGNAL(finished()), m_pThread, SLOT(quit()));
connect(m_pUpdateWorker, SIGNAL(finished()), m_pUpdateWorker, SLOT(deleteLater()));
connect(m_pUpdateWorker, SIGNAL(grab()), this, SLOT(onGrab()));
when I call "m_pThread->start();" screen starts to blokcing :(
If you have any advice or information, I would appreciate it. thank you for reading.
Use m_pImageUpdateThread->moveToThread(m_pThread);
I don't know in QT.
I sent you the code I used in C#.
Mainly you must use the delegates if you don't want to freeze the GUI.
hdisplay is the object HalconDotNet:HWindowControlWPF.
camera is a class where I define the camera parameters.
inside camera.Grab there is the code:
HOperatorSet.GrabImage(out ho_Image, _AcqHandle);
h_Image = new HImage(ho_Image);
At the initialization there is the code:
// Initialise the delegate
updateLiveDelegate = new UpdateLiveDelegate(this.UpdateLive);
HImage ho_Image = new HImage();
Here the code I use:
// ==================
// LIVE
// ==================
bool stopLive = true;
// Declare the thread
private Thread liveThread = null;
// Declare a delegate used to communicate with the UI thread
private delegate void UpdateLiveDelegate();
private UpdateLiveDelegate updateLiveDelegate = null;
private void btnLive_Click(object sender, RoutedEventArgs e)
{
try
{
stopLive = !stopLive;
// if stopLive = false, live camera is activated
if (!stopLive)
{
// Launch the thread
liveThread = new Thread(new ThreadStart(Live));
liveThread.Start();
}
}
catch (Exception ex)
{
// Error
}
}
private void Live()
{
try
{
while (stopLive == false)
{
if (camera.Grab(out ho_Image))
{
// Show progress
Dispatcher.Invoke(this.updateLiveDelegate);
}
else
{
// No grab
stopLive = true;
}
}
// here stopLive is true
}
catch (Exception ex)
{
// Error
}
}
private void UpdateLive()
{
try
{
int imageHeight;
int imageWidth;
string imageType;
ho_Image.GetImagePointer1(out imageType, out imageWidth, out imageHeight);
hDisplay.HalconWindow.SetPart(0, 0, imageHeight - 1, imageWidth - 1);
// display
hDisplay.HalconWindow.DispImage(ho_Image);
}
catch (Exception ex)
{
// Error
}
}
I am trying to determine if you can move one object instance to different threads at different points during run-time.
Below is some sample code to show you what I mean:
this->thread1 = new QThread( this );
this->thread2 = new QThread( this );
this->pObject->moveToThread( this->thread1 );
connect(this->thread1, SIGNAL(started()), this->pObject, SLOT(fnc1()));
connect(this->thread2, SIGNAL(started()), this->pObject, SLOT(fnc2()));
this->thread1->start();
//after thread1 has finished
this->pObject->moveToThread( this->thread2 );
this->thread2->start();
Is it possible to do this?
Edit: After Kuba's advice on not using a direct connection and him pointing out I must be interfering with the event loop somehow, I realised manually terminating the threads was not a good idea. I am adding my termination of the threads here to show where I am going wrong and to try and find a better way of achieving the same result.
connect(this->pObject, SIGNAL(finished()), this, SLOT(stopThread()));
void Class::stopThread( void )
{
if( this->thread1->isRunning() )
{
this->thread1->terminate();
return;
}
if( this->thread2->isRunning() )
{
this->thread2->terminate();
this->pObject->moveToThread( this->thread1 );
}
}
void Object::fnc1( void )
{
/*Does some work..*/
finished(); //Calls 'finished' to signal stopThread when done (not stopping on its own)
}
ADDITIONAL INFO
I have MainClass which holds the instances to both thread1, thread2 and pObject (pointer to the object I am trying to move from thread1 to thread2 and back again if necessary).
Main class constructor:
MainClass::MainClass( QWidget *parent ) : QMainWindow(parent)
{
this->ui.setupUi(this);
this->thread1 = new QThread( this );
this->thread2 = new QThread( this );
this->pObject->moveToThread( this->thread1 );
connect( this->pObject, SIGNAL(finished()), this, SLOT(stopThread()) );
connect( this->thread1, SIGNAL(started()), this->pObject, SLOT(fnc1()) );
connect( this->thread2, SIGNAL(started()), this->pObject, SLOT(fnc2()) );
}
Slot for when menu item is clicked:
void MainClass::on_action_call_fnc1_triggered( void )
{
if( this->thread1->isRunning() )
return;
/*EXECUTES SOME CLASSIFIED CODE THAT CANNOT BE SHOWN*/
this->thread1->start();//should trigger fnc1 execution
}
fnc1 held in Object class that is called when thread1 starts:
void Object::fnc1( void )
{
/*DOES SOME MORE SECRET PROCESSING THAT CANNOT BE SHOWN*/
this->finished(); // triggers MainClass::stopThread( void )
}
Slot for when menu item is clicked to start fnc2 executing:
void MainClass::on_action_call_fnc2_triggered( void )
{
if( this->thread1->isRunning() || this->thread2->isRunning() )
return;
this->pObject->moveToThread( this->thread2 );
this->thread2->start();//should trigger fnc2 execution
}
fnc2 held in Object:
void Object::fnc2( void )
{
/*DOES SOME MORE SECRET PROCESSING THAT CANNOT BE SHOWN*/
this->finished(); // triggers MainClass::stopThread( void )
}
stopThread function:
void MainClass::stopThread( void )
{
if( this->thread1->isRunning() )
{
/*this->thread1->quit();
this->thread1->exit();*/
this->thread1->terminate();
//this->thread1->wait( 0 ); //Trying different ways of stopping the thread
return;
}
if( this->thread2->isRunning() )
{
this->thread2->terminate();
//this->thread2->quit(); // Trying different ways of stopping the thread
//this->thread2->exit();
this->pObject->moveToThread( this->thread1 );
}
}
It will work, but you have to assert that the object's thread is indeed finished:
Q_ASSERT(pObject->thread() == nullptr);
pObject->moveToThread(thread2);
When thread1 is finished, the object's thread becomes null and only then you're allowed to move it to another thread from arbitrary thread. Otherwise, if object's thread is not finished yet, you could only move the object from its thread:
QTimer::singleShot(0, pObject, [this]{ pObject->moveToThread(thread2); }
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.
I've trying to get a pointer to a widget (in the code it's named text). But at least I've got only this
error: no matching function for call to 'std::basic_ostream::basic_ostream(wxWindow*)'
my code:
gui.h
#include <wx/wx.h>
class wxWCK : public wxFrame
{
public:
wxWCK(const wxString& title);
void OnClickCon(wxCommandEvent& event);
void OnClickSta(wxCommandEvent& event);
private:
wxButton *connect;
wxButton *start;
wxTextCtrl *text;
};
const int ID_CON = 100;
const int ID_STA = 101;
const int ID_MF0 = 102;
const int ID_TEX = 103;
void Connect();
void Start();
and gui.cpp
#include "gui.h"
wxWCK::wxWCK(const wxString& title)
: wxFrame(NULL, ID_MF0, title, wxDefaultPosition, wxSize(400,300))
{
wxPanel *panel = new wxPanel(this, -1);
wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
wxBoxSizer *hbox1 = new wxBoxSizer(wxHORIZONTAL);
wxBoxSizer *hbox2 = new wxBoxSizer(wxHORIZONTAL);
//Outputwidget
text = new wxTextCtrl(panel, ID_TEX, _T(""),
wxPoint(-1, -1), wxSize(1000, 1000), wxTE_MULTILINE);
//redirecting stream to the outputwidget
//std::ostream stream(text);
stream << "Hello" << std::endl;
connect = new wxButton(panel, ID_CON, _T("Connect"));
start = new wxButton(panel, ID_STA, _T("Start"));
hbox1->Add(text);
hbox2->Add(connect);
hbox2->Add(start);
vbox->Add(hbox1, 1, wxEXPAND);
vbox->Add(hbox2, 0, wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 10);
panel->SetSizer(vbox);
Connect(ID_CON, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(wxWCK::OnClickCon));
//Connect(ID_STA, wxEVT_COMMAND_BUTTON_CLICKED,
// wxCommandEventHandler(wxWCK::OnClickSta));
}
void wxWCK::OnClickCon(wxCommandEvent& WXUNUSED(event))
{ void Connect(); }
void wxWCK::OnClickSta(wxCommandEvent& WXUNUSED(event))
{ void Start(); }
void Connect()
{
//Try to get a pointer to 'text'
std::ostream stream(wxWindow::FindWindowById(ID_TEX));
stream << "Connected" << std::endl;
}
/*
void Start()
{
//Try to get a pointer to 'text'
std::ostream stream(wxWindow::FindWindowById(ID_TEX));
stream << "Started" << std::endl;
}
*/
I hope somebody can help me. A other solution can be to get the stream as global. But when I try to get the stream-declaration in the header, he says he dont know any text so I move the text-declaration outside the class and I got a muliple declaration error... I think, because I include the gui.h in gui.cpp and main.h .
-Casisto
edit:
I changed the the function to:
void Connect()
{
//Try to get a pointer to 'text'
std::ostream stream((wxTextCtrl*) wxWindow::FindWindowById(ID_TEX));
stream << "Connected" << std::endl;
}
Now I don't get a error or a warning, but when I click on "Connect"-Button, the wxTextCtrl get no "Input" (I mean, there is only "Hello" in there)
The initial error must have been from the old code of Connect() because wxWindow::FindWindowById() returns a wxWindow*.
First thing to try: change wxWCK::OnClickCon() to { (*text) << "Connected\n"; }. This should just work.
Next, if you really need that void Connect(), try again without creating the std::ostream (you don't really need it); something like { (*(wxTextCtrl*)wxWindow::FindWindowById(ID_TEX)) << "Connected\n"; }.
However, this might still not work, so make ID_CON = wxID_HIGHEST + 1, ID_STA = wxID_HIGHEST + 2 etc and try again. IIRC I've seen cases where ids with low values caused curious behaviour.
The error message doesn't fit the code you show, in it text is a wxTextCtrl, which derives from std::streambuf and so can be used to construct an associated std::ostream, and not a wxWindow.
IOW your code should work as shown, you must be not showing us everything.