Introduction
This article is about a file preview control (see the figure below). You may find such a file preview control helpful when you need to display content of a file as text, hexadecimal (HEX) dump or as an image. The control is based on WTL's CStatic
control - that means you can use the code in your WTL-based application. You can preview your files as HEX, text (Latin-1) and image (BMP) formats. File size doesn't matter - the control can preview even huge files without noticeable delays, because it loads the file in a separate thread.
The article provides a simple demo application and the source code of the control.
Figure 1 - Hexadecimal (HEX) file preview
Background
Some time ago, I needed a way for previewing content of files for one of my open-source projects - CrashRpt, a crash reporting library for Windows applications. When your app crashes, the CrashRpt
library generates an error report archive containing some files, such as crash minidump, error logs, desktop screenshots and so on. And user should be able to review the file contents before sending the error report over the Internet. So, I needed a control for previewing files in HEX, text and image format (see the figure below).
Figure 2 - Error Report Details Dialog of CrashRpt Library
Browsing the web didn't give me a control that fully sufficed my needs, so I decided to write my own control. This article describes a light-weight file preview control that can preview Latin-1 text files, binary files in HEX and BMP image files, because I don't want to overweight the code with additional library dependencies (libpng
, libjpeg
, and so on). But if you need more capabilities (UTF-8 and UTF-16 text preview, JPEG and PNG image preview), you may refer to CrashRpt source code and find the original, more powerful file preview control.
Using the Code
Using the control in your WTL application is very simple. You just need to copy FilePreviewCtrl.h and FilePreviewCtrl.cpp files to your project directory and add those files to your Visual C++ project. Put a static
control on your dialog and set static
control's name to IDC_PREVIEW
. Next add the #include "FilePreviewCtrl.h"
line to the beginning of your dialog's header file and add
CFi
lePreviewCtrl
m_filePreview;
member variable to your dialog's (or window's) class. Finally, in your OnInitDialog()
handler, subclass the static
control by adding the following line:
m_filePreview.SubclassWindow(GetDlgItem(IDC_PREVIEW));
Below there are several methods provided by CFilePreviewCtrl
class that you can use to preview files and customize control's behavior.
To open the file for preview, use SetFile()
method. To get the name of the currently previewed file, use GetFile()
method.
LPCTSTR GetFile();
BOOL SetFile(LPCTSTR szFileName, PreviewMode mode=PREVIEW_AUTO);
To set the current preview mode, use SetPreviewMode()
method. Using the GetPreviewMode()
allows to get the current preview mode.
PreviewMode GetPreviewMode();
void SetPreviewMode(PreviewMode mode);
The preview mode is defined by the PreviewMode
enumeration (see below). As you can see, the file preview control can detect the preview mode automatically (PREVIEW_AUTO
constant) or you can force another preview mode by specifying PREVIEW_HEX
, PREVIEW_TEXT
or PREVIEW_IMAGE
constant.
enum PreviewMode
{
PREVIEW_AUTO = -1, PREVIEW_HEX = 0, PREVIEW_TEXT = 1, PREVIEW_IMAGE = 2 };
Figure 3 - Text File Preview (Latin-1 Encoding)
Figure 4 - Image File Preview (BMP)
You can use the DetectPreviewMode()
method to determine what preview mode will be automatically chosen for a certain file.
PreviewMode DetectPreviewMode(LPCTSTR szFileName);
When there is nothing to preview, the file preview control displays empty screen with "No data to display" message on the top. You can override the text message by using SetEmptyMessage()
method.
void SetEmptyMessage(CString sText);
For HEX preview mode, it is possible to modify the number of bytes per line displayed by calling the SetBytesPerLine()
method.
BOOL SetBytesPerLine(int nBytesPerLine);
Points of Interest
One may ask how the file preview control autodetects the correct preview mode? It does this using two ways: by file extension and by file heading bytes.
First it checks file extension. If the file extension is TXT, INI, LOG, XML, HTM, HTML, JS, C, H, CPP, HPP, then the control assumes this is a text file. If not, the control loads first several bytes of the file and compares it with the BMP file signature (all BMP files have "BM
" magic characters in the beginning of the file). If the signature matches, the control assumes the file is a bitmap image file. If not, the control assumes the file is an unknown binary file and selects the HEX preview mode for it.
And some words on how this control previews huge text, image and binary files so rapidly.
Two things contribute to its preview speed: usage of file mapping and multithreading.
A file mapping is a Win32 object allowing you to map an arbitrarily large file to the operating memory and access only the part of the file by creating file view. This way, you can rapidly access any portion of the large binary file without wasting the memory and without time delays. You can find the CFileMemoryMapping
class in the FilePreviewCtrl.h
header file. Below the declaration of the CFileMemoryMapping
class is presented:
class CFileMemoryMapping
{
public:
CFileMemoryMapping();
~CFileMemoryMapping();
BOOL Init(LPCTSTR szFileName);
BOOL Destroy();
ULONG64 GetSize();
LPBYTE CreateView(DWORD dwOffset, DWORD dwLength);
private:
HANDLE m_hFile; HANDLE m_hFileMapping; DWORD m_dwAllocGranularity; ULONG64 m_uFileLength; CCritSec m_csLock; std::map<DWORD, LPBYTE> m_aViewStartPtrs; };
The CFileMemoryMapping::Init()
method uses CreateFile()
and CreateFileMapping()
WinAPI functions for initializing the file mapping object. Creating the file mapping doesn't actually allocate a memory. The memory allocation is performed in CFileMemoryMapping::CreateView()
method that uses MapViewOfFile()
API call to memory-map a small portion (view) of the file and returns the pointer to it. When the allocated view is not needed anymore, it is unmapped with the help of UnmapViewOfFile()
API call.
The CFileMemoryMapping
class allows to create several views at the same time to access them from different threads simultaneously. The created views are stored in CFileMemoryMapping::m_aViewStartPtrs
variable.
Multithreading is used when you need to perform a time-consuming work without blocking the main thread. To do the work asynchronously, another thread is created with the help of CreateThread()
WinAPI function and called the worker thread. The file preview control performs text file parsing in that another thread (text parsing is needed to determine line breaks). And it loads an image in another thread, too. You can find out how it does this by looking at the code of CFilePreviewCtrl::DoInWorkerThread()
private method.
While the worker thread performs image loading or text parsing, the main thread displays the portions that are ready for preview on timer events (WM_TIMER
message). Scrollbars are also updated on timer. When the worker thread finishes loading file, it sends the WM_FPC_COMPLETE
private message to the file preview control window to notify it about completion.
The asynchronous loading/parsing operation may even be cancelled when user opens another file for preview. To cancel the operation, the main thread sets the CFilePreviewCtrl::m_bCancelled
flag and waits for worker thread's exiting. When the worker thread encounters the cancel flag, it returns from the thread procedure.
History
- 28th May 2011 - Initial release