Der folgende Aufruf erstellt alle Verzeichnisse, sofern diese noch nicht vorhanden sind.
String verz="C:\\test1\\test2\\test3"; ForceDirectories(verz);
Escapesequenzen dienen zur Repräsentaion nicht darstellbarer Zeichen. Beispielsweise sind Zeilenumbrüche solch nicht darstellbare Zeichen, aber man möchte sie ja an bestimmten Stellen doch irgendwie einbringen, um dann einen Zeilenumbruch zu erzeugen.
Eine Escapesequenz wird mit einem Backlash "\" eingeleitet, gefolgt von
| \a | Alarmton |
| \b | Backspace |
| \f | Seitenvorschub |
| \n | Zeilenvorschub |
| \r | Wagenrücklauf |
| \t | Tabulator horizontal |
| \v | Tabulator vertikal |
| \\ | Backlash selbst |
| \' | Hochkommata |
| \" | doppelte Anführungszeichen |
Die Sequenzen können in Ereignissen wie OnKeyDown oder in der Klasse AnsiString (String) o.a. genutzt werden um beispielweise zu prüfen ob ein Tabulator gesetzt ist. Auf die Virtuellen Tastencodes von Windows wird verwiesen.
Die Schlüssel in der Registry unterliegen den gleichen Beschränkungen wie Dateien im Filesystem. Nicht jeder Nutzer kann jeden Schlüssel lesen oder verändern. Allerdings ist es manchmal sehr störend, wenn man ein Programm schreibt, welches allen Benutzer auf dem Rechner zur Verfügung stehen soll und einige Benutzer können auf einmal nicht auf die Registry zugreifen. Üblicherweise sind ja dort die Daten des Programmes abgelegt. Hier nun der Code wie ein Schlüssel für alle Benutzer zugänglich gemacht wird.
Dabei wird davon ausgegangen, dass es den Schlüssel schon gibt. Wir man mit der Registry arbeitet steht hier.
Leider liefer die Klasse TRegistry keinen Hendle auf das Schlüsselobjekt. Da hier aber Microsoft Funktionen für den Zugriff auf den Schlüssel genutzt werden müssen (die bietet die Klasse TRegistry ebenfalls nicht) muss das ganze mit der WIN 32 API erfolgen:
HKEY key=NULL;
long test=RegOpenKeyEx(HKEY_LOCAL_MACHINE,"Software\\MeinSchlüssel",0,KEY_ALL_ACCESS,&key);
if(test==ERROR_SUCCESS)
{
SECURITY_ATTRIBUTES sec_attr;
SECURITY_DESCRIPTOR sec_des;
memset(&sec_attr,0,sizeof(SECURITY_ATTRIBUTES));
InitializeSecurityDescriptor( &sec_des,SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sec_des,true,(PACL)NULL,false);
sec_attr.nLength=sizeof(SECURITY_ATTRIBUTES);
sec_attr.lpSecurityDescriptor=&sec_des;
sec_attr.bInheritHandle=TRUE;
RegSetKeySecurity(key,(SECURITY_INFORMATION)DACL_SECURITY_INFORMATION,&sec_des);
RegCloseKey(key);
}
Keinesfalls is dieses Vorgehen mit dem Paramter KEY_ALL_ACCESS zu verwechseln, dem man bei der Klasse TRegistry angeben kann. Diese gilt nur für Benutzer gleicher Ebene
In den Ereignissroutinen der VCL wird der Auslöser des Ereignisses als TObject *Sender der Ereignisroutine übermittelt. Oft nutzt man eine Routine für mehrere Objekte (Buttons, Checkboxen, Radiobuttons u.a.). Nun stellt sich das Problem, dass man zur Laufzeit gerne wissen möchte, wer von den Objekten das Ereigniss ausgelöst hat und welche Eigenschaften das Objekt gerade hat.
Ist es nur interesannt zu wissen, wer die Routine ausgelöst hat, reicht ein einfacher Vergleich der Zeiger auf das Objekt:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(Sender==Mein-gefragter-Button)
{
}
}
Will man jedoch die Eigenschaft Caption auslesen oder wissen, ob der Button einen Hint hat, muß man sich zuerst einen (Hilfs-)Button anlegen und diesem mitteilen, dass er alle Methoden und Eigenschaften von Sender übernehmen soll. Das geht mit einer Programmzeile:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TButton *Hilfsbutton=dynamic_cast<TButton*> (Sender);
if(Hilfsbutton)
String test=Hilfsbutton->Caption;
}
Hierbei wird mit Hilfe des Schlüsselwortes dynamic_cast<T>
(ptr) der Zeiger ptr in einen Zeiger der Klasse T umgewandelt. Dann wird dem
String test die Beschriftung des Button zugewiesen.
Hinweis: Für
die Nutzung von dynamic_cast müssen Laufzeitinformationen (RTTI)
verfügbar gemacht werden.
C++Bilder und Delphi haben etwas gemeinsam: Sie lassen es zu, dass der C++- bzw. Delphi-Code in den beiden Programmierumgebungen verwendet wird. Was aber interessiert, ist, wie man Delphi Units in C++Builder nutzen kann.
C++Builder interessiert es nicht, dass sich in einem Builder-Projekt neben C++Builder-Units auch Delphi-Units befinden. Er behandelt sie so, als wären sie C++Builder-Units. Insofern ist es manchmal sehr nützlich, Delphi-Units in einem C++Builder-Projekt einzubinden zu können. Das folgende Beispiel soll diesen Vorgang erläutern.
Die Beispiel-Delphi-Unit sieht folgendermassen aus:
{Beispiel Unit: CalcUnit.pas}
unit CalcUnit;
interface
type
OperationEnum = (plus, minus);
function Calc(i : Integer; j : Integer;operation : OperationEnum) :
Integer;
implementation
function Calc(i : Integer; j : Integer; operation : OperationEnum) :
Integer;
begin
case operation of
plus : Result := i+j;
minus : Result := i-j;
end;
end;
end.
Diese Unit, unter CalcUnit.pas gespeichert, zum C++Builder-Projekt hinzufügen:
Die Delphi-Unit ist jetzt im Projekt eingebunden. Wenn das Projekt jetzt compliert wird, dann wird eine *.hpp-Datei für diese *.pas-Datei automatisch generiert. In diesem Beispiel sieht "CalcUnit.hpp" folgednermassen aus:
// Borland C++ Builder
// Copyright (c) 1995, 2002 by Borland Software Corporation
// All rights reserved
// (DO NOT EDIT: machine generated header) 'CalcUnit.pas' rev:6.00
#ifndef CalcUnitHPP
#define CalcUnitHPP
#pragma delphiheader begin
#pragma option push -w-
#pragma option push -Vx
#include <SysInit.hpp> // Pascal unit
#include <System.hpp> // Pascal unit
//-- user supplied -----------------------------------------------
namespace Calcunit
{
//-- type declarations -------------------------------------------
#pragma option push -b-
enum OperationEnum { plus, minus };
#pragma option pop
//-- var, const, procedure ---------------------------------------
extern PACKAGE int __fastcall Calc(int i, int j, OperationEnum
operation);
} /* namespace Calcunit */
using namespace Calcunit;
#pragma option pop // -w-
#pragma option pop // -Vx
#pragma delphiheader end.
//-- end unit ----------------------------------------------------
#endif // CalcUnit
Man könnte jetzt diese .hpp-Datei vom unnötigen Code befreien, ist allerdings nicht notwendig. Um nun die in der .pas-Datei definierten und implementierten Funktionen etc. nutzen zu können, muss diese .hpp-Datei im Projekt includiert werden:
#include "CalcUnit.hpp"
Und jetzt kann von der .cpp-Datei aus auf die Funktionen etc. der .pas-Datei zugegriffen werden:
//----------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowMessage(Calc(2,1,plus));
}
//----------------------------------------------------------------
Oft steht man vor dem Problem, dass man eine Klasse nutzen möchte die noch nicht deklariert wurde, bzw. man in zwei verschiedenen Klassen Objekte der jeweils anderen Klasse anlegen möchte. Das Problem kann wie folgt gelöst werden:
class A
{
my_b=new B;
};
class B
{
my_b=new A;
};
Im obigen Beispiel wird also in der Klasse A ein Objekt der Klasse B
angelegt. Die ist aber noch gar nicht deklariert. Und auch in der Klasse B wird
wiederum ein Objekt der Klasse A angelegt. Ein Vertauschen der Deklarationen
würde also am Problem nichts ändern.
Lösung:
class B;
class A
{
my_b=new B;
};
class B
{
my_b=new A;
};
Durch die hinzugefügte Zeile, teilen wir dem Compiler erstmal mit, dass es eine Klasse B gibt. Damit ist dieser erstmal zufrieden, den die Deklaration kommt ja etwas später.
Wandelt einen String vom Typ STRING, der einen HEX-Wert enthält in einen Integer um.
int zahl=strtol(EingabeString.c_str(),NULL,16);
Die ADOConnection-Komponente bietet trotz installiertem MySQL unter der Eigenschaft Provider nicht die Möglichkeit an, sich mit einer MySQL Datenbank zu verbinden. Man kann aber den Connection-String selbst vorgeben, womit dann auch der Zugriff auf eine MySQL Datenbank möglich ist. Voraussetzung ist, dass MySQL installiert ist und der entsprechende ODBC-Treiber. Zurzeit sind die Versionen 3.51 und Alpha 5.0 als ODBC-Treiber verfügbar. Die ADO-Komponenten arbeiten mit beiden zusammen. Hier nur ein Beispiel für die Verbindung:
TADOConnection *con=new TADOConnection(this);
con->ConnectionString="DRIVER={MySQL ODBC 3.51 Driver}; SERVER=localhost; PORT=3306; DATABASE=test; USER=root; PASSWORD=; OPTION=3;";
oder mit dem 5.0 Treiber
TADOConnection *con=new TADOConnection(this);
con->ConnectionString="DRIVER={MySQL Connector/ODBC v5}; SERVER=localhost; PORT=3306; DATABASE=test; USER=root; PASSWORD=; OPTION=3;";
Die entsprechenden Paramter (SERVER, PORT, DATABASE, USER, PASSWORD) müssen natürlich mit den eigenen Daten bestückt werden.
Siehe auch ODBC-Treiber auslesen
Mit dem folgenden Codestück kann man in einem TMemo zu einer bestimmten Zeile scrollen oder nach Änderung auch immer die letzte Zeile anzeigen:
// Cursor an Anfang von Zeile 5 setzen Memo1->SelStart = Memo1->Perform(EM_LINEINDEX, 4, 0); // zur Zeile mit Cursor scrollen Memo1->Perform(EM_SCROLLCARET, 0, 0);
Grundlage dür die Formatierung von Text in einem TRichEdit sind die Methoden
SelStart gibt die Position im Text an, ab der die Formatierung erfolgen soll. SelLength gibt an, wie lang der Textteil ist, der formatiert werden soll.
Mit der Methode SelAttributes werden dann für den ausgewählten Text die Formatierungen gesetzt.
Im folgendem Beispiel wird der gesamte Text in einem TRichEdit durchsucht. Text der zwischen Anführungszeichen steht wird mit der Farbe Blau und der Schrifteigenschaft Fett dargestellt. Es wird dabei davon ausgegangen, dass das TRichEdit den Namen "Textanzeige" hat.
int start=1,ende;
start=PosEx("\"",Textanzeige->Text,start);
while(start>0)
{
Textanzeige->SelStart=start;
ende=PosEx("\"",Textanzeige->Text,start+1);
Textanzeige->SelLength=(ende-start)-1;
Textanzeige->SelAttributes->Color=clBlue;
Textanzeige->SelAttributes->Style=Textanzeige->SelAttributes->Style<<fsBold;
start=PosEx("\"",Textanzeige->Text,ende+1);
}
Das zweite Beispiel zeigt, wie man eine Zeile hervorheben kann. Es wird die Zeile 3 selektiert und dann der Hintergrund dieser Zeile auf die Farbe Gelb gesetzt
Richedit::CHARFORMAT2 cm; memset(&cm,0,sizeof(Richedit::CHARFORMAT2)); cm.cbSize=sizeof(Richedit::CHARFORMAT2); cm.dwMask=CFM_BACKCOLOR; cm.crBackColor=clYellow; Textanzeige->SelStart=Textanzeige->Perform(EM_LINEINDEX, 3, 0); Textanzeige->SelLength=Textanzeige->Lines->Strings[3].Length(); Textanzeige->Perform(EM_SETCHARFORMAT, SCF_SELECTION,(LPARAM)&cm );
Oftmals werden Daten als Integer-Wert gespeichert. Nun ensteht das Problem, dass man daraus gerne wieder den ASCII Code hätte.
int x=65; String buchstabe=(char)x;
Und weil es so schön ist auch gleich der umgekehrte Weg:
String buchstabe="A"; int x=(char)buchstabe[1];
Mit Hilfe dieses Beispiels kann man ein Layout ähnlich wie die IDE des C++Builders erzeugen. Die 3 Fensten können auf unterschiedliche Art und Weise an das Main-Fenster angedockt werden. Mittels eines Doppelklick werden sie wieder herausgelöst.
Über das Menü können die Fenster ein- und ausgeschaltet werden und wieder die Standardgröße und -position hergestellt werden.

