Solved

Override procedure from Virtual Treeview

Posted on 2013-01-03
14
974 Views
Last Modified: 2013-01-14
Hi

I did a little change in Virtual Treeview code. I enabled Selection with Alt key. It seems that this was disabled for some reason. I need my Treeview to be able to select nodes with Alt key.

So, I just commented these rows:

if ssAlt in ShiftState then
    begin
      AltPressed := True;
      // Remove the Alt key from the shift state. It is not meaningful there.
      Exclude(ShiftState, ssAlt);
    end
    else
      AltPressed := False;

Open in new window


to

if ssAlt in ShiftState then
    begin
    // Commented next 3 lines to enable Selecting rows with Alt key - this code was disabling select with Alt
(*      AltPressed := True;
      // Remove the Alt key from the shift state. It is not meaningful there.
      Exclude(ShiftState, ssAlt);*)
    end
    else
      AltPressed := False;

Open in new window



It works fine.

Now, I was thinking to override (have a new code in my unit) the whole procedure
procedure TBaseVirtualTree.HandleMouseDown(var Message: TWMMouse; var HitInfo: THitInfo);

Open in new window

so I don't change the original code. This way I don't loose my changes when upgrading to new version of VirtualTreeview from my current 4.8.6.

How can I do this?
The definition is in VirtualTrees.pas, in protected part of TBaseVirtualTree
TBaseVirtualTree = class(TCustomControl)
...
protected
...
procedure HandleMouseDown(var Message: TWMMouse; var HitInfo: THitInfo); virtual;

Open in new window



Thank you for advice!
0
Comment
Question by:Delphi_developer
  • 6
  • 4
  • 3
  • +1
14 Comments
 
LVL 32

Expert Comment

by:ewangoya
ID: 38740735
Two ways to do it
1. Create a new unit and register the new component in your delphi components
    unit CustomVitualTree;
    implementation
    type
      TMyBaseVirtualTree = class(TCustomControl)
       protected
       procedure HandleMouseDown(var Message: TWMMouse; var HitInfo: THitInfo); override;
     end;

2. In the unit where you are using the Virtual tree, just before the declaration of the Form type. But this is only available to your current form where the VirtualTree object is used

type
TBaseVirtualTree = class(VirtualTrees.TBaseVirtualTree)
protected
  procedure HandleMouseDown(var Message: TWMMouse; var HitInfo: THitInfo); override;
end;

//Your current code
TYourForm = class(TForm)
 ....
end;
0
 
LVL 32

Assisted Solution

by:sarabande
sarabande earned 20 total points
ID: 38740753
i don't know delphi very good. but from a general programming view you would derive your own treeview class from TBaseVirtualTree and provide an override of the HandleMouseDown function. then replace all occurences of TBaseVirtualTree in your code by the name of the derived tree classes.

Sara
0
 

Author Comment

by:Delphi_developer
ID: 38740870
@ewangoya:
thank you, and where and how do I put the procedure code? I tried just copy&paste the whole HandleMouseDown procedure to main unit, but gives errors on 'Undeclared identifier' for some properties of TBaseVirtualTree defined in original unit (VirtualTrees.pas).

Suggestion?

Edit: I use suggestion #2.
0
 
LVL 32

Expert Comment

by:ewangoya
ID: 38741215
For the members that are not known, try and look for the public or protected propertis that access those members. For example, it the error is undeclared identifier FStates, then there is a property TreeStates that accesses FStates. Replace every occurence of FStates with TreeStates.

You may encounter undeclared procedure or function, in such a case, you have to reimplement the procedure in your class
0
 
LVL 32

Assisted Solution

by:ewangoya
ewangoya earned 100 total points
ID: 38741272
Here is an example, note the replacements for FStates and FOptions. This gives you an idea of what you would have to do
Now if unfortunately there are members which don't have accessor properties, you are kind of stuck

type
  TAccessCustomVirtualTreeOptions = class(TCustomVirtualTreeOptions)
  end;

  TVirtualStringTree = class(VirtualTrees.TVirtualStringTree)
  private
    procedure StopTimer(const ID: Integer);
  protected
    procedure HandleMouseDown(var Message: TWMMouse; const HitInfo: THitInfo); override;
  end;

  TForm1 = class(TForm)
    VirtualStringTree1: TVirtualStringTree;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TBaseVirtualTree }

procedure TVirtualStringTree.HandleMouseDown(var Message: TWMMouse; const HitInfo: THitInfo);

var
  LastFocused: PVirtualNode;
  Column: TColumnIndex;
  ShiftState: TShiftState;

  // helper variables to shorten boolean equations/expressions
  AutoDrag,              // automatic (or allowed) drag start
  IsHit,                 // the node's caption or images are hit
  IsCellHit,             // for grid extension or full row select (but not check box, button)
  IsAnyHit,              // either IsHit or IsCellHit
  MultiSelect,           // multiselection is enabled
  ShiftEmpty,            // ShiftState = []
  NodeSelected: Boolean; // the new node (if any) is selected
  NewColumn: Boolean;    // column changed
  NewNode: Boolean;      // Node changed.
  NeedChange: Boolean;   // change event is required for selection change
  CanClear: Boolean;
  NewCheckState: TCheckState;
  AltPressed: Boolean;   // Pressing the Alt key enables special processing for selection.
  FullRowDrag: Boolean;  // Start dragging anywhere within a node's bound.

begin
  if [tsWheelPanning, tsWheelScrolling] * TreeStates <> [] then
  begin
    StopWheelPanning;
    Exit;
  end;

  if tsEditPending in TreeStates then
  begin
    StopTimer(EditTimer);
    DoStateChange([], [tsEditPending]);
  end;

  if not (tsEditing in TreeStates) or DoEndEdit then
  begin
    // Focus change. Don't use the SetFocus method as this does not work for MDI windows.
    if not Focused and CanFocus then
      Windows.SetFocus(Handle);

    // Keep clicked column in case the application needs it.
    Header.Columns.ClickIndex := HitInfo.HitColumn;

    // Change column only if we have hit the node label.
    if (hiOnItemLabel in HitInfo.HitPositions) or
      (toFullRowSelect in TAccessCustomVirtualTreeOptions(TreeOptions).SelectionOptions) or
      (toGridExtensions in TAccessCustomVirtualTreeOptions(TreeOptions).MiscOptions) then
    begin
      NewColumn := FocusedColumn <> HitInfo.HitColumn;
      if toExtendedFocus in TAccessCustomVirtualTreeOptions(TreeOptions).SelectionOptions then
        Column := HitInfo.HitColumn
      else
        Column := Header.MainColumn;
    end
    else
    begin
      NewColumn := False;
      Column := FocusedColumn;
    end;

    if NewColumn and
       (not Header.AllowFocus(Column)) then
    begin
      NewColumn := False;
      Column := FocusedColumn;
    end;

    NewNode := FocusedNode <> HitInfo.HitNode;

    // Translate keys and filter out shift and control key.
    ShiftState := KeysToShiftState(Message.Keys) * [ssShift, ssCtrl, ssAlt];
    if ssAlt in ShiftState then
    begin
      AltPressed := True;
      // Remove the Alt key from the shift state. It is not meaningful there.
      Exclude(ShiftState, ssAlt);
    end
    else
      AltPressed := False;

    // Various combinations determine what states the tree enters now.
    // We initialize shorthand variables to avoid the following expressions getting too large
    // and to avoid repeative expensive checks.
    IsHit := not AltPressed and ((hiOnItemLabel in HitInfo.HitPositions) or (hiOnNormalIcon in HitInfo.HitPositions));
    IsCellHit := not AltPressed and not IsHit and Assigned(HitInfo.HitNode) and
      ([hiOnItemButton, hiOnItemCheckBox] * HitInfo.HitPositions = []) and
      ((toFullRowSelect in TAccessCustomVirtualTreeOptions(TreeOptions).SelectionOptions) or (toGridExtensions in TAccessCustomVirtualTreeOptions(TreeOptions).MiscOptions));
    IsAnyHit := IsHit or IsCellHit;
    MultiSelect := toMultiSelect in TAccessCustomVirtualTreeOptions(TreeOptions).SelectionOptions;
    ShiftEmpty := ShiftState = [];
    NodeSelected := IsAnyHit and (vsSelected in HitInfo.HitNode.States);
    FullRowDrag := toFullRowDrag in FOptions.FMiscOptions;

    // Dragging might be started in the inherited handler manually (which is discouraged for stability reasons)
    // the test for manual mode is done below (after the focused node is set).
    AutoDrag := ((DragMode = dmAutomatic) or Dragging) and (not IsCellHit or FullRowDrag);

    // Query the application to learn if dragging may start now (if set to dmManual).
    if Assigned(HitInfo.HitNode) and not AutoDrag and (DragMode = dmManual) then
      AutoDrag := DoBeforeDrag(HitInfo.HitNode, Column) and (not IsCellHit or FullRowDrag);

    // handle button clicks
    if (hiOnItemButton in HitInfo.HitPositions) and (vsHasChildren in HitInfo.HitNode.States) then
    begin
      ToggleNode(HitInfo.HitNode);
      Exit;
    end;

    // check event
    if hiOnItemCheckBox in HitInfo.HitPositions then
    begin
      if (TreeStates * [tsMouseCheckPending, tsKeyCheckPending] = []) and not (vsDisabled in HitInfo.HitNode.States) then
      begin
        with HitInfo.HitNode^ do
          NewCheckState := DetermineNextCheckState(CheckType, CheckState);
        if DoChecking(HitInfo.HitNode, NewCheckState) then
        begin
          DoStateChange([tsMouseCheckPending]);
          CheckNode := HitInfo.HitNode;
          FPendingCheckState := NewCheckState;
          FCheckNode.CheckState := PressedState[FCheckNode.CheckState];
          InvalidateNode(HitInfo.HitNode);
        end;
      end;
      Exit;
    end;

    // Keep this node's level in case we need it for constraint selection.
    if (FRoot.ChildCount > 0) and ShiftEmpty or (FSelectionCount = 0) then
      if Assigned(HitInfo.HitNode) then
        FLastSelectionLevel := GetNodeLevel(HitInfo.HitNode)
      else
        FLastSelectionLevel := GetNodeLevel(GetLastVisibleNoInit);

    // pending clearance
    if MultiSelect and ShiftEmpty and not (hiOnItemCheckbox in HitInfo.HitPositions) and IsAnyHit and AutoDrag and
      NodeSelected then
      DoStateChange([tsClearPending]);

    // immediate clearance
    // Determine for the right mouse button if there is a popup menu. In this case and if drag'n drop is pending
    // the current selection has to stay as it is.
    with HitInfo, Message do
      CanClear := not AutoDrag and
        (not (tsRightButtonDown in FStates) or not HasPopupMenu(HitNode, HitColumn, Point(XPos, YPos)));
    if (not (IsAnyHit or FullRowDrag) and MultiSelect and ShiftEmpty) or
      (IsAnyHit and (not NodeSelected or (NodeSelected and CanClear)) and (ShiftEmpty or not MultiSelect)) then
    begin
      Assert(not (tsClearPending in FStates), 'Pending and direct clearance are mutual exclusive!');

      // If the currently hit node was already selected then we have to reselect it again after clearing the current
      // selection, but without a change event if it is the only selected node.
      // The same applies if the Alt key is pressed, which allows to start drawing the selection rectangle also
      // on node captions and images. Here the previous selection state does not matter, though.
      if NodeSelected or (AltPressed and Assigned(HitInfo.HitNode) and (HitInfo.HitColumn = FHeader.MainColumn)) then
      begin
        NeedChange := FSelectionCount > 1;
        InternalClearSelection;
        InternalAddToSelection(HitInfo.HitNode, True);
        if NeedChange then
        begin
          Invalidate;
          Change(nil);
        end;
      end
      else
        ClearSelection;
    end;

    // pending node edit
    if Focused and
      ((hiOnItemLabel in HitInfo.HitPositions) or ((toGridExtensions in FOptions.FMiscOptions) and
      (hiOnItem in HitInfo.HitPositions))) and NodeSelected and not NewColumn and ShiftEmpty then
      DoStateChange([tsEditPending]);

    // User starts a selection with a selection rectangle.
    if not (toDisableDrawSelection in FOptions.FSelectionOptions) and not (IsHit or FullRowDrag) and MultiSelect then
    begin
      SetCapture(Handle);
      DoStateChange([tsDrawSelPending]);
      FDrawSelShiftState := ShiftState;
      FNewSelRect := Rect(Message.XPos + FEffectiveOffsetX, Message.YPos - FOffsetY, Message.XPos + FEffectiveOffsetX,
        Message.YPos - FOffsetY);
      FLastSelRect := Rect(0, 0, 0, 0);
      if not IsCellHit then
        Exit;
    end;

    // Keep current mouse position.
    FLastClickPos := Point(Message.XPos, Message.YPos);

    // Handle selection and node focus change.
    if (IsHit or IsCellHit) and
       DoFocusChanging(FFocusedNode, HitInfo.HitNode, FFocusedColumn, Column) then
    begin
      if NewColumn then
      begin
        InvalidateColumn(FFocusedColumn);
        InvalidateColumn(Column);
        FFocusedColumn := Column;
      end;
      if DragKind = dkDock then
      begin
        StopTimer(ScrollTimer);
        DoStateChange([], [tsScrollPending, tsScrolling]);
      end;
      // Get the currently focused node to make multiple multi-selection blocks possible.
      LastFocused := FFocusedNode;
      if NewNode then
        DoFocusNode(HitInfo.HitNode, False);

      if MultiSelect and not ShiftEmpty then
        HandleClickSelection(LastFocused, HitInfo.HitNode, ShiftState, AutoDrag)
      else
      begin
        if ShiftEmpty then
          FRangeAnchor := HitInfo.HitNode;

        // If the hit node is not yet selected then do it now.
        if not NodeSelected then
          AddToSelection(HitInfo.HitNode);
      end;

      if NewNode or NewColumn then
      begin
        ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions,
                       not (toDisableAutoscrollOnFocus in FOptions.FAutoOptions));
        DoFocusChange(FFocusedNode, FFocusedColumn);
      end;
    end;

    // Drag'n drop initiation
    // If we lost focus in the interim the button states would be cleared in WM_KILLFOCUS.
    if AutoDrag and IsAnyHit and (FStates * [tsLeftButtonDown, tsRightButtonDown, tsMiddleButtonDown] <> []) then
      BeginDrag(False);
  end;

