2006. november 14., kedd

How to set a TEdit or TMemo to overwrite instead of insert


Problem/Question/Abstract:

How to set a TEdit or TMemo to overwrite instead of insert

Answer:

Solve 1:

You have to fake it because the control does not natively support overtype mode. Provide overtype capability for edits and memos:

procedure TScratchMain.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
  if (Sender is TCustomEdit) and Odd(GetKeyState(VK_INSERT)) then
    with TCustomEdit(Sender) do
      if SelLength = 0 then
        case Key of
          ' '..#126, #128..#255:
            begin
              SelLength := 1;
              if (SelLength > 0) and (SelText[1] = #13) then
                SelLength := 2;
            end;
        end;
end;

With this handler the control will start out in insert mode since the state of VK_INSERT is not toggled by default. Pressing it once will toggle the key and put the control in overtype mode. If you want it to start out in overtype, use "not Odd(...)" in the If statement.

Solve 2:

I managed to simulate it by doing this (you need to declare the FOverwrite: boolean somewhere in the form):

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
type
  TSmallPoint = packed record
    case integer of
      0: (x, y: Smallint);
      1: (long: integer);
  end;
var
  CaretPos: TPoint;
  sCaretPos: TSmallPoint;
begin
  if (FOverwrite) and (Edit1.SelLength = 0) then
  begin
    GetCaretPos(CaretPos);
    sCaretPos.x := CaretPos.x;
    sCaretPos.y := CaretPos.y;
    Edit1.SelStart := SendMessage(Edit1.Handle, EM_CHARFROMPOS, 0, sCaretPos.long);
    Edit1.SelLength := 1;
    Edit1.SelText := Key;
    Key := #0;
  end;
end;

procedure TForm1.Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  case Key of
    VK_INSERT: FOverwrite := not FOverwrite;
  end;
end;

2006. november 13., hétfő

How to start a search upon an [Enter] key press in a TEdit


Problem/Question/Abstract:

I have a series of 6 edit boxes that users type info in that are then passed to params in my SQL TQuery. The search query is started by clicking on a button. However, users have asked that if they type in one of the edit boxes and then press 'Enter' that the system searches. I can use Key Press event to trigger it and then if key = #13 to make sure its the enter key but then i want it to trigger the procedure that does the search, usually triggered by the tool button. Any ideas?

Answer:

Solve 1:

The best solution is to use actions, which I'll describe below. But if you don't want to use actions, do this:

Move your search procedure into a separate procedure, and then call that from both the toolbutton OnClick and edit OnKeyPress events, like this:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, ToolWin, StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    ToolBar1: TToolBar;
    ToolButton1: TToolButton;
    procedure ToolButton1Click(Sender: TObject);
    procedure Edit1KeyPress(Sender: TObject; var Key: Char);
  private
    {Private Declarations}
    procedure PerformSearch;
  public
    {Public declarations}
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.ToolButton1Click(Sender: TObject);
begin
  PerformSearch;
end;

procedure TForm1.PerformSearch;
begin
  { Do search here }
  ShowMessage('Search performed');
end;

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #13 then
  begin
    PerformSearch;
    Key := #0;
  end;
end;

end.

To use actions, place a TActionList component onto your form, then create an action called something like "SearchAction". Then assign SearchAction to the ToolButton's Action property. Finally, call the action's Execute method from the edit, like this:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, ToolWin, StdCtrls, ActnList;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    ToolBar1: TToolBar;
    ToolButton1: TToolButton;
    ActionList1: TActionList;
    SearchAction: TAction;
    procedure Edit1KeyPress(Sender: TObject; var Key: Char);
    procedure SearchActionExecute(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #13 then
  begin
    SearchAction.Execute;
    Key := #0;
  end;
end;

procedure TForm1.SearchActionExecute(Sender: TObject);
begin
  { Do search here }
  ShowMessage('Search performed');
end;

end.

Solve 2:

I'll go one further than Rick. All event handlers should only delegate (unless they're one line long in which case they are delegating). In other words if you have

procedure TForm1btnSearch.Click(Sender: TObject);
begin
  {...many lines of code that actually implement the search}
end;

Change this to:

procedure TForm1btnSearch.Click(Sender: TObject);
begin
  FindInformation;
end;

procedure TForm1.FindInformation;
begin
  {...many lines of code that actually implement the search.}
end;

There are, of course, exceptions to this rule, however, for the greater part, you will not do wrong to treat an event handler as a proxy rather than placing the code directly in it. For one thing, it makes it easier to move the domain code into a separate object, so you could end up with:

procedure TForm1btnSearch.Click(Sender: TObject);
begin
  MyInformationFinder.Execute;
end;

2006. november 12., vasárnap

Cancel that wrong drag operation


Problem/Question/Abstract:

Cancel that wrong drag operation

Answer:

Have you ever started moving a component while designing your form and realized you selected the wrong component?

No doubt, you can think of other instances when you would like to cancel a design-time drag operation you've already begun. Here's a tip that let's you do just that.

After you've begun the drag but before you release the mouse button, press the Esc key. The control will snap back to its original position!

2006. november 11., szombat

Manipulate shapes and inline shapes in Word


Problem/Question/Abstract:

I'm trying to insert a picture in a document and sent it to the back, with the text over the picture using automation.

Answer:

If Doc is a Word document:

{ ... }
var
  Pic: Word2000.Shape;
  Left, Top: OleVariant;
  { ... }

{To add a pic and make it appear behind text}
Left := 100;
Top := 100;
Pic := Doc.Shapes.AddPicture('C:\Small.bmp', EmptyParam, EmptyParam, Left, Top,
  EmptyParam, EmptyParam, EmptyParam);
Pic.WrapFormat.Type_ := wdWrapNone;
Pic.ZOrder(msoSendBehindText);
{To get a watermark effect}
Pic.PictureFormat.Brightness := 0.75;
Pic.PictureFormat.Contrast := 0.20;
{To make any white in a picture transparent}
Pic.PictureFormat.TransparencyColor := clWhite;
Pic.PictureFormat.TransparentBackground := msoTrue;
Pic.Fill.Visible := msoFalse;
{ ... }

2006. november 10., péntek

How to simulate combobox behaviour with a TEdit


Problem/Question/Abstract:

I'm trying to make a component that acts in certain cases like a combobox, i.e. when the user presses a button, a list box is shown and the user can select an item. The problem I have is that I need the list to hide itself whenever the user clicks the mouse outside the list (including clicks in non-windowed controls).

Answer:

One way you can do this is by listening for CM_CANCELMODE messages in the parent of your drop-down list (presumably an edit control or something similar). You will probably then have to work out whether the message originated from a click on the drop-down list or elsewhere. In the code below, FPopup points to the list component:

procedure TMyPopupEdit.CMCancelMode(var Message: TCMCancelMode);
var
  P: TPoint;
  R: TRect;
begin
  {Get the top-left coordinate of the Sender and see if it is within the popup
  control. If not, close the popup without changing the text in the edit box.}
  P.X := Message.Sender.Left;
  P.Y := Message.Sender.Top;
  R := Rect(0, 0, FPopup.Width, FPopup.Height);
  if not PtInRect(R, P) then
    if FPopupVisible then
      PopupCloseUp(FPopup, False);
end;

2006. november 9., csütörtök

How to implement the RPos


Problem/Question/Abstract:

Sometime Pos is not enough, because you need to find the position of the first character of a sub string in a string from the end of that string. There's the solution.

Answer:

function RPos(Substr: string; S: string): Integer;
var
  i: Integer;
begin
  Result := 0;
  if ((Length(S) > 0) and (Length(Substr) > 0)) then
    if (Length(S) >= Length(Substr)) then
      for i:= (Length(S) - Length(Substr)) downto 1 do
        if (Copy(S, i, Length(Substr)) = Substr) then
        begin
          Result := i;
          Exit;
        end;
end;

2006. november 8., szerda

How to set all tables linked to a TDatabase back to active


Problem/Question/Abstract:

Is there a way to set all tables linked to a particular TDatabase component back to active automatically?

Answer:

Not totally automatic but with just two lines of code. If you want certain tables that share DB1 to keep closed (shadow tables etc, only opened for special routines) use the Tag of the DataSets to determine which ones to open.

for i := 0 to pred(DB1.DataSetCount) do
  {if DataSets.Tag = 1 then}
  DataSets[i].Open;

2006. november 7., kedd

How to form a DDE link with a Netscape browser


Problem/Question/Abstract:

How to form a DDE link with a Netscape browser

Answer:

unit Netscp1;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, DdeMan;

type
  TForm1 = class(TForm)
    DdeClientConv1: TDdeClientConv;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    LinkStatus: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    URLName: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  LinkOpened: Integer;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if LinkOpened = 0 then
  begin
    DdeClientConv1.SetLink(' Netscape ', ' WWW_OpenURL ');
    if DdeClientConv1.OpenLink then
    begin
      LinkStatus.Text := ' Netscape Link has been opened ';
      LinkOpened := 1;
    end
    else
      LinkStatus.Text := ' Unable to make Netscape Link ';
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  LinkOpened := 0;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  DdeClientConv1.CloseLink;
  LinkOpened := 0;
  LinkStatus.Text := ' Netscape Link has been closed ';
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  ItemList: string;
begin
  if LinkOpened <> 0 then
  begin
    ItemList := URLName.Text + ', , 0xFFFFFFFF, 0x3, , ,';
    DdeClientConv1.RequestData(ItemList);
  end;
end;

end.

2006. november 6., hétfő

How to use Randomize so that the same value is not chosen more than once (2)


Problem/Question/Abstract:

Would you mind to make me a random procedure to change the background of my program in an interval of 15 seconds?

Answer:

The best would be to store the names in an array:

const
  CaImgs: array[0..9] of string = ('image1.jpg', 'image2.jpg', ...);

This way, on start-up, you can check that the images are there. Then, if you merely want a random image from the array, you do:

myFileName = CaImgs[random(10)];

This means that you have one chance out of ten of repeating the same image - no visible change. If you want to show always different images, but in random order, then you need a shuffle function (see above). To shuffle your array of filenames (despite being declared a constant, it's actually a var), you do this:

procedure shuffleImages;
var
  a: array[0..high(CaImgs)] of integer;
  j: integer;
  s: string;
begin
  for j := low(a) to high(a) do
    a[j] := j;
  shuffle(a, 0);
  for j := low(a) to high(a) do
  begin
    s := CaImgs[j];
    CaImgs[j] := CaImgs[a[j]];
    CaImgs[a[j]] := s;
  end;
end;

You do this once at application start. This way, the 10 images will show in random order (but the order will repeat throughout the current run).

In both cases (random of shuffle), you should call Randomize just once, at the start of the application.

2006. november 5., vasárnap

How to tell if a TPanel is moved outside the visible part of a TForm


Problem/Question/Abstract:

I use SC_DRAGMOVE so I can drag a TPanel around a form. Now, how do I tell the form when the panel is outside the form and the form should add some scrollbars?

Answer:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, StdCtrls, ExtCtrls;

type
  TPanel = class(ExtCtrls.TPanel)
  private
    procedure WMExitSizeMove(var message: TMessage); message WM_EXITSIZEMOVE;
  end;

  TForm1 = class(TForm)
    StatusBar: TStatusBar;
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    Label1: TLabel;
    ComboBox1: TComboBox;
    CheckBox1: TCheckBox;
    Panel1: TPanel;
    procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
const
  SC_DRAGMOVE = $F012;
begin
  Mouse.Capture := 0;
  sendmessage(panel1.handle, WM_SYSCOMMAND, SC_DRAGMOVE, 0);
end;

{ TPanel }

procedure TPanel.WMExitSizeMove(var message: TMessage);
begin
  Left := Left + 1;
  Left := Left - 1;
end;

end.

2006. november 4., szombat

One way to copy whole contents of an array into another array


Problem/Question/Abstract:

You have an array and you want to copy the value you have in it to another array.

Answer:

I just use a button to se if it compile and if I get an Error when I click it. There ar other ways, like copy an array in a for loop, but then you have to know how big it is. If you handle different arrays in an application and need to copy them into one, this is the way. As you can se, it works with just one index of the array too.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    Array1: array[0..40] of Integer;
    Array2: array[0..100] of Integer;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Move(Array1, Array2, SizeOf(Array1)); {Moves/Copy the Array1 to Array2.}
end;

end.

2006. november 3., péntek

Trapping Messages Sent to an Application


Problem/Question/Abstract:

I wrote code for the OnMessage event handler of Application object to trap all Windows messages sent to my application, but it doesn't seem to fire on all messages. Is there a way to trap all messages sent to my application?

Answer:

There sure is. And the answer to this "problem" is amazingly simple. But before I go into trapping messages at the application level, I should probably discuss some mechanics.

TApplication's "Hidden" Window
It's not a commonly known fact that the default Application object creates a hidden window when your application is started. But you can seen evidence of this by creating a new application saving it, then running it (make sure you don't rename anything - just keep the main form as "Form1" and the project as "Project1). When you run the application, you'll notice that the caption bar for your main form says, "Form1" while the icon displayed on the task bar says "Project1." That icon represents the application's hidden window, and it affects your program in many ways, especially when you're trying to handle messages sent to your application.
Delphi surfaces the OnMessage event for the Application object. The OnMessage event handler is "supposed" to allow you trap every message sent to your application. But there's a problem with this: OnMessage will only fire when there's something in the Application object's message queue. These messages are typically window management messages such as WM_PAINT or messages sent to the application from Windows through PostMessage, Broadcast or SystemMessage . However, messages sent directly to a window using SendMessage bypass the Application object's message queue, so OnMessage doesn't fire for those types of situations.
Some of you more familiar with handling windows messages might think that a solution to the problem above might be to override the WndProc method for the Application object. Unfortunately, that's not possible because TApplication's WndProc method is not only private, it's also declared as a static method which means it's not overrideable. So it's not only invisible, you can't create a TApplication subclass to override WndProc (not that you'd want either). But that doesn't mean that you can't get to the WndProc method using alternative means.

"Hooking" All Messages
Even though WndProc is all but closed to direct subclassing, TApplication does include a method called HookMainWindow that allows you to insert your own message handler at the top of WndProc to intercept messages sent to your application before they're handled by the Application object. This is convenient for all developers, and solves the problem of trapping any message sent to your application.
HookMainWindow is declared under TApplication as follows:

procedure HookMainWindow(Hook: TWindowHook);

Notice that HookMainWindow takes one parameter, Hook of type TWindowHook. TWindowHook is a method pointer type that's defined like so:

type
  TWindowHook = function(var Message: TMessage): Boolean of object;

Since TWindowHook is a method pointer, you can define your own method as the hook function as long as it follows the nomenclature defined for TWindowHook. Notice that the return value of the function is of type Boolean. This is the equivalent of the "Handled" parameter of OnMessage. If your function handles a particular message, you'd return true. This will be passed back to the Application's WndProc and message processing for that message will be terminated. Otherwise, you'd return False. Here's an example method:

function TForm1.AppHookFunc(var Message: TMessage): Boolean;
begin
  Result := False; //I just do this by default
  if Message.Msg = WM_ < SomethingOrOther > then
  begin
    ...DoSomething...
      Result := True;
  end;
end;

Okay, now that we've set up everything, we need to make the application hook the messages. This can be done in the main form's OnCreate method:

function TForm1.FormCreate(Sender: TObject);
begin
  HookMainWindow(AppHookFunc);
end;

I should mention that you need to clear the hook using, you guessed it, UnHookMainWindow, after you're done using it, and this can be done in the OnDestroy for the main form:

function TForm1.FormDestroy(Sender: TObject);
begin
  UnHookMainWindow(AppHookFunc);
end;

Okay, disgustingly simple. But I feel the best things in life are those that give maximum satisfaction for the least amount of cost (please don't read ANYTHING into that <G>). So, now you've got the tools to create your own message "hooker" (sorry, had to do that at least once). Until next time...

2006. november 2., csütörtök

How to convert decimal numbers to fractions


Problem/Question/Abstract:

I'm looking for a function that I can pass in a decimal and return a fraction.

Answer:

Solve 1:

The "Denominators" parameter is an array of potential denominators that would be acceptable. For example, to get a fractional inch dimension with a power of 2 denominator, you'd pass [2, 4, 8, 16, 32] for that parameter, and the function will figure out which potential denominator will work best.

function ConvertFloatToFraction(const Value: Double;
  const Denominators: array of Integer): string;
var
  Index: Integer;
  TempDelta: Double;
  MinDelta: Double;
  TempNumerator: Integer;
  FracValue: Double;
  Numerator: Integer;
  Denominator: Integer;
  IntValue: Integer;
begin
  IntValue := Trunc(Value);
  FracValue := Abs(Frac(Value));
  MinDelta := 0;
  Numerator := 0;
  Denominator := 0;
  for Index := 0 to High(Denominators) do
  begin
    TempNumerator := Round(FracValue * Denominators[Index]);
    TempDelta := Abs(FracValue - (TempNumerator / Denominators[Index]));
    if ((Index = 0) or (TempDelta < MinDelta)) then
    begin
      MinDelta := TempDelta;
      Numerator := TempNumerator;
      Denominator := Denominators[Index];
    end;
  end;
  if (Numerator = Denominator) then
  begin
    IntValue := IntValue + Sign(IntValue);
    Numerator := 0;
  end;
  Result := '';
  if ((IntValue <> 0) or (Numerator = 0)) then
    Result := IntToStr(IntValue);
  if ((IntValue <> 0) and (Numerator <> 0)) then
    Result := Result + ' ';
  if (Numerator <> 0) then
    Result := Result + IntToStr(Numerator) + '/' + IntToStr(Denominator);
end;


Solve 2:

This function takes the number to convert, the fraction scale you want returned such as 8 for eighths or 10 for tenths, etc. and a boolean to tell it to round up or down the nearest fraction. It returns a string with the integer portion, a space and then the fraction portion. It will also reduce the fraction to the smallest common denominator. You can use the ErrorFactor variable to adjust the percentage of when to consider a number close enough to the next level to be close enough. I use 4 percent of the fractional scale value.

function ToFraction(num: double; scale: integer; RoundUp: boolean): string;

{Function to find greatest common denominator}
  function GCD(A, B: integer): integer;
  begin
    if (B mod A) = 0 then
      result := A
    else if (B mod A) = 1 then
      result := 1
    else
      result := GCD((B mod A), A);
  end;

var
  x, y: integer;
  ScaleFrac,
    NumFrac,
    ErrorFactor: double;
begin
  ScaleFrac := 1 / scale;
  NumFrac := Frac(Num);
  ErrorFactor := ScaleFrac * 0.04; {error factor of 4 percent}
  x := 0;
  while (((x + 1) * ScaleFrac) < (NumFrac + ErrorFactor)) do
    inc(x);
  if RoundUp then
    if (((((x + 1) * ScaleFrac) - NumFrac) / 2) > (ScaleFrac / 2)) then
      inc(x);
  if (x = 0) then {no fraction, just the integer portion}
  begin
    result := IntToStr(Trunc(Num))
  end
  else
  begin {reduce the fraction as much as possible}
    y := GCD(x, scale);
    while (y <> 1) do
    begin
      x := x div y;
      scale := scale div y;
      y := GCD(x, scale);
    end;
    result := IntToStr(Trunc(Num)) + ' ' + IntToStr(x) + '/' + IntToStr(scale);
  end;
end;

2006. november 1., szerda

Create 32bit string resources with 16bit Resource Workshop


Problem/Question/Abstract:

Create 32bit string resources with 16bit Resource Workshop

Answer:

There is an easy way to do this (besides buying Resource Workshop version 4 :-)

create your strings as 16bit resource with Resource Workshop
store them as a *.RC text file (instead of *.RES)
compile the text file with BRCW32 (command line tool: BRC32) into a 32bit *.RES file