65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (12投票s)

2011年5月29日

BSD

6分钟阅读

viewsIcon

43307

downloadIcon

2343

本文演示了 WTL 应用程序中的文件预览控件。

引言

本文介绍了一个文件预览控件(参见下图)。当您需要以文本、十六进制 (HEX) 转储或图像形式显示文件内容时,这种文件预览控件可能会很有用。该控件基于 WTL 的 CStatic 控件 - 这意味着您可以在基于 WTL 的应用程序中使用该代码。您可以以 HEX、文本 (Latin-1) 和图像 (BMP) 格式预览文件。文件大小无关紧要 - 该控件甚至可以预览大文件,而不会出现明显的延迟,因为它会在单独的线程中加载文件。

本文提供了一个简单的演示应用程序和控件的源代码。

图 1 - 十六进制 (HEX) 文件预览

背景

前段时间,我需要一种方法来预览我的一个开源项目的文件内容——CrashRpt,一个用于 Windows 应用程序的崩溃报告库。当您的应用程序崩溃时,CrashRpt 库会生成一个错误报告存档,其中包含一些文件,例如崩溃 minidump、错误日志、桌面截图等等。用户应该能够在通过互联网发送错误报告之前查看文件内容。所以,我需要一个控件来以 HEX、文本和图像格式预览文件(参见下图)。

图 2 - CrashRpt 库的错误报告详情对话框

浏览网页并没有找到完全满足我需求的控件,所以我决定自己编写一个。本文描述了一个轻量级的文件预览控件,它可以预览 Latin-1 文本文件、HEX 格式的二进制文件和 BMP 图像文件,因为我不想通过额外的库依赖(libpnglibjpeg 等)使代码变得臃肿。但是,如果您需要更多功能(UTF-8 和 UTF-16 文本预览、JPEG 和 PNG 图像预览),您可以参考CrashRpt 源代码,找到更强大的原始文件预览控件。

Using the Code

在您的 WTL 应用程序中使用该控件非常简单。您只需将 FilePreviewCtrl.hFilePreviewCtrl.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_HEXPREVIEW_TEXTPREVIEW_IMAGE 常量来强制使用其他预览模式。

// Preview mode
enum PreviewMode
{
  PREVIEW_AUTO = -1,  // Auto
  PREVIEW_HEX  = 0,   // Hex
  PREVIEW_TEXT = 1,   // Text
  PREVIEW_IMAGE = 2   // Image  
}; 

图 3 - 文本文件预览(Latin-1 编码)

图 4 - 图像文件预览(BMP)

您可以使用 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日 - 初次发布
© . All rights reserved.