Jump to content
recursiveElk

Layout sizing of components issue

Recommended Posts

Hi, I've run into a problem where if i compile and run the program through the SMS IDE and "Open in Browser" it displays correctly. But when i run it locally from the generated 'www' folder index.html, it displays incorrectly.

Here is what it should look like ( and does in chrome eg http://111.111.1.11:1111/index.html):

https://puu.sh/wwUx3/54a855dc53.png

 

Here is what it looks like when run in chrome from the www/index.html
( eg file:///C:/#####//www/index.html) :

https://puu.sh/wwUAG/7ccc07ff32.png

OR
https://puu.sh/wwUTA/5a2a9a4b88.png

 

 

Source Code(.Rar):
https://puu.sh/wwUH5/ced5d37382.rar

Oh also to note i have already tried putting the FLayout1 in InitializeForm.

 

Edited by recursiveElk

Share this post


Link to post
Share on other sites

Resize is called in both cases after InitializeForm, It might not compile because of RTL version differences. 

 

It seems to be that the dimensions are set as the same values, but when its not server side the component doesn't adjust to area as intended.

 

Almost like when you increase the size of a label but the change isn't apparent visually until you fill the space with text, if that makes sense.

 

Here are links to the actual .pas/.css files.

Form1.pas : https://puu.sh/wxxHc/5daad68c7d.pas

Unit1.pas : https://puu.sh/wxxL7/f96f45d14c.pas

CSS : https://puu.sh/wxxJR/892892c720.css
 

Thanks!
 

Share this post


Link to post
Share on other sites

Are you using a new (Alpha/Beta) RTL?

 

I'm still on old RTL and in both cases they look identical, however it's different than in your case.

 

https://www.sendspace.com/file/31zcjg

https://postimg.org/image/wb8pv7txz/

 

I would suggest to try to manually do a layout, TLayout inside SMS (at least in old RTL) is untrusty, in most cases I ended up controlling things myself.

 

 

Share this post


Link to post
Share on other sites

Yes we are using an Alpha and it could be causing differences. There are definitely inconsistencies as i've managed to get it working off index.html when hosted on our server, but its still not working for a coworker. Refreshing App Manifest doesn't seem to change anything either.

What is very interesting is it seems to work for you vertically, but not horizontally, and in the version i gave you i've set the rowHeight manually but the ColWidth as scaling. Maybe the code to calculate the panel.width isn't firing correctly. 

Thanks for the second opinion!

Edit1: After further testing this seems to be a red herring, when both are set to absolute values i still see the discrepancy. 

 

local index.html : Top | "Open In Browser" server : Bottom

https://puu.sh/wxBtw/48280fa8fd.png
 

The Box titled 'testlast' isn't even part of the TLayout, just being resized absolute values ( 200 w, 100 h). This leads me to believe its a problem with how the checkboxes/styling is being resized/set and not TLayout.

Share this post


Link to post
Share on other sites

If you haven't considered try frow, it's great for a laying out controls where you want it and how you want it depending on screen size etc.

Nico sent some examples earlier with it and he wrote components/controls that might be of interest to you as well.

Share this post


Link to post
Share on other sites

There are quite a few things that must be understood about the way HTML works vs how delphi works.

First of all, when you create a control -- it is never ready when the constructor finishes. Javascript is a single threaded event driven beast - meaning that it can take several milliseconds after the constructor is done -- before you can safely do anything with controls.
This is why its so important to create controls in one place, adjust them in another and position them in yet another procedure.
Some things you can get away with, but ultimately it will become a mess if you just create things all over the place. Its not how the DOM works and will only lead to frustration.

 

Secondly, composite controls like TW3Checkbox requires a bit more time to ready. It will set it's csReady flag in ComponentState ONLY when all its children report that they have finished creating too. If this takes too long it will exit the wait loop to not slow down the system. But just like you would check csCreating, csDestroying etc. in Delphi -- so you must do here, but on pre-defined junctions in the creation process.

This is why the old RTL had a Handle.readyExecute() mechanism, which would call you when the element was ready to be used.

First thing i would do would be this:
 

TMyCheckbox = class(TW3Checkbox)
protected
 procedure ObjectReady; override;
end;

And set the styles there, rather than the way you are doing it now. Also remember that a control will always map its style to a css-style with the same name. So TMyCheckbox will automatically map to ".TMyCheckBox {}" in the stylesheet. And then you can do the same for the child elements ".TMyCheckBox > .TCheckMark" to set the style for the sub elements.

Then there is the layout -- you are remaking the layout for every single call to resize (!)
A layout should only be created once -- in either the InitializeForm or ObjectReady() method, and then simply updated in the resize method.. it will take care of itself.

You should also check if the instance is actually created in the resize() method before setting values, because ASYNC programming is nothing like synchronized programming.

And once in a while, a call to ReSize() is needed manually here and there.
 

TW3Dispatch.Execute(Resize, 50); // call resize in 50ms just to be sure

The whole point of making a new RTL was to create a system more in sync with how the browser and node.js works. But that means we have to follow the rules.

As for differences between server and design -- im guessing you have compiled with a cache manifest, so the server will always keep giving you the same css (from its cache) regardless. Hence the changes never occur until you either delete the manifest file or replace it :)

As to using a alpha rtl that was shared only between 3-4 people -- it shouldnt even be available.
And its now months behind the present evolution.

But if you look at the new methods and remember that everything in javascript is async, you can make some knockout stuff with this.
But the components is the last thing i update, because the real work is in the core of the rtl.
So in the Alpha things like checkbox will be utterly useless until i go over them.

Anyways, here is a preferences form from the Smart desktop using the layout control. Its fairly straight forward:

 

unit wb.desktop.window.prefs;

interface

uses
  W3C.DOM,
  System.Types,
  System.Types.Convert,
  System.Types.Graphics,
  System.Colors,
  System.Time,

  System.Streams,
  System.Reader, System.Stream.Reader,
  System.Writer, System.Stream.Writer,

  System.Structure,
  System.Structure.Json,

  System.Memory,
  System.Memory.Allocation,
  System.Memory.Buffer,

  System.Widget,

  wb.desktop.types,
  wb.desktop.window,
  wb.desktop.preferences,

  SmartCL.Layout,

  SmartCL.System, SmartCL.Storage, SmartCL.Storage.Cookie,
  SmartCL.Storage.Local, SmartCL.Storage.Session,

  SmartCL.Time, SmartCL.Controls.Elements,
  SmartCL.Graphics, SmartCL.Components,
  SmartCL.MouseTouch, SmartCL.Effects, SmartCL.Fonts, SmartCL.Borders,
  SmartCL.CSS.Classes, SmartCL.CSS.StyleSheet,

  SmartCL.Controls.Image, SmartCL.Controls.Label,
  SmartCL.Controls.Panel, SmartCL.Controls.Button,
  SMartCL.Controls.ToggleSwitch,
  SMartCL.Controls.CheckBox,
  SmartCL.Controls.Toolbar
  ;

type

  TWbPreferencesWindow = class(TWbWindow)
  private
    FEffectsOpen: TW3ToggleSwitch;
    FEffectsOpenLabel: TW3Label;

    FEffectsClose: TW3ToggleSwitch;
    FEffectsCloseLabel: TW3Label;

    FEffectsMaximize: TW3ToggleSwitch;
    FEffectsMaximizeLabel: TW3Label;

    FEffectsMinimize: TW3ToggleSwitch;
    FEffectsMinimizeLabel: TW3Label;

    FShowDocking: TW3CheckBox;

    FLayout:  TLayout;
    FButtonLayout: TLayout;

    FCancel:  TW3Button;
    FApply:   TW3Button;
    FSave:    Tw3Button;

    FPanel:   TW3Panel;
    FFooter:  TW3Panel;
  protected
    procedure HandleApply(Sender: TObject);
    procedure HandleSave(Sender: TObject);
    procedure HandleCancel(Sender: TObject);
    procedure StoreCurrentValues;
  protected
    procedure InitializeObject; override;
    procedure FinalizeObject; override;
    procedure ObjectReady; override;
    procedure Resize; override;
  end;


implementation

uses SmartCL.FileUtils;

//#############################################################################
// TWbPreferencesWindow
//#############################################################################

{$DEFINE HOOKTEST}

procedure TWbPreferencesWindow.InitializeObject;
var
  LAccess:  IWbDesktop;
  LReader:  IW3StructureReadAccess;
begin
  inherited;
  Header.Title.Caption := 'Preferences';

  FEffectsOpenLabel := TW3Label.Create(Content);
  FEffectsOpenLabel.Name :='lbEffectsOpen';
  FEffectsOpenLabel.SetBounds(10,10, 200 ,32);
  FEffectsOpenLabel.Caption := "Effect Window Open";
  FEffectsOpenLabel.AlignText := TTextAlign.taRight;

  FEffectsOpen := TW3ToggleSwitch.Create(Content);
  FEffectsOpen.Name :='btnEffectsOpen';
  FEffectsOpen.SetBounds(220,10,120,32);

  // ---------------------

  FEffectsCloseLabel := TW3Label.Create(Content);
  FEffectsOpenLabel.Name :='lbEffectsClose';
  FEffectsCloseLabel.SetBounds(10,52, 200 ,32);
  FEffectsCloseLabel.Caption := "Effect Window Close";
  FEffectsCloseLabel.AlignText := TTextAlign.taRight;

  FEffectsClose := TW3ToggleSwitch.Create(Content);
  FEffectsClose.Name :='btnEffectsClose';
  FEffectsClose.SetBounds(220,52,120,32);

  // ---------------------

  FEffectsMaximizeLabel := TW3Label.Create(Content);
  FEffectsMaximizeLabel.Name :='lbEffectsMaximize';
  FEffectsMaximizeLabel.SetBounds(10,94, 200 ,32);
  FEffectsMaximizeLabel.Caption := "Effect Window Maximize";
  FEffectsMaximizeLabel.AlignText := TTextAlign.taRight;

  FEffectsMaximize := TW3ToggleSwitch.Create(Content);
  FEffectsMaximize.Name :='btnEffectsMaximize';
  FEffectsMaximize.SetBounds(220,94,120,32);

  // ---------------------

  FEffectsMinimizeLabel := TW3Label.Create(Content);
  FEffectsMinimizeLabel.Name :='lbEffectsMinimize';
  FEffectsMinimizeLabel.SetBounds(10,136, 200 ,32);
  FEffectsMinimizeLabel.Caption := "Effect Window Minimize";
  FEffectsMinimizeLabel.AlignText := TTextAlign.taRight;

  FEffectsMinimize := TW3ToggleSwitch.Create(Content);
  FEffectsMaximize.Name :='btnEffectsMinimize';
  FEffectsMinimize.SetBounds(220,136,120,32);

  // ---------------------

  LAccess := GetDesktop() as IWbDesktop;
  LReader := LAccess.GetPreferences.GetPreferencesReader();

  FPanel := TW3Panel.Create(Content);
  FPanel.Name :='PnlPrefs';

  FShowDocking := TW3CheckBox.Create(FPanel);
  FShowDocking.Font.Size := 14;
  FShowDocking.Caption := 'Show workbench dock';
  FShowDocking.SetBounds(10,10, 300,16);

  FFooter := TW3Panel.Create(Content);
  FPanel.Name :='PnlPrefsFooter';
  FFooter.StyleClass := '';
  FFooter.Height:=32;

  FCancel := TW3Button.Create(FFooter);
  FCancel.Name :='btnCancel';
  FCancel.Caption := 'Cancel';
  FCancel.OnClick := procedure (Sender: TObject)
  begin
    CloseWindow();
  end;

  FApply := TW3Button.Create(FFooter);
  FApply.Name :='btnApply';
  FApply.Caption :='Apply';
  FApply.OnClick := @HandleApply;

  FSave :=Tw3Button.Create(FFooter);
  FSave.Name :='btnSave';
  FSave.Caption := 'Save';
  FSave.OnClick := @HandleSave;

  var LOwner: TW3CustomControl := TW3CustomControl(Parent);
  var wd := 400;
  var hd := 500;
  var dx :=(LOwner.width div 2) - (wd div 2);
  var dy :=(LOwner.height div 2) - (hd div 2);
  SetBounds(dx, dy, wd, hd);

end;

procedure TWbPreferencesWindow.FinalizeObject;
begin
  FEffectsOpen.free;
  FEffectsOpenLabel.free;

  FEffectsClose.free;
  FEffectsCloseLabel.free;

  FEffectsMaximize.free;
  FEffectsMaximizeLabel.free;

  FEffectsMinimize.free;
  FEffectsMinimizeLabel.free;

  inherited;
end;

procedure TWbPreferencesWindow.ObjectReady;
var
  LAccess:  IWbDesktop;
  LReader:  IW3StructureReadAccess;
begin
  inherited;

  FButtonLayout := Layout.Client(Layout.Margins(2).Spacing(10),
    [
      Layout.left(FSave),
      Layout.client(FApply),
      Layout.Right(FCancel)
    ]);

  FLayout := Layout.Client(Layout.Margins(4).Spacing(4),
    [ Layout.top(
      [
        Layout.top(Layout.Stretch.Spacing(8).Margins(2),
        [
        Layout.client(FEffectsOpenLabel),
        Layout.right(FEffectsOpen)
        ]),

        Layout.top(Layout.Stretch.Spacing(8).Margins(2),
        [
        Layout.client(FEffectsCloseLabel),
        Layout.right(FEffectsClose)
        ]),

        Layout.top(Layout.Stretch.Spacing(8).Margins(2),
        [
        Layout.client(FEffectsMinimizeLabel),
        Layout.right(FEffectsMinimize)
        ]),

        Layout.top(Layout.Stretch.Spacing(8).Margins(2),
        [
        Layout.client(FEffectsMaximizeLabel),
        Layout.right(FEffectsMaximize)
        ])
      ]),
      Layout.Client(FPanel),
      Layout.Bottom(Layout.Height(36), FFooter)
      ]
    );

  LAccess := GetDesktop() as IWbDesktop;
  LReader := LAccess.GetPreferences.GetPreferencesReader();

  FEffectsOpen.Checked := LReader.ReadBool(PREFS_WINDOW_EFFECTS_OPEN);
  FEffectsClose.Checked := LReader.ReadBool(PREFS_WINDOW_EFFECTS_CLOSE);
  FEffectsMinimize.Checked := LReader.ReadBool(PREFS_WINDOW_EFFECTS_MIN);
  FEffectsMaximize.Checked := LReader.ReadBool(PREFS_WINDOW_EFFECTS_MAX);

  TW3Dispatch.Execute(Invalidate, 200);
end;

procedure TWbPreferencesWindow.StoreCurrentValues;
var
  LAccess:  IWbDesktop;
  LWriter:  IW3StructureWriteAccess;
begin
  LAccess := GetDesktop() as IWbDesktop;
  LWriter := LAccess.GetPreferences.GetPreferencesWriter();

  // Apply to preferences
  LWriter.WriteBool(PREFS_WINDOW_EFFECTS_OPEN, FEffectsOpen.Checked);
  LWriter.WriteBool(PREFS_WINDOW_EFFECTS_CLOSE, FEffectsClose.Checked);
  LWriter.WriteBool(PREFS_WINDOW_EFFECTS_MIN, FEffectsMinimize.Checked);
  LWriter.WriteBool(PREFS_WINDOW_EFFECTS_MAX, FEffectsMaximize.Checked);
end;

procedure TWbPreferencesWindow.HandleApply(Sender: TObject);
begin
  try
    // Apply to preferences
    StoreCurrentValues();
  finally
    // Exit, dont save prefs permanently (!)
    CloseWindow();
  end;
end;

procedure TWbPreferencesWindow.HandleSave(Sender: TObject);
var
  LAccess:  IWbDesktop;
begin
  LAccess := GetDesktop() as IWbDesktop;

  try

    // Apply to preferences
    StoreCurrentValues();

    // Store values permanently
    try
      LAccess.SavePreferences();
    except
      on e: exception do
      showmessage(e.message);
    end;

  finally
    CloseWindow();
  end;
end;

procedure TWbPreferencesWindow.HandleCancel(Sender: TObject);
begin
end;

procedure TWbPreferencesWindow.Resize;
begin
  inherited;

  if FLayout <> nil then
  begin
    try
      FLayout.Resize(Content);
    except
      on e: exception do;
    end;
  end;

  if FButtonLayout<>nil then
  begin
    try
      FButtonLayout.Resize(FFooter);
    except
      on e: exception do;
    end;
  end;

  {$IFNDEF HOOKTEST}
  if assigned(FEditor) then
    FEditor.SetBounds(10,64, Content.Clientwidth - 20, Content.ClientHeight - 80);
  {$ENDIF}
end;

end.

Share this post


Link to post
Share on other sites

The biggest challenge for me, is that CSS makes it almost impossible to create code that looks the same everywhere.
A simple "margin: 2px" is enough to break the design between one stylesheet and another - and everyone thinks its the codebase fault.
The codebase works fine, but some composite controls have different default css values depending on the browser.

And if the rules are broken, like assigning values to a control still being created in the background -- results will be sporadic.
 

Under linux the standard checkmark has a padding of 2px by default in the Origyn browser, and the other browsers have varying degree of accuracy. Its the same for Windows -- even between versions of the same bloody browser.

There is also two stylesheets: the one you provide, and the one the browser calculates as a reasonable faximilee of the result.
This is why we write to handle.style["somevalue"] but read from the calculated stylesheet.
So its pretty complex stuff.

But as long as some rules are followed, like inheriting out controls you need to closely manage, always override the objectReady() method when doing safe changes, and making full use of callbacks -- then there is rarely any problem.

 

I do 90% of my code by hand (old school, never really bothered with designers) -- and as long as you follow the rules of JS and HTML, it works fine.

Check this out for instance. And naturally I had to write the css for each element i wanted to use.
Its like writing a make-file for C/C+++.

http://quartexhq.myasustor.com/

We could make things more uniform by generating the CSS using classes. A bit like what Delphi does -- but it would be very time consuming with little gains. But when the creation-chain is understood, then writing complex application is easy and fun.


smartdesk.png

Share this post


Link to post
Share on other sites

Firstly Thank you for the huge wealth of knowledge and info, really helpful.

A few points:

 

- Just to be clear I wasn't trying to say it was the code base's fault and apologise if it came off that way. Just trying to figure out how to fix problems I've largely brought onto myself.

- The reason i had TLayout in .Resize instead of InitializeForm was because i needed to adjust values in the Layout on form dimension change, i will now instead define the layout in InitializeForm and instead change just the objects sizes manually in Resize.

- Am i interpreting it correctly as you saying i should define all my styles in a Pascal-style through "procedure ObjectReady; override;" rather than in the custom style sheet?

- Lastly it's really helpful to get this "best practices" info as to better set up an application as without the experience it can be difficult to picture what's going on under the hood. I suspected it was a timing/async cause as you have now confirmed, but had no way of solving it myself. Learning day by day! :)

