Smart Mobile Studio
  • News
  • Forums
  • Download
  • Store
  • Showcases
    • Featured demos
    • The Smart Contest 2013, Round 1 – Graphics
  • Documentation
    • Get the book
    • System requirements
    • Prerequisites
    • Getting started
      • Introduction
      • Application architecture
      • The application object
      • Forms and navigation
      • Message dialogs
      • Themes and styles
    • Project types
      • Visual project
      • Game project
      • Console project
    • Layout manager
    • Networking
      • TW3HttpRequest
      • TW3JSONP
      • Loading files
  • About

Creating a list menu

Posted on 13.07.2012 by Jon Lennart Posted in Developers log 4 Comments

Smart Mobile currently ships with a rather spartan number of custom controls. Originally we were aiming at a rather minimal RTL and only the basic iOS widgets, this is how C# does it when it comes to iOS development. While we have the rounded iphone menu in the IDE, what is missing is a more versatile menu. Both Android and iOS devices have scrollable list menus that are flat and designed to cover the iphone’s entire horizontal width. You can then move around it with one finger, and when you touch a single item without scrolling – that is considered a “click”.

In this little article I’m going to create this list control to demonstrate how easy it is.

Roll your own

First, let’s start with the baseclasses. We are going to keep it very simple, reducing the list to only two classes: one representing a list item – and another representing the actual list.

Since iPhone lists have the capacity to include sub-controls, we are not going to cheat and wrap UL / LI tags, but use fully functional TW3CustomControls as list items. This will give us the freedom to not only style our list items – but to add buttons and whatever we want to each element.

So let’s start by defining a couple of classes:

  type

  (* Generic ancestor *)
  TLSTCustomMenuItem = Class(TW3CustomControl);

  (* Array type *)
  TLSTMenuItems = Array of TLSTCustomMenuItem;

  (* Menu item *)
  TLSTMenuItem = Class(TLSTCustomMenuItem)
  private
    FCaption: String;
    procedure setCaption(Const Value:String);
  protected
    procedure InitializeObject; override;
  public
    property  Caption:String read FCaption write setCaption;
  end;

  (* selection event *)
  TMenuItemSelectedEvent = procedure (Sender:TObject;Const aItem:TLSTCustomMenuItem);

  (* Our menu *)
  TLSTMenu = Class(TW3ScrollControl)
  private
    FItems:       TLSTMenuItems;
    FLastY:       Integer;
    FSelected:    TLSTMenuItem;
    FOnSelected:  TMenuItemSelectedEvent;
    procedure     HandleTouchBegins(Sender:TObject;Info:TW3TouchData);
    procedure     HandleTouchEnds(Sender:TObject;Info:TW3TouchData);
  protected
    procedure     HandleItemTapped(Const aItem:TLSTMenuItem);
    procedure     InitializeObject; override;
    procedure     FinalizeObject; override;
    procedure     ReSize;override;
  public
    property      OnItemSelected:TMenuItemSelectedEvent read FOnSelected write FOnSelected;
    property      Items:TLSTMenuItems read FItems;
    function      Add(Const aItem:TLSTCustomMenuItem):TLSTCustomMenuItem;overload;
    function      Add:TLSTMenuItem;overload;
  end;

The FItems array is basically a list of the child items. This is actually a bit overkill, since TW3Component already support parent/child management – but it’s Friday so I just used an array to keep track of things.

Ok, let’s move on a bit and look at the implementation for our item:

  //###########################################################################
  // TLSTMenuItem
  //###########################################################################

  procedure TLSTMenuItem.InitializeObject;
  begin
    inherited;
    Self.Color:=clWhite;
    Self.Height:=32;
  end;

  procedure TLSTMenuItem.setCaption(Const Value:String);
  begin
    FCaption:=Value;
    innerHTML:=Value;
  end;

This is a fairly simple setup. We give our item a default color of white, and set the height of the listitem to 22 pixels. The setcaption() is actually one you should look out for. As you can see i use the innerHTML property to set the caption. This is fine for this example – but it will kill all child controls (!). For a proper title, create a child TW3Label and position it by overriding the resize() method.

