Delphi Q&A


Innehållsförteckning


Komponenter/VCL och andra klasser

Abstrakta metoder

Varför får jag ett felmeddelande när programmet körs när jag skapat en tom arvtagare till TCustomGrid (bara för att testa)?

Svar:

TCustomGrid har en metod DrawCell som är abstrakt (pure virtual i C++), d.v.s. den måste överlagras.

procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
   AState: TGridDrawState); virtual; abstract;

Den av dig deklarerade metoden behöver däremot inte göra något, men den måste finnas där.

ActiveControl och metoder

Jag har ett databasformulär med ett antal TDBEdit på. I Redigera-menyn för fönstret har jag kommandon för klipp och klistra. När användaren väljer dessa ska CutToClipboard, CopyToClipboard eller PasteFromClipboard anropas för den TDBEdit som är aktiv. Jag har försökt använda ActiveControl för detta enligt följande

ActiveControl.CutToClipboard;

men det fungerar inte utan ger felmeddelandet "Unknown identifier". Varför?

Svar:

ActiveControl är en egenskap av typen TWinControl. Dessa klass innehåller inte metoden CutToClipboard, denna introduceras först i TCustomEdit. För att kunna komma ut metoden måste du type-casta ActiveControl till en TDBEdit (som är en arvtagare till TCustomEdit). Först bör du dock kolla och se till att ActiveControl verkligen är en TDBEdit, annars kan det bli problem.

if ActiveControl is TDBEdit then
   (ActiveControl as TDBEdit).CutToClipboard;

eller

   if ActiveControl is TDBEdit then
      TDBEdit(ActiveControl).CutToClipboard;

Aktivt fönster i mitt program

Hur tar jag reda på vilket fönster som är aktivt?

Svar:

Använd Screen.ActiveForm eller Form.ActiveMDIChild om det gäller MDI-fönster.

Annan Winsock-DLL

Går det att med dWinsock välja att använda en annan Winsock-provider än vanliga WINSOCK.DLL?

Svar:

Ja. Ändra WinsockAPI.DLLName till valfri DLL. Detta bör göras innan några socket-komponenter börjar användas, lämpligen i OnCreate.

WinsockAPI.DLLName := 'annandll.dll';

Använda TRegistry

Dokumentationen av TRegistry i hjälpfilerna till Delphi 2.0 är i princip obefintliga. Hur använder man det egentligen?

Svar:

I de uppdaterade hjälpfilerna är detta åtgärdat. De finns att ladda ned från . Den Delphi 2.0 som säljs nu har också dessa uppdaterade hjälpfiler. Men här är i alla fall ett exempel:

procedure TMainForm.SaveOptions;
var
   Reg: TRegistry;
begin
   Reg := TRegistry.Create;
   try
      Reg.RootKey := HKEY_CURRENT_USER; { Numerisk konstant }
      if not Reg.OpenKey('Software\Företagsnamn\Programnamn', true) then 
      begin
         ShowMessage('Kunde inte öppna registret.');
         Exit;
      end;
      Reg.WriteInteger('Bredd', Width);
      Reg.WriteInteger('Höjd', Height);
      Reg.WriteInteger('X', Left);
      Reg.WriteInteger('Y', Top);
      Reg.CloseKey;
      if Reg.OpenKey(RegKey + '\Undernyckel', true) then 
      begin
         Reg.WriteString('Lite text', Edit1.Text);
         Reg.CloseKey;
      end;
   finally
      Reg.Free;
   end;
end;

procedure TMainForm.ReadOptions;

var
   Reg: TRegistry;
begin
   Reg := TRegistry.Create;
   try
      Reg.RootKey := HKEY_CURRENT_USER;
      if not Reg.OpenKey('Software\Företagsnamn\Programnamn', false) then 
      begin
         ShowMessage('Kunde inte öppna registret.');
         Exit;
      end;
      if Reg.ValueExists('Width') then 
         Width := Reg.ReadInteger('Bredd');
      if Reg.ValueExists('Height') then 
         Height := Reg.ReadInteger('Höjd');
      if Reg.ValueExists('Left') then 
         Left := Reg.ReadInteger('X');
      if Reg.ValueExists('Top') then 
         Top := Reg.ReadInteger('Y');
      Reg.CloseKey;
      if reg.OpenKey(RegKey + '\Undernyckel', false) then 

      begin
         Edit1.Text := Reg.ReadString('Lite text');
         Reg.CloseKey;
      end;
   finally
      Reg.Free;
   end;
end;

Att anropa Free

Hur kan jag vara säker på att ett objekt, vars metod Free jag vill anropa, är giltigt?

Svar:

Free kollar alltid att objektet inte pekar på nil, men kollar inte om pekaren är giltig. Smartast är alltså att alltid ställa objektet till nil när du är klar med det.

Form.Free;
Form := nil;

Skillnaden mellan Destroy och Free är att Free kollar om objektet är nil först.

Avsluta vid deaktivering

Hur gör jag så att programmet avslutas så fort det deaktiveras?

Svar:

Skapa en protected metod för huvudfönstret enligt följande:

procedure TMainForm.AppDeactivate(Sender: TObject);
begin
   Close;
end;

Och lägg tillföljande kod i OnCreate för samma formulär (eller i projektkoden efter att formuläret i fråga skapats):

   Application.OnDeactivate := AppDeactivate;

Bara siffror i TEdit

Kan jag tvinga användaren att bara skriva in siffror i en TEdit?

Svar:

Enklast är kanske att använda TMaskEdit. Men det går också att använda OnKeyPress på följande sätt:

