split

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


 



replace

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;

}




 



round

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;

};


 



circle

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);

}


 



Integer nach HEX-String

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.


 



HEX-String nach Integer

Wandelt einen String vom Typ STRING, der einen HEX-Wert enthält in einen Integer um.

int zahl=strtol(EingabeString.c_str(),NULL,16);


 



Integer nach BIN-String

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());


 



BIN-String nach Integer

Auch hier kann man die Funktion strtol nutzen:

int zahl=strtol(eingabe.c_str(),NULL,2);


 



TRegistry - Lesen & schreiben

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 &regkey;

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 &regkey;

Siehe auch


 



Aktuelles Verzeichnis ermitteln, Verzeichnis der Anwendung ermitteln

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);


 



Verzeichnisse erstellen

Der folgende Aufruf erstellt alle Verzeichnisse, sofern diese noch nicht vorhanden sind.

  String verz="C:\\test1\\test2\\test3";

  ForceDirectories(verz);


 



Wie erstellt man ein Programm das im Tray läuft?

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.


 



Wie kann man den Computer herunterfahren lassen (SHUTDOWN)?

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);


 



Wie kann man einzelne Bits setzen, löschen oder switchen?

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;

}


 



Wie kann man Variablen/Strukturen in mehreren Forms benutzen?

Man kann dazu die Variablen/ / Strukturen als Member in die Klasse aufnehmen ODER externe Variablen/ / Strukturen nutzen. Im fogenden werden beide Methoden beschrieben:

  1. 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 Datei -> Unit-Header einschliessen..angeklickt:
    Unit-Header einschliessen

    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;
    
    
  2. 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)
    
    {
    
    }
    
    //---------------------------------------------------------------------------
    
    


 



Wie wird ein Programm statisch gelinkt?

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:

Statisch Linken

Statisch Linken

Statisch Linken

Danach ist das gesamte Projekt mit "Projekt -> Alle Projekte erzeugen" neu zu compilieren und zu linken.


 



Wer ist SENDER (in Ereignisroutinen) und wie kann man auf seine Eigenschaften zugreifen?

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.


 



Wie kann man andere Programme mit SHELLEXECUTE starten?

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?


 



Wie kann man verhindern, dass ein Programm mehrmals gestartet wird?

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;

}

Fenster in den Vordergrund bringen

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

Daten an das schon laufende Programm übergeben

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



}

//---------------------------------------------------------------------------


 



Erläuterung der vom C++Builder genutzen Datei-Suffixe

*.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.


 



Komponenten zur Laufzeit erzeugen

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;


 



Wie kann ich eine Klasse nutzen die noch nicht deklariert wurde?

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.


 



Virtuelle Tastencodes von Windows

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


 



Welche Zeichen dürfen als Dateinamen verwandt werden?

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:

< > : " / \ |*?


 



Wie startet man ein Programm mit CREATEPROCESS?

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?


 



Wie kann man ein Registryschlüssel speichern?

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

Und nun wie kommt man wieder ran

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;


 



Zeilenumbrüche in unterschiedlichen Systemen

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


 



Statt der Tabulator-Taste mit der Enter-Taste zum nächsten Feld springen (Enter statt TAB)

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


 



Wie arbeitet man mit Mengen (Sets, TSet)

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:

  1. Nicht temporäre Menge
  2. temporäre Menge
  3. Explizite Zuweisung

1. Nicht temporäre Menge

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

2. Temporäre Menge

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.

3. Explizite Zuweisung

Noch kürzer ist folgende Methode:

TMemo1->Font->Style=TMemo1->Font->Style<<fsBold;

oder

TMemo1->Font->Style=TMemo1->Font->Style>>fsBold;

Was nicht geht

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.


 



Ausdruck eines TMemo-Objektes

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();

}


 



Wie können alle Benutzer eines Rechners einen Registry-Schlüssel lesen

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

Siehe auch


 



Wie kann man ein Programm starten ohne das es in der Taskleiste erscheint?

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.


 



JPG Bilder als Desktophintergrund installieren

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 &regkey;

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.


 



Fenstersuche mit EnumWindows / FindWindow

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.


 



Versenden einer eMail mit den Indy-Komponenten (TidSMTP)

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:


 



Hauptfenster der Anwendung beim Start nicht anzeigen

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.


 



Das Icon einer Anwendung ermitteln

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;


 



Mit TDateTime rechnen und andere Datumsfunktionn

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.


 



Abholen von Mails mit den Indy-Komponenten (TidPOP3)

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

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.


 



Interessante Funktionen für Rechtecke

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.

SetRect

Setzt die Werte für ein Rechteck

SetRect(LPRECT rec,left,top,right,bottom);

SetRectEmpty

Setzt alle Werte eines Rechtecks auf 0

SetRectEmpty(LPRECT rec);

CopyRect

Kopiert ein Rechteck

CopyRect(LPRECT dest,LPRECT source);

EqualRect

Prüft, ob zwei Rechtecke gleich sind

EqualRect(LPRECT rec1,LPRECT rec2);

IsRectEmpty

Prüft, ob ein Rechteck leer ist, also Höhe UND Breite sind 0

IsRectEmpty(LPRECT rec);

PtInRect

Prüft, ob ein Punkt innerhalb eines Rechtecks liegt oder nicht. pt ist eine POINT Struktur.

PtInRect(LPRECT rec,pt);

OffsetRect

Verschiebt ein Rechteck um die angegebenen Werte, wobei negative Werte nach links bzw. nach oben verschieben

OffsetRect(LPRECT rec,x,y);

InflateRect

Dehnt oder staucht ein Rechteck um die angegebenen Werte, wobei negative Werte ein Stauchen bewirken

InflateRect(LPRECT rec,x,y);

IntersectRect

Prüft, ob sich zwei Rechtecke überlappen und gibt die Schnittmenge zurück

InflateRect(LPRECT dest,LPRECT rec1, LPRECT rec2);

UnionRect

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.


 



Delphi Units in C++Builder nutzen

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));

}

//----------------------------------------------------------------


 



Migration BCB 6 auf BCB 2006

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

Wer weitere Hinweise hat, oder neue Fehler -> bitte eMail an unten stehende Adresse


 



HTTP mit den Indy-Komponenten (IdHTTP)

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:


 



Zeitsynchronisation mit den Indy-Komponenten (IdTime)

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:


 



Up- und Download mit den Indy-Komponenten (IdFTP)

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:


 



ADO-Komponenten mit MySQL verwenden

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



 



Alle 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/



 



Scrollen im TMemo

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);



 



Wenig bekannte Stringfunktionen

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