VCL TImageList 缺乏 SaveToFile/Stream、LoadFromFile/Stream 方法,無法直接將設計時/運行時構建的圖像集合持久化到文件或流中,導致動態換膚、插件圖標管理、配置導出等場景難以實現。
利用 ComCtl32.dll 導出函數,直接將內核 ImageList 結構序列化為標準流格式。
IStream 接口,需 TStreamAdapter 適配;格式不透明、難以手工編輯/檢查;舊版 ComCtl32 可能不支持。uses CommCtrl, ActiveX; // 需引入
procedure SaveToStream_API(ImageList: TImageList; Stream: TStream);
var SA: IStream;
begin
SA := TStreamAdapter.Create(Stream) as IStream;
if not ImageList_Write(ImageList.Handle, SA) then RaiseLastOSError;
end;
procedure LoadFromStream_API(ImageList: TImageList; Stream: TStream);
var SA: IStream; NewHandle: HIMAGELIST;
begin
SA := TStreamAdapter.Create(Stream) as IStream;
NewHandle := ImageList_Read(SA);
if NewHandle = 0 then RaiseLastOSError;
ImageList.Handle := NewHandle; // 替換句柄
end;
繼承 TImageList → TImageListEx,手動遍歷 GetImages(Index, Image, Mask) 獲取每幀位圖與掩碼,序列化為:頭部(寬/高) + 幀數據(位圖大小+位圖數據+掩碼大小+掩碼數據)...。
TBitmap.SaveToStream 內部為 BMP 格式)。unit ImageListEx;
interface
uses Windows, SysUtils, Classes, Graphics, Controls, CommCtrl, ImgList, Consts;
type
TImageListEx = class(TImageList)
public
// API 方式
procedure LoadFromFile(const FileName: string);
procedure LoadFromStream(Stream: TStream);
procedure SaveToFile(const FileName: string);
procedure SaveToStream(Stream: TStream);
// 自定義格式方式 (推薦)
procedure LoadFromFileEx(const FileName: string);
procedure LoadFromStreamEx(Stream: TStream);
procedure SaveToFileEx(const FileName: string);
procedure SaveToStreamEx(Stream: TStream);
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TImageListEx]);
end;
{ TImageListEx - API 方式 }
procedure TImageListEx.LoadFromFile(const FileName: string);
var Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead);
try LoadFromStream(Stream); finally Stream.Free; end;
end;
procedure TImageListEx.LoadFromStream(Stream: TStream);
var SA: IStream;
begin
SA := TStreamAdapter.Create(Stream) as IStream;
Handle := ImageList_Read(SA);
if Handle = 0 then raise EReadError.CreateRes(@SImageReadFail);
end;
procedure TImageListEx.SaveToFile(const FileName: string);
var Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try SaveToStream(Stream); finally Stream.Free; end;
end;
procedure TImageListEx.SaveToStream(Stream: TStream);
var SA: IStream;
begin
SA := TStreamAdapter.Create(Stream) as IStream;
if not ImageList_Write(Handle, SA) then raise EWriteError.CreateRes(@SImageWriteFail);
end;
{ TImageListEx - 自定義格式方式 }
procedure TImageListEx.LoadFromFileEx(const FileName: string);
var Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead);
try LoadFromStreamEx(Stream); finally Stream.Free; end;
end;
procedure TImageListEx.LoadFromStreamEx(Stream: TStream);
var Width, Height: Integer; Bitmap, Mask: TBitmap; BinStream: TMemoryStream;
procedure LoadImageFromStream(Image: TBitmap);
var Count: DWORD;
begin
Image.Assign(nil);
Stream.ReadBuffer(Count, SizeOf(Count));
BinStream.Clear; BinStream.CopyFrom(Stream, Count);
BinStream.Position := 0; Image.LoadFromStream(BinStream);
end;
begin
Stream.ReadBuffer(Height, SizeOf(Height));
Stream.ReadBuffer(Width, SizeOf(Width));
Self.Height := Height; Self.Width := Width;
Bitmap := TBitmap.Create; Mask := TBitmap.Create; BinStream := TMemoryStream.Create;
try
while Stream.Position < Stream.Size do
begin
LoadImageFromStream(Bitmap); LoadImageFromStream(Mask);
Add(Bitmap, Mask);
end;
finally Bitmap.Free; Mask.Free; BinStream.Free; end;
end;
procedure TImageListEx.SaveToFileEx(const FileName: string);
var Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try SaveToStreamEx(Stream); finally Stream.Free; end;
end;
procedure TImageListEx.SaveToStreamEx(Stream: TStream);
var I: Integer; Width, Height: Integer; Bitmap, Mask: TBitmap; BinStream: TMemoryStream;
procedure SetImage(Image: TBitmap; IsMask: Boolean);
begin
Image.Assign(nil);
if IsMask then Image.Monochrome := True;
Image.Height := Self.Height; Image.Width := Self.Width;
end;
procedure SaveImageToStream(Image: TBitmap);
var Count: DWORD;
begin
BinStream.Clear; Image.SaveToStream(BinStream);
Count := BinStream.Size;
Stream.WriteBuffer(Count, SizeOf(Count));
Stream.CopyFrom(BinStream, 0);
end;
begin
Stream.WriteBuffer(Self.Height, SizeOf(Self.Height));
Stream.WriteBuffer(Self.Width, SizeOf(Self.Width));
Bitmap := TBitmap.Create; Mask := TBitmap.Create; BinStream := TMemoryStream.Create;
try
for I := 0 to Count - 1 do
begin
SetImage(Bitmap, False); SetImage(Mask, True);
GetImages(I, Bitmap, Mask); // 關鍵:從基類 Protected 方法獲取 Image+Mask
SaveImageToStream(Bitmap); SaveImageToStream(Mask);
end;
finally Bitmap.Free; Mask.Free; BinStream.Free; end;
end;
end.
TImageListEx、TTreeView (關聯 Images)、4 個 Button。ImageIndex。ImageListEx1.SaveToFile('C:\CJ.dat'); ImageListEx1.SaveToFileEx('C:\CJEx.dat');ImageListEx1.Clear; (清空驗證)ImageListEx1.LoadFromFile('C:\CJ.dat'); (API 方式恢復)ImageListEx1.LoadFromFileEx('C:\CJEx.dat'); (自定義格式恢復)| 場景 | 推薦方案 |
|---|---|
| 需與其他語言/進程交換 ImageList、追求零代碼維護 | API 方式 (SaveToStream/LoadFromStream) |
| 需嵌入元數據、加密壓縮、跨平台 (FMX)、格式可讀性 | 自定義格式 (SaveToStreamEx/LoadFromStreamEx) |
| 單純 Delphi 內部持久化、不想依賴 ComCtl32 版本 | 自定義格式 |
通過繼承 TImageList 並封裝 ImageList_Write/Read API 或實現自定義 BMP 序列化,成功彌補了 VCL 圖像列表持久化的缺失。TImageListEx 組件可直接安裝到組件面板,為動態圖標管理、主題切換、插件系統提供核心基礎設施。