procedure TMainForm.EditKeyPress(Sender: TObject; var Key: Char);
begin
   if not (Key in [',','-',#48..#57]) then 
      Key := #0;
end;

Om Key, som innehåller den knapp som tryckts ned, inte är en siffra, ett decimalkomma eller ett minustecken, kommer knapptryckningen att ignoreras. Problemet är att detta släpper igenom "tal" som ,,,00--,54. Därför bör man också lägga till en koll i OnCloseQuery som kollar att det är ett giltigt tal.

try
   StrToFloat(Edit.Text);
except
   ShowMessage('Du har inte skrivit in ett giltigt flyttal.');
   CanClose := false;
end;

Om det i stället måste vara ett heltal använder du i stället StrToInt;

Bugg i TMaskEdit?

Om jag försöker använda mig av OnKeyDown för en TMaskEdit i Delphi 2.0 verkar det som om alla tecken är VK_LEFT, oavsett vad jag trycker på. Är detta en bugg?

Svar:

Ja, man har på Borland bekräftat att det är en bugg.

Bugg i TStringGrid?

I Delphi 2 går det att i en TStringGrid ändra storleken på en icke-existerande kolumn och dra den utanför kanten på kontrollen med ett undantag som följd. Är detta en bugg? Hur fixar man den?

Svar:

Detta kräver att du har VCL-källkoden. Ändra rad 1945 där det står

   if (Pos >= GridExtent - Back) and (Pos <= GridExtent) then

till

   if (Pos >= GridExtent - Back) and (Pos <= GridExtent)
      and (I < GridCellCount) then

Deklaration av THelpEvent

Varför får jag inte OnHelp att fungera? Den klagar på att dekarationen inte är korrekt.

Svar:

THelpEvent är felaktigt dokumenterat och ska vara

THelpEvent = function(Command: Word; Data: Longint; 
   var CallHelp: Boolean): Boolean of object;

och inget annat.

Drag-och-släpp i TreeView

Hur ordnar man drag-och-släpp inom och mellan TreeView?

Svar:

I princip tagen från online-hjälpen, men fixar en bugg i TTreeView.

procedure TMain.TreeViewDragOver(Sender, Source: TObject; 
   X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
   Accept := Source is TTreeView;
end;

procedure TMain.TreeViewDragDrop(Sender, Source: TObject; 
   X, Y: Integer);
var
   AnItem: TTreeNode;
   AttachMode: TNodeAttachMode;
   HT: THitTests;
begin
   with (Sender as TTreeView) do
   begin
      if Selected = nil then Exit;
      HT := GetHitTestInfoAt(X, Y);
      AnItem := GetNodeAt(X, Y);
      if (HT - [htOnItem, htOnIcon, htNowhere, htOnIndent] <> HT) then
      begin
         if (htOnItem in HT) or (htOnIcon in HT) then 

            AttachMode := naAddChild
         else if htNowhere in HT then 
            AttachMode := naAdd
         else if htOnIndent in HT then 
            AttachMode := naInsert;
         Selected.MoveTo(AnItem, AttachMode);
// this prevents items being inaccessible -- not sure
// what causes it to happen, but this fixes it
         Items.AddChild(AnItem, '').Delete;
// make sure it's still visible
         Selected.MakeVisible;
      end;
   end;
end;

dupIgnore fungerar inte

Varför finns det fortfarande flera exemplar av samma strängar trots att jag satt Duplicates till dupIgnore och Sorted till True?

Svar:

Detta påverkar bara nya strängar som läggs till. Om du vill ta bort redan befintliga kopior kan du göra på två sätt; gör en loop enligt nedan (förutsätter att StringList inte är tom)

   I := 0;
   while I < (StringList.Count - 1) do
   begin
      if StringList[I] = StringList[I+1] then
         StringList.Delete(I)
      else
         Inc(I);
   end;

eller skapa en andra StringList dit du lägger till innehållet i den första med AddStrings (troligen långsammare än ovanstående).

Egen ikon för komponent

Jag har skapat en egen VCL-komponent som jag vill ha i komponentpaletten. Hur ritar jag en egen ikon och ser till att den kommer upp där?

Svar:

Öppna Image Editor, den fasansfulla resurseditorn som följer med Delphi. Skapa ett nytt projekt av typen Component Resource (DCR) och skapa en 24x24 bild. Byt namn på bilden till namnet på komponenten. Var noga med att skriva in namnet med stora bokstäver. Tänk på att den färg som är i nedre vänstra hörnet är den färg som kommer att användas som genomskinlighetsfärg, den pixeln bör alltså vara en färg som inte finns någon annanstans på bilden. Spara resursfilen i samma katalog som PAS-filen till komponenten. Länka in resursfilen med $R och installera din(a) komponent(er).

{$R MYCOMP.DCR}

Egen musmarkör

Hur använder jag egna musmarkörer?

Svar:

Skapa en markör som en CURSOR-resurs och se till att länka in den i projektet med $R. Deklarera en konstant enligt nedan med ett valfritt positivt heltal.

const
   Något_Lämpligt_Markörnamn = 1;

Använd sedan LoadCursor för att ladda in markören och tilldela den till Cursors-egenskapen med index Något_Lämpligt_Markörnamn.

   Screen.Cursors[Något_Lämpligt_Markörnamn] := LoadCursor(hInstance, 
      Namn_På_Markör_Resurs);

Sedan kan du använda markören precis om vanligt med

   Screen.Cursor := Något_Lämpligt_Markörnamn;

Finns egenskapen hos en komponent?

Hur kan jag ta reda på om en egenskap finns i ett valfritt objekt, och om den finns - sätta värdet på den?

Svar:

Följande (odokumenterade) exempel visar hur man sätter egenskapen Color till ett visst värde på alla komponenter som har den egenskapen på formuläret. Unit TypInfo måste inkluderas.

procedure SetColorForAll(const Value: TColor; const Controls: array of const);
var
   I: Integer;
   PropInfo: PPropInfo;
begin
   for I := Low(Controls) to High(Controls) do
   begin
      with TVarRec(Controls[I]) do
      begin      
         if vType = vtObject then { Är det ett objekt? }
         begin
            { Finns det en egenskap 'Color'? }
            PropInfo := GetPropInfo(VObject.ClassInfo, 'Color');
            if assigned(PropInfo) then
               SetOrdProp(VObject, PropInfo, Value);
         end;
      end;
   end;       
end;

Flytta fönster utan titelrad

Hur gör jag för att med musen flytta fönster utan titelrad eller överhuvudtaget flytta fönster utan att klicka på titelraden?

Svar:

Man måste lura Windows att tro att man klickat på titelraden. Lägg till en event-handler för meddelandet WM_NCHitTest

   private
      ...
      ...
      procedure WMNCHitTest(var M: TWMNCHitTest); message WM_NCHitTest;
      ...
      ...

och lägg till följande kod för denna:

procedure TForm.WMNCHitTest(var M: TWMNCHitTest);

begin
   inherited;                  { Ärv tidigare kod }
   if M.Result = htClient then { Har användaren klickat i }
                               { bakgrunden? }
      M.Result := htCaption;   { Lura Windows att tro att }
                               { klicket skett på titelraden }
end;

Kom ihåg att lägga till Messages i uses-satsen.

Flytta input focus till nästa kontroll

Hur flyttar man input focus till nästa kontroll i en dialog, d.v.s. hur man programmatiskt skickar en ?

Svar:

Med hjälp av:

Perform(WM_NEXTDLGCTL, 0, 0);

Du måste ha unit Messages med i deklarationen, då meddelandekonstanterna inte längre ligger i WinTypes som i Borland Pascal.

Flytta markören i TMemo

Hur gör jag för att flytta markören i en TMemo?

Svar:

Det finns ingen färdig funktionalitet för detta, men följande trick kan med fördel användas:

procedure MemoCursorTo(Memo:TMemo; MemoLine, MemoCol: Integer);
begin
   Memo.SelStart := SendMessage(Memo.Handle, EM_LINEINDEX, MemoLine, 0) + MemoCol - 1;
end;

EM_LINEINDEX returnerar det index i filen som raden MemoLine börjar på. Än bättre är förstås att härleda en ny klass av detta med TMemo som basklass.

Fokus i owner-draw listbox

Hur jag än bär mig åt, visas alltid en prickat kant runt det som är valt i min listbox som jag har som owner-draw. Jag har valt ett annat sätt att visa att den är vald, hur kan jag undvika att denna markeringsram ändå visas?

Svar:

Med parametern State får du reda på hur du ska rita objekten. Om odFocused finns med i State skall denna ram ritas ut och du kan genom att anropa DrawFocusRect få bort denna ram. DrawFocusRect arbetar nämligen med xor, vilket innebär att om du utför samma kommando två gånger, kommer det ursprunliga att återställas. I det här fallet blir det du som ritar det första gången, och Windows (eller rättare sagt Delphis VCL, då detta "problem" är Delphi-specifikt) återställer det.

if odFocused in State then
   Canvas.DrawFocusRect(Rect);

Free ger EAccessViolation

Jag skapar ett antal komponenter (bl.a. TMaskEdit) när programmet körs, men när jag sedan ska deallokera minnet genom att anropa Free, får jag ett undantag av typen EAccessViolation, varför?

Svar:

Genom att du skapar komponenterna med en ägare (Owner) kommer de automatiskt att frigöras av ägaren och det åligger inte dig.

Genomskinliga komponenter

Varför får jag ett felmeddelande när programmet körs när jag skapat en tom arvtagare till TCustomGrid (bara för att testa)?

Svar:

TCustomGrid har en metod DrawCell som är abstrakt (pure virtual i C++), d.v.s. den måste överlagras.

procedure DrawCell(ACol, ARow: Longint; ARect: TRect;
   AState: TGridDrawState); virtual; abstract;

Den av dig deklarerade metoden behöver däremot inte göra något, men den måste finnas där.

Göra medlemmar privata

Hur gömmer jag egenskaper och/eller metoder för en klass genom att göra dem private eller protected?

Svar:

Detta strider mot objekt-orienteringens lagar; medlemmar kan inte göras mindre synliga än de var i stamfadern/superklassen. I stället får du härleda fram din komponent från en TCustomxxxx (om någon sådan finns), t.ex. TCustomEdit eller TCustomGrid. De kompoenterna innehåller all funktionalitet men de flesta egenskaper är protected.

Göra något när laddningen är klar

Jag behöver visa en dialog direkt när programmet startat. Problemet är att alla egenskaper inte är satta ordentligt i OnCreate. Hur vet jag när programmet har laddats färdigt?

Svar:

För Delphi 2/3 kan du använda Loaded-metoden, slå upp den i hjälpen.

Högerjustera i TDBGrid/TGrid

Går det att högerjustera kolumner i en TGrid/TDBGrid?

Svar:

Detta går inte utan vidare i en TGrid, du måste göra den owner-draw och skriva dit texten själv, det är däremot inga problem för TDBGrid; ändra egenskapen Alignment för den TField som motsvarar kolumnen. Detta kan exempelvis göras på detta sätt:

Query.Fields[0].Alignment := taRightJustify;

Detta justerar den första kolumnen i Query till höger.

Ignorera tangentnedtryckningar

Hur kan jag "äta upp" en tangentnedtryckning, alltså se till att den ignoreras av mitt program?

Svar:

Lägg till en handler för OnKeyPress eller OnKeyDown. Vilken du väljer beror på behovet, OnKeyPress aktiveras inte när tangenten inte har en vanlig teckenkod (F1, Shift+F4 etc ger alltså inte OnKeyPress). Parametern Key är en var-parameter och dess värde kan alltså ändras. Sätt värdet till noll (0) och tangentnedtryckningen kommer inte att registreras för det objekt som handler påverkar. Om du använder OnKeyPress är Key i stället en Char och du får du tilldela den #0 i stället.

Ikon på aktivitetsfältet

Hur gör jag så att fler än ett fönster i en SDI-applikation får sin ikon i aktivitetsfältet i Delphi 2.0?

Svar:

CreateParams måste omdefinieras och WS_EX_APPWINDOW ska läggas till bitmaskan i Params.ExStyle.

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
   inherited CreateParams(Params);
   with Params do
      ExStyle := ExStyle or WS_EX_APPWINDOW;
end;

Inga skrivarfonter visas i TFontDialog

Inga skrivarfonter visas i dialogen TFontDialog!

Svar:

Detta är en bugg som kräver VCL-källkoden. Öppna DIALOGS.PAS och leta reda på metoden TFontDialog.Execute. Ändra där

hDC := 0;

till

hDC := Printer.Handle;

Ingen minimering vid start

Om jag i Programhanteraren har ställt in så att programmet ska köras minimerat så fungerar inte därför för Delphi-program. Varför?

Svar:

Detta beror på Delphis sätta att hantera fönster, mer finns att läsa i Varför ingen animering?. Lägg till följande kod i FormCreate:

procedure TForm1.FormCreate(Sender: TObject);
{$IFDEF WIN32}           { Delphi 2.0 (32 bit) }
var
   MyInfo: TStartUpInfo;
{$ENDIF}
begin
{$IFDEF WIN32}           { Delphi 2.0 (32 bit) }
   GetStartUpInfo(MyInfo);
   ShowWindow(Handle, MyInfo.wShowWindow);
{$ENDIF}
{$IFDEF WINDOWS}         { Delphi 1.0 (16 bit) }
   ShowWindow(Handle, cmdShow);
{$ENDIF}
end;

Ingen vald vid OnClick

Jag har en handler för en listview enligt nedan. Varför ger den ibland EAccessViolation?

begin
   Label1.Caption := ListView.Selected.Caption;
end;

Svar:

OnClick inträffar oavsett vara någonstans användaren klickar och är ingen garant för att något är valt. Om inget är valt kommer Selected att vara nil, och där har vi alltså orsaken till undantaget. Kolla alltså först om Selected är nil, alternativt byt handler till OnChange (kan vara bra i vissa fall).

Inställda egenskaper nollas

Jag har skrivit en egen komponent som fungerar bra med ett undantag; trots att jag ställt in vissa egenskaper i Object Inspector nollställs dessa ändå när programmet körs. Varför?

Svar:

Kolla i Create, initierar du några egenskaper där? I sådana fall måste du se till att detta inte görs när programmet körs separat. Använd ComponentState för att ta reda på detta. Använd då

procedure TBlahaComponent.Create(AOwner: TControl);
begin
   ...
   if csDesigning in ComponentState then
      FTrams := 'Blahablaha';
   ...
end;

i stället för

procedure TBlahaComponent.Create(AOwner: TControl);
begin
   ...
   FTrams := 'Blahablaha';
   ...
end;

Kan inte lägga till komponent

När jag ska lägga till en kompilerad komponent (DCU) klagar Delphi på att den inte hittar PAS-filen, vad gör jag åt det?

Svar:

En tänkbar orsak är att du försöker lägga till en 16-bitars komponent i Delphi 2/3, en annan att komponenten är kompilerad med debuginformation.

Komma runt 32 kB-begränsning i TMemo

Hur kommer man runt begränsningen i TMemo på 32 kB?

Svar:

Begränsningen ligger faktiskt inte på exakt 32 kB. Detta kan enkelt testas med koden här nedanför. Gränsen ligger på ca 20 kB om varje rad är på ett tecken och ca 52 kB om radlängden är 70 tecken.

Memo1.Lines.Clear;
NumLines := 0;
while true do 
begin
   try
      Memo1.Lines.Add(S); { S är en sträng av valfri längd }
      Inc(NumLines);
   except
      ShowMessage(Format('Loaded %d lines of length %d, %d bytes',
         [NumLines, Length(S), NumLines*Length(S)]);
      Exit;
   end;
end;

Det finns många tredjepartskomponenter med större gränser (eller inga alls). TurboPower har paketet Orpheus som bland mycket annat innehåller en TMemo-ersättare som klarar 16 MB data. Kör man Delphi 2.0 eller 3.0 är det förstås enklast att använda TRichEdit.

Om 64 kB räcker kan man också använda SetTextBuf/GetTextBuf i stället. Under Windows NT ligger begränsningen på 2 GB.

Komponent kräver PAS-fil

När jag försöker installera en komponent frågar Delphi efter PAS-filen, men jag har bara den kompilerade DCUn. Vad göra?

Svar:

Troligen försöker du lägga till en 16-bitars komponent i Delphi 2/3. Kolla efter och se om det inte finns en 32-bitars version av komponenten. Börja att kolla på ex.vis Delphi Super Page.

Kopiera mellan två RichEdit utan att förlora all formatering

Hur kopierar jag innehållet i två RichEdit? Om jag bara kör RichEdit1.Lines.Assign(RichEdit2.Lines) förloras all formattering.

Svar:

Används en stream. Spara ned allt i en TMemoryStream och läs sedan in det i det andra RichEdit-objektet.

procedure TForm1.Button1Click(Sender: TObject);
var
   S: TMemoryStream;
begin
   S := TMemoryStream.Create;
   try
      RichEdit1.Lines.SaveToStream(S);
      S.Position := 0;
      RichEdit2.Lines.LoadFromStream(S);
   finally
      S.Free;
   end;
end;

Körs komponenten i det separata programmet?

Kan jag från en komponent ta reda på om den körs i IDE eller i det fristående programmet?

Svar:

Egenskapen ComponentState talar bl.a. om detta. Testa med operatorn in om csDesigning ingår i ComponentState enligt följande:

if csDesigning in ComponentState then
   ShowMessage('Design mode!');

Listbox över flera kolumner

Hur gör jag för att strängarna i en listbox ska visas i flera kolumner?

Svar:

Sätt egenskapen TListBox.Columns till det antal kolumner du vill ha. Det som i praktiken händer är att VCL sätter biten LBS_MULTICOLUMN samt skickar meddelanden LB_SETCOLUMNWIDTH till listboxen.

Låta ENTER fungera som TAB

Hur gör jag för att ENTER ska fungera likadant som TAB i en textruta (dvs. flytta fokus till nästa kontroll)?

Svar:

Lägg till en event handler för OnKeyPress för önskad TEdit-kontroll med följande kod:

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
   if Key = #13 then
   begin
      Key := #0;    { Tangentnedtryckningen kommer att ignoreras }
      Perform(WM_NEXTDLGCTL, 0, 0); { Flytta till nästa kontroll }
   end;
end;

Lägga till nya komponenter på flikar

Hur lägger jag till komponenter till en flik på en TTabbedNotebook- eller TTabSet-komponent?

Svar:

Det viktiga här är egenskapen Parent. Tilldela denna egenskap hos komponenten den flikkomponent som du vill ska "äga" komponenten.

NewButton.Parent := TWinControl(TabbedNotebook1.Pages.Objects[n]);

n är det noll-baserade index bland flikarna som komponenten ska höra till. För TTabSet-objekt blir koden i stället:

NewButton.Parent := TWinControl(TabSet1.Tabs.Objects[n]);

Detta innebär dock inte att flikobjektet är ägaren till komponenten, den är enbart "förälder". Ägaren är fortfarande dialogen eller fönstret.

Lägga till programikoner och -grupp i Programhanteraren

Hur lägger man till programikoner och grupper i Programhanteraren med hjälp av DDE?

Svar:

Lägg till en TDDEClientConv på formuläret och anropa nedanstående funktioner. Mer information om de makron man ska skicka till Programhanteraren finns i API-hjälpen, slå på "Progman".

function CreateProgManGroup(DDEClient: TDdeClientConv; 
   strGroup: string): Boolean;
{By Andy Cooper - 100622.1041@COMPUSERVE.COM}
var
   pstrCmd : array[0..255] of char;
begin
   try
      StrPCopy (pstrCmd, Format('[CreateGroup(%s)]', [strGroup]) + #13#10);
      Result := DDEClient.ExecuteMacro(pstrCmd, False);
   except
      Result := False;
   end; {try}
end;

function CreateProgManItem(DDEClient: TDdeClientConv; 
   strGroup, strItem, strFile : string) : Boolean;
{By Andy Cooper - 100622.1041@COMPUSERVE.COM}
var
   pstrCmd : array[0..255] of char;
begin
   try
      StrPCopy (pstrCmd, Format('[ShowGroup(%s, 1)]', 
         [strGroup]) + #13#10);
      DDEClient.ExecuteMacro(pstrCmd, False);
      StrPCopy (pstrCmd, Format('[ReplaceItem(%s)]', 
         [strItem]) + #13#10);
      DDEClient.ExecuteMacro(pstrCmd, False);

      StrPCopy (pstrCmd, Format('[AddItem(%s,%s' + ',,)]', 
         [strFile,strItem]) + #13#10);
      Result := DDEClient.ExecuteMacro(pstrCmd, False);
      StrPCopy (pstrCmd, Format('[ShowGroup(%s, 1)]',
         [strGroup]) + #13#10);
      DDEClient.ExecuteMacro(pstrCmd, False);
   except
      Result := False;
   end; {try}
end;

Markören försvinner i DBGrid

I en av mina TStringGrid visas aldrig någon synlig markör när man redigerar en cell. Varför?

Svar:

Detta beror troligen på att du har satt DefaultRowHeight till 15 eller mindre. Sätt den till 15 så kommer markören att visas igen.

Max åtta tecken i strängegenskap

Hur begränsar jag längden av en strängegenskap i en komponent som jag håller på att göra?

Svar:

Skapa en ny typ definierad som string[max_antal_tecken] och omdefiniera egenskapen (om den redan finns) som den typen.

type
   TShortName = string[8];
   TFoo = class(TComponent)
      ...
   published
      ...
      property Name: TShortName read GetName write SetName;
      ...
   end;

Meddelande om flikbyte

Hur kan jag ta reda på när en användare byter flik i en TTabbedNotebook?

Svar:

Meddelandet OnChange skickas varje gång användaren byter flik. Du kan också få reda på vilken flik användaren vill byta till, dessutom får du tillfälle att förhindra bytet.

Minnesproblem med TStringList

Jag använder TStringList/TStrings och dess metoder SaveToFile och LoadFromFile i Delphi 2.0 för att läsa in och skriva till stora textfiler. När jag gjort det ett tag tar dock minnet slut, det verkar som en minnesläcka någonstans. Vad försiggår?

Svar:

Detta är en bugg i Delphi 2.0. Felet sitter bara i SaveToFile (eller snarare i SaveToStream som SaveToFile anropar), och kan i sådana fall fixas genom att ändra direkt i VCL-källkoden. Har du inte den får du i stället skapa en egen arvtagare till TStringList, men den klassen kan inte utnyttjas av t.ex. TMemo. Om du har VCL-källkoden ska följande ändras:

procedure TStrings.SaveToStream(Stream: TStream);
var
   S: String;
begin
   S := GetTextStr;  { Det var här buggen fanns }
   Stream.WriteBuffer(Pointer(S)^, Length(S));
end;

Med denna bugg förlorar du lika mycket minne som storleken av filen som du sparade.

Motsvarighet till onMouseOver

Finns det någon motsvarighet till onMouseOver i Delphi, alltså en händelse som utlöses när musen rör sig över ett objekt?

Svar:

Ja, det finns två odokumenterade meddelanden för detta; CM_MOUSEENTER och CM_MOUSELEAVE. De finns i unit Controls och är deklarerade enligt:

CM_MOUSEENTER             = CM_BASE + 19;
CM_MOUSELEAVE             = CM_BASE + 20;

För att avgöra vilket objekt som musen är över typomvandlar man lParam till (lämpligen) TObject. De deklareras och används enligt:

   private
      procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;
      ...
      ...

procedure TMainForm.CMMouseEnter(var Msg: TMessage);
var
   anObject :        TObject;
begin
   { anObject is the control over which the mouse is right now }
   anObject := TObject(Msg.lParam);
   if anObject <> nil then begin
      { First, you must find WHICH is the control under the mouse }
      { cursor, then, determine what action to do, etc... }
   end;
end;

Musmarkörens position med OnClick

Jag använder OnClick för att få reda på när användaren klickar på objektet i fråga. Hur får jag reda på var användaren klickade? Den informationen framgår inte av OnClick.

Svar:

Använd OnMouseDown i stället. Du kan också använda GetCursorPos för att manuellt ta reda på var musen befinner sig, men det är ju helt onödigt.

Mätare vid läsning/skrivning

Hur gör jag för att visa en mätare av något slag när jag läser in en fil med LoadFromFile eller skriver till en fil med SaveToFile? Måste jag läsa filen manuellt?

Svar:

Nej, nedanstående klass av Ray Lischner lägger till tre händelser som du kan utnyttja för att få reda på varje gång streamen läses eller skrivs. Konstrueraren tar en parameter, den stream som utför den verkliga läsningen. Du måste alltså skapa och förstöra två streamobjekt. När du skapar procedurer för att hantera händelserna (detta måste ju göras i kod) är det bara att deklarera dem likadant och tilldela TProgressStream.OnRead din egen funktion.

type
  TProgressStream = class(TStream)
  private
    fOnRead: TNotifyEvent;
    fOnWrite: TNotifyEvent;
    fOnSeek: TNotifyEvent;
    fStream: TStream;
  public
    constructor Create(Stream: TStream);
    function Read(var Buffer; Count: LongInt): LongInt; override;
    function Write(const Buffer; Count: LongInt): LongInt; override;
    function Seek(Offset: LongInt; Origin: Integer): LongInt;override;
    property OnRead: TNotifyEvent read fOnRead write fOnRead;
    property OnWrite: TNofityEvent read fOnWrite write fOnWrite;
    property OnSeek: TNotifyEvent read fOnSeek write fOnSeek;

  end;

constructor TProgressStream.Create(Stream: TStream);
begin
  inherited Create;
  fStream := Stream;
end;

function TProgressStream.Read(var Buffer; Count: LongInt): LongInt;
begin
  Result := Stream.Read(Buffer, Count);
  if Assigned(fOnRead) then
    fOnRead(Self);
end;

function TProgressStream.Write(const Buffer; Count: LongInt): LongInt;
begin
  Result := Stream.Write(Buffer, Count);
  if Assigned(fOnWrite) then
    fOnWrite(Self);
end;

function TProgressStream.Seek(Offset: LongInt; Origin: Word): LongInt;
begin
  Result := Stream.Seek(Offset, Origin);
  if Assigned(fOnSeek) then
    fOnSeek(Self);
end;

Ny font för snabbtips

Hur ändrar man den font som används vid snabbtips (ToolTips)?

Svar:

Gör först en arvtagarklass till THintWindow. I constructorn ändrar du kanvasfonten så som du vill. Kom ihåg att använda override i klassdeklarationen.

constructor TNewHintWindow.Create(AOwner: TComponent);
begin
   inherited Create(AOwner);
   Canvas.Font.Name := 'Times New Roman';
   Canvas.Font.Size := 12;
end;

Lägg sedan till följade i projektkoden:

HintWindowClass := TNewHintWindow; { Eller vad du nu vill kalla den  }
Application.ShowHint := False;     { Tar bort den vanliga klassen... }
Application.ShowHint := True;      { ...och installerar din variant  }

Owner-draw i TOutline

Jag försöker göra en TOutline till owner-draw i Delphi 1.0, men oavsett vad jag skriver i OnDrawItem ritas inte något upp. Jag har gjort precis som i hjälpfilen.

Svar:

Du har antagligen inte installerat patchen till Delphi 1.0. Den finns tillgänglig på Borlands FTP-server:

ftp.borland.com/pub/techinfo/techdocs/language/delphi/patch/

Patchen fixar det problemet och en del andra buggar.

Oändlig loop av TMediaPlayer

Hur gör jag för att t.ex. en videofilm som spelas upp i en TMediaPlayer ska upprepas oändligt?

Svar:

Använd händelsen OnNotify och sätt igång filmen/ljudet när den rapporteras färdig (egenskapen NotifyValue är nvSuccessful). För att denna händelse överhuvudtaget ska inträffa, måste egenskapen Notify sättas till True för varje gång, den nollställs nämligen till False när en händelse inträffat.

procedure TForm1.MediaPlayer1Notify(Sender: TObject);
begin
   with MediaPlayer1 do
   begin
      if NotifyValue = nvSuccessful then
      begin
         Notify := True;
         Play;
      end;
   end;
end;

Kom också ihåg att sätta Notify till True när avspelningen startas.

Påverka många komponenter på samma gång

Om jag har ett stort antal komponenter av en viss typ eller uppgift som jag på ett smidigt vill sätta en eller flera egenskaper på, hur gör jag då?

Svar:

Ett sätt vore att skapa en vektor med alla objekten, men det vore onödigt krångligt. Det finns flera alternativa lösningar;

Om du vill påverka alla komponenter av en viss typ kan du göra en for-sats som går igenom alla objekt i vektor Components (antalet objekt finns i ComponentCount, kom igen att Components är noll-baserad) och med run-time type information (RTTI) kollar om objektet är rätt typ.

for I := 0 to ComponentCount do
begin
   if Components[I] is TEdit then
      TEdit(Components[I]).Text := '';
end;

Om det bara gäller några av en viss typ kan du med FindComponent få en referens till en komponent om du vet namnet på den. Då är det bäst att namnge dem med en siffra på slutet, även om man skulle kunna lägga in namnen i en TStringList och därifrån ta namnen.

for I := 1 to 10 do
   TEdit(FindComponent('Edit' + IntToStr(I))).Text := '';

Radbrytning i snabbtips

Kan jag ha en radbrytning i ett snabbtips?

Svar:

Lägg in #13 där du vill ha radbrytningen. Observera att det bara är #13, inte #13#10 som är brukligt vid DOS-filer. Detta måste du göra i kod och inte i Object Inspectorn.

SpeedBtn.Hint := 'Test av'#13'radbrytning';

Radnummer från position i TMemo

Går det att få reda på vilken rad som det n:te tecknet i en TMemo är på?

Svar:

Använd meddelandet EM_LINEFROMCHAR på följande sätt:

Line := SendMessage(Memo.Handle,EM_LINEFROMCHAR,ChIndex,0);

eller

Memo.Perform(EM_LINEFROMCHAR,ChIndex,0);

Om du kör Delphi 2/3 ska detta i stället vara

Line := SendMessage(Memo.Handle,EM_EXLINEFROMCHAR,0,ChIndex);

eller

Memo.Perform(EM_EXLINEFROMCHAR,0,ChIndex);

Detta kan du kombinera med t.ex. TMemo.SelStart/SelLength och EM_LINESCROLL för att markera en del av texten i TMemo och scrolla dit.

Rulla ned drop-down combo i kod

Hur rullar jag ner en drop-down combobox i kod?

Svar:

Med

ComboBox.Perform(CB_SHOWDROPDOWN, 1, 0);

eller

SendMessage(ComboBox.Handle, CB_SHOWDROPDOWN, 1, 0);

Seg stängning av socket

Jag använder dWinsock. Varför tar det ibland en evinnerlig tid att stänga en socket med ClientSocket.Close? Om jag bara avslutar programmet rätt av går det fort.

Svar:

Troligen beror detta på ett mottagningsbuffern inte är tömd. Töm den innan du stänger ner.

Skapa komponenter när programmet körs

Varför kommer inte komponenter som jag skapar i kod upp överhuvudtaget? Jag tilldelar Left och Top rimliga värden.

Svar:

Med största sannolikhet har du missat att också tilldela egenskapen Parent. Följande är korrekt kod:

Btn := TButton.Create(Self);
Btn.Name := 'btnBrowse';
Btn.Parent := Self;     { <-- Viktigt! }
Btn.Left := 10;
Btn.Top := 10;
Btn.Caption := 'Bläddra';

Parent är inte samma sak som Owner, den senare är den som internt i Windows äger kontrollen. Ett Parent-förhållande är mer visuellt och är speciellt för Delphi. Om du har kontroller på en sida i en TTabbedNotebook är det den som är Parent till kontrollen, däremot är det formuläret som är Owner.

Skapa runtime-egenskaper

Hur gör jag så att man bara kan komma åt en egenskap i kod och inte via Object Inspectorn?

Svar:

Deklarara egenskapen under public i stället för published.

Skriva över i TMemo

Hur gör man för att ordna överskrivning i en TMemo? Inget händer när jag trycker på Insert.

Svar:

Denna funktion existerar inte i TMemo, men kan fuskas fram. Det finns heller inget sätt att ta reda på om man är i insert-läge, lämpligen genom att lägga till en event-handler för OnKeyDown, kolla om Key är VK_INSERT och sedan växla en flagga därefter. För att ordna överskrivningsuppförande för TMemo, lägg till en event handler för OnKeyPress med följande innehåll:

procedure TMain.MemoKeyPress(Sender: TObject; var Key: Char);
begin
   if IsInsert then 
      Exit;  { Avbryt om det inte är överskrivningsläge }
   Memo.SelLength := 1;  { markera ett tecken }
   Memo.SelText := '';  { Töm markeringen }
   Memo.SelLength := 0; { Ta bort markeringen }
end;

IsInsert i exemplet ovan talar om huruvida det är överskrivningsläge eller inte.

Snabba upp TGauge i loop

Jag har en tidskrävande loop där jag använder en TGauge för att visa hur långt den kommit. Från början hade jag ingen mätare, men när jag lagt till denna tog loopen nästan dubbelt så lång tid att genomföra. Varför? Jag använder Application.ProcessMessages för att se till att mätaren uppdateras.

Svar:

TGauge är synnerligen korkat skriven, den uppdateras oavsett om det innebär någon synbar förändring. Bästa sättet att undvika detta är att ha en räknare som räknar upp ett steg för varje iteration av loopen. Då kan man se till att mätaren bara uppdateras t.ex. var 100:e gång.

if (IterationCount mod 100) = 0 then
begin
   Gauge.Progress := Gauge.Progress + 100;
   Application.ProcessMessages;
end;

Spara objekt i fil

Går det att spara undan komponenter till en fil för att sedan kunna ladda in dem igen?

Svar:

Ja. Använd

procedure WriteComponentResFile(const FileName: String; Instance: TComponent);

för att spara (en instans av) komponenten och

function ReadComponentResFile(const FileName: String; Instance: TComponent): TComponent;

för att läsa tillbaka den.

Stänga fönster från FormShow

Hur stänger man ett fönster från FormShow (Close fungerar inte)?

Svar:

Skicka ett WM_CLOSE-meddelande:

SendMessage(Handle, WM_CLOSE, 0, 0);

Stänga MDI-fönster helt

När jag stänger de MDI-fönster jag använder i mitt program så minimeras de bara; de försvinner inte. Varför?

Svar:

Lägg in en event handler för OnClose (via Object Inspector, dubbelklicka på OnClose). En av parametrarna till OnClose är Action. Där ska du ange vad du vill ska hända när någon försöker stänga fönstret. Som standard är Action caMinimize som alltså innebär att fönstret minimeras. Ändra detta till caFree i stället.

procedure TChildForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   Action := caFree;
end;

Synkronisera TListBox med TTabSet

Jag har en TListBox med en massa namn och en TTabSet med flikar för alla bokstäver A-Z på ett formulär. Hur gör jag för att byta till rätt flik när ett namn väljs i listrutan? Om "Doe, Jane" väljs, ska t.ex. fliken "D" ocskå väljas.

Svar:

Lägg till följande kod i OnClick för listrutan:

TabSet.ItemIndex := Pos(AnsiUpperCase(Listbox.Items[ListBox.ItemIndex][1]),
   'ABCDEFGHIJKLMNOPQRSTUVWXYZ') - 1;

Om ÅÄÖ ska finnas med, måste detta specialkodas eftersom de har helt andra ASCII-värden.

Ta bort objektreferenser

Jag har skapat en komponent som refererar till en annan komponent. Hur kan jag ta reda på när komponenten som refereras tas bort så att jag kan ta bort referensen till den? Gör jag inte det inträffar ett skyddsfel.

Svar:

Omdefiniera medlemmen Notification. Om parametern AComponent är samma som din referens och parametern Operation är pmRemove kan du sätta referensvariabeln till nil.

procedure TFoo.Notification(AComponent: TComponent; 
   Operation: TOperation);
begin
   inherited Notification(AComponent, Operation);
   if Operation = opRemove then
   begin
      if AComponent = FReferens then 
         FReferens := nil;
   end;
end;

Ta bort titelraden

Hur får jag bort titelraden? Jag har testat att överlagra CreateParams och där ta bort WS_CAPTION från Params.Style utan framgång.

Svar:

Sätt formulärets BorderStyle till bsNone och sätt WS_DLGFRAME-biten i Params.Style.

begin
   inherited CreateParams(Params);
   with Params do
      Style := Style or WS_DLGFRAME;
end;

Ta bort vertikal scrollbar i TDBGrid

Hur tar man bort den vertikala scrollbar som alltid finns i en TDBGrid, vare sig det behövs eller inte?

Svar:

En ny klass måste härledas. Använd TDBGrid som basklass och skriv om metoden Paint enligt följande:

procedure TNoVScrollDBGrid.Paint;
begin
   SetScrollRange(Self.Handle, SB_VERT, 0, 0, False);
   inherited Paint;
end;

En redan färdig komponent finns att hämta som NOSCRGRD.ZIP.

TCollection i Delphi

Jag har ett gammalt projekt i DOS skrivet i Turbo Pascal som använder TCollection med underklasser. Hur konverterar jag detta enklast för att använda det under Delphi?

Svar:

I katalogen DELPHI\SOURCE\RTL70 finns OBJECTS.PAS, som innehåller dessa klasser. Detta är den gamla uniten Objects, och bör kunna kompileras direkt. Eventuellt kan man behöva klippa ut nödvändiga klasser och placera dem i en egen unit. Ett annat alternativ är klassen TList, som fungerar nästan exakt som TCollection.

Trunkerade filtillägg i TSaveDialog

Jag använder TSaveDialog för att låta användaren välja filnamn. Default-tillägget har jag satt till 'html'. Detta trunkeras dock alltid till 'htm', finns det en väg runt detta?

Svar:

Det beror på en bugg i TSaveDialog. Det man kan göra är att lägga till det tecken som behövs eller att använda traditionell Windows API för uppgiften.

TStringList.Find fungerar inte

Varför fungerar inte TStringList.Find som det ska?

Svar:

Detta beror med största sannolikhet på att din stringlist inte är sorterad. Anropa metoden Sort, eller se till att egenskapen Sorted är True.

TStrings vs. TStringList

Varför blir det fel när jag försöker skapa ett TStrings-objekt för att lagra ett antal strängar?

Svar:

Använd TStringList i stället. De kan verka snarlika, men TStrings kan inte lagra några strängar. Den används bara som ett interface mot t.ex. listrutor. Strängarna i en listruta lagras ju internt av Windows, TStrings är alltså bara en slags wrapper för funktioner och meddelanden i Windows API.

Tömma TMemo på all text

Hur tömmer man en TMemo-komponent på all text?

Svar:

Memo.Clear;

Utskrifter med RichEdit

När jag försöker skriva ut från en TRichEdit med Print-metoden inträffar ett EDivByZero-undantag. Problemet inträffar bara med NT 4.0. Var sitter felet?

Svar:

Detta beror på en bugg i COMCTRLS.PAS. Ändra i VCL-källkoden eller härled en ny klass från TCustomRichEdit och ändra Print-metoden till nedanstående:

procedure TCustomRichEdit.Print(const Caption: string);
var
   Range: TFormatRange;
   LastChar, MaxLen, LogX, LogY: Integer;
begin
   FillChar(Range, SizeOf(TFormatRange), 0);
   with Printer, Range do
   begin
      LogX := GetDeviceCaps(Handle, LOGPIXELSX);
      LogY := GetDeviceCaps(Handle, LOGPIXELSY);
      // The repositioned BeginDoc to now be compatible with
      // both NT 4.0 and Win95
      BeginDoc;
      hdc := Handle;
      hdcTarget := hdc;
      if IsRectEmpty(PageRect) then
      begin
         rc.right := PageWidth * 1440 div LogX;

         rc.bottom := PageHeight * 1440 div LogY;
      end
      else begin
         rc.left := PageRect.Left * 1440 div LogX;
         rc.top := PageRect.Top * 1440 div LogY;
         rc.right := PageRect.Right * 1440 div LogX;
         rc.bottom := PageRect.Bottom * 1440 div LogY;
      end;
      rcPage := rc;
      Title := Caption;
      // The original position of BeginDoc
      { BeginDoc; }
      LastChar := 0;
      MaxLen := GetTextLen;
      chrg.cpMax := -1;
      repeat
         chrg.cpMin := LastChar;
         LastChar := SendMessage(Self.Handle, 
            EM_FORMATRANGE, 1, Longint(@Range));
         if (LastChar < MaxLen) and (LastChar <> -1) then 
            NewPage;
      until (LastChar >= MaxLen) or (LastChar = -1);
      EndDoc;
   end;
   SendMessage(Handle, EM_FORMATRANGE, 0, 0);
end;

Varför ingen animering?

Varför sker ingen animering när Delphi-program minimeras och maximeras?

Svar:

Detta beror på uppbyggnaden av VCL, Delphis klassbibliotek. Programmets huvudfönster är rent fysiskt inte det riktiga huvudfönstret, i stället skapas alltid ett osynligt "applikationsfönster" med bredden och höjden 0 (noll), detta kan enkelt visas genom att använda WinSight eller motsvarande. På grund av detta har Borland tvingats lägga in kod i VCL som ser till att animering inte sker. Detta görs i TApplication.Minimize. I stället för ShowWindow(FHandle, SW_MINIMIZE) anropas ShowWinNoAnimate(FHandle, SW_MINIMIZE). TApplication.Minimize anropas i sin tur av huvudfönstret (inte det osynliga, riktiga, utan det som syns) i dess event handler för WM_SYSCOMMAND.

Delphi Developer's Journal har skrivit mer om och kring detta fenomen, inkl. sätt att kringå det.

Vilken musknapp?

Hur kollar jag vilken musknapp som trycks ned i en OnMouseDown?

Svar:

Kolla parametern Button; den kan vara mbRight, mbLeft eller mbMiddle för höger-, vänster- resp. mittenknappen.

Visa alla programgrupper i en lista

Hur gör jag för att visa alla programgrupper i Programhanteraren eller under Start-menyn i en listruta?

Svar:

Detta görs på bästa sätt med DDE, alltså kommunikation och datautbyte mellan program. Lägg till en TDDEClient på formuläret, deklarera Groups som PChar och kör följande kod för att lägga till programobjekten i ListBox1:

if not DDEClient.SetLink('ProgMan','PROGMAN') then
   ShowMessage('DDE-länk med Programhanteraren kunde inte öppnas.')
else
begin
   try
      DDEClient.OpenLink;
      Groups := DDEClient.RequestData('GROUPS');
      ComboBox1.Items.SetText(Groups);
      StrDispose(groups);
      DDEClient.CloseLink;
   except
      ShowMessage('Fel vid DDE-kommunikation.')
end;

Finessen här är att alla grupperna returneras separerade med radbrytningar, mycket smidigt om man vill få in dem i en TStringList eller en TStrings. Notera att RequestData allokerar minne åt dig, du behöver bara återlämna det med StrDispose. Mer information om DDE-kommunikation med Programhanteraren finns i API-hjälpen om du slår på 'Progman'.

Visa memo i DBGrid

Hur visar jag ett memofält i en DBGrid?

Svar:

Du måste göra griden owner-draw och lägga till en handler för OnDrawDataCell. Parametern Field ger all nödvändig information om fältets innehåll. För alla andra kolumner är det enklast att använda DrawDefaultDataCell så slipper man bry sig.

Vit ruta i TRichEdit

Hur får jag bort den vita ruta som kommer bildas i kanten när man ändras den vertikala storleken på en TRichEdit? Refresh och Repaint hjälper inte.

Svar:

Anropa nedanstående kod i både OnResize och OnPaint.

procedure TfrmMain.SetEditRect;
var
   R: TRect;
begin
   with RichEdit1 do
   begin
      R := Rect(0, 0, ClientWidth, ClientHeight);
      Perform(EM_SETRECT, 0, Longint(@R));
   end;
end;

Välja katalog från dialog

Hur visar man enklast en dialog för att låta användaren välja en katalog? TOpenDialog fungerar inte riktigt som jag tänkt det.

Svar:

Använd API-anropet SHBrowseForFolder() eller någon wrapperkomponent till funktionen -- förslagsvis Brad Stowers utmärkta sådan som kan hämtas från Delphi Free Stuff. Delphi 1.0-användare kan i stället använda SelectDirectory().

Ångra ändringar i TEdit/TMemo

Hur implementerar jag ett ångra-kommando i en TEdit eller TMemo?

Svar:

Av någon anledning finns det ingen färdig metod för detta. Därför tvingas man ta till Windows API på följande sätt:

Memo.Perform(EM_UNDO, 0, 0);

eller

SendMessage(Memo.Handle, EM_UNDO, 0);

Ändra fokus med högerknappen

Hur gör jag så att man kan använda högerknappen för att byta fokus i en dialog?

Svar:

Lägg till en handler för OnMouseDown. Kolla där vilket fönster (alla kontroller som kan få fokus är ju egentligen fönster) som är vid musmarkören med hjälp av WindowFromPoint och använd slutligen SetFocus för att fokusera kontrollen.

if Button = mbRight then
   SetFocus(WindowFromPoint(TPoint(MAKELONG(X,Y))));

Databaser

"Lock file has grown too large"

Vad betyder felmeddelandet "Lock file has grown too large" och hur får jag bort det?

Svar:

Detta är ett Paradox-specifikt problem och löses på följande sätt:

  1. Skapa tre nya underkataloger till den katalog som programfilen befinner sig i; TABLES, PRIV och NET.
  2. Sätt sessionens privata katalog till PRIV-katalogen.
    Session.PrivateDir := ExtractFilePath(ParamStr(0)) + 'PRIV';
    
  3. Sätt sessionens nätverkskatalog till NET-katalogen.
    Session.NetFileDir := ExtractFilePath(ParamStr(0)) + 'NET';
    
  4. Om LOCAL SHARE är påsatt i BDE Configuration och om du inte delar tabeller mellan olika program på samma gång, ändra LOCAL SHARE till false.

Aktuell position i tabell

Hur tar jag reda på var någonstans i en tabell som databascursorn befinner sig?

Svar:

Följande funktion fungerar för alla dBase- och Paradox-tabeller, ej SQL. Om din SQL-server ändå stöder detta, måste du göra direkta anrop mot dess API. Fungerar för alla arvtagare till TDataSet, alltså TTable, TQuery och TStoredProc - men även andra tredjepartskomponenter.

function RecordNumber(Dataset: TDataSet): Longint;
var
   CursorProps: CurProps;
   RecordProps: RECProps;
begin
   { Låt defaultreturvärdet vara 0 }
   Result := 0;
   with Dataset do
   begin
      { Är tabellen öppen? }
      if State = dsInactive then 
         DBError(SDataSetClosed);
      Check(DbiGetCursorProps(Handle, CursorProps));
      UpdateCursorPos;
      Check(DbiGetRecord(Handle, dbiNOLOCK, nil, @RecordProps));
      case CursorProps.iSeqNums of
         0: Result := RecordProps.iPhyRecNum;  { dBase   }
         1: Result := RecordProps.iSeqNum;     { Paradox }
      end;
   end;
end;

Annan textfärg i DBGrid

Hur kan jag välja vilken färg som ska visas i en cell i TDBGrid? Jag vill t.ex. att alla negativa tal ska skrivas ut med rött.

Svar:

Se till att den är owner-draw och lägg till följande kod i OnDrawDataCell:

if Table1.SelectedField.AsFloat < 0 then
   DBGrid1.Canvas.Font.Color := clRed
else 
   DBGrid1.Canvas.Font.Color := clBlack;
DBGrid1.DefaultDrawDataCell(Rect, Field, State);

Ansluta till AS/400

Vad finns det för möjligheter och verktyg för att ansluta Delphi-program till AS/400-databaser?

Svar:

ClientObjects/400 är en fristående och kompetent produkt som påstås ge högre prestanda än ODBC. Medföljer gör även ett anpassat rapportverktyg. Mer information finns på http://www.missystems.com/~almalek/co400.htm.

Antal poster i en tabell

Jag vill ha en mätare som talar om hur långt jag kommit i en databassökning (ej SQL). Kan jag ta reda på hur många poster som tabellen har utan att gå igenom hela tabellen och öka en räknare?

Svar:

Egenskapen RecordCount för TDataSet med arvtagare returnerar just detta. Problemet är att resultatet inte alltid stämmer för dBase-tabeller, RecordCount talar om hur många poster som är allokerade. Om du tar bort en post i en tabell tas den inte bort fysiskt, utan markeras bara som icke använd (som en hårddisk alltså). Har du tagit bort många poster sedan tabellen sist packades kan RecordCount komma att ge ett dåligt värde. För Paradox-tabeller returnerar den ett korrekt värde.

BDE över nätverket

Mitt program använder BDE och Paradox-tabeller och ska köras över nätverk. Hur gör jag detta på bästa sätt?

Svar:

  1. Skapa en katalog på servern, t.ex. F:\PDOXDATA.
  2. Se till att alla användare har både skriv- och läsrättigheter i denna katalog.
  3. Installera BDE på varje maskin eller på servern (se nedan).
  4. Sätt NET DIR i BDE Configuration att peka på den katalog du skapat på servern.

Notera:

[Borland Language Drivers]
LDPath=F:\IDAPI\LANGDRV

[IDAPI]
DLLPATH=F:\IDAPI
CONFIGFILE01=H:\DB\IDAPI.CFG

Databas-VBX i Delphi

Jag har ett antal VBXer för databaser, kan jag använda dessa i Delphi? Går det att använda dem tillsammans med Delphis data-komponenter?

Svar:

Du kan i vissa fall använda dem i Delphi, ja, men inte tillsammans med de i Delphi inbyggda TTable, TDBMemo m.fl. Men undvik detta, använd de befintliga komponenterna i stället. Du får bättre integration och det fungerar bättre. Tänk också på att Delphi endast klarar VBXer från Visual Basic 1.0. Prestandamässigt blir det nog ingen större skillnad om du använder VBXerna för att komma åt t.ex. Access-databaser, som inte kan användas direkt med BDE, men används dessa för Paradox- eller dBase-tabeller går det naturligtvis mycket snabbare om du använder Delphis inbyggda, då de inte går via ODBC utan stöder dessa databasformat direkt.

EDatabaseError $2C09

Varför får jag felmeddelandet EDatabaseError $2C09 när jag försöker starta mitt program under Windows 3.1? Samma program fungerar utmärkt under Win95/NT.

Svar:

Du har inte laddat SHARE i AUTOEXEC.BAT. SHARE krävs för fildelning av lokala filer.

EmptyTable ger EDBEngineError

Jag får ett undantag av typen EDBEngineError med meddelandet "Table busy" när jag försöker köra EmptyTable för att tömma en tabell. Inget annat program använder sig av tabellen.

Svar:

För att EmptyTable ska kunna köras, måste tabellen öppnas i exklusivt läge, dvs bara en klient får tillgång till den. Använd try...finally för att vara säker på att du stänger den exklusiva tabellen och öppnar den i vanligt läge. Tänk också på att tabellen inte får vara öppen i Delphi IDE, i sådana fall kan tabellen inte öppnas exklusivt.

Table.Close;
Table.Exclusive := true;
try
   try
      Table.Open;
   except
      ShowMessage('Tabellen används redan av en annan användare!');
   end;
   Table.EmptyTable;
finally
   if Table.Active then
      Table.Close;
   Table.Exclusive := False;
   Table.Open;
end;

EOF och BOF i tabell

Jag försöker använda TTable.BOF och TTable.EOF för att avgöra om man är i början eller i slutet av tabellen. Men om jag har två poster, är i början av tabellen, och anropar Next så returnerar EOF fortfarande False. Varför?

Svar:

Eftersom BOF och EOF bara returnerar True om ett anrop till Next eller Prior har misslyckats. Om du anropar Next ytterligare en gång kommer du finna att EOF returnerar True.

Exklusivt öppnad tabell

Jag ämnar tömma en tabell och måste därför sätta Exclusive till true. När jag försöker öppna tabellen igen får jag ett felmeddelande som säger att tabellen används och inte kan öppnas i exklusivt läge. Jag kör inget annat program som använder den tabellen. Varför detta problem?

Svar:

Troligen är tabellen öppnad i Delphi-miljön. Sätt Active till false i Object Inspector och kompilera om. Även Delphi IDE räknas som program.

Flyttal med TQuery och Oracle

Jag använder Delphi 2.0 för att ansluta till en Oracle Workgroup Server via SQL Links. När jag använder TQuery för att returnera ett live-result set kan jag inte ändra eller lägga till fält med AsFloat, talen avrundas bara till närmaste heltal.

Svar:

Detta är en bugg i TQuery eller SQL Links.

Query.SQL.Add('SELECT * FROM ARTIKLAR');
Query.Open;
Query.Insert;
Query.FieldByName('NAME').Value := 'GUMMIANKA';
Query.FieldByName('PRICE').Value := 23.90;
Query.Post;

Detta fungerar alltså inte, priset kommer att avrundas till 24.00. Använd i stället följande:

Query.Close;
Query.SQL.Clear;
Query.SQL.Add('INSERT INTO ARTIKLAR (NAME, PRICE) ' + 
   'VALUES (''GUMMIANKA'', 23.90)');
Query.ExecSQL;

Ett annat alternativ är att använda TTable.

Få bort ReportSmiths splash screen

Går det att få bort den splash screen som visas när ReportSmith startas?

Svar:

Gå in i RPTSMITH.INI och ändra följande:

[ReportSmith]
ShowAboutBox=0

Om du i stället kör run-time-versionen ska du ändra RS_RUN.INI enligt:

[RS_Runtime]
ShowAboutBox=0

Fånga key violation

Jag har en Paradox-tabell med det första fältet indexerat. När jag lägger till en ny post vill jag kunna ta reda på om det blir en key violation, hur går detta till?

Svar:

Använd Delphis utmärkta undantagshantering. Lägg till try just innan du anropar InsertRecord (eller den kod som lägger till posten) och därefter except.

try
   Table.InsertRecord(['123','456']);
except
   ShowMessage('Key violation!');
end;

Grid index out of range

Jag håller på med ett master-detail-formulär. Varför får jag undantaget 'Grid index out of range' när jag försöker byta till nästa post med Next?

Svar:

Detta är ett vanligt problem, ett flertal lösningar finns på http://www.dataweb.nl/~r.p.sterkenburg/bugs2/database.htm#TDBGridMM. Dessa kräver dock att du har VCL-källkoden.

Gömma fält i DBGrid

Hur döljer man tillfälligt fält som man inte vill visa i en DBGrid?

Svar:

Alla fält har en egenskap Visible som talar om huruvida fältet ska visas eller ej. Använd Fields[] eller FieldByName för att få rätt TField.

Table.FieldByName('NAMN').Visible := False;

eller

Table.Fields[3].Visible := False;

Index i efterhand

Jag vill konvertera ascii-tabeller till Paradox och efteråt sätta ett index på ett fält i Paradox-tabellen. Jag har hört att när indexet ska läggas till, kommer alla poster om inte stämmer med det att tas bort. Stämmer det, om ja - finns det en lösning?

Svar:

För själva konverteringen används lämpligen TBatchMove. Tala om från början vilka fält som ska finnas och vilken typ de ska vara och ställ in AbortOnKeyViol till vad du tycker är bäst - true avbryter vid en key-violation, false flyttar alla key-violation-poster till tabellen som du specifierar i egenskapen KeyViolTableName. Motsvarande AbortOnProblem och ProblemTableName också för hantering av fatala fel.

Interna begränsingar i BDE

Vad finns det för interna begränsningar i BDE?

Svar:

Klienter i systemet48
Sessioner per klient32
Öppna databaser per session32
Laddade drivrutiner på samma gång32
Sessioner i systemet64
Cursors per session4000
Lösenord per session100
Objekt i felstacken16
Låsningar av objekt av samma typ per cursor127

Lista på alla alias

Hur tar jag reda på vilka alias som finns installerade i systemet?

Svar:

Använd Session.GetAliasNames och skicka en TStringList som parameter.

Logga på Interbase i kod

Jag använder en Interbase-databas i ett av mina program. Hur undviker jag att login-dialogen kommer upp, och i stället ge användarnamn och lösenord i kod?

Svar:

I TDatabase finns en egenskap som heter Params. Där kan du t.ex. ange användarnamn och lösenord. Detta kan du göra när som helst innan databasen försöker öppnas, men enklast är att lägga det i TDatabase.OnLogin. Då ska du modifiera LoginParams, som skickas som parameter till OnLogin.

procedure TMain.DatabaseLogin(Database: TDatabase;LoginParams: TStrings);
begin
   LoginParams.Values['USER NAME'] := 'JAMES';
   LoginParams.Values['PASSWORD']:= 'FOOBAR';
end;

Av säkerhetsskäl bör du dock inte använda denna metod, men den kan ibland vara nödvändig. Du kan förstås också göra så här för att visa en egen login-dialog.

Lägga till BDE-alias

Hur lägger jag till BDE-alias i mina program?

Svar:

DbiAddAlias är en IDAPI-funktion för att utföra detta. Funktionen finns i unit DbiProcs. Definitionen är enligt följande:

DbiAddAlias(hCfg: hDBICfg; pszAliasName, pszDriverType, 
   pszParams: PChar; bPersistent: Bool): DBIResult;

hCfg

Måste vara nil under BDE 2.5. Även under 2.0 (den version som skickas med Delphi 1.0) bör den vara det, men detta är inget krav.

pszAliasName

Pekare på namnet på det alias som du vill skapa.

pszDriverType

En pekare på den drivrutinstyp som ska användas. Kan vara nil, szPARADOX, szDBASE, szASCII eller 'INTRBASE'. Notera att de som börjar med sz är konstanter som är deklarerade i unit DbiTypes. Om den är nil kommer drivrutinstypen inte att specificeras.

pszParams

Den enda giltiga parametern för Paradox, dBase och ASCII tabeller är PATH. Den ska då innehålla sökvägen till databasen. För InterBase-databaser finns fler val; SERVER NAME och USER NAME. Se exemplet nedan för information om hur man använder det.

bPersistent

Talar om huruvida aliaset ska vara lokalt för denna programinstans eller om det ska sparas i IDAPI.CFG och göras tillgänglig för alla program och senare instanser av programmet. Om detta är FALSE kommer aliaset att raderas när programmet avslutas.

Result := DbiAddAlias(nil, 'Foobar', szParadox, 'PATH:c:\foo', true);

Lösenord till tabeller

Jag använder en tabeller som har lösenord, men varje gång jag försöker ansluta till dem måste användaren skriva in rätt lösenord. Kan jag undvika detta?

Svar:

Det finns en relativt okänd komponent som heter TSession. När du har tabeller i ditt program skapas automatiskt en instans av denna komponent, kallad Session. Den har ett antal bra funktioner/egenskaper som gäller alla tabeller som används av programmet. Använd AddPassword och RemovePassword för att åstadkomma det du önskar.

Session.AddPassword('Passwd');

MS SQL Server under Windows NT 4.0

Varför får jag felmeddelandet "DB-Library network communications layer not loaded" när jag försöker ansluta till MS SQL Server 6.5 under Windows NT 4.0?

Svar:

Installera SQL Server klient-programmen från SQL Server-CDn. Använd klientinställningsprogrammet och välj protokoll; named pipes (DBNMP3.DLL), TCP/IP (DBMSSOC3.DLL) eller IPX/SPX (DBMSSPX3.DLL).

Mer information om MS SQL Server finns hos Ask the SQL Server Pro.

Mätare för TQuery

Hur gör man för att med en mätare visa hur långt en TQuery kommit i att hämta resultaten från en fråga:

Svar:

Detta går inte, det strider mot hela idén med SQL. Klienten ska helt avlastas och servern ska jobba i bakgrunden. En sådan feature skulle bara vara möjlig om SQL-servrarna hade stöd för detta, men så är inte fallet.

Oracle och SQL*NET 2.0 med Delphi 2.0 och Win95

När jag ska ansluta till en Oracle-databas med SQL*NET v2.0 under Windows 95 med Delphi 2 får jag felmeddelandet att VENDOR INIT DLL:en ORANT71.DLL saknas. Vad ska jag göra åt det?

Svar:

Den enda 32-bitars drivern som fungerar med ovan nämnda konfiguration är ORA72.DLL, använd den i stället. Den hör egentligen till Personal Oracle, men ska alltså användas även här. Se också till att TCP/IP-protokollet som används är "Microsoft TCP/IP" och inget annat, det är annars något som kan ge ytterligare problem. Mer information om hur man får SQL*NET v2.0 att fungera finns hos Oracle.

Skapa auto-increment i kod

Hur skapar jag ett Paradox-fält av typen auto-increment i kod?

Svar:

För detta måste du arbeta med local SQL.

with Query1 do
begin
   SQL.Clear;
   SQL.Add('CREATE TABLE "PDoxTbl.db" (ID AUTOINC,');
   SQL.Add('Name CHAR(255),');
   SQL.Add('PRIMARY KEY(ID))');
   ExecSQL;
   SQL.Clear;
   SQL.Add('CREATE INDEX ByName ON "PDoxTbl.db" (Name)');
   ExecSQL;
end;

Detta var förstås bara ett exempel, du får själv byta ut och lägga till fält och index. CREATE TABLE är mer ingående beskrivet på sidan 180 (190 i Acrobat-versionen) i Database Application Developer's Guide (som alltså även finns i Acrobat-format på Delphi CDn under \MANUALS).

Spara frågeresultat

Hur kan jag spara resultatet från en fråga i en separat tabell?

Svar:

Använd en TBatchMove-komponent. Sätt Source till svarstabellen och Destination till namnet på tabellen som ska skapas. Sätt lämpligen Mode till batCopy och anropa slutligen Execute.

Spara ändringar i tabell

När mitt program kraschar händer det att de ändringar jag gjort i en tabell inte skrivs till disk och därmed går förlorade. Finns det något sätt att tvinga ändringar i en tabell att skrivas till disk?

Svar:

Funktionen DbiSaveChanges gör precis detta.

Error := DbiSaveChanges(Table.Handle);

DbiProcs och DbiTypes måste läggas till i Uses-satsen. Denna funktion fungerar ej på SQL-tabeller.

SQL-datum med MS Access

Jag kör SQL mot en Access-databas. Trots att jag använder datum enligt standardformatet dd/mm/yy, får jag inga svar på frågeuttrycket. Vad är felet?

Svar:

MS Access har ett underligt sätt att hantera datum, skriv i stället i formatet #mm/dd/yy#.

Ta reda på databastyp

Hur kan jag när programmet körs ta reda på vilken databastyp som används? Jag behöver veta detta då SQL-syntaxen skiljer sig något.

Svar:

Använd IDAPI-anropet DbiGetDatabaseDesc. Funktionen tar två parametrar, den första en PChar med aliasnamnet, den andra en DBDrsc (kallad pDatabase i exemplet nedan). Inkludera unit DbiProcs och DbiTypes och anropa funktionen enligt

DbiGetDatabaseDesc('FOO', pDatabase);
if pDatabase.szDbType = 'INTRBASE' then
   ShowMessage('Databasen är Interbase');

DbiGetDatabaseDesc returnerar DBIERR_OBJNOTFOUND om aliaset inte kunde hittas.

Tabellistan från GetTableNames uppdateras inte

Jag använder GetTableNames för att ta reda på vilka tabeller som finns under ett alias. I programmet lägger jag till en tabell, men den finns inte med när jag gör ett nytt anrop till GetTableNames. Varför?

Svar:

Detta beror på databasmotorn BDE, som när programmet startas tar reda på alla tabeller som finns i katalogen som ett alias pekar på. Sedan kan du göra vad du vill med de tabellerna, men listan uppdateras ändå inte förrän programmet startas om. Det bör dock finnas ett BDE-anrop som fixar detta. Om du bara vill veta vilka filer som finns i en katalog är det enkelt att använda FindFirst och FindNext.

Undvika DBGrid-automatik

När man står på sista raden i en DBGrid och trycker på nedpil läggs automatiskt en post till. Kan detta undvikas?

Svar:

Ja, lägg till följande kod i event-handlern för formulärets OnKeyPress:

if Key = VK_DOWN then
begin
   Table.DisableControls;
   Table.Next;
   if Table.EOF then
      Key := #0
   else
      Table.Prior;
   Table.EnableControls;
end;

Uppdatera tabell

Jag kör ett Delphi-program som använder tabeller över nätverket. Hur ska jag göra för att uppdatera alla data-aware komponenter?

Svar:

Det finns ett DBI-anrop för detta, DbiCheckRefresh. Den kollar om tabellerna förändrats av andra användare, och uppdaterar då det som behövs. Anrop till denna funktion (som kräver att du inkluderar DbiProcs och gärna DbiTypes) kan t.ex. göras via en timer. Ett annat alternativ är automatisk uppdatering genom att installera en callback med DbiRegisterCallback.

Uppdateras tabellen eller inte?

De ändringar jag gör i en tabell märks inte förrän programmet startas om, trots att jag använder DbiSaveChanges för att spara ändringarna till disk. De som använder programmet blir osäkra på om en uppdatering verkligen sker. Lösning?

Svar:

Den enda lösningen jag känner till är att stänga tabellen och sedan öppna den igen. DbiForceReread eller DbiCheckRefresh skulle också kunna fungera, speciellt om tabellen ligger på nätverket och flera användare utnyttjar den. Om den körs över nätet är det också bäst att dubbelkolla att NETDIR pekar på samma katalog på servern på alla klienter.

Wildcardsökningar med SQL

Går det att göra wildcardsökningar med SQL?

Svar:

Ja, använd tecknena % (för att matcha ett valfritt antal tecken) och _ (ett valfritt tecken) tillsammans med LIKE.

SELECT NAME
FROM CUSTOMER
WHERE NAME LIKE 'M%'

Matchar alla poster där kolumnen NAME börjar på 'M'.

Ändra fältordning

Hur gör man för att programmatiskt ändra ordningen av fält i en DBGrid?

Svar:

Ändra egenskapen TField.Index och tilldela den det index bland fälten du vill att det ska ha.

NameField.Index := 0;

Windows API

"Kan inte läsa enhet X"

Jag vill kolla hur många giltiga enheter som systemet har. Hela tiden när jag kollar om det går att byta till en enhet, kommer hela tiden Windows felmeddelande om att det inte går att läsa enhet X upp. Hur undviker man detta?

Svar:

Funktionen SetErrorMode talar om för Windows hur interrupt 24h ska hanteras. Kombinera fritt följande värden:

SEM_FAILCRITICALERRORS

Visar inte någon dialog när kritiska fel inträffar.

SEM_NOGPFAULTERRORBOX

Visar ingen dialog när skyddsfel (GPF) inträffar. Bör enbart används av avlusningsprogram.

SEM_NOOPENFILEERRORBOX

Ingen dialog visas när Windows inte kan hitta en fil. Denna bör användas i detta fall.

Viktigt är att se till att det tidigare värdet återställs, detta görs lämpligen med undantagshantering enligt nedan:

OldErrorMode := SetErrorMode(SEM_NOOPENFILEERRORBOX);
try
   ...
   ...
finally
   SetErrorMode(OldErrorMode);
end;

Aktivt fönster i Delphi 2.0

Jag vill ta reda på vilket fönster som är aktivt i Delphi 2.0. Varför fungerar inte GetActiveWindow (det returnerar bara 0)?

Svar:

Av säkerhetsskäl fungerar inte GetActiveWindow under Win32 på andra fönster än dina egna. Testa i stället:

hWindow := GetWindow(Handle, GW_HWNDFIRST);

eller

hWindow := GetTopWindow(GetDesktopWindow);

Detta gäller även för GetWindowRect. Notera att dessa funktioner fungerar i 16-bit program under Win32, men inte med 32-bit program.

Anpassa fönsterstorlek efter aktivitetsfältet

Hur tar jag reda på hur stort aktivitetsfältet i W95/NT4 och anpassar fönsterstorleken efter den?

Svar:

Följande funktion ordnar detta, som enda parameter tar den ditt huvudformulär. Fungerar oavsett på vilken kant aktivitetsfältet befinner sig.

procedure SizeForTaskBar(MyForm: TForm);
var
   TaskBarHandle: HWnd;    { Handle till aktivitetsfältet }
   TaskBarCoord:  TRect;   { Aktivitetsfältets koordinater }
   CxScreen,               { Skärmbredd, pixels }
   CyScreen,               { Skärmhöjd, pixels }
   CxFullScreen,           { Klientområdets bredd, pixels }
   CyFullScreen,           { Klientområdets höjd, pixels }
   CyCaption:     Integer; { Höjden för en titelrad, pixels }
begin
   TaskBarHandle := FindWindow('Shell_TrayWnd', nil);
      { Aktivitetsfältets handle hämtat }
   if TaskBarHandle = 0 then { Win95/98/NT4 körs inte, så vi behöver }
                             { bara maximera fönstret                }
      MyForm.WindowState := wsMaximized
   else { Vi kör Win95/98/NT4 }
   begin
      MyForm.WindowState := wsNormal;
      GetWindowRect(TaskBarHandle,TaskBarCoord); { Hämta aktivitets-   }
                                                 { fältets koordinater }
      { Hämta och beräkna diverse koordinater för skärmen etc }
      CxScreen      := GetSystemMetrics(SM_CXSCREEN);
      CyScreen      := GetSystemMetrics(SM_CYSCREEN);
      CxFullScreen  := GetSystemMetrics(SM_CXFULLSCREEN);
      CyFullScreen  := GetSystemMetrics(SM_CYFULLSCREEN);
      CyCaption     := GetSystemMetrics(SM_CYCAPTION);
      MyForm.Width  := CxScreen - (CxScreen - CxFullScreen) + 1;
      MyForm.Height := CyScreen - (CyScreen - CyFullScreen) + CyCaption + 1;
      MyForm.Top    := 0;
      MyForm.Left   := 0;
      if (TaskBarCoord.Top = -2) and (TaskBarCoord.Left = -2) then 
         { Aktivitetsfältet är antingen till vänster eller högst upp }
      begin
         if TaskBarCoord.Right > TaskBarCoord.Bottom then { Högst upp }
            MyForm.Top  := TaskBarCoord.Bottom
         else { Aktivitetsfältet är till vänster }
            MyForm.Left := TaskBarCoord.Right;
      end;
   end;
end;

Avsluta annat program

Hur gör jag för att avsluta ett annat program om jag har dess fönster-handle?

Svar:

Använd

PostMessage(Handle, WM_QUIT, 0, 0);

eller

PostMessage(Handle, WM_CLOSE, 0, 0);

Enhetstyp med GetDriveType

Hur tar jag reda på vad olika enheter är för typ (hårddisk, diskett, CD-ROM osv)?

Svar:

För detta finns funktionen GetDriveType. Som enda parameter tar den en integer som talar om vilken enhet det gäller, 0=A:, 1=B: osv. Returvärdet kan vara antingen DRIVE_REMOVABLE (mediet kan tas ut, t.ex. en diskettenhet), DRIVE_FIXED (en fast enhet, vanligen en hårddisk) eller DRIVE_REMOTE (nätverksenhet). Detta gäller Win16 och Delphi 1.0. Win32 tar i stället som parameter, sökvägen till rotkatalogen, och returnerar även DRIVE_CDROM och DRIVE_RAMDISK.

Vid fel returneras 0, under Win32 även 1, om det inte finns någon rotkatalog.

Under Win16 finns ett problem med denna funktion, den returnerar DRIVE_REMOTE för CD-ROM-enheter. Detta beror på en bugg i MSCDEX. Bugg är visserligen inte heller rätt, DOS uppförande mot CD-ROM:en krävde detta beteende.

ExitWindows fungerar inte

Jag försöker använda ExitWindows för att starta om Windows, men Windows avslutas bara utan att startas om. Är det något fel på min Windows-installation?

Svar:

Nej, däremot en bugg i dokumentationen. Där påstås att parametern Reserved (som måste vara 0) ska vara först och därefter ReturnCode som talar om att Windows ska startas om. Kasta om parametrarna och det fungerar fint. Om avsikten bara är att avsluta Windows ska denna parameter vara noll (och det var också därför som Windows bara avslutades i fallet ovan). Om du använder Delphi 2.0 skall i stället ExitWindowsEx användas.

ExitWindows(EW_REBOOTSYSTEM, 0);   { Startar om hela datorn  }
ExitWindows(EW_RESTARTWINDOWS, 0); { Startar bara om Windows }
ExitWindows(0, 0);                 { Avslutar Windows        }

Med ExitWindowsExec kan du avsluta Windows, köra ett program och sedan köra igång Windows igen.

Flytta filer till Papperskorgen

Hur flyttar man filer till Papperskorgen under W95/NT4 och Delphi 2.0?

Svar:

function RecycleFile(FileToRecycle: TFilename): Boolean;
var 
   Struct: TSHFileOpStruct;
   pFromc: array[0..255] of Char;
   Resultval: Integer;
begin
   if not FileExists(FileToRecycle) then 
   begin
      RecycleFile := False;
      exit;
   end
   else 
   begin
      FillChar(pfromc,SizeOf(pfromc),0);
      StrPCopy(pfromc,ExpandFilename(FileToRecycle) + #0#0);
      Struct.wnd := 0;
      Struct.wFunc := FO_DELETE;
      Struct.pFrom := pFromC;
      Struct.pTo   := nil;
      Struct.fFlags:= FOF_ALLOWUNDO;
      Struct.fAnyOperationsAborted := False;
      Struct.hNameMappings := nil;
      Resultval := ShFileOperation(Struct);
      RecycleFile := (Resultval = 0);
   end;
end;

Flytta muspekaren

Hur kan jag flytta muspekaren till valfri position på skärmen?

Svar:

Använd API-anropet SetCursorPos. Som parametrar anger du helt enkelt koordinaterna i x- och y-led.

SetCursorPos(100, 150);

Få reda på förändringar i Urklipp

Jag har ett program med en texteditor och ett verktygsfält i. Hur bär jag mig bäst åt för att ändra knapparna och menyerna efter vad användaren markerar och efter innehållet i Urklipp?

Svar:

Gör för det första en gemensam handler för OnKeyPress, OnMouseUp och OnChange som kollar med Clipboard.HasFormat(CF_TEXT) så att innehållet i Urklipp finns som text. Använd sedan SetClipboardViewer för att lägga till ditt fönster i den kedja av fönster som får reda på genom att WM_DRAWCLIPBOARD när Urklippet förändrats. Funktionens returnvärde är en hWnd som du använder i handlern för WM_DRAWCLIPBOARD och skickar meddelandet vidare till det fönstret. Kom ihåg att skicka WM_CHANGECBCHAIN när du inte längre behöver få meddelandet (lämpligen i OnDestroy eller OnClose).

Fånga in bild av skrivbordet

Hur fångar man in en bild av skrivbordet så att jag kan visa det i ett av mina fönster eller spara undan som en bitmap?

Svar:

procedure TScrnFrm.GrabScreen;

var
   DeskTopDC: HDC;
   DeskTopCanvas: TCanvas;
   DeskTopRect: TRect;
    
begin
   DeskTopDC := GetWindowDC(GetDeskTopWindow);
   DeskTopCanvas := TCanvas.Create;
   DeskTopCanvas.Handle := DeskTopDC;
   DeskTopRect := Rect(0, 0, Screen.Width, Screen.Height);
   ScrnForm.Canvas.CopyRect(DeskTopRect, DeskTopCanvas, DeskTopRect);
   ReleaseDC(GetDeskTopWindow, DeskTopDC);
end;

Förhindra Alt+Ctrl+Del

Jag håller på med ett säkerhetsprogram där det krävs att jag kan förhindra användaren från att växla till ett annat program eller starta om datorn. Hur?

Svar:

Om det räcker med att förhindra programbyte kan du använda dig av en CBT-hook, slå upp SetWindowsHookEx i hjälpen. Denna förhindrar dock inte att användaren trycker Alt+Ctrl+Del och stänger av ditt program. För att även förhindra detta måste du skriva en kerneldrivrutin. Skaffa Windows 95/NT SDK/DDK och sök på Internet efter CTRL2CAP, det är ett exempelprogram som ändrar Ctrl-tangenten till Caps Lock.

Genomskinliga bitmaps ovanpå varandra

Hur ritar jag två genomskinliga bitmaps ovanpå varandra?

Svar:

Följande procedur ordnar detta. Parametrar:

t

Den TCanvas som bitmappen ska ritas på.

x, y

Koordinaterna för övre vänstra hörnet.

s

Bitmappen som ska ritas.

TrCol

Den färg som ska vara genomskinlig.

OBS! Glöm inte bort att rita om mål-bitmappen med t.ex. Image1.Invalidate.

procedure DrawTransparent(t: TCanvas; x,y: Integer; 
   s: TBitmap; TrCol: TColor);
var
   bmpXOR, bmpAND, bmpINVAND, bmpTarget: TBitmap;
   oldcol: Longint;
begin
   try
      bmpAND := TBitmap.Create; 
      bmpAND.Width := s.Width; 
      bmpAND.Height := s.Height;
      bmpAND.Monochrome := True;
      oldcol := SetBkColor(s.Canvas.Handle, ColorToRGB(TrCol));
      BitBlt(bmpAND.Canvas.Handle, 0, 0, s.Width, s.Height, 
         s.Canvas.Handle, 0, 0, SRCCOPY);
      SetBkColor(s.Canvas.Handle, oldcol);
      bmpINVAND := TBitmap.Create; 
      bmpINVAND.Width := s.Width; 
      bmpINVAND.Height := s.Height; 
      bmpINVAND.Monochrome := True;
      BitBlt(bmpINVAND.Canvas.Handle, 0, 0, s.Width, s.Height, 
         bmpAND.Canvas.Handle, 0, 0, NOTSRCCOPY);
      bmpXOR := TBitmap.Create; 
      bmpXOR.Width := s.Width; 
      bmpXOR.Height := s.Height;
      BitBlt(bmpXOR.Canvas.Handle, 0, 0, s.Width, s.Height, 
         s.Canvas.Handle, 0, 0, SRCCOPY);
      BitBlt(bmpXOR.Canvas.Handle, 0, 0, s.Width, s.Height, 
         bmpINVAND.Canvas.Handle, 0, 0, SRCAND);
      bmpTarget := TBitmap.Create; 
      bmpTarget.Width := s.Width; 
      bmpTarget.Height := s.Height;
      BitBlt(bmpTarget.Canvas.Handle, 0, 0, s.Width, s.Height, 
         t.Handle, x, y, SRCCOPY);
      BitBlt(bmpTarget.Canvas.Handle, 0, 0, s.Width, s.Height, 
         bmpAND.Canvas.Handle, 0, 0, SRCAND);
      BitBlt(bmpTarget.Canvas.Handle, 0, 0, s.Width, s.Height, 
         bmpXOR.Canvas.Handle, 0, 0, SRCINVERT);
      BitBlt(t.Handle, x, y, s.Width, s.Height, 
         bmpTarget.Canvas.Handle, 0, 0, SRCCOPY);
   finally
      bmpXOR.Free;
      bmpAND.Free;
      bmpINVAND.Free;
      bmpTarget.Free;
   end;
end;

Giltigt fönster-handle?

Kan jag på ett enkelt sätt ta reda på om ett fönster-handle är giltigt?

Svar:

Javisst, använd IsWindow():

if IsWindow(Form.Handle) then
   ShowMessage('Giltigt!');

Gå förbi GDI vid utskrift

Hur kan jag gå förbi GDI vid utskrifter och skicka rådata direkt till skrivaren?

Svar:

Använd API-funktionen Escape.

Escape(Printer.Handle, PASSTHROUGH, 0, @Buffer, nil);

Buffer pekar först på en 16-bitars bufferlängd, därefter de data du vill skriva.

Hur man använder GetVersion

Hur använder man GetVersion för att ta reda på versionen av DOS och Windows?

Svar:

Funktionen returnerar en Longint. I den ligger alla versiontal på varandra och kan återfås med anrop till Hiword, Loword, Hi och Lo. Det första (låga) ordet, d.v.s. de första 16 bitarna innehåller versionen av Windows. I den finns huvudversionsnumret (3 för Windows 3.1) i den låga byten och underversionsnumret i den höga. DOS-versionen ligger i det höga ordet, men här är ordningen omkastad, den höga byten innehåller alltså huvudversionen och den låga underversionen.

DosVer := IntToStr(Hi(Hiword(GetVersion))) + '.' + IntToStr(Lo(Hiword(GetVersion)));
WinVer := IntToStr(Lo(Loword(GetVersion))) + '.' + IntToStr(Hi(Loword(GetVersion)));

Win32-användare kan också använda GetVersionEx() för att få reda på mer ingående information.

Hämta ikon från ett program

Hur hämtar jag en ikon i en ICO-fil eller en som ligger inbäddad i en EXE-, DLL- eller CPL-fil?

Svar:

Funktionen ExtractIcon (unit ShellAPI) fungerar för alla ovanstående. Du får ikonen som ett handle som du sedan t.ex. kan tilldela en TImage.

IconHandle := ExtractIcon(hInstance, 'C:\WINDOWS\FOO.EXE', 0);

Ovanstående exempel returnerar den första ikonen i FOO.EXE. Den sista parametern är ett noll-baserat index på den ikon som du vill extrahera. Genom att skicka -1 returnerar funktionen i stället hur många ikoner som filen innehåller.

Information om fonter

Jag vill ha information om de fonter som finns installerade på systemet, vad de heter och vilka egenskaper de har. Har hittat en funktion som heter EnumFontFamilies, men hur används den?

Svar:

Ett enklare sätt än EnumFontFamilies är TCanvas.Fonts och TPrinter.Fonts som alltid innehåller tillgängliga teckensnitt, men där finns enbart namnet på dem och inget annat. EnumFontFamilies är deklarerad enligt följande:

function EnumFontFamilies(DC: HDC; Family: PChar; 
   EnumProc: TFontEnumProc; Data: PChar): Integer;

DC

Handle till den device-context (TCanvas.Handle) som du vill ha fonterna ifrån. Här skickar du Form.Canvas eller Printer.Canvas. Informationen kan variera om du tar skärmen eller skrivaren.

Family

Denna funktion fyller två syften; dels kan du få information om alla varianter (fet, kursiv, fet-kursiv osv.) av ett teckensnitt, eller också få veta vilka teckensnittsfamiljer som är installerade. För det förstnämnda skickar du namnet på teckensnittfamiljen, för att få alla teckensnitt skickar du i stället nil.

EnumProc

Innehåller adressen till den funktion som Windows ska anropa för varje font (callback). Denna skapas genom ett anrop till MakeProcInstance, vilket måste göras först. Glöm inte bort att deallokera minnet med FreeProcInstance när du är klar.

Data

Om du vill skicka något till callback-funktion (t.ex. vilken del av programmet som begär informationen) så kan du ha det här. Kan också vara nil om denna funktionalitet inte behövs.

Funktionen i EnumProc ska vara av typen EnumFontFamProc, en funktion som inte är deklarerad i Pascal men som översatt från C blir på följande sätt:

function EnumFontFamProc(LogFont: PLogFont; TextMetric: PNewTextMetric; 
   FontType: Integer; Data: Longint) : Integer;

Ex.

function EnumerateFonts(LogFont: PLogFont; TextMetric: PNewTextMetric; FontType: Integer; Data: PChar) : Integer; export; begin FontList.Add(StrPas(LogFont^.lfFaceName)); EnumerateFonts := 1; end;

Exemplet ovan lägger till alla fonter i FontList (lämpligen en TStringList). Låt funktionen returnera 1 om du vill fortsätta få information via callbacken, om du är klar returnerar du i stället 0.

Exemplet nedan visar hur man allokerar minne för callbacken och gör anropet till EnumFontFamilies:

var
   EnumProc: TFarProc;
begin
   EnumProc := MakeProcInstance(@EnumerateFonts, Application.Handle);
   EnumFontFamilies(Canvas.Handle, nil, EnumProc, nil);
   FreeProcInstance(EnumProc);
end;

Detta förutsätter förstås att din callback heter just EnumerateFonts.

Ingen deallokering vid programavslut

Ett program jag gjort städar inte alltid upp efter sig ordentligt, filer blir kvar på disken och ändringar i databastabeller verkar inte skrivas till disk. Varför?

Svar:

Detta är en bugg i Delphi som har att göra med WM_ENDSESSION som Windows skickar till alla program när en användare har valt att avsluta Windows. Detta meddelanden hanteras inte VCL och alla destruktörer och exit procedures anropas därmed inte. Lösningen är att själv ta hand om meddelandet och där anropa Halt. Gör en funktion HookProc enligt nedan och anropa sedan Application.HookMainWindow(HookProc), lämpligen i projektkoden.

function HookProc(var Message: TMessage): Boolean;
begin
   Result := false;
   if Message.Msg = WM_ENDSESSION then
   begin
      if WordBool(Message.wParam) then
      begin
{ Windows ska stängas av - rensa! }
         Halt;
{ Detta borde stänga ned ordentligt, men har vi tid }
{ att hantera alla meddelanden innan Windows redan  }
{ är nere?                                          }
{      Close;} { Detta fungerar inte alltid, undvik }
    end;
  end;
end;

Körs Windows NT?

Hur kan jag avgöra vilket Windows-version som körs? Windows NT 3.1 returnerar ju samma värde på GetVersion som vanliga Windows 3.1.

Svar:

Det har tillkommit en extra flagga till GetWinFlags, WF_WINNT ($4000). Denna är på om Windows NT körs.

if (GetWinFlags and WF_WINNT) <> 0 then
   ShowMessage('Windows NT körs!');

GetWinFlags() finns inte med i Win32, där ska man i stället använda GetVersionEx() för att få motsvarande information.

Låta systemet gå i suspend

Hur gör jag för att låta datorn gå ner i suspend-/sovläge?

Svar:

Använd SetSystemPowerState(). Dess andra parameter avgör om systemet ska tvingas ner eller om det bara ska gå ner om alla program och drivrutiner tillåter det.

Lägga till dokument i Start-menyn

Hur lägger man till dokument i listan över de senaste dokumenten i Start-menyn?

Svar:

Använd ShAddToRecentDocs(). Funktionen tar två parametrar. Den första kan man i normala fall låta vara SHARD_PATH och den andra parametern är en pekare till sökvägen till dokumentet. Funktionen har inget returvärde.

Lägga till egna kommandon i systemmenyn

Hur gör jag för att lägga till egna kommandot i programmets systemmeny?

Svar:

Börja med att deklarera en unik konstant för ditt kommando, baserad på WM_USER och med en valfri offset. Kom ihåg att lägga till Messages till uses-satsen.

const
   WM_ABOUT = WM_USER + 150;

Det handle som systemmenyn har ges med GetSystemMenu(hWnd, Boolean). Använd sedan t.ex. AppendMenu för att lägga till kommandon. Detta göra lämpligen i OnCreate. I exemplet nedan läggs en avdelare till före själva kommandot.

AppendMenu(GetSystemMenu(Handle, False), MF_SEPARATOR, 0, '');
AppendMenu(GetSystemMenu(Handle, False), MF_BYPOSITION, WM_ABOUT, '&About...');

För att du ska kunna utföra något när kommanot väljs, måste du göra en egen event-handler för Application.OnMessage.

procedure TMain.WinMsgHandler(var Msg: TMsg; var Handled: Boolean);
begin
   if Msg.Message = WM_SYSCOMMAND then
   begin
      if Msg.wParam = WM_ABOUT then 
         ShowMessage('About Application 1.0');
   end;
end;

Som synes innehåller wParam den konstant som du deklarerade och valde för menykommandot. För att Delphi ska använda din nya event-handle, måste du tilldela den till Application.OnMessage, även det görs lämpligen i OnCrete för huvudformuläret.

Application.OnMessage := WinMsgHandler;

Marginaler för skrivaren

Hur kan jag ta reda på vilka fysiska marginaler en skrivare har, alltså hur stor del av sidan som skrivaren inte kan skriva ut på?

Svar:

Med Escape kan du göra anrop till skrivardrivrutinen.

var
   Margins: TPoint; { TPoint.X, TPoint.Y }
   PhysicalSize: TPoint
begin
   Escape(Printer.Handle, GETPRINTINGOFFSET, 0, nil, @Margins);
{ Den översta vänstra skrivbara punkten finns nu i Margins }
   Escape(Printer.Handle, GETPHYSPAGESIZE, 0, nil, @PhysicalSize);
{ Papprets fysiska storlek placeras i PhysicalSize }
   LeftMargin := Margins.X;
   TopMargin := Margins.X;
   RightMargin := PhysicalSize.X - GetDeviceCaps(HORZSIZE) - Margins.X;
   BottomMargin := PhysicalSize.Y - GetDeviceCaps(VERTSIZE) - Margins.Y;
end;

Maximal fönsterstorlek

Kan jag begränsa den största och minsta storlek som ett fönster får ha?

Svar:

Gör en event handler för meddelandet WM_GETMINMAXINFO enligt nedan:

private
   { Private declarations }
   procedure WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo);
      message WM_GETMINMAXINFO;

Och använd meddelanden på följande sätt:

procedure TMainForm.WMGetMinMaxInfo(var Message: TWMGetMinMaxInfo); begin with Message.MinMaxInfo^ do begin ptMaxSize.X := 640; { Maximerad bredd } ptMaxSize.Y := 96; { Maximerad höjd } ptMaxPosition.X := 0; { x-pos som maximerad } ptMaxPosition.Y := 0; { y-pos som maximerad } ptMinTrackSize.X := 500; { Minsta bredd } ptMinTrackSize.Y := 96; { Minsta höjd } ptMaxTrackSize.X := 640; { Största bredd } ptMaxTrackSize.Y := 150; { Största höjd } end; Message.Result := 0; { Tala om att du ändrat MinMaxInfo } inherited; end;

Meddela andra program om att systemfärgerna ändrats

Mitt program ändrar systemfärgerna. Hur kan jag tala om för alla andra program att ändringar genomförts och att deras fönster måste ritas om?

Svar:

Skicka meddelandet WM_SYSCOLORCHANGE till alla fönster (HWND_BROADCAST) enligt nedan.

PostMessage(HWND_BROADCAST, WM_SYSCOLORCHANGE, 0, 0);

Meddelande om menystängning

Hur kan jag ta reda på om en meny stängts (utan att något kommando valts)?

Svar:

Fånga meddelandet WM_EXITMENULOOP. Detta är bara implementerat i Win32 och gäller således bara för Delphi 2/3.

Minska flimmer vid uppdateringar

När jag lägger till ett stort antal strängar i en TListBox/TOutline/TTreeView flimrar det väldigt mycket och tar väldigt lång tid. Kan detta förhindras?

Svar:

Använd LockWindowUpdate(). Den gör att all omritning i fönstret hindras tills dess att du anropar funktionen igen. Använd try och finally för att vara säker på att detta återlämnas.

LockWindowUpdate(ListBox.Handle);
try
   ...
   ...
finally
   LockWindowUpdate(0);
end;

Endast ett fönster kan ha denna egenskap samtidigt.

Named pipes under Windows 95

Varför får jag inte named pipes att fungera under Windows 95?

Svar:

En Win95-dator kan inte fungera som server för named pipes, bara som klient.

Omdirigeringar med WinExec

Varför kan jag inte omdirigera resultatet från ett DOS-program jag startar med WinExec? Jag har försökt med

RetVal:= WinExec('c:\pgp.pif +force ' + FileName + ' -z ' + 
   InputString + ' -o ' + FileSave + ' > c:\pgp\temp.txt', 0);

men filen skapas överhuvudtaget inte.

Svar:

Omdirigeringar fungerar bara vid prompten, de hanteras av kommandotolken (normalt COMMAND.COM, CMD.EXE i Windows NT). I ditt fall skickas ' > c:\pgp\temp.txt' som parameter till programmet i stället. Det man kan göra är att starta COMMAND.COM med /C och därefter skicka kommandoraden. Växeln /C innebär att kommandotolken startas och kör ett kommando för att sedan avslutas.

ReadKey under Win32 console

Finns det ingen motsvarighet till ReadKey för Win32 console?

Svar:

Nej, det enda som fungerar rätt av är Write/Writeln och Read/Readln. En speciallösning krävs.

type TKeyShiftState = (kssRightAlt, kssLeftAlt, 
   kssRightCtrl, kssLeftCtrl, kssShift, kssNumLock, 
   kssScrollLock, kssCapsLock, kssEnhancedKey);

type TKeyShiftSet = set of TKeyShiftState;

type
   PKeyEventRecordFix = ^TKeyEventRecordFix;
   TKeyEventRecordFix = packed record
      bKeyDown: BOOL;
      wRepeatCount: Word;
      wVirtualKeyCode: Word;
      wVirtualScanCode: Word;
      case Integer of
         0: (
            UnicodeChar: WCHAR;
            dwControlKeyState: DWORD);   // Fix an error in Windows.pas
         1: (
            AsciiChar: CHAR)
   end;

var 
   VirtualKeyCode: Word;
   VirtualScanCode: Word;

{
  This function checks whether we are interested in this particular
  keypress - we want to ignore Shift,Alt,Ctrl and the ??-Lock keys.

  Use the KeyShiftState field to test for whether anyone is pressing
  these keys ...
}
function VetoOnVKCode(const VKCode: Word): Boolean;
const
  NumVetoKeys = 6;
  VetoKey: array[1..NumVetoKeys] of Word = (VK_CONTROL, VK_SHIFT,
                                            VK_MENU,    VK_CAPITAL,
                                            VK_NUMLOCK, VK_SCROLL);
var
  iKey: Integer;
begin
   for iKey := 1 to NumVetoKeys do
      if VKCode = VetoKey[iKey] then
      begin
         Result := True;
         Exit
      end;
   Result := False
end;

function ReadKey: Char;
var
   NumRead:       Integer;
   HConsoleInput: THandle;
   InputRec:      TInputRecord;
begin
{
  Get Input-handle for the console ...
}
   HConsoleInput := GetStdHandle(STD_INPUT_HANDLE);
{
  Put thread to sleep until Console-Input is available- don't waste CPU
  cycles deadlocking the machine ...
}
   repeat
      if WaitForSingleObject(HConsoleInput,INFINITE) <> 
         WAIT_OBJECT_0 then
         raise Exception.Create('Invalid handle for console input');
                        // Input buffer destroyed during Wait
      if ReadConsoleInput(HConsoleInput,
                        InputRec,
                        1,
                        NumRead) and
         (InputRec.EventType = KEY_EVENT) then
      begin
         with PKeyEventRecordFix(@InputRec.KeyEvent)^ do
         if bKeyDown and   // Input buffer contains key-presses AND
                           // key-releases. We only want the presses.
            not VetoOnVKCode(wVirtualKeyCode) then
         begin
            VirtualScanCode := wVirtualScanCode;
            VirtualKeyCode := wVirtualKeyCode;
            KeyShiftState := TKeyShiftSet(LongRec(dwControlKeyState).Lo);
            Result := AsciiChar;
            Exit;
         end;
      end;
   until False
end;

Rotera text

Hur roterar man text?

Svar:

Eftersom Delphis inbyggda TFont inte klarar detta, måste man gå via Windows API.

var
   Font: hFont;
   LogFont: TLogFont;
begin
   LogFont.lfheight := 30;
   LogFont.lfwidth := 10;
   LogFont.lfweight := FW_BOLD;
   LogFont.lfEscapement := -200;
   LogFont.lfcharset := DEFAULT_CHARSET;
   LogFont.lfoutprecision := OUT_TT_PRECIS;
   LogFont.lfquality := DRAFT_QUALITY;
   LogFont.lfpitchandfamily := FF_MODERN;
   StrCopy(LogFont.lfFaceName,'Times New Roman');
   Font := CreateFontIndirect(LogFont);
   Canvas.Font.Handle := Font;
   Canvas.TextOut(10,10, 'Roterad text');
   Canvas.Font.Name := 'Arial';
   DeleteObject(Font);
end;

Strukturen LogFont fylls med lämpliga värden, här blir det en 30pt Times New Roman Fet roterad 20 grader medsols. Från detta skapas ett GDI-objekt med CreateFontIndirect(). Denna tilldelas sedan till Canvas.Font.Handle. Texten skrivs och Canvas.Font.Name sätts till något annat. Detta för att fontobjektet Font inte får vara aktivt när det raderas med DeleteObject().

SetSysModalWindow under Win32

Jag vill göra en dialog i Delphi 2 system-modal, d.v.s. en dialog som inte tillåter att man byter till något annat fönster eller program, men det fungerar inte. Varför, samma kod går bra i Delphi 1?

Svar:

P.g.a. den högre säkerheten under Win32 tillåts inte systemmodala fönster (enkla meddelanden undantagna).

Skicka tangenter

Hur skickar man simulerade tangenttryckningar, motsvarande SendKeys i VB?

Svar:

procedure SendKeys(S: string; Window: hWnd);
var
   I: Integer;
begin
   ShowWindow(Window, SW_RESTORE);
   for I := 1 to Length(S) do
      SendMessage(Window, WM_CHAR, Ord(S1[i]), 0);
end;

Window kan du få antingen genom ett anrop till FindWindow() eller helt enkelt skicka Handle om det är ett fönster i ditt program.

Skriva ut bitmap

Hur skriver man ut en bitmap? Jag använder Printer.Canvas.Draw(100, 100, Image.Picture.Graphic), men det kommer bara ut ett vitt papper.

Svar:

Detta beror på att bilden som finns i Image är en device-dependent bitmap (DDB), vilket gör att den inte går att skriva ut på de flesta skrivare. Lösningen är att konvertera bilden till en device-independent bitmap (DIB) och använda StretchDIBits() i stället för Draw().

procedure PrintBitmap(Bitmap: TBitmap; X, Y: Integer);
var
   Info: PBitmapInfo;
   InfoSize: Integer;
   Image: Pointer;
   ImageSize: Longint;
begin
   with Bitmap do
   begin
      GetDIBSizes(Handle, InfoSize, ImageSize);
      Info := MemAlloc(InfoSize);
      try
         Image := MemAlloc(ImageSize);
         try
            GetDIB(Handle, Palette, Info^, Image^);
            with Info^.bmiHeader do
               StretchDIBits(Printer.Canvas.Handle, X, Y, Width,
                  Height, 0, 0, biWidth, biHeight, Image, Info^,
                  DIB_RGB_COLORS, SRCCOPY);
         finally
            FreeMem(Image, ImageSize);
         end;
      finally
         FreeMem(Info, InfoSize);
      end;
   end;
end;

Innan detta skickas till skrivaren kan det dock vara smart att använda GetDeviceCaps() för att ta reda på om skrivaren stöder bitmaps. Plottrar gör ju t.ex. knappast det.

Skrivarupplösning

Hur tar jag reda på en skrivares upplösning?

Svar:

Använd GetDeviceCaps enligt nedan. Notera att det finns en upplösning i X-led och en i Y-led. Normalt är dessa samma, men det bör man inte räkna med - många bläckstråleskrivare kör t.ex. med 600x300 dpi. GetDeviceCaps fungerar även för device contexts som går till skärmen, men användningsområdet är förstås rätt smalt på den fronten.

X_Upplosning := GetDeviceCaps(Printer.Handle, LOGPIXELSX);
Y_Upplosning := GetDeviceCaps(Printer.Handle, LOGPIXELSY);

Skärmupplösning

Hur tar jag reda på skärmens upplösning?

Svar:

Använd GetSystemMetrics med parametrarna SM_CXSCREEN för x-upplösning och SM_CYSCREEN för y-upplösning. Du kan också skicka en rad andra parametrar, livsviktigt om du tänkt ha ett eget non-client område för ett fönster.

Spela upp ljud

Hur spelar jag upp ljud i Windows?

Svar:

Om du vill spela upp något av de standardljud som finns inställda i Kontrollpanelen ska du använda MessageBeep. Som parameter anger du vilket sorts ljud du vill ha. Giltiga värden är MB_ICONASTERISK, MB_ICONEXCLAMATION, MB_ICONHAND (eller MB_ICONSTOP som betyder samma sak), MB_ICONQUESTION, MB_OK och -1. Det sistnämnda gör ett ljud i datorns vanliga högtalare även om ett ljudkort finns. Detta finns mer beskrivet i hjälpen.

Om du vill spela upp en valfri WAV-fil finns den något undangömda funktionen sndPlaySound(). Den finns inte i den vanliga hjälpfilen utan måste letas upp i MMSYSTEM.HLP. Syntaxen är:

sndPlaySound(lpszSoundName: PChar; uFlags: Word): Bool;

lpszSoundName pekar på filnamnet som ska spelas och är av typen PChar. uFlags är i normala fall SND_ASYNC, även om några andra alternativ finns. Det går bl.a. att lägga in ljud i resurserna för att slippa skicka med separata WAV-filer som kan ändras av slutanvändaren. Detta går till på följande sätt:

Skapa en ny RC-fil och lägg till följande i den:

WAVE1 WAVE PRELOAD FIXED PURE "FILNAMN.WAV"

Detta kommer att skapa en resurstyp som heter WAVE (kan iofs heta vad som helst). I en instans av denna typ kommer ljudfilen du anger att finnas. RC-filen kompilerar du till en RES med BRCC.EXE. Om du har Resource Workshop kan du lägga till ljuden direkt i RES-filen. För att sedan spela upp ljudet i exemplet ovan gör du följade deklarationer:

rhMySound: THandle;
pMySound: Pointer;
hMySound: THandle;

Följande använder du för att spela upp själva ljudet:

rhMySound := FindResource(HInstance, 'WAVE1', 'WAVE');
hMySound := LoadResource(HInstance, rhMySound);
try
   pMySound := LockResource(hMySound);
   try
      sndPlaySound(pMySound, SND_SYNC or SND_MEMORY);
   finally
      UnlockResource(hMySound);
   end;
finally
   FreeResource(hMySound);
end;

Glöm inte att inkludera din resursfil där du lagt ljudet med $R. Använd inte den som Delphi själv skapar, utan gör en egen.

Starta ett program och vänta på att det körs klart

Hur gör jag för att starta ett program och sedan vänta tills det kört färdigt?

Svar:

Nedan följer en klassiker, WinExecAndWait i versioner för Win16 resp. Win32. Koden är är skriven av Pat Ritchey och tagen ur Lloyd's Help File.

function WinExecAndWait(Path: string; Visibility: Word): Word;
var
   InstanceID: THandle;
   PathLen: Integer;
begin
   { Enkel konvertering från string till PChar }
   PathLen := Length(Path);
   Move(Path[1], Path[0], PathLen);
   Path[PathLen] := #00;
   { Kör programmet }
   InstanceID := WinExec(@Path, Visibility);
   if InstanceID < 32 then { ett returvärde som är mindre 
                             än 32 indikerar ett fel      }
      WinExecAndWait := InstanceID
   else 
   begin
      repeat
         Application.ProcessMessages;
      until Application.Terminated or (GetModuleUsage(InstanceID) = 0);
      WinExecAndWait := 32;
   end;
end;

function WinExecAndWait32(FileName: string; Visibility: Integer): Integer;
var
   zAppName: array [0..512] of Char;
   zCurDir: array [0..255] of Char;
   WorkDir: string;
   StartupInfo: TStartupInfo;
   ProcessInfo: TProcessInformation;
begin
   StrPCopy(zAppName, FileName);
   GetDir(0, WorkDir);
   StrPCopy(zCurDir, WorkDir);
   FillChar(StartupInfo, Sizeof(StartupInfo),#0);
   StartupInfo.cb := Sizeof(StartupInfo);
   StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
   StartupInfo.wShowWindow := Visibility;
   if not CreateProcess(nil,
      zAppName,    { pekare till kommando att köra }
      nil,         { pekare till säkerhetsattribut för processen }
      nil,         { pekare till säkerhetsattribut för tråden }
      false,       { handle inheritance flag }
      CREATE_NEW_CONSOLE or  { flaggor }
      NORMAL_PRIORITY_CLASS,
      nil,         { pekare till nytt environment block }
      nil,         { pekare till aktuell katalog }
      StartupInfo, { pekare till STARTUPINFO }
      ProcessInfo) then Result := -1 { pekare till PROCESS_INF }
   else 
   begin
      WaitforSingleObject(ProcessInfo.hProcess, INFINITE);
      GetExitCodeProcess(ProcessInfo.hProcess, Result);
   end;
end;

Stora datamängder från Urklipp

Hur kan jag få en större textmassa (eller annan data) från urklipp till en variabel?

Svar:

Funktionen du ska använda är GetAsHandle(), en medlem av TClipboard. Det som returneras du är en variabel av typen THandle, som för att kunna användas som en pekare först måste behandlas med GlobalLock(). När du är klar anropar du sedan GlobalUnlock(). Lämpligt här är att innesluta detta i ett try ... finally-block.

hClipData := Clipboard.GetAsHandle(CF_TEXT);
p := GlobalLock(hClipData);
try
   { Kod som behandlar ClipData }
finally
   GlobalUnlock(hClipData);
end;

En beskrivning av alla vanliga urklippsformat finns i API-hjälpen om du slår på "clipboard formats".

Stänga av datorn med InitiateSystemShutdown

Varför får jag inte InitiateSystemShutdown att fungera i syfte att stänga av datorn?

Svar:

InitiateSystemShutdown fungerar bara under Windows NT och används främst för att stänga av datorer över nätverket. Den lokala datorn stängs lämpligast av med ExitWindowsEx (eller ExitWindows under Win16).

Stänga ner NT

Man kan ju stänga av eller starta om datorn med ExitWindowsEx, men det fungerar inte under Windows NT, varför?

Svar:

För det första krävs det att du som användare har rättigheter att göra detta, men även att processen har dessa rättigheter. Normalt har den inte det, och du måste justera dess process token privileges så att den får tillåtelse till detta.

var
   tpPrev, tp: TTokenPrivileges;
   token: THandle;
   dwRetLen   : DWord;
begin
   OpenProcessToken(GetCurrentProcess, 
      TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, token);
   tp.PrivilegeCount := 1;
   if LookupPrivilegeValue(nil, 'SeShutdownPrivilege', 
      tp.Privileges[0].LUID) then
   begin
      tp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED
      dwRetLen := 0;
      AdjustTokenPrivileges(token, False, tp, 
         SizeOf(tpPrev), tpPrev, dwRetLen);
   end;
   CloseHandle(token);
end;

Nu står det dig fritt att anropa ExitWindowsEx(), ex.vis:

ExitWindowsEx(EWX_POWEROFF, 0);

Systemmodala dialoger

Går det att göra så att en dialog kontrollerar hela systemet; att man inte kan byta till något annat fönster eller program innan programmet stängs?

Svar:

Ja, med WinAPI-funktionen SetSysModalWindow(hWnd). Eftersom bara ett fönster kan vara systemmodalt bör du lämpligen återställa detta fönster när du är klar. Inget problem då SetSysModalWindow() returnerar det gamla fönstret.

procedure TForm1.Button1Click(Sender: TObject);
var
   OldWin: Word;
begin
   OldWin := SetSysModalWindow(Form2.Handle);
   Form2.ShowModal;
   SetSysModalWindow(OldWin) ;
end;

Systemmodala fönster fungerar bara under Win16.

Ta reda på om något DOS-program är igång

Hur kan jag ta reda på om ett DOS-program är igång?

Svar:

Alla DOS-program körs i var sin Virtual DOS Machine (VDM). Dessa VM:ar körs av WINOA386.MOD. Då kan man som med alla vanliga Windows-program kolla om de körs med ett anrop till GetModuleHandle. Om funktionen returnerar 0 (noll) körs ingen process med det namnet.

if GetModuleHandle('WINOA386.MOD') <> 0 then
   ShowMessage('DOS-program igång.');

Värt att notera är att detta enbart fungerar i avancerat läge under Windows 3.1x. Nu är det dock bara gamla 386:or med lite minne som fortfarande inte kan köra Windows 3.1 i avancerat läge, så det bör inte innebära några problem. Windows 3.11 och senare körs enbart i avancerat läge.

Vilken menyfont används?

Hur tar man reda på vilken font som används till menyerna?

Svar:

var
   NCM: TNonClientMetrics;
begin
   NCM.cbSize := SizeOf(TNonClientMetrics);
   SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @NCM, 0);
   Canvas.Font.Handle := CreateFontIndirect(NCM.lfMenuFont);
   { Fonten kommer automatiskt att frigöras }
end;

Vilken är Windows-katalogen?

Hur tar jag reda på vilken katalog som är Windows- resp. System-katalogen i anvädarens system?

Svar:

Använd GetWindowsDirectory() resp. GetSystemDirectory(). Använd inte GetWindowsDir() resp. GetSystemDir(), dessa är avsedda att användas av DOS-program och är överhuvudtaget inte implementerade under Win32.

Vilket användarnamn?

Hur tar jag reda på vad användaren har för inloggnings-ID under Win16?

Svar:

Ett alternativ är att använda BDE-funktionen dbiGetNetUserName, ännu bättre är dock den dåligt dokumenterade WinAPI-funktionen WNetGetUser. Den är inte dokumenterad i vanliga API-hjälpen och finns inte med i unit WinProcs. Importera funktionen enligt:

function WNetGetUser(User : PChar; var Len : Integer) : Integer; 
   far; external 'USER.EXE'  index 516;

Nedanstående funktion förenklar handhavandet:

function GetCurrentUser: string;
var
   cbUser: Integer;
   szUser: PChar;
   Status: Integer;
begin
   cbUser := 10;
   szUser := StrAlloc(cbUser);
   try
      Status := WNetGetUser(szUser, cbUser);
      if Status = WN_SUCCESS then
         GetCurrentUser := StrPas(szUser)
      else
         GetCurrentUser := 'NOBODY';
   finally
      StrDispose(szUser);
   end;
end;

Funktionen kräver att nätverket är MS Network-kompatibelt, vilket normalt är fallet.

Visa ikon överst utan att återställa först

Hur visar man ett programs ikon överst utan att först återställa det?

Svar:

ShowWindow(Form.Handle, SW_MINIMIZED);

Vit textbakgrund

När jag skriver ut text med Canvas.TextOut blir hela tiden bakgrunden vit. Kan detta undvikas?

Svar:

Använd WinAPI-funktionen SetBkMode() enligt följande:

SetBkMode(Canvas.Handle, TRANSPARENT);

Om du vill återställa till det ursprungliga använder du OPAQUE i stället för TRANSPARENT.

Ändra bakgrundsbilden till Skrivbordet

Hur byter man bakgrundsbild för skrivbordet i kod?

Svar:

Använd SystemParametersInfo med nedanstående parametrar. szFileName är en PChar.

SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, szFileName, 0);

Ändra papperskälla och/eller -storlek för skrivare

Hur ändrar jag papperskällan och/eller pappersstorleken för en skrivare?

Svar:

Använd GetPrinter() och strukturen TDevMode resp. DevMode enligt nedan.

var
   Device: array[0..255] of Char;
   Driver: array[0..255] of Char;
   Port: array[0..255] of Char;
   hDMode: THandle;
   PDMode: PDevMode;
begin
   Printer.PrinterIndex := Printer.PrinterIndex;
   Printer.GetPrinter(Device, Driver, Port, hDMode);
   if hDMode <> 0 then 
   begin
      pDMode := GlobalLock(hDMode);
      if pDMode <> nil then 
      begin
         pDMode^.dmFields := pDMode^.dmFields or dm_PaperSize;
         pDMode^.dmPaperSize := DMPAPER_LEGAL;
         GlobalUnlock(hDMode);
      end;
   end;
end;

Exemplet ändrar pappersstorleken till Legal.

Är enheten giltig?

Hur kan jag bygga en lista med alla giltiga enheter i systemet?

Svar:

Problemet här är att Windows tar hand om int 24h och visar feldialoger om man försöker byta till en felaktig enhet. Detta fixar man med SetErrorMode(), vilket jag beskrivit tidigare i Delphi Q&A. Här följer en funktion för att avgöra om en finns och om det i sådana fall finns någon diskett/CD i.

function DiskReady(Drive: Char): Boolean;
var
   ErrorMode: Word;
begin
{ Gör allt till stora bokstäver }
   if Drive in ['a'..'z'] then 
      Dec(Drive, $20);
{ Kolla så det verkligen är en bokstav }
   if not (Drive in ['A'..'Z']) then
      raise EConvertError.Create('Not a valid drive ID');
{ Stäng av felmeddelandena }
   ErrorMode := SetErrorMode(SEM_FailCriticalErrors);
   try
{ enhet 1 = a, 2 = b, 3 = c, etc }
      if DiskSize(Ord(Drive) - $40) = -1 then
         Result := False
      else
         Result := True;
   finally
{ Återställ }
      SetErrorMode(ErrorMode);
   end;
end;

Öppna "Den här datorn" i kod

Hur öppnar man Den här datorn i kod?

Svar:

I och med Win32 tillkom ShellExecuteEx(). Skillnaden ligger i fler möjligheter att utnyttja Shell. Nedanstående kod öppnar Den här datorn. Slå upp SHGetSpecialFolderLocation(), på samma sätt kan man få upp andra virtuella mappar som t.ex. Nätverket eller Kontrollpanelen.

if Succeeded(SHGetMalloc(malloc)) then 
begin
   if Succeeded(SHGetSpecialFolderLocation(Handle,
      CSIDL_DRIVES, idlist)) then 
   begin
      with Info do 
      begin
         cbSize := SizeOf(info);
         fMask := SEE_MASK_IDLIST;
         wnd := Handle;
         lpVerb := 'open'; { eller 'explore' }
         lpFile := nil;
         lpParameters := nil;
         lpDirectory := nil;
         nShow := SW_NORMAL;
         lpIdlist := idlist;
      end;
      ShellExecuteEx(@info);
      malloc.Free(idlist);
   end;
   malloc.Release;
end;
Här är info deklarerat som en TShellExeuteInfo, malloc som IMalloc och idlist som PItemIDList.

Öppna egenskaperna

Hur bär man sig åt för att öppna egenskaperna för en fil i W95/NT4?

Svar:

function OpenPropertyPage(const FileName: String;OwnerhWnd: THandle): hInst;

{ Returvärde <= 32 vid fel }

var
   Instance: HInst;
   ExecInfo: TShellExecuteInfo;

begin
   Result := 0;
   with ExecInfo do 
   begin
      cbSize := SizeOf(TShellExecuteInfo);
      fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_INVOKEIDLIST or SEE_MASK_FLAG_NO_UI;
      wnd := OwnerhWnd;
      lpVerb := 'properties'; 
      lpFile := PChar(FileName);
      lpParameters := nil;
      lpDirectory := nil;
      nShow := SW_SHOWNORMAL;
      hInstApp := HInstance;
      lpIDList := nil;

   end;
   ShellExecuteEx(@ExecInfo);
   Result := ExecInfo.hInstApp;
end;

Felmeddelanden

"Circular unit reference"

Vad innebär felmeddelandet "Circular unit reference"?

Svar:

Du har två units, här kallade A och B, som länkar in varandra i uses-satsen i interface-delen. Detta går inte, antingen måste du se till att den ena uniten inte behöver den andra eller också (vilket är det enklaste) flyttar du den ena uniten till uses under implementation i stället. Detta är en relativt okänd feature i Pascal. Den förutsätter dock att om A ligger i uses B så får inte B behöva något från A i interface-delen, utan bara i implementation-delen.

*** A.PAS
unit A;

interface

uses WinTypes, WinProcs, B;

type
   TFooBar = class(TFoo); { Den senare en fiktiv klass i unit B }

implementation

end.

*** B.PAS (fel)
unit B;

interface

uses WinTypes, WinProcs, A;

implementation

begin
    Blablabla; { fiktiv funktion i unit A }
end.

Detta går inte! Felet ordnas genom att flytta uses A i unit B till implementation-delen då Blablabla inte är beroende av A i interface-delen, däremot är A beroende av B i interface-delen då den vill härleda en klass från B.

*** B.PAS (rätt)
unit B;

interface

uses WinTypes, WinProcs;

implementation

uses A;

begin
    Blablabla; { fiktiv funktion i unit A }
end.

"Debugger kernel error"

När jag testar mitt Delphi 2.0-program får jag felmeddelandet "Debugger Kernel Error, code 3". Vad beror detta på?

Svar:

Med största sannolikhet beror det på att du anropar en 16-bitars DLL. Det har funnits fall med detta felmeddelande där ingen 16-bitars kod funnits alls. Se över de DLLer du importerar och anropar.

"Disk full, cannot optimize"

Jag får ett felmeddelande som säger att jag inte har tillräckligt med plats på hårddisken när jag ska kompilera, "Cannot optimize". Vad beror detta på?

Svar:

Slå av Optimize for size and load time under Options | Compiler | Linker och gör en rebuild. För att sedan optimera programmet kan du använda programmet W8LOSS som finns i BIN-katalogen. Detta problem är även beskrivet i README.TXT.

"Duplicate resource identifier"

Jag får felmeddelandet "Duplicate Resource identifier in c:\delphi\lib\controls.res". Varför?

Svar:

Sannolikt har du härlett någon komponent från någon av Delphis standardkomponenter som finns i CONTROLS.RES, men inte ändrat resurs-numren på de resurser som används av din komponent. Varje resurs-ID måste vara helt unikt.

"Error reading symbol file"

När jag öppnar mitt Delphi 1.0-projekt i Delphi 2.0 får jag felmeddelandet "Error reading symbol file". Vad är detta och vad kan jag göra för att rätta till det?

Svar:

Detta beror på att symbolfilen som används av bl.a. debuggern, *.DSM, inte har samma format i Delphi 1 och 2. Radera helt enkelt filen för att undvika problemet. Den återskapas ändå när du kompilerar projektet.

"File not found" vid rebuild

Jag har installerat ett antal komponenter i några olika kataloger, men när får felmeddelandet "File not found " när COMPLIB.DCL ska kompileras. Jag har dubbelkollat alla sökvägar och sett till att filerna finns där de ska. Varför hittar Delphi inte mina filer?

Svar:

Detta beror sannolikt på att att själva utvecklingsmiljön Delphi är skriven i Delphi (ett bevis på verktygets kraftfullhet). Detta gör att den TEdit som innehåller alla sökvägar bara returnerar 255 tecken. Om du har långa sökvägar är det sannolikt att detta överskrids, med effekten att en eller flera sökvägar trunkeras eller försvinner helt. Lösningen är att helt enkelt kort ned längder på sökvägarna eller antalet sökvägar. Ha en katalog där du lägger alla småkomponenter som inte behöver ha en egen katalog.

Med GetText eller GetTextBuf går det att komma undan detta med 255 tecken, men det har Borland tydligen inte utnyttjat i detta fallet.

"Key Violation"

När jag arbetar med mitt databasformulär händer det att jag får felmeddelandet "Key violation" när jag försöker lägga till en post. Varför?

Svar:

Key violation är en databasterm som innebär att du försöker lägga till två poster där ett visst fält har samma värde. Ett fält (det första) i varje tabell kan sättas som "key" och måste då vara unikt. Du kan inte ha två poster med samma värde i key-fältet. Du kan skriva in felhanteringsrutin för detta eller kolla om fältet verkligen ska vara ett key-fält.

"Not enough actual parameters" vid Beep-anrop

Jag får felmeddelandet "Not enough actual parameters" när jag ska kompilera. Felet uppstår när jag ska anropa funktionen Beep, men enligt hjälpen tar Beep inga parametrar.

Svar:

I Win32 finns en funktion som också heter Beep, i likhet med den funktion i unit SysUtils. Den senare tar inga parametrar. Det finns två lösningar på problemet;

  1. Anropa SysUtils.Beep. En ganska okänt möjlighet som kan användas om en funktion med samma namn finns i flera units.
  2. Anropa MessageBeep(0) direkt. Det är detta anrop som SysUtils.Beep egentligen gör.

"Out of memory"

När jag försöker kompilera ett Delphi 2.0-projekt under Delph 1.0 får jag felmeddelanden "Out of disk space", trots att jag har massor med plats. Projeketet innehåller ingen 32-bits kod eller komponenter. Varför detta?

Svar:

Felet beror sannolikt på komponentresursfilerna, *.DCR. Dessa är inte kompatibla mellan Delphi 1 och 2. Tyvärr går det inte att spara dessa med Image Editor från Delphi 2 i Delphi 1-format, du måste klippa och klistra alla bilder och ikoner.

"Undefined type in pointer definition"

Jag försöker deklarera en record som jag ska använda till en länkad lista enligt följande,

Foo = record
   Data : Word;
   Next : ^Foo;
End;

men jag får felmeddelandet "Undefined type in pointer definition" vid deklarationen för Next, vad beror detta på?

Svar:

Till skillnad från C++, får du inte deklarera ett fält som pekar på en variabel till en sådan record som deklareras just där, du måste ta omvägen och deklarera en typ som pekar till en sådan record.

PFoo = ^Foo;
Foo = record
   Data: Word;
   Next: PFoo;
end;

202: Stack overflow

Jag får runtime-fel 202: Stack overflow när jag kör mitt program, hur får jag bort det?

Svar:

Testa att ställa upp Stack size (Options | Project Options | Linker) från 8192 till 16834, 32767 eller 65536 och kontrollera att du har kryssat i Smart callbacks (Options | Project Options | Compiler). Om det ändå inte fungerar är det ett fel i din kod, kontrollera att du inte fastnar i en rekursionsloop eller försöka använda för många och för stora variabler.

Fel 998 under Windows NT 4.0

Varför blir det fel 998 när jag ska bygga om komponentbiblioteket under Delphi 2.0x och Windows NT 4.0?

Svar:

Detta är en bugg i Windows NT 4.0 som är fixad i och med Service Pack 2. Felet uppkommer också under andra omständigheter och gäller även Borland C++ 5.0 och 5.01.

Mer information om denna buggfix finns i Microsofts Knowledge Base-artikel Q159090.

Korrupt COMPLIB.DCL

Efter att jag installerat några komponenter kan inte COMPLIB.DCL laddas, Delphi påstår att den är korrupt. Hur fixar jag detta?

Svar:

Börja med att köra ScanDisk eller CHKDSK för att kolla om det är några fel på disken. Testa sedan att kopiera COMPLIB.DCL från CDn. Om inte det fungerar kan du skapa ett nytt component library och lägga till alla komponenter manuellt. Slutligen, installera om Delphi om du inte får det att fungera trots dessa åtgärder.

Skyddsfel i COMPLIB.DCL

Varför blir det ett skyddsfel i COMPLIB.DCL när jag minimerar ett formulär i Delphi 1.0?

Svar:

Detta är en bugg i Delphi 1.0. Skaffa patchen (finns tre olika varianter beroende på vilken version av Delphi som du kör) från Borlands FTP-server, eller uppgradera till 1.02.

ftp.borland.com/pub/techinfo/techdocs/language/delphi/patch/

Skyddsfel med Crystal Reports 5.0

Varför får jag skyddsfel i KERNEL32.DLL när jag använder Crystal Reports 5.0 med Paradox-tabeller? Skyddsfelet uppkommer när programmet stängts.

Svar:

Detta beror troligen på att du använder en nyare version (3.5) av BDE än den som följde med Crystal Reports.

Allmänt

Anpassa efter version

Jag försöker använda samma kod för både Delphi 1.0 och 2.0. Hur tar jag reda på vilken version som används vid kompileringen?

Svar:

Använd $IFDEF för att kolla vilket av VER80 (Delphi 1.0), VER90 (Delphi 2.0), VER100 (Delphi 3.0) och VER110 (Delphi 4.0) som är definierat.

{$IFDEF VER80}
   uses WinTypes, WinProcs;
{$ELSEIF}
   uses Windows;
{$ENDIF}

Eftersom WinTypes och WinProcs är utbytta mot Windows i Delphi 2.0+ kommer denna kod att länka in de förstnämnda om det är Delphi 1.0 som körs, enbart Windows i annat fall. Du kan dock definiera alias på units i Delphi 2.0+, så just detta exempel är alltså onödigt. Det kan dock med fördel användas då storleken på vissa typer skiljer sig mellan 16-bit och 32-bit.

Antal dagar mellan datum

Hur räknar man ut antalet dagar mellan två datum?

Svar:

function DaysBetween(Date1, Date2: TDateTime): Longint;

var
   YearResult, MonthResult, DayResult: Word;
   TDay1, TDay2, DateDiff: TDateTime;

begin
   if Date2 > Date1 then
      DateDiff := Date2 - Date1
   else
      DateDiff := Date1 - Date2;
   DecodeDate(DateDiff, YearResult, MonthResult, DayResult);
   DaysBetween := (YearResult * 365) + (MonthResult * 31) + DayResult;
end;

Funktionen ger inte ett exakt värde, det förutsätts t.ex. att alla månader har 31 dagar, men det är ett bra närmevärde.

Antal färger på skärmen

Hur tar jag reda på hur många färger som skärmen klarar av att visa?

Svar:

GetDeviceCaps(Canvas.Handle, BitsPixel)

returnerar hur många bitar som används per pixels. Två upphöjt till antal bitar ger antal färger, men nedanstående tabell är enklast att använda:

BitarFärger
22
416
8256
1665536
2416.8M
3216.8M + transparens

Använda egna hjälpfiler i Delphi 3

Varför hitter inte Delphi 3 mina hjälpavsnitt? Jag har lagt till alla K- och A-nyckelord och gjort enligt konstens alla regler, men det fungerar inte.

Svar:

Ta bort den (gömda) filen DELPHI3.GID och starta om Delphi.

Avlusa DLL

Jag har skrivit en DLL som jag vill avlusa. Hur gör jag detta?

Svar:

Om den inbyggda debuggern i Delphi duger, kan du göra en unit som innehåller alla funktionerna du vill använda och sedan anropa den i stället. När det fungerar felfritt flyttar du tillbaka dem till DLL:en. Om detta inte går, vissa hook-funktioner måste t.ex. vara i en DLL, kan du använda Turbo Debugger 4.6 (följer med RAD Pack) om det gäller Delphi 1.0. Troligen kan du använda debuggern i Borland C++ 5.0 till Delphi 2.0. Delphi 3.0 har inbyggt stöd för DLL-avlusning.

Begränsningar i Windows

Vilka begränsningar finns i själva Windows vad det gäller t.ex. timers och andra systemresurser?

Svar:

Följande är enligt Windows 95 Resource Kit:

 Windows 3.1Windows 95
Meny-handtagca 29932k
Timers32Obegränsat
COM- och LPT-portar4 av varjeObegränsat
Rader per listruta8k32k
Data per listruta64kObegränsat
Data per editruta64kObegränsat
Regioner*Obegränsat
Fysiska pennor och brushes*Obegränsat
Logiska pennor och brushes**
Logiska fonter*750-800
Installerade fonter250-300 1000
Device contexts200 16k

Ovanstående gäller inte Windows NT.

* Det beror på hur många som får plats i GDI-stacken på 64 kB. Vissa av dessa objekt har i Windows 95 flyttats till den 32-bitars GDI-stack som tillkommit.

Beräkna skottår

Hur avgör man om ett år är skottår?

Svar:

Med den gregorianska kalendern definieras ett skottår som:

Således är 2000 ett skottår, men inte år 1900. I kod innebär det följande:

if (((Year mod 4) = 0) and ((Year mod 100) <> 0)) or ((Year mod 400) = 0) then
   ShowMessage('Skottår!');

Den julianska kalender som användes i de flesta europeiska länder fram till 1500-1700-talet räknade alla år som var jämnt delbara med fyra som skottår. Delphis datumfunktioner utgår ifrån att gregoriansk kalender gäller.

Bilder bland resurserna

De bilder som jag ska ha på knapparna till verktygsfältet vill jag ha liggande i en resursfil. Hur gör jag detta?

Svar:

Börja med att länka in den kompilerade resursfilen med $R. Använd sedan LoadBitmap för att ladda bilden och tilldela det handle du får till knappens egenskap Glyph.Handle.

SpeedButton.Glyph.Handle := LoadBitmap(HInstance,'BITMAP-NAMN');

Byta namn på registernycklar

Hur byter man namn på registernycklar?

Svar:

Det gör man inte rätt upp och ner, utan man måste kopiera hela nyckelträdet rekursivt och därefter ta bort det gamla.

Clusterstorleken

Hur tar jag reda på hur stor en hårddiskpartitioner clusters/allokeringsenheter/block är?

Svar:

Under Win16 finns det inget API-anrop för detta, vi får använda assembler:

function GetClusterSize(Drive: Byte): Word;
var
   Regs: TRegisters;
begin
   Regs.cx := 0;         {set For error-checking just to be sure}
   Regs.ax := $3600;     {get free space}
   Regs.dx := Drive;     {0=current, 1=a:, 2=b:, etc.}
   Msdos(Regs);
   GetClusterSize := Regs.ax * Regs.cx;      {cluster size!}
end;

Win32 introducerade GetDiskFreeSpace som ger oss en hel del information om det fysiska kring en hårddiskpartition. Funktionen nedan tar som parameter sökvägen till roten på disken, men är lite mer förlåtande än GetDiskFreeSpace och kapar av ev. underkataloger (de får inte vara med till anropet).

function GetClusterSize(RootDir: string): Integer;
var
   SectorPerCluster, BytesPerSector, 
   FreeClusters, TotalClusterCount: Integer;
   TempS: string;
begin
   TempS := RootDir;
   if Length(TempS) > 3 then
      Delete(TempS, 4, Length(TempS)-3);
   GetDiskFreeSpace(PChar(TempS), SectorPerCluster, 
      BytesPerSector, FreeClusters, TotalClusterCount);
   GetClusterSize := SectorPerCluster * BytesPerSector;
end;

Delphi 2.0 under Win32s

Kan jag köra Delphi 2.0 under Windows 3.1 om jag har Win32s installerat?

Svar:

Nej. Så enkelt är det. Delphi 2.0 anropar rutiner specifika för riktiga Win32 samt Windows 95/NT4.

Delphi FAQ?

Var finner man Delphi FAQ:en?

Svar:

http://proxy.sbrain.vtyh.fi/delphi/delphi_faq.html

DLLer i C++ med single som returvärde

Jag har skrivit en DLL i C++ som har en funktion med en single som returvärde. När jag kör programmet blir det ett skyddsfel. Varför?

Svar:

Skicka i stället flyttalet som en var-parameter (call by reference alltså). En trolig orsak är att DLLen är skriven i MS Visual C++, MS och Borland använder olika format för flyttal (Borland använder IEEE, MS en egen variant) som returvärden.

Rent allmänt bör man undvika single resp. float, använd i stället double för bättre framtida kompabilitet (och föralldel bättre precision).

DLLer tillsammans med VB

Vilka regler ska man hålla sig till när man skapar DLLer i Delphi som ska anropas från VB-program?

Svar:

DoEvents i Delphi

I Visual Basic finns en funktion som heter DoEvents som man anropar när man vill att systemet ska kunna fortsätta jobba när man håller på med något tidskrävande. Finns denna möjlighet i Delphi?

Svar:

Ja, Application.ProcessMessages. Det som utförs är då väsentligen följande:

while PeekMessage(MsgStruct, Handle, 1, 65000, PM_REMOVE) do
begin
   TranslateMessage(MsgStruct);
   DispatchMessage(MsgStruct);
end;

MsgStruct är definierad som en TMsg.

Dynamiska vektorer

Hur skapar jag dynamiska vektorer, där vektorns storlek inte är förbestämt när programmet kompileras?

Svar:

Börja med att bestämma den maximala storleken, lämpligt kan vara att välja prick 64 kB (FFFFh) då det är det största minnet som kan allokeras med GetMem. I detta exempel ska vektorn innehålla variabler av Integer-typ.

const
   MaxIntArraySize = $FFFF div SizeOf(Integer);

Definiera sedan några typer;

type
   PIntArray = ^TIntArray
   TIntArray = array [0..MaxIntArraySize] of Integer;

Deklarera därefter en variabel av typen PIntArray och använd GetMem för att allokera just det antal bytes som du behöver;

GetMem(Numbers, Count * SizeOf(Integer)); 
try
   Numbers^[0] := 3655;
   Numbers^[1] := 9873;
   Numbers^[2] := 76;
   ...
   ...
finally
   FreeMem(Numbers, Count * SizeOf(Integer)); 
end;

FreeMem lämnar tillbaka det allokerade minnet. Undantagshanteringen ser till att det alltid blir återlämnat. Använd gärna MaxAvail för att ta reda på om det finns tillräckligt mycket minne ledigt först.

Om du behöver allokera mer minne än 64 kB kan du använda dig av GlobalAlloc/GlobalLock och GlobalFree/GlobalUnlock i stället för GetMem och FreeMem.

EAccessViolation vid manuell urladdning av DLLer

Jag har problem med EAccessViolations som uppkommer när den sista av de DLLer som jag manuellt laddat i en annan DLL ska tas bort från minnet. Lösning?

Svar:

Testa nedanstående för Delphi 1.0.

var
   SaveExit: Pointer;

procedure LibExit; far;
begin
   If ExitCode = wep_System_Exit then
   begin
      while (SaveExit <> nil) do
         SaveExit := ExitProc;
      {System shutdown in progress}
   end 
   else
   begin
      while (SaveExit <> nil) do
      begin
         SaveExit := ExitProc;
      end;
      {DLL is unloaded}
   end;
   ExitProc := SaveExit;
end;

begin
   HeapAllocFlags := GMEM_MOVABLE or GMEM_DDESHARE;
   SaveExit := ExitProc;
   ExitProc := @LibExit;
end.

Engelskt uttal av Delphi

Hur uttalas Delphi egentligen på engelska? [delphii] eller [delphaj]?

Svar:

Detta är en fråga som (faktiskt) diskuterats flitigt i nyhetsgrupper och på mailinglistor. Det verkar som om européer i större grad använder [delphii] som uttal, medan amerikanare använder [delphaj]. I princip spelar det ingen roll, men de som jobbat på Borland uppges säga [delphaj], men det verkar som om [delphii] är det korrekta uttalet. Typ.

Fel färger med 16-färgers bitmapp

Varför blir färgerna fel med den 16 färgers bitmap jag har gjort med Paint Shop Pro?

Svar:

En trolig orsak är att du minskat ned från ett större antal färger till 16 färger och därmed skapat en egen palett. Problemet är att TBitmap utgår ifrån att standardpaletten används när bilden är på 16 färger. Öka färgdjupet till 256 färger, då kommer paletten att laddas in.

Fel språk på knappar

Varför är texten på knapparna i fördefinierade dialoger (t.ex. MessageBox) på fel språk när jag skriver ett svenskt program?

Svar:

Detta hör ihop med den språkversion av Windows som du har installerad. I MessageBoxEx går det att välja vilket språk det ska vara på knapparna, men det förutsätter att flera språk är installerade.

Filägare i NTFS

Hur gör jag för att ta reda på vem som ägar en fil eller en katalog på ett Windows NT-system med NTFS?

Svar:

I nedanstående funktionen returneras ägaren i Username och ägarens domän i Domain. Om ägaren är ett inbyggt konto är Domain = BUILTIN. Notera att Username inte behöver peka på en viss användare, det kan även peka på en grupp, en domän, ett borttaget konto eller ett alias (kontotypen returneras av LookupAccountSID och sparas här i OwnerType).

function GetFileOwner(FileName: string; 
   var Domain, Username: string): Boolean;
var
   SecDescr: PSecurityDescriptor;
   SizeNeeded, SizeNeeded2: DWORD;
   OwnerSID: PSID;
   OwnerDefault: BOOL;
   OwnerName, DomainName: PChar;
   OwnerType: SID_NAME_USE;
begin
   GetFileOwner := False;
   GetMem(SecDescr,1024);
   GetMem(OwnerSID,SizeOf(PSID));
   GetMem(OwnerName,1024);
   GetMem(DomainName,1024);
   try
      if not GetFileSecurity(PChar(FileName), 
         OWNER_SECURITY_INFORMATION, 
         SecDescr,1024,SizeNeeded) then
         Exit;
      if not GetSecurityDescriptorOwner(SecDescr, 
         OwnerSID,OwnerDefault) then
         Exit;
      SizeNeeded := 1024;
      SizeNeeded2 := 1024;
      if not LookupAccountSID(nil,OwnerSID,OwnerName, 
         SizeNeeded,DomainName,SizeNeeded2,OwnerType) then
         Exit;
      Domain := DomainName;
      Username := OwnerName;
   finally
      FreeMem(SecDescr);
      FreeMem(OwnerName);
      FreeMem(DomainName);
   end;
   GetFileOwner := True;
end;

Finns filen?

Hur kollar jag om en fil finns eller inte?

Svar:

Enklast är att bara använda FileExists(<filnamn>), vill man vara omständlig och ha full kontroll kan man använda varianten nedan.

AssignFile(F, Filnamn);
try
   Reset(F);
except
   { filen finns inte}
end;

F deklareras som t.ex. file.

Gömma program helt

Jag har ett program som jag bara vill visa som en trayicon. Hur döljer jag huvudfönstret från aktivitetsfältet och Alt+Tab?

Svar:

Lägg till den nedan markerade rader på rätt ställen i projektkoden.

begin
   Application.Initialize;
   Application.ShowMainForm := False;        { LÄGG TILL }
   Application.CreateForm(TForm1, Form1);
   ShowWindow(Application.Handle, SW_HIDE);  { LÄGG TILL }
   Application.Run;
end.

Heltal under Delphi 1.0 och 2.0 - skillnader

Jag håller på att porta ett program från Delphi 1.0 till Delphi 2.0. Programmet läser från binära datafiler. När jag kör programmet under Delphi 2.0 blir alla värden fel och filen tar slut innan det ska. Varför?

Svar:

Troligen läser du värden av typen Integer. I Delphi 1.0 är denna typ två bytes stor och i Delphi 2.0 fyra bytes. Använd i stället Smallint (som är en byte stor under Delphi 1.0) i 32-bitarsversionen. Om du vill använda samma källkod för båda versionerna kan du använda kompilatordirektiv:

var
{$IFDEF VER90}
   A: array [0..9] of Smallint;
{$ELSE}
   A: array [0..9] of Integer;
{$ENDIF}

Hur gör man en egen Object Inspector?

Jag försöker göra en komponent som ska fungera på samma sätt som Object Inspectorn i Delphi-miljön. Hur ska jag gå till väga?

Svar:

Kolla i filerna doc\dsgnintf.int och doc\typinfo.int, därifrån kan man lura ut det mesta.

Hur registreras OCXer?

Jag har några OCXer som jag skulle vilja använda. När jag försöker göra detta får jag veta att de inte är registrerade. Hur gör jag detta

Svar:

Kör programmet REGSVR32.EXE med OCXen som parameter. Programmet ligger i System-mappen. Du kan också göra detta via kod, i alla OCXer exporteras nämligen funktionen DllRegisterServer. Anrop sker enligt nedan:

procedure RegisterOCX(Filename: string);
var
   hOCX: Integer;
   pReg: procedure;
begin
   hOCX := LoadLibrary(Filename);
   if (hOCX <> 0) Then
   begin
      pReg := GetProcAddress(hOCX, 'DllRegisterServer');
      pReg;   { Anropa funktionen }
      FreeLibrary(hOCX);
   end
end;

Icke-visuella ActiveX-kontroller

Jag har gjort en icke-visuell komponent som jag vill göra om till en ActiveX, men när jag startar ActiveX Control Wizard finns inte min komponent med, varför?

Svar:

ActiveX-kontroller måste vara visuella eftersom de måste kunna visas under utvecklingen. Att detta inte gäller för vanliga komponenter beror på att Delphi på egen hand ordnar ett visuellt gränssnitt med komponentens ikon.

Import av DLLer under Windows NT

Varför klagar mitt program över att DLLer saknas när det körs under Windows NT? Filerna finns i systembiblioteket eller sökvägen. Samma program under Win95 fungerar utmärkt.

Svar:

Detta beror på (en möjlig bugg i) NTs loader. Man måste ange filtillägget för de DLLer som man importerar. Om du nu importerar enligt

procedure Foo; external 'BAR' index 1;

ska du ändra detta detta till:

procedure Foo; external 'BAR.DLL' index 1;

Tänk också på att DLL-importer är känsliga för stora och små bokstäver.

Infoga GUID

Hur skapar jag enklast ett nytt, unikt, GUID för en ActiveX-kontroll i Delphi 3?

Svar:

Tryck Ctrl+Skift+G i editorn.

Ingen RTTI?

Varför är ClassInfo för instansen av en egentillverkad klass jag gjort alltid nil?

Svar:

För att RTTI ska genereras måste det finnas metoder/datamedlemmar/egenskaper i klassen som är published. Se också till att kompilatorn överhuvudtaget generar RTTI genom att se till att $M+ är på.

Installerade modem under Win95

Hur tar jag reda på vilka modem som är installerade under Windows 95?

Svar:

Detta är lagrat i registret under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Class\Modem\nnnn där nnnn är ett nollbaserat ordningstal för varje modem. Man får vara med på att även drivrutiner för direkt kabelanslutning finns med. Rena modem känns igen då värdet DeviceType där är satt till 0. Motsvarande information finns för alla andra systemdelar, t.ex. skrivare, nätverkskort, -protokoll och skärmar.

Katalogen programmet startades ifrån

Hur avgör jag ifrån vilken katalog programmet startades ifrån?

Svar:

Application.ExeName ger det fulla namnet på programmet. Använd sedan ExtractFileName (unit SysUtils) för att få ut enbart katalogen.

Kompilera DOS-program med Delphi

Går det att kompilera DOS-program med Delphi?

Svar:

Ja, fast det är ingen lätt historia. Nedanstående gäller för Delphi 1.0. Kompilering kommer att ske med kommandoradskompilatorn DCC.EXE och kräver följande:

  1. Borland Pascal 7 RTL-källkod.
  2. Delphi VCL källkod.
  3. Turbo Assembler, TASM.EXE.
  4. Den uppdaterade DLIB.EXE.

Instruktionerna förutsätter att BP7 RTL ligger i \BP\RTL, att Delphi VCL ligger i \DELPHI\SOURCE och att såväl \DELPHI\BIN som TASM.EXE ligger i sökvägen.

Kopiera följande filer (alla är *.ASM) från \BP\RTL\SYS till \DELPHI\SOURCE\RTL\SYS: MAIN, PARS, LAST, HEAP, F87H, EI87, EI86, DLIB och DAPP.

DOS Real mode

  1. Det viktigaste är att fixa en SYSTEM.TPU för DOS-läge. Detta kräver en del jobb eftersom Delphi VCL inte inkluderar någon MAIN.ASM som krävs för att kompilera SYSTEM.PAS i DOS-läge. Några saker måste tas bort från MAIN.ASM eftersom de nu finns i EXIT.ASM:
    • Ta bort HaltTurbo, HaltError, Terminate, PrintString från sektionen PUBLIC.
    • Ta bort koden för ovanstående funktioner.
    • Lägg till "EXTRN HaltError:NEAR,HaltTurbo:NEAR" efter "ASSUME CS:CODE,DS:DATA" längst upp under Externals.
    • Byt ut "SHORT" mot "JMP SHOR HaltError" i "Int3FHandler".
    • Lägg till "JMP HaltError" efter "MOV AX,200" i Int00Handler.
    • Lägg till "JMP HaltTurbo" efter "MOV AX,255" i Int23Handler.
  2. Skapa objektfiler (*.OBJ) från alla assemblerfiler i \DELPHI\SOURCE\RTL\SYS, skriv:
    TASM *.ASM
    
  3. Kompilera SYSTEM, skriv
    DCC -cd -$d- -o\BP\RTL\LIB SYSTEM
    
    i \DELPHI\SOURCE\RTL\SYS.
  4. Starta din TURBO.TPL, skriv
    DLIB TURBO.TPL +..\SOURCE\RTL\SYS\SYSTEM.TPU 
    
    i \DELPHI\BIN.
  5. Andra filer som du bör inkludera i TURBO.TPL:
    • \BP\RTL\OVR\OVERLAY
    • \BP\RTL\CRT\CRT
    • \BP\RTL\DOS\DOS
    • \BP\RTL\PRT\PRINTER
    • \DELPHI\SOURCE\RTL70\STRINGS
    • \BP\RTL\TV\MEMORY
    • \BP\RTL\COMMON\OBJECTS
    Kompilera filerna (vissa kräver Tasm) i deras respektive kataloger med DCC och parametern -cd och -$d- för att skapa TPU-filer av version 8. Lägg sedan till dessa till TURBO.TPL enligt steg 4.

Klart! Använd DCC med växeln -cd för att skapa DOS EXE-filer med klasser, undantagshantering etc.

DOS DPMI

  1. Se till att objektfilerna från steg 2, ovan, finns kvar.
  2. Skapa OBP-filer från ASM, skriv
    TASM -op -d_DPMI_ *.ASM *.OBP
    
    i \DELPHI\SOURCE\RTL\SYS.
  3. Kompilera SYSTEM, skriv
    DCC -cp -$d- -o\BP\RTL\LIB SYSTEM
    
    i \DELPHI\SOURCE\RTL\SYS.
  4. Starta din TURBO.TPL, skriv
    DLIB TPP.TPL +..\SOURCE\RTL\SYS\SYSTEM.TPP
    
    i \DELPHI\BIN.
  5. Andra filer som du bör inkludera i TURBO.TPL:
    • \BP\RTL\CRT\CRT
    • \BP\RTL\DOS\DOS
    • \BP\RTL\PRT\PRINTER
    • \DELPHI\SOURCE\RTL70\STRINGS
    • \DELPHI\SOURCE\RTL70\WINDOS
    • \DELPHI\SOURCE\RTLWIN\WINAPI
    • \BP\RTL\TV\MEMORY
    • \BP\RTL\COMMON\OBJECTS
    • \DELPHI\SOURCE\RTL\SYS\SYSUTILS (se nedan)
    Kompilera filerna (vissa kräver Tasm) i deras respektive kataloger med DCC och parametern -cp och -$d- för att skapa TPP-filer av version 8. Lägg sedan till dessa till TPP.TPL enligt steg 4.

SysUtils for DOS DPMI

För att få SysUtils att fungera ordentligt i DOS måste du göra några mindre ändringar i källkoden. Lämpligt är att börja med en backup och sedan använda "{$IFDEF WINDOWS} ... {$ENDIF}" vid alla ändringar.

  1. RTM.EXE stöder bara _lread/write och inte _hread/write och SysUtils måste uppdateras för detta. Exemplet nedan gäller för FileRead, FileWrite har index 86. I interface:
    {$IFDEF WINDOWS}
    function FileRead(Handle: Integer; 
       var Buffer; Count: Longint): Longint;
    {$ELSE}
    function FileRead(Handle: Integer; 
       var Buffer; Count: Word): Word;
    {$ENDIF}
    
    I implementation:
    function FileRead(Handle: Integer; var Buffer; Count: Word): Word;
       external 'KERNEL' index 82; { _lread }
    
  2. De units som används i implementation-sektionen måste ändras.
    {$IFDEF WINDOWS}
    uses WinTypes, WinProcs, ToolHelp;
    {$ELSE}
    uses WinAPI;
    {$ENDIF}
    
  3. Ta bort lite undantagshantering.
    • I interface
      {$IFDEF WINDOWS}
         procedure EnableExceptionHandler(Enable: Boolean);
      {$ENDIF}
      
    • Lägg till {$IFDEF WINDOWS} och {$ENDIF} kring GetModNameAndLogAddr i implementation-sektionen.
    • Ändra ShowException.
      var
         .. existing definitions
         Buffer: array[0..255] of Char;
      {$IFDEF WINDOWS}
         GlobalEntry: TGlobalEntry;
         hMod: THandle;
      {$ENDIF}
      begin
      {$IFDEF WINDOWS}
         .. existing code
      {$ENDIF}
      end;
      
    • Före ErrorHandler:
      {$IFDEF WINDOWS}
      const
         Flags   = $10;
         .. other consts
         Recurse: Word = 0;
      {$ENDIF}
      
    • I ErrorHandler;
         1:        E := OutOfMemory;
      {$IFDEF WINDOWS}
         2,4..10:  with ExceptMap[ErrorCode] do E := EClass.CreateRes(EIdent);
         3,11..16:
         ..
      end;
      {$ELSE}
         2..16:    with ExceptMap[ErrorCode] do E := EClass.CreateRes(EIdent);
      {$ENDIF}
      else
      
    • Före InteruptCallBack, lägg till {$IFDEF WINDOWS} och lägg till {$ENDIF} efter EnableExceptionHandler.
    • I DoneExceptions, lägg till {$IFDEF WINDOWS} kring anropet till EnableExceptionHandler.
    • I InitExceptions, lägg till {$IFDEF WINDOWS} kring satsen TaskID := GetCurrentTask; samt anropet till EnableExceptionHandler.
  4. Lägg till INI-stöd. Efter kommentaren { Initialization file support }, lägg till följande:
    {$IFNDEF WINDOWS}
    function GetProfileInt(appName, keyName: PChar;
       default: Integer): Word; far; external 'KERNEL' index 57;
    function GetProfileString(appName, keyName, default, returned: PChar;
       size: Integer): Integer; far; external 'KERNEL' index 58; 
    {$ENDIF}                                                       
    
  5. Kompilera SysUtils för DOS från kommandoraden med:
    DCC -cp SYSUTILS
    

Allt är klart för att använda TPP.TPL i dina program.

Klasser for DOS DPMI

Att ändra Classes för att fungera under DOS DPMI är väldigt enkelt jämfört med SysUtils.

Ändra uses-satsen i interface-sektionen till följande:

{$IFDEF WINDOWS}
uses SysUtils, WinTypes, WinProcs;
{$ELSE}
uses SysUtils, WinAPI;
{$ENDIF}

Du måste även skapa en unit som heter Consts som har innehåll enligt nedan. Av någon anledning skickar inte Borland med denna fil.

{========================================}
unit Consts;

interface

const
   SClassNotFound             = 61447;
   SDuplicateClass            = 61457;
   SRegisterError             = 61498;
   SResNotFound               = 61449;
   SLineTooLong               = 61459;
   SReadError                 = 61443;
   SWriteError                = 61444;
   SInvalidImage              = 61448;
   SFCreateError              = 61441;
   SFOpenError                = 61442;
   SMemoryStreamError         = 61445;
   SInvalidProperty           = 61538;

   SUnknownProperty           = 61462;
   SPropertyException         = 61464;
   SInvalidPropertyPath       = 61461;
   SInvalidPropertyValue      = 61460;
   SReadOnlyProperty          = 61463;
   SCharExpected              = 61534;
   SSymbolExpected            = 61535;
   SParseError                = 61530;
   SDuplicateName             = 61455;
   SInvalidName               = 61456;
   SListIndexError            = 61451;
   SDuplicateString           = 61453;
   SSortedListError           = 61452;
   SIdentifierExpected        = 61531;

   SStringExpected            = 61532;
   SNumberExpected            = 61533;
   SInvalidBinary             = 61539;
   SInvalidString             = 61537;
   SAssignError               = 61440;

implementation

end.
{========================================}

Kopiera nu TYPINFO.INT till TYPINFO.PAS och kopiera alla procedur- och funktionsdeklarationer från interface till implementation. Lägg till en begin/end-sats till varje funktion och se till att de returnerar 0, NIL etc beroende på dess typ. Kompilera koden med:

DCC -cp CLASSES

Nu kan du använda TList, TStrings, TStringList, TStream etc i dina DOS-program (endast DPMI). P.g.a. ändringar i SysUtils klarar THandleStream.Read/Write bara en buffer stor som en word och inte en longint. Du kan inte heller få TReader/TWriter att fungera om du inte kan få tag på TYPINFO.PAS eller TYPINFO.TPP från Borland.

Konvertera från flyttal

Hur gör man för att konvertera variabler av typen Single till strängar? Jag har försökt med IntToStr, men det ger felmeddelandet "Type mismatch".

Svar:

FloatToStr är vad du letar efter. Använd detta för StrToFloat konverterar följdaktligen från String till Single. Sök på "floating-point conversion routines" i Delphi-hjälpen för en lista på alla funktioner som behandlar konvertering till/från flyttal. Om du vill ha bättre kontroll över resultatet kan du använda Format i stället.

Konvertera Unix-datum till och från TDateTime

Hur konverterar jag Unix-datum till Delphis TDateTime-typ?

Svar:

Unix-datum sparas som antalet sekunder sedan 1970-01-01 00.00 i en signed longint (Unix-datum kommer därför att slå runt någon gång 2039). Nedanstående konverterar mellan de olika datumtyperna.

const
   UnixStartDate: TDateTime = 719163.0;

function DelphiDateTimeToUnix(ConvDate: TDateTime): Longint;
begin
   Result := Round((ConvDate - UnixStartDate) * 86400);
end;

function UnixToDelphiDateTime(USec: Longint): TDateTime;
begin
   Result := (USec / 86400) + UnixStartDate;
end;

Konvertera Visual Basic till Delphi

Finns det något program för att konvertera Visual Basic-program till Delphi?

Svar:

Ett företag som heter Eagle Research har en produkt som heter VB2D och som uppges klara detta. För närvarande klarar den vara VB3 och Delphi 1.0, men i augusti -96 ska det enligt uppgift ha kommit en ny version som klarar VB4 och Delphi 2.0.

Eagle Research
360 Ritch St, Suite 300
San Francisco, CA 94107
United States
Fax: +1 415 495-3638
Tel: +1 415 495-3136
E-mail: vb2d@xeaglex.com

Köra DOS-kommando

Jag vill på ett enkelt sätt kopiera en fil och tänkte använda DOS-kommandot COPY för detta. När jag kör nedanstående kod får jag felmeddelandet "File not found".

WinExec('copy c:\foo.txt d:\foo.txt', SW_SHOWNORMAL);

Vad är det för fel?

Svar:

COPY är en intern funktion i COMMAND.COM - någon COPY.EXE eller COPY.COM finns inte. För dessa kommandon anropar du COMMAND.COM och användar /C. Använd helst ShellExecute för att köra program. Kom då bara ihåg att ShellExecute vill ha parametrarna till programet separat.

WinExec('command.com /c copy c:\foo.txt d:\foo.txt',SW_SHOWNORMAL);

eller ännu hellre

ShellExecute(Handle,nil, 'command.com', 
   '/c copy c:\foo.txt d:\foo.txt', '', 
   SW_SHOWNORMAL);

Du kan också använda funktionerna i LZEXPAND för att kopiera filer.

Köra kod innan DLL tas bort från minnet

Hur gör jag för att ett visst stycke kod ska köras just innan en DLL tas bort från minnet? Jag använder Delphi 1.0.

Svar:

Skapa en funktion med namnet WEP och exportera den.

function WEP(nExitType: Integer): Integer; export;
begin
   { Kod här }
   WEP := 1;
end;

En närmare beskrivning finns i WinAPI-hjälpen.

Långa filnamn under Win16

Går det att ta reda på långa filnamn under Delphi 1.0 och Win16?

Svar:

Ja, enligt nedan. Ett annat alternativ är att använda Call32NT för att direkt anropa Win32 API.

Funktionerna bör var självförklarande, första parametern är för mig dock okänd.

function GetLongPathName(SubstExpand: Byte; 
   SourcePath, DestPath: PChar): Boolean; assembler;
asm
   mov ax, 7160h
   mov cl, 2
   mov ch, SubstExpand
   lds si, SourcePath
   les di, DestPath
   int 21h
   jnc @1
   mov bx,ax
   mov al,false
   jmp @2
@1:
   xor bx,bx
   mov al,true
@2:
   mov dx,seg @data
   mov ds,dx
   mov Dos.DOSError, bx
end;

function GetShortPathName(SubstExpand: Byte; 
   SourcePath, DestPath: PChar): Boolean; assembler;
asm
   mov ax, 7160h
   mov cl, 1
   mov ch, SubstExpand
   lds si, SourcePath
   les di, DestPath
   int 21h
   jnc @1
   mov bx,ax
   mov al,false
   jmp @2
@1:
   xor bx,bx
   mov al,true
@2:
   mov dx,seg @data
   mov ds,dx
   mov Dos.DOSError, bx
end;

Läsning av fil ger alltid #26

Varför blir Ch i koden nedan alltid #26 när jag läst ett antal tecken (olika för varje fil)?

var
   F: TextFile;
   Ch: Char;
begin
   AssignFile(F, Filnamn);
   Reset(F);
   while ... do 
   begin
      Read(F, Ch);
      .
      .
      .
   end;
end;

Svar:

#26 är ASCII-koden för filslut i textfiler, men den kan finnas var som helst i en binärfil (som du uppenbarligen försöker läsa). Ändra TextFile till bara File så ordnar det sig. Undvik dock att läsa in en fil tecken för tecken om detta inte är absolut nödvändigt, det är fruktansvärt långsamt. Läs i stället in filen i stora portioner med en buffer på ett antal kB (8 kB är ganska lagom) och stega igenom den buffern i stället.

Mailinglistor för Delphi- och Windows-utvecklare

Vilka mailinglistor finns det som kan intressera mig som Delphi-utvecklare?

Svar:

Adv-Pas

Område:Avancerad Pascal-programmering
Adress:listserv%brufpb.bitnet@listserv.net
Meddelande:subscribe ADV-PAS <ditt namn>

ASSMPC

Område:Assembler-programmering på PCn
Adress:listserv%brufpb.bitnet@listserv.ne
Meddelande:subscribe ASSMPC <ditt namn>

Delphi-L

Område:Delphi
Adress:listserv@vm3090.ege.edu.tr
Meddelande:sub DELPHI-L <ditt namn>

Delphi Notes

Område:Delphi-integration med Lotus Notes
Adress:majordomo@list.gen.com
Meddelande:subscribe delphi_notes <ditt namn>

Delphi-talk

Område:Delphi
Adress:majordomo@listserv.bridge.net
Meddelande:subscribe delphi-talk <ditt namn>

dWinsock-announce

Område:Annonseringar om nyheter med Winsock-komponenten dWinsock.
Adress:listserv@aait.com
Meddelande:subscribe dwinsock-ann

dWinsock-support

Område:Utveckling med dWinsock.
Adress:listserv@aait.com
Meddelande:subscribe dwinsock-sup

Pascal-programmering

Område:
Adress:info-pascal-request@brl.mil
Meddelande:subscribe INFO-PASCAL <ditt namn>

InterBase

Område:Databasutvecklinge med Borland InterBase
Adress:listproc@esunix1.emporia.edu
Meddelande:subscribe INTERBASE <ditt namn>

Paradox

Område:Databasutveckling med Borland Paradox
Adress:listserv%brufpb.bitnet@listserv.net
Meddelande:sub PARADOX <ditt namn>

Pascal-L

Område:Själva språket Pascal
Adress:listserv@vm3090.ege.edu.tr
Meddelande:subscribe PASCAL-L <ditt namn>

TASM-L

Område:Turbo Assembler och Turbo Debugger
Adress:listserv%brufpb.bitnet@listserv.net
Meddelande:sub TASM-L <ditt namn>

Teechart

Område:Diagramkomponenten Teechart
Adress:teechart-request@lhn.gns.cri.nz
Meddelande:SUBSCRIBE

QuickReport

Område:QuickReport rapportgenerator
Gå till:http://scar.qsd.no/products/quickrep/mailing/

Winhlp-L

Område:WinHelp, Windows hjälpfiler och dess kompilator
Adress:listserv@admin.humberc.on.ca
Meddelande:sub WINHLP-L <ditt namn>

Motsvarighet till Delay

Finns det någon motsvarighet till Delay som alltså gör så att programmet väntar en viss tidsrymd innan det fortsätter?

Svar:

I Win32 finns Sleep() och SleepEx() åstadkommer just detta. Dessa anrop finns inte i Win16, men man kan genom att använda GetTickCount och Application.ProcessMessages ordna detta rätt lätt. GetTickCount returnerar antal millisekunder sedan Windows startades.

procedure Delay(msecs: Longint);
var
   FirstTickCount: Longint;
begin
   FirstTickCount := GetTickCount;
   repeat    
      Application.ProcessMessages; 
   until (GetTickCount - FirstTickCount) >= Longint(msecs);
end;

Nästlade kommentarer

Kan man nästla kommentarer, alltså ha kommentarer inuti kommentarer, i Delphi?

Svar:

Nej, inte om du använder samma kommenteringsmetod. Pascal stöder ju både { } och (* *). Du kan nästla kommentarer genom att använda olika typer. Det räcker i alla fall till två nivåers nästling. I Delphi 2.0+ kan man också använda C++-kommentarer, //, som räcker raden ut.

(*
   DoSomething;
{  Blaha; }
   Foo;
*)

Omvandla från hexadecimal sträng

Hur omvandlar jag en hexadecimal sträng till ett tal på enklast möjliga sätt?

Svar:

Sätt ett dollartecken framför och skicka till vanliga StrToInt.

StrToInt('$9A');

Programkrascher under OS/2

Varför kraschar mitt program när det ska startas under OS/2 2.1? Det fungerar perfekt när jag testat det under Windows 3.1 och Windows 95.

Svar:

Den enda lösningen (såvitt jag vet) är att se till att BDE Configuration är igång samtidigt. Felet har möjligen åtgärdats i senare versioner av OS/2. Lägg en skugga av BDECFG.EXE i startmappen.

Påskägg

Vad finns det för påskägg (easter eggs) i Delphi?

Svar:

Det finns tre stycken (i Delphi 1.0), alla aktiveras genom att man i Om-dialogen håller nere Alt och skriver något.

En sista sak, som väl egentligen inte är ett påskägg, är att du får upp versionsnumret när du skriver version. För en patchad engelsk version av Delphi 1.0 är versionnumret (av någon anledning) 1.47.135.0.

Radbrytning i meddelanden

Hur får jag in radbrytningar i meddelanderutor?

Svar:

Genom att lägga in #13, #10 eller ^M i texten. Här gäller det alltså tecknena, dessa ska alltså inte stå innanför citationstecknena.

MessageDlg('Ett långt'#13' meddelande!', mbOK, 0);
MessageDlg('Ett långt'#10' meddelande!', mbOK, 0);
MessageDlg('Ett långt'^M' meddelande!', mbOK, 0);

Radera hela katalogträd

Hur raderar jag hela katalogträd?

Svar:

Det troligen enklaste är att använda sig av rekursion, alltså funktioner som anropar sig själva. Nedanstående funktion kontrollerar inte om filerna är skrivskyddade eller öppna utan förutsätter att det bara är att ta bort. Ingen felkontroll förekommer, detta fixar man enklast med try ... except.

procedure RemoveTree(DirName: string);
var
   FileSearch: SearchRec;
begin
   { Börja med att ta bort alla underkataloger }
   ChDir(DirName);
   FindFirst('*.*', Directory, FileSearch);
   while (DosError = 0) do 
   begin
      if (FileSearch.Name <> '.') and (FileSearch.Name <> '..') and
         ( (FileSearch.Attr and Directory) <> 0) then 
      begin
         if DirName[Length(DirName)] = '\' then
            RemoveTree(DirName + FileSearch.Name)
         else
            RemoveTree(DirName + '\' + FileSearch.Name);
         ChDir(DirName);
      end;
      FindNext(FileSearch);
   end;
   { Ta därefter bort alla filer }
   FindFirst('*.*', AnyFile, FileSearch);

   while (DosError = 0) do 
   begin
      if (FileSearch.Name <> '.') and (FileSearch.Name <> '..') then
         DeleteFile(FileSearch.Name);
      FindNext(FileSearch);
   end;
   RmDir(DirName)
end;

Rotera bilder

Hur roterar man bilder?

Svar:

Använd 2 st TImage, en för källan och en för destinationen. Följande kod borde duga (t är rotationsvinkeln):

ct := Cos(t);
ccst := 1 / Sqr(Cos(t)) + Sin(t);
for x := 0 to Src.Width do
   for y := 0 to Src.Height do
      Dest.Pixels[x,y] := Source.Pixels[Round((x * ct + y) * ccst),
         Round((y * ct - x) * ccst));

Skicka med VBRUNn00.DLL

Måste jag skicka med VBRUN100/200/300 om jag använder en VBX i mitt program? I sådana fall, vilken?

Svar:

Nej, VBXerna innehåller kompilerad kod som inte tolkas. Däremot krävs BIVBX11.DLL som ska installeras i systemkatalogen.

Skillnanden mellan Concat och AppendStr

Vad är skillnaden mellan Concat och AppendStr?

Svar:

Concat fungerar som operatorn + och kan lägga ihop flera strängar samtidigt, AppendStr klarar bara av en - men gör det hela något snabbare.

Skärmsläckare i Delphi

Hur gör man skärmsläckare i Delphi?

Svar:

  1. Lägg till {$D SCRNSAVE } någonstans i projektfilen (*.DPR).
    {$D SCRNSAVE Min skärmsläckare}
    
  2. Stäng av alla delar av BorderIcons och ställ in så att programmet täcker hela skärmen (lämpligen genom att sätta Left och Top till 0 och samtidigt sätta WindowState till wsMaximize).
  3. Lägg till lämpliga event handlers med kod för de meddelanden som ska få skärmsläckaren att avbrytas.
  4. Kolla efter parametrarna /C och /S i exempelvis OnCreate. Om parametern är /C, ska inställningsdialogen visas i stället för skärmsläckaren.
  5. Lägg till en handler för OnIdle och skriv in koden för själva skärmsläckaren där.
  6. Byt namn på den färdiga EXE-filen till .SCR och kopiera den till Windows-katalogen.

Standardstorlek för kodfönster

När Delphi kraschar (och vanligen hänger hela datorn) sparas inte storleken på kodfönstret och jag tvingas ändra den manuellt gång på gång. Går det att fixa en standardstorlek för alla kodfönster?

Svar:

I sektionen [Editor] i DELPHI.INI finns två värden som kan användas till detta, DefaultWidth och DefaultHeight. Dessa skapas inte som standard utan du tvingas lägga till dem manuellt.

DELPHI.INI är dokumenterad i DOC\INIFILE.TXT.

Starta program från Winhelp

Jag har sett hjälpfiler som startar andra program och dokument. Hur går detta till?

Svar:

Att bara starta program, är väldigt enkelt, för detta finns makrot ExecProgram. Detta makro tar två parametrar, sökvägen till programmet samt hur programmet ska visas. Den senare är inte som man skulle kunna tro, samma som SW_XXX-konstanterna, här är det följande värden som gäller:

0Normal
1Minimerad
2Maximerad
!ExecProgram(`NOTEPAD.EXE', 0)

Notera gravaccenten.

Om du vill starta dokument måste du använda Windows API-funktionen ShellExecute. I WinHelp kan du i princip anropa vilka funktioner i vilken DLL som helst genom att lägga in RegisterRoutine i sektionen [Config] i hjälpprojekets HPJ-fil enligt nedan:

[Config]
RegisterRoutine("SHELL", "ShellExecute", "uSSSSu")

Var noga med att det blir rätt med stora och små bokstäver. Sedan sker anropet i hjälpfilen på samma sätt som i ett vanligt program:

!ShellExecute(0, `open', `readme.txt', `', `', 1)

Kom ihåg att URLer och mailadresser också räknas som dokument, du kan alltså hoppa direkt till en websida eller starta mailprogrammet med ett färdig adresserat och tomt mail från Winhelp. Var bara noga med att specificera rätt protokoll, http://, ftp:// eller mailto:.

!ShellExecute(0, `open', `http://www.delphi32.com', `', `', 1)
!ShellExecute(0, `open', `mailto:foo@bar.com', `', `', 1)

Tänk också på att funktioner som AutoCorrect i Word kan strula till det hela genom att ersätta raka citattecken mot typografiska (ASCII 0146). Det enklaste är att göra ett makro som infogar rätt tecken från början.

Statiska variabler

Finns det en motsvarighet i Delphi till statiska variabler i C/C++?

Svar:

Ja, de kallas av någon anledning för typed constants och är förvillande lika vanliga konstanter. Skillnaden är att dess typ specificeras och att värdet alltså kan förändras under gång.

procedure CallCount;
const
   Count: Integer = 0;
begin
   Inc(Count);
end;

Innehållet av Count kommer att finnas kvar även andra gången proceduren anropas. CallCount kommer alltså att innehålla antalet gånger proceduren anropats.

Strukturer med samma namn

Hur gör jag om jag ska deklarera en Windows-varianten av TBitmap, d.v.s. strukturen som hör till WinAPI och inte Delphi-klassen? De har ju samma namn.

Svar:

Markera vilken unit du anser. Samma metod kan användas för funktioner, vilket tagits upp i Delphi Q&A tidigare.

var
   Bitmap: WinTypes.TBitmap;

Ta bort programfilen i Delphi 2.0

Jag vill att mitt program ska kunna ta bort sig självt när det är klart. I Delphi 1.0 kan jag använda

DeleteFile(Application.ExeName);

men det fungerar inte i Delphi 2.0.

Svar:

Nej, programfilen är öppen under hela tiden programmet körs i Win32. Den bästa lösningen ligger i att utnyttja WININIT.INI. I den filen finns en [rename]-sektion som du kan använda för att byta namn på, flytta eller ta bort filer (detta används av t.ex. installationsprogram som måste byta ut systemfiler som används och är låsta när Windows GUI körs). Om filen ska tas bort ska nyckeln kallas nul. Korta filnamn måste användas. När filer tagits bort försvinner också raden i WININIT.INI.

[rename]
NUL=C:\MYDIR\SELFDEL.EXE

Ett annat (sämre) sätt är att göra en BAT-fil som använder START för att köra ditt program (BAT-filer kan ta bort sig själva) eller lämna kvar programmet för att låta det tas bort av programmet du installerade (detta problemet uppstår väl i huvudsak med installationsprogram) första gången det körs.

Tal- och datumformat

Var hittar jag information om vilka tal-, valuta- och datumformat som gäller på den dator programmet körs på?

Svar:

Det enklaste är att använda de färdiga variabler (eg. typed constants) som finns i unit SysUtils:

CurrencyString: string[7];
CurrencyFormat: Byte;
NegCurrFormat: Byte;
ThousandSeparator: Char;
DecimalSeparator: Char;
CurrencyDecimals: Byte;
DateSeparator: Char;
ShortDateFormat: string[15];
LongDateFormat: string[31];
TimeSeparator: Char;
TimeAMString: string[7];
TimePMString: string[7];
ShortTimeFormat: string[15];
LongTimeFormat: string[31];
ShortMonthNames: array[1..12] of string[3];
LongMonthNames: array[1..12] of string[15];
ShortDayNames: array[1..7] of string[3];
LongDayNames: array[1..7] of string[15];

Ett annat sätt är att läsa i sektionen [intl] i WIN.INI. Mer information om vad dessa betyder finns i hjälpen till Delphi, slå t.ex. på "currency formatting constants".

TDateTime i Delphi 1.0/2.0

Jag har gjort ett program som jag både har en 16- och en 32-bitsversion av. Jag använder Paradox-tabeller för att spara data i. Ett av fälten är ett datumfält som jag kommer åt genom en TDateTime. Problemet är att datum som sparats med Delphi 1.0 blir fel i när det läses med Delphi 2.0 och vice versa. Vad är felet?

Svar:

TDateTime skiljer sig något mellan de olika versionerna av Delphi. I Delphi 1.0 baseras typen på datumet 1 januari 0001, medan Delphi 2.0 baseras på 30:e decemeber 1899 för att vara mer i linje med hur OLE lagrar datum. En lösning kan vara att läsa det från databasen med AsString, för att sedan konvertera värdet till en TDateTime.

Veckonummer från datum

Hur tar jag reda på veckonumret (1-52) från ett datum såsom 96-01-10?

Svar:

function WeekNum(DateToUse: TDateTime): Integer;
var 
   xYear, xMonth, xDay: Word;
   xDate: TDateTime;
begin
   DecodeDate(xYear,xMonth,xDay,DateToUse);
   xDate := EncodeDate(xYear,1,1);
   WeekNum := ((DateToUse -  xDate) div 7) + 1;
end;

Vem är Windows registrerat till?

Hur tar man reda på namnet på personen och företaget som Windows är registrerat till?

Svar:

Detta är lagrat i registret under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion. Följande funktion letar reda på informationen åt dig:

procedure GetRegistered(var Name, Company: string);
var
   Reg: TRegIniFile;
   OS: TOSVersionInfo;
   WinType: String;
begin
   if Length(CompanyName) = 0 then
   begin
      OS.dwOSVersionInfoSize := sizeof(TOSVERSIONINFO);
      GetVersionEx(OS);
      case OS.dwPlatformId of
         VER_PLATFORM_WIN32s        : WinType := 'Windows 3.1x/32s';
         VER_PLATFORM_WIN32_WINDOWS : WinType := 'Windows 95';
         VER_PLATFORM_WIN32_NT      : WinType := 'Windows NT';
      end;
      Reg := TRegIniFile.Create('');
      try
         with Reg do

         begin
            RootKey := HKEY_LOCAL_MACHINE;
            LazyWrite := True;
            if Wintype = 'Windows NT' then
               OpenKey('\SOFTWARE\Microsoft\Windows NT',False)
            else
               OpenKey('\SOFTWARE\Microsoft\Windows',False);
            Company := ReadString('CurrentVersion',
               'RegisteredOrganization', '');
            Name := ReadString('CurrentVersion',
               'RegisteredOwner', '');
         end;
      finally
         Reg.Free;
      end;
   end;
end;

Vilken beskrivning har en filtyp?

Hur tar man reda på beskrivningen av en filtyp, den beskrivning som visas i Utforskaren?

Svar:

Detta lagras i Registret, använd TRegistry för att komma åt det. Varje registrerad filtyp lagras under HKEY_CLASSES_ROOT, därefter som undernyckel med en punkt framför. Värdet på den nyckeln hänvisar sedan till en annan nyckel, som i sin tur innehåller beskrivningen. Om vi tar registerfiler (*.reg) som exempel:

HKEY_CLASSES_ROOT\.reg

Värdet för detta är 'regfile'. Sök upp nyckeln HKEY_CLASSES_ROOT\regfile och kolla dess värde, där finns beskrivningen (som är 'Registreringsposter'). Kör REGEDIT.EXE och kolla runt lite i registret för att lära dig dess struktur och innehåll.

Visa disassembly

Jag läste i Borland TI3172 att man i Delphi 2 kunde få upp ett fönster kallat DisassemblyView som visade assemblerkoden för den kod man avlusade (på samma sätt som CPU Window i Turbo Debugger). Det stod att man skulle lägga till ett strängvärde med innehållet "1" under HKEY_CURRENT_USER\Software\Borland\Delphi\2.0\Debugging, men vad ska värdet heta?

Svar:

Värdets namn ska vara EnableCPU.

Visa Kontrollpanelen

Hur gör jag för att öppna en dialog från Kontrollpanelen?

Svar:

Under Windows (NT) 3.x startar du bara CONTROL.EXE med namnet på dialogen som parameter:

ShellExecute(Handle, nil, 'control.exe', 'Skrivare', '', SW_SHOWNORMAL);

Detta fungerar förvisso även under Windows 95/98/NT4, men en bättre metod som fungerar med alla språk och dessutom låter dig välja flik att börja med är den här:

ShellExecute(Handle, Nil, 'rundll32.exe', 
             'shell32.dll,Control_RunDLL desk.cpl ,2', 
             '', SW_SHOWNORMAL);

desk.cpl är den kontrollpanelsfil som påverkar skärm- och skrivbordsinställningarna. Tvåan efteråt talar om vilken flik som ska visas (utifrån ett nollbaserat index räknat från vänster). I det här fallet visas Utseende/Appearance-fliken.

Ändra text i LaserJet-fönster

Jag vet att det går att ändra texten som visas i displayen på en HP LaserJet, men hur går man till väga?

Svar:

Detta görs med PJL (Printer Job Language). Följande kommandon är aktuella:

RDYMSG

Den text som visas när skrivaren är klar för utskrift, normalt är denna text "00 READY" eller "00 REDO".

OPMSG

Ett speciellt "operator message" visas och skrivaren tas offline.

STMSG

Ett statusmeddelande visas och skrivare tas offline. I retur skickas den tangent som användaren trycker ned för att få skrivaren online igen.

Ett exempel på RDYMSG:

<esc>%-12345X@PJL <cr><lf>
@PJL JOB NAME = "Delphi job" <cr><lf>
@PJL RDYMSG DISPLAY = "DELPHI JOB" <cr><lf>

<esc>%-12345X@PJL <cr><lf>
@PJL ENTER LANGUAGE = PCL <cr><lf>
<esc>E  ...your PCL job, sent directly...<esc>E
   <esc>%-12345X<esc>%-12345X@PJL <cr><lf>

@PJL COMMENT Restore READY message <cr><lf>
@PJL RDYMSG DISPLAY = "" <cr><lf>
@PJL EOJ NAME = "End of Delphi Job" <cr><lf>
<esc>%-12345X

Ovanstående är vad en spooler skulle skicka, själva tillämpningsprogrammet skickar enbart raderna i mitten.

Informationen kommer ifrån Printer Job Language Technical Reference Manual. 1992. Edition 1. Hewlett Packard corp.

Är CDn som sitter i en musik-CD?

Hur kollar jag om den CD som ligger i är en musik-CD eller inte?

Svar:

Kolla volymnamnet med FindFirst (D1) eller GetVolumeInformation (D2), det ska vara 'AUDIO CD' för alla musik-CDs.

Öppna parametrar

Hur använder jag öppna parametrar, alltså vektorer där storleken inte är bestämd vid kompileringen?

Svar:

Gör som vanligt, men utelämna den av hakparenteser innefattade storleken.

function Compute(const A: array of Real): Real;

Använd SizeOf(), High() och Low() för att få information om vektorn när programmet körs. SizeOf() ger storleken, High() ger indexet på det sista värdet och Low() det lägsta indexet.

for I := Low(C) to High(C) do
   B[I] := Sqrt(C[I]);

Öppna URL i browser

Hur öppnar jag en URL i den installerade standardbrowsern?

Svar:

Om du utvecklar för Win32 är det enklast är att använda ShellExecute, den hanterar automatiskt de vanligaste protokollen. Vill du ta reda på vilken browser som är default kan du avläsa värdet av HKEY_CLASSES_ROOT\http\shell\open\command.



RXML parse error: This tag doesn't handle content.
 | <modified type="iso">