Jump to content
Attila Kovacs

Property editor - on the finest art

Recommended Posts

I was always pissed because of the property editor and its incapability to helping the work, so I made this extra filter-box.

It has a little discomfort as the Property Editor steals the focus on the first click, I'll check what can I do if I find some time.

Also, adding/deleting/storing the predefined filters are unimplemented yet.

If someone feels the power to get it done, don't hesitate to apply.

 

 

Edited by Attila Kovacs
  • Like 5

Share this post


Link to post

Try this: Write some long text in a TLabel.Caption or a TEdit.Text when you have a vertical scrollbar visible in Object Inspector.

Your cursor will go under that scrollbar and you will not see what you are typing beneath. Same applies when you try to change existing long text.

Share this post


Link to post

That's very useful! I always wanted to have an option that would keep certain properties "pinned", no matter what control is selected.

Share this post


Link to post

Hi Attlia,

looks very promising. But where can I find the code fort it ?

Share this post


Link to post

Thanks @mael and @M.Joos.

 

This is actually very simple with the RegisterSelectionEditor.

Here is the prototype I made, I don't really have any time to make it to a "product" nor to enhance it more.

 

First you will need a simple wizard skeleton like http://www.tempest-sw.com/opentools/simple.html

 

Then merge this code into it.

Let me know if anything is missing.

 

TCustomPropeFilter = class(TSelectionEditor, ISelectionpropertyFilter)
private
  // I've no clue who is managing the instance so everything static
  class var FFilterPane: TPanel;
  class var FFilterBox: TButtonedEdit;
  class var FFilterCombo: TComboBox;
  class var FFilterList: TStringList;
  class var FPatternList: TStringList;
  class var FPropertyInspector: TForm;
  class var FOldPropertyInspectorActivate: TNotifyEvent;
  class procedure OnFilterComboCloseUp(Sender: TObject);
  class procedure RefreshSelection;
  class procedure OnFilterComboKeyPress(Sender: TObject; var Key: Char);
  class procedure OnPropertyInspectorActivate(Sender: TObject);
protected
  { ISelectionPropertyFilter }
  procedure FilterProperties(const ASelection: IDesignerSelections; const ASelectionproperties: IInterfaceList);
end;

constructor TSimpleWizard.Create;
begin

  // how to unregister?
  RegisterSelectionEditor(TControl, TCustomPropeFilter);

  TCustomPropeFilter.FPropertyInspector := TForm(Application.FindComponent('PropertyInspector'));
  if TCustomPropeFilter.FPropertyInspector <> nil then
  begin
    TCustomPropeFilter.FOldPropertyInspectorActivate := TCustomPropeFilter.FPropertyInspector.OnActivate;
    TCustomPropeFilter.FPropertyInspector.OnActivate := TCustomPropeFilter.OnPropertyInspectorActivate;

    TCustomPropeFilter.FFilterPane := TPanel(TCustomPropeFilter.FPropertyInspector.FindComponent('FilterPane'));
    if TCustomPropeFilter.FFilterPane <> nil then
    begin
      TCustomPropeFilter.FFilterBox := TButtonedEdit(TCustomPropeFilter.FPropertyInspector.FindComponent('FilterBox'));

      // I don't like the default search box. Do you?
      if TCustomPropeFilter.FFilterBox <> nil then
        TCustomPropeFilter.FFilterBox.Visible := False;

      TCustomPropeFilter.FFilterCombo := TComboBox.Create(TCustomPropeFilter.FFilterPane);
      TCustomPropeFilter.FFilterCombo.Name := 'FilterCombo';
      TCustomPropeFilter.FFilterCombo.Text := '';
      TCustomPropeFilter.FFilterCombo.Margins.Left := 0;
      TCustomPropeFilter.FFilterCombo.Margins.Top := 3;
      TCustomPropeFilter.FFilterCombo.Margins.Right := 0;
      TCustomPropeFilter.FFilterCombo.Margins.Bottom := 0;
      TCustomPropeFilter.FFilterCombo.AlignWithMargins := True;
      // TCustomPropeFilter.FFilterCombo.Top := 100; // needed only if default search-box is visible
      TCustomPropeFilter.FFilterCombo.Align := alTop;
      TCustomPropeFilter.FFilterPane.InsertControl(TCustomPropeFilter.FFilterCombo);
      TCustomPropeFilter.FFilterCombo.Items.Add('Pos & Size');
      TCustomPropeFilter.FFilterCombo.Items.Add('Display');
      TCustomPropeFilter.FFilterCombo.Items.Add('Name');
      TCustomPropeFilter.FFilterCombo.Items.Add('NewForm');
      TCustomPropeFilter.FFilterCombo.OnCloseUp := TCustomPropeFilter.OnFilterComboCloseUp;
      TCustomPropeFilter.FFilterCombo.OnKeyPress := TCustomPropeFilter.OnFilterComboKeyPress;
      TCustomPropeFilter.FFilterList := TStringList.Create;
      TCustomPropeFilter.FPatternList := TStringList.Create;
    end;
  end;
end;

{ TCustomPropeFilter }

class procedure TCustomPropeFilter.OnFilterComboCloseUp(Sender: TObject);
begin
  RefreshSelection;
end;

class procedure TCustomPropeFilter.RefreshSelection;
var
  AList: IDesignerSelections;
  Designer: IDesigner;
begin
  Designer := ActiveRoot as IDesigner;
  if Designer = nil then
    Exit;

  AList := CreateSelectionList;
  Designer.GetSelections(AList);

  // If the selection contains only the current form. (No controls)
  if (AList.Count = 1) and (AList[0] = Designer.Root) then
    Designer.NoSelection
  else
    Designer.ClearSelection;
  Designer.SetSelections(AList);
end;

class procedure TCustomPropeFilter.OnFilterComboKeyPress(Sender: TObject; var Key: Char);
begin
  case Key of
    #13:
      begin
        RefreshSelection;
        Key := #0;
      end;
    #27:
      begin
        if TCustomPropeFilter.FFilterCombo.Text <> '' then
        begin
          TCustomPropeFilter.FFilterCombo.ItemIndex := -1;
          TCustomPropeFilter.FFilterCombo.Text := '';
          RefreshSelection;
        end;
        Key := #0;
      end;
  end;
end;

class procedure TCustomPropeFilter.OnPropertyInspectorActivate(Sender: TObject);
var
  CursorPos: TPoint;
  c: TControl;
begin
  // This is insane but I've no better idea to avoid stealing the focus by Property Editor's OnActivate event
  GetCursorPos(CursorPos);
  c := FFilterPane.ControlAtPos(FFilterPane.ScreenToClient(CursorPos), True, True, True);
  if (c <> nil) and (c.Name = 'FilterCombo') then
    Exit;

  if Assigned(FOldPropertyInspectorActivate) then
    FOldPropertyInspectorActivate(Sender);
end;

procedure TCustomPropeFilter.FilterProperties(const ASelection: IDesignerSelections; const ASelectionproperties: IInterfaceList);
var
  i, j: Integer;
  LSelectedItem: TPersistent;
  LProperty: IProperty;
  LPropertyName: String;
  MethodProperty: IMethodProperty;

  function PatternMatch(AList: TStringList; APropName: String): Boolean;
  var
    k: Integer;
  begin
    for k := 0 to AList.Count - 1 do
      if Pos(AList[k], APropName) > 0 then
        Exit(True);
    Result := False;
  end;

begin
  if TCustomPropeFilter.FFilterCombo <> nil then
  begin
    // todo load the list from file or registry
    case TCustomPropeFilter.FFilterCombo.ItemIndex of
      - 1:
        begin
          if TCustomPropeFilter.FFilterCombo.Text <> '' then
          begin
            TCustomPropeFilter.FPatternList.Clear;
            TCustomPropeFilter.FFilterList.Text := StringReplace(TCustomPropeFilter.FFilterCombo.Text, ',', #13#10, [rfReplaceAll]);
            for i := TCustomPropeFilter.FFilterList.Count - 1 to 0 do
              if Copy(TCustomPropeFilter.FFilterList[i], 1, 1) = '*' then
              begin
                TCustomPropeFilter.FPatternList.Add(Copy(TCustomPropeFilter.FFilterList[i].ToLower, 2));
                TCustomPropeFilter.FFilterList.Delete(i);
              end;
          end
          else
            Exit;
        end;
        
      // everything hardcoded because I'm a lazy dog  
      0:
        begin
          TCustomPropeFilter.FFilterList.Clear;
          TCustomPropeFilter.FFilterList.Add('Top');
          TCustomPropeFilter.FFilterList.Add('Left');
          TCustomPropeFilter.FFilterList.Add('Width');
          TCustomPropeFilter.FFilterList.Add('Height');
          TCustomPropeFilter.FFilterList.Add('ClientWidth');
          TCustomPropeFilter.FFilterList.Add('ClientHeight');
          TCustomPropeFilter.FFilterList.Add('TabOrder');
          TCustomPropeFilter.FFilterList.Add('Align');
          TCustomPropeFilter.FFilterList.Add('AlignWithMargins');
          TCustomPropeFilter.FFilterList.Add('Margins');
        end;
      1:
        begin
          TCustomPropeFilter.FFilterList.Clear;
          TCustomPropeFilter.FFilterList.Add('Caption');
          TCustomPropeFilter.FFilterList.Add('Text');
          TCustomPropeFilter.FFilterList.Add('Datasource');
          TCustomPropeFilter.FFilterList.Add('DataField');
        end;
      2:
        begin
          TCustomPropeFilter.FFilterList.Text := 'Name';
        end;
      3:
        begin
          TCustomPropeFilter.FFilterList.Clear;
          TCustomPropeFilter.FFilterList.Add('ActiveControl');
          TCustomPropeFilter.FFilterList.Add('BorderIcons');
          TCustomPropeFilter.FFilterList.Add('BorderStyle');
          TCustomPropeFilter.FFilterList.Add('Caption');
          TCustomPropeFilter.FFilterList.Add('ClientHeight');
          TCustomPropeFilter.FFilterList.Add('ClientWidth');
          TCustomPropeFilter.FFilterList.Add('Font');
          TCustomPropeFilter.FFilterList.Add('Name');
          TCustomPropeFilter.FFilterList.Add('Position');
        end;
    end;

    // todo implement pattern search like in the original IDE search box
    for i := 0 to ASelection.Count - 1 do
    begin
      LSelectedItem := ASelection[i];
      if (LSelectedItem is TControl) then
        for j := ASelectionproperties.Count - 1 downto 0 do
        begin
          if Supports(ASelectionproperties[j], IProperty, LProperty) then
            if not Supports(ASelectionproperties[j], IMethodProperty, MethodProperty) then
            begin
              LPropertyName := LProperty.GetName;
              if (TCustomPropeFilter.FFilterList.IndexOf(LPropertyName) = -1) and //
                (not PatternMatch(TCustomPropeFilter.FPatternList, LPropertyName.ToLower)) then
                ASelectionproperties.Delete(j);
            end;
        end;
    end;
  end;
end;

 

Edited by Attila Kovacs

Share this post


Link to post
16 hours ago, Attila Kovacs said:

Thanks @mael and @M.Joos.

 

This is actually very simple with the RegisterSelectionEditor.

Here is the prototype I made, I don't really have any time to make it to a "product" nor to enhance it more.

 

First you will need a simple wizard skeleton like http://www.tempest-sw.com/opentools/simple.html

 

Then merge this code into it.

Let me know if anything is missing.

 


TCustomPropeFilter = class(TSelectionEditor, ISelectionpropertyFilter)
private
  // I've no clue who is managing the instance so everything static
  class var FFilterPane: TPanel;
  class var FFilterBox: TButtonedEdit;
  class var FFilterCombo: TComboBox;
  class var FFilterList: TStringList;
  class var FPatternList: TStringList;
  class var FPropertyInspector: TForm;
  class var FOldPropertyInspectorActivate: TNotifyEvent;
  class procedure OnFilterComboCloseUp(Sender: TObject);
  class procedure RefreshSelection;
  class procedure OnFilterComboKeyPress(Sender: TObject; var Key: Char);
  class procedure OnPropertyInspectorActivate(Sender: TObject);
protected
  { ISelectionPropertyFilter }
  procedure FilterProperties(const ASelection: IDesignerSelections; const ASelectionproperties: IInterfaceList);
end;

constructor TSimpleWizard.Create;
begin

  // how to unregister?
  RegisterSelectionEditor(TControl, TCustomPropeFilter);

  TCustomPropeFilter.FPropertyInspector := TForm(Application.FindComponent('PropertyInspector'));
  if TCustomPropeFilter.FPropertyInspector <> nil then
  begin
    TCustomPropeFilter.FOldPropertyInspectorActivate := TCustomPropeFilter.FPropertyInspector.OnActivate;
    TCustomPropeFilter.FPropertyInspector.OnActivate := TCustomPropeFilter.OnPropertyInspectorActivate;

    TCustomPropeFilter.FFilterPane := TPanel(TCustomPropeFilter.FPropertyInspector.FindComponent('FilterPane'));
    if TCustomPropeFilter.FFilterPane <> nil then
    begin
      TCustomPropeFilter.FFilterBox := TButtonedEdit(TCustomPropeFilter.FPropertyInspector.FindComponent('FilterBox'));

      // I don't like the default search box. Do you?
      if TCustomPropeFilter.FFilterBox <> nil then
        TCustomPropeFilter.FFilterBox.Visible := False;

      TCustomPropeFilter.FFilterCombo := TComboBox.Create(TCustomPropeFilter.FFilterPane);
      TCustomPropeFilter.FFilterCombo.Name := 'FilterCombo';
      TCustomPropeFilter.FFilterCombo.Text := '';
      TCustomPropeFilter.FFilterCombo.Margins.Left := 0;
      TCustomPropeFilter.FFilterCombo.Margins.Top := 3;
      TCustomPropeFilter.FFilterCombo.Margins.Right := 0;
      TCustomPropeFilter.FFilterCombo.Margins.Bottom := 0;
      TCustomPropeFilter.FFilterCombo.AlignWithMargins := True;
      // TCustomPropeFilter.FFilterCombo.Top := 100; // needed only if default search-box is visible
      TCustomPropeFilter.FFilterCombo.Align := alTop;
      TCustomPropeFilter.FFilterPane.InsertControl(TCustomPropeFilter.FFilterCombo);
      TCustomPropeFilter.FFilterCombo.Items.Add('Pos & Size');
      TCustomPropeFilter.FFilterCombo.Items.Add('Display');
      TCustomPropeFilter.FFilterCombo.Items.Add('Name');
      TCustomPropeFilter.FFilterCombo.Items.Add('NewForm');
      TCustomPropeFilter.FFilterCombo.OnCloseUp := TCustomPropeFilter.OnFilterComboCloseUp;
      TCustomPropeFilter.FFilterCombo.OnKeyPress := TCustomPropeFilter.OnFilterComboKeyPress;
      TCustomPropeFilter.FFilterList := TStringList.Create;
      TCustomPropeFilter.FPatternList := TStringList.Create;
    end;
  end;
end;

{ TCustomPropeFilter }

class procedure TCustomPropeFilter.OnFilterComboCloseUp(Sender: TObject);
begin
  RefreshSelection;
end;

class procedure TCustomPropeFilter.RefreshSelection;
var
  AList: IDesignerSelections;
  Designer: IDesigner;
begin
  Designer := ActiveRoot as IDesigner;
  if Designer = nil then
    Exit;

  AList := CreateSelectionList;
  Designer.GetSelections(AList);

  // If the selection contains only the current form. (No controls)
  if (AList.Count = 1) and (AList[0] = Designer.Root) then
    Designer.NoSelection
  else
    Designer.ClearSelection;
  Designer.SetSelections(AList);
end;

class procedure TCustomPropeFilter.OnFilterComboKeyPress(Sender: TObject; var Key: Char);
begin
  case Key of
    #13:
      begin
        RefreshSelection;
        Key := #0;
      end;
    #27:
      begin
        if TCustomPropeFilter.FFilterCombo.Text <> '' then
        begin
          TCustomPropeFilter.FFilterCombo.ItemIndex := -1;
          TCustomPropeFilter.FFilterCombo.Text := '';
          RefreshSelection;
        end;
        Key := #0;
      end;
  end;
end;

class procedure TCustomPropeFilter.OnPropertyInspectorActivate(Sender: TObject);
var
  CursorPos: TPoint;
  c: TControl;
begin
  // This is insane but I've no better idea to avoid stealing the focus by Property Editor's OnActivate event
  GetCursorPos(CursorPos);
  c := FFilterPane.ControlAtPos(FFilterPane.ScreenToClient(CursorPos), True, True, True);
  if (c <> nil) and (c.Name = 'FilterCombo') then
    Exit;

  if Assigned(FOldPropertyInspectorActivate) then
    FOldPropertyInspectorActivate(Sender);
end;

procedure TCustomPropeFilter.FilterProperties(const ASelection: IDesignerSelections; const ASelectionproperties: IInterfaceList);
var
  i, j: Integer;
  LSelectedItem: TPersistent;
  LProperty: IProperty;
  LPropertyName: String;
  MethodProperty: IMethodProperty;

  function PatternMatch(AList: TStringList; APropName: String): Boolean;
  var
    k: Integer;
  begin
    for k := 0 to AList.Count - 1 do
      if Pos(AList[k], APropName) > 0 then
        Exit(True);
    Result := False;
  end;

begin
  if TCustomPropeFilter.FFilterCombo <> nil then
  begin
    // todo load the list from file or registry
    case TCustomPropeFilter.FFilterCombo.ItemIndex of
      - 1:
        begin
          if TCustomPropeFilter.FFilterCombo.Text <> '' then
          begin
            TCustomPropeFilter.FPatternList.Clear;
            TCustomPropeFilter.FFilterList.Text := StringReplace(TCustomPropeFilter.FFilterCombo.Text, ',', #13#10, [rfReplaceAll]);
            for i := TCustomPropeFilter.FFilterList.Count - 1 to 0 do
              if Copy(TCustomPropeFilter.FFilterList[i], 1, 1) = '*' then
              begin
                TCustomPropeFilter.FPatternList.Add(Copy(TCustomPropeFilter.FFilterList[i].ToLower, 2));
                TCustomPropeFilter.FFilterList.Delete(i);
              end;
          end
          else
            Exit;
        end;
        
      // everything hardcoded because I'm a lazy dog  
      0:
        begin
          TCustomPropeFilter.FFilterList.Clear;
          TCustomPropeFilter.FFilterList.Add('Top');
          TCustomPropeFilter.FFilterList.Add('Left');
          TCustomPropeFilter.FFilterList.Add('Width');
          TCustomPropeFilter.FFilterList.Add('Height');
          TCustomPropeFilter.FFilterList.Add('ClientWidth');
          TCustomPropeFilter.FFilterList.Add('ClientHeight');
          TCustomPropeFilter.FFilterList.Add('TabOrder');
          TCustomPropeFilter.FFilterList.Add('Align');
          TCustomPropeFilter.FFilterList.Add('AlignWithMargins');
          TCustomPropeFilter.FFilterList.Add('Margins');
        end;
      1:
        begin
          TCustomPropeFilter.FFilterList.Clear;
          TCustomPropeFilter.FFilterList.Add('Caption');
          TCustomPropeFilter.FFilterList.Add('Text');
          TCustomPropeFilter.FFilterList.Add('Datasource');
          TCustomPropeFilter.FFilterList.Add('DataField');
        end;
      2:
        begin
          TCustomPropeFilter.FFilterList.Text := 'Name';
        end;
      3:
        begin
          TCustomPropeFilter.FFilterList.Clear;
          TCustomPropeFilter.FFilterList.Add('ActiveControl');
          TCustomPropeFilter.FFilterList.Add('BorderIcons');
          TCustomPropeFilter.FFilterList.Add('BorderStyle');
          TCustomPropeFilter.FFilterList.Add('Caption');
          TCustomPropeFilter.FFilterList.Add('ClientHeight');
          TCustomPropeFilter.FFilterList.Add('ClientWidth');
          TCustomPropeFilter.FFilterList.Add('Font');
          TCustomPropeFilter.FFilterList.Add('Name');
          TCustomPropeFilter.FFilterList.Add('Position');
        end;
    end;

    // todo implement pattern search like in the original IDE search box
    for i := 0 to ASelection.Count - 1 do
    begin
      LSelectedItem := ASelection[i];
      if (LSelectedItem is TControl) then
        for j := ASelectionproperties.Count - 1 downto 0 do
        begin
          if Supports(ASelectionproperties[j], IProperty, LProperty) then
            if not Supports(ASelectionproperties[j], IMethodProperty, MethodProperty) then
            begin
              LPropertyName := LProperty.GetName;
              if (TCustomPropeFilter.FFilterList.IndexOf(LPropertyName) = -1) and //
                (not PatternMatch(TCustomPropeFilter.FPatternList, LPropertyName.ToLower)) then
                ASelectionproperties.Delete(j);
            end;
        end;
    end;
  end;
end;

 

Thanks a lot.

I will look and eventually extend your code somewhat. I am really missing the OI expert from Uwe Schuster, that went along similar ideas that you have. So maybe I can come up with a similar solution than Uwe's expert. At least your code is a good starting point, so thanks a lot for sharing.

  • Like 1

Share this post


Link to post

Seems pretty cool.

Would like to have some way to set this up, maybe an .ini-file etc, to give some custom properties.

 

Did not check the code, so don't know that was it your plugin, but colorization of properties would alonne help a ton. Sometimes you spend very long time for property like Name etc.

 

-Tee-

Share this post


Link to post

I would like to see this plugin to get more support, now that code is already there, and installer, maybe, to speed up the start...

 

Edit, how anyone can understand what I menat, coz I can't 😄

 

Would be nice to have installer for different IDE-versions, so it would be easier to start using it.

 

I would suggest, if possible, add some standard colorizxation of properties, but it should be configurable, but editing INI-file would be OK, to me anyways. Because there are so many components and different properties, and some are interested in very different properties...

 

 

-Tee-

 

 

Edited by Tommi Prami
  • Like 2

Share this post


Link to post

@Tommi Prami 

Sorry, I'm not touching the property editor at all, so no coloring or any drawing possible.

It has a filter interface where I can decide what should be in the list and what not.

 

However, one could easily filter those properties which are declared only in the component class, or in the last 2 classes (TCustomXXX, but I think there are no published properties), or build a tree and select which ones you want to see.

 

So could you have a clear overview of the components own properties.

 

Edit:

 

I made a little test and It's not that useful as I thought: (%'s represent the class depth (parents))

 

 

Edited by Attila Kovacs

Share this post


Link to post

I think it could be fantastic if it was possible to design custom property lists per class.  The first time a class is seen, it could be added to a list of class property configs, with all the props present.  In a config dialog somewhere, it could be possible to enable/disable each prop.  The filter could even use digits 0..9 to select one of multiple configurations per class. Naturally, if it could be installed with default filters for many of the standard components, that would be nice. I really like the idea - more than I like quick edit. Nice work!

  • Thanks 1

Share this post


Link to post
On 2/7/2019 at 8:12 PM, Attila Kovacs said:

This is actually very simple with the RegisterSelectionEditor.

Here is the prototype I made, I don't really have any time to make it to a "product" nor to enhance it more.

 

First you will need a simple wizard skeleton like http://www.tempest-sw.com/opentools/simple.html

 

Then merge this code into it.

Let me know if anything is missing.

Where are TSelectionEditor and ISelectionpropertyFilter declared? I expected them in ToolsAPI, but they aren't there.

 

Found them:

DesignEditors and DesignIntf

Edited by dummzeuch

Share this post


Link to post
6 minutes ago, dummzeuch said:

Where are TSelectionEditor and ISelectionpropertyFilter declared? I expected them in ToolsAPI, but they aren't there.

DesignIntf and DesignEditors.

 

BTW, the ImageIndexMapper makes use of these to replace the ImageIndex properties with the ImageName ones.

Share this post


Link to post

Just in case anybody else wants to compile this code. It requires the following uses lists:

interface

uses
  Windows,
  SysUtils,
  Classes,
  ToolsAPI,
  ExtCtrls,
  StdCtrls,
  Forms,
  DesignEditors,
  ComponentDesigner,
  DesignIntf;

implementation

uses
  Controls,
  Types;

 

  • Like 1

Share this post


Link to post

@dummzeuch Sorry, I forgot them. As everybody every time...

 

Btw. I just found out that it's not working if the selection is in a popup window like columns of a grid etc...

I had no time yet to figure out what the problem is.

Maybe you will have more ideas as me..

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×