TMemo vs. Esc key

 Delphi  Comments Off on TMemo vs. Esc key
Jun 192016
 

If you have ever had a TMemo on a modal form with an OK and Cancel button, where the latter has its Cancel property set to true, you might have found that Esc does not cancel the dialog while the memo has got the focus.

According to David Heffernan that’s because the VCL tells it to use all keys but it then doesn’t handle Esc. David also provides a fix for this via an interposer class. While this works it means that you have to add this interposer to every form in your application.

If you are already using Andras Hausladen’s excellent VCL Fix Pack there is another option: Add David’s fix to the hook installed by InitContextMenuFix (works only for Delphi 6-2007) or add a special hook for the fix only (for later Delphi versions where the context menu bug has been fixed).

So far I have only done the first. Look for the code between the two “TMemo Esc Fix” comments

procedure TContextMenuFixWinControl.DefaultHandler(var Message);
type
  TDefHandler = procedure(Self: TControl; var Message);
begin
  if HandleAllocated then
  begin
    with TMessage(Message) do
    begin
      { Here was the WM_CONTEXTMENU Code that is not necessary because
        DefWndProc will send this message to the parent control. }

      { Keep the odd bahavior for grids because everybody seems to be used to it. }
      if (Msg = WM_CONTEXTMENU) and (Parent <> nil) and (Parent is TCustomGrid) then
      begin
        Result := Parent.Perform(Msg, WParam, LParam);
        if Result <> 0 then Exit;
      end;

      // Begin - TMemo Esc Fix
      if (Msg = WM_GETDLGCODE) and (Parent <> nil) and Self.InheritsFrom(TCustomMemo) then
      begin
        //inherited DefaultHandler(Message);
        TDefHandler(@TControl.DefaultHandler)(Self, Message);
        Result := Result and not DLGC_WANTALLKEYS;
        Exit;
      end;

      if (Msg = CM_WANTSPECIALKEY) and (Parent <> nil) and Self.InheritsFrom(TCustomMemo) then
      begin
        case TCMWantSpecialKey(Message).CharCode of
        VK_ESCAPE:
          begin
            Result := 0;
            Exit;
          end;
        VK_RETURN, VK_EXECUTE, VK_CANCEL:
          begin
            Result :=1;
            Exit;
          end;
        end;
      end;
      // End - TMemo Esc Fix

      case Msg of
        WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:
          Result := SendMessage(LParam, CN_BASE + Msg, WParam, LParam);
        CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
          begin
            SetTextColor(WParam, ColorToRGB(Font.Color));
            SetBkColor(WParam, ColorToRGB(Brush.Color));
            Result := Brush.Handle;
          end;
      else
        if Msg = RM_GetObjectInstance then
          Result := LRESULT(Self)
        else
          Result := CallWindowProc(DefWndProc, Handle, Msg, WParam, LParam);
      end;
      if Msg = WM_SETTEXT then
        SendDockNotification(Msg, WParam, LParam);
    end;
  end
  else
    //inherited DefaultHandler(Message);
    TDefHandler(@TControl.DefaultHandler)(Self, Message);
end;

What it does is basically the same as David’s interposer class:

  1. It handles WM_GETDLGCODE by calling the inherited message handler (in this case: The original WindowProc) and removing the DLGC_WANTALLKEYS bit from the result.
  2. It handles CM_WANTSPECIALKEY, checks for VK_ESCAPE for which it sets Result to 0, meaning “I’m not interested in this key.”, and VK_RETURN, VK_EXECUTE, VK_CANCEL setting Result to 1 meaning “I’m interested in these keys.”.

Andy’s code hooks TWinControl.DefaultHandler so the code above gets called for all TWinControls, but we don’t want to meddle with the Esc key handling of other controls. There was a small problem with checking whether the control is actually a Memo. “Self is TMemo” did not work because TContextMenuFixWinControl.DefaultHandler is a class method, so the compiler thinks that Self is a class rather than a class instance and didn’t want to compile this code. Changing the condition to Self.InheritsFrom(TCustomMemo) did the trick.

Sarch path dialog behaviour changed from Delphi 2010 to XE

 Delphi  Comments Off on Sarch path dialog behaviour changed from Delphi 2010 to XE
Jun 192016
 

Yesterday, while working with Delphi 10.1 something happened that made me think I had introduced a bug in the search path dialog enhancement of GExperts:

