Eventually figured out how to delete the associated log target when a wxLogWindow is closed, but for some reason it prevents the app from closing cleanly, which has been reported elsewhere, as i recall.
In the end decided to go the custom log target + frame route and reasonably happy with the result, which can be treated pretty much like an ordinary frame. It's a small class which allows FIFO or free running modes and has a few buttons to allow control of the entries as they scroll through a wxTextCtrl. Used the TextCtrl itself as the memory buffer in FIFO mode, deleting a line from the top of the ctrl when a new one is added at the bottom, once the definable line limit is reached. The free running mode uses increasing amounts of memory as might be expected, but releases it when the frame closes, unlike wxLogFrame.
Here's the code - hopefully useful to someone out there
Any enhancements / problems / bugs, please let me know...
Code: Select all
///////////////////////////////////////////////////////////////////////////////
// Dual logging (file + frame) custom log target wxLogWindow alternative
///////////////////////////////////////////////////////////////////////////////
//
#ifndef WX_PRECOMP
#include "wx\wx.h"
#endif
#include "wx/log.h"
#include "wx/timer.h"
#include "wx/app.h"
#define ART_MAX_FIFO_LOG_MSG_COUNT 200
#define ART_MAX_LINE_LENGTH 500
enum
{
ART_START_STOP_BTN = wxID_HIGHEST + 1,
ART_PAUSE_CONTINUE_BTN,
ART_FIFO_DATA_TIMER,
ART_FIFO_MODE,
ART_FREE_RUNNING_MODE,
ART_OP_MODE_RUNNING,
ART_OP_MODE_STOPPED,
ART_OP_MODE_PAUSED
};
#define ART_NEED_TEST_DATA // Comment out when not testing
///////////////////////////////////////////////////////////////////////////////
//
class ArtFIFOLog : public wxFrame, wxLog
{
public:
ArtFIFOLog(int ThePrimaryMode = ART_FIFO_MODE) :
wxFrame((wxFrame *)0, wxID_ANY, "Event Log"), wxLog()
{
SetMinSize(wxSize(600, -1));
ShouldRun = true;
DataTimer = 0;
MsgCount = 0;
PrimaryMode = ThePrimaryMode;
OperationalMode = ART_OP_MODE_RUNNING;
// Create log target for frame (entries arrive via DoLogText() below)
new wxLogChain((wxLog *)this);
// Add the controls + connect to event handler
wxBoxSizer *TheTopSizer = new wxBoxSizer(wxVERTICAL);
wxPanel *ThePanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 2521440L | wxCLIP_CHILDREN);
LogCtrl = new wxTextCtrl(ThePanel,
wxID_ANY,
wxEmptyString,
wxDefaultPosition,
wxSize(600, 300),
wxTE_LEFT | wxTE_MULTILINE | wxNO_FULL_REPAINT_ON_RESIZE);
StartStopBtn = new wxButton(ThePanel, ART_START_STOP_BTN, "Stop");
PauseContinueBtn = new wxButton(ThePanel, ART_PAUSE_CONTINUE_BTN, "Pause");
TheTopSizer->Add(LogCtrl, 1, wxEXPAND | wxALL, 5);
TheTopSizer->Add(StartStopBtn, 0, wxEXPAND | wxALL, 5);
TheTopSizer->Add(PauseContinueBtn, 0, wxEXPAND | wxALL, 5);
Connect(ART_START_STOP_BTN, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(ArtFIFOLog::OnButton));
Connect(ART_PAUSE_CONTINUE_BTN, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(ArtFIFOLog::OnButton));
Connect(wxID_ANY, wxEVT_CLOSE_WINDOW,
wxCloseEventHandler(ArtFIFOLog::OnClose));
ThePanel->SetSizer(TheTopSizer);
TheTopSizer->Fit(this);
Show();
#ifdef ART_NEED_TEST_DATA
StartDataTimer();
#endif
}
private:
// wxLog supplied messages arrive here...
virtual void DoLogText(const wxString &TheMsg)
{
// Discard new entries
if(OperationalMode == ART_OP_MODE_STOPPED)
return;
// Store entries for later use
if(OperationalMode == ART_OP_MODE_PAUSED)
{
PauseBuffer.Add(TheMsg);
return;
}
// ART_OP_MODE_RUNNING
if(PrimaryMode == ART_FREE_RUNNING_MODE)
{
LogCtrl->AppendText(TheMsg + "\n");
}
else // ART_FIFO_MODE
{
// Append messages to the log until limit's reached
// then remove a line for each added
if(MsgCount < ART_MAX_FIFO_LOG_MSG_COUNT)
{
LogCtrl->AppendText(TheMsg + "\n");
MsgCount++;
}
else if(MsgCount >= ART_MAX_FIFO_LOG_MSG_COUNT)
{
Freeze();
RemoveTopLine();
LogCtrl->AppendText(TheMsg + "\n");
Thaw();
}
}
}
void RemoveTopLine(void)
{
// Find and remove top line
int TheEndPoint = LogCtrl->GetRange(0, ART_MAX_LINE_LENGTH).Find("\n");
LogCtrl->Remove(0, TheEndPoint + 2);
}
void OnButton(wxCommandEvent &TheEvent)
{
switch(TheEvent.GetId())
{
case ART_START_STOP_BTN:
{
if(OperationalMode == ART_OP_MODE_RUNNING)
{
OperationalMode = ART_OP_MODE_STOPPED;
StartStopBtn->SetLabel("Start");
PauseContinueBtn->Disable();
}
else if(OperationalMode == ART_OP_MODE_STOPPED)
{
OperationalMode = ART_OP_MODE_RUNNING;
StartStopBtn->SetLabel("Stop");
PauseContinueBtn->Enable();
}
break;
}
case ART_PAUSE_CONTINUE_BTN:
{
if(OperationalMode == ART_OP_MODE_RUNNING)
{
OperationalMode = ART_OP_MODE_PAUSED;
PauseContinueBtn->SetLabel("Continue");
StartStopBtn->Disable();
}
else if(OperationalMode == ART_OP_MODE_PAUSED)
{
// Display any buffered entries
DoLogCatchup();
OperationalMode = ART_OP_MODE_RUNNING;
PauseContinueBtn->SetLabel("Pause");
StartStopBtn->Enable();
}
}
}
}
void DoLogCatchup(void)
{
if(PrimaryMode == ART_FREE_RUNNING_MODE)
{
for(long j = 0; j < PauseBuffer.size(); j++)
LogCtrl->AppendText(PauseBuffer[j] + "\n");
}
else // ART_FIFO_MODE
{
// Clear the control and add the latter ART_MAX_FIFO_LOG_MSG_COUNT buffer entries
if(PauseBuffer.size() >= ART_MAX_FIFO_LOG_MSG_COUNT)
{
LogCtrl->Clear();
long TheStartIdx = PauseBuffer.size() - ART_MAX_FIFO_LOG_MSG_COUNT;
for(long j = TheStartIdx; j < PauseBuffer.size(); j++)
LogCtrl->AppendText(PauseBuffer[j] + "\n");
}
else // PauseBuffer.size() < ART_MAX_FIFO_LOG_MSG_COUNT
{
// Fill the ctrl to max if possible
long TheMakeupLimit = ART_MAX_FIFO_LOG_MSG_COUNT - PauseBuffer.size();
if(PauseBuffer.size() < TheMakeupLimit)
TheMakeupLimit = PauseBuffer.size();
for(long j = 0; j < TheMakeupLimit; j++)
LogCtrl->AppendText(PauseBuffer[j] + "\n");
// ... then enter normal FIFO mode
long j;
for(j = TheMakeupLimit; j < PauseBuffer.size(); j++)
{
Freeze();
RemoveTopLine();
LogCtrl->AppendText(PauseBuffer[j] + "\n");
Thaw();
}
MsgCount = j;
}
}
// Finally empty the buffer
PauseBuffer.Clear();
}
void OnClose(wxCloseEvent &TheEvent)
{
if(DataTimer)
{
DataTimer->Stop();
delete DataTimer;
}
// Remove the frame's Log Target as defined with new wxLogChain((wxLog *)this); in ctor
delete wxLog::SetActiveTarget(NULL);
}
#ifdef ART_NEED_TEST_DATA
void StartDataTimer(void)
{
DataTimer = new wxTimer(this, ART_FIFO_DATA_TIMER);
Connect(DataTimer->GetId(), wxEVT_TIMER,
wxTimerEventHandler(ArtFIFOLog::OnTimer), NULL, this);
DataTimer->Start(200, wxTIMER_CONTINUOUS);
}
void OnTimer(wxTimerEvent &event)
{
wxLogError("Test log entry................................................");
}
#endif
wxTextCtrl *LogCtrl;
long MsgCount;
wxButton *StartStopBtn;
wxButton *PauseContinueBtn;
wxTimer *DataTimer;
bool ShouldRun;
int PrimaryMode;
int OperationalMode;
wxArrayString PauseBuffer;
};
class MyApp : public wxApp
{
public:
virtual bool OnInit()
{
if(!wxApp::OnInit())
return false;
// Redirect default error target to file
FILE *LogFile = fopen(".\\TestLogFile.txt", "w+");
delete wxLog::SetActiveTarget(new wxLogStderr(LogFile));
// Setup log file timestamp DT format
wxLog::SetTimestamp("%Y-%m-%d %H:%M:%S");
// Create LogWindow (creates a wxLogChain)
//LogWindow = new wxLogWindow((wxWindow *)0, "Event Log", true);
FIFOLog = new ArtFIFOLog(); //ART_FREE_RUNNING_MODE);
return true;
}
int OnExit()
{
return(0);
}
private:
//wxLogWindow *LogWindow;
ArtFIFOLog *FIFOLog;
wxLogChain *LogChain;
};
IMPLEMENT_APP(MyApp)