How does the TVirtualTreeview editor work? - delphi

I am exploring Virtual Treeview in Delphi and ran a sample program where the editor is invoked by pressing F2 beginning the editing process uses the built-in editor in Virtualtreeview (No attached editing component). The text changed, but immediately changed back to the original when I clicked on a different node.
This led me to explore the source code in VirtualTrees.pas to study how the editing process works. Everything appears to boil down to the TBaseVirtualTree.doedit. I have examined each step but am uncertain what exactly operates the editing box positioned in the column.
procedure TBaseVirtualTree.DoEdit;
begin
Application.CancelHint;
StopTimer(ScrollTimer);
StopTimer(EditTimer);
DoStateChange([], [tsEditPending]);
if Assigned(FFocusedNode) and not (vsDisabled in FFocusedNode.States) and
not (toReadOnly in FOptions.FMiscOptions) and (FEditLink = nil) then
begin
FEditLink := DoCreateEditor(FFocusedNode, FEditColumn);
if Assigned(FEditLink) then
begin
DoStateChange([tsEditing], [tsDrawSelecting, tsDrawSelPending, tsToggleFocusedSelection, tsOLEDragPending,
tsOLEDragging, tsClearPending, tsDrawSelPending, tsScrollPending, tsScrolling, tsMouseCheckPending]);
ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions,
not (toDisableAutoscrollOnEdit in FOptions.AutoOptions));
if FEditLink.PrepareEdit(Self, FFocusedNode, FEditColumn) then
begin
UpdateEditBounds;
// Node needs repaint because the selection rectangle and static text must disappear.
InvalidateNode(FFocusedNode);
if not FEditLink.BeginEdit then
DoStateChange([], [tsEditing]);
end
else
DoStateChange([], [tsEditing]);
if not (tsEditing in FStates) then
FEditLink := nil;
end;
end;
end;
So my question is how is the actual keyboard input being placed in the node.text by VirtualTree and how is the result of the edit placed into the data record?

You need to handle the OnNewText event eg:
procedure TForm1.VSTNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; Text: UnicodeString);
var
data: TMyData;
begin
data := TMyData(Sender.GetNodeData(Node)^);
if Assigned(data) then
begin
if Column = 0 then
data.Caption := Text
else
data.Value := Text;
end;
end;
This event is called right after you edit the text in the editor.
The editor is implemented via the IVTEditLink interface. FEditLink.BeginEdit starts the process of the editing.
The build-in editor TStringEditLink implements IVTEditLink, and if you want to know how that works, You need to study the code.
If you need to use your own editor (eg a ComboBox like editor) you will need to implement IVTEditLink and return your EditLink in the OnCreateEditor event.
There are a few good examples of property editors in the Demo directory of the VST, that show how to implement your own editors.

Related

Shortcut triggers TAction on first created form instead of form with focus

I found (in Delphi 2010) that shortcuts always end up on first form (as owned by main form) that has that action, but not the currently focused form. My TMainFrm owns several TViewFrm. Each has a TActionManager with the same TActons.
I see some ways out, but wonder whats the best fix.. (and not a bad hack)
The forms are navigated using a tabset which calls their Hide() and Show(). I'd did not expect hidden forms to receive keypresses. Am i doing something wrong?
It seems that action shortcuts are always start at the main form, and using TCustomForm.IsShortCut() get distributed to owned forms. I see no logic there to respect hidden windows, should i override it and have it trigger the focused form first?
Disabling all TActions in TViewFrm.Hide() .. ?
Moving the TActionToolBar to TMainFrm but that is a pit of snakes and last resort.
I have found a workaround thats good enough for me; my main form now overrides TCustomForm.IsShortcut() and first checks visible windows from my list of editor tabs.
A list which i conveniently already have, so this might not work for everyone.
// Override TCustomForm and make it check the currently focused tab/window first.
function TFormMain.IsShortCut(var Message: TWMKey): Boolean;
function DispatchShortCut(const Owner: TComponent) : Boolean; // copied function unchanged
var
I: Integer;
Component: TComponent;
begin
Result := False;
{ Dispatch to all children }
for I := 0 to Owner.ComponentCount - 1 do
begin
Component := Owner.Components[I];
if Component is TCustomActionList then
begin
if TCustomActionList(Component).IsShortCut(Message) then
begin
Result := True;
Exit;
end
end
else
begin
Result := DispatchShortCut(Component);
if Result then
Break;
end
end;
end;
var
form : TForm;
begin
Result := False;
// Check my menu
Result := Result or (Menu <> nil) and (Menu.WindowHandle <> 0) and
Menu.IsShortCut(Message);
// Check currently focused form <------------------- (the fix)
for form in FEditorTabs do
if form.Visible then
begin
Result := DispatchShortCut(form);
if Result then Break;
end;
// ^ wont work using GetActiveWindow() because it always returns Self.
// Check all owned components/forms (the normal behaviour)
if not Result then
Result := inherited IsShortCut(Message);
end;
Another solution would be to change DispatchShortCut() to check for components being visible and/or enabled, but that might impact more than i'd like. I wonder whether the original code architects had a reason not to -- by design. Best would be have it called twice: first to give priority to visible+enabled components, and second call as fallback to normal behavior.

How to write and show something on Delphi IDE status bar

I want to know how can I write a module to show something like clock or other thing on Borland Delphi 7 IDE status bar, because I know it's possible but I couldn't find how!
To insert a text in a StatusBar, you have to insert a panel first.
Just select your statusbar, find the property "Panels" (or perform double click over the statusbar) and click in "Add new".
After that, you can write what you want inside the panel in the property "Text" (you can insert one or more panels).
To do it programmatically, you can do something like this:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
StatusBar1.Panels[0].Text := 'Today is: ' + FormatDateTime('dd/mm/yyyy hh:nn:ss', Now);
end;
Since OP didn't replied with more details, I'm going to post a little demonstration how to reach a status bar of Delphi's edit window. I had no success with adding new distinct status panel w/o disturbing layout, so I'm just changing the text of INS/OVR indicator panel.
Disclaimer: I still do not have access to the machine with Delphi 7 installed, so I've done that in BDS ("Galileo") IDE. However, differences should be minor. I believe what main difference lies in the way how we locate edit window.
Key strings are: 'TEditWindow' for edit window class name and 'StatusBar' for TStatusBar control name owned by edit window. These strings are consistent across versions.
{ helper func, see below }
function FindForm(const ClassName: string): TForm;
var
I: Integer;
begin
Result := nil;
for I := 0 to Screen.FormCount - 1 do
begin
if Screen.Forms[I].ClassName = ClassName then
begin
Result := Screen.Forms[I];
Break;
end;
end;
end;
procedure Init;
var
EditWindow: TForm;
StatusBar: TStatusBar;
StatusPanel: TStatusPanel;
begin
EditWindow := FindForm('TEditWindow');
Assert(Assigned(EditWindow), 'no edit window');
StatusBar := EditWindow.FindComponent('StatusBar') as TStatusBar;
(BorlandIDEServices as IOTAMessageServices).AddTitleMessage(Format('StatusBar.Panels.Count = %d', [StatusBar.Panels.Count]));
//StatusPanel := StatusBar.Panels.Add;
StatusPanel := StatusBar.Panels[2];
StatusPanel.Text := 'HAI!';
end;
initialization
Init;
finalization
// nothing to clean up yet
Another note: As you see, I use Open Tools API to output debug messages only, to interact with IDE I do use Native VCL classes. Therefore, this code must be in package.
The code above is a relevant part of the unit which should be contained in package. Do not forget to add ToolsAPI to uses clause as well as other appropriate referenced units (up to you).
Package should require rtl, vcl and designide (important!).
Since I run the testcase directly from initialization section, installing the package is enough for testcase to run and produce some result.

Can a Component Editor be executed on multiple components?