I had dropped some directories from the explorer onto the memo inserted by the GExperts enhancement, switched to the list view and back to the memo, then pressed the “Make Relative” button and exited the dialog with OK. Nothing special here until I noticed that the search path now contained the last directory twice: Once as a relative path and once as an absolute path.

Today I investigated this a bit more and found that the behaviour of the search path editor dialog had changed from Delphi 2010 to XE: From then on the content of the edit field gets added to the search path even if you don’t press the Add button but just the OK button. Since this only happens if it is not already there, you usually don’t notice, unless you have changed the entries in the list from absolute to relative paths. Then you end up with a duplicate.

I disabled the GExperts enhancements to make sure it’s not cause by it: It’s a change in the dialog itself.

SearchPathOddity

“Hooking” KeyDown in a Firemonkey form

 Delphi, dzLib  Comments Off on “Hooking” KeyDown in a Firemonkey form
Jun 182016
 

As said in my last post: " The hard part is hooking the form in a way so all I need to do is call TForm_ActivatePositioning as in the VCL."

As it turns out, that’s even easier to do than in the VCL. No subclassing of the window, just adding a new control is sufficient. As this StackOverflow answer points out, a Firemonkey form calls the DialogKey method of all its child controls when a key is pressed, starting with the one that has got the focus. So, all we’ve got to do is creating a control that handles the keys we want to intercept and add it to the form.

Here is the code:

type
  TFormPositioningActivator = class(TControl)
  private
    FModifier: TShiftState;
  protected
    procedure DialogKey(var Key: Word; Shift: TShiftState); override;
  public
    constructor Create(_Form: TCustomForm; _Modifier: TShiftState); reintroduce;
  end;

{ TFormHookChild }

constructor TFormPositioningActivator.Create(_Form: TCustomForm; _Modifier: TShiftState);
begin
  inherited Create(_Form);
  FModifier := _Modifier;
  Parent := _Form;
end;

procedure TFormPositioningActivator.DialogKey(var Key: Word; Shift: TShiftState);
begin
  inherited;
  if Shift = FModifier then begin
    case Key of
      vkLeft: TForm_MoveTo(self.Parent as TForm, dwpLeft);
      vkRight: TForm_MoveTo(self.Parent as TForm, dwpRight);
      vkUp: TForm_MoveTo(self.Parent as TForm, dwpTop);
      vkDown: TForm_MoveTo(self.Parent as TForm, dwpBottom);
      vkHome: TForm_MoveTo(self.Parent as TForm, dwpTopLeft);
      vkEnd: TForm_MoveTo(self.Parent as TForm, dwpBottomLeft);
      vkPrior: TForm_MoveTo(self.Parent as TForm, dwpTopRight);
      vkNext: TForm_MoveTo(self.Parent as TForm, dwpBottomRight);
    else
      Exit; // so Key doesn't get set to 0
    end;
    Key := 0;
  end;

end;

function TForm_ActivatePositioning(_Form: TForm; _Modifier: TShiftState = [ssCtrl, ssAlt]): TObject;
begin
  Result := TFormPositioningActivator.Create(_Form, _Modifier);
end;

All you have to do is call TForm_ActivatePositioning(Self) in the form’s constructor and be done.

Snapping a Firemonkey window to monitor halves / quadrants

 Delphi, dzLib  Comments Off on Snapping a Firemonkey window to monitor halves / quadrants
Jun 182016
 

I always wanted to start playing with Firemonkey but so far just didn’t find the right project. This is my first try to port a VCL utility function to Firemonkey. Note that this will probably not work on all platforms. It’s tested on Windows only.

So, how do we get the code from my last post to work with a Firemonkey program? It turned out to be not too difficult. Monitors have been renamed to Displays, TForm.BoundsRect is now only TForm.Bounds. There doesn’t seem to be an equivalent to TForm.Constraints (even though Simon J. Stuart has posted a TConstraintForm solution on StackOverflow) so we will for now ignore that.

Here is the code:

procedure TForm_MoveTo(_frm: TCustomForm; _Position: TdzWindowPositions);

  procedure ToTop(var _Re: TRect; _MinHeight, _MaxHeight: Integer);
  begin
    _Re.Bottom := _Re.Top + _Re.Height div 2;
    if _Re.Height < _MinHeight then
      _Re.Bottom := _Re.Top + _MinHeight;
    if (_MaxHeight > 0) and (_Re.Height > _MaxHeight) then
      _Re.Bottom := _Re.Top + _MaxHeight;
  end;

  procedure ToBottom(var _Re: TRect; _MinHeight, _MaxHeight: Integer);
  begin
    _Re.Top := _Re.Top + _Re.Height div 2;
    if _Re.Height < _MinHeight then
      _Re.Top := _Re.Bottom - _MinHeight;
    if (_MaxHeight > 0) and (_Re.Height > _MaxHeight) then
      _Re.Top := _Re.Bottom - _MaxHeight;
  end;

  procedure ToLeft(var _Re: TRect; _MinWidth, _MaxWidth: Integer);
  begin
    _Re.Right := _Re.Left + _Re.Width div 2;
    if _Re.Width < _MinWidth then
      _Re.Right := _Re.Left + _MinWidth;
    if (_MaxWidth > 0) and (_Re.Width > _MaxWidth) then
      _Re.Right := _Re.Left + _MaxWidth;
  end;

  procedure ToRight(var _Re: TRect; _MinWidth, _MaxWidth: Integer);
  begin
    _Re.Left := _Re.Left + _Re.Width div 2;
    if _Re.Width < _MinWidth then
      _Re.Left := _Re.Right - _MinWidth;
    if (_MaxWidth > 0) and (_Re.Width > _MaxWidth) then
      _Re.Left := _Re.Right - _MaxWidth;
  end;

  function TryMonitorFromPoint(_pnt: TPoint; out _Display: TDisplay): boolean;
  var
    i: Integer;
    Display: TDisplay;
  begin
    Result := False;
    for i := 0 to Screen.DisplayCount - 1 do begin
      Display := Screen.Displays[i];
      Result := Display.WorkArea.Contains(_pnt);
      if Result then begin
        _Display := Display;
        Exit;
      end;
    end;
  end;

type
  TDummyConstraints = record
    MinWidth, MaxWidth: Integer;
    MinHeight, MaxHeight: Integer;
  end;
var
  re: TRect;
  Bounds: TRect;
  NewMonitor: TDisplay;
  Constraints: TDummyConstraints;
begin
  re := Screen.DisplayFromForm(_frm).WorkareaRect;
  Bounds := _frm.Bounds;
  Constraints.MinWidth := 0;
  Constraints.MaxWidth := 0;
  Constraints.MinHeight := 0;
  Constraints.MaxHeight := 0;
  case _Position of
    dwpTop: begin
        ToTop(re, Constraints.MinHeight, Constraints.MaxHeight);
        if re = Bounds then begin
          if TryMonitorFromPoint(Point((re.Left + re.Right) div 2, re.Top - re.Height div 2), NewMonitor) then begin
            re := NewMonitor.WorkareaRect;
            ToBottom(re, Constraints.MinHeight, Constraints.MaxHeight);
          end;
        end;
      end;
    dwpBottom: begin
        ToBottom(re, Constraints.MinHeight, Constraints.MaxHeight);
        if re = Bounds then begin
          if TryMonitorFromPoint(Point((re.Left + re.Right) div 2, re.Bottom + re.Height div 2), NewMonitor) then begin
            re := NewMonitor.WorkareaRect;
            ToTop(re, Constraints.MinHeight, Constraints.MaxHeight);
          end;
        end;
      end;
    dwpLeft: begin
        ToLeft(re, Constraints.MinWidth, Constraints.MaxWidth);
        if re = Bounds then begin
          if TryMonitorFromPoint(Point(re.Left - re.Width div 2, (re.Top + re.Bottom) div 2), NewMonitor) then begin
            re := NewMonitor.WorkareaRect;
            ToRight(re, Constraints.MinWidth, Constraints.MaxWidth);
          end;
        end;
      end;
    dwpRight: begin
        ToRight(re, Constraints.MinWidth, Constraints.MaxWidth);
        if re = Bounds then begin
          if TryMonitorFromPoint(Point(re.Right + re.Width div 2, (re.Top + re.Bottom) div 2), NewMonitor) then begin
            re := NewMonitor.WorkareaRect;
            ToLeft(re, Constraints.MinWidth, Constraints.MaxWidth);
          end;
        end;
      end;
    dwpTopLeft: begin
        ToTop(re, Constraints.MinHeight, Constraints.MaxHeight);
        ToLeft(re, Constraints.MinWidth, Constraints.MaxWidth);
      end;
    dwpTopRight: begin
        ToTop(re, Constraints.MinHeight, Constraints.MaxHeight);
        ToRight(re, Constraints.MinWidth, Constraints.MaxWidth);
      end;
    dwpBottomLeft: begin
        ToBottom(re, Constraints.MinHeight, Constraints.MaxHeight);
        ToLeft(re, Constraints.MinWidth, Constraints.MaxWidth);
      end;
    dwpBottomRight: begin
        ToBottom(re, Constraints.MinHeight, Constraints.MaxHeight);
        ToRight(re, Constraints.MinWidth, Constraints.MaxWidth);
      end;
  end;
  _frm.Bounds := re;
end;

As you can see, the implementation is very similar to the VCL implementation. With a bit of effort I could probably make them nearly indistinguishable.

It’s in dzlib, in unit u_dzFmxUtils

Now, that was the easy part: Moving the form. The hard part is hooking the form in a way so all I need to do is call TForm_ActivatePositioning as in the VCL. No idea yet on how to accomplish that, but I’m just getting started with Firemonkey.

Snapping windows to monitor halves / quadrants revisited

 Delphi, dzLib  Comments Off on Snapping windows to monitor halves / quadrants revisited
Jun 182016
 

In my last post I talked about snapping windows to monitor halves and quadrants. I have been using that code for a few days and found it has a few shortcomings:

  1. If a window has size constraints, these will still be respected (which is good) but this will result in the window not being moved correctly. Snapping to the left and top half works fine, but snapping to the right or bottom half will move part of the window outside the active monitor.
  2. If a window has size constraints, moving from the left half of the right hand side monitor to the right half of left hand side monitor will not work. the same applies from the top half of the bottom monitor to the bottom half of the top monitor.
  3. It’s a bit inconvenient to move a window from a quadrant of one monitor to a quadrant of a different monitor. E.g. sometimes I want to move a window from the top right quadrant of the right hand side monitor to the top right quadrant of the left hand side monitor and vice versa. In order to do that I have to press Ctrl+Alt+Left (moves it to the left half of that monitor) Ctrl+Alt+Left (moves it to the right half of the other monitor) Ctrl+Alt+PgUp (finally moves it to the top right quadrant of that monitor). It would be nice to accomplish this
    1. with less key strokes
    2. without resizing the window

    Currently I am leaning towards Ctrl+Alt+PgUp moving the window to the top right quadrant of the same monitor and pressing it again moving it to the same quadrant of the monitor to the right of that monitor, or if that doesn’t exist, to the monitor above. But I’m not yet sure about the order of monitors here. If you want to voice your opinion, use my corresponding Google+ post

The fix for the constraints issues isn’t that difficult: Just read the form’s constraints and adjust the position accordingly. Here is the new code:

procedure TForm_MoveTo(_frm: TCustomForm; _Position: TdzWindowPositions);

  procedure ToTop(var _Re: TRect; _MinHeight, _MaxHeight: Integer);
  begin
    _Re.Bottom := _Re.Top + TRect_Height(_Re) div 2;
    if TRect_Height(_Re) < _MinHeight then
      _Re.Bottom := _Re.Top + _MinHeight;
    if (_MaxHeight > 0) and (TRect_Height(_Re) > _MaxHeight) then
      _Re.Bottom := _Re.Top + _MaxHeight;
  end;

  procedure ToBottom(var _Re: TRect; _MinHeight, _MaxHeight: Integer);
  begin
    _Re.Top := _Re.Top + TRect_Height(_Re) div 2;
    if TRect_Height(_Re) < _MinHeight then
      _Re.Top := _Re.Bottom - _MinHeight;
    if (_MaxHeight > 0) and (TRect_Height(_Re) > _MaxHeight) then
      _Re.Top := _Re.Bottom - _MaxHeight;
  end;

  procedure ToLeft(var _Re: TRect; _MinWidth, _MaxWidth: Integer);
  begin
    _Re.Right := _Re.Left + TRect_Width(_Re) div 2;
    if TRect_Width(_Re) < _MinWidth then
      _Re.Right := _Re.Left + _MinWidth;
    if (_MaxWidth > 0) and (TRect_Width(_Re) > _MaxWidth) then
      _Re.Right := _Re.Left + _MaxWidth;
  end;

  procedure ToRight(var _Re: TRect; _MinWidth, _MaxWidth: Integer);
  begin
    _Re.Left := _Re.Left + TRect_Width(_Re) div 2;
    if TRect_Width(_Re) < _MinWidth then
      _Re.Left := _Re.Right - _MinWidth;
    if (_MaxWidth > 0) and (TRect_Width(_Re) > _MaxWidth) then
      _Re.Left := _Re.Right - _MaxWidth;
  end;

  function SamePoint(const _pnt1, _pnt2: TPoint): Boolean;
  begin
    Result := (_pnt1.X = _pnt2.X) and (_pnt1.Y = _pnt2.Y);
  end;

  function SameRect(const _re1, _re2: TRect): Boolean;
  begin
    Result := SamePoint(_re1.TopLeft, _re2.TopLeft) and SamePoint(_re1.BottomRight, _re2.BottomRight);
  end;

