Showing a dropdown menu when clicking a button

 Delphi, dzLib  Comments Off on Showing a dropdown menu when clicking a button
Aug 312014
 

A rather common question on StackOverflow, the Delphi newsgroups and elsewhere is how to display a drop down menu when the user presses a button.

ButtonWithDropdown

There are many proposed solutions and even something built into newer versions of Delphi (Which doesn’t work for me for some reason.)

Here is mine (which is based on this answer on StackOverflow):
First, I create a helper class based on TComponent. It links the button (which can be TButton or TBitBtn or anything else derived from TCustomButton) to the popup menu and hooks its OnClick event. To access the OnClick event, which is protected in TCustomButton, we need to cast it to TCustomButtonHack. The OnClick event then displays the popup menu. For convenience I set the helper class’s parent to the button, so it automatically gets freed when the button does get freed.

type
  TButtonPopupMenuLink = class(TComponent)
  private
    FBtn: TCustomButton;
    FMenu: TPopupMenu;
    FLastClose: DWORD;
  public
    constructor Create(_btn: TCustomButton; _pm: TPopupMenu);
    procedure doOnButtonClick(_Sender: TObject);
  end;

{ TButtonPopupMenuLink }

type
  TCustomButtonHack = class(TCustomButton)
  end;

constructor TButtonPopupMenuLink.Create(_btn: TCustomButton; _pm: TPopupMenu);
begin
  inherited Create(_btn);
  FBtn := _btn;
  FMenu := _pm;
  FMenu.PopupComponent := FBtn;
  FBtn.OnClick := Self.doOnButtonClick;
end;

procedure TButtonPopupMenuLink.doOnButtonClick(_Sender: TObject);
var
  Pt: TPoint;
begin
  if GetTickCount - FLastClose > 100 then begin
    Pt := FBtn.ClientToScreen(Point(0, FBtn.ClientHeight));
    FMenu.Popup(Pt.X, Pt.Y);
    { Note: PopupMenu.Popup does not return until the menu is closed }
    FLastClose := GetTickCount;
  end;
end;

And just for some more convenience I add a procedure that just creates that helper class, so I don’t have to expose the class through the unit’s interface but only that procedure:

procedure TButton_AddDropdownMenu(_btn: TCustomButton; _pm: TPopupMenu);
begin
  TButtonPopupMenuLink.Create(_btn, _pm);
end;

To use it I call that procedure from the form’s constructor:

constructor TMyForm.Create(_Owner: TComponent);
begin
  inherited Create;
  // other stuff
  TButton_AddDropdownMenu(b_MenuButton, pm_MenuButton);
end;

I have put that code into u_dzVclUtils, which is part of my dzlib library.

 Posted by on 2014-08-31 at 16:36

Translating Windows messages to strings

 Delphi, dzLib  Comments Off on Translating Windows messages to strings
Aug 242014
 

I could not find anything like this so I wrote it myself:

This class translates most Windows message ids into their symbolic name.

type
  TWmMessageToString = class
    function MsgToString(const _WmMsg: Cardinal): string; overload;
    function MsgToString(const _Msg: TMessage): string; overload;
  end;

The names are taken from

  • Delphi 2010’s messages.pas
  • Delphi 2010’s controls.pas
  • Wine

It seems pretty complete, but if a message cannot be found, the MsgToString methods return its hexadecimal and decimal representation.

The code is part of my dzlib its the u_dzWmMessageToString unit.

 Posted by on 2014-08-24 at 22:42

Preventing a dialog from closing while autocomplete is active

 Delphi, dzLib  Comments Off on Preventing a dialog from closing while autocomplete is active
Aug 242014
 

In an older blog post I wrote about AutoComplete for TEdits using SHAutoComplete.

I just actually tried to use that function in one of my applications and found that there is a quite annoying problem with it: If you have set the OK button’s Default property to true (so it gets “clicked” when you press return), selecting an entry from the autocomplete list with the return key also closes the form, which is usually not what the user wants.

I turns out that I am not the first to stumble upon that problem.

The suggestion posted there by mghie is a bit ugly because it hooks the Application.OnMessage event which might conflict with other code that uses it.

I had another problem anyway (see below) so I extended a class that hooks a TEdit’s WindowProc method instead. Here is the code:

procedure TAutoCompleteActivator.NewWindowProc(var _Msg: TMessage);
begin
  if (_Msg.Msg = CM_WANTSPECIALKEY) then begin
    if (_Msg.wParam = VK_RETURN) or (_Msg.wParam = VK_ESCAPE) then begin
      if IsAutoSuggestDropdownVisible then begin
        _Msg.Result := 1;
        Exit; //==>
      end;
    end;
  end;
  inherited NewWindowProc(_Msg);
end;

The IsAutoSuggestDropdownVisible function is directly taken from mghie’s answer:

function EnumThreadWindowsProc(AWnd: HWnd; AParam: LParam): BOOL; stdcall;
var
  WndClassName: string;
  FoundAndVisiblePtr: PInteger;
begin
  SetLength(WndClassName, 1024);
  GetClassName(AWnd, PChar(WndClassName), Length(WndClassName));
  WndClassName := PChar(WndClassName);
  if WndClassName = 'Auto-Suggest Dropdown' then begin // do not translate
    FoundAndVisiblePtr := PInteger(AParam);
    FoundAndVisiblePtr^ := Ord(IsWindowVisible(AWnd));
    Result := False;
  end else
    Result := True;
end;

function IsAutoSuggestDropdownVisible: Boolean;
var
  FoundAndVisible: Integer;
begin
  FoundAndVisible := 0;
  EnumThreadWindows(GetCurrentThreadId, @EnumThreadWindowsProc,
    LParam(@FoundAndVisible));
  Result := FoundAndVisible > 0;
end;

This works fine in my program compiled with Delphi 2010 and running on Windows 8.1 (your mileage may vary).

Now to the other problem mentioned above:
In the old blog post I published a TEdit_SetAutocomplete function that activates autocomplete for a TEdit control. This function works fine as as long as you don’t try to call it in the form’s constructor. If you do, it does nothing. The reason is that the TEdit’s handle gets destroyed and recreated after the form’s constructor was called, which results in autocomplete being turned off again. One option would have been to put the function call into the form’s OnShow handler, but I am no fan of distributing code that in my opinion belongs into the constructor to these event handlers, so I wanted a different solution.

It turned out that I already had one in my dzlib.u_dzVclUtils unit: TWinControl_ActivateDropFiles returns a TObject that hooks the TWinControl’s WindowProc and handles the WM_NCCREATE and WM_NCDESTROY messages. I refactored that class a bit to create a generic TWindowProcHook ancestor and derived TAutoCompleteActivator from it. Its WmNcCreate method now looks like this:

procedure TAutoCompleteActivator.WmNcCreate;
begin
  inherited;
  SetAutoComplete;
end;

procedure TAutoCompleteActivator.SetAutoComplete;
begin
  TEdit_SetAutocomplete(FCtrl as TCustomEdit, FSource, FType);
end;

So every time the window handle gets created anew, it activates autocomplete for it again.

The full code can be found in my dzlib library on SourceForge. It’s in the u_dzVclUtils unit.

 Posted by on 2014-08-24 at 22:20

Sabotaged by Windows Update

 Delphi, Windows  Comments Off on Sabotaged by Windows Update
Aug 152014
 

Microsoft released a Windows Update this week that caused quite a problem for Delphi developers still using Delphi 2006 to 2010. When starting a second instance of the IDE they now get the error

Cannot create file C:\Users\Admin\AppData\Local\Temp\EditorLineEnds.ttr

The update in question is a security update for all supported Windows versions and has the number KB2982791. It prevents the file EditorLineEnds.ttr to be created/overwritten because that file is a Truetype font and changing it while it is active is apparently a security risk.

Unfortunately this prevents a meaningful use of these Delphi versions. So what can we do?

There is this question on StackOverflow from which I got the above information. As far as I know there are the following workarounds:

  1. Uninstall the Windows update KB2982791, which might not be such a good idea. After all it’s a security update. Daniel Magin blogged about this.
  2. Use Andreas Hausladen’s IdeFixpack (Delphi 2007 version, beware that version 4.4 does not work under Windows 8, version 4.3 seems to work), (for later Delphi versions). Note that the editor option Show Lineends will use a different character if you use this fix because the IDE won’t load the EditorLineEnds.ttr font any more.
  3. Rename the file every time you want to start Delphi. This is what I will be writing more about.

Of course you don’t want to rename the file by hand every time you start Delphi. So either, you use a batch file to do that for you (see Daniel Magin’s post), or you use the program I wrote for this particular task.

It’s called dzEditorLineEndsFix and is a Tray Icon application that uses FindFirstChangeNotification / FindNextChangeNotification (I always wanted to use these API functions but never had an appropriate use case.) to detect when the file is being created and then moves it to its own unique subdirectory under %temp%.

In theory it will clean up after itself, but that might fail because the file(s) are still in use. So once in a while you should have a look into your %temp% folder and delete all subdirectories called ttr* from it.

Sources are available from the dzEditorLineEndsFix page on SourceForge. There is also a precompiled executable if you don’t want to compile it yourself and trust me not to have added a Trojan to it 😉

 Posted by on 2014-08-15 at 22:52

Using a bar code scanner? Watch out for your keyboard layout!

 Linux, Windows  Comments Off on Using a bar code scanner? Watch out for your keyboard layout!
Aug 142014
 

We are using a bar code scanner to scan the serial numbers of all hard disk drives we buy. This is supposed to make it easier and less error prone to put them into an Excel list for tracking their whereabouts (we use quite a lot of drives for storing video data).

Of course, when I bought 4 TB SATA drives for setting up yet another Linux server, I too put these drives in the list. And since I am a lazy bastard™ I borrowed the scanner to scan them. It worked like a charm so I returned the scanner and started building the raid.

I put labels on the drive bays with our internal number, so in the case of a drive failure I could use the list I mentioned to find out which drive to swap out.

One of the drives apparently was defective to start with, so what did I do? I asked mdadm which drive it was (/dev/sdg) and used

ls -l /dev/disk/by-id

to find its serial number.

lrwxrwxrwx 1 root root  9 Aug 14 08:59 ata-ST4000DM000-1F2168_Z301W61Y -> ../../sdg

Then I opened up the list to find the drive’s internal number. To my surprise, none of the serial numbers in the list seemed to match.

It turned out that on my computer, because I use a UK keyboard layout, the scanner swapped Y and Z. So, in the list the drive had the serial number Y301W61Z while in reality it was Z301W61Y.

Fun with computers, not.

 Posted by on 2014-08-14 at 10:15

Windows 7 Blue Screen Of Death with error 0x7B

 Windows, Windows 7  Comments Off on Windows 7 Blue Screen Of Death with error 0x7B
Aug 132014
 

If you ever had to change the motherboard of your computer and wanted to keep the Windows installation on it, you might have encountered the dreaded Blue Screen of Death with an unhelpful error code.

In my case, this was a Windows 7 installation where the on board SATA controller started to misbehave. These problems didn’t show up in Windows but only when I did my weekly backup using CloneZilla. I wonder what would have happened if I had happily continued to use this computer because Windows didn’t warn me?

First I put the hard disk into a different computer, started CloneZilla and made a backup. No errors showed up, so it wasn’t the hard disk but the controller.

Then I threw out the old motherboard and took a new (cheap) one that still supported my old Dual Core processor and RAM. Connected everything and booted the box. I was expecting for it just to work. This is Windows 7, one of the most modern operating systems available, so driver issues should be a thing of the past, right?

Well, not so. Windows 7 crashed and rebooted, crashed and rebooted, crashed and rebooted. After a while I got tired of this 😉 , pressed F8 for the boot menu and disabled automatic reboot on errors. Then I got the BSOD in all it’s glory: Error code 0x7B, meaning “INACCESSIBLE_BOOT_DEVICE”.

In the olden (Windows XP) days this was a symptom of the SATA controller running in AHCI mode and Windows expecting IDE mode or vice versa. Apparently this is still the same with Windows 7 (When will you ever learn, Microsoft?).

Unfortunately the new board’s BIOS did not really allow me to switch the SATA mode (it seemed to allow me, but it didn’t make any difference). So after swearing a lot I turned to Google and found … a lot, most of it not really helpful. Hours (literally!) later, the solution turned out to be the following:

To allow Windows 7 to boot in IDE as well as AHCI mode, I had to enable the following drivers (by setting “Start” to “0” in the registry, there might be other options to do this):

HKLM\System\CurrentControlSet\services\intelide
HKLM\System\CurrentControlSet\services\pciide
HKLM\System\CurrentControlSet\services\msahci
HKLM\System\CurrentControlSet\services\iastorV

The first two allow Windows 7 to boot from SATA in IDE mode. The second two allow Windows 7 to boot from SATA in AHCI mode.

So, why isn’t that the default? I have no idea.
(Do I have to point out that Linux booted on the updated box without any problems? Which one is the more user friendly system?)

 Posted by on 2014-08-13 at 16:45

Enabling the Developer Tools in Delphi Chromium Embedded

 Delphi  Comments Off on Enabling the Developer Tools in Delphi Chromium Embedded
Aug 062014
 

Recently I had to debug some JavaScript code that did not work correctly, when loaded into a Chromium Embedded frame in one of my applications. There is built-in support for the Developer Tools in Chrome, which is also available in Chromium Embedded.

After searching the web, I found that the DCEF3 sources already come with a demo on how to show these tools and also show them externally using Google Chrome (which of course must be installed for this to work).

It’s actually quite simple:

The first thing to do is set the port for remote debugging to something else but 0. This must be done before the Chromium Embedded library is being initialized, e.g. in the initialization section of the form that uses it.

initialization
  CefRemoteDebuggingPort := 9000;

Then you need a Chromium browser window to display the tools, that is, a second TChromium control which can reside either on the same form or on a different form, or even in a different application (like Google Chrome). I chose to go with the first option because it is the least hassle. My TChromium control was set to alClient, so I just added a TSplitter and another TChromium control, set to alBottom to it and set both to Visible=false by default.

Now, to show the Developer Tools, there is only very little code:

procedure TMyForm.SetDebugToolsEnabled(_Value: Boolean);
begin
  chr_Debug.Visible := _Value;
  spl_Debug.Visible := _Value;
  if _Value then begin
    if not FDevToolLoaded then begin
      chr_Debug.Load(chr_Embedded.browser.Host.GetDevToolsUrl(True));
      FDevToolLoaded := True;
    end;
  end;
end;

Where chr_Embeeded is the TChromium control that displays the actual content my programs uses and chr_Debug is the TChromium control that shows the Developer Tools.

Much easier than I originally thought, but far from obvious if you don’t know that the Devoloper Tools are just another browser window (which I didn’t).

 Posted by on 2014-08-06 at 10:56