Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people, just like you, are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
Solved

Override procedure from Virtual Treeview

Posted on 2013-01-03
14
989 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 33

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
Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
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 26

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
 

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 26

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 26

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 26

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

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
This video shows how to quickly and easily add an email signature for all users on Exchange 2016. The resulting signature is applied on a server level by Exchange Online. The email signature template has been downloaded from: www.mail-signatures…
The Email Laundry PDF encryption service allows companies to send confidential encrypted  emails to anybody. The PDF document can also contain attachments that are embedded in the encrypted PDF. The password is randomly generated by The Email Laundr…

856 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