end;

procedure TVirtualStringTree.StopTimer(const ID: Integer);
begin
  if HandleAllocated then
    KillTimer(Handle, ID);
end;

Open in new window

0
 

Author Comment

by:Delphi_developer
ID: 38742035
Thank you, but this is too complicated, I thought it would be much easier - just a definition and new code. Let me play with it and get back to you.
0
 
LVL 25

Assisted Solution

by:Sinisa Vuk
Sinisa Vuk earned 50 total points
ID: 38743051
The most easiest method is to make copy of source (.pas) where you want to make changes and put it in folder where your project is. make changes in that file and add it to uses. When you will compile then delphi looks in project first. Other projects will be linked with original source.
For experienced developer it is best to inherited original class (if you do not have source or because ofcopyright issues)
0
Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

 

Accepted Solution

by:
Delphi_developer earned 0 total points
ID: 38760244
Thank you, but this was too complicated solution.
What i decided to do is to change the source and make sure I'm notified when I overwrite with newer version. I do this by defined a var or const in source and use it in my app - simple as:  
sleep(myConstInVirtualTree * 0);

Open in new window

... and this const defined in source has a comment that this source has been changed and need to be backed up to use it on new installations or copy modified code to new source.

This should work good enough, for now.

Can I anticipate any issues with this approach?
0
 