Da auch Einstellungen im Objektinspektor erforderlich sind, hat es wenig Sinn den Quellcode hier darzustellen. Aus diesen Grunde hier das komplette Projekt zum Download
Wandelt einen Integer, der eine Zahl enthält in einen STRING um, der die Zahl als HEX-Wert enthält.
char buff[20]; sprintf(buff,"%X",inputzahl); Ergebnis->Text=buff; //Anzeige in einer Edit-Komponeten o.a.
oder mit dem C++Builder Befehl
String Hexzahl=IntToHex(Wert,Stellen)
Wert gibt den Umrechnungswert an, Stellen die Anzahl der zurückgelieferten Stellen.
Mit der Funktion ShellExecute können andere Programme oder Dateien und die damit verbundenen Programme gestartet werden. Die Funktion erwartet folgende Parameter:
| HWND hwnd | Zeiger auf das Elternfenster |
| LPCTSTR lpop | Operationscode: 'open' = öffnet eine Datei 'print' = Druckt die Datei 'explore' = öffnet den Ordner |
| LPCTSTR file | Zeiger auf den Dateinamen oder Ordner |
| LPCTSTR para | Wenn 'file' ein Programm ist, können hier die Kommandozeilenparameter definiert werden |
| LPCTSTR direc | Das ggf. ausgewählte Directory |
| INT show | In welcher Form das Programm gestartet werden soll
SW_HIDE = Fenster versteckt SW_SHOWNORMAL = Fenster normal anzeigen |
Beispiele (Dabei wird davon ausgegangen, dass die Anwendung die die Funktion aufruft "MeineAnwendung" heisst:
Das folgende Beispiel öffnet eine HTML Datei (MeineHTMLDatei.html)
int aus=(int)ShellExecute(MeinFormular->Handle,"open","MeineHTMLDatei.html",NULL,NULL,SW_SHOWNORMAL);
if(aus<=32)
Application->MessageBox("Browser nicht installiert","HTML-Datei starten",MB_APPLMODAL|MB_ICONSTOP|MB_OK);
Das folgende Beispiel öffnet das Programm TEST.EXE und übergibt als Parameter den Wert "parameter1":
int aus=(int)ShellExecute(MeinFormular->Handle,0,"TEST.EXE","parameter1",0,SW_SHOWNORMAL);
if(aus<=32)
Application->MessageBox("Fehler bei Ausführung des Progammes","Programm starten",MB_APPLMODAL|MB_ICONSTOP|MB_OK);
In der Regel liegen Dateinnamen u.a. als ANSI-String vor. Die Funktion erwartet jedoch Zeiger auf Strings. Dazu kann die Methode c_str() genutzt werden.
Beispiel:
String datei="MeineHTMLDatei.html";
int aus=(int)ShellExecute(MeinFormular->Handle,"open",datei.c_str(),NULL,NULL,SW_SHOWNORMAL);
if(aus<=32)
Application->MessageBox("Browser nicht installiert","HTML-Datei starten",MB_APPLMODAL|MB_ICONSTOP|MB_OK);
Siehe auch Wie startet man ein Programm mit CREATE PROCESS?
Hierfür ist die Funktion SHGetFileInfo vorgesehen. Das folgende Beispiel zeigt wie das Icon geholt wird und in einem TImage-Objekt angezeigt wird:
//Icon holen
SHFILEINFO shinfo;
SHGetFileInfo("g:\\tespdf.pdf", NULL, &shinfo, sizeof(shinfo), SHGFI_SMALLICON|SHGFI_ICON);
Das Handle des Icon steht nun in shinfo.hIcon zur Verfügung
//Icon in TImage-Objekt anzeigen TIcon *ic=new TIcon(); ic->Handle=shinfo.hIcon; Image1->Picture->Icon=ic; delete ic;
Für das ReNamer Programm brauchte ich die EXIF-Informationen. Einige schon im Internet vorhandene Quellcodes probiert. Die waren jedoch teilweise fehlerhaft, liefen nicht mit mit dem BCB oder auch nur schlecht zu bedienen. Alos eine eigene Klasse entworfen.
Die EXIF-Daten sind in Form von nummerischen Key/Value - Paaren in den Dateien verankert. Es sind also folgende Schritte notwendig:
Die Klasse übernimmt die Punkte 1-3.
GExif |
||
| public: | __fastcall GExif(String file); | Konstruktor - Der Parameter enthält den kompletten Pfad der Datei, deren EXIF-Daten gelesen werden sollen |
| bool isError; | Zeigt an, ob ein Fehler aufgetreten ist | |
| String __fastcall GetLastError(); | Gibt den Fehler als String zurück | |
| TStringList* __fastcall GetListWithNames(); | Gibt eine Key=Value Stringlist mit den gefunden Daten zurück. Dabei ist der Keywert durch ein Text ersetzt worden | |
| TStringList* __fastcall GetListWithTags(); | Gibt eine Key=Value Stringlist mit den gefunden Daten zurück. Dabei ist der Keywert erhalten geblieben | |
| String __fastcall GetValueByTag(int Key); | Gibt von einem beliebigen Key den Textwert zurück | |
Es wäre kein Problem gewesen, auch die Änderung von EXIF-Daten einzubauen. Jeoch hat die GDI+ Klasse Image von Microsoft so einen kleinen entscheidenen Nachteil:

Ich finde das ein ziemlich starkes Stück, solch einen Designfehler einzubauen und das Problem auf die Entwickler abzuwälzen. Sicherlich gibt es Lösungen dafür, doch als ich da gelesen hatte, ist mir die Lust vergangen.....
Download |
|
| GEXIF nur die Klasse | 6 Kb |
| GEXIF und DemoKlasse mit Demoprojekt | 743 KB |
Das Starten mit CREATEPROCESS ist nicht so einfach wie mit Shellexecute, bietet aber dafür mehr Möglichkeiten. Das folgende Beispiel startet ein Programm names test.exe und wartet darauf, dass es beendet wird. Das heisst, dieses Programm arbeitet erst weiter, wenn der Process des anderen Programmes beendet wurde.
String rt;
STARTUPINFO si;
PROCESS_INFORMATION pi;
memset(&si,0,sizeof(STARTUPINFO));
si.cb=sizeof(STARTUPINFO);
si.dwFlags=STARTF_USESHOWWINDOW;
si.wShowWindow=SW_SHOW;
rt="test.exe";
if(!CreateProcess(NULL,rt.c_str(),NULL,NULL,false,0,0,0,&si,&pi))
{
Application->MessageBox("Fehler beim Aufruf von test.exe","test.exe",MB_APPLMODAL|MB_ICONSTOP|MB_OK);
return;
}
WaitForSingleObject(pi.hProcess,INFINITE);
CloseHandle(pi.hThread);
Wichtig sind die Angaben in der STARTUPINFO Struktur. Dort wird das Aussehen bestimmt (Fenster sichtbar usw.). Durch ändern des WaitForSingleObject kann auch erreicht werden, dass das Programm nicht endlos wartet, sondern eine vorgegebene Zeit (in Milisekunden)
WaitForSingleObject(pi.hProcess,6000);
Bei einer Konsolenapplikation kann mit dem CreationFlag CREATE_NO_WINDOW verhindern dass überhaupt ein Fenster erzeugt wird.
Siehe auch Wie kann man andere Programme mit SHELLEXECUTE starten?
Mit dieser Komponente ist ein Up- und Download mit dem FTP (File Tranfer Protokoll möglich. Hier ein Beispiel für einen einfachen Upload und ein einfachen Download. Weitere Feature wie das Lesen des Directorys o.a. ergibt sich dann aus der Beschreibung der Komponente
Aus dem Reiter "Indy-Clients" ist eine TidFTP Komponente in das Formular mit aufzunehemen. Zunächst die die Parameter für den Server zu setzen:
FTP->Host="www.meinServer.de"; //Die FTP Komponente FTP->Username="******"; FTP->Password="*****"; FTP->Passive=false; //ist auch Standard FTP->Port=21; //ist auch Standard FTP->TransferType=ftBinary;//ist auch Standard
Dann kann die Verbindung hergestellt werden:
FTP->Connect();
Nun kann ein Up- oder Download erfolgen. Zunächst der Upload:
FTP->Put("h:\\bpl\\bild.jpg","/bild.jpg",false);
Die Parameter stehen für:
Mit diesen Parametern kann man also ein Datei, die auf dem localem System "bild.jpg" heisst auch als "meer.jpg" uploaden, ohne sie vorher unzubenennen. Wenn der Upload abeschlossen ist, wird die Verbindung geschlossen.
FTP->Disconnect();
Nun ein Download. Auch hier wird eine Verbindung hergestellt (wird das innerhalb eines Programmes mehrmals gemacht, kann eine bestehende Verbindung natürlich für mehrere Up- oder Download genutzt werden.
FTP->Host="www.meinServer.de"; //Die FTP Komponente FTP->Username="******"; FTP->Password="*****"; FTP->Passive=false; //ist auch Standard FTP->Port=21; //ist auch Standard FTP->TransferType=ftBinary;//ist auch Standard
Dann kann die Verbindung hergestellt werden:
FTP->Connect();
Nun der Download:
FTP->Get("/bild.jpg","h:\\bpl\\bild.jpg",true,false);
Die Parameter stehen für:
Mit diesen Parametern kann man also ein Datei, die auf dem Remoteserver "bild.jpg" heisst auch als "meer.jpg" downloaden, ohne sie vorher unzubenennen. Wenn der Download abeschlossen ist, wird die Verbindung geschlossen.
FTP->Disconnect();
Das ist hier nur der "Grund". Zu Beachten ist grundsätzlich, in welchem Modus eine Datei Up- oder downgeloaded werden soll, welcher Transfermode einzusetzen ist, welche Rechte die Datei auf dem Server bekommen muss u.v.m. Hinweise dazu stehen auch in der Beschreibung der Komponente
Beschreibung zu weiteren Indy-Komponeten:
Zunächst soll es hier darum gehen, auf Windowsmessages zuzugreifen die in der Form oder im aktuellen Steuerelement nicht auf der "Ereignis" -Seite des Objectinspetors zu finden sind.
Als Beispiel soll die Windowsmessage WM_MOVE abgefangen und ausgewertert werden.
Im ersten Schritt ist eine Botschaftszuordnungstabelle in der Headerdatei des Formulars zu erstellen. Diese sieht wie folgt aus:
//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Grids.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // Von der IDE verwaltete Komponenten
TStringGrid *StringGrid1;
TButton *Button1;
private: // Benutzer-Deklarationen
public: // Benutzer-Deklarationen
__fastcall TForm1(TComponent* Owner);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_MOVE, TMessage,OnMove)
END_MESSAGE_MAP(TForm)
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
Eine Botschaftszuordnungstabelle besteht aus:
Der in der Botschaftszuordnungstabelle befindliche MESSAGE_HANDLER besteht aus:
Im zweiten Schritt ist in der Headerdatei die eben erwähnte Funktion zu verankern
//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Grids.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // Von der IDE verwaltete Komponenten
TStringGrid *StringGrid1;
TButton *Button1;
private: // Benutzer-Deklarationen
void __fastcall OnMove(TMessage& Message);
public: // Benutzer-Deklarationen
__fastcall TForm1(TComponent* Owner);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_MOVE, TMessage,OnMove)
END_MESSAGE_MAP(TForm)
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
Nunmehr sollte sich die Headerdatei bereits ohne Fehler compilieren lassen.
Im dritten Schritt muss in der *.cpp des Formulars die eben in der Headerdatei angelegte Funktion implementiert werden
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "iostream"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::OnMove(TMessage& Message)
{
}
//---------------------------------------------------------------------------
Das sind excat die Arbeiten, die die IDE macht, wenn man ein Doppelklick in der Ereignisseite auf ein Ereignis macht. Damit sind die Arbeiten auch schon abgeschlossen. Um zu testen, ob die Message auch kommt, wir eine Messagbox genutzt:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "iostream"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::OnMove(TMessage& Message)
{
Application->MessageBox("Message da","Test",MB_OK|MB_ICONINFORMATION|MB_APPLMODAL);
}
//---------------------------------------------------------------------------
Bei einigen Messages kann das problematisch werden, da Windos diese ziemlich oft versendet.
Nun muss nur noch die TMessagestruktur ausgewertet werden und Windows mitgeteilt werden, ob oder das wir die Message bearbeitet haben. Hierzu sollte man sich in der MSDN die akutellen Informationen über die Message (hier WM_MOVE) besorgen. Danach wir also der wParam nicht genutzt und er lParam enthält die neuen Daten der Position. Wichtig ist der Rückgabewert der Message. Dieser ist 0 wenn wir die Nachricht bearbeitet haben.
Um auf die Werte von lParam zugreifen zu können, gibt es Makros
int PosX = LOWORD(lParam); int PosY = HIWORD(lParam);
oder man nutzt die von der Struktur vorgegebenen Möglichkeiten
int PosX = Message.LParamLo; int PosY = Message.LParamHi;
Wenn wir irgendwas mit den Werten gemacht haben (das Fenster verschoben) teilen wir dem System mit, dass unsere Anwendung die Nachricht bearbeitet hat.
Message.Result=0;
Um ein Fenster also am Postion 0/0 festzuhalten ist folgender Code nötig
Left=0; Top=0; Message.Result=0;
Diese Komponente synchronisiert die PC-Zeit mit einem Internetzeitserver. Dazu wird nicht das Windows "Einfallstor" RPC benutzt, sondern der "offizielle" Weg über Port 37 und das RFC 868 - Time Protocol. Port 37 darf also nicht durch eine Firewall geblockt sein.
Aus dem Reiter "Indy-Clients" ist eine TidTime Komponente in das Formular mit aufzunehemen. Wem das Timeout zu gring erscheit kann es erhöhen; sollte aber nicht notwendig sein.
bool test=false;
IdTime1->Host=NAME_DES_ZEITSERVERS;
try
{
test=IdTime1->SyncTime();
}
catch(...)
{
ShowMessage("Syncronisation nicht erfolgreich!");
}
if(test)
ShowMessage("Syncronisation erfolgreich!");
Folgende Zeitserver können u.a. genutzt werden:
Beschreibung zu weiteren Indy-Komponeten:
Das Fenster einer anderen Anwendung kann man mit folgendem Codestück finden:
EnumWindows((WNDENUMPROC)ShowAllWindows, 0);
BOOL CALLBACK ShowAllWindows(HWND hwnd,LPARAM lParam)
{
char pcWinTitle[256];
if(!GetWindow(hwnd, GW_OWNER))
{
GetWindowText(hwnd, pcWinTitle, 255);
//mach was mit dem Titel ->durchsuchen o.a.
}
return true;
}
Grundlage ist irgendein Teil aus dem Fenstertitel den man wissen sollte. Die Funktion GetWindowText holt den Fenstertitel, den man dann durchsuchen sollte. Das HWND wird der Funktion übergeben und steht, wenn man das richtigte Fenster gefunden hat, zur Verfügung.
Ist der Fenstername bekannt so gibt
HWND handle=FindWindow(0,Fenstername)
direkt das Handle des Fensters zurück.
Ersetzt Teile ein- oder mehrmals in einem String und gibt die Anzhal der
Treffer zurück..
Für den C++Builder 6 gibt es nunmehr die
Funktion StringReplace.
| Parameter: | |
| String | der zu durchsuchende String |
| String | Suchmuster |
| String | Ersetzmuster |
| bool | Alles oder nur den ersten Treffer ersetzen Vorgabe = true = alles |
| Rückgabewert: | |
| int | Anzahl der Treffer |
int replace(String &text,String such, String ersetz, bool all=true)
{
int x,l,treffer=0;
String teil;
l=text.Length();
for(x=1;x<=l;x++)
{
teil=text.SubString(x,such.Length());
if(teil==such)
{
treffer++;
text.Delete(x,such.Length());
text.Insert(ersetz,x);
x+=ersetz.Length()-1;
l=text.Length();
if(all==false)
break;
}
}
return treffer;
}
Im OnKeyDown-Ereignis des Formulars- nicht von irgendeiner Komponente - ist folgender Code zu hinterlegen.
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
if(Key==13)
{
Key=0;
Perform(WM_NEXTDLGCTL,0,0);
}
}
//---------------------------------------------------------------------------
In Objektinspektor ist unter den Eigenschaften des Formulars - nicht von irgendeiner Komponente - ist folgende Eigenschaft zu setzen:
KeyPreview = true
Wer mit dem C++Builder eine IBAN prüfen und erzeugen möchte, kann diese Routinen nutzen.
Die Routine PruefeIBAN erwartet als Übergabeparameter eine IBAN und gibt true oder false zurück, wennn diese stimmt
Die Routine MakeIBAN erzeugt eine IBAN. Dabei wird der korrekte Ländercode, die Bankleitzahl und eine auf 10-Stellen erweiterte Kontonummer vorausgesetzt (Übergabeparameter).
Die Rountinen ModuloBerechnen und ChangeBuchstaben sind "interne" Funktionen
Die Routinen basieren auf Informationen aus dem Internet (Bundesbank). Es wird ausdrücklich darauf hingewiesen, dass nur das kontoführende Geldinstitut eine IBAN berechnet und ich aus diesem Grunde keine Gewähr für die korrekte Funktion übernehmen kann.
bool PruefeIBAN(String iban)
{
iban=iban.UpperCase();
iban=AnsiRightStr(iban,iban.Length()-4)+AnsiLeftStr(iban,4);
iban=ChangeBuchstaben(iban);
return ModuloBerechnen(iban)==1?true:false;
}
//---------------------------------------------------------------------------
int ModuloBerechnen(String iban)
{
String htext="";
int i=1,z;
while (i=<iban.Length())
{
do
{
htext+=iban[i++];
z=StrToInt(htext)%97;
}
while(z==StrToInt(htext)&&i<ban.Length());
htext=IntToStr(z);
}
return StrToInt(htext);
}
//---------------------------------------------------------------------------
String ChangeBuchstaben(String value)
{
int x,z;
for(x=1;x=<value.Length();x++)
{
z=StrToIntDef(value[x],-1);
if(z==-1)
{
z=(char)value[x]-55;
value.Delete(x,1);
value.Insert(IntToStr(z),x);
x++;
}
}
return value;
}
//---------------------------------------------------------------------------
String MakeIBAN(String land, String blz, String kto)
{
String rt,iban=land+"00"+blz+kto;
iban.UpperCase();
iban=AnsiRightStr(iban,iban.Length()-4)+AnsiLeftStr(iban,4);
iban=ChangeBuchstaben(iban);
rt=IntToStr(98-ModuloBerechnen(iban));
if(rt.Length()2<)
rt="0"+rt;
return land+rt+blz+kto;
}
//---------------------------------------------------------------------------
Das bedeutet, sämtliche Funktionen u.a. werden in einen
ausführbare EXE eingebaut. Es sind dann keine weiteren Packages mehr mit
auszuliefern. Das betrifft aber nicht Klassen und Funktionen die aus selbst
geschriebenen DLL's genutzt werden. Es ist empfehlenswert, die *.EXE Datei mit
dem Programm TDUMP zu
prüfen. Dieses gibt u.a. alle benötigten DLL's aus.
Unter
Projekt -> Optionen -> Packages und Linker die in den Grafiken
angezeigten Häckchen entfernen:



