1 unit Editors;
2 
3 {$MODE Delphi}
4 
5 // Utility unit for the advanced Virtual Treeview demo application which contains the implementation of edit link
6 // interfaces used in other samples of the demo.
7 
8 interface
9 
10 uses
11   LCLIntf, delphicompat, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
12   StdCtrls, Laz.VirtualTrees, Buttons, ExtCtrls, MaskEdit, LCLType, EditBtn;
13 
14 type
15   // Describes the type of value a property tree node stores in its data property.
16   TValueType = (
17     vtNone,
18     vtString,
19     vtPickString,
20     vtNumber,
21     vtPickNumber,
22     vtMemo,
23     vtDate
24   );
25 
26 //----------------------------------------------------------------------------------------------------------------------
27 
28 type
29   // Node data record for the the document properties treeview.
30   PPropertyData = ^TPropertyData;
31   TPropertyData = record
32     ValueType: TValueType;
33     Value: String;      // This value can actually be a date or a number too.
34     Changed: Boolean;
35   end;
36 
37   // Our own edit link to implement several different node editors.
38 
39   { TPropertyEditLink }
40 
41   TPropertyEditLink = class(TInterfacedObject, IVTEditLink)
42   private
43     FEdit: TWinControl;        // One of the property editor classes.
44     FTree: TVirtualStringTree; // A back reference to the tree calling.
45     FNode: PVirtualNode;       // The node being edited.
46     FColumn: Integer;          // The column of the node being edited.
47   protected
48     procedure EditExit(Sender: TObject);
49     procedure EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
50   public
51     destructor Destroy; override;
52 
BeginEditnull53     function BeginEdit: Boolean; stdcall;
CancelEditnull54     function CancelEdit: Boolean; stdcall;
EndEditnull55     function EndEdit: Boolean; stdcall;
GetBoundsnull56     function GetBounds: TRect; stdcall;
PrepareEditnull57     function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
58     procedure ProcessMessage(var Message: TMessage); stdcall;
59     procedure SetBounds(R: TRect); stdcall;
60   end;
61 
62 //----------------------------------------------------------------------------------------------------------------------
63 
64 type
65   TPropertyTextKind = (
66     ptkText,
67     ptkHint
68   );
69 
70 // The following constants provide the property tree with default data.
71 
72 const
73   // Types of editors to use for a certain node in VST3.
74   ValueTypes: array[0..1, 0..12] of TValueType = (
75     (
76       vtString,     // Title
77       vtString,     // Theme
78       vtPickString, // Category
79       vtMemo,       // Keywords
80       vtNone,       // Template
81       vtNone,       // Page count
82       vtNone,       // Word count
83       vtNone,       // Character count
84       vtNone,       // Lines
85       vtNone,       // Paragraphs
86       vtNone,       // Scaled
87       vtNone,       // Links to update
88       vtMemo),      // Comments
89     (
90       vtString,     // Author
91       vtNone,       // Most recently saved by
92       vtNumber,     // Revision number
93       vtPickString, // Primary application
94       vtString,     // Company name
95       vtNone,       // Creation date
96       vtDate,       // Most recently saved at
97       vtNone,       // Last print
98       vtNone,
99       vtNone,
100       vtNone,
101       vtNone,
102       vtNone)
103   );
104 
105   // types of editors to use for a certain node in VST3
106   DefaultValue: array[0..1, 0..12] of String = (
107     (
108       'Virtual Treeview',         // Title
109       'native Delphi controls',   // Theme
110       'Virtual Controls',         // Category
111       'virtual, treeview, VCL',   // Keywords
112       'no template used',         // Template
113       '> 900',                    // Page count
114       '?',                        // Word count
115       '~ 1.000.000',              // Character count
116       '~ 28.000',                 // Lines
117       '',                         // Paragraphs
118       'False',                    // Scaled
119       'www.delphi-gems.com',    // Links to update
120       'Virtual Treeview is much more than a simple treeview.'), // Comments
121     (
122       'Dipl. Ing. Mike Lischke',  // Author
123       'Mike Lischke',             // Most recently saved by
124       '3.0',                      // Revision number
125       'Delphi',                   // Primary application
126       '',                         // Company name
127       'July 1999',                // Creation date
128       'January 2002',             // Most recently saved at
129       '',                         // Last print
130       '',
131       '',
132       '',
133       '',
134       '')
135   );
136 
137   // Fixed strings for property tree (VST3).
138   PropertyTexts: array[0..1, 0..12, TPropertyTextKind] of string = (
139     (// first (upper) subtree
140      ('Title', 'Title of the file or document'),
141      ('Theme', 'Theme of the file or document'),
142      ('Category', 'Category of theme'),
143      ('Keywords', 'List of keywords which describe the content of the file'),
144      ('Template', 'Name of the template which was used to create the document'),
145      ('Page count', 'Number of pages in the document'),
146      ('Word count', 'Number of words in the document'),
147      ('Character count', 'Number of characters in the document'),
148      ('Lines', 'Number of lines in the document'),
149      ('Paragraphs', 'Number of paragraphs in the document'),
150      ('Scaled', 'Scaling of the document for output'),
151      ('Links to update', 'Links which must be updated'),
152      ('Comments', 'Description or comments for the file')
153      ),
154     (// second (lower) subtree
155      ('Author', 'name of the author of the file or document'),
156      ('Most recently saved by', 'Name of the person who has saved the document last'),
157      ('Revision number', 'Revision number of the file or document'),
158      ('Primary application', 'Name of the application which is primarily used to create this kind of file'),
159      ('Company name', 'Name of the company or institution'),
160      ('Creation date', 'Date when the file or document was created'),
161      ('Most recently saved at', 'Date when the file or document was saved the last time'),
162      ('Last print', 'Date when the file or document was printed the last time'),
163      ('', ''),   // the remaining 5 entries are not used
164      ('', ''),
165      ('', ''),
166      ('', ''),
167      ('', '')
168    )
169   );
170 
171 //----------------------------------------------------------------------------------------------------------------------
172 
173 type
174   PGridData = ^TGridData;
175   TGridData = record
176     ValueType: array[0..3] of TValueType; // one for each column
177     Value: array[0..3] of Variant;
178     Changed: Boolean;
179   end;
180 
181   // Our own edit link to implement several different node editors.
182 
183   { TGridEditLink }
184 
185   TGridEditLink = class(TPropertyEditLink, IVTEditLink)
186   public
EndEditnull187     function EndEdit: Boolean; stdcall;
PrepareEditnull188     function PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean; stdcall;
189   end;
190 
191 //----------------------------------------------------------------------------------------------------------------------
192 
193 implementation
194 
195 uses
196   PropertiesDemo, GridDemo;
197 
198 //----------------- TPropertyEditLink ----------------------------------------------------------------------------------
199 
200 // This implementation is used in VST3 to make a connection beween the tree
201 // and the actual edit window which might be a simple edit, a combobox
202 // or a memo etc.
203 
204 destructor TPropertyEditLink.Destroy;
205 
206 begin
207   Application.ReleaseComponent(FEdit);
208   inherited;
209 end;
210 
211 //----------------------------------------------------------------------------------------------------------------------
212 
213 procedure TPropertyEditLink.EditExit(Sender: TObject);
214 begin
215   FTree.EndEditNode;
216 end;
217 
218 type
219   TVirtualStringTreeAccess = class(TVirtualStringTree);
220 
221 procedure TPropertyEditLink.EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
222 var
223   CanAdvance: Boolean;
224   node: PVirtualNode;
225   col: TColumnIndex;
onsiderAllowFocusnull226   GetStartColumn: function(ConsiderAllowFocus: Boolean = False): TColumnIndex of object;
olumnnull227   GetNextColumn: function(Column: TColumnIndex; ConsiderAllowFocus: Boolean = False): TColumnIndex of object;
228   GetNextNode: TGetNextNodeProc;
229 
230 begin
231   CanAdvance := true;
232 
233   case Key of
234     VK_ESCAPE:
235       if CanAdvance then
236       begin
237         FTree.CancelEditNode;
238         Key := 0;
239       end;
240 
241     VK_RETURN:
242       if CanAdvance then
243       begin
244         FTree.InvalidateNode(FNode);
245         if (ssShift in Shift) then
246           node := FTree.GetPreviousVisible(FNode, True)
247         else
248           node := FTree.GetNextVisible(FNode, True);
249         FTree.EndEditNode;
250         if node <> nil then FTree.FocusedNode := node;
251         Key := 0;
252         if FTree.CanEdit(FTree.FocusedNode, FTree.FocusedColumn) then
253           {$PUSH}
254           {$OBJECTCHECKS OFF}
255           TVirtualStringTreeAccess(FTree).DoEdit;
256           {$POP}
257       end;
258 
259     VK_UP,
260     VK_DOWN:
261       begin
262         // Consider special cases before finishing edit mode.
263         CanAdvance := Shift = [];
264         if FEdit is TComboBox then
265           CanAdvance := CanAdvance and not TComboBox(FEdit).DroppedDown;
266         //todo: there's no way to know if date is being edited in LCL
267         //if FEdit is TDateEdit then
268         //  CanAdvance := CanAdvance and not TDateEdit(FEdit).DroppedDown;
269 
270         if CanAdvance then
271         begin
272           // Forward the keypress to the tree. It will asynchronously change the focused node.
273           PostMessage(FTree.Handle, WM_KEYDOWN, Key, 0);
274           Key := 0;
275         end;
276       end;
277 
278     VK_TAB:
279       if CanAdvance then
280       begin
281         FTree.InvalidateNode(FNode);
282         if ssShift in Shift then
283         begin
284           GetStartColumn := FTree.Header.Columns.GetLastVisibleColumn;
285           GetNextColumn := FTree.Header.Columns.GetPreviousVisibleColumn;
286           GetNextNode := FTree.GetPreviousVisible;
287         end
288         else
289         begin
290           GetStartColumn := FTree.Header.Columns.GetFirstVisibleColumn;
291           GetNextColumn := FTree.Header.Columns.GetNextVisibleColumn;
292           GetNextNode := FTree.GetNextVisible;
293         end;
294 
295         // Advance to next/previous visible column/node.
296         node := FNode;
297         col := GetNextColumn(FColumn, True);
298         repeat
299           // Find a column for the current node which can be focused.
300           while (col > NoColumn) and
301           {$PUSH}
302           {$OBJECTCHECKS OFF}
303             not TVirtualStringTreeAccess(FTree).DoFocusChanging(FNode, node, FColumn, col)
304           {$POP}
305           do
306             col := GetNextColumn(col, True);
307 
308           if col > NoColumn then
309           begin
310             // Set new node and column in one go.
311             {$PUSH}
312             {$OBJECTCHECKS OFF}
313             TVirtualStringTreeAccess(FTree).SetFocusedNodeAndColumn(node, col);
314             {$POP}
315             Break;
316           end;
317 
318           // No next column was accepted for the current node. So advance to next node and try again.
319           node := GetNextNode(node);
320           col := GetStartColumn();
321         until node = nil;
322 
323         FTree.EndEditNode;
324         Key := 0;
325         if node <> nil then
326         begin
327           FTree.FocusedNode := node;
328           FTree.FocusedColumn := col;
329         end;
330         if FTree.CanEdit(FTree.FocusedNode, FTree.FocusedColumn) then
331           {$PUSH}
332           {$OBJECTCHECKS OFF}
333           with TVirtualStringTreeAccess(FTree) do
334           begin
335             EditColumn := FocusedColumn;
336             DoEdit;
337           end;
338         {$POP}
339       end;
340 
341   end;
342 end;
343 
344 //----------------------------------------------------------------------------------------------------------------------
345 
BeginEditnull346 function TPropertyEditLink.BeginEdit: Boolean; stdcall;
347 
348 begin
349   Result := True;
350   FEdit.Show;
351   FEdit.SetFocus;
352 end;
353 
354 //----------------------------------------------------------------------------------------------------------------------
355 
CancelEditnull356 function TPropertyEditLink.CancelEdit: Boolean; stdcall;
357 
358 begin
359   Result := True;
360   FEdit.Hide;
361 end;
362 
363 //----------------------------------------------------------------------------------------------------------------------
364 
EndEditnull365 function TPropertyEditLink.EndEdit: Boolean; stdcall;
366 
367 var
368   Data: PPropertyData;
369   Buffer: array[0..1024] of Char;
370   S: String;
371 
372 begin
373   Result := True;
374 
375   Data := FTree.GetNodeData(FNode);
376   if FEdit is TComboBox then
377     S := TComboBox(FEdit).Text
378   else
379   begin
380     if FEdit is TCustomEdit then
381       S := TCustomEdit(FEdit).Text
382     else
383       raise Exception.Create('Unknow edit control');
384   end;
385 
386   if S <> Data.Value then
387   begin
388     Data.Value := S;
389     Data.Changed := True;
390     FTree.InvalidateNode(FNode);
391   end;
392   FEdit.Hide;
393   FTree.SetFocus;
394 end;
395 
396 //----------------------------------------------------------------------------------------------------------------------
397 
GetBoundsnull398 function TPropertyEditLink.GetBounds: TRect; stdcall;
399 
400 begin
401   Result := FEdit.BoundsRect;
402 end;
403 
404 //----------------------------------------------------------------------------------------------------------------------
405 
PrepareEditnull406 function TPropertyEditLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode;
407   Column: TColumnIndex): Boolean; stdcall;
408 
409 var
410   Data: PPropertyData;
411 
412 begin
413   Result := True;
414   FTree := Tree as TVirtualStringTree;
415   FNode := Node;
416   FColumn := Column;
417 
418   // determine what edit type actually is needed
419   FEdit.Free;
420   FEdit := nil;
421   Data := FTree.GetNodeData(Node);
422   case Data.ValueType of
423     vtString:
424       begin
425         FEdit := TEdit.Create(nil);
426         with FEdit as TEdit do
427         begin
428           Visible := False;
429           Parent := Tree;
430           Text := Data.Value;
431         end;
432       end;
433     vtPickString:
434       begin
435         FEdit := TComboBox.Create(nil);
436         with FEdit as TComboBox do
437         begin
438           Visible := False;
439           Parent := Tree;
440           Text := Data.Value;
441           Items.Add(Text);
442           Items.Add('Standard');
443           Items.Add('Additional');
444           Items.Add('Win32');
445         end;
446       end;
447     vtNumber:
448       begin
449         FEdit := TMaskEdit.Create(nil);
450         with FEdit as TMaskEdit do
451         begin
452           Visible := False;
453           Parent := Tree;
454           EditMask := '9999';
455           Text := Data.Value;
456         end;
457       end;
458     vtPickNumber:
459       begin
460         FEdit := TComboBox.Create(nil);
461         with FEdit as TComboBox do
462         begin
463           Visible := False;
464           Parent := Tree;
465           Text := Data.Value;
466         end;
467       end;
468     vtMemo:
469       begin
470         FEdit := TComboBox.Create(nil);
471         // In reality this should be a drop down memo but this requires
472         // a special control.
473         with FEdit as TComboBox do
474         begin
475           Visible := False;
476           Parent := Tree;
477           Text := Data.Value;
478           Items.Add(Data.Value);
479         end;
480       end;
481     vtDate:
482       begin
483         FEdit := TDateEdit.Create(nil);
484         with FEdit as TDateEdit do
485         begin
486           Visible := False;
487           Parent := Tree;
488           Date := StrToDate(Data.Value);
489         end;
490       end;
491   else
492     Result := False;
493   end;
494   if Result then
495   begin
496     FEdit.OnKeyDown := EditKeyDown;
497     FEdit.OnExit := EditExit;
498   end;
499 end;
500 
501 //----------------------------------------------------------------------------------------------------------------------
502 
503 procedure TPropertyEditLink.ProcessMessage(var Message: TMessage); stdcall;
504 
505 begin
506   FEdit.WindowProc(Message);
507 end;
508 
509 //----------------------------------------------------------------------------------------------------------------------
510 
511 procedure TPropertyEditLink.SetBounds(R: TRect); stdcall;
512 
513 var
514   Dummy: Integer;
515 
516 begin
517   // Since we don't want to activate grid extensions in the tree (this would influence how the selection is drawn)
518   // we have to set the edit's width explicitly to the width of the column.
519   FTree.Header.Columns.GetColumnBounds(FColumn, Dummy, R.Right);
520   if FEdit is TDateEdit then
521     R.Right := R.Right - TDateEdit(FEdit).ButtonWidth;
522   FEdit.BoundsRect := R;
523 end;
524 
525 //---------------- TGridEditLink ---------------------------------------------------------------------------------------
526 
EndEditnull527 function TGridEditLink.EndEdit: Boolean;
528 
529 var
530   Data: PGridData;
531   Buffer: array[0..1024] of Char;
532   //S: WideString;
533   S: String;
534   I: Integer;
535   D: TDateTime;
536 
537 begin
538   Result := True;
539   Data := FTree.GetNodeData(FNode);
540   if FEdit is TComboBox then
541   begin
542     S := TComboBox(FEdit).Text;
543     if S <> Data.Value[FColumn - 1] then
544     begin
545       Data.Value[FColumn - 1] := S;
546       Data.Changed := True;
547     end;
548   end
549   else
550   if FEdit is TMaskEdit then
551   begin
552     I := StrToInt(Trim(TMaskEdit(FEdit).EditText));
553     if I <> Data.Value[FColumn - 1] then
554     begin
555       Data.Value[FColumn - 1] := I;
556       Data.Changed := True;
557     end;
558   end
559   else
560   if FEdit is TCustomEdit then
561   begin
562     S := TCustomEdit(FEdit).Text;
563     if S <> Data.Value[FColumn - 1] then
564     begin
565       Data.Value[FColumn - 1] := S;
566       Data.Changed := True;
567     end;
568   end
569   else
570   if FEdit is TDateEdit then
571   begin
572     D := TDateEdit(FEdit).Date;
573     if D <> Data.Value[FColumn - 1] then
574     begin
575       Data.Value[FColumn - 1] := D;
576       Data.Changed := True;
577     end;
578   end
579   else
580     raise Exception.Create('Unknow Edit Control');
581 
582   if Data.Changed then
583     FTree.InvalidateNode(FNode);
584   FEdit.Hide;
585 end;
586 
587 //----------------------------------------------------------------------------------------------------------------------
588 
PrepareEditnull589 function TGridEditLink.PrepareEdit(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex): Boolean;
590 
591 var
592   Data: PGridData;
593   TempText: String;
594 begin
595   Result := True;
596   FTree := Tree as TVirtualStringTree;
597   FNode := Node;
598   FColumn := Column;
599 
600   // Determine what edit type actually is needed.
601   FEdit.Free;
602   FEdit := nil;
603   Data := FTree.GetNodeData(Node);
604   case Data.ValueType[FColumn - 1] of
605     vtString:
606       begin
607         FEdit := TEdit.Create(nil);
608         with FEdit as TEdit do
609         begin
610           Visible := False;
611           Parent := Tree;
612           TempText:= Data.Value[FColumn - 1];
613           Text := TempText;
614           OnKeyDown := EditKeyDown;
615         end;
616       end;
617     vtPickString:
618       begin
619         FEdit := TComboBox.Create(nil);
620         with FEdit as TComboBox do
621         begin
622           Visible := False;
623           Parent := Tree;
624           TempText:= Data.Value[FColumn - 1];
625           Text := TempText;
626           // Here you would usually do a lookup somewhere to get
627           // values for the combobox. We only add some dummy values.
628           case FColumn of
629             2:
630               begin
631                 Items.Add('John');
632                 Items.Add('Mike');
633                 Items.Add('Barney');
634                 Items.Add('Tim');
635               end;
636             3:
637               begin
638                 Items.Add('Doe');
639                 Items.Add('Lischke');
640                 Items.Add('Miller');
641                 Items.Add('Smith');
642               end;
643           end;
644           OnKeyDown := EditKeyDown;
645         end;
646       end;
647     vtNumber:
648       begin
649         FEdit := TMaskEdit.Create(nil);
650         with FEdit as TMaskEdit do
651         begin
652           Visible := False;
653           Parent := Tree;
654           EditMask := '9999;0; ';
655           TempText:= Data.Value[FColumn - 1];
656           Text := TempText;
657           OnKeyDown := EditKeyDown;
658         end;
659       end;
660     vtPickNumber:
661       begin
662         FEdit := TComboBox.Create(nil);
663         with FEdit as TComboBox do
664         begin
665           Visible := False;
666           Parent := Tree;
667           TempText:= Data.Value[FColumn - 1];
668           Text := TempText;
669           OnKeyDown := EditKeyDown;
670         end;
671       end;
672     vtMemo:
673       begin
674         FEdit := TComboBox.Create(nil);
675         // In reality this should be a drop down memo but this requires
676         // a special control.
677         with FEdit as TComboBox do
678         begin
679           Visible := False;
680           Parent := Tree;
681           TempText:= Data.Value[FColumn - 1];
682           Text := TempText;
683           Items.Add(Data.Value[FColumn - 1]);
684           OnKeyDown := EditKeyDown;
685         end;
686       end;
687     vtDate:
688       begin
689         FEdit := TDateEdit.Create(nil);
690         with FEdit as TDateEdit do
691         begin
692           Visible := False;
693           Parent := Tree;
694           Date := StrToDate(Data.Value[FColumn - 1]);
695           OnKeyDown := EditKeyDown;
696         end;
697       end;
698   else
699     Result := False;
700   end;
701 end;
702 
703 //----------------------------------------------------------------------------------------------------------------------
704 
705 end.
706