LVL 25

Expert Comment

by:Sinisa Vuk
ID: 38760806
why is my solution difficult? You changed source anyway. So just put this modified file in folder where your project is and this is it. Delphi IDE will do the rest.
0
 

Author Comment

by:Delphi_developer
ID: 38760926
Oh, I didn't see your comment at all! It is interesting and easy, but how would it work when source has some other units that relies on?

Now I have all components in different folders in delphi\lib folder. So, virtual treeview has all it's files in delphi\lib\VTVSource folder. So, if I change something and wne I overwrite with incorrect source versoin, I just copy full folder from backup.

If I put just changed file into my app folder, then it is not only to take of this file, but also the whole source folder, since it may rely on files that I didn't change and are different than the new source. Right?

And if I have my system in place, its easier to maintain 'the list' of all files I changed, since I 'don't really need a list' as all changed files are 'connected' in my
ValidateSourceFiles

Open in new window

procedure, which validates for all variables in source files.

I'm not saying your suggestion is not good, just not that practical.

Right?
0
 
LVL 25

Expert Comment

by:Sinisa Vuk
ID: 38761070
No, you don't need to copy the whole folder, just changed file. This is "the big" thing. Delphi IDE will use this file instead of original (where component is installed) and use other
(depending) files from original folder. Main problem is if you change versions of original component often. Then you will need to make changes one time more or create inherited class at least.
0
 

Author Comment

by:Delphi_developer
ID: 38761106
I understand you, completely. That is why I decided my suggestion is best for me. In your case if I don't copy the file in my new folder (new project, rebuilding old project, upgrading Delphi version, etc.), ti will take the original source even if it shouldn't, because I have no 'security' measure implemented.

In my case, as soon as I don't have the correct file in my 'path' I will know. This way I know there is only one source of files, the appropriate folder and not something here something there. This is actually gonna solve some of my dilemmas I had before, when I had installed multiple versions of same component and when new system installation was done, I had to figure out which of the files I was actually using.

You see my point?
0
 
LVL 25

Expert Comment

by:Sinisa Vuk
ID: 38761158
Yes. I show you as I did before. This is common when do changes (fixes) on original delphi source. This way I only backup my projects (folders). When reinstall delphi or some component - I don't bother with changes once more.
0
 

Author Closing Comment

by:Delphi_developer
ID: 38773852
Thank you all for your suggestions, I decided my answer is best choice and assigned points for trying and steering me into my direction.
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

Creating an auto free TStringList The TStringList is a basic and frequently used object in Delphi. On many occasions, you may want to create a temporary list, process some items in the list and be done with the list. In such cases, you have to…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
This video discusses moving either the default database or any database to a new volume.
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.

759 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

22 Experts available now in Live!

Get 1:1 Help Now