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.
GoogleComments Off on Exported GMail mbox contains GMail labels
Just in case it isn’t already common knowledge: The “Takeout” export of GMail will give you a single file in mbox format (which will be rather large).
That file contains the content of the “all mail” folder. And each of the messages in the file has a header like this:
So with the right tool it is possible to restore the labels you have set in GMail. Unfortunately I know of no such tool. And also I don’t know of any (Windows) email client that allows you to set user defined tags for emails.
DelphiComments Off on Building less annoying user interfaces (Part 2)
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:
All printer names are prefixed with the manufacturer name, so incremental search does not help.
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.
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:
TControlConstraints = (ccMinWidth, ccMinHeight, ccMaxWidth, ccMaxHeight);
TControlConstraintsSet = set of TControlConstraints;
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);
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;
procedure TControl_SetMinConstraints(_Control: TControl);
So, in the constructor I simply call:
constructor TMyForm.Create(_Owner: TComponent);
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);
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.
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.
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.
DelphiComments Off on InplaceExeWrapper for those tools that do not allow specifying an output file
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.
Copy the modified file to the output file output.dproj
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]
Executable : Executable to call (if set to "copy" we only copy InFile to OutFile)
InFile : input filename
OutFile : output filename, defaults to infile
--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
-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: