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
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;
}
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;
};
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);
}
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.
Wandelt einen String vom Typ STRING, der einen HEX-Wert enthält in einen Integer um.
int zahl=strtol(EingabeString.c_str(),NULL,16);
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());
Auch hier kann man die Funktion strtol nutzen:
int zahl=strtol(eingabe.c_str(),NULL,2);
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;
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);
Der folgende Aufruf erstellt alle Verzeichnisse, sofern diese noch nicht vorhanden sind.
String verz="C:\\test1\\test2\\test3"; ForceDirectories(verz);
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.
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);
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;
}
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)
{
}
//---------------------------------------------------------------------------
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.
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.
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?
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
}
//---------------------------------------------------------------------------
| *.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.
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;
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.
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 |
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:
< > : " / \ |*?
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?
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;
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 |
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
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.
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 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
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.
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 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.
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:
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.
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;
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.
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:
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 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.
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));
}
//----------------------------------------------------------------
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
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:
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:
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:
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 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/
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);
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 a |