Ok, let’s look at the main scrolling control. The actual scrolling behavior is inherited from TW3ScrollControl which is a minimalistic, touch based scrollbox. Unlike the Delphi scrollbox – the smart version contains a sub-control called “content” which is what is actually moved.

This means that when we populate our listbox, we have to create our list-items with “content” as parent. Ok, let’s have a look:

  //###########################################################################
  // TLSTMenu
  //###########################################################################

  procedure TLSTMenu.InitializeObject;
  begin
    inherited;
    FItems.SetLength(0);
  end;

  procedure TLSTMenu.FinalizeObject;
  begin
    FItems.Clear;
    inherited;
  end;

  procedure TLSTMenu.HandleTouchBegins(Sender:TObject;Info:TW3TouchData);
  begin
    (* remember current scroll position *)
    if (sender<>NIL)
    and (sender is TLSTMenuItem) then
    begin
      FLastY:=Content.Top;
      FSelected:=TLSTMenuItem(sender);
    end else
    begin
      FLastY:=-1;
      FSelected:=NIL;
    end;
  end;

  procedure TLSTMenu.HandleTouchEnds(Sender:TObject;Info:TW3TouchData);
  begin
    (* No scrolling but touched? Its a tap *)
    if Content.Top=FLastY then
    begin
      if (FSelected<>NIL) then
      begin
        if (FSelected=sender) then
        begin
          HandleItemTapped(FSelected);
          FLastY:=-1;
          FSelected:=NIL;
        end;
      end;
    end;
  end;

  procedure TLSTMenu.HandleItemTapped(Const aItem:TLSTMenuItem);
  begin
    if assigned(FOnSelected) then
    FOnSelected(self,aItem);
  end;

  function TLSTMenu.Add:TLSTMenuItem;
  begin
    result:=TLSTMenuItem(Add(TLSTMenuItem.Create(Content)));
    result.OnTouchBegin:=HandleTouchBegins;
    result.OnTouchEnd:=HandleTouchEnds;
  end;

  function TLSTMenu.Add(Const aItem:TLSTCustomMenuItem):TLSTCustomMenuItem;
  begin
    if aItem<>NIL then
    begin
      if FItems.IndexOf(aItem)<0 then
      begin
        BeginUpdate;
        FItems.Add(aItem);
        EndUpdate;
        content.LayoutChildren;
      end;
    end;
    result:=aItem;
  end;

  procedure TLSTMenu.ReSize;
  var
    mItem:  TLSTCustomMenuItem;
    mSize:  Integer;
    x:  Integer;
    dy: Integer;
  begin
    inherited;
    for x:=0 to FItems.Length-1 do
    begin
      mItem:=FItems[x];
      if mItem.visible then
      inc(mSize,mItem.height);
    end;
    Content.Height:=mSize;

    dy:=0;
    for x:=0 to FItems.Length-1 do
    begin
      mItem:=FItems[x];
      if mItem.visible then
      begin
        mItem.SetBounds(0,dy,width,mItem.Height);
        inc(dy,mItem.Height);
      end;
    end;
  end;

Everything here should be fairly straight forward. We hook the onTouchBegins and onTouchEnd events on all our list items. Why? Because we have to solve a little problem. Since our list is scrollable, that means we have to somehow distinguish between a scroll swipe (up or down) and an actual tap. To achieve this we set down a simple law: If the Y position of the list is in both events, then we consider that a tap (or selection). If you have moved your finger on the other hand, then the tap is ignored.

The final procedure, namely resize, does two things: first, it calculates the total height of all the list items and size the content control accordingly. Then it loops through and positions the child elements.

Styling

Ok, with the behavior out of the way – we just need to add one final piece: namely styling. So double-click on the project CSS file and add the following:

.TLSTMenuItem {
  -webkit-user-select: auto;
  background: #FFFFFF;
  border-bottom: solid 1px #9A9A9A;

    font-family: "Helvetica Neue", Helvetica, sans-serif;
    font-size: 17px;
    color: #888888;

}

Let’s give the control a test-drive, so we add the unit to our mainform’s uses clause, create an instance, and add some items:

unit Form1;

interface

uses
  w3system, w3graphics, w3ctrls, w3components, w3forms, w3fonts,
  w3borders, w3application, unit1;

type
  TForm1=class(TW3form)
  private
    { Private methods }
    {$I 'Form1:intf'}
    FMenu:  TLSTMenu;
    procedure HandleItemSelected(Sender:TObject;Const aItem:TLSTCustomMenuItem);
  protected
    { Protected methods }
    procedure InitializeObject; override;
    procedure FinalizeObject; override;
    procedure StyleTagObject; reintroduce; virtual;
    procedure Resize; override;
  end;

implementation

//############################################################################
// TForm1
//############################################################################

procedure TForm1.InitializeObject;
var
  x:  Integer;
begin
  inherited;
  {$I 'Form1:impl'}
  W3HeaderControl1.Title.Caption:='Form header';
  w3HeaderControl1.BackButton.Visible:=False;

  FMenu:=TLSTMenu.Create(self);
  FMenu.Color:=clWhite;
  FMenu.StyleClass:='TPDFMenu';
  FMenu.Content.height:=120;
  FMenu.Content.color:=clGreen;
  FMenu.OnItemSelected:=HandleItemSelected;

  FMenu.BeginUpdate;
  try
    for x:=1 to 20 do
    FMenu.Add.Caption:='List item #' + IntToStr(x);
  finally
    FMenu.EndUpdate;
  end;
end;

procedure TForm1.FinalizeObject;
begin
  FMenu.free;
  inherited;
end;

procedure TForm1.HandleItemSelected(Sender:TObject;Const aItem:TLSTCustomMenuItem);
begin
  self.W3HeaderControl1.Title.Caption:=aItem.innerText;
end;

procedure TForm1.Resize;
var
  dy: Integer;
begin
  dy:=W3HeaderControl1.top + W3HeaderControl1.height + 2;
  FMenu.SetBounds(0,dy,width,height-dy);
  inherited;
end;

procedure TForm1.StyleTagObject;
begin
  //Custom styling
end;

end.

As you can see from the code, we catch the touch events and change the title of a header control we added to the form. This makes it quite easy to see what you are doing.

Scroll list galore

Scroll list galore

You can download the project file here (zip archive) and play with it!

« Box2d support
Smart Mobile Studio Hotfix 2 »

4 thoughts on “Creating a list menu”

  1. petermm says:
    14.07.2012 at 18:23

    While I agree that it would be great to have as much “native” components as possible, wouldn’t it be helpfull to find a way to use an existing JS/HTML5 GUI Lib (such as Teleriks Kendo)? Do you think that can be done?
    PMM

    • Jon Lennart says:
      16.07.2012 at 13:23

      It can be done, but that would defeat the purpose of using pascal. Native JS libraries dont have stuff like inheritance and all the good stuff from pascal, and in many cases they dont even have destructors. While we could have wrapped systems like Sencha – it would perhaps look better, but the underlying system would still be alien and introduce a whole bunch of problems that would be alien to the average pascal programmer. In time, our component model will come into its own – and the benefits will be more visible.

  2. Jørn E. Angeltveit says:
    16.07.2012 at 13:43

    It is (should be) possible to use existing libraries, but we haven’t had enough time to demonstrate this with complete packages.

    Many of the solutions we use today (both in the RTL and in various demos) are in fact wrappers for existing browser technology and existing solutions. There are both pros and cons, of course, with this. If you choose to wrap an existing component library, you might miss a lot of the compiler features (obfuscation and smart linking) and you might end up with a framework where the gap between your app-code and your component-package-code feels just a bit to large.

    Check out the Box2d wrapper CWBudde wrote a few days ago.
    http://smartmobilestudio.com/2012/07/04/box2d-support/

    That is one example of such a wrapper.

    We made a choice a while a go to create our own basic components, but we have always planned for an open components structure that will make it possible for everyone to write/wrap their own component packages and use these (and even share/sell these to other Smart users). During the autumn we will (hopefully) see lots of such solutions. One of the obstacles today is the visual designer and registration of such components in the IDE. This has a very high priority these days, but the complexity of this work is rather indescribable. During the alpha (and partly the beta) development stage, we have experimented with several solutions to get what we want, but it’s really a brain-twister to get all the pieces right.

    Steema Software has probably done what you have requested. They had a html-chart solution already, and created a Smart-version of that. The missing pieces here are 1) make sure that the wrapper fully supports smart linking and obfuscation, and 2) get the component registered in the IDE (i.e. get the component onto the component palette, get the properties into the object inspector and get the live rendering/mock representation into the visual designer.

    The component works like a charm if you’re doing it by code and avoid the packing of the generated code (i.e. obfuscation & smart linking).

    http://smartmobilestudio.com/2012/04/26/steema-teechart-converted-to-smart/

    Almost forgot:
    André Mussche also wrapped a DHTMLX grid back in January, during the Alpha testing.
    https://plus.google.com/110131086673878874356/posts/8tLVcw7djom

    Smart does now support variants as fully fledged JavaScript class containers. Ie instead of using FGrid: TObject, you use FGrid: Variant, and you’ll automatically have full support for all the JavaScript methods available. Thus, no need to wrap up every property/method in the pascal class and do a binding to the equivalent JavaScript member trough a asm-section. You can just call FGrid.setHeader directly from your Pascal source. Now THAT is nice indeed 🙂

    • gmhispagnol says:
      17.07.2012 at 17:27

      Think about the possibility of wrapping Sencha component libraries like in ExtPascal project. They made a scanner that parses the documentation and generates all wrappers automatically for Delphi server side components. The same could be done without much effort for Smart.

Comments are closed.

Pages

  • About
  • Feature Matrix
  • Forums
  • News
  • Release History
  • Download
  • Showcases
    • The Smart Contest 2013, Round 1 – Graphics
  • Store
  • Documentation
    • Creating your own controls
    • Debugging, exceptions and error handling
    • Differences between Delphi and Smart
    • Get the book
    • Getting started
      • Introduction
      • Local storage, session storage and global storage
      • Application architecture
      • The application object
      • Forms and navigation
      • Message dialogs
      • pmSmart Box Model
      • Themes and styles
    • Layout manager
    • Networking
      • Loading files
      • TW3HttpRequest
      • TW3JSONP
    • Prerequisites
    • Real data, talking to sqLite
    • System requirements
    • Project types
      • Visual project
      • Game project
      • Console project

Archives

  • December 2019
  • December 2018
  • November 2018
  • July 2018
  • June 2018
  • February 2018
  • September 2017
  • April 2017
  • November 2016
  • October 2016
  • September 2016
  • April 2016
  • March 2016
  • January 2016
  • October 2015
  • September 2015
  • July 2015
  • April 2015
  • January 2015
  • December 2014
  • October 2014
  • September 2014
  • August 2014
  • July 2014
  • June 2014
  • March 2014
  • February 2014
  • January 2014
  • December 2013
  • November 2013
  • October 2013
  • August 2013
  • July 2013
  • June 2013
  • May 2013
  • April 2013
  • March 2013
  • February 2013
  • January 2013
  • December 2012
  • November 2012
  • August 2012
  • July 2012
  • June 2012
  • May 2012
  • April 2012
  • March 2012
  • February 2012
  • January 2012
  • November 2011
  • October 2011
  • September 2011

Categories

  • Announcements (25)
  • Developers log (119)
  • Documentation (26)
  • News (104)
  • News and articles (16)

WordPress

  • Register
  • Log in
  • WordPress

Subscribe

  • Entries (RSS)
  • Comments (RSS)
© Optimale Systemer AS