Danach ist das gesamte Projekt mit "Projekt -> Alle Projekte erzeugen" neu zu compilieren und zu linken.
Ein BMP-Bild kann man leicht als Desktophintergrund verankern, in dem man nur die entsprechenden Einträge in der Registry setzt. Das geht mit einem JPG-Bild leider nicht. Dazu sind Eingriffe in den Active Desktop notwendig. Die folgende Routine zeigt ein BMP oder JPG Bild als Desktophintergrund an. Sie benötigt als Parameter den absoluten Pfad zum Bild und die Art der Anzeige (zentriert, nebeneinander, gestreckt):
Die Funktion benötigt folgende Header-Dateien:
#include <Registry.hpp> #include <ComObj.hpp>
/**%FUNCTION************************************************************
**
** FUNKTIONSNAME:
%N SetWallpaper
**
** PARAMETER:
%I String file,int anzeige
** RÜCKGABEWERT:
%R bool
**
** BESCHREIBUNG:
%S Zeigt ein neues Hintergrundbild (BMP oder JPG) an.
%S String file ist der komplette Pfad zum Bild (Bsp. C:\\test\\pic01.jpg
%S anzeige ist regelt die Art der Anzeige:
%S 0=zentriert
%S 1=nebeneinander
%S 2=gestreckt
%S der Rückgabewert zeigt an, ob die Funktion erfolgreich war.
**
***********************************************************************/
bool SetWallpaper(String file,int anzeige)
{
if(file=="")
return false;
bool rt=true;
TRegistry& regkey=*new TRegistry();
bool keygood=regkey.OpenKey("Control Panel\\Desktop",true);
regkey.LazyWrite=false;
if(keygood)
{
switch(anzeige)
{
case 0: // zentriert
regkey.WriteString("WallpaperStyle","0");
regkey.WriteString("TileWallpaper","0");
break;
case 1: // nebeneinander
regkey.WriteString("WallpaperStyle","0");
regkey.WriteString("TileWallpaper","1");
break;
default: // gestreckt
regkey.WriteString("WallpaperStyle","2");
regkey.WriteString("TileWallpaper","0");
break;
}
if(ExtractFileExt(file).LowerCase()==".bmp")
{
regkey.WriteString("Wallpaper",file);
rt=SystemParametersInfo(SPI_SETDESKWALLPAPER,0,file.c_str(),SPIF_SENDCHANGE);
}
else
{
IActiveDesktop *desktop;
WideString wFile=file;
HRESULT ap=CoCreateInstance(Shlobj::CLSID_ActiveDesktop,NULL,CLSCTX_INPROC_SERVER,IID_IActiveDesktop,(void**)&desktop);
if(ap!=S_OK)
rt=false;
else
{
desktop->SetWallpaper(wFile,0);
desktop->ApplyChanges(AD_APPLY_ALL|AD_APPLY_FORCE);
}
delete desktop;
}
}
else
rt=false;
regkey.CloseKey();
delete ®key;
return rt;
}
//---------------------------------------------------------------------------
Wer Fehler im Stil von
[C++ Error] shobjidl.h(2193): E2238 Multiple declaration for 'FOLDERSETTINGS' [C++ Error] shobjidl.h(8095): E2238 Multiple declaration for 'DESKBANDINFO' [C++ Error] shlobj.h(1422): E2238 Multiple declaration for 'FVSHOWINFO' [C++ Error] shlobj.h(3457): E2238 Multiple declaration for 'SHELLFLAGSTATE'
bekommt, sollte unter Projekt->Optionen->Verzeichisse/Bedingungen dort unter Bedingungen NO_WIN32_LEAN_AND_MEAN eintragen.
Das folgende Beispiel zeigt, wie man Werte in die Registry schreibt.Dabei wird standardmäßig der Key unter HKEY_CURRENT_USER angelegt. Wird ein anderes Verzeichnis gewünscht, so ist die Eigenschaft RootKey zu setzen.
#include <vcl/Registry.hpp> .... //Eine neues TRegistry Object angelegen TRegistry& regkey=*new TRegistry(); //Wenn abweichend von HKEY_CURRENT_USER den RootKey setzen (das ist ein INT-Wert!!): regkey.RootKey=HKEY_LOCAL_MACHINE; //Den gewünschten Schlüssel vorgeben true = //wenn der Schlüssel nicht existiert wird er angelegt bool keygood=regkey.OpenKey("Software\\Mein Programm",true); //Werte sofort in die Registry schreiben; nicht erst beim Close regkey.LazyWrite=false; //Wenn ein gültiger Schlüssel da ist if(keygood) { //Schreiben eines boolschen Wertes BOOLTEST ist //der Name des Wertes, bo der Inhalt regkey.WriteBool("BOOLTEST",bo); //Schreiben eines Strings STRINGTEST ist der //Name "Hallo" ist der Wert regkey.WriteString("STRINGTEST","Hallo"); } //Registry schliessen regkey.CloseKey(); //Objekt löschen delete ®key;Die Funktion WriteInteger u.a. arbeiten analog.
Das folgende Beispiel zeigt, wie man Werte aus der Registry liest:
//Eine neues TRegistry Object angelegen
TRegistry& regkey=*new TRegistry();
//Wenn abweichend von HKEY_CURRENT_USER den RootKey setzen (das ist ein INT-Wert!!):
regkey.RootKey=HKEY_LOCAL_MACHINE;
//Den gewünschten Schlüssel vorgeben
//hier jetzt false (nur lesen wenn auch da)
bool keygood=regkey.OpenKey("Software\\Mein Programm",false);
//Wenn ein gültiger Schlüssel da ist
if(keygood)
{
//Wenn unser Wert da ist
if(regkey.ValueExists("BOOLTEST"))
{
//wird er in die Variable bo (bool) gelesen
bo=regkey.ReadBool("BOOLTEST");
}
//Wenn unser Wert da ist erhält die
//Variable st den Inhalt ansonsten "kein Wert"
regkey.ValueExists("STRINGTEST")?st=regkey.
ReadString("STRINGTEST"):st="kein Wert";
}
//Registry schliessen
regkey.CloseKey();
//Objekt löschen
delete ®key;
Nachdem einige COBOL-Programmieren doch gesagt haben:" C/C++ kommt nicht in Frage, dort kann man keine Bits setzen" hier eine kleine Klasse die das realisiert.
Dabei arbeitet die Klasse so, dass zuerst ein Wert, der zu verändern ist mit "SetValue(long lValue)" gesetzt wird. Dann kann diesermit den Methoden "SetBit, ClearBit, ChangeBit und IsSet" bearbeitet werden. Wer es einfacher oder keine Klasse will braucht nur den Teil zum löschen oder setzen zu nutzen. Ggf. könnte die Klasse noch derart erweitert werden, dass man gleich beim setzen oder ändern der zu bearbeitenden Wert übergibt.
class GBits
{
private:
unsigned long value;
public:
GBits();
GBits(unsigned long lValue);
GBits(String lValue);
void SetValue(unsigned long lValue);
void SetValue(String lValue);
unsigned long SetBit(int b);
unsigned long ClearBit(int b);
bool IsSet(int b);
unsigned long ChangeBit(int b);
String GetString();
unsigned long GetValue();
};
GBits::GBits()
{
value=0;
}
GBits::GBits(unsigned long lValue)
{
value=lValue;
}
GBits::GBits(String lValue)
{
value=strtoul(lValue.c_str(),NULL,2);
}
void GBits::SetValue(unsigned long lValue)
{
value=lValue;
}
void GBits::SetValue(String lValue)
{
value=strtol(lValue.c_str(),NULL,2);
}
unsigned long GBits::SetBit(int b)
{
long t=1;
t=t<<b;
return value|=t;
}
unsigned long GBits::ClearBit(int b)
{
long t=1;
t=t<<b;
return value&=~t;
}
bool GBits::IsSet(int b)
{
long t=1;
t=t<<b;
if(value&t)
return true;
else
return false;
}
unsigned long GBits::ChangeBit(int b)
{
long t=1;
t=t<<b;
return value^=t;
}
String GBits::GetString()
{
String rt;
unsigned long be=value;
int l;
while(be>0)
{
if(be%2==0)
rt+="0";
else
rt+="1";
be/=2;
}
rt=strrev(rt.c_str());
l=rt.Length();
if(l>16&&l<32)
{
while(rt.Length()<32)
rt="0"+rt;
}
else if(l>8&&l<16)
{
while(rt.Length()<16)
rt="0"+rt;
}
else if(l<8)
{
while(rt.Length()<8)
rt="0"+rt;
}
return rt;
}
unsigned long GBits::GetValue()
{
return value;
}
Hier mal eine kleine Zusammenfassung, was ich bei der Migration von ca. 30 Projekten für Probleme hatte. Nur ein geringer Teil der Projekt lies sich ohne Probleme umstellen. Dabei macht der Compiler keinen Ärger. Nur der Linker machte ordentlich zicken (s.u.). Der Compiler machte nur bei geänderten Indyklassen eine berechtigte Anmerkung. Im Vergleich zu der beim BCB 6 mitgelieferten Indyklassen (ich hatte nie ein Update gemacht) sind einige Änderungen in der Indy 9er Version erfolgt. Mit der Hilfe konnten diese schnell erledigt werden. Problemlos konnten alle eigenen Komponeten in das neue System integriert werden. Beim umstellen machten DLLs am meisten ärger. Fast keine konnte ohne Probleme migriert werden. Hier erscheint doch der Linker und/oder die Konvertierungsroutinen verbesserungsbedürftig. Es wurden immer BCB 6 nach BCB 2006 migriert. Keine anderen. Ziel war es auch jedesmal eine Standalone (statisch gelinkte) Anwendung zu erhalten. Diese Ziele standen nicht immer in Übereinstimmung mit dem Linker:
[Linker Fataler Fehler] Fatal: Zugriffsverletzung.
Link beendet.
Keine Ahnung was er da wollte -> Neues Projekt
angelegt. Habe auch den Hinweise bekommen, dass dies passiert (auch passiert?),
wenn die Option "keine Optimierung" aktiv ist und mehr als 20 CPP Dateien
vorliegen
[Linker Warnung] Warning: Image wurde als
ausführbare Datei gelinkt, aber mit einer .DLL- oder
.BPL-Erweiterung
Keine Ahnung warum. Der Eintrag in der *.bdsproj
lautete auf CppDynamicLibrary -> Neues Projekt angelegt
[Linker Fehler] Error: Ungelöste externe
'Sysutils::EDivByZero::' referenziert von E:\BUILDER2006\LIB\CP32MT.LIB|xx
Es folgten weitere 25 Fehler, die auf Standard VCL Klassen
hinwiesen. Die vcl.h. war eingebunden. Seltsamerweise tratt dieser Fehler nur
bei einigen DLLs auf nicht bei allen, obwohl alle die VCL nutzen. Hier half nur
die rti.lib und vcl.lib dem Projekt hinzuzufügen
[Linker Fataler Fehler] Fatal: Datei 'STLPMT.LIB'
kann nicht geöffnet werden
In der *.bdsproj war kein Verweis
auf die Lib zu finden.Auch ein Grep über alle Dateien in dem Projektordner
brachte nicht zutage, warum er unbeginnt diese Lib wollte. Die STL wurde NICHT
benutzt. Manchmnal half, ein neues Projekt anzulegen, jedoch nicht immer. Diese
Lib gibt es wohl im BCB 2006 nicht mehr. Habe dem Linker dann in 2
hartnäckigen Fällen dann die Lib aus dem BCB 6 vorgeworfen. Dann war
Ruhe.
[Linker Fehler] Error: Datei 'TRAYICON.RES' kann
nicht geöffnet werden
Im ganzen Projektordner ist kein Verweis
auf diese RES nicht zu finden. Auch unklar, warum er diese wollte.
Selbstverständlich nutzte das Projekt keine Daten aus der RES, keine Tray
Komponente o.a.. Hier musste ich dem Linker den Verweis auf die RES mitgeben.
Diese liegt unter ....\Demos\CPP\Controls
Rundet Prozessorunabhängig.
Hier ist die math.h mit #include >math.h einzubinden!
| Parameter: | |
| double | Zahl die zu runden ist |
| int | Anzahl der zu rundenden Stellen |
| Rückgabewert: | |
| double | die gerundete Zahl |
include <math.h>
...
double round(double zahl,int stellen)
{
double order=pow(10.0,stellen);
return (int)(zahl*order+(zahl>0?0.5:-0.5))/order;
};
Zunächst benötigt man einen Resource-Editor (man kann Cursors auch von Dateien laden, aber das ist umständlicher. Wer Geld ausgeben will, kann sich den Resource Builder holen. Nettes Tool, aber teuer.
Es gibt auch die kostenlose Alternative XN Resource Editor. Die weiteren Ausführungen beziehen sich auf diesen Editor.
Also mit diesem über RESOURCE -> ADD RESOURCE -> CURSOR GROUP eine Cursor erstellen

Danach die Cursor-Resource anwählen und ein schickes Bild malen

Die rot umrandete Ziffer für merken oder wenn einfacher -> aufschreiben
Wenn der Cursor fertig ist, alles über FILE -> SAVE AS als *.res Datei speichern. Dabei unbedingt einen anderen Namen wählen als das Programmprojekt. Es wird dann eine compilierte Rsourcendatei abgespeichert. Im C++Builder diese nun über PROJEKT -> DEM PROJEKT HINZUFÜGEN mit in das Projekt aufnehmen.
Nun haben wir eine Resourcedatei mit einem Cursor in unserem Projekt.
Wir können den Cursor nunmehr beispielsweise im OnCreate Ereignis laden. Durch die Zuweisung an Screen->Cursor gilt er für das ganze Fomular. Das kann man ggf. auch nur für einzelne Komponenten machen.
void __fastcall TForm1::FormCreate(TObject *Sender)
{
TCursor meiner=6;
Screen->Cursors[meiner]=LoadCursor(HInstance, MAKEINTRESOURCE(1));
Screen->Cursor = TCursor(meiner);
}
//---------------------------------------------------------------------------
Die oben gemerkte Ziffer aus der Resourcendatei wird als Macroparamter in das MAKEINTRESOURCE gesetzt. Das Array mit Curorbildern hat ja schon Inhalte (Screen->CursorCount). Also für "meiner" einen Wert nehmen, der keine vorhanden überschreibt.
Mit folgendem Codestück lassen sich die Prozesse und/oder die laufenden Threads auslesen:
#include <tlhelp32.h>
.........
PROCESSENTRY32 pe;
DWORD Pid=-1;
String procname;
value="notepad.exe"; //Als Beispiel das Programm Notepad
//Einen Schnappschuss des Liste holen -> mit TH32CS_SNAPTHREAD können die Threads geholt werden
HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if ((int)hSnapshot==-1)
return false;
pe.dwSize=sizeof(PROCESSENTRY32);
bool retval=Process32First(hSnapshot,&pe);
while(retval)
{
procname=pe.szExeFile; // in der PROCESSENTRY32-Struktur stehen dann die Infos....
procname=procname.LowerCase();
if(procname==value||procname.Pos(value)>0)
{
Pid=pe.th32ProcessID; //ProzessID holen
break;
}
pe.dwSize=sizeof(PROCESSENTRY32);
retval=Process32Next(hSnapshot,&pe);
}
CloseHandle(hSnapshot);
if(Pid==-1)
//Fehler Prozess nicht gefunden
//Prozess öffnen
HANDLE hProc=OpenProcess(PROCESS_TERMINATE|SYNCHRONIZE,false,Pid);
if(hProc==NULL)
//Fehler
Prozess abschiessen
TerminateProcess(hProc,0);
//Warte 5 Sek. ob es funktioniert hat
DWORD ws=WaitForSingleObject(hProc,5000);
if(ws==WAIT_TIMEOUT)
//Prozess konnte innerhalb von 5 Sek. nicht beendet werden
//ok
Für das Versenden empfiehlt es sich, die INDY-Komponenten zu verwenden. In den "hauseigenen" Komponenten habe ich kein Hinweis gefunden, wie die Authentifizierung am Mailserver erfolgt. Dies ist nun mittlerweile bei allen seriösen Servern Bedingung. Hier also ein kurzen Beispiel für den Mailversand.
In das Formular ist eine TIdSMTP1 (zu finden unter INDY-Clients) und eine TIdMessage (zu finden unter INDY-Misc) aufzunehmen.
Zunächt wird der Mailserver angegeben (hier GMX):
IdSMTP1->Host="mail.gmx.net";
Dann müssen für die Authentifizierung Username und Passwort vorgegeben werden :
IdSMTP1->UserId="********"; IdSMTP1->Password="*******";
IdSMTP1->Username="********"; // Indy 9 BCB 2006 vorher IdSMTP1->UserId IdSMTP1->Password="*******";
Vorgabe des Portes des Mailservers:
IdSMTP1->Port=25;
Nun wird die Message erzeugt und mit Parametern versorgt. Absender der Mail (die meisten Provider nehmen die Mail nur an, wenn tatsächlich die registriete Mailadresse als Absender drin steht):
IdMessage1->From->Text="eigene Adresse@gmx.net"; IdMessage1->Sender->Text="Eigener Name";
Vorgrabe der Empfänger:
IdMessage1->Recipients->EMailAddresses="zieladresse@asdadasdasda.de";
Vorgabe der Betreffzeile:
IdMessage1->Subject="Test";
Für den Mailtext ist eine TStringlist o.a. (Memofeld, Richtextfeld, Listbox) erforderlich:
TStringList *body=new TStringList();
body->Add("Hallo");
IdMessage1->Body=body;
Wenn ein Attachment angehängt werden soll:
IdMessage1->MessageParts->Add(); TIdAttachment *Att=new TIdAttachment(IdMessage1->MessageParts,"g:\\Dokument1.txt");
Für weitere Anhänge, die Zeilen mit neuem Zeiger wiederholen.
Nun können wir senden:
try
{
IdSMTP1->Connect(5000); // nach 5 Sek. Timeout
IdSMTP1->Send(IdMessage1);
IdSMTP1->Disconnect();
}
catch(...)
{
Application->MessageBox("Fehler beim Versenden der Nachricht","Mail",MB_OK|MB_ICONSTOP|MB_SYSTEMMODAL);
}
Aufräumen:
delete body; delete Att;
Siehe auch: Abholen von Mails mit den Indy-Komponenten (IdPOP3)
Beschreibung zu weiteren Indy-Komponeten:
Man kann dazu die Variablen/ / Strukturen als Member in die Klasse aufnehmen ODER externe Variablen/ / Strukturen nutzen. Im fogenden werden beide Methoden beschrieben:
Die Variablen und Strukturen in die Klasse aufnehmen
Genau wie Buttons und andere Elemente der Klasse kann man natürlich seine eigenen Variablen und Strukturen in die Klasse des Formulars aufnehmen. Hier wieder ein Beispiel um den int Summe sowie eine Struktur Names punkt in die Klasse des Formulars aufzunehmen. Dazu wird die Header-Datei des Formulars geöffnet:
class TForm1 : public TForm
{
__published: // Von der IDE verwaltete Komponenten
private: // Anwender-Deklarationen
int summe;
struct
{
int x;
int y;
}punkt;
__fastcall TForm1(TComponent* Owner);
public: // Anwender-Deklarationen
};
Um nun diese Variablen in anderen Formularen zu nutzen, muss die
Header-Datei dieser Unit den anderen Formularen bekannt gemacht werden. Davon
ausgehend, dass ein weiteres Formular mit dem Namen Unit2 vorliegt, wir diese
in den Vordergrund gebracht und unter 
Die Unit, die nun die gewünschten Variablen enthält, wird mit die die Uint2 eingebunden. In diesem Fall ist es Unit1.
In der Unit2 ist jetzt also alles bekannt, was in der Unit1 ist. Das ist im allgemeinen die Klasse Unit1 mit all ihren Daten. Nun kann man in der Unit2 so auf die Daten zugreifen:
Unit1->summe=300; Unit1->punkt.x=10; Unit1->punkt.y=20;
Externe Variablen nutzen
In der Hilfe kann man das unter dem Stichwort Gültigkeitsbereich nachlesen. Die Variablen/Strukturen müssen außerhalb jeder Funktion im Kopf der *.cpp Datei angelegt werden. Wird keine sofortige Inittialisierung vorgenommen kann die Variable/Struktur auch in der Header-Datei angelegt werden. es wird im folgenden davon ausgegangen, dass es ein Hauptformular gibt in dem die Variablen/Strukturen angelegt werden und ein Nebenformular in dem diese Variablen/Strukturen ebenfalls gelten sollen
Hauptformular:
Es wird eine Variable summe mit dem Wert 500 sowie
eine Struktur Names punkt angelegt. In diesem Hauptformular kann wie gewohnt
mit diesen Variablen/Strukturen gearbeitet werden
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
int summe=500;
struct
{
int x;
int y;
}punkt;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
Nebenformular:
Die Anlage der Variablen/Strukturen erfolgt
zusätzlich mit dem Schlüsselwort extern. So weiß der
Compiler, wie die Variablen/Strukturen heissen, dass aber der Speicherplatz der
Variablen/Strukturen woanders liegt.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form2;
extern int summe;
extern struct
{
int x;
int y;
}punkt;
//---------------------------------------------------------------------------
__fastcall TForm2::TForm2(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
Eine HTML-Seite kann mit der Indy-Komponente IdHTTP geholte werden. Dazu ist die Komponente aus dem Bereich Indy-Clients auszuwählen und in das Formular zu ziehen. Eine HTTP-Seite wird dann wie folgt geholt und in einen String gespeichert:
HTTP->HandleRedirects=true; // die IdHTTP Komponente
String Seite=HTTP->Get("http://www.xxxxxxx.de");
In dem String Seite ist dann der Quelltext der angeforderten HTML-Seite. Unbedingt erforderlich ist die Angabe des Protokolls (http://). Ein www.xxxxx.de alleine reicht nicht
HandleRedirects gibt an, wie mit HTTP-Meldungen 3xx umgegangen wird. Das setzen auf true besagt, dass der Seite gefolgt wird. Dabei ist ein Standard wert von 15 vorgegeben. Wird mehr benötigt, so ist RedirectMax zu erhöhen.
Die Komponente bietet noch einige Features mehr wie Proxie und Authentifizierung.
Beschreibung zu weiteren Indy-Komponeten:
Im Example Verzeichnis des C++Builders igbt es ein Demo das unter
folgendem Pfad liegt:
\CBuilder4\Examples\Controls\Tray
Zunächst wird das Package TRAYPKG.BPK im C++Builder geöffnet. Dann wird es compiliert und installiert. Im Register System sollte es dann eine neue Komponente geben, die TRAYCOMP heißt.
Meines Erachtens ist ein Fehler in diesem Programm. Wird die Animation eingeschaltet, wird immer noch ein leeres Icon anm Schluß angezeigt. Um den Fehler zu beheben, ist im C++Builder das Package traypkg,bpk zu öffnen. Die Datei TrayComp.cpp ist in den Editor zu laden. Dort gibt es folgende Klassenfunktion:
void __fastcall TTrayComp::OnAnimation(TObject* Sender)
{
if (IconIndex < FIconList->Count)
FIconIndex++;
else
FIconIndex = 0;
SetIconIndex(FIconIndex);
Update();
}
Diese ist wie folgt zu ändern:
void __fastcall TTrayComp::OnAnimation(TObject* Sender)
{
if (IconIndex < FIconList->Count-1)
FIconIndex++;
else
FIconIndex = 0;
SetIconIndex(FIconIndex);
Update();
}
Dann das ganze compilieren und neu installieren.
Nun kann man das Projekt TRAYDEMO.BPR öffnen, compilieren und ausführen lassen. Dieses Projekt zeigt ein Icon im Tray.
Ist im Prinzip ganz einfach:
Das Demo zeigt noch, wie man verschiedene Icons intervallgesteuert anzeigt.
Auch hier kann man die Funktion strtol nutzen:
int zahl=strtol(eingabe.c_str(),NULL,2);
Mit einer Zeile geht es wohl nicht, die Variabel eingabe enthält den Dezimal-Wert, der String bin enthält dann die binäre Darstellung:
String bin;
long eingabe=25;
...
while(eingabe>0)
{
if(eingabe%2==0)
bin+="0";
else
bin+="1";
eingabe/=2;
}
bin=strrev(bin.c_str());
Der folgender Programmausschnitt zeigt das dynamische Anlegen eines Buttons:
TButton *my=new TButton(this); my->Top=300; my->Left=300; my->Caption="Test"; my->Parent=this;
Es ist in der Hilfe zu der entsprechenden Komponente (hier TButton) nachzuschauen, ob und ggf. welche Parameter bei dem Anlegen einer neuen Klasse zu übergeben sind. Weitere Eigenschaften können hinzugefügt werden. Wichtig ist die Eingeschaft Parent. Sie gibt das Elternobjekt des Button an, also das Objekt auf dem der Button erscheinen soll. Das kann direkt das Formular sein, oder bsp. eine Groupbox oder ein Panel. Soll ein Ereignis bearbeitet werden, ist das Ereignis wie folgt zu setzen:
my->OnClick=MyButtonClick;
Dazu muß dann manuell in der Header-Datei im __published Abschnitt die Ereignisrountine eingefügt werden:
__published: // Von der IDE verwaltete Komponenten void __fastcall MyButtonClick(TObject *Sender); //Manuell eingefügt
In der CPP-Datei muß nun noch die Ereignissroutine programmiert werden:
void __fastcall TForm1::MyButtonClick(TObject *Sender)
{
ShowMessage("Button gedrückt");
}
Das Beispiel ziegt das OnClick-Ereignis. Andere Ereignisse haben andere Parameter. Diese sind der Hilfe zu entnehmen. Wird die Komponente nicht nehr benötigt, so muss sie gelöscht werden (bsp. im Destruktor des Formulars):
if(my) delete my;
Auch für das Empfangen empfiehlt es sich, die INDY-Komponenten zu verwenden. In den "hauseigenen" Komponenten habe ich kein Hinweis gefunden, wie die Authentifizierung am POP3-Server erfolgt. Hier also ein kurzen Beispiel für den Mailempfang
In das Formular ist eine TIdPOP3 (zu finden unter INDY-Clients) und eine TIdMessage (zu finden unter INDY-Misc) aufzunehmen.
Zunächt wird der Mailserver angegeben (hier GMX):
POP->Host="pop.gmx.net"; // die TidPOP3 Komponente
Dann müssen für die Authentifizierung Username und Passwort vorgegeben werden :
POP->Username="********"; // Indy 9 BCB 2006 vorher TIdPOP3->UserId POP->Password="*******";
Vorgabe des Portes des POP3-Servers (ist auch schon so Standard):
POP->Port=110;
Nun können wir eine Verbindung herstellen:
POP->Connect();
Nun schauen wir erstmal nach ob überhaupt Mails da sind:
int AnzahlMails=POP->CheckMessages();
Im Fehlerfall wird 0 zurückgegeben. Wenn Mails da sind, können wir diese nun abholen. Dazu wird die TidMessage-Komponente benötigt, sowie ein boolscher Wert, der uns mitteilt, ob die Abholung erfolgreich war:
In den einzelnen Teilen einer Message wird nun nachgeschaut, was enthalten ist. Ist es Text wird er wie hier in einem MemoFeld (Memo1) angezeigt. Ist es ein Anhang, wird dieser gespeichert. Dann wird die Mail von Server gelöscht.
bool ergebnis;
for(int x=1;x<=AnzahlMails;x++)
{
IdMessage->Clear();
ergebnis=POP->Retrieve(x,IdMessage);
if(ergebnis)
{
if(IdMessage->MessageParts->Count>0)
{
for(int y=0;y<IdMessage->MessageParts->Count;y++)
{
TIdText *msgText= dynamic_cast<TIdText*>(IdMessage->MessageParts->Items[y]);
TIdAttachment *att= dynamic_cast<TIdAttachment*>(IdMessage->MessageParts->Items[y]);
if(msgText&&msgText->ClassNameIs("TIdText"))
{
Memo1->Lines=msgText->Body;
}
else if(att&&att->ClassNameIs("TIdAttachment"))
{
att->SaveToFile("g:\\"+att->FileName);
}
}
else
Memo1->Text=IdMessage->Body->Text;
}
POP->Delete(x); //Mail vom Server löschen
}
Dies ist wirklich nur ein Demo. Das Objekt IdMessage hat natürlich noch das Subject-Feld, die ganzen Adress- und Datumsfelder die ausgewertet werden können und ggf. müssen. Jedoch ist damit erstmal ein Grundstock gelegt. Und zu guter Letzt nicht vergessen die Verbindung wieder zu schließen. Mithilfe dieses Quickies ist es dann wohl kein Problem mehr, einen eMailchecker für den Systray zu programmieren.
POP->Disconnect();
Siehe auch: Versenden einer eMail mit den Indy-Komponenten (IdSMTP)
Beschreibung zu weiteren Indy-Komponeten:
Mit dem folgenden Codestück können ale im System installierten ODBC-Treiber ausgelesen werden. Der einzelne Treiber wird in dem String drv übergeben. Außer den includes ist die odbc32.lib unter {bcb}\lib\psdk "dem Projekt hinzuzufügen".
#include "odbcinst.h"
#include "sql.h"
#include "sqlext.h"
......
HENV hEnv;
UCHAR szDriverDesc[300];
SWORD pcbDriverDesc;
UCHAR szDriverAttributes[300];
SWORD pcbDrvrAttr;
RETCODE retcode=0;
String drv;
if (SQLAllocEnv(&hEnv)==SQL_SUCCESS)
{
while (retcode=SQLDrivers(hEnv, SQL_FETCH_NEXT,(UCHAR FAR *)&szDriverDesc, 300,(SWORD FAR *)&pcbDriverDesc,(UCHAR FAR *)&szDriverAttributes,300,(SWORD FAR *)&pcbDrvrAttr) != SQL_NO_DATA_FOUND&&retcode!=SQL_ERROR)
{
drv=(char*)szDriverDesc;
}
SQLFreeEnv(hEnv);
}
Weitere Informationen zur ODBC-API gibt es unter http://odbcrouter.com/api/
Die GDI+ Bibliothek von Windows hat verbesserte grafische Funktionen. U.a. gehören dazu, dass Laden und Speichern von Bilder in unterschiedlichsten Foirmaten, das verbesserte rendern der Grafiken bei Größenämderungen u.v.m.
Hier nun eine Anleitung zum einbinden dieser Biblothek:
Zunächst wird die LIB-Datei dem Projekt hinzugefügt

Folgende Variablen werden in die PRIVATE Section des Formulars aufgenommen:

Der Namespace wird angegeben:

Der Konstruktor und Destruktor des Formulars werden wie folgt aufgebaut:

Zu den Bedingungen für den Compiler wird folgendes hinzugefügt:

Nunmehr kann die Bibliothek genutzt werden. Hier ein Beispiel für das Laden eines Bildes, wobei es egal ist, welches Format das Bild hat:
String dateiname="bild.jpg"; Image image(WideString(dateiname)); //Bild laden Graphics::TBitmap *bitmap=new Graphics::TBitmap; //eine VCL Bitmap bitmap->Width=image.GetWidth(); bitmap->Height=image.GetHeight(); Gdiplus::Graphics graphics((HDC)bitmap->Canvas->Handle); GDI mitteilen, wo unsere Bitmap ist graphics.DrawImage(ℑ, 0, 0, image.GetWidth(), image.GetHeight()); Bild reinmalen delete bitmap;
Teilt einen String anhand eines Trennzeichens auf und gibt die Ergebnisse in einem TStringList-Objekt zurück.
| Parameter: | |
| TStringList | enthält zum Schluß die aufgeteilten Werte |
| String | String der aufzuteilen ist |
| String | Trennzeichen |
| Rückgabewert: | |
| void | |
| Beispiel: | TStringList *worte=new TStringList(); split(worte,"Das ist eine Website"," "); ..... String teileins=worte->Strings[0]; ..... delete worte; |
void split(TStringList *feld,String eingabe,String delimiter)
{
int pos,dellang;
feld->Clear();
feld->Duplicates=dupAccept;
dellang=delimiter.Length();
pos=eingabe.AnsiPos(delimiter);
while(pos>0)
{
if(pos-1>0)
feld->Add(eingabe.SubString(1,pos-1));
eingabe.Delete(1,pos+dellang-1);
pos=eingabe.AnsiPos(delimiter);
}
if(eingabe.Length()>0)
feld->Add(eingabe);
}
Wie arbeite ich mit einer TStringList
Zur Speicherung eines Registryschlüssels mittlels der Methode SaveKey müssen folgende Voraussetzungen erfüllt sein:
Das setzen der Rechte:
TOKEN_PRIVILEGES tkp;
HANDLE hToken;
if (!OpenProcessToken (GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
{
ShowMessage ("OpenProcessToken geht nicht");
return;
}
if(!LookupPrivilegeValue(NULL,SE_BACKUP_NAME,&tkp.Privileges[0].Luid))
{
ShowMessage ("LookupPrivilegeValue geht nicht");
return;
}
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(TOKEN_PRIVILEGES),NULL, NULL );
if(GetLastError()!= ERROR_SUCCESS)
{
ShowMessage ("AdjustTokenPrivileges geht nicht");
return;
}
Den Schlüssel speichern:
TRegistry * reg = new TRegistry();
DeleteFile("test.sav"); //Eine bestehende Datei löschen!!!!
if (reg->SaveKey("Software\\Analyser","test.sav"))
{
ShowMessage("Speicherung erfolgreich");
}
else
{
ShowMessage("Speicherung misslungen");
}
delete reg;
Zu beachten ist, dass SaveKey keine REG-Datei erzeugt. Vielmehr
kann die erzeugte Datei dazu genutzt werden, Schlüssel in der
Registrierung mit RestoreKey, ReplaceKey und LoadKey wieder
herzustellen.
REG-Dateien werden von dem Editor der Registry (regedit)
genutzt. Dieser verarbeitet REG-Dateien und die Dateiendung *.reg ist mit
diesem Programm verbunden. Da nun der Editor mit der Registry an sich nichts zu
tun hat, erzeugt SaveKey auch keine REG-Dateien
Zum einlesen des gespeicherten Schlüssel mittels RestoreKey müssen folgende Voraussetzungen erfüllt sein:
Das setzen der Rechte (Backup):
TOKEN_PRIVILEGES tkp;
HANDLE hToken;
if (!OpenProcessToken (GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
{
ShowMessage ("OpenProcessToken geht nicht");
return;
}
if(!LookupPrivilegeValue(NULL,SE_BACKUP_NAME,&tkp.Privileges[0].Luid))
{
ShowMessage ("LookupPrivilegeValue geht nicht");
return;
}
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(TOKEN_PRIVILEGES),NULL, NULL );
if(GetLastError()!= ERROR_SUCCESS)
{
ShowMessage ("AdjustTokenPrivileges geht nicht");
return;
}
Das setzen der Rechte (Restore):
if (!OpenProcessToken (GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
{
ShowMessage ("OpenProcessToken geht nicht");
return;
}
if(!LookupPrivilegeValue(NULL,SE_RESTORE_NAME,&tkp.Privileges[0].Luid))
{
ShowMessage ("LookupPrivilegeValue geht nicht");
return;
}
tkp.PrivilegeCount=1;
tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(TOKEN_PRIVILEGES),NULL, NULL );
if(GetLastError()!= ERROR_SUCCESS)
{
ShowMessage ("AdjustTokenPrivileges geht nicht");
return;
}
(Wer weiss, wie man das in einem Schritt macht -> bitte melden)
Eine Dateiauswahlbox anzeigen:
OpenDialog1->Title="Profil importieren"; OpenDialog1->FilterIndex=2; OpenDialog1->FileName=""; if(!OpenDialog1->Execute()) return;
Schlüssel öffnen, erzeugen und einlesen. Wobei der zu erzeugende Schlüssel der Dateiname ist. Aus diesem Grunde darf dieser auch nicht verändert werden.
TRegistry *reg = new TRegistry();
String htext=ExtractFileName(OpenDialog1->FileName);
reg->OpenKey("Software\\PocketFTP\\Profile\\",false);
reg->CreateKey(htext);
if (reg->RestoreKey(htext,OpenDialog1->FileName)==true)
SetStatus("Import erfolgreich");
else
SetStatus("Import misslungen");
delete reg;
Nicht nur in der Klasse AnsiString (kurzform String -> nicht zu verwechseln mit der STL Klasse string) gibt es Funktionen für Strings (Klassenmethoden). Nebenher existiert eine ganze Reihe von extra Funktionen, die weniger bekannt sind (BDS 2006):
| AnsiContainsStr | Gibt an, ob ein String in einem anderen String enthalten ist. Dabei wird die Groß-/Kleinschreibung berücksichtigt. |
| AnsiContainsText | Gibt an, ob ein String in einem anderen String enthalten ist. Dabei wird die Groß-/Kleinschreibung nicht berücksichtigt. |
| AnsiEndsStr | Gibt an, ob ein String das Ende eines anderen Strings bildet. Dabei wird die Groß-/Kleinschreibung berücksichtigt. |
| AnsiEndsText | Gibt an, ob ein String das Ende eines anderen Strings bildet. Dabei wird die Groß-/Kleinschreibung nicht berücksichtigt. |
| AnsiIndexStr | Gibt den Index des angegebenen Strings in einem Stringarray zurück. Dabei wird die Groß-/Kleinschreibung berücksichtigt. |
| AnsiIndexText | Gibt den Index des angegebenen Strings in einem Stringarray zurück. Dabei wird die Groß-/Kleinschreibung nicht berücksichtigt. |
| AnsiLeftStr | Gibt einen Teilstring der angegebenen Länge zurück, der am Anfang des Strings beginnt. |
| AnsiMatchStr | Gibt an, ob ein Stringarray eine exakte Übereinstimmung mit dem angegebenen String enthält. Dabei wird die Groß-/Kleinschreibung berücksichtigt. |
| AnsiMatchText | Gibt an, ob ein Stringarray den angegebenen String enthält. Dabei wird die Groß-/Kleinschreibung nicht berücksichtigt. |
| AnsiMidStr | Gibt einen Teilstring der angegebenen Länge zurück, der an einer bestimmten Position beginnt. |
| AnsiReplaceStr | Ersetzt alle Fundstellen eines Teilstrings durch einen anderen String. Dabei wird die Groß-/Kleinschreibung berücksichtigt. |
| AnsiReplaceText | Ersetzt alle Fundstellen eines Teilstrings durch einen anderen String. Die Groß-/Kleinschreibung wird dabei nicht berücksichtigt. |
| AnsiResemblesText | Gibt an, ob sich zwei Strings ähneln. |
| AnsiReverseString | Gibt die Umkehrung des angegebenen Strings zurück. |
| AnsiRightStr | Gibt einen Teilstring mit einer bestimmten Länge zurück, der sich am Ende des angegebenen Strings befindet. |
| AnsiStartsStr | Gibt an, ob ein String am Anfang eines anderen Strings steht. Dabei wird die Groß-/Kleinschreibung berücksichtigt. |
| AnsiStartsText | Gibt an, ob ein String am Anfang eines anderen Strings steht. Dabei wird die Groß-/Kleinschreibung nicht berücksichtigt. |
| DecodeSoundexInt | Konvertiert die Integer-Darstellung einer SoundEx -Codierung in den entsprechenden phonetischen String. |
| DecodeSoundexWord | Konvertiert die Word-Darstellung einer SoundEx -Codierung in den entsprechenden phonetischen String. |
| DupeString | Gibt die mehrfache Verkettung eines Strings mit sich selbst zurück. Die Anzahl der Wiederholungen kann angegeben werden. |
| IfThen | Gibt abhängig von einer Bedingung einen von zwei angegebenen Werten zurück. |
| LeftBStr | Gibt einen Teilstring mit der angegebenen Byte-Anzahl zurück, der am Anfang des Strings beginnt. |
| LeftStr | Gibt einen Teilstring der angegebenen Länge zurück, der am Anfang des Strings beginnt. |
| MidBStr | Gibt einen Teilstring mit der angegebenen Byte-Anzahl zurück, der an einer bestimmten Position beginnt. |
| MidStr | Gibt einen Teilstring der angegebenen Länge zurück, der an einer bestimmten Position beginnt. |
| PosEx | Gibt den Indexwert eines Teilstrings zurück (einschl. Offset) |
| RandomFrom | Gibt ein zufällig aus einem Array ausgewähltes Element zurück. |
| ReverseString | Gibt die Umkehrung des angegebenen Strings zurück. |
| RightBStr | Gibt einen Teilstring mit der angegebenen Byte-Anzahl zurück, der am Ende des Strings beginnt. |
| RightStr | Gibt einen Teilstring mit einer bestimmten Länge zurück, der sich am Ende des angegebenen Strings befindet. |
| SearchBuf | Durchsucht einen Textpuffer nach einem Teilstring. |
| Soundex | Konvertiert einen String in seine SoundEx-Repräsentation. |
| SoundexCompare | Vergleicht die SoundEx -Repräsentationen zweier Strings. Unit |
| SoundexInt | Konvertiert einen String in einen Integer, der den phonetischen Wert des Strings repräsentiert. |
| SoundexProc | Gibt an, ob sich zwei Strings ähneln. |
| SoundexSimilar | Gibt an, ob sich zwei Strings ähneln. |
| SoundexWord | Konvertiert einen String in die Word-Repräsentation seines phonetischen Wertes. |
| StuffString | Fügt einen Teilstring an der angegebenen Position in einen String ein. Die vorhandenen Zeichen werden dabei überschrieben. |
Weitere Informationen (Parameter und Returnwerte) gibt die Hilfe. Auf einige ausgewählte Funktionen möchte ich hinweisen:
Folgende Tastencodes werden immer wieder im Zusamenhang mit der Programmierung benötigt. Sie sind als Konstanten in Windows hinterlegt.
| Name der Konstanten | Hex-Wert | Taste |
| VK_LBUTTON | 01 | Linke Maustaste |
| VK_RBUTTON | 02 | Rechte Maustaste |
| VK_CANCEL | 03 | STRG + Unterbrechen |
| VK_MBUTTON | 04 | Mittlere Maustaste |
| VK_BACK | 08 | Backspace |
| VK_TAB | 09 | Tabulator |
| VK_CLEAR | 0C | Entfernen |
| VK_RETURN | 0D | Return |
| VK_SHIFT | 10 | Shift |
| VK_CONTROL | 11 | STRG |
| VK_MENU | 12 | ALT |
| VK_PAUSE | 13 | Pause |
| VK_CAPTIAL | 14 | CapsLock |
| VK_ESCAPE | 1B | Escape |
| VK_SPACE | 20 | Leertaste |
| VK_PRIOR | 21 | Bild ab |
| VK_NEXT | 22 | Bild auf |
| VK_END | 23 | Ende |
| VK_HOME | 24 | Pos1 |
| VK_LEFT | 25 | Pfeil links |
| VK_UP | 26 | Pfeil oben |
| VK_RIGHT | 27 | Pfeil rechts |
| VK_DOWN | 28 | Pfeil unten |
| VK_SELECT | 29 | Select |
| VK_EXECUTE | 2B | Ausführen |
| VK_SNAPSHOT | 2C | Druck |
| VK_INSERT | 2D | Einfügen |
| VK_DELETE | 2E | Entfernen |
| VK_HELP | 2F | Hilfe |
| - | 30 - 39 | 0 - 9 (Wert entspricht dem ASCII Code) |
| - | 41 - 5A | A - Z (Wert entspricht dem ASCII Code) |
| VK_NUMPAD0 | 60 | Num 0 |
| VK_NUMPAD1 | 61 | Num 1 |
| VK_NUMPAD2 | 62 | Num 2 |
| VK_NUMPAD3 | 63 | Num 3 |
| VK_NUMPAD4 | 64 | Num 4 |
| VK_NUMPAD5 | 65 | Num 5 |
| VK_NUMPAD6 | 66 | Num 6 |
| VK_NUMPAD7 | 67 | Num 7 |
| VK_NUMPAD8 | 68 | Num 8 |
| VK_NUMPAD9 | 69 | Num 9 |
| VK_MULTIPLY | 6A | Multiplikationstaste |
| VK_ADD | 6B | Additionstaste |
| VK_SEPERATOR | 6C | Seperatortaste |
| VK_SUBTRACT | 6D | Subtraktionstaste |
| VK_DEZIMAL | 6E | Dezimaltaste |
| VK_DIVIDE | 6f | Divisionstaste |
| VK_F1 - VK_F24 | 70 - 87 | F1 - F24 |
| VK_NUMLOCK | 90 | Numlock |
| VK_SCROLL | 91 | Rollen |
Hierfür kann ShowMainForm genutzt werden. ShowMainForm ist eine Eigenschaft von TApplication.
Im Konstruktor ist der Standardwert true eingestellt. Wenn Sie diesen Standardwert beibehalten, wird das Hauptfenster beim Start der Anwendung automatisch angezeigt. In der Eigenschaft MainForm ist angegeben, welches Formular das Hauptformular ist.
Um das Hauptformular beim Start der Anwendung auszublenden, setzen Sie ShowMainForm vor dem Aufruf von Application->Run in der Haupt-Projektdatei auf false und stellen sicher, daß die Eigenschaft Visible des Formulars auf false steht.
| *.cpp | C++Quelltextdatei |
| *.c | C Quelltextdatei |
| *.pas | Delphi Quelltextdatei |
| *.h | Headerdatei |
| *.bpr | Einzelne Projektdatei |
| *.bpg | Projektgruppe |
| *.dfm | Enthält das Formular |
| *.ddp | Diagrammdatei eines Formulars |
| *.obj | compilierte Quelltextdatei* |
| *.rc | Resource Quelltextdatei |
| *.res | Compilierte Resourcedatei |
| *.dsk | gespeicherte Desktopeinstellungen |
| *.bpk | Projektoptionsquelldatei eines Package (analog *.bpr) |
| *.bpl | Compiliertes Package |
| *.bpi | Package Importbibliothek |
| *.lib | Importbibliothek |
| *.csm | Vorcompilierte Headerdatei* |
| *.tds | Externe Debug-Symboltabelle* |
| *il? | Vom inkrementellen Linker erstellt* |
| *.~* | Die Dateien mit diesem ~ Zeichen sind Backup-Dateien |
* Durch löschen dieser Dateien wird ein komplettes Neuerzeugen des Projektes erzwungen. Dies kann bei einigen Fehlern hilfreich sein.
Die eigentliche Startdatei eines Projektes wird normalerweise nicht editiert. Über die Projektverwaltung hat diese den Name der EXE-Datei mit der Endung CPP. Diese Datei sollte ungefähr so aussehen wie der folgende Programmauszug. In diese Datei ist der als zum Einfügen gekennzeichnete Teil einzufügen.
Sofern das Programm bisher einmal gestartet wurde, wird ein Mutex erstellt. Bei nochmaligen Start ist dieser schon vorhanden und der Programmablauf kann abgebrochen werden.
#include <vcl.h>
#pragma hdrstop
USERES("MP3Player.res");
USEFORM("UnitMain.cpp", Main);
USEFORM("UnitOptionen.cpp", Optionen);
USEFORM("UnitCopy.cpp", Copyright);
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR cmdLine, int)
{
try
{
//*********** Eingfügen **********************
HANDLE hMutex=OpenMutex(MUTEX_ALL_ACCESS,0,"MeinProgramm");
if(!hMutex)
{
hMutex=CreateMutex(0,0,"MeinProgramm");
}
else
{
ShowMessage("Das Programm läuft bereits");
return 0;
}
//********** Ende Einfügen ******************
Application->Initialize();
Application->CreateForm(__classid(TMain), &Main);
Application->CreateForm(__classid(TOptionen), &Optionen);
Application->CreateForm(__classid(TCopyright), &Copyright);
Application->Run();
//*********** Eingfügen **********************
ReleaseMutex(hMutex);
//********** Ende Einfügen ******************
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
Soll das bereits laufende Programm in den Vordergrund gebracht werden, ist die Zeile ShowMessage("Das Programm läuft bereits"); mit folgendem Programmcode zu tauschen:
HWND hWnd=FindWindow(0,"Name_des_Programmfensters"); SetForegroundWindow(hWnd);
Name_des_Programmfensters ist durch den Fensternamen zu ersetzen, der dem Fenster entspricht, welches angezeigt werden soll. Ggf. muss das Fenster, wenn der Name nicht bekannt ist oder nur teilweise bekannt ist gesucht werden
Dieser Fall tritt üblicherweise ein, wenn die in der Kommandozeile übergebenen Daten an das schon laufende Programm zu übergeben sind. Dazu werden:
Zunächst die Datenübergabe per WM_COPYDATA. Dies wird auch in der obigen Startdatei des Projektes durchgeführt. Beispielsweise nachdem das Fenster in den Vordergrund gebracht wurde:
HWND hWnd=FindWindow(0,"Name_des_Programmfensters");
if(strlen(cmdLine)!=0)
{
COPYDATASTRUCT cds;
cds.dwData=NULL;
cds.cbData=strlen(cmdLine)+1;
cds.lpData=cmdLine;
SendMessage(hWnd,WM_COPYDATA,NULL,(LPARAM)&cds);
}
Dadurch werden die Daten der Kommandozeile das schon laufende Programm gesandt. Nun muss das Programm diese empfangen können. Dazu muss es auf die Nachricht WM_COPYDATA reagieren. Hierzu ist es zunächst erforderlich, dass diese Nachricht in die Botschaftsbearbeitungsroutine des FORMULARS mit aufgenommen wird. In der Headerdatei des Formulars, welches die Nachricht empfangen soll. Ist folgende Deklaration aufzunehmen:
class TMain : public TForm
{
__published: // Von der IDE verwaltete Komponenten
....
private: // Anwenderdeklarationen
void __fastcall WmCopyData(TWMCopyData& Message);
public: // Anwenderdeklarationen
__fastcall TMain(TComponent* Owner);
// Messagehandling deklarieren:
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_COPYDATA, TWMCopyData, WmCopyData)
END_MESSAGE_MAP(TForm)
};
//---------------------------------------------------------------------------
extern PACKAGE TMain *Main;
//---------------------------------------------------------------------------
#endif
Der Codeausschnitt zeigt die Headerdatei. Die sieht je nach Formular anders aus, jedoch die Struktur ist immer gleich. In den public Abschnitt ist der rot dargestellte Code aufzunehmen. Dann reagiert das Formular auf die Nachricht WM_COPYDATA. Jedoch muss nun nur noch die Routine geschrieben werden, die die Daten entgegen nimmt.Diese Routine wurde oben in dem private Abschnitt deklariert. Nun muss sie noch in der CPP-Datei programmiert werden:
void __fastcall TMain::WmCopyData(TWMCopyData& Message)
{
String slCmdLine=(char*)Message.CopyDataStruct->lpData;
//mach was mit den Daten
}
//---------------------------------------------------------------------------
Mit der folgenden Wrapperklasse lassen siche schnell un einfach Task für den Windows Taskscheduler (Geplante Tasks) erstellen
Die Klasse ist vorbereitet, um daraus eine Komponente zu machen..... wer Lust hat.....
GTask |
||
| public: | __fastcall GTask(TComponent* Owner); | Konstuktor. Da die Klasse (noch?) keine Komponente ist, wird hier NULL übergeben. |
| __fastcall ~GTask(); | Destruktor | |
| bool __fastcall AddTask(); | Fügt einen Task in die Taskliste ein. Der Rückgabewert zeigt an, ob dies erfolgreich war. Im Fehlerfall, kann mit GetError() der Fehler ermittelt werden. | |
| bool __fastcall DeleteTask(); | Löscht einen Task aus der Taskliste. Die Eigenschaft TaskName ist mit dem zu löschenden Task zu versorgen | |
| String __fastcall GetError(); | Liefert den letzen Fehler als String zurück | |
| __property | String TaskName | Der Name für den zu bearbeitenden Task zum Setzen oder Löschen |
| TStringList* Taskliste | Gibt eine Liste aller vorhandenen Tasks im Scheduler zurück | |
| String Taskpfad | Gibt den Pfad zum Ordner der Task zurück | |
| String Command | Das auszuführende Programm, welches der Task aufruft | |
| String Parameter | Parameter, die an das auszuführende Programm übergeben werden | |
| String WorkingDir | Arbeitsverzeichnis für das auszuführende Programm | |
| String Kommentar | Wahlfreie angabe eines Kommetars | |
| String User | Benutzername unter dem der Task ausgeführt werden soll (wahlfrei) | |
| String Passwort | Passwort des Benutzers (wahlfrei) | |
| bool OnlyLogIn | Gibt an, ob der Task nur bei angemeldetem Benutzer ausgeführt wird | |
| bool Active | Gibt an, ob der Task aktiv ist | |
| void SetStartDatumZeit(unsigned short Tag,unsigned short Monat,unsigned short Jahr,unsigned short Stunde,unsigned short Minute); | Setzt das Datum und die Zeit für den Startzeitpunkt | |
| void SetStartDatum(unsigned short Tag,unsigned short Monat,unsigned short Jahr); | Setzt das Datum für den Startzeitpunkt | |
| void SetStartZeit(unsigned short Stunde,unsigned short Minute); | Setzt die Zeit für den Startzeitpunkt | |
| TDateTime StartZeitpunkt | Gibt den Startzeitpunkt als TDateTime-Objekt zurück (nur lesen) | |
| EN_ZeitArt ZeitArt | Zeigt an, um welche Art Task es sich handelt:
|
|
| unsigned short Tagesintervall | Bei täglichem Task: der Tagesintervall |
|
| unsigned short Wochenintervall unsigned short Wochentag |
Bei wöchentlichem Task: der Wochenintervall und der Wochetag Für den Wochentag stehen Konstanten (siehe MSDN) zur Verfügung |
|
| long Monatstag short Monate |
Bei monatlichem Intervall mit best. Tag: der Tag des Monats Die Monate Für die Monate stehen Konstanten (siehe MSDN) zur Verfügung |
|
| unsigned short Monatswoche unsigned short Monatswochentag short Monate |
Bei monatlichem Intervall mit Wochenintervall: die Woche der Wochentag die Monate Für alle drei stehen Konstanten (siehe MSDN) zur Verfügung |
|
| unsigned short Idlezeit | Bei einem Idle Task: die Idlezeit |
|
| void SetEndDatum(unsigned short Tag,unsigned short Monat,unsigned short Jahr); | Setzt den Endzeitpunkt (wahlfrei) eines Tasks | |
| TDateTime EndZeitpunkt | Gibt den Endzeitpunkt als TDateTime-Objekt zurück (nur lesen) | |
| int WdhIntervall int WdhDauer bool WdhKillTask |
Bei einer Wiederholung des Task der Intervall die Dauer ob der Task gelöscht werden soll |
|
| bool RemoveTask | Zeigt an, ob der Task gelöscht werden soll | |
| bool KillTaskAfter int KillTaskAfterStunden int KillTaskAfterMinuten |
Zeigt an, ob der laufende Task nach den angegebenen Stunden und angegebenen Minuten gelöscht wird |
|
| bool StartTaskAfterIdle WORD StartTaskAfterIdleMinuten WORD StartTaskAfterIdleMinutenRetry |
Zeigt an,ob der Task erst nach einer Idelzeit vom den angegebenen Minuten gestartet wird oder wenn das nicht geht, wann es wieder versucht wird |
|
| bool KillTaskIfNotIdle | Zeigt an, ob der Task beendet wird, wenn der Rechner nicht im Leerlauf ist | |
| bool NoAkku | Zeigt an,ob der Task gestartet werden soll, wenn der Rechner im Batteriebetrieb läuft | |
| bool KillTaskIfAkku | Zeigt an, ob der Task gestartet werden soll, wenn der Rechner im Batteriebetrieb läuft | |
| bool StartComputer | Zeigt an, ob der Rechner reaktiviert werden soll | |
Es ist unerlässlich, bei einem komplexen Task die Zusammenhänge der einzelnen Komponenten zu kennen. Dazu ist der Windowsdialog zum Einrichten eines Tasks hilfreich und die Hilfe
Download Klasse
Download Demoprojekt
Zeichnet mit der WIN32-API einen Kreis.
| Parameter: | |
| HDC | Der Device-Context |
| int | Startposition X |
| int | Startposition Y |
| int | Radius |
| Rückgabewert: | |
| void | |
void circle(HDC hdc,int x,int y,int radius)
{
Ellipse(hdc,x-radius,y-radius,x+radius,y+radius);
}
Grundsätzlich können alle Zeichen im Dateinamen genutzt werden. Alle ASCII Zeichen bis 255 sind erlaubt. Jedoch dient der Punkt (.) zur
Abtrennung des Filenames und der Extension. Viele Viren u.a. mach sich das zunutze indem sie einen Dateinamen wie viren.jpg.pif bilden.
der Backslash
(\) dient zur Trennung der Directories. Der Dopplepunkt (:) für die Laufwerkskennzeichnung.
Folgende Zeiche dürfen somit nicht genutzt werden:
< > : " / \ |*?
Mengen sind eine praktische Sache. Sie kommen zwar innerhalb der VCL nicht so oft vor, aber manchmal doch an entscheidender Stelle. Als Beispiel für eine Menge dient hier die Eigenschaft Styles der Schriftart, die beispielsweise in einem TMemo oder TRichEdit Objekt gesetzt werden kann und die für die Darstellung des Schriftstiles zuständig ist (bold, kursiv,..)
Das Einstellen im Objektinspektor ist einfach. Programmgesteuert gibt es ein paar kleine Hürden.
Grundsätzlich gib es 3 Methoden des Zugriffes:
Die in den Fontstyles verwendete Menge heisst TFontStyles. Von dieser legen wir uns eine eigene Menge an
TFontStyles mystyle;
Nun weisen wir dieser Menge die von uns gewünschten Element zu. Das geschieht mit dem << Operator. Im folgenden wollen wir erreichen, dass die Schrift bold und italic ist:
mystyle<<fsBold<<fsItalic;
Die Menge enthält nun die von uns gewünschten Stile. Nun mussen wir diese Stile unserer TMemo oder TRichEdit Komponente zuweisen. Hier für eine TMemo1 Komponente:
TMemo1->Font->Style=mystyles;
Nun wird der Text in der gewünschten Form ausgegeben. Nun wollen wir das der Stil italic nicht mehr unseren Text ziert. Das entfernen eine Elementes aus der Menge geschiet mit dem >> Operator
mystyle>>fsItalic; TMemo1->Font->Style=mystyles;
entfernt den italic Stil und weist der Komponete die neuen Angaben zu. Manchmal kann es nützlich sein, die Menge komplett zu löschen:
mystyle.Clear();
oder zu wissen ob ein bestimmtes Element gesetzt ist:
bool test=mystyles.Contains(fsItalic); if(test) //ist enthalten
Für eine schnelle Änderung kann das Mengenfeld der Komponente mit einer temporären Menge geändert werden:
TMemo1->Font->Style=TFontStyles()<<fsBold;
Dies ist jedoch nur für das setzen von Elementen sinnvoll.
Noch kürzer ist folgende Methode:
TMemo1->Font->Style=TMemo1->Font->Style<<fsBold;
oder
TMemo1->Font->Style=TMemo1->Font->Style>>fsBold;
Aus den obigen Ausführungen geht hervor, dass leider die naheliegende Lösung
TMemo1->Font->Style<<fsBold
nicht funktioniert. Diese Code wird zwar compiliert und führt zu keinem Fehler, bewrikt aber leider auch nichts.
Die Klasse AnsiString oder kurz String, ist eine Klasse zur Aufnahme und zur Bearbeitung eines Stringes. Ein Array von Strings vom Typ AnsiString wird in der Klasse TStringList verwaltet. Sie dient also zur Bearbeitung und Aufnahme mehrerer Strings.
Die Klasse TStringlist wird auch in mehreren Kompnenten genutzt. So beispielsweise in der TMemo-Komponente als Eingenschaft Lines in der TListbox-Komponete mit der Eigenschaft Item.
Nicht nur mehrere Strings können damit schnell verwaltet werden auch das Laden und Speichern von Textdateien ist damit schnell zu realisieren
Hier nun einige Beispiele und Hinweis auf oft benötigte Methoden:
Das Anlegen einer TStringList
TStringList *datei=new TStrinList();
Laden einer Textdatei:
datei->LoadFromFile("c:\\meineDatei.txt");
Speichern der Strings in eine Datei:
datei->SaveToFile("c:\\meineDatei.txt");
Einen weiteren String an die Liste ranhängen:
datei->Add("neuer String");
Einen weiteren String an eine bestimmte Position x einfügen
datei->Insert(x,"neuer String");
Anzahl der Strings ermitteln (Anzahl der Zeilen in der Datei):
int anzahl=datei->Count
Alle Strings durchlaufen (Zugriff auf einen einzelnen String):
for(int x=0;x<datei->Count;x++) String einer=datei->Strings[x];
Die String in der Stringliste alle löschen:
datei->Clear();
Einen einzelnen String an Position x löschen:
datei->Delete(x);
einen String suchen; die Position wird zurückgegeben:
pos=datei->IndexOf("Suchbegriff");
Alle String zu einem String zusammefassen:
String gesamt=datei->Text;
Alle String zu einem String zusammefassen, aber mit Komma getrennt:
String gesamt=datei->CommaText;
Alle String zu einem String zusammefassen, aber mit dem in Delimiter angebenen Zeichenkette getrennt:
String gesamt=datei->DelimitedText;
Die Stringliste sortieren. Es dürfen dann nur mittels Add String eingefügt werden. Ein Insert schlägt fehl:
datei->Sorted=true;
Die Stringliste wieder löschen:
delete datei;
Weitere Methoden sind in der Hilfe zu finden.
Die unterschiedlichen Systeme habe unterschiedliche Zeilenumbrüche:
| System | Hexadezimal | Dezimal | Zeichen |
| Windows | 0D 0A | 13 10 | \r\n |
| Unix,Linux | 0A | 10 | \n |
| MAC | 0D | 13 | \r |
Ein Tip aus dem Bytes And More Forum:
In den Quellcode der Applikation (Projekt -> Quellcode anzeigen) ist folgender Text (rot) mit aufzunehmen:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
DWORD dwExStyle=GetWindowLong(Application->Handle,GWL_EXSTYLE);
dwExStyle |= WS_EX_TOOLWINDOW;
SetWindowLong(Application->Handle,GWL_EXSTYLE,dwExStyle);
try
{
Application->Initialize();
Application->CreateForm(__classid(TMain), &Main);
Application->CreateForm(__classid(TProjekt), &Projekt);
Application->CreateForm(__classid(TLaden), &Laden);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
catch (...)
{
try
{
throw Exception("");
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
}
return 0;
}
Dabei wird dem System erklärt, dass es sich um ein
Tool-Window handelt.
Die Windows 32 API enthält einige nette Funktionen für den Umgang mit Rechtecken, die das entwickeln doch vereinfachen können.Die Windows-API erwartet immer einen Zeifer auf eine RECT Struktur (LPRECT ). Dabei wird in der Struktur left, top, right, bottom angegeben. Damit sind die absoluten Koordinaten des Rechteckes gemeint. Das setzen eines Rechteckes mit SetRect(LPRECT rec,100,100,120,120); ergibt also ein Rechteck welches an der Position 100,100 beginnt und an der Position 120,120 endet und somit 20 Pixel groß ist.
Setzt die Werte für ein Rechteck
SetRect(LPRECT rec,left,top,right,bottom);
Setzt alle Werte eines Rechtecks auf 0
SetRectEmpty(LPRECT rec);
Kopiert ein Rechteck
CopyRect(LPRECT dest,LPRECT source);
Prüft, ob zwei Rechtecke gleich sind
EqualRect(LPRECT rec1,LPRECT rec2);
Prüft, ob ein Rechteck leer ist, also Höhe UND Breite sind 0
IsRectEmpty(LPRECT rec);
Prüft, ob ein Punkt innerhalb eines Rechtecks liegt oder nicht. pt ist eine POINT Struktur.
PtInRect(LPRECT rec,pt);
Verschiebt ein Rechteck um die angegebenen Werte, wobei negative Werte nach links bzw. nach oben verschieben
OffsetRect(LPRECT rec,x,y);
Dehnt oder staucht ein Rechteck um die angegebenen Werte, wobei negative Werte ein Stauchen bewirken
InflateRect(LPRECT rec,x,y);
Prüft, ob sich zwei Rechtecke überlappen und gibt die Schnittmenge zurück
InflateRect(LPRECT dest,LPRECT rec1, LPRECT rec2);
Gibt die größtmögliche Fläche zweier Rechtecke zurück. Führt also beide Rechtecke zusammen
InflateRect(LPRECT dest,LPRECT rec1, LPRECT rec2);
Der C++Builder kennt zusätzlich zu der WIN 32 API Struktur RECT den Typ TRect. Dieser wird für die Funktionen (bsp. Canvas->FillRect) des C++Builders benutzt. Die Initialisierung kann direkt mit
TRect re(100,100,150,150); oder TRect re(TPoint,TPoint);
erfolgen (Es gibt auch Funktionen, die das machen, sind aber m.E. durch die obigen Möglichkeiten obsolet. Das obige Beispiel hat ein Rechteck von der Position 100,100 bis zur Position 150,150 also in einer Größe von 50 Pixel erzeugt. Mit den Methoden Width und Height kann die Weite und Höhe des Rechtecks ermittelt werden. In diesem Beispiel 50. Die TRect Struktur ist mit der RECT Struktur der WIN 32 API kompatibel. Das heisst, ein Aufruf einer der WIN 32 API Funktionen kann direkt mit der TRect Struktur erfolgen.
bool test=IsRectEmpty(re);
Hier noch zwei Templates für die Prüfung, ob ein Punkt innerhalb eines Rechteckes liegt oder ob sich zwei Rechtecke überlappen.
//---------------------------------------------------------------------------
// Funktion prüft ob der übergebenen Punkt innerhalb des Rechtecks liegt
// -> ein Punkt liegt innerhalb des Rechtecks, wenn er sich auf der
// oberen bzw. linken Seite oder innerhalb aller 4 Seiten befindet
// -> ein Punkt auf der rechten oder unteren Seite liegt ausserhalb!
//---------------------------------------------------------------------------
template <typename T>
inline bool Rect<T>::Contains(const T &x, const T &y) const
{
return ((x >= Left) && (x < Right) && (y >= Top) && (y < Bottom));
}
//--------------------------------------------------------------------------
// Funktion prüft ob sich die beiden Rechtecke überschneiden
//--------------------------------------------------------------------------
template <typename T>
inline const bool Rect<T>::Intersection(const Rect<T> &rhs) const {
return ((Bottom > rhs.Top) && (Top < rhs.Bottom) &&
(Right > rhs.Left) && (Left < rhs.Right));
}
Die beiden Routinen wurden von Rico Sonntag aus dem C++Forum entwickelt.
Hierfür ist der Systemaufruf ExitWindowsEx vorgesehen. Hier können folgende Parameter vorgegeben werden:
bool ExitWindowsEx(UNIT flags, DWORD res);
Die Werte zum Parameter flags können mit der Pipe | kombiniert werden:
| EWX_FORCE | Fährt das System ohne die Messages WM_QUERYENDSESSION und WM_ENDSESSION zu versenden herunter |
| EWX_LOGOFF | Alle Prozesse werden heruntergefahren und der User ausgeloggt |
| EWX_POWEROFF | Fährt das System herunter und schaltet die Stromversorgung ab |
| EWX_REBOOT | Fährt das System herunter und bootet neu |
| EWX_SHUTDOWN | Fährt das System herunter nachdem alle Buffer geflusht wurden. |
Der Parameter res ist reserviert und wird nicht ausgewertet.
Beispiel:
DWORD res=0; ExitWindowsEx(EWX_FORCE|EWX_SHUTDOWN,res);
Unter Windows XP sind für diesen Vorgang allerdings Rechte vonnöten. Das Programm benötigt das Recht, den Rechner herunterfahren zu lassen.
Das Recht wird so programmiert:
HANDLE hToken; TOKEN_PRIVILEGES tkp; OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken); LookupPrivilegeValue(NULL,SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid); tkp.PrivilegeCount=1; tkp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken,false,&tkp,0,(PTOKEN_PRIVILEGES)NULL,0);
Ausgehend, dass
#include<vcl/printers.hpp>
.....
void druck();
{
int x;
static int lineHoehe,lineCount,lineSeite;
//Printer Setup Dialog aufrufen
if(!PrinterSetupDialog1->Execute())
return;
//Font festlegen
Printer()->Canvas->Font->Name="Arial";
Printer()->Canvas->Font->Size=10;
//Höhe einer Zeile berechnen
lineHoehe=abs(MulDiv(Printer()->Canvas->Font->Size,GetDeviceCaps(Printer()->Handle,LOGPIXELSY),72));
lineHoehe*=1.4;
lineSeite=(Printer()->PageHeight/lineHoehe)-4;
//Startzeile zum drucken festlegen
lineCount=4;
//Name für Druckjob festlegen
Printer()->Title="Memo";
//Drucken beginnen
Printer()->BeginDoc();
//In Position x-Achse 300 eine Überschrift
Printer()->Canvas->TextOut(300,2*lineHoehe,"Memo");
//Alle Zeilen von Memo auf x-Achse 200 ausgeben
for(x=0;x<Memo->Lines->Count;x++)
{
Printer()->Canvas->TextOut(200,lineCount*lineHoehe,Memo->Lines->Strings[x]);
lineCount++;
// ggf. neue Seite
if(lineCount==lineSeite)
{
lineCount=4;
Printer()->NewPage();
}
}
//Drucken Ende
Printer()->EndDoc();
}
Die folgenden Funktionen habe ich erst sehr spät entdeckt. Leider werden sie auch mit keinem Wort in der Klasse TDateTime erwähnt, noch sind sie Bestandteile der Klasse. Letzteres empfinde ich als ausgesprochen ärgerlich. Schließlich will man ja mal mit einem Datum rechnen. Zwar hat die Klasse diverse Operatoren überlagert, aber das ist meist nicht das was mal gerade braucht.
Zu beachten ist, dass für alle Funktionen die
#include <DateUtils.hpp>
includiert wird.
Hier nun die Funktionen, die einen doch ein bisschen Arbeit abnehmen:
Diese Funktionen arbeiten alle gleich. Sie erwarten als Übergabeparameter ein TDateTime Objekt und einen Wert. Ist der Wert negativ wird zurückgerechnnet. Beispiel:
String ausgabe; TDateTime dt=Now(); ausgabe=dt.DateTimeString(); //heutiges Datum dt=IncYear(dt,-20); ausgabe=dt.DateTimeString(); //vor 20 Jahren
Weiterhin gibt es Funktionen, die ein bestehendes Datum abändern. Dies sind:
Diese Funktionen arbeiten alle gleich. Sie erwarten als Übergabeparameter ein TDateTime Objekt und einen Wert. Der Wert wird als neuer Bestandteil des TDateTime Objektes eingefügt. Beispiel:
String ausgabe; TDateTime dt=Now(); ausgabe=dt.DateTimeString(); //heutiges Datum dt=RecodeYear(dt,1890); ausgabe=dt.DateTimeString(); //Jahr wird auf 1890 gesetzt.
Zusätzlich gibt es noch
mit denen ganze Daten und Zeitwerte auf einmal geändert werden können.
Weitere Funktionen (der Name dürfte sprechend sein):
| CompareDate |
| CompareDateTime |
| CompareTime |
| DateOf |
| DateTimeToJulianDate |
| DateTimeToModifiedJulianDate |
| DateTimeToUnix |
| DayOf |
| DayOfTheMonth |
| DayOfTheWeek |
| DayOfTheYear |
| DaysBetween |
| DaysInAMonth |
| DaysInAYear |
| DaysInMonth |
| DaysInYear |
| DaySpan |
| DecodeDateDay |
| DecodeDateMonthWeek |
| DecodeDateTime |
| DecodeDateWeek |
| DecodeDayOfWeekInMonth |
| EncodeDateDay |
| EncodeDateMonthWeek |
| EncodeDateTime |
| EncodeDateWeek |
| EncodeDayOfWeekInMonth |
| EndOfADay |
| EndOfAMonth |
| EndOfAWeek |
| EndOfAYear |
| EndOfTheDay |
| EndOfTheMonth |
| EndOfTheWeek |
| EndOfTheYear |
| HourOf |
| HourOfTheDay |
| HourOfTheMonth |
| HourOfTheWeek |
| HourOfTheYear |
| HoursBetween |
| HourSpan |
| IncDay |
| IncHour |
| IncMilliSecond |
| IncMinute |
| IncSecond |
| IncWeek |
| IncYear |
| IsInLeapYear |
| IsPM |
| IsSameDay |
| IsToday |
| IsValidDate |
| IsValidDateDay |
| IsValidDateMonthWeek |
| IsValidDateTime |
| IsValidDateWeek |
| IsValidTime |
| JulianDateToDateTime |
| MilliSecondOf |
| MilliSecondOfTheDay |
| MilliSecondOfTheHour |
| MilliSecondOfTheMinute |
| MilliSecondOfTheMonth |
| MilliSecondOfTheSecond |
| MilliSecondOfTheWeek |
| MilliSecondOfTheYear |
| MilliSecondsBetween |
| MilliSecondSpan |
| MinuteOf |
| MinuteOfTheDay |
| MinuteOfTheHour |
| MinuteOfTheMonth |
| MinuteOfTheWeek |
| MinuteOfTheYear |
| MinutesBetween |
| MinuteSpan |
| ModifiedJulianDateToDateTime |
| MonthOf |
| MonthOfTheYear |
| MonthsBetween |
| MonthSpan |
| NthDayOfWeek |
| RecodeDate |
| RecodeDateTime |
| RecodeDay |
| RecodeHour |
| RecodeMilliSecond |
| RecodeMinute |
| RecodeMonth |
| RecodeSecond |
| RecodeTime |
| RecodeYear |
| SameDate |
| SameDateTime |
| SameTime |
| SecondOf |
| SecondOfTheDay |
| SecondOfTheHour |
| SecondOfTheMinute |
| SecondOfTheMonth |
| SecondOfTheWeek |
| SecondOfTheYear |
| SecondsBetween |
| SecondSpan |
| StartOfADay |
| StartOfAMonth |
| StartOfAWeek |
| StartOfAYear |
| StartOfTheDay |
| StartOfTheMonth |
| StartOfTheWeek |
| StartOfTheYear |
| TimeOf |
| Today |
| Tomorrow |
| TryEncodeDateDay |
| TryEncodeDateMonthWeek |
| TryEncodeDateTime |
| TryEncodeDateWeek |
| TryEncodeDayOfWeekInMonth |
| TryJulianDateToDateTime |
| TryModifiedJulianDateToDateTime |
| TryRecodeDateTime |
| UnixToDateTime |
| WeekOf |
| WeekOfTheMonth |
| WeekOfTheYear |
| WeeksBetween |
| WeeksInAYear |
| WeeksInYear |
| WeekSpan |
| WithinPastDays |
| WithinPastHours |
| WithinPastMilliSeconds |
| WithinPastMinutes |
| WithinPastMonths |
| WithinPastSeconds |
| WithinPastWeeks |
| WithinPastYears |
| YearOf |
| YearsBetween |
| YearSpan |
| Yesterday |
Die Funktionen werden alle in der Hilfe erläutert.
Mit der folgenden Routine ist es möglich Strings in dem "natural sort" Verfahren sortieren zu lassen. Das Bild zeigt den Unterschied bezüglich der Sortierung.

Der Windowsexplorer sortiert die Dateien in dieser Art und Weise. Die Routine basiert auf einer JAVA-Routine, die ich für den C++Builder angepasst habe.
Als Übergabeparameter werden die zu vergleichenden Strings übergeben. Die üblichen Rückgabeparameter sind:
Download des Beispielprojektes
int NaturalSort(String s1, String s2)
{
int thisMarker=0,thatMarker=0,s1Length=s1.Length(),s2Length=s2.Length();
while (thisMarker=<s1Length&&thatMarker=<s2Length)
{
String thisChunk=getChunk(s1,s1Length,thisMarker);
thisMarker+=thisChunk.Length();
String thatChunk=getChunk(s2,s2Length,thatMarker);
thatMarker+=thatChunk.Length();
int result=0;
int p1=thisChunk.SubString(1,1).ToIntDef(-1);
int p2=thatChunk.SubString(1,1).ToIntDef(-1);
if (p1!=-1&&p2!=-1)
{
result=thisChunk.Length()-thatChunk.Length();
if(result==0)
{
for(int i=1;i=<thisChunk.Length();i++)
{
result=(char)thisChunk[i]-(char)thatChunk[i];
if(result!=0)
return result;
}
}
}
else
{
result=thatChunk.AnsiCompare(thisChunk);
}
if (result!=0)
return result;
}
return s1Length - s2Length;
}
//---------------------------------------------------------------------------
String getChunk(String s, int slength, int marker)
{
String chunk;
String c=s.SubString(marker,1);
chunk+=c;
marker++;
int p1=c.ToIntDef(-1);
if (p1!=-1)
{
while(marker<slength)
{
c=s.SubString(marker,1);
p1=c.ToIntDef(-1);
if(p1==-1)
break;
chunk+=c;
marker++;
}
}
else
{
while(markers<length)
{
c=s.SubString(marker,1);
p1=c.ToIntDef(-1);
if(p1!=-1)
break;
chunk+=c;
marker++;
}
}
return chunk;
}
Aktuelles Verzeichnis ermitteln:
String AktVerz=GetCurrentDir();
Efolgt der Programmaufruf über einen Link und ist dort ein Arbeitsverzeichnis eingestellt, so gibt der obige Aufruf das Arbeitsverzeichnis zurück. dann kann man mit folgenden Codestück den Pfad des Programmes erhalten:
String AktVerz=ExtractFilePath(Application->ExeName);
Verzeichnis der Anwendung ermitteln:
char htext[MAXPATH]; memset(htext,0,MAXPATH); GetModuleFileName(NULL,htext,MAXPATH); String AktVerz=ExtractFilePath(htext);
Eine schnelle Sortierroutine von Alfred Schneider aus dem Entwicklerforum.
Diese sortiert ein Grid unter Angabe der Spalte alphanummerisch. Im Gegensatz zu anderen Routinen arbeitet diese sehtr schnell und ist auch größerern Grids gewachsen. Sortierungen nach Datum oder nummerisch können impelmentiert werden, in dem der Vergelich entsprechend abgeändert wird.
void StringGridSort(TStringGrid* StringGrid, int ACol)
{
int w,x,z,sw;
String sbuf,s;
z=StringGrid->RowCount-1;
if(z>1)
{
for(x=StringGrid->FixedRows;x<=z;x++)
{
s=StringGrid->Cells[ACol][x];
sw=0;
for(w=x;w<=z;w++)
{
if(s.AnsiCompare(StringGrid->Cells[ACol][w])>0)
{
s=StringGrid->Cells[ACol][w];
sw=w;
}
}
if(sw>0)
{
w=sw;
sbuf=StringGrid->Rows[x]->Text;
StringGrid->Rows[x]->Text=StringGrid->Rows[w]->Text;
StringGrid->Rows[w]->Text=sbuf;
}
}
}
}
Eine GUID (Globally Unique Identifier) kann mit der Funktion CreateGUID erzeugt werden. Diese erwartet als Übergabeparamter eine GUID Objekt, welches dann mit den entsprechenden Werten gefüllt wird.
GUID g; CreateGUID(g); Guid=IntToStr((__int64)g.Data1);
Die Klasse GUID enthält folgende mögliche Werte zurück: