LiveBindings in XE3: TPrototypeBindSource and Custom Generators

In the last post I looked at formatting the data we’re binding to our UI. This post is related, but it might take a little while to see how.

Back when I wrote about TAdapterBindSource, one of the adapters I used was a TDataGeneratorAdapter. This combination was useful to give me both a) design-time fields from which to visually bind in the LiveBindings Designer and b) design-time data in those fields to help layout my UI more easily.

This capability is great for very quickly being able to mock up screens for review, iterating over UI candidates until you narrow in on a reasonably final prototype. Not only can you do all this without having to commit to where the data is going to come from, once you do decide on a UI layout, you can progress this prototype to production using one of the Adapters we’ve already looked at.

This combination of TAdapterBindSource and TDataGenerator is so useful, they’ve been combined together into a single component called a TPrototypeBindSource.

My custom email address generator displaying in the list of generators available at designtime. Note the live preview data at the bottom.

So here’s where the formatting comes in. The more realistic we can make the data being generated and displayed in our UI prototype, the more easily people will be able to visualise the end product, and hopefully by extension the better the feedback we’ll get during our UI review. The formatting options we’ve looked at in the last post will help us get some of the way there.

However, your business likely has one or more data types that are common in your domain: SKUs, Part numbers, Customer codes, IP addresses, etc. Further, there are data types that are not domain specific, but region-specific, like postcodes/zipcodes, phone numbers, states, ABN numbers, Social Security numbers, etc. If I have to craft out separate format strings every time I want to show one of these fields in my UI prototype I’ll waste a lot of time, or more likely, I won’t bother.

What would be better is if I could define a custom generator for each of these data types once, then re-use it whenever I need a field of that type. Well, that’s what the example code that accompanies this post does.

The data type I’ve chosen to create a generator for is one that hopefully everyone is familiar with: email addresses.

To register a custom generator, I need to create a descendant of TDelegateValueGenerator. In my case it looks like this:

TEmailAddressGenerator = class(TDelegateValueGenerator)
protected
  function CreateDelegate: TValueGeneratorDelegate; override;
end;

As you can see, the only function I’ve overridden is CreateDelegate, which looks like this:

function TEmailAddressGenerator.CreateDelegate: TValueGeneratorDelegate;
begin
  Result := nil;
  case FieldType of
    ftString: Result := TTypedListValueGeneratorDelegate.Create(Options,
                                                                        LoadAddresses);
  else
    Assert(False);
  end;
end;

First, I’m checking that the requested FieldType is one I support. In this case a string, but you could write a generator that produces multiple different types, such as the Lorem Ipsum generator that generates strings and TStrings. Then, for each FieldType I’m creating an appropriate TTypedListGeneratorDelegate,  here a TTypedListGeneratorDelegate<string>. This takes two parameters, a set of Options (I’m passing in the default of [optShuffle, optRepeat]) and a TArray of the FieldType. Here I’m passing in a function called LoadAddresses, which returns a TArray<string> containing a whole bunch of generated email addresses, which will be shuffled and repeated over.

How many values you put in this Array is up to you, and depends on a number of things, such as:

  • How big each one is,
  • how many you’ll be displaying at once,
  • whether you care if duplicates pop-up.
  • It also obviously depends on the datatype itself: for something like states there are only a small set of values, so you may well put them all in.

In this case I’m generating 200 random email addresses, where the local part (the bit before the @) and the first part of the domain are just random strings of varying length, and the domain extension is randomly chosen from a small group of valid extensions:

const
  CDomainExtensions: array[0..18] of string =
  (
    'com', 'org', 'net', 'mobi', 'info',
    'com.au', 'org.au', 'net.au',
    'com.sg', 'org.sg', 'net.sg',
    'com.cn', 'net.cn', 'gov.cn',
    'co.jp', 'ed.jp',
    'co.uk', 'gov.uk', 'net.uk'
  );

function LoadAddresses: TArray;
  function RandomString(Length : Integer) : string;
  const
    s = 'abcdefghijklmnopqrstuvwxyz';
  var
    I, strLength: Integer;
  begin
    strLength := System.Length(s);
    Result := '';

    for I := 0 to Length do
      Result := Result + s[Random(strLength) + 1];
  end;
var
  LList: TList;
  LAddress : string;
  I: Integer;
begin
  Randomize;
  LList := TList.Create;
  try
    for I := 0 to 199 do
    begin
      LAddress := Format('%s@%s.%s', [RandomString(Random(8) + 3),
                                      RandomString(Random(8) + 3),
                                      CDomainExtensions[Random(18)]]);
      LList.Add(LAddress);
    end;
    Result := LList.ToArray;
  finally
    LList.Free;
  end;
end;

Lastly, I register and unregister my generator in the initialization/finalization sections like so:

initialization
  RegisterValueGenerator('EmailAddresses', 
                         [ftString],
                         TValueGeneratorDescription.Create(TEmailAddressGenerator,
                                                           'Email%d', 
                                                           'EmailGenerator'));
finalization
  UnRegisterValueGenerator('EmailAddresses', [ftString], '');
end.

The first two parameters to both the RegisterValueGenerator and the UnRegisterValueGenerator calls are the same:

  • The name of our generator
  • the set of FieldTypes our generator supports.
The third parameter to the RegisterValueGenerator call is a wrapper object for our generator, and the parameters to it’s constructor are:
  • The class type of our generator
  • The string to use as the default field name, where the %d will be substituted for a number. In this case, if we create multiple fields using our generator in a TPrototypeBindSource, they’ll by default be called Email1, Email2, etc.
  • The name of the unit that contains our generator.

This all goes in a package, and once you build it and install it into the IDE, it’ll show up in the list of generators when you are defining fields in a TPrototypeBindSource and/or a TDataGeneratorAdapter. It will also be used at designtime and/or runtime to serve up values for any Email fields you’ve defined.

With a little investment in creating these generators, you should be able to very quickly create interactive prototype UIs that more closely reflect your domain’s data, then fill out the backend when you choose.

Be the first to leave a comment. Don’t be shy.

Join the Discussion

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>