Fake TSpeedButton based on TBitBtn updated again

 Delphi  Comments Off on Fake TSpeedButton based on TBitBtn updated again
Nov 102018
 

My fake TSpeedButton based on TBitBtn has been updated again. It now supports any combination of Glyph and single/multi line Text. It also supports setting Margin and Spacing.

Note that it still requires the Glyph to be on the left.

(Yes, I know, I should have selected some examples where the text does actually fit the button. ;-). But this is just the same problem as in the original TBitBtn control, I even fixed some if its bugs.)

(Click on the image to view an animated demo.)

Also, the original BitBtn object is now exposed via a read only property and there is a new pointer type Data property to attach some additional information to the object.

In addition I added a new class TdzSpeedBitBtnGroup which easily emulates the behaviour of a TRadioGroup using TdzSpeedBitBtns.

The source code is part of my dzlib library which is available from OSDN.

Making “Stay-on-top-forms” do want you want in Delphi

 Delphi  Comments Off on Making “Stay-on-top-forms” do want you want in Delphi
Nov 052018
 

There is an article by Peter Laman on the topic Making “Stay-on-top-forms” do want you want on the Embarcadero blog. It’s from 2004 and of course the link to the component he talks about no longer works, because it points to cc.borland.com which no longer exists.

Here is a working link.

(I don’t know whether the component still works or is even reqired. I just encountered a problem with a stay on top form and found this article.)

Edit: It’s not a component but a class TStayOnTopForm derived from TForm. You are supposed to derive your form from this class and it does everything else for you. Unfortunately it doesn’t seem to work in my test program, but it does work in the demo that comes with it.

Creating Component Templates in Delphi

 Delphi  Comments Off on Creating Component Templates in Delphi
Nov 032018
 

One nice feature of the Delphi IDE that I keep forgetting are Component Templates. That means you drop and customize one or multiple components on a form and then give them a new name so you can easily create them again on a different form in a different program.

Let’s start with a simple template for an OK button:

  1. Drop a button on a form.
  2. Give it a meaningful name, e.g. “btnOK
  3. Set its “Caption” property to “OK”.
  4. Set its “Default” property to True.
  5. Make sure that the button is sill selected.
  6. Select Component -> Create Component Template from the menu.
  7. Give it a name, e.g. “OKButton”.

That’s it. A new entry has been added to the Tool Palette. Now, whenever you want to add an OK button, you simply press Ctrl+Alt+P to bring up the Tool Plaette, then enter “OK” and maybe some more keys to filter the list and select the new entry. A copy of the created template will be added to your form. No need to change its properties again.

Now, a form usually has not just an OK but also a Cancel button, why not create a template “OkCancelButtons” for that?


(Click on the picture for an animated tutorial for the below.)

  1. Drop a button on a form.
  2. Give it a meaningful name, e.g. “btnOK
  3. Set its “Caption” property to “OK”.
  4. Set its “Default” property to True.
  5. Drop another button on the form.
  6. Move it to your liking in relation to the OK button.
  7. Set its “Caption” property to “Cancel”.
  8. Give it a meaningful name, e.g. “btnCancel”
  9. Set its “Cancel” property to True.
  10. Select both buttons with Shift+Left Mouse Button.
  11. Select Component -> Create Component Template from the menu.
  12. Give it a name, e.g. “OKCancelButton”.

Again, a new entry has been added to the Tool Palette which you can use whenever you need these two buttons on any form in any program. They will have the correct captions, the Default and Cancel properties will be set and they will be in the right place in relation to each other.

To get rid of a component template you don’t need any more, use the “Delete <component name>” entry context menu on the entry in the Tool Palette.

Component templates are stored in the file
%AppData%\Roaming\Embarcadero\BDS\{version}\bds.dct for current versions, and in delphi.dct in the executable directory for Delphi 6 and 7. I don’t know whether even older versions supported them, but if yes I’d expect them to store them in the same way as Delphi 6 and 7.

(Thanks to Ondrej Kelle for that link.)

Two buttons to make a difference in the Set Tab Order expert

 Delphi, GExperts  Comments Off on Two buttons to make a difference in the Set Tab Order expert
Nov 012018
 

