J
Altes Topic, aber wenn man gerade auf der Suche nach Performance-Problemen mit grossen Textfiles ist ...
Wie hiess die Frage nochmal? Ob man Developer_X verbieten sollte eine wxGauge zur Anzeige des Ladeprozesses zu verwenden?
Spass beiseite:
Ich hab einfach die Implementation von wxTextFile genommen und mit einem Event gespickt. Das geht bei anderen
File Klassen genau so. Schöner wäre es natürlich wenn man die Read() Methode überschreiben würde. Hmmm, ich glaub das ging in dem Falle nicht so einfach, weil das m_file privat ist.
Header progresstextfile.h, Events + Deklaration
///////////////////////////////////////////////////////////////////////////////
// Name: progresstextfile.h
// Purpose: declaration of ProgressTextFile class
// Author: Jedzia
// Modified by:
// Created: 11.04.2011
// Copyright: (c) 2011 Jedzia
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef PROGRESSTEXTFILE_H
#define PROGRESSTEXTFILE_H
#include "wx/textbuf.h"
#include "wx/file.h"
#include <wx/thread.h>
#include <wx/string.h>
#include <wx/event.h>
DECLARE_EVENT_TYPE( LoadProgressCommandEvent, -1 )
// A custom event that transports the loading progress data.
class LoadProgressEvent: public wxCommandEvent
{
public:
LoadProgressEvent( wxEventType commandType = LoadProgressCommandEvent, int id =0 )
: wxCommandEvent(commandType, id) { }
// You *must* copy here the data to be transported
LoadProgressEvent( const LoadProgressEvent &event )
: wxCommandEvent(event) {
this->SetInt( event.GetInt() );
this->SetText( event.GetText() );
} // Required for sending with wxPostEvent()
wxEvent* Clone() const { return new LoadProgressEvent(*this); }
wxString GetText() const { return m_Text; }
void SetText( const wxString& text ) { m_Text = text; }
private:
wxString m_Text;
};
typedef void (wxEvtHandler::*LoadProgressEventFunction)(LoadProgressEvent &);
#define LoadProgressEventHandler(func) (wxObjectEventFunction)(wxEventFunction)(wxCommandEventFunction) wxStaticCastEvent(LoadProgressEventFunction, &func)
#define EVT_LOAD_PROGRESS(winid, func) wx__DECLARE_EVT1(LoadProgressCommandEvent, winid, LoadProgressEventHandler(func))
#define EVT_LOAD_PROGRESS_RANGE(id1,id2, fn) wxDECLARE_EVENT_TABLE_ENTRY(LoadProgressCommandEvent, id1, id2, LoadProgressEventHandler(fn), (wxObject*)NULL ),
// ----------------------------------------------------------------------------
// ProgressTextFile
// ----------------------------------------------------------------------------
class WXDLLIMPEXP_BASE ProgressTextFile : public wxTextBuffer
{
public:
enum {
Loading = 1,
Parsing,
};
// constructors
ProgressTextFile() { m_pHandler = NULL; }
//ProgressTextFile(const wxString& strFileName);
ProgressTextFile(const wxString& strFileName, wxEvtHandler *handler = NULL);
protected:
// implement the base class pure virtuals
virtual bool OnExists() const;
virtual bool OnOpen(const wxString &strBufferName,
wxTextBufferOpenMode OpenMode);
virtual bool OnClose();
virtual bool OnRead(const wxMBConv& conv);
virtual bool OnWrite(wxTextFileType typeNew, const wxMBConv& conv);
protected:
wxEvtHandler *m_pHandler;
private:
wxFile m_file;
wxDECLARE_NO_COPY_CLASS(ProgressTextFile);
};
Source progresstextfile.cpp, Implementation
///////////////////////////////////////////////////////////////////////////////
// Name: progresstextfile.cpp ( from wxWidgeds src/common/textfile.cpp )
// Purpose: implementation of ProgressTextFile class
// Author: Vadim Zeitlin
// Modified by: Jedzia 2011
// Created: 03.04.98
// RCS-ID: $Id: textfile.cpp 61508 2009-07-23 20:30:22Z VZ $
// Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// ============================================================================
// headers
// ============================================================================
#include "progresstextfile.h" // class's header file
#include "wx/wxprec.h"
#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__
#ifndef WX_PRECOMP
#include "wx/string.h"
#include "wx/intl.h"
#include "wx/file.h"
#include "wx/log.h"
#endif
#include "wx/textfile.h"
#include "wx/filename.h"
#include "wx/buffer.h"
DEFINE_EVENT_TYPE( LoadProgressCommandEvent )
// ============================================================================
// ProgressTextFile class implementation
// ============================================================================
/*ProgressTextFile::ProgressTextFile(const wxString& strFileName)
: wxTextBuffer(strFileName)
{
m_pHandler = NULL;
}*/
ProgressTextFile::ProgressTextFile(const wxString& strFileName, wxEvtHandler *handler)
: wxTextBuffer(strFileName)
{
m_pHandler = handler;
}
// ----------------------------------------------------------------------------
// file operations
// ----------------------------------------------------------------------------
bool ProgressTextFile::OnExists() const
{
return wxFile::Exists(m_strBufferName);
}
bool ProgressTextFile::OnOpen(const wxString &strBufferName, wxTextBufferOpenMode OpenMode)
{
wxFile::OpenMode FileOpenMode;
switch ( OpenMode )
{
default:
wxFAIL_MSG( wxT("unknown open mode in ProgressTextFile::Open") );
// fall through
case ReadAccess :
FileOpenMode = wxFile::read;
break;
case WriteAccess :
FileOpenMode = wxFile::write;
break;
}
return m_file.Open(strBufferName.c_str(), FileOpenMode);
}
bool ProgressTextFile::OnClose()
{
return m_file.Close();
}
bool ProgressTextFile::OnRead(const wxMBConv& conv)
{
int lastpercent = -1;
int nxRemaining = -1;
// file should be opened
wxASSERT_MSG( m_file.IsOpened(), wxT("can't read closed file") );
// read the entire file in memory: this is not the most efficient thing to
// do it but there is no good way to avoid it in Unicode build because if
// we read the file block by block we can't convert each block to Unicode
// separately (the last multibyte char in the block might be only partially
// read and so the conversion would fail) and, as the file contents is kept
// in memory by ProgressTextFile anyhow, it shouldn't be a big problem to read
// the file entirely
size_t bufSize = 0;
// number of bytes to (try to) read from disk at once
static const size_t BLOCK_SIZE = 4096;
wxCharBuffer buf;
// first determine if the file is seekable or not and so whether we can
// determine its length in advance
wxFileOffset fileLength;
{
wxLogNull logNull;
fileLength = m_file.Length();
}
// some non-seekable files under /proc under Linux pretend that they're
// seekable but always return 0; others do return an error
const bool seekable = fileLength != wxInvalidOffset && fileLength != 0;
if ( seekable )
{
// we know the required length, so set the buffer size in advance
bufSize = fileLength;
if ( !buf.extend(bufSize) )
return false;
// if the file is seekable, also check that we're at its beginning
wxASSERT_MSG( m_file.Tell() == 0, wxT("should be at start of file") );
char *dst = buf.data();
int lastpercent = -1;
nxRemaining = bufSize;
for ( size_t nRemaining = bufSize; nRemaining > 0; )
{
size_t nToRead = BLOCK_SIZE;
// the file size could have changed, avoid overflowing the buffer
// even if it did
if ( nToRead > nRemaining )
nToRead = nRemaining;
ssize_t nRead = m_file.Read(dst, nToRead);
if ( nRead == wxInvalidOffset )
{
// read error (error message already given in wxFile::Read)
return false;
}
if ( nRead == 0 )
{
// this file can't be empty because we checked for this above
// so this must be the end of file
break;
}
dst += nRead;
nRemaining -= nRead;
// Event bei Ladevorgang auslösen
if(m_pHandler)
{
int percent = 100 - ((nRemaining / (float) fileLength) * 100);
if(percent > lastpercent)
{
//wxString bar = wxString::Format(wxT("%d bytes remaining.\n"), nRemaining);
//wxString bar = wxString::Format(wxT("%d bytes remaining.\n"), percent);
LoadProgressEvent event( LoadProgressCommandEvent, Loading );
event.SetInt( percent );
wxPostEvent( m_pHandler, event ); // SEND CUSTOM EVENT TO GUI MAINTHREAD
wxThread::Sleep(5);
lastpercent = percent;
}
}
}
wxASSERT_MSG( dst - buf.data() == (wxFileOffset)bufSize,
wxT("logic error") );
}
else // file is not seekable
{
char block[BLOCK_SIZE];
for ( ;; )
{
ssize_t nRead = m_file.Read(block, WXSIZEOF(block));
if ( nRead == wxInvalidOffset )
{
// read error (error message already given in wxFile::Read)
return false;
}
if ( nRead == 0 )
{
// if no bytes have been read, presumably this is a
// valid-but-empty file
if ( bufSize == 0 )
return true;
// otherwise we've finished reading the file
break;
}
// extend the buffer for new data
if ( !buf.extend(bufSize + nRead) )
return false;
// and append it to the buffer
memcpy(buf.data() + bufSize, block, nRead);
bufSize += nRead;
}
}
const wxString str(buf, conv, bufSize);
// there's no risk of this happening in ANSI build
#if wxUSE_UNICODE
if ( bufSize > 4 && str.empty() )
{
wxLogError(_("Failed to convert file \"%s\" to Unicode."), GetName());
return false;
}
#endif // wxUSE_UNICODE
// we don't need this memory any more
buf.reset();
// now break the buffer in lines
// last processed character, we need to know if it was a CR or not
wxChar chLast = '\0';
// the beginning of the current line, changes inside the loop
wxString::const_iterator lineStart = str.begin();
const wxString::const_iterator end = str.end();
int cnt = 0;
lastpercent = -1;
for ( wxString::const_iterator p = lineStart; p != end; p++ )
{
cnt++;
const wxChar ch = *p;
switch ( ch )
{
case '\n':
// could be a DOS or Unix EOL
if ( chLast == '\r' )
{
if ( p - 1 >= lineStart )
{
AddLine(wxString(lineStart, p - 1), wxTextFileType_Dos);
}
else
{
// there were two line endings, so add an empty line:
AddLine(wxEmptyString, wxTextFileType_Dos);
}
}
else // bare '\n', Unix style
{
AddLine(wxString(lineStart, p), wxTextFileType_Unix);
}
lineStart = p + 1;
break;
case '\r':
if ( chLast == '\r' )
{
// Mac empty line
AddLine(wxEmptyString, wxTextFileType_Mac);
lineStart = p + 1;
}
//else: we don't know what this is yet -- could be a Mac EOL or
// start of DOS EOL so wait for next char
break;
default:
if ( chLast == '\r' )
{
// Mac line termination
if ( p - 1 >= lineStart )
{
AddLine(wxString(lineStart, p - 1), wxTextFileType_Mac);
}
else
{
// there were two line endings, so add an empty line:
AddLine(wxEmptyString, wxTextFileType_Mac);
}
lineStart = p;
}
}
chLast = ch;
// Event beim Line Parsen auslösen
if(m_pHandler)
{
int percent = ((cnt / (float) fileLength) * 100);
if(percent > lastpercent)
{
//wxString bar = wxString::Format(wxT("%d bytes remaining.\n"), nRemaining);
//wxString bar = wxString::Format(wxT("%d bytes remaining.\n"), percent);
LoadProgressEvent event( LoadProgressCommandEvent, Loading );
event.SetInt( percent );
wxPostEvent( m_pHandler, event ); // SEND CUSTOM EVENT TO GUI MAINTHREAD
lastpercent = percent;
}
}
}
// anything in the last line?
if ( lineStart != end )
{
// add unterminated last line
AddLine(wxString(lineStart, end), wxTextFileType_None);
}
return true;
}
bool ProgressTextFile::OnWrite(wxTextFileType typeNew, const wxMBConv& conv)
{
wxFileName fn = m_strBufferName;
// We do NOT want wxPATH_NORM_CASE here, or the case will not
// be preserved.
if ( !fn.IsAbsolute() )
fn.Normalize(wxPATH_NORM_ENV_VARS | wxPATH_NORM_DOTS | wxPATH_NORM_TILDE |
wxPATH_NORM_ABSOLUTE | wxPATH_NORM_LONG);
wxTempFile fileTmp(fn.GetFullPath());
if ( !fileTmp.IsOpened() ) {
wxLogError(_("can't write buffer '%s' to disk."), m_strBufferName.c_str());
return false;
}
size_t nCount = GetLineCount();
for ( size_t n = 0; n < nCount; n++ ) {
fileTmp.Write(GetLine(n) +
GetEOL(typeNew == wxTextFileType_None ? GetLineType(n)
: typeNew),
conv);
}
// replace the old file with this one
return fileTmp.Commit();
}
Verwendung z.B. in deinem Frame
Header:
#include "progresstextfile.h"
...
void OnIndexLoadEvent( LoadProgressEvent &event );
Source:
BEGIN_EVENT_TABLE( MainForm, wxFrame )
...
EVT_THREAD(WORKER_EVENT, MainForm::OnThreadCompletion)
EVT_LOAD_PROGRESS(wxID_ANY, MainForm::OnIndexLoadEvent)
END_EVENT_TABLE()
...
void MainForm::OnIndexLoadEvent( LoadProgressEvent &event )
{
switch( event.GetId() )
{
case ProgressTextFile::Loading:
{
int percent = event.GetInt();
wxString msg = wxString::Format(wxT("Loading %d%%.\n"), percent);
statusbar->SetStatusText(msg);
progressbar->SetValue(percent);
}
break;
case ProgressTextFile::Parsing:
{
int percent = event.GetInt();
wxString msg = wxString::Format(wxT("Parsing %d%%.\n"), percent);
statusbar->SetStatusText(msg);
progressbar->SetValue(percent);
}
break;
}
}
Das File wird dann in einem Thread in der Entry() Methode mit
wxThread::ExitCode IndexLoadThread::Entry()
{
ProgressTextFile * file = new ProgressTextFile(m_filename, m_pHandler);
if (file->Exists())
{
if(!file->Open())
{
delete file;
file = NULL;
}
}
else
{
delete file;
file = NULL;
}
wxThreadEvent event( wxEVT_COMMAND_THREAD, MainForm::WORKER_EVENT );
event.SetClientData (file);
//event.SetInt( 50 );
wxQueueEvent( m_pHandler, event.Clone() );
return (wxThread::ExitCode)0; // success
}
geladen und an das MainForm per event zurückgegeben.
An den m_pHandler kommt man über die GetEventHandler() Methode eines Widgets ( wxEvtHandler *m_pHandler; ).
void MainForm::OnThreadCompletion(wxThreadEvent& event)
{
m_sf = static_cast<wxTextFile *>(event.GetClientData());
if(m_sf)
{
toolbar->EnableTool(ID_SEARCH, true);
statusbar->SetStatusText(wxT("Index-file ") + GetSearchFileName() + wxT(" loaded. "));
}
else
statusbar->SetStatusText(wxT("Error while loading the index-file ") + GetSearchFileName());
}
Sieht nur so kompliziert aus, weil die wxTextFile-Implementation 1:1 übernommen wurde.
Mfg. Jed