gpt4 book ai didi

delphi - 编写自定义属性检查器 - 验证值时如何处理就地编辑器焦点?

转载 作者:行者123 更新时间:2023-12-03 15:13:31 24 4
gpt4 key购买 nike

概述

我正在尝试编写自己的简单属性检查器,但我面临着一个困难且相当令人困惑的问题。首先,我要说的是,我的组件并不是要使用或处理组件属性,而是允许向其添加自定义值。我的组件的完整源代码位于问题的更下方,一旦您将其安装在包中并从新的空项目运行它,它应该看起来像这样:

enter image description here

问题(简​​要)

问题与就地编辑器的使用和验证属性值有关。这个想法是,如果属性值无效,则向用户显示一条消息,通知他们该值不能被接受,然后将焦点返回到最初关注的行和就地编辑器。

我们实际上可以使用 Delphi 自己的对象检查器来说明我正在寻找的行为,例如尝试在 Name 属性中编写一个无法接受的字符串,然后单击离开对象检查器。将显示一条消息,关闭它后,它将重新聚焦到 Name 行。

源代码

如果没有任何代码,问题就变得太模糊了,但由于我试图编写的组件的性质,它也很大。为了问题和示例的目的,我已尽可能地剥离它。我确信会有一些评论问我为什么不这样做或那样做,但重要的是要知道我不是 Delphi 专家,我经常做出错误的决定和选择,但我总是愿意学习,所以所有评论欢迎,特别是如果它有助于找到我的解决方案。

unit MyInspector;

interface

uses
Winapi.Windows,
Winapi.Messages,
System.Classes,
System.SysUtils,
Vcl.Controls,
Vcl.Dialogs,
Vcl.StdCtrls,
Vcl.Graphics,
Vcl.Forms;

type
TMyInspectorItems = class(TObject)
private
FPropertyNames: TStringList;
FPropertyValues: TStringList;

procedure AddItem(APropName, APropValue: string);
procedure Clear;
public
constructor Create;
destructor Destroy; override;
end;

TOnMouseMoveEvent = procedure(Sender: TObject; X, Y: Integer) of object;
TOnSelectRowEvent = procedure(Sender: TObject; PropName, PropValue: string; RowIndex: Integer) of object;

TMyCustomInspector = class(TGraphicControl)
private
FInspectorItems: TMyInspectorItems;
FOnMouseMove: TOnMouseMoveEvent;
FOnSelectRow: TOnSelectRowEvent;

FRowCount: Integer;
FNamesFont: TFont;
FValuesFont: TFont;

FSelectedRow: Integer;

procedure SetNamesFont(const AValue: TFont);
procedure SetValuesFont(const AValue: TFont);

procedure CalculateInspectorHeight;
function GetMousePosition: TPoint;
function MousePositionToRowIndex: Integer;
function RowIndexToMousePosition(ARowIndex: Integer): Integer;
function GetRowHeight: Integer;
function GetValueRowWidth: Integer;
function RowExists(ARowIndex: Integer): Boolean;
function IsRowSelected: Boolean;

protected
procedure Loaded; override;
procedure Paint; override;
procedure WMKeyDown(var Message: TMessage); message WM_KEYDOWN;
procedure WMMouseDown(var Message: TMessage); message WM_LBUTTONDOWN;
procedure WMMouseMove(var Message: TMessage); message WM_MOUSEMOVE;
procedure WMMouseUp(var Message: TMessage); message WM_LBUTTONUP;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;

function RowCount: Integer;

property Items: TMyInspectorItems read FInspectorItems write FInspectorItems;
property OnMouseMove: TOnMouseMoveEvent read FOnMouseMove write FOnMouseMove;
property OnSelectRow: TOnSelectRowEvent read FOnSelectRow write FOnSelectRow;
published
property Align;
end;

TMyPropertyInspector = class(TScrollBox)
private
FInspector: TMyCustomInspector;
FInplaceStringEditor: TEdit;

FSelectedRowName: string;
FLastSelectedRowName: string;
FLastSelectedRow: Integer;

function SetPropertyValue(RevertToPreviousValueOnFail: Boolean): Boolean;

procedure InplaceStringEditorEnter(Sender: TObject);
procedure InplaceStringEditorExit(Sender: TObject);
procedure InplaceStringEditorKeyPress(Sender: TObject; var Key: Char);
procedure SelectRow(Sender: TObject; PropName, PropValue: string; RowIndex: Integer);
function ValidateStringValue(Value: string): Boolean;
protected
procedure Loaded; override;
procedure WMSize(var Message: TMessage); message WM_SIZE;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;

procedure AddItem(APropName, APropValue: string);
function GetSelectedPropertyName: string;
function GetSelectedPropertyValue: string;
function RowCount: Integer;
end;

var
FCanSelect: Boolean;

implementation

{ TMyInspectorItems }

constructor TMyInspectorItems.Create;
begin
inherited Create;
FPropertyNames := TStringList.Create;
FPropertyValues := TStringList.Create;
end;

destructor TMyInspectorItems.Destroy;
begin
FPropertyNames.Free;
FPropertyValues.Free;
inherited Destroy;
end;

procedure TMyInspectorItems.AddItem(APropName, APropValue: string);
begin
FPropertyNames.Add(APropName);
FPropertyValues.Add(APropValue);
end;

procedure TMyInspectorItems.Clear;
begin
FPropertyNames.Clear;
FPropertyValues.Clear;
end;

{ TMyCustomInspector }

constructor TMyCustomInspector.Create(AOwner: TComponent);
begin
inherited Create(AOwner);

FInspectorItems := TMyInspectorItems.Create;

FNamesFont := TFont.Create;
FNamesFont.Color := clWindowText;
FNamesFont.Name := 'Segoe UI';
FNamesFont.Size := 9;
FNamesFont.Style := [];

FValuesFont := TFont.Create;
FValuesFont.Color := clNavy;
FValuesFont.Name := 'Segoe UI';
FValuesFont.Size := 9;
FValuesFont.Style := [];
end;

destructor TMyCustomInspector.Destroy;
begin
FInspectorItems.Free;
FNamesFont.Free;
FValuesFont.Free;
inherited Destroy;
end;

procedure TMyCustomInspector.Loaded;
begin
inherited Loaded;
end;

procedure TMyCustomInspector.Paint;

procedure DrawBackground;
begin
Canvas.Brush.Color := clWindow;
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(Rect(0, 0, Self.Width, Self.Height));
end;

procedure DrawNamesBackground;
begin
Canvas.Brush.Color := clWindow;
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(Rect(0, 0, Self.Width div 2, Self.Height));
end;

procedure DrawNamesSelection;
begin
if (FRowCount > -1) and (RowExists(MousePositionToRowIndex)) then
begin
Canvas.Brush.Color := $00E0E0E0;
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(Rect(0, RowIndexToMousePosition(FSelectedRow),
Self.Width div 2, RowIndexToMousePosition(FSelectedRow) + GetRowHeight));
end;
end;

procedure DrawNamesText;
var
I: Integer;
Y: Integer;
begin
FRowCount := FInspectorItems.FPropertyNames.Count;

Canvas.Brush.Style := bsClear;
Canvas.Font.Color := FNamesFont.Color;
Canvas.Font.Name := FNamesFont.Name;
Canvas.Font.Size := FNamesFont.Size;

Y := 0;
for I := 0 to FInspectorItems.FPropertyNames.Count -1 do
begin
Canvas.TextOut(2, Y, FInspectorItems.FPropertyNames.Strings[I]);
Inc(Y, GetRowHeight);
end;
end;

procedure DrawValuesBackground;
begin
Canvas.Brush.Color := clWindow;
Canvas.Brush.Style := bsSolid;
Canvas.FillRect(Rect(Self.Width div 2, 0, Self.Width, Self.Height));
end;

procedure DrawValuesSelection;
begin
if (FRowCount > -1) and (RowExists(MousePositionToRowIndex)) then
begin
Canvas.DrawFocusRect(Rect(Self.Width div 2, RowIndexToMousePosition(FSelectedRow),
Self.Width, RowIndexToMousePosition(FSelectedRow) + GetRowHeight));
end;
end;

procedure DrawValues;
var
I, Y: Integer;
begin
FRowCount := FInspectorItems.FPropertyValues.Count;

Y := 0;
for I := 0 to FInspectorItems.FPropertyValues.Count -1 do
begin
Canvas.Brush.Style := bsClear;
Canvas.Font.Color := FValuesFont.Color;
Canvas.Font.Name := FValuesFont.Name;
Canvas.Font.Size := FValuesFont.Size;

Canvas.TextOut(Self.Width div 2 + 2, Y + 1, FInspectorItems.FPropertyValues.Strings[I]);
Inc(Y, GetRowHeight);
end;
end;

begin
DrawNamesBackground;
DrawNamesSelection;
DrawNamesText;
DrawValuesBackground;
DrawValuesSelection;
DrawValues;
end;

procedure TMyCustomInspector.WMKeyDown(var Message: TMessage);
begin
inherited;

case Message.WParam of
VK_DOWN:
begin

end;
end;
end;

procedure TMyCustomInspector.WMMouseDown(var Message: TMessage);
begin
inherited;

Parent.SetFocus;

FSelectedRow := MousePositionToRowIndex;

if FSelectedRow <> -1 then
begin
if Assigned(FOnSelectRow) then
begin
FOnSelectRow(Self, FInspectorItems.FPropertyNames.Strings[FSelectedRow],
FInspectorItems.FPropertyValues.Strings[FSelectedRow], FSelectedRow);
end;
end;

Invalidate;
end;

procedure TMyCustomInspector.WMMouseMove(var Message: TMessage);
begin
inherited;

if Assigned(FOnMouseMove) then
begin
FOnMouseMove(Self, GetMousePosition.X, GetMousePosition.Y);
end;
end;

procedure TMyCustomInspector.WMMouseUp(var Message: TMessage);
begin
inherited;
end;

procedure TMyCustomInspector.SetNamesFont(const AValue: TFont);
begin
FNamesFont.Assign(AValue);
Invalidate;
end;

procedure TMyCustomInspector.SetValuesFont(const AValue: TFont);
begin
FValuesFont.Assign(AValue);
Invalidate;
end;

procedure TMyCustomInspector.CalculateInspectorHeight;
var
I, Y: Integer;
begin
FRowCount := FInspectorItems.FPropertyNames.Count;

Y := GetRowHeight;
for I := 0 to FRowCount -1 do
begin
Inc(Y, GetRowHeight);
end;

if Self.Height <> Y then
Self.Height := Y;
end;

function TMyCustomInspector.GetMousePosition: TPoint;
var
Pt: TPoint;
begin
Pt := Mouse.CursorPos;
Pt := ScreenToClient(Pt);
Result := Pt;
end;

function TMyCustomInspector.MousePositionToRowIndex: Integer;
begin
Result := GetMousePosition.Y div GetRowHeight;
end;

function TMyCustomInspector.RowIndexToMousePosition(
ARowIndex: Integer): Integer;
begin
Result := ARowIndex * GetRowHeight;
end;

function TMyCustomInspector.GetRowHeight: Integer;
begin
Result := FNamesFont.Size * 2 + 1;
end;

function TMyCustomInspector.GetValueRowWidth: Integer;
begin
Result := Self.Width div 2;
end;

function TMyCustomInspector.RowCount: Integer;
begin
Result := FRowCount;
end;

function TMyCustomInspector.RowExists(ARowIndex: Integer): Boolean;
begin
Result := MousePositionToRowIndex < RowCount;
end;

function TMyCustomInspector.IsRowSelected: Boolean;
begin
Result := FSelectedRow <> -1;
end;

