FilePreviewCtrl - 以文本、HEX 和图像格式预览文件






4.56/5 (12投票s)
本文演示了 WTL 应用程序中的文件预览控件。
引言
本文介绍了一个文件预览控件(参见下图)。当您需要以文本、十六进制 (HEX) 转储或图像形式显示文件内容时,这种文件预览控件可能会很有用。该控件基于 WTL 的 CStatic
控件 - 这意味着您可以在基于 WTL 的应用程序中使用该代码。您可以以 HEX、文本 (Latin-1) 和图像 (BMP) 格式预览文件。文件大小无关紧要 - 该控件甚至可以预览大文件,而不会出现明显的延迟,因为它会在单独的线程中加载文件。
本文提供了一个简单的演示应用程序和控件的源代码。
背景
前段时间,我需要一种方法来预览我的一个开源项目的文件内容——CrashRpt,一个用于 Windows 应用程序的崩溃报告库。当您的应用程序崩溃时,CrashRpt
库会生成一个错误报告存档,其中包含一些文件,例如崩溃 minidump、错误日志、桌面截图等等。用户应该能够在通过互联网发送错误报告之前查看文件内容。所以,我需要一个控件来以 HEX、文本和图像格式预览文件(参见下图)。
浏览网页并没有找到完全满足我需求的控件,所以我决定自己编写一个。本文描述了一个轻量级的文件预览控件,它可以预览 Latin-1 文本文件、HEX 格式的二进制文件和 BMP 图像文件,因为我不想通过额外的库依赖(libpng
、libjpeg
等)使代码变得臃肿。但是,如果您需要更多功能(UTF-8 和 UTF-16 文本预览、JPEG 和 PNG 图像预览),您可以参考CrashRpt 源代码,找到更强大的原始文件预览控件。
Using the Code
在您的 WTL 应用程序中使用该控件非常简单。您只需将 FilePreviewCtrl.h 和 FilePreviewCtrl.cpp 文件复制到您的项目目录,并将这些文件添加到您的 Visual C++ 项目中。在您的对话框上放置一个 static
控件,并将 static
控件的名称设置为 IDC_PREVIEW
。接下来,将 #include "FilePreviewCtrl.h"
行添加到对话框头文件的开头,并添加 CFilePreviewCtrl m_filePreview;
成员变量到您的对话框(或窗口)类。最后,在您的 OnInitDialog()
处理程序中,通过添加以下行来子类化 static
控件
m_filePreview.SubclassWindow(GetDlgItem(IDC_PREVIEW));
下面是 CFilePreviewCtrl
类提供的一些方法,您可以使用它们来预览文件和自定义控件的行为。
要打开文件进行预览,请使用 SetFile()
方法。要获取当前预览文件的名称,请使用 GetFile()
方法。
// Returns the file name of the current file
LPCTSTR GetFile();
// Sets current file and preview mode.
// You can pass NULL as file name to clear the preview.
BOOL SetFile(LPCTSTR szFileName, PreviewMode mode=PREVIEW_AUTO);
要设置当前预览模式,请使用 SetPreviewMode()
方法。使用 GetPreviewMode()
可以获取当前预览模式。
// Returns current preview mode
PreviewMode GetPreviewMode();
// Sets current preview mode
void SetPreviewMode(PreviewMode mode);
预览模式由 PreviewMode
枚举定义(见下文)。如您所见,文件预览控件可以自动检测预览模式(PREVIEW_AUTO
常量),或者您可以通过指定 PREVIEW_HEX
、PREVIEW_TEXT
或 PREVIEW_IMAGE
常量来强制使用其他预览模式。
// Preview mode
enum PreviewMode
{
PREVIEW_AUTO = -1, // Auto
PREVIEW_HEX = 0, // Hex
PREVIEW_TEXT = 1, // Text
PREVIEW_IMAGE = 2 // Image
};
您可以使用 DetectPreviewMode()
方法来确定特定文件将自动选择哪种预览模式。
// Detects a correct preview mode for certain file
PreviewMode DetectPreviewMode(LPCTSTR szFileName);
当没有内容可预览时,文件预览控件会显示一个空白屏幕,顶部显示“无数据显示”消息。您可以使用 SetEmptyMessage()
方法覆盖此文本消息。
// Sets the text to display when nothing to preview (the default is "No data to display")
void SetEmptyMessage(CString sText);
对于 HEX 预览模式,可以通过调用 SetBytesPerLine()
方法来修改每行显示的字节数。
// Sets count of bytes per line for Hex preview
BOOL SetBytesPerLine(int nBytesPerLine);
关注点
有人可能会问,文件预览控件是如何自动检测到正确的预览模式的?它通过两种方式实现:文件扩展名和文件开头字节。
首先它检查文件扩展名。如果文件扩展名是 TXT、INI、LOG、XML、HTM、HTML、JS、C、H、CPP、HPP,则控件假定这是一个文本文件。如果不是,控件会加载文件的前几个字节并将其与 BMP 文件签名进行比较(所有 BMP 文件在文件开头都有“BM
”魔术字符)。如果签名匹配,控件假定该文件是位图图像文件。如果不是,控件假定该文件是未知二进制文件,并为其选择 HEX 预览模式。
关于该控件如何如此迅速地预览巨大的文本、图像和二进制文件,还有一些说明。
其预览速度有两个因素:文件映射和多线程。
文件映射是一种 Win32 对象,它允许您将任意大的文件映射到操作系统内存中,并通过创建文件视图只访问文件的部分。这样,您就可以快速访问大型二进制文件的任何部分,而不会浪费内存和时间延迟。您可以在 FilePreviewCtrl.h
头文件中找到 CFileMemoryMapping
类。下面是 CFileMemoryMapping
类的声明
// Used to map file contents into memory
class CFileMemoryMapping
{
public:
CFileMemoryMapping();
~CFileMemoryMapping();
// Initializes the file mapping
BOOL Init(LPCTSTR szFileName);
// Closes the file mapping
BOOL Destroy();
// Returns memory-mapped file size
ULONG64 GetSize();
// Creates a view for a portion of the memory-mapped file
LPBYTE CreateView(DWORD dwOffset, DWORD dwLength);
private:
HANDLE m_hFile; // Handle to current file
HANDLE m_hFileMapping; // Memory mapped object
DWORD m_dwAllocGranularity; // System allocation granularity
ULONG64 m_uFileLength; // Size of the file.
CCritSec m_csLock; // Sunc object
std::map<DWORD, LPBYTE> m_aViewStartPtrs; // Base of the view of the file.
};
CFileMemoryMapping::Init()
方法使用 CreateFile()
和 CreateFileMapping()
WinAPI 函数来初始化文件映射对象。创建文件映射实际上并不分配内存。内存分配是在 CFileMemoryMapping::CreateView()
方法中执行的,该方法使用 MapViewOfFile()
API 调用来内存映射文件的一小部分(视图)并返回指向它的指针。当不再需要分配的视图时,它会借助 UnmapViewOfFile()
API 调用取消映射。
CFileMemoryMapping
类允许同时创建多个视图,以便从不同的线程同时访问它们。创建的视图存储在 CFileMemoryMapping::m_aViewStartPtrs
变量中。
当您需要在不阻塞主线程的情况下执行耗时工作时,会使用多线程。为了异步执行工作,会使用 CreateThread()
WinAPI 函数创建另一个线程,并将其称为工作线程。文件预览控件在该另一个线程中执行文本文件解析(文本解析需要确定换行符)。它也会在另一个线程中加载图像。您可以通过查看 CFilePreviewCtrl::DoInWorkerThread()
私有方法的代码来了解它是如何实现的。
当工作线程执行图像加载或文本解析时,主线程在计时器事件(WM_TIMER
消息)上显示准备好预览的部分。滚动条也会在计时器上更新。当工作线程完成文件加载时,它会向文件预览控件窗口发送 WM_FPC_COMPLETE
私有消息,通知其完成。
当用户打开另一个文件进行预览时,异步加载/解析操作甚至可能会被取消。要取消操作,主线程设置 CFilePreviewCtrl::m_bCancelled
标志并等待工作线程退出。当工作线程遇到取消标志时,它会从线程过程返回。
历史
- 2011年5月28日 - 初次发布