var
  re: TRect;
  Bounds: TRect;
  NewMonitor: TMonitor;
  Constraints: TSizeConstraints;
begin
  re := _frm.Monitor.WorkareaRect;
  Bounds := _frm.BoundsRect;
  Constraints := _frm.Constraints;
  case _Position of
    dwpTop: begin
        ToTop(re, Constraints.MinHeight, Constraints.MaxHeight);
        if SameRect(re, Bounds) then begin
          NewMonitor := MonitorFromPoint(Point((re.Left + re.Right) div 2, re.Top - TRect_Height(re) div 2));
          if Assigned(NewMonitor) then begin
            re := NewMonitor.WorkareaRect;
            ToBottom(re, Constraints.MinHeight, Constraints.MaxHeight);
          end;
        end;
      end;
    dwpBottom: begin
        ToBottom(re, Constraints.MinHeight, Constraints.MaxHeight);
        if SameRect(re, Bounds) then begin
          NewMonitor := MonitorFromPoint(Point((re.Left + re.Right) div 2, re.Bottom + TRect_Height(re) div 2));
          if Assigned(NewMonitor) then begin
            re := NewMonitor.WorkareaRect;
            ToTop(re, Constraints.MinHeight, Constraints.MaxHeight);
          end;
        end;
      end;
    dwpLeft: begin
        ToLeft(re, Constraints.MinWidth, Constraints.MaxWidth);
        if SameRect(re, Bounds) then begin
          NewMonitor := MonitorFromPoint(Point(re.Left - TRect_Width(re) div 2, (re.Top + re.Bottom) div 2));
          if Assigned(NewMonitor) then begin
            re := NewMonitor.WorkareaRect;
            ToRight(re, Constraints.MinWidth, Constraints.MaxWidth);
          end;
        end;
      end;
    dwpRight: begin
        ToRight(re, Constraints.MinWidth, Constraints.MaxWidth);
        if SameRect(re, Bounds) then begin
          NewMonitor := MonitorFromPoint(Point(re.Right + TRect_Width(re) div 2, (re.Top + re.Bottom) div 2));
          if Assigned(NewMonitor) then begin
            re := NewMonitor.WorkareaRect;
            ToLeft(re, Constraints.MinWidth, Constraints.MaxWidth);
          end;
        end;
      end;
    dwpTopLeft: begin
        ToTop(re, Constraints.MinHeight, Constraints.MaxHeight);
        ToLeft(re, Constraints.MinWidth, Constraints.MaxWidth);
      end;
    dwpTopRight: begin
        ToTop(re, Constraints.MinHeight, Constraints.MaxHeight);
        ToRight(re, Constraints.MinWidth, Constraints.MaxWidth);
      end;
    dwpBottomLeft: begin
        ToBottom(re, Constraints.MinHeight, Constraints.MaxHeight);
        ToLeft(re, Constraints.MinWidth, Constraints.MaxWidth);
      end;
    dwpBottomRight: begin
        ToBottom(re, Constraints.MinHeight, Constraints.MaxHeight);
        ToRight(re, Constraints.MinWidth, Constraints.MaxWidth);
      end;
  end;
  _frm.BoundsRect := re;
end;

It’s already in the dzlib repository on sourceforge, unit u_dzVclUtils.

Snapping windows to monitor halves / quadrants

 Delphi  Comments Off on Snapping windows to monitor halves / quadrants
Jun 072016
 

You probably know about the Windows 7+ feature to snap a window to the left or right side of the monitor via Windows+Left / Windows+Right hotkey. You might even know that Windows 10 extended this to snap a window to the top or bottom and even to one of the quadrants of your monitor. (I for one had read about that Windows 10 feature but had already forgotten it, since I don’t plan to upgrade in the near future.)

But what if you don’t have Windows 10? Can this be done programmatically? Of course it can! Just add appropriate Actions, assign the shortcuts to your form and write the code. Or set the form’s KeyPreview property to true and put the code into the FormKeyDown event handler. Unfortunately this requires some rather tiresome copy and paste plus clicking on the object inspector for each of the forms. And, of course, copy and paste is evil, because it violates the DRY principle: If you make a mistake once, you will have to correct it in all the copies you have made in the meantime.

Enter TForm_ActivatePositoning: A utility function which I have added to dzLib today:

Just by calling

TForm_ActivatePositioning(Self);

you can add this functionality to every form in every project you’ll ever write.

Ok, so what does it do?

It subclasses the window (that is: Replaces its WindowProc (Wow, I just learnt that there is a new approach to subclassing, must have missed that for a few years)) to intercept the CM_CHILDKEY message which is sent by the VCL whenever any child control of a form receives a WM_KEYDOWN message:

type
  TFormPositioningActivator = class(TWindowProcHook)
  private
    FModifier: TShiftState;
    procedure CmChildKey(var _Msg: TMessage);
    function TheForm: TForm;
  protected
    procedure NewWindowProc(var _Msg: TMessage); override;
  public
    constructor Create(_Form: TForm; _Modifier: TShiftState);
  end;

constructor TFormPositioningActivator.Create(_Form: TForm; _Modifier: TShiftState);
begin
  inherited Create(_Form);
  FModifier := _Modifier;
end;

procedure TFormPositioningActivator.CmChildKey(var _Msg: TMessage);
var
  Key: Word;
begin
  Key := (_Msg.WParamLo and $FF);
  if GetModifierKeyState = FModifier then begin
    case Key of
      VK_LEFT, VK_NUMPAD4: TForm_MoveTo(TheForm, dwpLeft);
      VK_RIGHT, VK_NUMPAD6: TForm_MoveTo(TheForm, dwpRight);
      VK_UP, VK_NUMPAD8: TForm_MoveTo(TheForm, dwpTop);
      VK_DOWN, VK_NUMPAD2: TForm_MoveTo(TheForm, dwpBottom);
      VK_PRIOR, VK_NUMPAD9: TForm_MoveTo(TheForm, dwpTopRight);
      VK_NEXT, VK_NUMPAD3: TForm_MoveTo(TheForm, dwpBottomRight);
      VK_HOME, VK_NUMPAD7: TForm_MoveTo(TheForm, dwpTopLeft);
      VK_END, VK_NUMPAD1: TForm_MoveTo(TheForm, dwpBottomLeft);
    else
      Exit; //==> exit, so Result doesn't get set to 1
    end;
    _Msg.Result := 1;
  end;
end;

procedure TFormPositioningActivator.NewWindowProc(var _Msg: TMessage);
begin
  if _Msg.Msg = CM_CHILDKEY then
    CmChildKey(_Msg);

  inherited NewWindowProc(_Msg);
end;

function TFormPositioningActivator.TheForm: TForm;
begin
  Result := TForm(FCtrl);
end;

function TForm_ActivatePositioning(_Form: TForm; _Modifier: TShiftState = [ssCtrl, ssAlt]): TObject;
begin
  Result := TFormPositioningActivator.Create(_Form, _Modifier);
end;

It uses the existing TWindowProcHook class which is already used for drag&drop and autocomplete (wow, was that really two years ago?) so the actual code is quite simple, as you can see above.

I did not use the Windows key as modifier but rather Ctrl+Alt because Microsoft says we should not use the Windows key. But I’m not quite sure about this. Maybe I could instead check for the Windows version to see if those hotkeys are already available and only add those that aren’t, using the Windows key.

But for now, the following hotkeys are implemented:

  • Ctrl+Alt+Up – snap window to the top of the monitor, or if already there, to the bottom of the monitor above it
  • Ctrl+Alt+Down – same for the bottom of the monitor
  • Ctrl+Alt+Left – same for the left of the monitor
  • Ctrl+Alt+Right – same for the left of the monitor
  • Ctrl+Alt+Home – snap the window to the upper left quadrant of the monitor
  • Ctrl+Alt+End – same for the lower left quadrant
  • Ctrl+Alt+PgUp – same for the upper right quadrant
  • Ctrl+Alt+PgDown- same for the lower right quadrant

Those hotkeys also work for the keys on the numeric keypad, regardless whether NumLock is active or not. If you look at your keyboard you will notice why I choose these particular keys: The nicely correspond to the quadrants / halves of the monitor and should it easier to memorize them.

Numpad
(Picture courtesy of Wikipedia.)

GExperts 1.38 experimental twm 2016-06-05

 Delphi, GExperts  Comments Off on GExperts 1.38 experimental twm 2016-06-05
Jun 052016
 

This is another test release before Erik is going to do an official 1.39 release. Please report any bugs you may find (preferentially in the GExperts community on Google+ or the bug tracker on SourceForge)

Again, I have built installers for each Delphi version. These installers should install everything that is necessary, including the files for the Code Formatter.

Erik and I fixed various bugs and added a few minor features:

  • The Backup Project Expert now uses the current project name for the ZIP file (contributed by xander xiao)
  • Again the Backup Project Expert now has an option to recourse into subdirectories, optionally ignoring __history and __restore, and can ignore backup files (*.~*). Also it ignores .svn, .git and .hg subdirs.
  • The preview in the Code Formatter configuration dialog now uses SynEdit for syntax highlighting
  • The To Do Expert now reads the numeric todo priority (e.g. specified in the Delphi Edit-Todo dialog). We no longer need the “todo 1” .. “todo 5” tokens.
    GExperts-ToDo-Expert
  • The Editor Popup Menu configuration dialog no longer allows duplicates (contributed by Achim Kalwa)
  • Yet another unicode bug was fixed, this time in the Macro Templates Expert (contributed by Denis Bisson)
  • There is now a stand alone version of the PE Information Expert which, as all the existing stand alone versions of experts requires the GExperts DLL. It can be called with a filename as parameter and also supports drag & drop.

Here are the links:

Displaying huge text files

 Delphi  Comments Off on Displaying huge text files
May 292016
 

Sometimes programmers have to deal with larger than usual text files. Examples are log files or XML dumps of databases. For my tests, I used a dump of the English Wikipedia in XML format. This file is 48 gigabytes in size and as I found out only today contains 789,577,286 lines of text.

dzLargeTextViewer-Line708754410

If you google for “large text viewer” you get quite a few hits, but many of these are not what they advertise.

Others kind of work but have various shortcomings, e.g. Large Text File Reader which allows you to only display the first n lines of a file. But don’t try to enter too large a number because it will then just hang. Then there is Log Expert which was suggested by this answer on StackOverflow but apparently tries to load the whole file into memory, nearly crashing my Windows installation.

I found the following viewers which seem to work:

LTFViewer from the now defunct swiftgear.com site (Link therefore goes to archive.org). This works but seems to be rather slow in reading the file. Closed it after about an hour when it had read about 200 million lines.

glogg which is a log file viewer with searching and filtering. It’s multi platform which usually means that the Windows version is barely usable. Glogg is not too bad on this account. It took quite a while loading the file which ran in the background. Unfortunately while it is loading the file, you cannot browse it. It only displays the first page, scrolling is impossible. You can use the filter function though. It took about one hour to read the 48 GB file using 1 GB of memory in the process. Once it has loaded it, it supports e.g. incremental search, which, in a file of this size takes quite a while.

Another option is a file viewer written in JavaScript at www.readfileonline.com. I had my doubts about this but since it works locally and browses the file in batches of a given size, it can be very fast. It even has a search function, but of course that one has limits when talking about a file of 48 gigabytes.