Short Version
I am trying to implement my first ever Component Editor for a custom button I have made. With the help of some online articles I have successfully installed the editor and can see the menu item when I right click on my button in the Form Designer.
But this component editor menu is not showing when selecting more than one of my button controls.
Do Component Editors only work with single selected controls by default, or can they work with multiple selected controls and if so how?
Long Version
I was in the process of implementing a TPropertyEditor for one of my own components but have now decided that a TComponentEditor would be better served, or so I thought.
Basically I have a TCustomButton which I have ownerdrawn, this button component has several published properties for changing the appearance such as the border and fill color etc.
The Component Editor I am implementing displays in the context menu a new menu item to "Load settings from a File". When executed a simple TOpenDialog is shown to which you can select the appropriate file, for example an Ini File which I then read and set the values from the File accordingly.
Everything is working good from what I can see, but as I am still sort of new and getting to grips with the whole custom controls side of Delphi I noticed something that does not happen - I am not sure if this is the actual intended behavior or whether I can change it.
The problem is using the Component Editor menu on multiple selected instances of my button control. If just one button is selected and I right click in the Designer, my menu is shown at the top of the context menu, however multiple selected controls do not display the Component Editor menu.
Code Sample
type
TMyButtonEditor = class(TComponentEditor)
public
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;
implementation
{ TMyButtonEditor }
procedure TMyButtonEditor.ExecuteVerb(Index: Integer);
var
OpenDialog: TOpenDialog;
begin
case Index of
0:
begin
OpenDialog := TOpenDialog.Create(nil);
try
OpenDialog.Filter := 'All Files (*.*)|*.*';
if OpenDialog.Execute then
begin
// handle opened file..
end;
finally
OpenDialog.Free;
end;
end;
end;
end;
function TMyButtonEditor.GetVerb(Index: Integer): string;
begin
case Index of
0:
begin
Result := 'Load settings from File...';
end;
end;
end;
function TMyButtonEditor.GetVerbCount: Integer;
begin
Result := 1;
end;
In register procedure unit:
RegisterComponentEditor(TMyButton, TMyButtonEditor);
From what I can see only single components can use a Component Editor at any given time, or am I wrong and they can be used on multiple controls?
I was hoping to select say maybe 3 or 4 of my buttons on the Form Designer and use the Component Editor to apply imported settings on those buttons all at once.
Component editors can only operate on a single component.
This is one very good reason to prefer making properties available through the Object Inspector rather than component editors, wherever possible. Because the Object Inspector can operate on multiple components at once.

Is there any way to disable selecting of text in a memo control?

Is there any way to disable selecting of text in a memo control because it's very anoying.
The memo is Read Only.
I think you should rethink. I realise that your control is used in read-only mode, but still, what if the end user wishes to copy a part of the text? Then he needs to be able to select the part in question.
Still, if you are certain that you need to disable every kind of selection, the easiest approach is to use a TRichEdit instead of the TMemo, and do simply
procedure TForm1.RichEdit1SelectionChange(Sender: TObject);
begin
RichEdit1.SelLength := 0;
end;
You could also use the onMouseUp event
procedure TForm1.Memo1MouseUp(Sender: TObject: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
if Memo1.SelLength > 0 then
Memo1.SelLength := 0;
end;
But, that doesn't stop selecting with the keyboard..
or you could also use the onEnter, and just change the focus to another control on your form.
procedure TForm1.Memo1Enter(Sender: TObject);
begin
Edit1.SetFocus;
end;
I played around with TRichEdit and TMemo until I was bored to tears. Yes, you can do a few tricks with event handling on the object, but it still is not the desired effect - and the cursor winds up blinking somewhere. So the best thing I could find was to use TLabel. I'm using Borland C++ Builder 6, and the \n is translated correctly with inline text strings for TLabel. So,
Label1->Caption = "this is a test of the emergency\n"
"broadcast station, this is only\n"
"a test. If this had been an\n"
"actual emergency, blah blah blah...\n";
Works just fine. I haven't tried to read in from a file, but I'm certain that if the stream were exactly as seen it would also work. Since you are going to have to enter or read in the text you want displayed anyway - this should work well instead of using a bunch of TLabels for each line. If you have a concern for word wrapping, you will have to process that portion separately. If it static, then just do it by hand like I did in the example. I sure hope this helps or at least gives an idea...
atomkey -
As i understand you would like to use memo as label actually (and sometimes it really have sense).
When i need to use TcxMemo (memo component from DeveloperExpress) as label i use such simple procedure:
procedure ShowMemoAsLabel(m: TcxMemo);
begin
m.Enabled := False;
m.Properties.ReadOnly := True;
// AH: Unfortunately it doesn't copy some important properties, maybe it will
// be fixed in future versions of DEX, but at moment we do some job ourselves.
m.StyleDisabled := m.Style;
m.StyleDisabled.BorderColor := m.Style.BorderColor;
m.StyleDisabled.BorderStyle := m.Style.BorderStyle;
m.StyleDisabled.Color := m.Style.Color;
m.StyleDisabled.Edges := m.Style.Edges;
m.StyleDisabled.Shadow := m.Style.Shadow;
m.StyleDisabled.TextColor := m.Style.TextColor;
m.StyleDisabled.TextStyle := m.Style.TextStyle;
m.StyleDisabled.TransparentBorder := m.Style.TransparentBorder;
end;

Create an exact copy of TPanel on Delphi5

I have a TPanel pnlMain, where several dynamic TPanels are created (and pnlMain is their Parent) according to user actions, data validations, etc. Every panel contains one colored grid full of strings. Apart from panels, there are some open source arrows components and a picture. Whole bunch of stuff.
Now I want user to be able to print this panel (I asked how to do it on this question), but before printing, user must be presented with a new form, containing copy of pnlMain. On this form user has to do some changes, add few components and then print his customized copy of pnlMain. After printing user will close this form and return to original form with original pnlMain. And – as you can guess – original pnlMain must remain intact.
So is there any clever way to copy whole TPanel and it’s contents? I know I can make it manually iterating through pnlMain.Controls list.
Code based as iterating on child controls, but not bad in anyway ;-)
procedure TForm1.btn1Click(Sender: TObject);
function CloneComponent(AAncestor: TComponent): TComponent;
var
XMemoryStream: TMemoryStream;
XTempName: string;
begin
Result:=nil;
if not Assigned(AAncestor) then
exit;
XMemoryStream:=TMemoryStream.Create;
try
XTempName:=AAncestor.Name;
AAncestor.Name:='clone_' + XTempName;
XMemoryStream.WriteComponent(AAncestor);
AAncestor.Name:=XTempName;
XMemoryStream.Position:=0;
Result:=TComponentClass(AAncestor.ClassType).Create(AAncestor.Owner);
if AAncestor is TControl then TControl(Result).Parent:=TControl(AAncestor).Parent;
XMemoryStream.ReadComponent(Result);
finally
XMemoryStream.Free;
end;
end;
var
aPanel: TPanel;
Ctrl, Ctrl_: TComponent;
i: integer;
begin
//handle the Control (here Panel1) itself first
TComponent(aPanel) := CloneComponent(pnl1);
with aPanel do
begin
Left := 400;
Top := 80;
end;
//now handle the childcontrols
for i:= 0 to pnl1.ControlCount-1 do begin
Ctrl := TComponent(pnl1.Controls[i]);
Ctrl_ := CloneComponent(Ctrl);
TControl(Ctrl_).Parent := aPanel;
TControl(Ctrl_).Left := TControl(Ctrl).Left;
TControl(Ctrl_).top := TControl(Ctrl).top;
end;
end;
code from Delphi3000 article
Too much code... ObjectBinaryToText and ObjectTextToBinary do the job nicely using streaming.
Delphi 7 have a code example, don't know 2009 (or 2006, never bothered to look) still have it.
See D5 help file for those functions (don't have d5 available here).
I'd do it by using RTTI to copy all the properties. You'd still have to iterate over all the controls, but when you need to set up the property values, RTTI can help automate the process. You can get an example towards the bottom of this article, where you'll find a link to some helper code, including a CopyObject routine.

Resources