Thanks for the help, this style of programming is rather new to me as well as the language, but the more i learn about it the more exciting it becomes!

Share this post


Link to post
Share on other sites

No i didnt mean that you somehow blamed anything - just tend to be people that think it has to be a mistake. When it in fact is how JS works rather than how the pascal implementation is done on top of js.

 

>>- Am i interpreting it correctly as you saying i should define all my styles in a Pascal-style through "procedure ObjectReady; override;" rather than in the custom style sheet?

 

ObjectReady() is what the name says, a method that fires when the element is successfully created and injected into the dom.

Personally im a fan of having matching css-styles and pascal classes, but if you must adjust something "safely" (not just styles) then this is a nice place to do just that.
 

InitializeObject() is the constructor in SMS, so depending on what you do - that can also be a good, just keep in mind that the prototype may still be building when this fires.

But! We also have a procedure just for CSS stuff, namely StyleTagObject(). This was made exactly for what you want.

 

It is defined as such:

 

    (* This gives you the ability to set basic styles straight after
       the element has been created *)
    procedure StyleTagObject; virtual;

So if we are going by the book - that is the procedure you want to override when messing with styles manually.

 

But try to inherit out the things you need to change, give it a unique name and then write a css style for it. The style and the class will find each other automatically so you dont have to think about it.