Yours truly also wrote such a tool, back when I received a huge XML file from a customer, also a database dump, and had to resort to Linux and the less command line tool to view it. dzLargeTextViewer doesn’t have many features. It theoretically allows you to display files up to MaxInt64 lines. I have tested it with the aforementioned Wikipedia dump. The first version ran out of memory after around 75 million lines. The second version used 1 GB of memory and could index about 300 million lines. The third version now has read all nearly 790 million lines and displays them, using only 3.5 MB of memory. To achieve that, it created a 6 GB index file. It stores that file as <Filename>.LineIndex so it can be reused when the same file is opened again. My tool assumes ansi strings, though, while glogg reads the file as UTF-8.

Clearing a TTreeView

 Delphi  Comments Off on Clearing a TTreeView
May 282016
 

Note to self: If you want to clear the items that were added at design time to a TTreeView, you must make sure it has a handle. The following does not work (in Delphi 2007):

constructor TForm1.Create(Owner: TCoponent);
begin
  inherited;
  TreeView1.Items.Clear;
end;

Adding a TreeView1.HandleNeeded makes it work:

constructor TForm1.Create(Owner: TCoponent);
begin
  inherited;
  TreeView1.HandleNeeded
  TreeView1.Items.Clear;
end;

Known IDE Packages in Delphi

 Delphi  Comments Off on Known IDE Packages in Delphi
May 262016
 

Prompted by a comment in this Google+ post I had a look at what is actually listed in [HKEY_CURRENT_USER\Software\Borland|Codegear|Embarcadero\BDS|Delphi\#.0\Known IDE Packages] and found some interesting entries:

In Delphi 6 and 7 there is direct60.bpl and direct70.bpl which can probably simply be removed nowadays. The server used by “Delphi Direct” which was the equivalent of the current “Welcome Page” no longer exists. Removing this package results in the Environment Options dialog having one less tab:

DelphiDirect

I never understood the purpose of Delphi Direct. Even back in the 1990ies there wasn’t any interesting content delivered to it. In my opinion it was totally wasted by Borland.

Then there is delphiclxide60.bpl and delphiclxide70.bpl. These packages apparently contain the designer for CLX applications (anybody remember theses?). When removed, there are still some CLX items in the File->New->Other menu and these can still be used, but you will no longer be able to visually design these forms and dialogs. Also the File->New->CLX Application entry will be gone. In Delphi 6 this results in some designtime packages not being able to load, in Delphi 7 it doesn’t but that might just be because I removed them earlier.

CLXNew

Since I doubt that anybody is still developing CLX applications, it’s probably safe to remove these entries as well.

Moving on to the “modern” IDE, which was introduced with Delphi 8 (which I don’t own, so I’ll look at Delphi 2005 instead):

The startpageide90.bpl package is the equivalent of Delphi Direct mentioned above. It is still there in Delphi 10.1 Berlin as startpageide230.bpl. It has been suggested to remove this package because it is hardly useful. I haven’t done that because my delphi7help4bds expert uses it to display HTML content.

There is caliberide90.bpl or Borland.Caliber.IDE100.bpl as it is called in Delphi 2006. After removing it in Delphi 2006 I found nothing missing. Caliber apparently is a requirement management tool now owned by MicroFocus (who acquired Borland after they spun off their development tools as CodeGear). I’m not sure what exactly it does or did in Delphi 2005 and 2006 but it is no longer there in Delphi 2007 and later. The same applies to TGIDE90.BPL which is the “Together IDE integration”. It’s the UML modelling tool Borland acquired in 2003.

Another interesting entry is historyide90.bpl. It apparently implements the history tab (at the bottom of the editor, to the right of the Code and Design tabs).

HistoryTab

It’s still there in Delphi 10.1 Berlin.

Some other observation: Starting with Delphi 2006 the key “Known IDE Packages” has subkeys, one of them is “Delphi”, others, that were in use at some time are:

  • CSharp
  • DelphiDotNet
  • CBuilder

There might have been others but I recently stopped installing anything but the Delphi personality even though my (employer’s) subscription is on Rad Studio just in case.

I’ll stop here with listing those packages. Just some comment about enabling and disabling them:

Apparently they don’t get loaded if the entry does not have a value. Some of the packages have a value of (Untitled) which apparently means “nobody bothered to give them a name but we need a value so we added something generic”. If you remove this value (set it to an empty string) the package will no longer be loaded. But it’s probably not a good idea to disable them by removing the value. I did my tests by creating a new “unused” subkey and moving these packages there, including the description.

I’ll probably write another post on this topic at a later time. I’m also thinking about writing a tool to disable/enable entries in Known IDE Packages.