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