And yes, I wish we had proper docs for this. It sort of takes for granted that you know a bit JS, a bit css and a bit html -- and then a lot of object pascal.
But if you look at TW3CustomControl there is a lot of commenting in the code - an you can follow how an element is created, various mechanisms and events are hooked up etc. by reading the code there.

Also about resize. Only IE has an actual onResize() event.. none of the other browser have that. So we figure out how to do resize through Width/Height changes. If you do a lot of them, call Obj.BeginUpdate; first -- then Obj.EndUpdate when you are done -- that way you just get one resize rather than 100 of them..

    (* Scheduled update mechanism:
      Before performing changes to multiple styles or properties, wrap
      them in BeginUpdate() and EndUpdate() calls. You combine this
      with calls to AddToComponentState() and RemoveFromComponentState()
      to inform the control what has changed.


      Note: Internally, you can have multiple nested BeginUpdate/Endupdate.
            It makes no difference since the control uses a counter to keep track
            of simulanious updates


      Example:
        BeginUpdate;
        try
          MoveTo(10,10);
          SetSize(100,100);
          Background.fromColor(clRed);
        finally
          AddToComponentState([csMoved,csSized]);
          EndUpdate;
        end; *)
    procedure BeginUpdate;    procedure EndUpdate; virtual;

Well, hope it helps! When im finally done with the RTL i will be focusing on docs, fixing all the examples to run under the new RTL and naturally -- make sure the CSS and visual controls all follow the rules.

 

Share this post


Link to post
Share on other sites

Brilliant thanks this is incredibly useful!

 

Likely what was messing with the display of the boxes could even come down to the transition delay in the css i was using.

 

Will try out restructuring my test project to get it running as intended with best practices.

Share this post


Link to post
Share on other sites

After some further testing i've come up with a few issues, hopefully easily solved!

  • I've isolated the css issue to the fact that the way im setting css for component TW3CheckBox does not generate in time as suspected. All the css works with other components perfectly / consistently.

    I tried to set the style in an overridden ObjectReady class and also StyleTagObject but i cant seem to make either work. It would be great if someone could show me a quick and dirty example of how to set style for a composite class like TW3CheckBox, or how to wait until it's ready because that does not seem to work for me right now. 

 

  • The nature of the application i'm writing requires needs scaling by width, how can i achieve this using Tlayout? Initially i had it placed in Resize so i could change the scaling/margins based on the most recent width and height of the client. Is there a way to define TLayout once with variables, and then edit those values later?

 

  • When i use BeginUpdate and EndUpdate around my resize code it resizes infinitely, i assume i'm using this wrong, is it not appropriate to use for the main form?

Cheers,

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×