VCL中SetSubComponent引发EReadError:子组件属性读取异常
问题描述
开发VCL组件时,将两个AlmediaDev的TscGPGlyphButton封装到TscPanel中:
- 设计时面板和两个按钮显示正常,但运行时仅面板渲染,按钮不显示;
- 添加
SetSubComponent(True)解决了按钮不显示的问题,却触发EReadError,提示CloseButton/MinimizeButton的Left属性不存在(实际该属性在父类中存在且设计时可见); - 调整
published段中按钮属性的声明顺序,报错的按钮会随之改变。
原因分析
- 只读属性的流处理冲突:将子组件实例直接以只读属性(
read FCloseButton)声明在published段时,Delphi的DFM流系统会尝试持久化子组件的所有属性,但由于属性是只读的,没有对应的write方法,流加载时无法正确设置子组件的属性,进而触发错误。 - SetSubComponent的影响:调用
SetSubComponent(True)后,子组件会被标记为父组件的从属部分,DFM会自动保存子组件的属性信息,但父组件的只读属性无法承接这些属性的写入操作,导致流系统误判属性不存在。
解决方法
方案1:标记子组件属性为不持久化
将published段中的按钮属性添加stored False修饰,阻止DFM保存子组件的属性信息,避免流加载时的冲突:
published property CloseButton: TscGPGlyphButton read FCloseButton stored False; property MinimizeButton: TscGPGlyphButton read FMinimizeButton stored False;
方案2:封装子组件需要暴露的属性
不直接published整个子组件实例,而是将需要在设计时调整的子组件属性单独封装到父组件的published段中,既保留设计可配置性,又避免流系统的冲突:
private function GetCloseButtonGlyphKind: TscGPGlyphButtonKind; procedure SetCloseButtonGlyphKind(Value: TscGPGlyphButtonKind); // 其他需要封装的属性的get/set方法 published property CloseButtonGlyphKind: TscGPGlyphButtonKind read GetCloseButtonGlyphKind write SetCloseButtonGlyphKind; // 其他封装的属性
对应的get/set方法实现示例:
function TImprovedFluentTopBar.GetCloseButtonGlyphKind: TscGPGlyphButtonKind; begin Result := FCloseButton.GlyphOptions.Kind; end; procedure TImprovedFluentTopBar.SetCloseButtonGlyphKind(Value: TscGPGlyphButtonKind); begin FCloseButton.GlyphOptions.Kind := Value; end;
方案3:调整子组件创建时机与状态控制
在组件创建时,确保子组件在DFM加载前初始化,同时设置子组件的ComponentState避免不必要的属性序列化:
procedure TImprovedFluentTopBar.SetupComponent; begin FMinimizeButton := TscGPGlyphButton.Create(Self); FMinimizeButton.SetSubComponent(True); FMinimizeButton.ComponentState := FMinimizeButton.ComponentState + [csNoSave]; // 阻止属性保存到DFM FCloseButton := TscGPGlyphButton.Create(Self); FCloseButton.SetSubComponent(True); FCloseButton.ComponentState := FCloseButton.ComponentState + [csNoSave]; // 其他初始化代码... end;
完整修正代码(采用方案1)
interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, scControls, scGPControls, scStyledForm, Constants.UI; type TImprovedFluentTopBar = class(TscPanel) private FCloseButton: TscGPGlyphButton; FMinimizeButton: TscGPGlyphButton; procedure SetupComponent; protected procedure Resize; override; public constructor Create(AOwner: TComponent); override; published property CloseButton: TscGPGlyphButton read FCloseButton stored False; property MinimizeButton: TscGPGlyphButton read FMinimizeButton stored False; end; procedure Register; implementation procedure Register; begin RegisterComponents('Improved UI Design', [TImprovedFluentTopBar]); end; { TImprovedFluentTopBar } constructor TImprovedFluentTopBar.Create(AOwner: TComponent); begin inherited; SetupComponent; if (AOwner <> nil) and (csDesigning in AOwner.ComponentState) then begin if not (AOwner is TForm) then begin raise Exception.Create('Owner must be a TForm.'); end; var Found: Boolean := False; for var I:Integer := 0 to TForm(AOwner).ComponentCount - 1 do begin if TForm(AOwner).Components[I] is TImprovedFluentTopBar then begin {$IFNDEF DEBUG} raise Exception.Create('TImprovedFluentTopBar is already on the form.'); {$ENDIF} end else if TForm(AOwner).Components[I] is TscStyledForm then begin Found := True; end; end; if not Found then begin {$IFNDEF DEBUG} //raise Exception.Create('TscStyledForm not found in current TForm'); {$ENDIF} end; end; end; procedure TImprovedFluentTopBar.Resize; begin inherited; end; procedure TImprovedFluentTopBar.SetupComponent; begin FMinimizeButton := TscGPGlyphButton.Create(Self); FMinimizeButton.SetSubComponent(True); FCloseButton := TscGPGlyphButton.Create(Self); FCloseButton.SetSubComponent(True); Self.Caption := ''; Self.DragForm := True; Self.Align := alTop; Self.Height := 28; FMinimizeButton.Name := 'MinButton'; FCloseButton.Name := 'ClsButton'; FMinimizeButton.Parent := Self; FMinimizeButton.Align := alRight; FMinimizeButton.Left := 10; FCloseButton.Parent := Self; FCloseButton.Align := alRight; FCloseButton.Left := 10; FCloseButton.CanFocused := False; FCloseButton.GlyphOptions.Kind := scgpbgkClose; FCloseButton.GlyphOptions.Thickness := 1; FCloseButton.GlyphOptions.DisabledColor := clWhite; FCloseButton.GlyphOptions.FocusedColor := cl_CLOSE_BUTTON; FCloseButton.GlyphOptions.HotColor := cl_CLOSE_BUTTON; FCloseButton.GlyphOptions.NormalColor := clWhite; FCloseButton.GlyphOptions.PressedColor := cl_CLOSE_BUTTON_PRESSED; FCloseButton.GlyphOptions.DisabledColorAlpha := 30; FCloseButton.GlyphOptions.FocusedColorAlpha := 255; FCloseButton.GlyphOptions.HotColorAlpha := 255; FCloseButton.GlyphOptions.NormalColorAlpha := 255; FCloseButton.GlyphOptions.PressedColorAlpha := 255; FCloseButton.Visible := True; end; end.
内容的提问来源于stack exchange,提问作者Arthur Araujo
相关产品推荐
相关产品推荐