{ TMyPropertyInspector }

constructor TMyPropertyInspector.Create(AOwner: TComponent);
begin
inherited Create(AOwner);

Self.DoubleBuffered := True;
Self.Height := 150;
Self.HorzScrollBar.Visible := False;
Self.TabStop := True; // needed to receive focus
Self.Width := 250;

FInspector := TMyCustomInspector.Create(Self);
FInspector.Parent := Self;
FInspector.Align := alTop;
FInspector.Height := 0;
FInspector.OnSelectRow := SelectRow;

FInplaceStringEditor := TEdit.Create(Self);
FInplaceStringEditor.Parent := Self;
FInplaceStringEditor.BorderStyle := bsNone;
FInplaceStringEditor.Color := clWindow;
FInplaceStringEditor.Height := 0;
FInplaceStringEditor.Left := 0;
FInplaceStringEditor.Name := 'MyPropInspectorInplaceStringEditor';
FInplaceStringEditor.Top := 0;
FInplaceStringEditor.Visible := False;
FInplaceStringEditor.Width := 0;
FInplaceStringEditor.Font.Assign(FInspector.FValuesFont);

FInplaceStringEditor.OnEnter := InplaceStringEditorEnter;
FInplaceStringEditor.OnExit := InplaceStringEditorExit;
FInplaceStringEditor.OnKeyPress := InplaceStringEditorKeyPress;

FCanSelect := True;
end;

destructor TMyPropertyInspector.Destroy;
begin
FInspector.Free;
FInplaceStringEditor.Free;
inherited Destroy;
end;

procedure TMyPropertyInspector.Loaded;
begin
inherited Loaded;
end;

procedure TMyPropertyInspector.WMSize(var Message: TMessage);
begin
FInspector.Width := Self.Width;
Invalidate;
end;


procedure TMyPropertyInspector.AddItem(APropName, APropValue: string);
begin
FInspector.CalculateInspectorHeight;
FInspector.Items.AddItem(APropName, APropValue);
FInspector.Invalidate;
Self.Invalidate;
end;

function TMyPropertyInspector.GetSelectedPropertyName: string;
begin
Result := '';

if FInspector.FSelectedRow <> -1 then
begin
Result := FInspector.FInspectorItems.FPropertyNames.Strings[FInspector.FSelectedRow];
end;
end;

function TMyPropertyInspector.GetSelectedPropertyValue: string;
begin
Result := '';

if FInspector.FSelectedRow <> -1 then
begin
Result := FInspector.FInspectorItems.FPropertyValues.Strings[FInspector.FSelectedRow];
end;
end;

function TMyPropertyInspector.RowCount: Integer;
begin
Result := FInspector.RowCount;
end;

procedure TMyPropertyInspector.InplaceStringEditorEnter(Sender: TObject);
begin
FCanSelect := False;
FLastSelectedRow := FInplaceStringEditor.Tag;
end;

procedure TMyPropertyInspector.InplaceStringEditorExit(Sender: TObject);
begin
if SetPropertyValue(True) then
begin
FCanSelect := True;
end;
end;

procedure TMyPropertyInspector.InplaceStringEditorKeyPress(Sender: TObject;
var Key: Char);
begin
if Key = Chr(VK_RETURN) then
begin
Key := #0;
FInplaceStringEditor.SelectAll;
end;
end;

procedure TMyPropertyInspector.SelectRow(Sender: TObject; PropName, PropValue: string; RowIndex: Integer);
begin
FSelectedRowName := PropName;
FLastSelectedRowName := PropName;

FInplaceStringEditor.Height := FInspector.GetRowHeight - 2;
FInplaceStringEditor.Left := Self.Width div 2;
FInplaceStringEditor.Tag := RowIndex;
FInplaceStringEditor.Text := GetSelectedPropertyValue;
FInplaceStringEditor.Top := FInspector.RowIndexToMousePosition(FInspector.FSelectedRow) + 1 - Self.VertScrollBar.Position;
FInplaceStringEditor.Visible := True;
FInplaceStringEditor.Width := FInspector.GetValueRowWidth - 3;
FInplaceStringEditor.SetFocus;
FInplaceStringEditor.SelectAll;
end;