The Set Tab Order expert in GExperts allows you to change the tab order of controls by dragging them in a tree view. I have always wondered why the standard Tab Order dialog of the IDE has got buttons to move the current control up or down while the one in GExperts does not.

OK, now it has them too:

These buttons even have keyboard shortcuts: Ctrl+Up moves the current item up, Ctrl+Down moves it down.

I’m thinking about further improvements for that expert, but nothing concrete yet. I kind of like the approach that the CnPack IDE wizards take, but on the other hand too much automation can be bad too. What if I want to have a button on the lower left to be the last control in the tab order? Like an “About” button, which in my opinion should not be between the last input field and the OK button because I rarely click it?

New GExperts Feature: Favorites menu

 Delphi, GExperts  Comments Off on New GExperts Feature: Favorites menu
Oct 272018
 

I have stolen yet another idea for a new GExperts feature, this time from Dave Nottage‘s Codex Delphi Expert. (But the code is all mine, including any bugs you might experience.)

It’s the Favorites sub menu in the File menu:

The content of this sub menu is generated on the fly, so it will always contain the current entries of the Favorite Files Expert. Since that expert’s dialog is non modal, you can change the configuration and immediately see that the Favorites sub menu content changes. All entries are prefixed with numbers 0-9 and then letters A-T, so there is a maximum of 10+20=30 entries on each level. In addition there is X for Configure which opens the Expert’s configuration dialog:

Unfortunately this new feature will only be available for Delphi 7 and later. I was unable to get it to work in Delphi 6. Sorry about that! But maybe somebody else wants to try it? The source code is here.

There is no release with this feature yet. To get it, for now you have to compile your own GExperts dll which isn’t exactly rocket science anyway.

The State of the GExperts Mailing list and G+ Community

 Delphi, GExperts  Comments Off on The State of the GExperts Mailing list and G+ Community
Oct 262018
 

You might have heard that Google is “sunsetting” Google+ so the GExperts community there will no longer exist after August 2019.

There is still the GExperts Mailing List on Yahoo, but unfortunately either Google Mail or Yahoo changed something so I am no longer able to post to it (I get a rather unhelpful error message.)

So I have asked Daniel, the administrator of Delphi Praxis whether I can get a GExperts sub forum in the new English Delphi Praxis.

I haven’t received an answer yet, but I hope it will be positive. If yes, I will announce it here and as soon as that sub forum goes live.

As for my general Google+ activity, I have not yet decided what to do. Currently it looks likely that I won’t migrate to any other “social media” site at all. I definitely won’t go to Facebook.

Building less annoying user interfaces (Part 2)

 Delphi  Comments Off on Building less annoying user interfaces (Part 2)
Oct 142018
 

In Part 1 I was ranting about input validation, now it’s about form sizes.

In my previous post I already said that I am not a big fan of fixed size dialogs. They are fine as long as they are large enough to show all of their content, but as soon as there is a list that can grow to an arbitrary size, fixed size dialogs are a no go. Look at the Microsoft Printer installation dialog again:

It contains two lists, one of printer manufacturers and the other of printer models of the selected manufacturer. Of course, it could be even worse and have one list only (And even that wouldn’t have surprised me really. Microsoft has all sorts of bad UIs.). So the list of manufacturers is sorted and even has incremental search. It’s also not too long so it’s probably fine for most users. But the list of printer models is really stupid:

  1. All printer names are prefixed with the manufacturer name, so incremental search does not help.
  2. The list is not high enough to display all entries. Thus there is a vertical scroll bar. OK, for several hundred printers listed for HP alone no screen would have been large enough, so it can’t be avoided. But one page only shows 4 lines and the last one is even only half visible.
  3. And also, the list is not even wide enough to display the full text of the entries. So we get a horizontal scroll bar too which further reduces the number of lines visible.

In my opinion this is really bad UI design. That dialog could be improved a lot by simply changing it to be sizable, anchor both lists to the left, top and bottom and the right list to the right as well.

I faked that dialog with Delphi so I could show you what I mean:

I even reproduced the ridiculously large white areas around the lists. But this dialog can be sized and has even got a maximize button. Let’s see what it looks like if I make it just a little bit larger:

