我的主頁: http://www.tommstudio.com/
屬性編輯器對於大多數Delphi程序員來說無疑是很熟悉的,在對象編輯器的內核中有著大量的屬性編輯器,每個對象編輯器中的屬性都對應一個屬性編輯器類的實例。
Delphi5中提供了一些新的高級特性,使我們能夠定義新的屬性編輯器,為以有的屬性提供新的功能,或者設定和顯示新的控件的新的屬性的顯示方法。在Delphi5以前,對象編輯器只能夠以文本的形式顯示屬性值。在Delphi 5中給屬性編輯器提供了新的特性,使我們能夠以任何形式顯示屬性的名稱和值,如下圖所示如果屬性有一個下拉列表,我們就可以為每一個列表項添加一個圖標。下面我們就來研究一下如何實現屬性編輯器的自繪畫的功能。
屬性編輯刷新器
所有的屬性編輯器都是從TpropertyEditor繼承下來的。我們可以為特定的屬性類型、屬性名或控件注冊一個屬性編輯器。對象編輯器檢查每一個要顯示的屬性的名稱和類型,選擇合適的屬性編輯器類。然後它會創建這個類的一個實例(每個屬性對應一個實例)。當我們選擇了另一個控件,對象編輯器會釋放全部的屬性編輯器對象,然後為新的控件創建新的對象。
屬性編輯器可以決定如何顯示屬性的值以及用戶如何設定一個新的屬性值。比如,TintegerProperty調用IntToStr函數以字符串的形式顯示整數值並用StrToInt函數來轉換用戶輸入的新值。 當用戶輸入了一個新的屬性值時,TcolorProperty同樣使用一個整型值來表示,但把整數解釋為顏色,並盡可能地映射顏色值為一個名稱(如clBlack或clBtnFace) 。
一個屬性編輯器實現上述功能是通過重載TpropertyEditor的一個或多個方法來實現的。絕大多數的屬性編輯器需要重載GetValue方法,GetValue方法獲得屬性值的字符串形式。以及SetValue方法,SetValue方法把一個字符串轉化為屬性值。要想了解關於編寫屬性編輯器的進一步信息,需要仔細研究DsgnIntf.pas文件(在Delphi5SourceToolsapi目錄下)以及Delphi 5 在線幫助(在"property editors, creating"部分裡)。
基礎步驟
要實現一個最基本的自繪畫屬性編輯器,我們只需要重載TpropertyEdiotr的PropDrawValue 方法。比如如前面圖中所見到的,TcolorProperty屬性重載了PropDrawValue方法在顏色名前顯示一個對應於相應顏色的彩色小方塊。為了理解如何使用PropDrawValue方法,我們為Tfont對象寫一個新的屬性編輯器,新的編輯器將會用當前字體名對應的字體來顯示Tfont對應的屬性。
Delphi本身已經提供了一個屬性編輯器TfontProperty,它在對象編輯器中添加了一個省略按鈕,用戶可以點擊按鈕調出標准的Windows字體選擇對話框來設定字體的屬性。我們可以直接從TfontProperty繼承新的編輯器,類的聲明如下:
type
TVisualFontProperty = class(TFontProperty)
public
procedure PropDrawValue(Canvas: TCanvas;
const Rect: TRect; Selected: Boolean); override;
end;
// 替換乏味的Tfont屬性值的顯示方式,用選定的字體樣式
//和字體來畫相應的屬性值,用戶可能會選擇比較大的字體
//尺寸,所以這裡保留字體大小不動,只有當字體顏色不同
//於背景色的時候,才用相應的顏色畫,否則前景背景一樣
//的話就無法看到字體的屬性值了
procedure TVisualFontProperty.PropDrawValue(
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
var
Font: TFont;
begin
Font := TFont(GetOrdValue);
if Font <> nil then begin
if ColorToRGB(Font.Color) <> ColorToRGB(clBtnFace) then
Canvas.Font.Color := Font.Color;
Canvas.Font.Name := Font.Name;
Canvas.Font.Style := Font.Style;
end;
inherited;
end;
function TVisualFontProperty.GetValue: string;
var
Font: TFont;
begin
Font := TFont(GetOrdValue);
if Font = nil then
Result := inherited GetValue
else
Result := Format('%s, %d', [Font.Name, Font.Size]);
end;
// Windows不能縮放圖標,所以如果圖標大小不匹配的話,
//把它畫到一個臨時的位圖上,然後縮放位圖。
procedure StretchIcon(Canvas: TCanvas;
const Rect: TRect; Icon: TIcon);
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.Height := Icon.Height;
Bitmap.Width := Icon.Width;
Bitmap.Canvas.Brush.Color := clBtnFace;
Bitmap.Canvas.FillRect(Rect);
Bitmap.Canvas.Draw(0, 0, Icon);
Canvas.StretchDraw(Rect, Bitmap);
finally
Bitmap.Free;
end;
end;
procedure DrawGraphic(Canvas: TCanvas; const Rect: TRect;
Graphic: TGraphic; const Value: string);
var
R: TRect;
HeightRatio, WidthRatio: Single;
begin
Canvas.FillRect(Rect);
//縮放圖像使其符合給定空間大小,
//同時保持圖像寬高比不變
HeightRatio := (Rect.Bottom - Rect.Top) / Graphic.Height;
WidthRatio := (Rect.Right - Rect.Left) / Graphic.Width;
R := Rect;
if HeightRatio < WidthRatio then
R.Right := R.Left + Trunc(Graphic.Width * HeightRatio)
else
R.Bottom := R.Top + Trunc(Graphic.Height * WidthRatio);
if (Graphic is TIcon) and
((HeightRatio > 1) or (WidthRatio > 1)) then
StretchIcon(Canvas, R, TIcon(Graphic))
else
Canvas.StretchDraw(R, Graphic);
// 在圖像的右邊,讓繼承的編輯器畫缺省的文本,比如“Ticon“
R.Left := R.Right;
R.Right := Rect.Right;
R.Top := Rect.Top;
R.Bottom := Rect.Bottom;
Canvas.TextRect(R, R.Left+1, R.Top+1, Value);
end;
procedure TVisualGraphicProperty.PropDrawValue(
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
var
Graphic: TGraphic;
begin
Graphic := TGraphic(GetOrdValue);
if (Graphic = nil) or Graphic.Empty or
(Graphic.Height = 0) or (Graphic.Width = 0) then
inherited
else
DrawGraphic(Canvas, Rect, Graphic, GetVisualValue);
end;
type
TBoldComponentNameProperty =
class(TComponentNameProperty)
public
procedure PropDrawName(Canvas: TCanvas;
const Rect: TRect; Selected: Boolean); override;
end;
procedure TBoldComponentNameProperty.PropDrawName(
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
var
Style: TFontStyles;
begin
Style := Canvas.Font.Style;
Canvas.Font.Style := Canvas.Font.Style + [fsBold];
try
inherited;
finally
//恢復字體的樣式以便Delphi正確的畫屬性值
Canvas.Font.Style := Style;
end;
end;
procedure ListDrawValue(const Value: string;
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
procedure ListMeasureHeight(const Value: string;
Canvas: TCanvas; var Height: Integer);
procedure ListMeasureWidth(const Value: string;
Canvas: TCanvas; var Width: Integer);
// 在下拉列表框的每一個列表項旁邊畫一個復選框
procedure TSetPropertyEx.ListDrawValue(const Value: string;
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
var
IsChecked: Boolean;
OrdValue: Integer;
begin
OrdValue := GetOrdValue;
IsChecked := GetEnumValue(EnumInfo, Value) in
TIntegerSet(OrdValue);
Canvas.FillRect(Rect);
Canvas.TextRect(Rect, Rect.Left + Checked.Width + 2,
Rect.Top + 1, Value);
if IsChecked then
Canvas.Draw(Rect.Left + 1, Rect.Top + 1, Checked)
else
Canvas.Draw(Rect.Left + 1, Rect.Top + 1, Unchecked);
end;
procedure TSetPropertyEx.ListMeasureHeight(
const Value: string; Canvas: TCanvas;
var Height: Integer);
begin
if Height < Checked.Height then
Height := Checked.Height;
end;
procedure TSetPropertyEx.ListMeasureWidth(
const Value: string; Canvas: TCanvas;
var Width: Integer);
begin
Width := Width + Checked.Width + 2;
end;
//根據True或者False來畫一個復選框及布耳值的文本標簽
procedure DrawBoolCheckBox(Canvas: TCanvas;
const Rect: TRect; const Value: string);
begin
Canvas.FillRect(Rect);
Canvas.TextRect(Rect, Rect.Left + Checked.Width + 2,
Rect.Top + 1, Value);
if Value = BooleanIdents[False] then
Canvas.Draw(Rect.Left + 1, Rect.Top + 1, UnChecked)
else
Canvas.Draw(Rect.Left + 1, Rect.Top + 1, Checked);
end;
{ TSetElementPropertyEx }
// 每個列表項旁邊顯示一個復選框,用戶必須雙擊
//而不是單擊才能切換復選框狀態
procedure TSetElementPropertyEx.PropDrawValue(
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
begin
DrawBoolCheckBox(Canvas, Rect, Value);
end;
{ TBoolPropertyEx }
// 為ByteBool, WordBool和LongBool類型顯示復選框
procedure TBoolPropertyEx.PropDrawValue(Canvas: TCanvas;
const Rect: TRect; Selected: Boolean);
begin
DrawBoolCheckBox(Canvas, Rect, Value);
end;
//為全部的集合屬性注冊一個統一的屬性編輯器
function SetMapper(Obj: TPersistent; PropInfo: PPropInfo):
TPropertyEditorClass;
begin
if PropInfo.PropType^.Kind = tkSet then
Result := TSetPropertyEx
else
Result := nil;
end;
procedure Register;
begin
RegisterPropertyEditor(TypeInfo(TFont), nil, '',
TVisualFontProperty);
RegisterPropertyEditor(TypeInfo(TGraphic), nil, '',
TVisualGraphicProperty);
RegisterPropertyEditor(TypeInfo(TComponentName),
TComponent, 'Name', TBoldComponentNameProperty);
RegisterPropertyEditor(TypeInfo(Boolean), nil, '',
TBooleanPropertyEx);
RegisterPropertyEditor(TypeInfo(ByteBool), nil, '',
TBoolPropertyEx);
RegisterPropertyEditor(TypeInfo(WordBool), nil, '',
TBoolPropertyEx);
RegisterPropertyEditor(TypeInfo(LongBool), nil, '',
TBoolPropertyEx);
RegisterPropertyMapper(SetMapper);
end;