test

progging - To wander about and beg; to seek food or other supplies by low arts; to seek for advantage by mean shift or tricks.
progging - Programmer slang for writing computer code.
Viser innlegg med etiketten Delphi. Vis alle innlegg
Viser innlegg med etiketten Delphi. Vis alle innlegg

mandag 21. januar 2013

Multiple TFrame's on a form

I got a run-time error in my application when I had added two instances of the same TFrame on the same form. The error message looked like this:
A component named MyFrame already exists

Fair enough, but the thing was that only one of the frames was called 'MyFrame'. The other one was called 'MyFrame2'.

Apparently, the problem is that one of the two frames on my form using the default name as it is in unit that declares the frame TMyFrame.

So, if you only uses one frame of type TMyFrame on the form, it is okay to call it MyFrame, but if you have two ore more, none of them can be called MyFrame.

Lesson learned!

søndag 11. september 2011

Delphi: General method to check if key is pressed

To check if a specific key is pressed you can use the GetKeyState method.

type
  TUtility = class
  public
    class function CheckKeyPressed(key: Integer): Boolean;
    class function ShiftKeyPressed(): Boolean;
    class function CtrlKeyPressed(): Boolean;
    class function AltKeyPressed(): Boolean;
  end
One method can check any key you want using virtual key codes:
// Helper methods to check various key states
class function TUtility.CheckKeyPressed(key: Integer): Boolean;
begin
  //The return value of GetKeyState specifies the status of the specified virtual key, as follows:
  //
  //If the high-order bit is 1, the key is down; otherwise, it is up.
  //If the low-order bit is 1, the key is toggled. A key, such as the CAPS LOCK key, is toggled
  //if it is turned on.
  // The key is off and untoggled if the low-order bit is 0. A toggle key's indicator light (if any)
  // on the keyboard will be on when the key is toggled, and off when the key is untoggled.
  //
  // Check high-order of state
  Result := (GetKeyState(key) and $80) <> 0;
end;
...and specific methods for the most interesting keys can be made:
// Helper methods to check various key states
class function TUtility.ShiftKeyPressed(): Boolean;
begin
  Result := CheckKeyPressed(VK_SHIFT);
end;

class function TUtility.CtrlKeyPressed(): Boolean;
begin
  Result := CheckKeyPressed(VK_CONTROL);
end;

class function TUtility.AltKeyPressed(): Boolean;
begin
  Result := CheckKeyPressed(VK_MENU);
end;

tirsdag 6. september 2011

Define your custom TColor type in Delphi

Do define your own custom color in Delphi, do this:

const
  clChartRed = TColor($B3B3FF);       // 70 % gradient Red on White
  clChartYellow = TColor($B3FFFF);    // 70 % gradient Yellow on White
But remember that Delphi defines the colors as BGR and not the usual RGB so remember to swap numbers.
So the color red would be:
const
  clRed = TColor($0000FF);       // Red

tirsdag 30. august 2011

Delphi Enumerable List - example

Just an example of a list of a specific type in delphi.
unit MyUtility;

interface

uses
  Classes,  {TList}
  Chart;    {TChart}

type
  TChartList = class;
  TChartListEnumerator = record
  private
    FIndex: Integer;
    FList: TChartList;
  public
    constructor Create(AList: TChartList);
    function MoveNext: Boolean; inline;
    function GetCurrent: TChart; inline;
    property Current: TChart read GetCurrent;
  end;
  TChartList = class(TList)
  public
    procedure Add(AChart: TChart);
    function GetItem(i:Integer): TChart;
    function GetEnumerator: TChartListEnumerator;
    property Items[i:Integer]: TChart read GetItem; default;
  end;

implementation

{TChartList}

procedure TChartList.Add(AChart: TChart);
begin
  inherited Add(AChart);
end;

function TChartList.GetItem(i:Integer): TChart;
begin
  Result := List[i];
end;

function TChartList.GetEnumerator: TChartListEnumerator;
begin
  Result := TChartListEnumerator.Create(Self);
end;

{ TChartListEnumerator }

constructor TChartListEnumerator.Create(AList: TChartList);
begin
  FIndex := -1;
  FList := AList;
end;

function TChartListEnumerator.GetCurrent: TChart;
begin
  Result := FList.List[FIndex];
end;

function TChartListEnumerator.MoveNext: Boolean;
begin
  Result := FIndex < FList.Count - 1;
  if Result then
    Inc(FIndex);
end;

end.
And it's use:
var
  Chart: TChart;
begin
  for Chart in ChartList do begin
    Chart.DoSomeThing();
  end;

mandag 29. august 2011

How to clone a Delphi Form

In Visual Studio 2005 it's pretty easy to make a copy of an existing from directly from the project manager in the IDE (simply copy-paste with Ctrl-C and Ctrl-V). This is of course very useful instead of starting from scratch when you have a form that looks a lot like your new form will do.
The trick to do this in Delphi is:

  1. Do a "Save As" of the form in the file menu (.pas and .dfm will be copied and the unit name will be changed)
  2. Edit the saved as form to change the class name (easiest to do it in the designer)
  3. Add back the old form using "Add to Project...(Shift + F11)

tirsdag 23. august 2011

Fun with Tooltip - Delphi THintWindow

On most occasions getting a tooltip on a control is as simple as clicking enable and typing a string.

In the case of TeeChart you can get a tooltip on a Series by adding a tool called "Mark Tips".

After assigning this to a series, a tooltip will automatically show up when you hold the mouse over the series. BUT, It will not show up when holding the mouse over the series' mark, which is what I needed. So then I had to implement the tooltip functionality my self.



I used the THintWindow class to show a tooltip that looks the same as the other tooltips, although I had to set the (background) color property to clInfoBk in order for it to look the same. To find out what part of the chart the mouse was in I used the TeeChart function procedure TCustomChart.CalcClickedPart(Pos: TPoint; Var Part: TChartClickedPart);. This will return a TChartClickedPart record that will give you a lot of useful information, like if its a Series or SeriesMarks etc. It will the also have a reference to the series itself as well as the index of the series.

I used this method to show tooltip on both the SeriesMarks and the Series, so I could skip using the "Mark Tips" tool from TeeChart.

The THintWindow was okay to use after reading the documentation. It has methods for calculating it's necessary width and height. It was too tricky to to find the size of the cursor so that it could be positioned at the bottom of the mouse cursor (like is normally done) so I simply placed it on top of the mouse cursor.
Following is the code that is called on the MouseMove event:


procedure TFrame_Plot.ShowEventSeriesToolTip();
var
  point: TPoint;
  clickedPart: TChartClickedPart;
  labelString: string;
  rect: TRect;
begin
  point := Chart.GetCursorPos();
  Chart.CalcClickedPart(point, clickedPart);
  Case clickedPart.Part of
    cpSeriesMarks, cpSeries:
    begin
      if (clickedPart.ASeries = SeriesInfoEvent) or (clickedPart.ASeries = SeriesEvent) then 
      begin
        if Assigned(CV_HintWindow) and (CV_HintWindow.Tag <> clickedPart.PointIndex) then 
        begin
          // Hide and free it!
          CV_HintWindow.ReleaseHandle();
          FreeAndNil(CV_HintWindow);
        end;
        if not Assigned(CV_HintWindow) then begin
          labelString := clickedPart.ASeries.Labels[clickedPart.PointIndex];
          CV_HintWindow := THintWindow.Create(Self);
          CV_HintWindow.Color := clInfoBk;
          // Calculate the WIDHT of the rectangle
          rect := CV_HintWindow.CalcHintRect(200, labelString, nil);
          // Find the position on screen to place the tooltip
          rect.TopLeft := Chart.ClientToScreen(point);
          // Move position so that it's above mouse cursor
          rect.Top := rect.Top - rect.Bottom;
          // Adjust Right and Bottom considering position
          rect.Right := rect.Right + rect.Left;
          rect.Bottom := rect.Bottom + rect.Top;
          // Use the tag to remember which Event we are showing tooltip for
          CV_HintWindow.Tag := clickedPart.PointIndex;
          // Show it!
          CV_HintWindow.ActivateHint(rect, labelString);
        end
      end
      else if Assigned(CV_HintWindow) then begin
        // Hide and free it!
        CV_HintWindow.ReleaseHandle();
        FreeAndNil(CV_HintWindow);
      end;
    end;
  else
    if Assigned(CV_HintWindow) then begin
      // Hide and free it!
      CV_HintWindow.ReleaseHandle();
      FreeAndNil(CV_HintWindow);
    end;
  end;
end;
The result looks like this:


That's it!

fredag 19. august 2011

Implementing enumerators on delphi records

Implementing enumerators are pretty easy in Delphi and many articles exist on the topic:

http://hallvards.blogspot.com/2007/10/more-fun-with-enumerators.html
http://www.thedelphigeek.com/2007/03/fun-with-enumerators.html