No horizontal scroll bar any more. And all visible lines are completely visible rather than only half of the last one. And if I increased the height even more, it would become much easier to scroll the printer list down to the right model.

OK, so we make all our dialogs sizeable and set the control’s anchors correctly. Easy as cake. Is that it?

No. With more freedom for the user we get more things he can screw up. Have a look at the next screen shot:

A sizeable dialog can also be made smaller and this is what it looks like. Eeek! So what can we do about it? We set size restrictions.

The easiest way to do that, is designing forms in the smallest size you want them to have and then set its minimum size to the designed size.

I have got two simple helper functions that do this for me:

type
  TControlConstraints = (ccMinWidth, ccMinHeight, ccMaxWidth, ccMaxHeight);
  TControlConstraintsSet = set of TControlConstraints;
const
  ccMin = [ccMinWidth, ccMinHeight];
  ccMax = [ccMaxWidth, ccMaxHeight];
  ccAll = [ccMinWidth, ccMinHeight, ccMaxWidth, ccMaxHeight];
  ccFixedHeightMinWidth = [ccMinWidth, ccMinHeight, ccMaxHeight];
  ccFixedWidthMinHeight = [ccMinWidth, ccMinHeight, ccMaxWidth];

procedure TControl_SetConstraints(_Control: TControl; _Which: TControlConstraintsSet);
begin
  if ccMinWidth in _Which then
    _Control.Constraints.MinWidth := _Control.Width;
  if ccMinHeight in _Which then
    _Control.Constraints.MinHeight := _Control.Height;
  if ccMaxWidth in _Which then
    _Control.Constraints.MaxWidth := _Control.Width;
  if ccMaxHeight in _Which then
    _Control.Constraints.MaxHeight := _Control.Height;
end;

procedure TControl_SetMinConstraints(_Control: TControl);
begin
  TControl_SetConstraints(_Control, ccMin);
end;

So, in the constructor I simply call:

constructor TMyForm.Create(_Owner: TComponent);
begin
  inherited;
  TControl_setMinConstraints(Self);
end;

Since we started out with a fixed size form, this does not have any negative effects. The form still has a minimum size that corresponds to the fixed size it used to have, so even users whose monitors are too small aren’t any worse off, and those who have large monitors can use all the space they want to.

Two more screenshots to just give you an idea of the difference:

This is the original dialog on a 24″ monitor with 1920×1280 pixel resolution:

It feels like a postage stamp.

And this is my fake dialog resized to about 1/4 of the monitor resolution:

(Of course here you see that I didn’t bother to add more lines to the lists than necessary to get my point across.)

Here is another example where a fixed sized dialog is not a good idea.

There is one entry field that takes a file name. In a fixed sized dialog you would usually end up with clipping that name because nowadays file names are very long since nobody has to type them any more. If you are a user, you still want to be able to see the full path. So, you simply make the dialog wider and tadaaa:

This is a special case where you probably don’t want the dialog to become any higher than the designed size. So what do you do? Simple, you also restrict the height:

constructor TMyForm.Create(_Owner: TComponent);
begin
  inherited;
  TControl_SetConstraints(Self, ccFixedHeightMinWidth);
end;

Now that user can resize it to make it wider, but the height will not change.

I’ll leave it at that for now. Expect more rambling in a future post.

And while you are waiting for that one, you can read what
David Millington said in his blog posts on How to Design a Great UI (part 2, part 3).

Some remarks on “How Design a Great UI”

 Delphi  Comments Off on Some remarks on “How Design a Great UI”
Oct 112018
 

David Millington has published his third blog post on How to Design a Great UI. While I agree with many of his points I disagree with some others. So here is what I disagree with and why:

Use BorderStyle bsDialog

This means several things:

  1. The dialog has a thicker border than usual
  2. The dialog has a close icon only.
  3. The dialog cannot be sized.

The last point is what I strongly disagree with for dialogs like the one in his example. It has got a list of chapters from a book that are to be exported. This list can be rather long so it is quite likely that it at one time get a vertical scroll bar. Also, chapter headings might be much longer than simply “Chapter 1” (and you want to display those headings so the user can make an informed decision on which chapter(s) to export.), so it is also likely that the list gets a horizontal scroll bar (or even worse, if the control you use does not support horizontal scrolling: The items get cut off on the right.). This reduces usability significantly. Just have a look at the Microsoft Printer installation dialog:

We are no longer living in the 1990ies, when you could count yourself lucky if you owned a 17″ Monitor with a resolution of 1280×1024 pixels. Today, there are users who have got a a 30 inch monitor or even several of those and with resolutions up to 8K, where this dialog shows only in the center with the perceived size of a postage stamp.
As a user I want to maximize this kind of dialog so I see more entries of the list and also read the whole description.
So, rather than using BorderStyle = bsDialog, use bsSizeable and make sure that you set the Anchors and Align properties of all controls in a way they resize and move sensibly.

Position: poOwnerFormCenter (or alternatively poMainFormCenter.)

Again, this was fine for the 1990ies. But nowadys the owner form can be huge (e.g. full screen on a 30″ monitor) and you clicked a button somewhere on that form (e.g. in the bottom right) to get to this dialog. Would you really like the new dialog to pop up centered on the owner form? Your mouse just moved to that button and your visual focus also did. Now, all of a sudden you have to move your focus (and your mouse) to the middle of the screen. My suggestion in that case would be to center the new dialog on the button the user clicked to open it. Of course this should still make sure that the dialog is fully visible and does not cross monitor borders.

InplaceExeWrapper for those tools that do not allow specifying an output file

 Delphi  Comments Off on InplaceExeWrapper for those tools that do not allow specifying an output file
Oct 072018
 

There are a lot of command line tools that are very useful, but have one flaw: They directly modify a file in place and do not allow you to specify an output file instead.

So, if you e.g. want to compare the modified file to the original, you have to make a copy first. Or, if you just want to view the modified file but keep the original or you cannot modify the file itself because it is immutable due to whatever reason (e.g. it resides on a read only medium like a cdrom), again you have to make a copy first.

My particular use case was the great tool DrpojNormalizer written by Uwe Raabe (and later replaced by ProjectMagician) which I wanted to use for showing changes in Delphi .dproj files in BeyondCompare. These tools are actually Delphi IDE plugins but both come with a command line program too. But unfortunately both modify the file in place so they are one example of the above mentioned category of tools.

Enter InplaceExeWrapper which called as

InplaceExeWrapper --expectfilenameonstdout c:\path\to\dprojnormalizercmd.exe input.dproj output.dproj

does the following:

  1. Create a temporary directory under %TEMP%
  2. Copy the input file input.dproj there
  3. Call the tool as
    dprojnormalizer tempfile.dproj
    
  4. Copy the modified file to the output file output.dproj
  5. Delete the temporary directory

So basically it replaces the missing functionality of specifying an output file for the tool it calls.

Here is the full help on command line parameters and options:

Synopsis: InplaceExeWrapper [options] Executable InFile [OutFile]

Parameters:
Executable        : Executable to call (if set to "copy" we only copy InFile to OutFile)
InFile            : input filename
OutFile           : output filename, defaults to infile

Options:
--CheckResult=value : If set, the executable must return the value given for this option (must be a number)
--debug           : if given, some debug output is written to error.txt in the temp directory and the temp directo
ry will not be deleted.
--ExpectFilenameOnStdout : if set, the output of the executable must contain the filename
--help
-?
-h
-H                : display parameter help
--ShowCmdLine     : Show command line as passed to the program.
--StartupLog=value : Write a startup log to the given file.
--TempDir=value   : directory to use for temporary files (must exist)
--toStdOut        : if set, output is written to stdout and InFile is not changed

The option –ExpectFilenameOnStdout can be used to detect if the called program actually worked. If the output does not contain the file name InplaceExeWrapper assumes that the call failed.

There is the special “executable” parameter “copy” which simply copies InFile to OutFile. I needed it to make the file editable in BeyondCompare. Here is the required configuration to be added to Tools -> File Formats:

Name: Delphi Project
Mask: *.dproj
Conversion:
Loading:

Path\to\InplaceExeWrapper.exe  --expectfilenameonstdout "Path\To\dprojnormalizercmd.exe" %s %t

Saving:

Path\to\InplaceExeWrapper.exe copy %s %t

InplaceExeWrapper has a project page on OSDN which includes a binary download as well as the source code.