C#: Alle Sichtbaren Prozesse / Fenster auflisten
Immer öfter benötige ich beim erstellen einer Software Zugriff auf fremde Fenster, doch leider bietet hier die Process-Klasse des .NET Frameworks nur wenig Hilfe. Denn mit ihr kann mann nicht alle Fenster ansprechen!
Problem:
Nehmen wir mal wir möchten alle zur Zeit geöffneten Fenster in einer Liste darstellen, dabei haben wir 3 Windows Explorer Fenster, 2 Mal FireFox und 3 anderen Programme geöffnet.
Wenn wir nun auf die Mittel des .NET Frameworks zurückgreifen, müssten wir das so angehen:
foreach (Process p in Process.GetProcesses())
{
if (p.MainWindowHandle != null){
MessageBox.Show(p.MainWindowTitle + " - " + p.MainWindowHandle.ToString());
}
}
Der Code ist sehr simpel und leicht verständlich. Wir prüfen, ob ein Fenster sichtbar ist, in dem wir schauen, ob ein Handle dafür hinterlegt ist. Leider kann man das meines Wissens nicht anders lösen.
Aber wie es euch vielleicht schon aufgefallen ist benutzen wir hier die Funktion p.MainWindowHandle() - das Problem hierbei ist das kleine Wörtchen "Main", das in dem Funktionsnamen steckt, denn diese Funktion liefert uns NUR den Titel und das Fensterhandle des Hauptfensters des Programms, oder in anderen Worten, des Fensters eines Programms, das zuletzt aktiv gewesen ist.
Das bedeutet, dass wenn wir 3 Firefox Fenster offen haben wir nur das Handle und den Titel des zuletzt aktiven Fenster bekommen. Schlimmer ist es noch mit den Systemfenstern, da diese immer unter dem Prozess "Explorer" laufen, der unter anderem auch für die Darstellung der Taskleiste verantwortlich ist.
Das bedeutet, dass wir im Normalfall nicht die Windows Explorer Fenster ansprechen können sondern stattdessen das Handle der Taskleiste zurückbekommen, da diese meißt aktiver ist als die Windows Fenster und meistens auch immer im Vordergrund bleibt. (Siehe letzter Artikel)
Lösung:
Doch wie bekommen wir nun alle Fenster? Hier bietet uns das Framework leider keine Hilfe, weswegen wir auf die Win32 API zurückgreifen müssen. Hier finden wir Methoden, die uns viel über die Fenster verraten:
[DllImport("user32.dll")] //Die Position und Größe eines Fensters bestimmen
public static extern long GetWindowRect(IntPtr hwnd, ref RECT lpRect);
[DllImport("user32.dll")] //Prüfen, ob ein Fenster Sichtbar ist
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.Dll")] //Alle offenen Fenster abrufen
public static extern int EnumWindows(ProcessListDemo.Windows.WinCallBack x, int y);
[DllImport("User32.Dll")] //Titel eines Fensters auslesen
public static extern void GetWindowText(int h, StringBuilder s, int nMaxCount);
[DllImport("User32.Dll")] //Die Klasse des Fensters besimmen
public static extern void GetClassName(int h, StringBuilder s, int nMaxCount);
Die wichtigsten hier sind "EnumWindows" und "IsWindowVisible". Die erste liefert uns eine Liste mit allen Fensterhandles die zur Zeit aktiv sind und mit der Zweiten können wir bestimmen, ob ein Fenster sichtbar ist oder nicht. Die anderen Funktionen lassen wir erstmal ungeachtet.
Wie eben schon erwähnt benutzen wir die EnumWindows-Funktion um uns alle Fensterhandles zu besorgen. Diese Funktion arbeitet hierbei über einen Delegaten Aufruf.
///
/// Beinhaltet alle zur Zeit geöffneten und Minimierten Fenster
///
public LinkedList lstWindows { get; set; }
///
/// Delegate Funktion für EnumWindows (Siehe Declarations)
/// Gibt die Werte an EnumWindowCallBack weiter
///
public delegate bool WinCallBack(int hwnd, int lParam);
private void getWindows()
{
//Liste mit Fenstern befüllen
ProcessListDemo.Declarations.EnumWindows(new WinCallBack(EnumWindowCallBack), 0);
}
///
/// Diese Funktion wird durch die Delegate Funktion WinCallBack aufgerufen
/// und iteriert durch alle zur Zeit geöffneten Fenster
private bool EnumWindowCallBack(int hwnd, int lParam)
{
IntPtr windowHandle = (IntPtr)hwnd;
StringBuilder sb = new StringBuilder(1024);
StringBuilder sbc = new StringBuilder(256);
ProcessListDemo.Declarations.GetClassName(hwnd, sbc, sbc.Capacity);
ProcessListDemo.Declarations.GetWindowText((int)windowHandle, sb, sb.Capacity);
//Nur Prozesse mit einer Beschreibung, also einem Fenster bearbeiten
if (sb.Length > 0)
{
ProcessListDemo.Declarations.RECT r = new ProcessListDemo.Declarations.RECT(); //Fensterposition & Größe bestimmen:
ProcessListDemo.Declarations.GetWindowRect(windowHandle,ref r);
Window w = new Window( sb + "",
windowHandle,
sbc + "",
ProcessListDemo.Declarations.IsWindowVisible(windowHandle),
new ProcessListDemo.Declarations.Point(r.Left, r.Top),
new ProcessListDemo.Declarations.Point(r.Right - r.Left, r.Bottom - r.Top),
Window.WinType.Normal);
this.lstWindows.AddLast(w);
}
return true;
}
In einfachen Worten erklärt passiert hier folgendes: Wir rufen die EnumWindows Funktion auf und übergeben dieser als Parameter die Referenz auf die Funktion EnumWindowCallBack, dazu benötigen wir die Delegate Funktion WinCallBack. Wichtig hierbei ist, dass alle Funktionen die gleichen Parameterdefinitionen haben. Der zweite Parameter ist bei mir hier aber ungenutzt, dieser ist optional vorhanden, um zB. weitere Referenzen zu übergeben wie zB. eine Listbox worin die Ergebnisse gespeichert werden sollen. Da wir hier aber die Ergebnisse direkt in die Klassen-variable schreiben benötigen wir diese nicht.
Nachdem die EnumWindows Funktion nun aufgerufen wurde macht sie nichts anderes, als die ihr übergebene Funktion (EnumWindowCallBack) aufzurufen. Diese wird dabei für jeden Prozess einmal aufgerufen und bekommt als Parameter das Handle des Fensters.
Im Prinzip nicht kompliziert. Ich habe hier, um das ganze besser zu visualisieren zwei Klassen namens "Window" und "Windows" erstellt. Diese übernehmen die ganze Arbeit für euch und erstellen eine Komplette Übersicht aller Fenster inklusive der Angaben, wie Größe, Position, Handle und Titel. Diese könnt ihr gern in euren eigenen Projekten nutzen.
Bild 1: Die Eigenschaften der Window-Klasse
Wenn ihr euch mal die Ergebnisse der EnumWindows Funktion anschaut werdet ihr feststellen, dass es ziemlich viele Fenster sind die da so im Hintergrund laufen, aber unsichtbar sind! Um diese Fenster zu filtern benutzen wir die Funktion IsWindowVisible und prüfen ob das Fenster Sichtbar ist oder nicht.
Bild 2: Das Demoprogramm in Aktion, zeigt alle Fensterprozesse
In dem Demoprogramm zeige ich euch wie ihr die zwei Klassen "Window" und "Windows" sehr einfach dazu benutzen könne, um eine Komplette Übersicht über alle Fenster des Systems zu bekommen.
private void cmdList_Click(object sender, EventArgs e)
{
//Alte Liste Leeren
lstProcess.Items.Clear();
//Alle vorhandenen Fenster bestimmen
Windows windows = new Windows();
//Alle Fenster durchgehen und in die Lite einsetzen
foreach (Window w in windows.lstWindows)
{
ListViewItem lvi = new ListViewItem(w.winTitle);
lvi.SubItems.AddRange(new string[] { w.winClass,
w.winHandle.ToString(), w.winVisible.ToString() });
lstProcess.Items.Add(lvi);
}
}
Wie man sieht ist diese Angelegenheit dank der "Windows"-Klassen nun sehr einfach geworden. Zum besseren Verständnis solltet ihr euch das Demoprojekt herunterladen.
Download des Demoprojekts: Download [VS 2008 C#]
Wie hat euch dieser Artikel gefallen? Habt ihr Verbesserungsvorschläge, Kritik oder Lob? Bitte ein Kommentar schreiben!
.NET: Windows Taskleiste verstecken
In den letzten Tagen ist mir durch einen Zufall aufgefallen, dass die Windows Taskleiste (seit Windows Vista) in einem Fenster Prozess des Explorers läuft.
Das interessante/lustige hierbei ist, dass die Taskleiste als Fenster erkannt wird und dementsprechend auch als Fenster behandelt werden kann. Also können wir sämtliche Funktionen für Fenster auch auf die Taskleiste anwenden.
Als Beispiel blende ich hier die Taskleiste mit Hilfe der ShowWindow Funktion aus. Das lustige hierbei, ist dass nicht die Ganze Taskleiste von dieser Aktion betroffen ist. Der "Start"-Knopf von Windows bleibt weiterhin sichtbar und funktioniert, nur das Drumherum ist nun verschwunden.
Der Prozess den wir suchen läuft als "explorer" und sollte der einzige Prozess sein, der ein MainWindowHandle hat aber keinen MainWindowTitle. Wenn man diese zwei Kriterien anwendet, kann man ganz einfach diesen Prozess isolieren:
[DllImport("user32.dll")]
public static extern long ShowWindow(IntPtr hWnd, int nCmdShow);
private IntPtr tbHandle = IntPtr.Zero;
private void button1_Click(object sender, EventArgs e)
{
Process[] pr = Process.GetProcesses();
foreach (Process p in pr)
{
if (p.MainWindowHandle != null && p.MainWindowTitle == ""
&& p.ProcessName == "explorer")
{
tbHandle = p.MainWindowHandle;
}
}
if (tbHandle != IntPtr.Zero)
{
ShowWindow(tbHandle, 0);
}
else
{
MessageBox.Show(this, "Konnte Prozess nicht finden!",
"Fehler", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
Da man nun das MainWindowHandle hat kann man nun mit Hilfe der ShowWindow-Funktion aus der Win32 API das Fenster verstecken.
WICHTIG: Bitte vor dem Verstecken der Taskleiste unbedingt das MainWindowHandle abspeichern, da man diese sonst nicht wieder einblenden kann!
Wenn man die Aktion erfolgreich ausführt sieht man folgendes:
Bild 1: Die Taskleiste ist verschwunden, nur der Start-Knopf ist noch da
Wie man sieht ist nur noch der Start-Knopf da und die gesamte Taskleiste ist verschwunden und ist auch nicht anklickbar!
Wenn man die Option "Taskleiste Automatisch ausblenden" aktiviert hat hat man sogar einen noch schöneren Effekt!
Also wenn ihr nicht wollt, dass euch jemand auf die Taskleiste glotzt oder ihr jemandem einen kleinen Streich spielen wollt könnt ihr das ja mal testen!
Falls ihr eure Taskleiste versteckt habt und aus versehen das Programm beendet könnt ihr die Taskleiste nicht mehr über das Programm wiederherstellen! Die einzige Lösung ist es im Taskmanager ([STRG]+[SHIFT]+[ESC]) den Prozess "explorer.exe" zu beenden und dann über Datei->Neuer Task->"explorer"->[ENTER] den Explorer neu zustarten.
Ich hoffe diese kleine Spielerei hat euch gefallen, das Demo Projekt (diesmal in C#) gibts hier:
Demoprojekt: Download [VS 2008]
VB.NET: FitzBox IP erneuern ueber uPnP
Die FritzBox ist als Router weit verbreitet und bietet einen Haufen an Konfigurationsmöglichkeiten an. Eins der nützlichsten Features ist uPnP (Universal Plug and Play). Diese Schnittstelle bietet uns eine sehr bequeme Möglichkeit mit dem Gerät zu kommunizieren ohne dass man einen Benutzernamen oder ein Kennwort benötigt!
Wer öfter mal im Internet unterwegs ist, kennst sicherlich mehrere gute Gründe seine IP zu wechseln. Und das ist sehr einfach, denn alles was dafür nötig ist, ist eine erneute Einwahl ins Internet.
Dies kann man sehr einfach über die Benutzeroberfläche der FritzBox machen oder einfach "oldschool"-mäßig das Netzwerkkabel aus der FritzBox ziehen und wieder einstecken. (Letzteres erfordert ebenfalls keinen Benutzernamen und Passwort)
Doch wenn man des öfteren auf diese Funktionalität angewiesen ist, ist es doch relativ nervig und umständlich. Deswegen werden wir hier ein einfaches kleines Programm entwickeln, dass für uns den Router zu einer Neueinwahl ins Internet "zwingt".
Wie oben bereits erwöhnt heißt die Technologie dafür UPnP (Universal Plug and Play). Über diese Technologie lassen sich sehr viele Befehle an die Fritzbox senden, vorausgesetzt, man befindet sich in dem selben Netzwerk.
Von Statusabfragen über Auslastungen und Übertragung, lassen sich sogar Telefonnummern anwählen und Anrufe steuern, im Prinzip kann man alle Funktionen darüber ansteuern. Hier gibts eine nette Übersicht mit vielen Beispielen.
Wie werden die Anfragen eigentlich an die Fritzbox übertragen? Das geschieht sehr einfach über einen POST Aufruf, denn auf der Fritzbox (und auch auf den meisten anderen Routern) läuft ein kleiner Webserver denn man ganz einfach über den Browser erreicht, wenn man die adresse "fritz.box" in die Adresszeile tippt.
Dort ist die Fritzbox genauso aufgebaut wie eine gewöhnliche Webseite. Und diese nimmt auch POST und GET aufrufe entgegen. Genau hier setzen wir an und werden unsere Befehle rüber schicken.
Dim client As New TcpClient
client.Connect("fritz.box", 49000)
Dim stream As NetworkStream = client.GetStream
Dim bytes As Byte() = New Byte((My.Resources.msg.Length)) {}
bytes = Encoding.ASCII.GetBytes(My.Resources.msg)
stream.Write(bytes, 0, bytes.Length)
bytes = New Byte(1024) {}
Dim str As String = String.Empty
Dim count As Integer = stream.Read(bytes, 0, bytes.Length)
str = Encoding.ASCII.GetString(bytes, 0, count)
stream.Close()
client.Close()
Der Code ist sehr simpel, wir erstellen uns einen WebClient und verbinden uns mit der FritzBox. Nach der Verbindung senden wir ein POST Request raus und lesen anschließend die Antwort wieder aus.
Mit dem obigen Code senden wir folgenden Befehl an die Fritzbox:
POST /upnp/control/WANIPConn1 HTTP/1.1
HOST: fritz.box:49000
SOAPACTION: "urn:schemas-upnp-org:service:WANIPConnection:1#ForceTermination"
CONTENT-TYPE: text/xml ; charset="utf-8"
Content-Length: 293
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:ForceTermination xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1" />
</s:Body>
</s:Envelope>
Wie man sieht ist der Befehl ein gewöhnlicher POST Aufruf inkl. Header. Dieser übergibt einen Befehl urn:schemas-"upnp-org:service:WANIPConnection:1#ForceTermination", der der Fritzbox sagt die Verbindung ins Internet neu aufzubauen.
Man muss hierbei sehr aufpassen, besonders die Content-Length Angabe muss genaustens passen, da sonst ein Fehler als Antwort eintrudelt.
Wie immer gibts auch eine kleine Demoanwendung.
Download hier: Download [VS 2008 Projekt]
Tutorial: Model-View-Controller (MVC) Struktur in Java Projekten nutzen
Die MVC-Architektur erfreut sich in letzter Zeit sehr hoher Beliebtheit, ständig hört man über neue Frameworks die auf diese Architektur schwören. (Beispiele: Spring Framework, ASP.NET MVC, Objective C...)
Bild 1: Der Aufbau - Quelle: Wikipedia
Doch was steckt eigentlich dahinter und warum sollte man seine Struktur überdenken?
Ich denke jeder, der eine Anwendung geschrieben hat schon die Erfahrungen gemacht, dass der Code immer unübersichtlicher wird, je größer ein Projekt wird.
Hier setzt MVC an, denn durch die strickte Teilung von Präsentation (View), der Programmlogik (Controller) und der Datenschicht (Model) erhöht sich die Lesbarkeit und Wartbarkeit des Codes. So kann man ein Projekt viel einfacher um weitere Funktionen ergänzen ohne den halben Sourcecode durchzugucken.
In diesem Artikel möchte ich diese Architektur an einem kleinem Java Beispiel demostrieren. Das Programm ist natürlich nicht wirklich zu gebrauchen, es soll lediglich das Vorgehen nahelegen.
Wie bereits oben erwähnt soll die Präsentation, also das was der Benutzer zu sehen bekommt (üblicherweise das Formular, also die GUI) von der Logik getrennt werden. Deshalb dürfen in der View-Klasse nur Sachen stehen, die der Darstellung der Elemente dienen. Das was passiert wenn man eins dieser Elemente anklickt wird dann über den Controller gesteuert.
Die Berechnungen an sich stehen dann im Model, wo auch alle Objekte verweilen, die unser Programm nutzt.
Unser Demo Programm ist sehr einfach aufgebaut, wir haben nur vier Klassen:
Bild 2: Der Aufbau des Projekts
Bevor ihr euch wundert, das Programm soll uns später die Quadratwurzel einer Zahl berechnen.
An Bild 1 könnt ihr erkennen, dass die Controller Klasse die wichtigste ist, da sie eigentlich das Ganze Programm steuert und dafür sorgt, dass die View und das Model sich verständigen können. Deswegen muss die Controller Klasse die anderen Klassen kennen, die anderen brauchen sich gegenseitig aber nicht!
public class Main {
static WurzelController controller;
/**
* Diese Klasse wird nur dazu benutzt alle nötigen
* Komponenten zu Initialisieren und die erste
* View anzuzeigen
*/
public static void main(String [] args){
controller = new WurzelController();
controller.showView();
}
}
Die Klasse Main macht hier auch nichts anderes, als die anderen Klassen zu instanzieren und uns die View anzuzeigen.
An den Konstruktoren kann man auch die Abhängigkeiten erkennen. Eigentlich macht man der View auch das Model bekannt (siehe Bild 1), aber da unser Beispiel zu simpel ist können wir uns diese Bekanntschaft sparen.
Das Model ist ebenfalls sehr einfach aufgebaut, es enthält die nötigen Rechenoperationen die für unser Programm wichtig sind und bietet öffentliche Methoden mit denen der Controller die Werte abfragen kann
/**
* Das Model ist komplett unabhängig von den anderen
* Klassen und weiß nicht was um ihn herum geschieht.
* Es ist völlig egal ob man dieses Model aus einem
* Fenster oder einer Konsolen Eingabe verwendet -
* beiden würde funktionieren.
*/
public class WurzelModel {
long _value;
public WurzelModel(){
zurückSetzen();
}
public void zurückSetzen(){
this._value = 0;
}
public void berechneWurzel(long wert){
this._value = (wert * wert);
}
public long getWurzel(){
return this._value;
}
}
Wichtig hierbei ist, dass diese Klasse keine Beziehungen zu den anderen Klassen hat! Man muss sie so aufbauen, dass man sie ohne eine Änderung in einem anderen Projekt benutzen könnte.
Unsere View ist ganz typisch aufgebaut. Es werden die Steuerelemente initialisiert und auf der JForm plaziert, desweiteren muss die View Methoden bieten, mit denen man die ActionListener für die auf dem Formular liegenden Steuerelemente setzen kann. Über diese wird unser Controller mit der View kommunizieren. Dazu kommen noch die getter und setter Methoden für die Textfelder, diese werden benötigt, damit man aus dem Controller Zugriff darauf hat!
/* Die View-Klasse diese Enthält nur die Präsentation
* hier sollte man keinerlei Programmlogik finden
* alle Berechnungen und Reaktionen auf Benutzeraktionen
* sollten allesammt im Controller stehen
*/
public class WurzelView extends JFrame{
private JLabel lbl1 = new JLabel("Eingabe: ");
private JTextField txtEingabe = new JTextField(3);
private JButton cmdCalc = new JButton("Wurzen Berechnen >");
private JTextField txtErg = new JTextField(5);
private JButton cmdClear = new JButton("Zurüclsetzen");
public WurzelView(){
super("Wurzel Berechnen");
initForm();
}
/**
* Die JForm initialisieren und alle Steuerelemente
* darauf positionieren
*/
private void initForm(){
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(new FlowLayout());
this.setBounds(200, 200, 500, 100);
this.add(lbl1);
this.add(txtEingabe);
this.add(cmdCalc);
this.add(txtErg);
this.add(cmdClear);
}
public void resetView(){
this.txtEingabe.setText("");
this.txtErg.setText("");
}
public String getEingabe(){
return this.txtEingabe.getText();
}
public void setErgebnis(String erg){
this.txtErg.setText(erg);
}
/**
* Funktionen bereitstellen, mit denen man später aus
* dem Controller die nötigen Listener hinzufügen kann
*/
public void setWurzelBerechnenListener(ActionListener l){
this.cmdCalc.addActionListener(l);
}
public void setResetFormListener(ActionListener l){
this.cmdClear.addActionListener(l);
}
}
Bleibt nur noch der Controller. Dieser ist auch sehr simpel aufgebaut:
/**
* Der Controller muss beide die View und das Model kennen
* da dieser für die Kommunikation zwischen den Beiden sorgt
*/
public class WurzelController {
private WurzelView _view;
private WurzelModel _model;
public WurzelController(){
this._model = new WurzelModel();
this._view = new WurzelView;
addListener();
}</pre>
<pre>
<div id="_mcePaste"> public void showView(){</div>
<div id="_mcePaste"> this._view.setVisible(true);</div>
<div id="_mcePaste"> }</div>
/**
* Die Listener, die wir aus den Internen Klassen generieren
* werden der View bekannt gemacht, sodass diese mit
* uns (dem Controller) kommunizieren kann
*/
private void addListener(){
this._view.setWurzelBerechnenListener(new WurzelBerechnenListener());
this._view.setResetFormListener(new ResetFormListener());
}
/**
* Inneren Listener Klassen implementieren das Interface ActionListener
*
* 1: Hier wird erst der eingegebene Wert aus der View geholt
* 2: Der Wert wird dem Model übergeben und die Wurzel berechnet
* 3: Die berechnete Wurzel wird aus dem Model geladen und
* 4: Wieder der View zum Darstellen übergeben
*
* ACHTUNG: Fehlerprüfung muss noch implementeirt werden
*/
class WurzelBerechnenListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
long wert = Long.valueOf(_view.getEingabe());
_model.berechneWurzel(wert);
_view.setErgebnis(String.valueOf(_model.getWurzel()));
}
}
/**
* Hier wird dem View und dem Model gesagt ihre gespeicherten
* Werte zu löschen.
*/
class ResetFormListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
_view.resetView();
_model.zurückSetzen();
}
}
}
Wie man sieht werden hier nur die dem Konstruktor übergebenen Referenzen auf die View und das Model gespeichert und dann die Erzeugten ActionListener der View übergeben.
Die ActionListener sind interne Klassen die nun automatisch von unserer View aufgerufen werden, wenn der Benutzer einen der Buttons klickt! Praktisch oder?
Wenn man die WurzelBerechnenListener Klasse anschaut kann man auch die Typische Kommunikation zwischen den Schichten sehen. Die View lößt ein Event aus, für dessen Abhandlung der Controller verantwortlich ist. Nun holt der Controller sich alle nötigen Daten aus der View (in diesem Fall ist es nur ein Wert, die Eingabe) und lässt das Model das Quadrat berechnen. Schließlich holt der Controller sich die berechneten Daten zurück und lässt die View diese anzeigen.
Im Grunde gar nicht kompliziert oder? So sieht das Fenster zur Laufzeit aus:
Bild 3: Das Programm zur Laufzeit
Wenn man ein neues Projekt anfängt sollte man sich natürlich erst einmal genau überlegen ob man die MVC Architektur benutzt, denn diese ist bei kleinen Projekten mit einem höheren Aufwand verbunden. Wenn man aber weiß, dass in absehbarer Zeit an dem Programm weitere Änderungen oder Erweiterungen vorgenommen werden sollte man MVC in Betracht ziehen!
ACHTUNG: Habe eben erst gemerkt, dass ich überall "Wurzel" geschrieben habe, das ergibt natürlich gar keinen Sinn!
oh mann, muss wohl sehr verwirrt gewesen sein! Quadrat müsste es natürlich heißen.
Keine Lust das nun alles nochmal zu ändern!
Die Sourcen könnt ihr hier laden: Download
Interessantes aus der Java Welt Teil 1
In diesem Beitrag möchte ich mal auf ein paar Besonderheiten eingehen, denen man wohl nicht jeden Tag beim Programmieren begegnet und die teilweise auch anders arbeiten, als wie man im Kopf denkt!
Ich möchte hier mal mit einem kleinem Beispiel anfangen. Guckt euch mal den kurzen Quellcode hier an und versucht zu berechnen was nach der Ausführung des Codes auf dem Bildschirm ausgegeben wird, bzw. lässt sich dieser Code überhaupt kompilieren?
public class Main {
public static void main(String[] args) {
int a = 2;
int b = 3;
int c = a+++b;
System.out.println(c);
}
}
Kompilieren lässt es sich aber was wird nun ausgegeben? Was passiert wenn man ein dreifaches Plus benutzt?
Naja das Ergebnis ist in jedem Fall 5. Doch um zu erfahren warum das so ist, muss man ersteinmal wissen, was der Kompiler da eigentlich macht, denn wir haben 2 Möglichkeiten die ausgeführt werden können:
int c = a_+_++b; //1: Präfix Inkrement ( _ soll ein Leerzeichen darstellen)
int c = a++_+_b; //2: Postfix Inkrement
Beide Rechenwege existieren doch nur einer wird ausgeführt, nämlich der 2te, denn nur der liefert als Ergebnis 5 zurück! Führt man den ersten Rechenweg aus erhält man 6 als Ergebnis!
Wenn der Kompiler diese Zeile liest und das Plus nach dem a gelesen hat erwartet er nun eigentlich den zweiten Summanden findet aber ein weiteres Plus, so wird erstmal die a++ Anweisung ausgeführt und anschließend + b dazu gerechnet.
Warum ist das so? Eigentlich wird doch in beiden Rechenwegen eine der Variablen um 1 (durch das ++ inkrement) erhöht, oder etwa nicht? Betrachten wir dazu dieses kleine Beispiel:
public static void test(){
int a = 2;
a = ++a;
int b = 2;
b = b++;
System.out.println(a); //Ergibt 3
System.out.println(b); //Ergibt 2
}
Im Falle von a wird der Wert von a erst erhöht und dann zugewiesen, dafür sorgt das so genannte Präfix (++a) da die Pluse auf der linken Seite stehen. Bei b wird erst der Wert von b zugewiesen und dann erhöht. Dadurch dass der Wert aber schon zugewiesen wurde verfällt diese Erhöhung wieder. Wenn die Operatoren auf der rechten Seite stehen nennt man sie postfix.
Wie man sieht stecht doch einiges hinter einer solchen kleinen Anweisung. Deswegen sollte man hier (wenn man will, dass der erste Fall eintritt) immer Leerzeichen setzen und dem Kompiler so genau sagen, welche operation durchgeführt werden soll!
Benutzt beim Übergeben von Werten diese Schreibweise möglichst nicht, da es zu unerwarteten Werten kommen kann. Um hier sicher zu gehen und nichts zu reskieren kann man auch die etwas längere Schreibform benutzen:
starteFunktion(variable++); //stattdessen kann man auch das nutzen:
starteFunktion(variable + 1);
Die zweite Variante übergibt ebenfalls den Wert von variable um 1 erhöht, ändert aber nicht deren Wert!
Wer mehr zu diesem Thema wissen möchte kann hier gucken: Galileo Java ist auch eine Insel
Java: DB4O Anbindung in einer Client / Server umgebung
Da ich mich zur Zeit selbst mit diesem Thema auseinander setze, dachte ich, es könnte nicht schaden ein kleines Tutorial zu schreiben. Hier möchte ich die ersten Schritte erklären und an ein paar Beispielen demonstrieren.
DB4O steht für Database for Objects und ist, wie der Name es schon sagt eine Datenbank für Objekte. Im Unterschied zu einer SQL Datenbank (wie MySQL) werden hier keine Tabellen erstellt, in denen die Daten gesichert werden, sondern die Objekte "als Ganzes" in die Datenbank geschrieben.
Das bringt einen gewaltigen Vorteil, denn wenn man ein Objekt in einer herkömmlchen relationalen Datenbank sichern möchte muss man es in einzelne Teile zerlegen und diese Teile dann in die Tabellen schreiben. Beim Auslesen wieder das ganze Spielchen umgekehrt, man sucht sich alle Werte des Objekts wieder aus der Datenbank zusammen um dann eine neue Instanz davon zu erstellen und mit den alten Werten zu füllen.
All das ist mit DB4O nicht mehr nötig, da hier die Datenbank diese Aufgaben für uns übernimmt, somit ist es sehr einfach für uns auch sehr komplexe Objekte schnell und bequem zu sichern und zu laden.
Fangen wir aber ersteinmal mit der Installation an
Die Installation, wenn man diese so nennen kann ist bei DB4O sehr einfach, und besteht nur darin, die nötigen Librarys in das Projektverzeichnis zu kopieren. Somit ist keine Installation beim Client mehr nötig, das Programm bringt dann alles was nötig ist selbst mit!
Als erstes benötigt ihr die Librarys von der DB4O - Homepage
Nach dem ihr diese heruntergeladen habt erstellt ihr ein "lib" Verzeichnis in eurem Projektordner.
In diesen neu erstellten Ordner entpackt ihr die *.jar Dateien aus dem heruntergeladenen Archiv aus dem gleichnamigen Ordner.
Nun wechselt ihr in Eclipse (bzw. eure IDE) und aktualisiert mal die Ansicht, ihr solltet nun den neuen Ordner mit den ganzen *.jar Dateien sehen.
Der letzte Schritt besteht darin, die Librarys zu dem Klassenpfad hinzuzufügen. Dazu klickt ihr mit der Rechten Maustaste auf euer Projekt und wählt "Properties", dann wählt ihr rechts "Java Build path", und nun den Tab "Libraries".
Nun klickt ihr auf "Add JARs..." und wechselt in euren lib Ordner. hier wählt ihr alle *.jar Dateien aus und klickt 2x auf "OK". - Das wars auch schon!
Wenn das alles erledigt ist, sollte euer Projektbaum ca so auusehen wie auf dem Bild links.
Die Praxis
Wenn ihr die Installation schon einfach fandet, dann werdet ihr die Implementation lieben! Denn das ist genauso schnell erledigt, denn alle große Arbeiten übernehmen für uns die Libraries, die wir im letzten Schritt importiert haben!
Als erstes benätigen wir eine Klasse deren Objektinstanzen wir dann in die Datenbank schreiben können. Dazu habeich eine einfache kleine Klasse "Car" geschrieben:
import java.io.*;
import java.util.*;
public class Car implements Serializable{
private static final long serialVersionUID = 1L;
private String marke;
private int ps;
private LinkedList teile = new LinkedList();
// Konstruktoren
public Car(){}
public Car (int ps){
this.ps = ps;
}
public Car(String marke){
this.marke = marke;
}
public Car(String marke, int ps){
this.marke = marke;
this.ps = ps;
}
public void addTeil(String teil){
teile.add(teil);
}
// Getter &amp; Setter
public LinkedList getTeile(){
return teile;
}
public void setMarke(String marke){
this.marke = marke;
}
public void setPs(int ps){
this.ps = ps;
}
public String getMarke(){
return this.marke;
}
public int getPs(){
return ps;
}
}
Wichtig ist bei dieser kleinen Klasse ist, dass sie das Interface java.io.Serializable implementiert, damit es auch selialisiert werden kann!
Ok nun haben wir ein Objekt, wie krigen wir dieses in die Datenbank? Das geht sehr einfach, wie man an diesem Codeschnippsel sehen kann:
ObjectContainer db = Db4o.openFile
("C:/test.yap");
try{
db.set(new Car("VW", 124));
}
catch(DatabaseFileLockedException e){
e.printStackTrace();
}
finally{
db.close();
}
Erst wird ein ObjectContainer erstellt, in den wir die Datenbankdatei einlesen, die bei DB4O die Endung yap haben. Wenn diese Datei noch nicht vorhanden ist wird diese automatisch angelegt. Hier gibt es zu beachten, dass die Pfadangabe nicht mit dem Windowstypischen "\" (Backslash) angegebenwird, sondern mit einem normal Slash.
Im folgenden Try-Catch Block wird die Methode Set aufgerufen, der wir auch eine Instanz unseres Car-Objekts übergeben. Das wars auch schon, nun ist diese instanz der Car Klasse schon in der Datenbank gespeichert!
Genauso einfach geht es auch einene Wert aus der Datenbank wieder auszulesen:
bjectContainer db = Db4o.openFile
("C:/test.yap");
try{
ObjectSet result = db.get(new Car(null));
}
catch(Exception e){
e.printStackTrace();
}
finally{
db.close();
}
Dazu wird die Methode Get benutzt, die als Rückgabewert ein ObjectSet mit allen Autos in der Datenbank liefert. Als Parameter wird der Get Methode eine leere Car Klasse übergeben, zu dem Warum komme ich nun.
Abfragesprachen / Abfragemethoden
Wie ihr an dem Letzten Codebeispiel gesehen habt wird hier keine SQL Sprache genutzt um die nötigen Werte aus der Datenbank zu ziehen, sondern es wird mit Objekten gearbeitet. DB4O bietet drei verschiedene Wege um an die gespeicherten Objekte zu kommen:
QBE - Query by Example
SODA - Simple Object Database Access
NQ - Native Queries
Ich werde hier aber nur auf die ersten zwei zusprechen kommen.
QBE bietet die einfachste Möglichkeit an unsere gespeicherten Objekte zu kommen, diese Methode habe ich auch bei dem Beispiel oben genutzt.
Bei dieser methode wird vom Benutzer ein Objekt erzeugt, nach dem dann in der Datenbank gesucht wird. Wenn wir den Fall von eben aufgreifen, wo nur eine leere Car Klasse übergeben wurde, werden wir sehen, dass der Wert null mit allen Werten übereinstimmt. Das heißt, will ich alle Werte aus der Datenbank haben, übergebe ich ihr eine "leere" Klasse.
Wenn ich die Get Abfrage etwas ändere und statt null, "VW" übergebe, würde uns die Datenbank alle Autos ausspucken, die als marke VW haben. Genauso können wir auch nach der PS Zahl suchen, indem wir statt null eine Zahl übergeben. Wir können aber auch, wenn wir genau wissen wonach wir suchen, auch beide Werte übergeben. db.get("VW",120);
Achting, wir können diese methoden nur anwenden, weil wir in der Car Klasse die einzelnen Konstruktoren definiert haben. So muss für jede Suchanfrage auch der passende Konstruktor vorhanden sein. Oder man erstellt einen Konstruktor, mit dem man alles abdecken kann. So könnte man in unserem Beispiel hier auch so vorgehen um alle VW Autos gelistet zu bekommen: db.get("VW", null);
Leider hat diese Vorgehensweise so simpel sie auch ist entscheidende Nachteile. So dürfen in den Konstruktoren keine Werte initialisiert werden, denn wenn ich im Konstruktor die Variable marke standardmäßig als marke = "VW"; initialisieren würde, würde mir die Datenbank bei jeder Suche nur Autos der marke VW ausspucken.
Desweiteren kann man keine Bedingungen benutzen, denn was ist wenn ich zB. alle Autos sehen will, die mehr als 100 PS haben?
Genau hier setzt SODA an. Hier sollte man wissen, dass alle Abfragen in DB4O unter der Haube in SODA laufen, so wird jede Anfrage automatisch konvertiert.
SODA bietet uns hier viele weitere Werkzeuge die man auch aus zB. SQL kennt. So können wir nun auch komplexere Abfragen erstellen. Setzen wir nun mal unser Problem von eben um und fragen alle Autos ab, die mehr als 100 PS haben:
Query q = aClient.query();
q.constrain((Car.class));
q.descend("ps").constrain(100).greater();
listResult(q.execute());
Wie man sieht ist diese Abfragemethode etwas komplexer. Aber auf den zweiten Blick ist es aber doch ganz logisch. Zu erst wird ein Query Objekt erzeugt, dem wir dann ein "constrain" (zu Deut. Beschränkung, Begrenzung) übergeben. Diesem übergeben wir "Car.class", somit sagen wir dem Query, dass er nur Objekte vom Typ Car aus der Datenbank holt.
Die nächste Zeile ist eigentlich auch selbsterklärend, dort setzen wir fest, dass die Ergebnisse absteigend (descend) sortiert werden, undzwar nach der Eigenschaft "ps" und durch eine Weitere Beschränkung nur Werte über 100 akzeptiert.
Die letzte Zeile führt den Befehl aus.
Ähnlich kann man auch einen Maximalwert setzen, sodass nur Autos angezeigt werden die zwischen 100 und 130 PS haben:
Query q = aClient.query();
q.constrain((Car.class));
Constraint c1 = q.descend("ps").constrain(100).greater();
q.descend("ps").constrain(130).smaller().and(c1);
listResult(q.execute());
Natürlich kann man auch Autos abrufen, die einen bestimmten Wert enthalten. ZB alle Autos die die den Buchstaben w im Namen haben:
Query q = aClient.query();
q.constrain((Car.class));
q.descend("marke").constrain("w").like();
listResult(q.execute());
Client / Server umgebung
So, nach der Kurzen Einführung wollen wir nun das eigentlich Ziel umsetzen, nämlich die Datenbank in ein Client Server System integrieren. Zum Glück ist das ebenfalls nicht allzu kompleziert!
Malen wir uns erstmal ein kleines Diagramm um zu verbiltlichen, wie der Aufbau der Programme sein soll:
Das Tolle ist, dass die DB4O Libraries und die Arbeit abnehmen eine Verbindugn aufzubauen, die Sockets zu verwalten und die einzelnen Threads anzulegen. Somit müssen wir uns nur auf die Implementierung konzentrieren.
Unsere Server Applikation ist dementsprechend sehr einfach gestrickt:
import com.db4o.*;
public class Server {
public static void main (String [] args){
//Server starten mit lokaler Datei
System.out.println("Starte Server...");
ObjectServer server = Db4o.openServer("C:/test.yap",54321);
server.grantAccess("test","pass");
server.grantAccess("andererUser","pass2");
try {
while(true){
//Leerlauf damit der Server anbleibt
System.out.println("SERVER: [DB4O] läuft...");
Thread.sleep(10000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
server.close();
}
}
}
Wie man sieht sieht man nicht viel. Was auffällt, ist dass man nun statt openFile openServer benutzt wird, und als zweiten Parameter nun einen Port mitgibt, auf dem der Server auf Verbindungen wartet.
Danach werden noch die Benutzer beschrieben, denen der Zugriff auf die Datenbank gewährt werden soll. Und eigentlich wars das auch schon! Die anschließende Schleife sorgt nur dafür, dass das Programm unendlich läuft (zumindest bis der Benutzer es terminiert) und Anfragen von Clients akzeptiert!
Gut nun schauen wir uns mal den Client an:
import com.db4o.*;
public class ClientAdd {
static ObjectContainer aClient = null;
public static void main(String [] args){
aClient = Db4o.openClient("localhost", 54321, "test", "pass");
//------------------------------------
Car myCar1 = new Car("Mercedes", 98);
Car myCar2 = new Car("VW", 158);
myCar1.addTeil("Dieselmotor");
myCar1.addTeil("Klimaanlage");
myCar1.addTeil("ABS");
myCar2.addTeil("ERP");
myCar2.addTeil("Schiebedach");
myCar2.addTeil("Automatik");
//Sende Objekte
System.out.println("Sende Objekte...");
aClient.set(myCar1);
aClient.set(myCar2);
aClient.commit();
System.out.println("Fertig");
aClient.close();
}
}
Wie man sieht ist die Verbindung zum Server noch einfacher gestrickt als der Server. Es wird wiedermal ein ObjectContainer instanziert und kann nun genauso benutzt werden wie in den Beispielen von oben.
Ich denke mal das dürfte als Einstieg reichen. Wenn ihr Probleme oder Fragen habt könnt ihr diese hier gern posten, vielleciht kommt dann irgendwann ein zweiter Teil.
Ein Lauffähiges Beispiel gibts hier: Download [Eclipse Project]
Wer mehr erfahren will guckt hier.
Eine wetere gute Anleitung gibts hier.
Tetris nachprogrammieren in Java
Ich hatte ja nun schon einige Java Tuts hier im Blog, auch zwei Spiele waren dabei (Fill IT und Snake). Nun bin ich letztens über mein erstes Java Spiel-Projekt gestolpert, da habe ich nämlich versucht Tetris nachzubasteln.
Das Ergebnis von diesem kleinen Projekt könnt ihr hier in dem Bild links betrachten.
Im Nachhinein weiß ich, dass ich lieber ein anderes (einfacheres) Spiel als erstes Projekt hätte wählen sollen, Snake oder so, denn ich habe knapp vier Wochen an diesem hier gesessen.
Natürlich hat es sich dennoch gelohnt und ich habe viel bei der Entwicklung gelernt, und dabei ist eine komplett spielbare Version bei herausgekommen!
Da dies mein erster Gehversuch war ist der Code natürlich nicht wirklich professionell und (wahrscheinlich) voll von irgendwelchen Anfängerdummheiten, desweiteren sind noch einige (meist kleinere) Bugs und Probleme vorhanden, die das Spielvergnügen trügen!
Die gute Nachricht ist aber, dass ich vieles kommentiert habe, und man sich somit relativ schnell in den Code einlesen kann!
Features:
- Bekanntes Spielprinzip
- Vier verschiedene Objekte (Zwei fehlen zum Original -> L-Blöcke)
- Punkteanzeige
- Anzeige der nächsten Figur
- Vier verschiedene Schwierigkeitslevel
Bekannte Probleme:
- Unterste Zeile ist fehlerhaft
- Man kann Objekte nach rechts aus dem Bildschirm schieben
- Die Oberfläche neigt zum "blinken"
- Die Steuerung ist nicht "flüssig"
- Letzter Schwierigkeitsgrad ist unspielbar, da zu schnell
- Stürzt manchmal ab, sobald die erste Figur unten ankommt
Die oben gelisteten Bugs sind da, stören aber nur wenig. Da ich nicht die Absicht habe dieses Spiel weiterzuentwickeln, schreibt mir bitte keine Kommentare oder Mails mit weiteren Fehlern oder Bugs.
Falls ihr selber an einem solchen Spiel arbeitet und fragen zu dem Aufbau oder dem Quellcode habt könnt ihr mich natürlich gerne kontaktieren.
Folgendes solltet ihr schon mal gesehen haben um dem Quellcode zu verstehen:
- Vererbung
- Abstrakte Klassen
- GUI Programmierung
- Zeichnen (Java Graphics Klasse)
- Allgemeine Objekt Orientierte Programmierung
- Gute Kenntnisse der Basis Java Klassen
Die Downloads findet ihr hier:
.NET Administratorrechte fuer eigenes Programm einfordern
Wenn man mit .NET entwickelt wird man schnell feststellen, dass man zur Laufzeit bestimmte Ordner nicht öffnen kann, bestimmte Dateien nicht löschen oderbestimmte Systemfunktionen nicht ausführen kann!
Das liegt daran, dass dem Programm die nötigen Rechte fehlen, denn seit Windows Vista werden alle Programme ohne Administratorrechte ausgeführt. Braucht ein Programm dann aber doch diese Rechte sieht man diesen wenig gemochten Dialog von Windows:
Bild 1: Windows Benutzerkontensteuerung in Aktion
Das Angezeigte unterscheidet sich dann natürlich je nach Programm, aber sobald man hier auf Zulassen geklickt hat wird das Programm mit den Vollen Rechten gestartet.
Aber woran kann ich erkennen, dass ich volle Rechte benötige? - Ganz einfach, wenn man Fehlermeldungen erhält, die besagen, dass man keine Rechte besitzt um auf die Dienste zuzugreifen, oder wenn Systemfunktionen (zB. WMI) nicht mehr korrekt ausgeführt werden.
Wie sorge ich nun dafür, dass mein Programm Adminrechte erhält? - Hier gibt es zwei Möglichkeiten. Die erste ist es das Programm mit rechter Maustaste anzuklicken und dann "Als Administrator ausführen" zu wählen:
Bild 2: So kann man jedem Programm Administratorrechte geben
Natürlich kann man die Adminrechte direkt in unserem Programm beantragen, in dem man in den Projekteinstellungen im Tab "Anwendung" auf den Button "Einstellungen für die Benutzerkonstensteuerung anzeigen" klickt. Dazu wird die Datei "app.manifest" im Projektordner generiert, die dann folgende Einstellungen beinhaltet:
<!-- UAC-Manifestoptionen
Wenn Sie die Ebene der Benutzerkontensteuerung für Windows ändern
möchten, ersetzen Sie den Knoten "requestedExecutionLevel" wie folgt:
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Wenn Sie aus Gründen der Abwärtskompatibilität Datei- und Registrierungsvirtualisierung
verwenden möchten, löschen Sie den Knoten "requestedExecutionLevel".
-->
Wie man in dem Grünen Kommentarblock erkennen kann, gibt es drei verschiedene Einstellungsmöglichkeiten, nämlich level="asInvoker" (welcher voreingestellt ist), level="requireAdministrator" und level="highestAvailable".
requestedExecutionLevel level="requireAdministrator" uiAccess="false"
Wenn man nun den unter dem Kommentarblock stehenden Eintrag requestedExecutionLevel auf requireAdministrator ändert, wird unser Programm bei jedem Start automatisch Administratorrechte einfordern, und man wird bei jedem Start diesen unschönen Windows Dialog aus Bild 1 zu sehen bekommen.
Wie ihr euch sicher grade denkt ist das sehr unangenehm für den Benutzer, der später mit der Software arbeiten muss. Dieser muss jedes Mal die Ausführung dieses Programms bestätigen. Außerdem sollte man im Hinterkopf behalten, dass Microsoft diesen Dialog nicht zum Spaß eingeführt hat.
So sollte man immer versuchen seine Software so zu gestalten, dass man auch ohne Administratorreche auskommt. Sollte es aber nicht anders gehen, sollte man den Teil, der diese Rechte benötigt stets aus dem Programm auslagern, sodass dieser Spezielle Fall dann gesondert aufgerufen werden kann und man den Benutzer ggf darauf vorbereiten kann, dass gleich eine Meldung von Windows kommt.
Das Hauptprogramm kann und sollte dann auch weiterhin mit dem geminderten Rechten laufen. So verwirrt man den Benutzer nicht und kann Fehlerquellen später besser eingrenzen.
Programmfortschritt in der Windows Taskleiste anzeigen
Wenn ihr Windows 7 bereits verwendet, werden euch sicherlich einige Neuerungen aufgefallen sein. Einige von denen ist die neue Funktion der Taskleiste den Fortschritt eines Programms darzustellen.
Ihr werdet dies beobachten, wenn ihr z.B. eine Datei mit dem Internet Explorer 8 herunterladet oder eine Datei kopiert. Das Programm zeigt den eigenen Fortschritt dann unter dem Eigenen Symbol in der Taskleiste an.
Diese Funktion ist extrem praktisch, so kann man sie benutzen um den Benutzer immer aktuell zu halten, ohne dass er das eigentliche Programm offen hat. Mögliche Anwendungsfälle wären Dateidownloads, Kopiervorgänge, Bildererzeugung und allgemeine Vorgänge, die etwas Zeit in Anspruch nehmen.
Natürlich können auch wir diese Funktionen in unseren Projekten nutzen. Leider werden die Aufrufe an das Betriebssystem unmanaged gestartet, was bedeutet, dass diese Aufrufe umgewandelt (gewrapped) werden müssen. Da das ein sehr komplexer Vorgang ist hat Microsoft uns diesen gespart und schon einige vorgefertigte Klasse angefertigt auf die wir zurückgreifen können.
Diese Librarys finden wir im MSDN in der .NET Interop Sample Library. Diese Beispielsammlung enthällt viele weitere Beispiele. Wir betrachten hier nur das was mit dem Fortschrittsbalken in der Taskleiste zu tun hat.
Deswegen brauchen wir auch nicht alle Klassen, sondern nur diese:
Vista Bridge Sample Library
Windows7.DesktopIntegration
Windows7.DesktopIntegration. Registration.
Windows7.DesktopIntegration enthält hierbei die Klasse WindowsFormsExtensions, welche uns die nötigen "Extensions"-Funktionen zur Verfügung stellt um das Aussehen der Taskleiste ändern zu können.
In unserem (einfachen) Fall werden wir nur die beiden Methoden benötigen:
SetTaskbarProgress(float percent)
SetTaskbarProgressState(ThumbnailProgressState state)
Mit der Ersten Methode können wir den Fortschritt der Progressbar steuern, und mit der zweiten das Aussehen. Für die zweite Funktion müssen wir neben dem Zustands Parameters auch das Handle des Fensters angeben, für das die Aktion ausgeführt wird.
So könnten die Aufrufe folgendermaßen aussehen:
WindowsFormsExtensions.SetTaskbarProgress(Me.ProgressBar1)
WindowsFormsExtensions.SetTaskbarProgressState(Me, _
Windows7Taskbar.ThumbnailProgressState.Error)
Garnicht mal so kompliziert oder?
Dabei kann das Taskleistensymbol unseres Programms 5 verschiedene Werte annehmen:
Wenn wir nun also ein neues Windows Forms Projekt erstellen müssen wir ersteinmal die drei oben erwähnten Class Librarys importieren. Das machen wir in dem wir über Datei->Hinzufügen->Vorhandenes Projekt wählen
Auf diese Weise fügen wir alle drei oben erwähnten Projekte dem unseren hinzu. Wenn das geschafft ist müssen wir noch die nötigen Verweise hinzufügen. Dazu wählen wir Projekt->Verweis hinzufügen... und dann den Tab "Projekte"
Hier wählen wir alle drei Projekte aus und klicken auf "OK"!
Wie ihr nun im Projektmappen Explorer erkennen werdet ist das Projekt gleich mal um einiges gewachsen! Ihr könnt dieses Vorgehen auch umgehen, indem ihr die importierten Class Librarys als DLL kompiliert und direkt in das Projekt einbindet. Das schafft vielleicht mehr Übersicht.
Nun haben wir schon das größte Hindernis schon überwunden! Nun gehts ans Programmieren, wofür ihr die oben vorgestellten Funktionen benutzen könnt um eurem Programm den nötigen Windows 7 Look zu verpassen!
Ich habe dazu wie immer eine kleine Demo angefertigt. Meine sieht so aus:
Bild 4: Das Demoprojekt in Aktion
Das Demoprojekt könnt ihr hier laden: Download [VS 2008]
VB.NET: Saubere Klassen erstellen mit Properties
Heutzutage wird fast nurnoch Objektorientiert programmiert. Das bedeutet, dass ein größeres Projekt schnell mal auf eine Klassenanzahl von 100 und mehr wächst.
Damit man den Überblick behält und den Code, den man schon geschrieben hat möglichst wiederverwenden kann, sollte man ein paar grundlegende Regeln beachten.
In diesem Artikel möchte ich ein Beispiel zeigen, wie man eine Klasse in ein Projekt integriert und dank der benutzten Standardmethoden an ein Datagrid bindet.
Warum schreibe ich diesen Artikel überhaupt? Erst gestern habe wurde in einem Forum die Frage gestellt, warum man eine Liste (Of T) nicht als DataSource für ein DataGridView benutzen kann. Dabei ist das sehr wohl möglich, der User hat seine Klasse aber "unschön" gestaltet, weswegen sich die anderen Steuerelemente auch quer gestellt haben.
Hier ein Beispiel einer solchen "Unschönen" Klasse:
Public Class Auto
Public marke As String
Public motor As String
Public geschw As Integer
End Class
Dim al As New List(Of Auto)
al.Add(New Auto With {.marke = "VW", .motor = "Diesel", .geschw = 0})
al(0).geschw += 10
Wie man sieht sind hier alle Klassenvariablen öffentlich zugänglich, und die Klasse hat keinerlei Kontrolle darüber, wie sie befüllt werden. Ein weiterer Nachteil ist auch die Inkompabilität zu anderen Steuerelementen, da diese einen Standardkonformen Aufbau der Klasse erwarten. Finden sie bestimmte Elemente in der Klasse nicht so verweigern diese die Funktionalität!
Wie müsste diese Klasse also umgebaut werden, damit diese kompatibel zu anderen Objekten wird?
Public Class Auto
Private geschw As Integer
Public Sub New(ByVal marke As String, ByVal motor As String)
Me._marke = marke
Me._motor = motor
End Sub
Private _marke As String
Public Property Marke() As String
Get
Return _marke
End Get
Set(ByVal value As String)
_marke = value
End Set
End Property
Private _motor As String
Public Property Motor() As String
Get
Return _motor
End Get
Set(ByVal value As String)
_motor = value
End Set
End Property
Public Sub beschleunigen(ByVal a As String)
geschw += a
End Sub
Public Sub bremsen(ByVal a As String)
geschw -= a
End Sub
End Class
Wie man sieht ist die Klasse nun natürlich um einiges länger geworden. Leider kann man das zur zeit noch nicht anders machen. Aber Abhilfe kommt in der neuen VB.NET Version, wo man die properties so wie in C# 3.0 erstellen kann.
Die Zugriffe auf die Klassenvariablen werden nun über properties gesteuert, die nun auch zB. überprüfen könnten, ob der übergebene Wert überhaupt passt und gegebenenfalls noch Änderungen daran vornehmen.
Aber was bringt uns nun dieser längere Aufbau der Klasse? Nun, wenn wir unser Objekt, das aus dieser Klasse entsteht an andere Objekte übergeben, erwarten diese, dass sie properties vorfinden, mit denen sie arbeiten können.
Hat die Klasse keine Properties so können wir auch bestimmte Funktionen auch nicht benutzen. Ich möchte das mal an einem DataGridView demonstrieren, da dieses Element von den properties sehr schön Gebrauch macht!
Fügt man folgenden Code einem Form mit einer DataGridView hinzu:
Private Sub Form1_Load() Handles MyBase.Load
Dim autos As New List(Of Auto)
autos.Add(New Auto("VW", "Diesel"))
autos.Add(New Auto("Audi", "Benziner"))
autos.Add(New Auto("Mercedes", "Diesel"))
autos.Add(New Auto("Renault", "Diesel"))
autos.Add(New Auto("BMW", "Beziner"))
Dim bs As New BindingSource
bs.DataSource = autos
Me.DataGridView1.DataSource = bs
End Sub
erhält man folgendes Bild:
Bild 1: Die Datagridview stellt alle Informationen angenehm dar
Wie man sieht konnte man mit nur 3 Zeilen Code (Abgesehen vom Erzeugen der Werte) alle Daten der Liste visualisieren. Man beachte desweiteren, dass sogar die Namen der Properties (Marke und Motor) ausgelesen werden und der richtigen Tabelle zugeordnet werden!
Dies könnte man natürlich auch ohne Properties hinbekommen, doch würde man viel mehr Aufwand betreiben müssen und man denke an die Zukunft, in der unser Modell sehr einfach zu warten wäre.
Hier gibts ein kleines Demoprojekt: Download [VS 2008]






