GithubHelp home page GithubHelp logo

Comments (16)

joachimmarder avatar joachimmarder commented on June 2, 2024 1

O(1) stuff also take time. There is no significant performance penalty in using Begin/EndUpdate (or am I getting this wrong?) . Delphi is already very slow when you move a form to a different monitor. The main form of PyScripter contains about 5 VTs. Saving 10 calls to UpdateScrollbars when you switch monitors would not harm. But it is not just UpdateScrollbars. For example TBaseVirtualTree.SetDefaultNodeHeight contains:

OK, convinced and changed in master. Let me know if you see any difference in your app, then I can merge it to the V7_stable branch.

from virtual-treeview.

joachimmarder avatar joachimmarder commented on June 2, 2024 1

I would also remove the IsDPIChange parameter from [...] AutoScale.

Agreed and changed in master.

from virtual-treeview.

pyscripter avatar pyscripter commented on June 2, 2024

Update:
It turns out that the issue was a result of changing a font to the wrong size in a parent Window. So, in a way this was a false alarm. However my comments about ChangeScale and AutoScale still hold.

Here is my go at simplifying ChangeScale:

procedure TBaseVirtualTree.ChangeScale(M, D: Integer{$if CompilerVersion >= 31}; isDpiChange: Boolean{$ifend});
begin
  // Needs to be called before inherited.  Does nothing if M=D. 
  SetDefaultNodeHeight(MulDiv(FDefaultNodeHeight, M, D));
  // changes Fonts and FCurrentPPI.
  inherited ChangeScale(M, D{$if CompilerVersion >= 31}, isDpiChange{$ifend});

  if M = D then
    Exit;

  TVTHeaderCracker(FHeader).ChangeScale(M, D, {$if CompilerVersion >= 31}isDpiChange{$ELSE} M <> D{$ifend});
  Indent := MulDiv(Indent, M, D);
  FTextMargin := MulDiv(FTextMargin, M, D);
  FMargin := MulDiv(FMargin, M, D);
  FImagesMargin := MulDiv(FImagesMargin, M, D);
  // Scale utility images, #796
  if FCheckImageKind = ckSystemDefault then begin
    FreeAndNil(FCheckImages);
    if HandleAllocated then
      FCheckImages := CreateSystemImageSet(Self);
  end;
  UpdateHeaderRect();
  ScaleNodeHeights(M, D);

  PrepareBitmaps(True, False); // See issue #991
end;

In my testing with different monitor configurations, the above scales both the tree and the header correctly, but clearly more testing is needed.

Comments:

  • There is no point in supporting DPI awareness in Delphi version earlier than Berlin (I would even say Rio). It just does not work. Users of such versions should be advised to develop DPI Unaware applications.
  • ChangeScale with IsDPIChange set to False is only called internally to clear the ScalingFlags. There is no need to take action. See TControl.ChangeScale, TCustomForm.ScaleForCurrentDPI (comment at the end) and TWinControl.ScaleControls.
  • I would also remove the IsDPIChange parameter from the Header ChangeScale method (does not make sense for a TPersistent) and AutoScale.
  • If AutoScale changes the default node height, then the height of existing nodes should also change when node height is fixed. Otherwise you end up having nodes of different height. This could be done in SetDefaultNodeHeight. In that case ScaleNodeHeights(M, D) above should only be called if node height is variable.
  • The scaling code above should probably be surrounded by Begin/EndUpdate.

If you agree with the above changes I could submit a PR.

from virtual-treeview.

joachimmarder avatar joachimmarder commented on June 2, 2024

I think it makes absolutely no sense to disable DPI scaling in a DPI aware application. Everything would look wrong. I think that toAutoChangeScale should just control the optimization of the NodeHeight. What do you think?

I agree. It was the way as it is now when I took over the project, and at least some developers using Virtual-Treeview wanted full control over scaling. But I am very open for changing this for the next version 8.0, but I would like to keep it that way in V7, whcih will only receive bugfixes.

from virtual-treeview.

joachimmarder avatar joachimmarder commented on June 2, 2024

There is not point in supporting DPI awareness in Delphi version earlier than Berlin (I would even say Rio). It just does not work.

In an environment where all monitors have the same scaling, but >100%, I think the code still does anything useful. I can't proof this at the moment, but I don't see any benefit in changing this.

I would also remove the IsDPIChange parameter from the Header ChangeScale method (does not make sense for a TPersistent) and AutoScale.

But the parameter is used, and I don't see that it does any harm. What is the exact benefit of this change? We can remove it from TVirtualTreeColumn.ChangeScale().

The scaling code above should probably be surrounded by Begin/EndUpdate.

The only that changes multiple nodes is in ScaleNodeHeights(), and this has calls to Begin/EndUpdat.

from virtual-treeview.

pyscripter avatar pyscripter commented on June 2, 2024

In an environment where all monitors have the same scaling, but >100%, I think the code still does anything useful. I can't proof this at the moment, but I don't see any benefit in changing this.

In a DPI unaware application DPI is always 96 and no scaling is required. If on the other hand users develop DPI aware applications with Delphi versions earlier than Berlin, good luck to them. The scaling of VT would be the least of their problems. But in any case. we can keep the scaling for versions earlier than Berlin. I have modified the code above accordingly.

But the parameter is used, and I don't see that it does any harm.

The point is that it shouldn't be used. Not a major issue, but rather a design principle. Calls to the ChangeScale of a TPersistent are not made by VCL but by the component when scaling is needed. With the suggested changes above the header ChangeScale or AutoSize would never be called when IsDPIChange if False or when M=D.

The only that changes multiple nodes is in ScaleNodeHeights(), and this has calls to Begin/EndUpdat.

You are changing node height, margins, header size etc. Don't each of these result in calls to UpdateScrollbars ? It would save multiple calls to UpdateScrollbars for once (see SetDefaultNodeHeight).

from virtual-treeview.

joachimmarder avatar joachimmarder commented on June 2, 2024

ChangeScale with IsDPIChange set to False is only called internally to clear the ScalingFlags. There is no need to take action.

Are you sure, even if M<>D?

If AutoScale changes the default node height, then the height of existing nodes should also change when node height is fixed.

This should already be the case.

This could be done in SetDefaultNodeHeight.

I don't think this breaking change is a good idea. OK, someone needs to call ScaleNodeHeights() now to achieve this, but vice versa there would be no good way to prevent this automatic scaling.

You are changing node height, margins, header size etc. Don't each of these result in calls to UpdateScrollbars ? It would save multiple calls to UpdateScrollbars for once (see SetDefaultNodeHeight).

I typically use Begin/EndUpdate only for code that has a complexity of O(n), not to surround code with a complexity of O(1). You are right about UpdateScrollbars(), it is called 3 times in this case, but as far as I know it does not run over all nodes. Under this premise, I don't expect any significant effect.

Please excuse that I am a bit reluctant to do changes here, but it took several iterations to get to the current code. I see a chance of breaking things here again, without getting a good value in return.

from virtual-treeview.

pyscripter avatar pyscripter commented on June 2, 2024

Are you sure, even if M<>D?

In recent versions when IsDpiChange=False then M=D. See TControl.ChangeScale, TCustomForm.ScaleForCurrentDPI (comment at the end) and TWinControl.ScaleControls. Note that TCustomForm.ChangeScale is not used at all (see https://quality.embarcadero.com/browse/RSP-41988). You can confirm using the debugger.

It would certainly occur in versions earlier than Berlin, before IsDPIChange was introduced. It would also occur if one explicitly calls TWinControl.ScaleBy. But Vcl does not use that method.

In any case I have removed the IsDPIChange check in the code above.

This should already be the case.

I think not if the change happens via AutoSize.

I typically use Begin/EndUpdate only for code that has a complexity of O(n), not to surround code with a complexity of O(1).

O(1) stuff also take time. There is no significant performance penalty in using Begin/EndUpdate (or am I getting this wrong?) . Delphi is already very slow when you move a form to a different monitor. The main form of PyScripter contains about 5 VTs. Saving 10 calls to UpdateScrollbars when you switch monitors would not harm. But it is not just UpdateScrollbars. For example TBaseVirtualTree.SetDefaultNodeHeight contains:

    if (FUpdateCount = 0) and HandleAllocated and not (csLoading in ComponentState) then
    begin
      ValidateCache;
      UpdateScrollBars(True);
      ScrollIntoView(FFocusedNode, toCenterScrollIntoView in FOptions.SelectionOptions, True);
      Invalidate;
    end;

You are saving calls to ValidateCache, which is slow. Note that AutoSize may change the default node size for a second time.

from virtual-treeview.

pyscripter avatar pyscripter commented on June 2, 2024

Please excuse that I am a bit reluctant to do changes here,

I fully understand! I also manage open source projects and I know very well what you mean.

from virtual-treeview.

pyscripter avatar pyscripter commented on June 2, 2024

Let me know if you see any difference in your app

I did some measurements and you save a few milliseconds. However the time it takes to move my form between monitors varies between 400-900 milliseconds!, so the difference unfortunately is imperceptible (as I was expecting - see https://en.delphipraxis.net/topic/4768-vcl-handling-of-dpi-changes-poor-performance).

By the way, I noticed:

procedure TVTHeader.FontChanged(Sender : TObject);
begin
  inherited;
  {$IF CompilerVersion < 31}
  AutoScale(false);
  {$IFEND}
end;

Is the conditional compilation intentional?

from virtual-treeview.

joachimmarder avatar joachimmarder commented on June 2, 2024

Is the conditional compilation intentional?

Well, I guess it was intentional, that does not mean I can explain it right now. ;-)

from virtual-treeview.

pyscripter avatar pyscripter commented on June 2, 2024

Good to see we converged a bit. Some of the remaining differences:

ScalingFlags

There are about 50 overrides of ChangeScale in the Vcl. None of these checks the ScalingFlags. The only units that use the ScalingFlags are Forms and Controls. The flag sfHeight is used to decide whether to scale the overall height of the control or the ClientHeigth. If M<>D the header and the rest of the stuff needs to be scaled. And the way it is now, it only affects the control during loading,

ScaleForPPI

See

procedure TControl.ScaleForPPI(NewPPI: Integer);
begin
  if not FIScaling and (NewPPI > 0) then
  begin
    FIScaling := True;
    try
      if FCurrentPPI = 0 then
        FCurrentPPI := GetDesignDpi;

      if NewPPI <> FCurrentPPI then
        ChangeScale(NewPPI, FCurrentPPI, True);
    finally
      FIScaling := False;
    end;
  end;
end;

As you can see it either does nothing (if FIScaling is True) or calls ChangeScale (in this case recursively). Not a good idea in either case. Fortunately it does nothing.

I think the idea was to set the FCurrentPPI early. Instead move the inherited call at the top to set the Font and the FCurrentPPI right. Initially, the inherited call was at the end, and was moved up a bit.

from virtual-treeview.

pyscripter avatar pyscripter commented on June 2, 2024

Out of curiosity, I was looking into the role of scaling flags, and why the current code works. sfHeight is set by TControl.SetHeight and since Height is a stored property, it is always read from the dfm. So sfHeight would always be set at loading time.

But the question remained for what kind of controls sfHeight might not be set. The answer relates to embedded sub-controls.
This is an example:

constructor TSpTBXCustomTabControl.Create(AOwner: TComponent);
begin
  inherited;
  FPages := TList.Create;
  FEmptyTabSheet := TSpTBXTabSheet.Create(Self);
  FEmptyTabSheet.Parent := Self;

The tabsheet is created and the TabControl is set as Parent without setting Left, Top, Width or Height (alClient alignment). So when ChangeScale is called on the TabSheet the scaling flags will be empty and the Left, Top, Width and Height will not be scaled.

In any case the scaling of the VT header should not be related to the scaling flags.

from virtual-treeview.

joachimmarder avatar joachimmarder commented on June 2, 2024

The way how sfHeight was obviously wrong, thanks for pointing this out. I guess this code origins from the early days of multi monitor scaling. I removed the check for sfHeight and merged this change to V7_stable.

Are there any other changes in your code that you think are important or have a true positive impact? Especially moving up the inherited call?

from virtual-treeview.

pyscripter avatar pyscripter commented on June 2, 2024

Are there any other changes in your code that you think are important or have a true positive impact? Especially moving up the inherited call?

Remaining issues:

a) Scaling Flags:

The following code is still in ChangeScale:

        // It is important to evaluate the TScalingFlags before calling inherited, becuase they are differetn afterwards!
        if csLoading in ComponentState then
          Flags := ScalingFlags
        else
          Flags := DefaultScalingFlags; // Important for #677

This code was important when you were checking for scaling flags. With the check of scaling flags gone, this code is not needed. The local variable Flags is not used anywhere below!

b) ScaleForPPI

Please see the arguments for removing above. ChangeScale is called by ScaleforPPI. Calling it from ChangeScale makes no sense.

c) Remove IsPPIChange from the header ChangeScale parameters

Although less important, it would improve clarity and simplicity. When this routine is called it will scale the header. Always. And you only call it when M <> D.

d) Position of the inherited call

It will work as long as it is before the PrepareBitmaps and AutoScale calls, but also after the scaling of the DefaultNodeHeight. This last thing originally escaped me. The inherited call will change the font size if ParentFont is False. This will result in a call to AutoScale, which may change DefaultNodeSize if say the font size has increased. If the scaling of the DefaultNodeSize was to follow, it would result in double scaling of DefaultNodeSize. Please note also that when ParentFont is True (the common case), then the font change will occur after ChangeScale exits and would work correctly independently of the position of the inherited call.

To sum up the inherited call is OK where it is now!. Alternatively see the updated ChangeScale function at the top.

e) Linking scaling to toAutoChangeScale

I think we agreed that toAutoChangeScale should only affect the AutoScale node height optimization and would not be linked to DPI scaling.

f) Potential node height inconsistency if you change default node height with AutoScale

This is not directly related to ChangeScale. It could happen if

  • toVariableNodeHeight is not set
  • you have some nodes
  • ChangeScale is called which call AutoScale
  • AutoScale changes the DefaultNodeSize
  • add some new nodes.

It could also happen if:

  • toVariableNodeHeight is not set
  • you have some nodes
  • change the font
  • AutoScale changes the DefaultNodeSize
  • add some new nodes

I would address these issues by changing the node height of all existing nodes when the default node size changes, if toVariableNodeHeight is not set.

g) Calling AutoScale from ChangeScale

AutoScale is called when the font changes (but not when loading - see CMFontChanged method). So when ParentFont is False, ChangeScale will change the font and AutoSize will be called indirectly. When ParentFont is True, then AutoScale will be called after a DPI change when the parent font is scaled. This will occur after ChangeScale exits.
I am not sure what the point is in calling AutoSize from ChangeScale (when M=D as it was recently changed). When M=D the font will not be changed and there is no need to call AutoSize. It is worth considering removing that call. Also, It may be worth calling AutoSize from the Loaded method, to ensure it is called at least once, whether the application is DPI aware or not.

from virtual-treeview.

joachimmarder avatar joachimmarder commented on June 2, 2024
f) Potential node height inconsistency if you change default node height with AutoScale

This is not directly related to ChangeScale. It could happen if
- 
-     toVariableNodeHeight is not set
-     you have some nodes
-     ChangeScale is called which call AutoScale
-     AutoScale changes the DefaultNodeSize
-     add some new nodes.

After applying changes to f), this cannot happen any more.

DefaultNodeSize is the default size of new nodes, and I don't think it is a good idea if the setter changes existing nodes. But I see your point and think AutoScale() should, just like ChangeScale() already does, scale the existing nodes. It should do the same with the header height.

from virtual-treeview.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.