function TMyPropertyInspector.SetPropertyValue(
RevertToPreviousValueOnFail: Boolean): Boolean;
var
S: string;
begin
Result := False;

S := FInplaceStringEditor.Text;

if ValidateStringValue(S) then
begin
Result := True;
end
else
begin
ShowMessage('"' + S + '"' + 'is not a valid value.');
Result := False;
end;
end;

function TMyPropertyInspector.ValidateStringValue(Value: string): Boolean;
begin
// a quick and dirty way of testing for a valid string value, here we just
// look for strings that are not zero length.
Result := Length(Value) > 0;
end;

end.

问题(详细)

我的困惑都归结为谁首先获得焦点以及如何正确处理和响应它。因为我自定义绘制行,所以我确定单击检查器控件时鼠标所在的位置,然后绘制所选行以显示这一点。然而,在处理就地编辑器时,尤其是 OnEnterOnExit 事件,我一直面临着各种奇怪的问题,在某些情况下,我陷入了验证周期例如,重复显示错误消息(因为焦点从我的检查器切换到就地编辑器并来回切换)。

要在运行时填充我的检查器,您可以执行以下操作:

procedure TForm1.Button1Click(Sender: TObject);
begin
MyPropertyInspector1.AddItem('A', 'Some Text');
MyPropertyInspector1.AddItem('B', 'Hello World');
MyPropertyInspector1.AddItem('C', 'Blah Blah');
MyPropertyInspector1.AddItem('D', 'The Sky is Blue');
MyPropertyInspector1.AddItem('E', 'Another String');
end;

您可以尝试的一些事情:

  • 点击一行
  • 从就地编辑器中删除内容
  • 选择另一行
  • 出现验证错误消息框(先不要关闭它)
  • 在消息框仍然可见的情况下,将鼠标移到另一行上
  • 现在按 Enter 键关闭消息框
  • 您会注意到所选行现已移动到鼠标所在的位置

我需要的是在验证消息框显示并关闭后,我需要将焦点设置回首先验证的行。它变得令人困惑,因为似乎(或者我认为)就地编辑器 OnExit 是在我的检查器的 WMMouseDown(var Message: TMessage); 代码之后调用的。

如果问题仍然不清楚,我可以尽可能简单地说,Delphi 对象检查器的行为就是我试图在我的组件中实现的行为。您在就地编辑器中输入一个值,如果验证失败,则显示一个消息框,然后将焦点返回到最后选择的行。一旦焦点从就地编辑器移开,就地编辑器验证就会发生。

最佳答案

I just can't seem to figure out what is been called first and what is blocking events been fired, it becomes confusing because the way I draw my selected row is determined by where the mouse was when clicking on the inspector control.

这是您的事件流程:

  • TMyCustomInspector.WMMouseDown 被调用
    1. 其中调用了Parent.SetFocus
      • 焦点将从“编辑”控件中移除,并调用 TMyPropertyInspector.InplaceStringEditorExit
      • 消息对话框由SetPropertyValue显示
    2. FSelectedRow 正在重置
    3. TMyPropertyInspector.SelectRow 被调用(通过 TMyCustomInspector.FOnSelectRow),它将焦点重置到替换的编辑控件。

您需要做的是防止在验证失败的情况下重置FSelectedRow。所有需要的成分都已经存在,只需添加以下一个条件:

  if FCanSelect then
FSelectedRow := MousePositionToRowIndex;

几点说明:

  • FCanSelect 设为 TMyCustomInspector 的 protected 或私有(private)字段,
  • 您需要检查 TMyCustomInspector.MousePositionToRowIndex 中的限制才能返回 -1

关于delphi - 编写自定义属性检查器 - 验证值时如何处理就地编辑器焦点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31774845/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com