For some reason, I had a simple record to hold a small array of another type where the use was to loop round and do some action on every type. I used a record instead of a class to avoid the Create/Destroy and since it was just used to hold some simple data.
The first thing I noticed was that the Enumerator had to be a class (and not a record as recommended here) because I could not have a forward declaration to a record. Since my "list type" was a record I had to "forward declare" the enumerator before the record type.

  TLongTrendFieldsEnumerator = class;

  TLongTrendFields = record
  private
    { Private declarations }
    PlotList: array[0..MAX_LONG_TREND_FIELDS] of TPlot;
    function GetItem(index : Integer) : TPlot;
  public
    { Public declarations }
    property Items[i:Integer] : TPlot read GetItem; default;
    function GetEnumerator: TLongTrendFieldsEnumerator;
  end;

The next thing to think of is that the list type is a record, so we don't want to copy the instance when we pass it to the enumerator. So we use pointer instead, beginning with declaring a pointer type

  TLongTrendFieldsEnumerator = class;

  TLongTrendFieldsPointer = ^TLongTrendFields;
  TLongTrendFields = record
  private
  ...
So then the enumerator class will look like this:


  TLongTrendFieldsEnumerator = class
  private
    index: Integer;
    fields: TLongTrendFieldsPointer;
  public
    constructor Create(aFields: TLongTrendFieldsPointer);
    function MoveNext: Boolean;
    function GetCurrent: TPlot;
    property Current: TPlot read GetCurrent;
  end;  
The only difference in the implementation is that when the list type creates the enumerator it has to send a reference/pointer to the enumerator's constructor.
function TLongTrendFields.GetEnumerator: TLongTrendFieldsEnumerator;
begin
  Result := TLongTrendFieldsEnumerator.Create(@Self);
end;
The rest of the implementation is identical to the other examples (without pointers) thanks to Delphi's nice pointer syntax handling!

fredag 5. august 2011

Dynamically adding a GRAPHIC field to an existing Paradox table

I wanted to add a few new fields to an existing Paradox database table. We already had a method for adding new fields. This is using SQL to add new fields kinda like:

'ALTER TABLE ' + TableName + ' ADD COLUMN ' + Field + ' ' + FieldType

so the FieldType is a string and one of that parameters. So I thought GRAPHIC would be a valid type. But no, it complains with the message "Capability not supported":



According to Embarcadero:
"This error is returned by the BDE when the BDE parses an SQL string to be sent to a server and the syntax of the string is not supported by the BDE"
So, as of now I still don't have a solution to this problem.

NOTE: Using BLOB as FieldName works just fine (but testing this caused other problems...)

EDIT: 2011-08-25
Using MEMO as the type gave the same error. Using BLOB works and actually creates a MEMO field! :)

Only workaround found was to delete the table and create a new one using Delphi TTable class and field type ftGraphic - FieldDefs.Add('Icon', ftGraphic, 0, False);

Useful link for simple SQL coding:
http://www.thedbcommunity.com/index.php?option=com_content&task=view&id=138&Itemid=46
This also mentions under "Unsupported Capabilities" that "There are a number of table modifications which cannot be performed using Local SQL, such as foreign key constraints, range limitations, picture settings, etc."

Getting an Image into QuickReport


I had a big problem getting a .bmp file to show up in a QReport. Everything seemed straight forward. I had to do this dynamically during run-time, so I would read in the image to a paradox database and then have the QReport point to the correct database field.

The problem was that when I first configured the QReport, the database field was MEMO and not GRAPHIC. This information is stored in the .QR2 report file. And even though I corrected this field in the database using Database Manager, it would not be updated in the QuickReport.

The solution was to remove the dataset and add it again. This time the field correctly showed up as GRAPHIC, and the image showed up beautifully :-)

One possible solution could be to only remove the field in the dataset and then add it again.

The initial code for getting in image into the DB was:


(FieldByName('Icon') as TGraphicField).LoadFromFile('\path\to\image.bmp');

Although this works fine, the image file has to be available on the disk when the program runs. A better solution is to get the images included in the .exe as an embedded resource. The simplest way to achieve this was to include a TImageList on a form, and populate this with the images I needed. Then use the following code to put them in the database:


procedure InsertImageToDB(Field: TField; ImageIndex: Integer);
var
  stream: TStream;
  bmp: TBitmap;
begin
  bmp := TBitmap.Create();
  stream := Field.DataSet.CreateBlobStream(Field, bmWrite);
  try
    Frm.ImageList.GetBitmap(ImageIndex, bmp);
    bmp.SaveToStream(stream);
  finally
    stream.Free;
    bmp.Free;
  end;
end;
This has to be included in a Edit()...Post() block to get it posted to the DB.