Abou Chleih

{the magic lies between the brackets}

Menü Schließen

Kategorie: C# (Seite 1 von 4)

Beiträge zur objektorientierten typsicheren Programmiersprache C# in Verbindung mit dem .NET-Framework von Microsoft

ASP.NET Core App via Apache bereitstellen #2 – Konfiguration Apache

Um eine weitere Apache Instanz zu erstellen, gibt es bereits ein Skript, welches einem einige Arbeit abnimmt.
Die grundsätzliche Idee dahinter ist jedoch einfach: Man kopiert die originale Instanz (apache2) , also Konfigurationsdateien, Binaries, etc. in einen zweiten Ordner (apache2-xxx).

Je nach System findet sich das Skript in
/usr/share/doc/apache2.2-common/examples/setup-instance <instance>
oder
/usr/share/doc/apache2/examples/setup-instance <instance>

<instance> ist hierbei der Name der Instanz, die erstellt werden soll: /usr/share/doc/apache2/examples/setup-instance netcore

Dies erstellt also die entsprechenden Verzeichnis und Dateien /etc/apache2-netcore, /etc/logrotate.d/apache2-netcore, /var/log/apache2-netcore sowie das Init-Skript für den Boot /etc/init.d/apache2-netcore.

Um die entsprechenden Links zu aktivieren, also apache2-netcore als Service verfügbar zu machen. Führen wir nun ein update-rc.d apache2-netcore defaults aus.
Dies verlinkt die executables in /etc/rcX.d/, also dem entsprechenden Runlevel für den Boot Prozess.

Nun können wir den Service mit init.d oder service starten: /etc/init.d/apache2-netcore start oder service apache2-netcore start

Nun können wir uns an die Konfiguration de Apache2 Instanz NetCore machen. Dazu navigieren wir ins Verzeichnis /etc/apache2-netcore/sites-available/ und bearbeiten das file 000-default.conf (HTTP) oder default-ssl.conf (HTTPS).

Diese Dateien haben folgenden Aufbau (in diesem Beispiel hört die ASP.NET Core Application auf localhost:5000):

<VirtualHost *:8080>
     ServerName sub.mydomain.com 
     ErrorLog ${APACHE_LOG_DIR}/errorNetCore.log 
     CustomLog ${APACHE_LOG_DIR}/accessNetCore.log combined 
     ProxyPass / http://localhost:5000/ 
     ProxyPassReverse / http://localhost:5000/ 
     ProxyPreserveHost On
</VirtualHost>

ServerName: Hört auf Aufruf von sub.mydomain.com
ErrorLog: Schreibt Error Log in entsprechendes File, (Apache bspw. PHP Fehler kommen hier rein)
CustomLog: Schreibt alle Aufrufe in File (=AccessLog Config)
ProxyPass: Forward Proxy Config, leitet alle Anfragen von http://sub.mydomain.com:8080/ an http://localhost:5000/ weiter
/ steht hier für das Top Directory, ProxyPass /sub/ http://localhost:5000/ würde bspw. http://sub.mydomain.com:8080/sub/ an http://localhost:5000/ weiterleiten
ProxyPassReverse: Reverse Proxy, leitet Response um. Schreibt alle Urls in der Rückantwort um, bspw. sollte http://localhost:5000 zu http://sub.mydomain.com:8080 umgeschrieben werden.
ProxyPreserveHost: Header bleibt erhalten

Nun speichern wir die Seite und aktivieren sie mit a2ensite sub.mydomain.com und starten die Instanz neu service apache2-netcore restart.

Anschließend starten wir die ASP.NET Core Applikation: dotnet PFADZURDLL

ASP.NET Core App via Apache bereitstellen #1 – Compiling & Vorbereitung

Mit .NET Core ist es seit einiger Zeit problemlos möglich cross-plattform Applikationen zu erstellen.
Darunter fallen auch ASP.NET Core Web-Anwendungen.
Um diese Anwendungen allerdings allgemein bereitzustellen, müssen wir diese zuerst für den allgemeinen Zugriff aus dem Internet freigeben.
Dies geschieht auf Grund der Plattformunabhängigkeit natürlich ohne Microsoft IIS. In diesem Beispiel wird ein Apache2 Webserver in der Standardkonfiguration genutzt.
Voraussetzung sind daher ein installierter Server mit funktionaler Apache2 Installation und natürlich eine ASP.NET Core Anwendung.

Was werden wir hier nun genau machen?

  • Compilen der ASP.NET Core Anwendung
  • Konfigurieren einer eigenen Apache Instanz, die als Reverse Proxy fungiert
  • Bereitstellen der Applikation

Um die ASP.NET Anwendung bereitzustellen, müssen wir diese zuerst compilen, dies mache ich bevorzugt auf meiner Entwicklungsmaschine (hier: Windows).
Wir gehen als in das Projektverzeichnis ({Solutionpath}\src\{Projectpath}) und öffnen dort eine Kommandozeile oder PowerShell.

Mit dem Befehl dotnet publish -c Release (-c = –configuration) starten wir nun den Build Prozess (im Release Modus).
Sollten nun Fehler kommen, dass „bower“ nicht gefunden werden kann, so kann dies an zwei Dingen liegen.

  • Bower nicht installiert
  • Path-Variable nicht korrekt

Um Bower zu installieren, geben wir in der PowerShell einfach npm install bower ein, das entsprechende Node.js Projekt wird dann geladen und installiert.
Anschließend installieren wir noch Gulp via npm install gulp, eine weitere Abhängigkeit.

Sollte der Fehler immer noch erscheinen, so müssen wir die Path-Variable auf das Web Verzeichnis unserer Visual Studio Installation verweisen lassen:
PowerShell: $env:path = $env:path + ";C:\Program Files (x86)\Microsoft Visual Studio 14.0\Web\External"
Die VS Version entsprechend anpassen (hier: VS 2015).

Nun sollte das Projekt compilen und die lauffähige Version unter {Solutionpath}\src\{Projectpath}\bin\Release zu finden sein.

Diesen Ordner jetzt auf den Linux Server laden.

Um die Applikation jetzt ausführen zu können, benötigen wir die .NET Core Runtime auf dem Server (hier: Debian).
Um diese zu installieren, laden wir uns das Archiv (tar.gz) herunter und entpacken es:
Die Befehle dazu finden sich auf der entsprechenden Microsoft-Website:

Für v1.0.3:
sudo apt-get install curl libunwind8 gettext
curl -sSL -o dotnet.tar.gz https://go.microsoft.com/fwlink/?linkid=847105
sudo mkdir -p /opt/dotnet && sudo tar zxf dotnet.tar.gz -C /opt/dotnet
sudo ln -s /opt/dotnet/dotnet /usr/local/bin

Für v2.0.0:
deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-trusty-prod trusty main" > /etc/apt/sources.list.d/dotnetdev.list
apt-get update
apt-get install dotnet-sdk-2.0.0

Anschließend sollte man das Projekt starten können, zum Testen reicht hier: dotnet {Projektname}.dll im Pfad {App}\publish\.

Observer Pattern (Beobachter-Muster) in Java und C#

Wie benachrichtige ich mehrere Clients möglichst ohne Zeitverlust und ohne das Rad neu zu erfinden?
Eine weitverbreitete Möglichkeit ist das Observer Pattern (und erweitert, das Subscriber Pattern).
Es ist push-basiert, der Beobachtete gibt also allen Beobachtern Bescheid, was eine Benachrichtigung an die Beobachter in kürzester Zeit ermöglicht.

ObserverPattern

Was wir in Java dafür brauchen sind folgende Klassen und Interfaces, welche wir im Code referenzieren. In C# gibt es keine äquivalenten Klassen, wir müssen etwas selber schreiben.
[tabs]
[tab title=“Java“]

import java.util.Observable;
import java.util.Observer;

[/tab]
[/tabs]

Erstellen wir nun die Klasse, welche später beobachtet werden soll.
In C# nutzen wir EventHandler für eine asynchrone Benachrichtigung:

[tabs]
[tab title=“Java“]

public class Beobachteter extends Observable

[/tab]
[tab title=“C#“]

public class Observable
	{
		private bool changed;
		event EventHandler notifier;
		
		public void addObserver(Observer obs)
		{
			notifier += obs.update;
		}
	
		private void notifyObservers(dynamic obj)
		{
			if (this.changed) {
				if(notifier != null)
					 notifier(obj, EventArgs.Empty);
			}
			this.changed = false;
		}
		
		public void setChanged()
		{
			this.changed = true;
		}
		
		public void doIt(){
			setChanged();
			notifyObservers(32123);
		}
		
	}

[/tab]
[/tabs]

Wir lassen unseren Beobachteten die Klasse Observable im Namespace java.util.* erben. (Siehe oben)
Dadurch bekommt die Klasse u.A. folgende Methode vererbt, welche wir im Folgenden nutzen:
[tabs]
[tab title=“Java“]

public void AddObserver(){
       Beobachteter.addObserver(Observer o);
}

[/tab]
[tab title=“C#“]

 Durch die eigene Implementierung, stehen uns die identischen Methoden zur Verfügung. 

[/tab]
[/tabs]

Wie man auf dem Bild sieht, muss man nun die Beobachter (Observer) einem Beobachtbaren (Observable) hinzufügen, im Folgenden nenne ich diesen dann Beobachteten.
Wir erstellen verschiedene Beobachter und fügen diese nun einem Objekt der Klasse Beobachteter zu, welche – der Einfachheit halber – direkt in den Beobachtern erstellt wird.
Die Beobachter implementieren das Interface von java.util.Observer.

[tabs]
[tab title=“Java“]

public class Beobachter implements Observer{
    public Beobachter(){
         Beobachteter observable = new Beobachteter();
         observable.addObserver(this); //fügt dem Beobachteten mich als Beobachter hinzu.
    }
}

[/tab]
[tab title=“C#“]

 	public class Observer
	{
		string name;
		
		public Observer()
		{
		}
		
		public Observer(string name){
			this.name = name;
		}
	
		public void update(object message, EventArgs args){
			//Console.WriteLine(this + "received: "+message+obs);
		}
		
		public override string ToString()
		{
			return "{Observer,"+this.name+"}";
		} 
	}

[/tab]
[/tabs]

Nun haben wir einen oder mehrere Beobachter und einen Beobachteten.
Wie lassen wir die Beobachter nun wissen, wenn sich etwas ändert?
Die Klasse Observable bietet hier einige passende Methoden. Ich fokussiere mich hier allerdings auf folgende zwei:

[tabs]
[tab title=“Java“]

 setChanged(); 
 notifyObservers(message);

[/tab]
[tab title=“C#“]

setChanged();  
notifyObservers(message);

[/tab]
[/tabs]

Durch das dynamic keyword können wir dem EventHandler „notifier“ jegliche Daten als Parameter durchreichen, diese Implementierung vereinfacht das Handling der Daten extrem.

setChanged() ist von Nöten, da ein Beobachter auch nur benachrichtigt wird, wenn sich auch etwas geändert hat.
In Java ist es zudem sehr praktisch, dass man jede Art von Daten in der Methode

notifyObservers();

versenden kann. Mit dieser werden die Beobachter auch benachrichtigt.

Im Java Observer wird nun die Methode

update(Observable _ob, Object message)

, welche wir durch das Interface implementierten, ausgeführt.
In C# rufen der EventHandler die Observer-Methode

 update(dynamic message, EventArgs empty) 

aus.

Im Anhang befindet sich das C# Test Projekt, als .sln Solution:
ObserverPattern

[C#] Transponieren einer Matrix

Eine Matrix wird transponiert indem man ihre Spalten gegen ihre Zeilen tauscht.
Eine Matrix A der Ordnung 3×2 wird somit eine Matrix ATrans mit der Ordnung 2×3

Die allgemeine Definition lautet:
Für die Matrix A

Quelle: Wikipedia

ist die transponierte Matrix AT definiert als

Quelle: Wikipedia

Somit ergäbe sich für folgende Matrix A mit der Ordnung 2 Zeilen x 3 Spalten

Matrix_A_2R3C_123_456

die folgende transponierte Matrix AT mit der Ordnung 3 Zeilen x 2 Spalten

Matrix_ATrans_3R2C_14_25_36

Die Umsetzung des Transponierens lässt sich in C# mit folgendem Code bewerkstelligen,

int m, n;
Console.Write("Ordnung der Matrix angeben: \r\n");
Console.Write("Zeilen: ");
m = Convert.ToInt16(Console.ReadLine());
Console.Write("Spalten: ");
n = Convert.ToInt16(Console.ReadLine());
int[,] A = new int[m, n];
int[,] Atrans = new int[n, m];

Console.Write("Elemente der Matrix eingeben: ");
for (int i = 0; i < m; i++)
{
	for (int j = 0; j < n; j++)
	{
		A[i, j] = Convert.ToInt16(Console.ReadLine());
	}
}

Console.WriteLine("\nMatrix A: ");
for (int i = 0; i < m; i++)
{
	for (int j = 0; j < n; j++)
	{
		Console.Write(A[i, j] + "\t");
		Atrans[j, i] = A[i, j];
	}
	Console.WriteLine();
}

Console.WriteLine("Transponierte (ATrans) Matrix : ");
for (int i = 0; i < n; i++)
{
	for (int j = 0; j < m; j++)
	{
		Console.Write(Atrans[i, j] + "\t");
	}
	Console.WriteLine();
}
Console.Read();

Programmiertechnisch Windows Forms-Anwendungen erstellen

In C# gibt es verschiedene Techniken, GUIs zu erzeugen:

  • winforms
  • wpf

In Windows Forms (kurz meist winforms) ist das relativ einfach, da man hier komplett in C# arbeiten muss und nicht im „Backend-Code“ (C#) und im „Frontend-Code“ (XAML), wie in WPF.

Zu Beginn müssen wir zuerst einen Frame (hier: Form), der alle Controls (Textboxen, Labels, etc.) hält, erstellen und soweit vorbereiten.
Dazu legen wir eine Klasse MyForm an, welche von Form erbt:

 public partial class MyForm:Form 

Nun müssen wir noch die Attribute, wie bspw. Größe und Art des Rahmens anpassen. Dazu greifen wir auf die Properties der Klasse zu und setzen diese:

this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; //Setzt den Rahmen auf einen nicht vergrößerbaren, schmalen Rahmen ohne Min./Max-Boxen
this.ClientSize = new Size(Breite, Höhe);
this.Name = "Meine Form"; //Setzt den Namen der Form
this.ResumeLayout(false); //Unterdrückt Änderungen an der GUI
this.PerformLayout(); //Lässt Änderungen an der GUI zu und lädt diese 

Nun erstellen wir ein Objekt dieser Form:

 MyForm myFrmObj = new MyForm(); 

Um jetzt Controls (hier werden wir eine Textbox hinzufügen) zur Form hinzuzufügen, müssen wir ein Objekt dieses Controls erstellen:

 TextBox txtContent = new TextBox(); 

Anschließend greifen wir wieder auf die Attribute zu und ändern diese zu unseren Wünschen ab.

 txtContent.Size = new Size(Width, Height);
txtContent.Location = new Point(X,Y); 

Achtung die Position (Location) ist relativ, d.h. X = 0, Y = 0 sind im linken oberen Eck der Form, nicht des Bildschirms.

Nun fügen wir das Control zu unserer Form hinzu:

myFrmObj.Controls.Add(txtContent);

Natürlich kann man auch andere Attribute setzen, hier verweise ich auf das MSDN von Microsoft, in dem die Attribute von Klassen erklärt sind.
Aber auch IntelliSense hilft hier weiter.

Was jetzt noch wichtig ist, ist die Option

txtContent.AutoSize = true;

. Diese lässt Controls automatisch abhängig vom Content wachsen.

Um nun noch Events hinzuzufügen, erstellen wir die entsprechende Methode private void myMethod(object sender, EventArgs e) und fügen den Handler diesem Event zu:

this.txtContent.Click += new System.EventHandler(this.myMethod);

Zu guter letzt muss man die Controls beim Aufruf der Form initialisieren lassen, sodass man Zugriff während der Laufzeit bekommt.
Hierzu fügt man im Konstruktor der Form den Befehl InitializeComponent(); ein:

public MyForm()
{
     InitializeComponent();
}

Mehr Informationen zum Handlern und Events findet man im MSDN.

Generelle Infos findet man auf der Supportseite von Microsoft.

SQL Datenbanken in Windows Phone 8 nutzen

Man kann Daten in Projekten auf vielfältige Arten speichern, eine davon ist die Hinterlegung der Daten in einer Datenbank.
Dazu gibt es in Windows Phone mehrere Optionen. Das bekannteste und weit verbreitetste Datenbank-Datenbanksystem  ist SQLite.
Sie ist open-source und kann kostenfrei in Projekten eingesetzt werden. Zudem hat SQLite den Ruf sehr stabil und schnell zu laufen.

Wieso soll ich denn überhaupt eine Datenbank nutzen?

Das muss man nicht, aber ab einer bestimmten Komplexität der Daten/Informationen, ist eine Datenbank sinniger, als ein „plumpes“ Hinterlegen der Informationen in Dateien, bspw. über XML oder JSON.

Beginnen wir…
Grundsätzlich gibt es eine Vielzahl von Anbietern, welche SQLite Zugriffe bereitstellen, bspw. SQLite-Net, SQLitePCL, uvm…

Ich habe mich für SQLitePCL entschieden, da ich hier den „simplen“ SQL Syntax nutzen kann und nicht – wie bei SQLite-NET – über LINQ gehen muss.

Installieren wir uns nun also SQLitePCL. Hierbei handelt es sich um ein NuGet-Paket, welches wir in VisualStudio mit über den NuGet Paket Manager installieren können.
Hier gibt es zwei Wege:

  • GUI:
    Rechtsklick auf das Projekt -> NuGet-Pakete verwalten.
    Jetzt aktivieren wir auf der linken Seite die Option „Online“ und suchen nach SQLite-PCL. Hier installieren wir die „Portable Class Library for SQLite“.
    Sobald gefunden, installieren wir sie.
  • Kommandozeile (Nuget-Console):
    Wir öffnen die Kommandozeile und geben folgendes ein: Install-Package SQLitePCL

SQLitePCL_Nuget_Install

 

Danach installieren wir noch die benötigte Windows Library „SQLite for Windows Phone“, hierbei handelt es sich NICHT um ein NuGet-Paket sondern eine Windows SDK Extension:

SQLitePCL_SDK_Install.png

 

Nun installieren wir die Library und fügen eine/n Referenz/Verweis unserem Projekt hinzu.

Anschließend können wir endlich anfangen.

Ganz allgemein sieht ein SQLite-Befehl so aus – wie man die Verbindung (conn) aufbaut, erfahrt ihr gleich:

 
                using (var command = conn.Prepare("INSERT INTO MyTable(VALUE1) VALUES (?);"))
                {
                    command.Bind(1, MeinWert); //1-indexed parameter
                    command.Step(); //Absetzen des Befehls an die Datenbank
                }

Grundsätzlich muss man erst einmal eine Datenbank anlegen.

[spoiler title=“Datenverbindung erstellen“ style=“fancy“ anchor=“Datenbank“]

        public DatabaseConn()
        {
            SQLiteConnection conn = new SQLiteConnection("mydatabase.db");
        }

[/spoiler]
Dies legt die Datenbank in einer Datei namens „mydatabase.db“ ab oder öffnet diese.
Jetzt müssen wir – falls noch nicht geschehen – die Datenbank mit Tabellen füllen:
[spoiler title=“Tabellen-Erstellung“ style=“fancy“ anchor=“Tabelle“]

        public void LoadOrCreateDatabase()
        {
            string sql = @"CREATE TABLE IF NOT EXISTS 
                        MyTable
                        (Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                         Header VARCHAR(255),
                         Text VARCHAR(4096),
                         Position INTEGER);";

            using (var statement = conn.Prepare(sql))
            {
                statement.Step();
            }
        }

[/spoiler]

Um Daten aus der Datenbank abzurufen, muss man erstmal differenzieren.

  • Bekommt man einen Datensatz zurück?
  • Bekommt man mehrere Datensätze zurück?

Für den ersten Fall können wir einfach eine Abfrage absetzen und die Spalten des ersten Datensatzes durchlaufen:

[spoiler title=“Select-Einzeldatensatz“ style=“fancy“ anchor=“Einzeldatensatz“]

using (var command = conn.Prepare("SELECT Id, Header, Text, Position FROM MyTable WHERE Id = ?"))
                {
                    command.Bind(1, ID); //Fill the first (one-indexed!) parameter
                    if (command.Step() == SQLiteResult.ROW || command.Step() == SQLiteResult.DONE) // Überprüfe, ob Reihe existiert oder Step ausgeführt
                    {
                        MyTableObject obj= new MyTableObject ()
                        {
                            id = (long)command["Id"],
                            header = (string)command["Header"],
                            text = (string)command["Text"],
                            position = (long)command["Position"]
                        };
                        return obj;
                    }
                }

[/spoiler]

Für den zweiten Fall müssen wir durch alle Zeilen laufen und diese Daten in unsere Objekte laden:

[spoiler title=“Select mehrerer Datensätzen“ style=“fancy“ anchor=“Mehrfachdatensätze“]

                using (var command = conn.Prepare("SELECT Id, Header, Text, Position FROM MyTable "))
                {
                    while (SQLiteResult.ROW == command.Step()) //Solange noch eine Reihe verfügbar ist, mache weiter
                    {
                        MyTableObject obj= new MyTableObject ()
                        {
                            id = (long)command["Id"],
                            header = (string)command["Header"],
                            text = (string)command["Text"],
                            position = (long)command["Position"]
                        };
                        this.list.Add(obj);
                    }
                }

[/spoiler]

Und zu guter Letzt Befehle ohne Rückgabe. Diese sind sehr einfach und lassen sich

[spoiler title=“Befehle ohne Rückgabe“ style=“fancy“ anchor=“Ohne-Rückgabe“]

                using (var command = conn.Prepare("DELETE FROM MyTable WHERE Id = ?;"))
                {
                    command.Bind(1, "MEINWERT");
                    command.Step(); //Absetzen des Befehls
                }

[/spoiler]

Das war’s. Jetzt könnt ihr eure Datenbank füllen und mit ihr arbeiten.

Release des Valve Server Tools für Windows Phone 8

Gestern war es soweit, das Valve Server Tool ging im Windows Phone Store online und kann nun auf Windows Phone 8.1 Geräten installiert werden.

„Das Valve Server Tool ist eine App zur Abfrage von Server-Daten der HLDS (Half-Life-Dedicated Servers) und SRCDS (Source Dedicated Servers), bspw. Counter-Strike:Source Servern für Game-Server Admins.
Welche Map läuft gerade auf dem Server und wie viele Spieler befinden sich auf diesem? Welche Regeln sind für diesen gerade aktiv?

Features:
* Klassische Server Information über das Valve Query Protokoll (A2S_INFO)
* Informationen über die aktuellen Spieler auf dem Server (A2S_PLAYER)
* Informationen über die, auf dem Server gültigen Regeln (A2S_RULES)

App-Sprache: Englisch“

Zur Projektseite geht’s hier entlang.

Update:
– Fixed connection issues to servers

Screenshots:
Serverdata_wvga PlayerStats_wvga Mainmenu_wvga Rules_wvga

UML Beziehungen und ihre Umsetzung im Code

Wer Software plant und entwickelt wird unweigerlich mit der Unified Modeling Language (kurz UML) in Berührung kommen.
Ich werde in  diesem Blog-Eintrag vor allem auf das Klassendiagramm, genauer auf die Beziehungen der einzelnen Klassen eingehen.

Das Klassendiagramm:

UML_ClassdiagramDas Klassendiagramm zeigt  Klassen eines Namespaces an und deren jeweilige Abhängigkeiten. Es handelt sich um eine statische Darstellung, da lediglich die Attribute und Methoden, sowie die Verbindungen der Klassen untereinander dargestellt werden. Es wird nicht gezeigt, wie diese Verbindungen stattfinden.

Darstellungselemente:

Zur Realisierung eines Klassendiagramms stehen folgende Formen und Notationen zur Verfügung

Klasse

ClassEine Klasse wird mit als Rechteck dargestellt und enthält Attribute und Methoden (Operationen). Sollte es sich um eine abstrakte Klasse handeln, so wird der Klassennamen kursiv dargestellt

Zusätzlich werden die in der Klasse enthaltenen Attribute und Operationen (bspw. Methoden) mit deren Daten- oder ggf. Rückgabetypen und Sichtbarkeiten dargestellt.

Class_ExplanationClass_AttrOperation

Zur Darstellung der Sichtbarkeit von Attributen und Funktionen bietet UML folgende Zeichen:

  • + steht für public, also eine öffentliche Funktion/ein öffentliches Attribut
  • # steht für protected, also eine geschützte Funktion/ein geschütztes Attribut
  • – steht für eine private Funktion/ein privates Attribut

Wie Anfangs erwähnt, stellt das Klassendiagramm auch die Beziehungen der Klassen untereinander dar. Ich werde in diesem Beitrag auf folgende Beziehungen eingehen und zu der jeweiligen Beziehung ein Codebeispiel (C#) geben:

  • Vererbung
  • Binäre Assoziation (zwei Klassen sind beteiligt)
  • Aggregation
  • Komposition
Vererbung:

Eine Vererbung (engl.: Inheritance) ist auch als „Ist-Ein-Beziehung“ oder „Ist-Implementiert-Als-Beziehung“ bekannt.

Als Beispiel dienen hier die Klassen Employee und Manager
Da ein Manager auch ein Mitarbeiter (Employee) ist, erbt die Klasse Manager von Employee
Dadurch werden Variablen, Felder und Methoden, welche nicht private sind in die erbende Klasse übernommen.
Achtung: Konstruktoren werden nicht vererbt und müssen daher manuell angesprochen werden:

public Manager(string _name):base(_name){ ... }

Die Umsetzung in C# würde wie folgt aussehen (Bild):

[spoiler title=“Employee-Klasse“ style=“fancy“ anchor=“BeispielVererbung“]

namespace Vererbung
{
    class Employee
    {
        static int staticID = 1;

        protected int ID = 0;
        protected string Name = "";

        public Employee(string _name)
        {
            CreateEmployee(_name);
        }

        protected void CreateEmployee(string _name)
        {
            staticID++;
            ID = staticID;
            Name = _name;
        }

        public int getID()
        {
            return ID;
        }

        public string getName()
        {
            return Name;
        }
    }
}

[/spoiler]

[spoiler title=“Manager-Klasse“ style=“fancy“]

namespace Vererbung
{
    class Manager:Employee
    {
        static int staticManagerID = 1;

        int ManagerID;

        public Manager(string _name):base(_name)
        {
            CreateManager();
        }

        private void CreateManager()
        {
            staticManagerID++;
            ManagerID = staticManagerID;
        }

        public int getManagerID()
        {
            return ManagerID;
        }
    }
}

[/spoiler]

(Binäre) assoziation:

Eine Assoziation ist eine beliebige Beziehung zwischen den Objekten von Klassen. Hier werden wir nur die binären Assoziationen (2 Klassen sind beteiligt) behandeln.

Dies ist eine gerichtete Assoziation, das bedeutet, dass Klasse 1 die Klasse 2 kennt, dagegen Klasse 2 nichts von Klasse 1 weiß. In der Beschreibung wird Beschrieben auf welche Weise die Objekte in Verbindung stehen

Links sehen wir eine bidirektionale binäre Assoziation, d.h. die Firma kennt seinen Mitarbeiter und der Mitarbeiter seine Firma.

Die Form der Assoziation sähe in der Programmierung wie folgt aus (Man beachte die Kommentierung):

[spoiler title=“Bidirektionale binäre Assoziation“ style=“fancy“ anchor=“BeispielAssoziation“]

public class Firma 
{ 
  public Mitarbeiter mitarbeiter; 
}

 public class Mitarbeiter 
{ 
  public Firma firma; 
}

public class Verzeichnis
{ 
  Mitarbeiter Mustermann = new Mitarbeiter(); 
  Firma  Musterfirma  = new Firma(); 
  Mustermann.firma  = Musterfirma;
  Musterfirma.mitarbeiter = Mustermann; 
  // Man beachte, dass die Objekte von Firma und Mitarbeiter in einer
  // weitern Klasse liegen und somit nicht voneinander abhängig sind
  // und somit weiterleben, auch wenn eines der Objekte zerstört wird
}

[/spoiler]

Aggregation:

Bei der Aggregation handelt es sich um eine spezielle Form der Assoziation. Sie ist auch als „Ganzes-Teil-Beziehung“ oder „Hat-Ein-Beziehung“ bekannt.
Darunter versteht man ein Objekt (Aggregatobjekt), welches aus verschiedenen Einzelteilen (Objekten) besteht, wobei diese nicht existenzabhängig sind (d.h. sie leben weiter, auch wenn das Aggregatobjekt zerstört wird.

Eine Beispiel für eine Aggregation wäre das Auto (als Ganzes) und ein Rad, als Teil.

[tabs]
[tab title=“Allgmeines Beispiel“]

public class Aggregatklasse
{
    ExistenzUnabhängigeKlasse objKlasse;
    public void doSomething(ExistenzUnabhängigeKlasse obj)
    {
      //Das Objekt der Klasse ExistenzUnabhängigeKlasse wird als Parameter übergeben 
      objKlasse = obj; 
	  // und ist somit noch existent wenn die Aggregatklasse zerstört wird
    }
}

[/tab]
[tab title=“Auto-Rad-Beispiel“]Wird nachgereicht…

[/tab]
[/tabs]

Komposition:

CompositionBei der Komposition handelt es sich um eine strenge Form der Aggregation. Das Kompositionsobjekt besteht, wie bei der Aggregation auch, aus mehreren Einzelteilen (Komponentenobjekte), welche jedoch im Gegensatz zur Aggregation mit der Zerstörung des Ganzen (Kompositionsobjekt) zerstört werden (Existenzabhängigkeit).
Damit ist auch die Kardinalität/Multiplizität auf Seite des Kompositionsobjekts immer 1. Das bedeutet, dass eine Komponente nur Teil eines Objekts sein kann. Somit wird sichergestellt, dass die Lebensdauer aller Objekte gleich ist. Die Kardinalität auf der Seite der Komponenten kann jedoch variable sein

Beispiel:
Ein Kunde einer Bank hat mindestens eine Anschrift und keine oder mehrere hinterlegte Bankverbindungen (kann per Rechnung zahlen).

CompositionExampleStirbt der Kunden, so wohnt er logischerweise nicht mehr und ist ebenso wenig ein Kunde der Bank. Seine Anschrift(en), sowie seine Bankverbindung(en) werden also zerstört

Die Umsetzung im Code würde wie folgt aussehen:

[spoiler title=“Codebeispiel Komposition“ style=“fancy“ anchor=“BeispielKomposition“]

public class CompositionClass
{
    SomeUtilityClass objSC = new SomeUtilityClass(); //Das Objekt der Klasse
    // existiert lediglich in der CompositionClass und wird mit dem Objekt der
    // CompositionClass vernichtet

    public void doSomething()
    {
        objSC.someMethod();
    }
}

[/spoiler]

[C#/.NET] Eigenen OpenFileDialog erstellen

Zwar hat das .NET-Framework mit den von der CommonDialog-Klassen ebernden Dialog-Klassen (dazu gehören OpenFileDialog, FolderBrowserDialog) schon Dialoge, welche die Funktion bieten Daten zu öffnen, zu speichern oder das Verzeichnis zu wählen. Diese sind aber leider nicht erweiterbar und daher nicht für sämtliche Zwecke zu gebrauchen.

In diesem Beitrag werde ich deshalb eine Möglichkeit aufzeigen, einen eigenen Dialog zu erstellen für verschiedene Zwecke zu erstellen.

Einfacher Dialog zum Öffnen von Dateien

Zum Erstellen eines einfachen OpenFileDialogs benötigen wir erstmal eine Form. In der Form platzieren wir ein TreeView, sowie zwei Buttons. Zusätzlich benötigen wir eine ImageList, welche die eigentlichen Icons der Dateien und Verzeichnisse zwischenspeichert und aus welcher wir die benötigten Icons beziehen werden.

Nachdem wir die benötigten Controls hinzugefügt haben, benötigen wir eine Methode, die uns alle Laufwerke in die TreeView lädt. Diese bilden nämlich jeweils den Stammknotenpunkt (RootTreeNode).

Die Methode sieht wir folgt aus und soll bei dem Aufruf der Form durchlaufen werden:

private void GetAllDrives()
{
	DriveInfo[] drives = DriveInfo.GetDrives();
	foreach (var drive in drives)
	{
		TreeNode rootTreeNode = new TreeNode();
		rootTreeNode.Text = drive.Name;
		rootTreeNode.Tag = drive.Name;
		rootTreeNode.ImageIndex = GetIconOfFile_Folder(drive.Name);
		rootTreeNode.SelectedImageIndex = rootTreeNode.ImageIndex;
		rootTreeNode.Nodes.Add(" "); //Placeholder to enable expanding (+)
		FolderAndFiles_treeView.Nodes.Add(rootTreeNode);
	}
}

Die Methode GetIconOfFile_Folder bezieht das Icon des Elements, wie es der Windows Explorer auch anzeigt.
Zum Beziehen wird der Namespace System.Runtime.InteropServices benötigt. Diesen binden wir per

using System.Runtime.InteropServices;

ein.
Folgender Code wird zum Beziehen der Icons benötigt:

[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
	public IntPtr hIcon;
	public IntPtr iIcon;
	public uint dwAttributes;
	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
	public string szDisplayName;
	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
	public string szTypeName;
};

class Win32
{
	public const uint SHGFI_ICON = 0x100;
	public const uint SHGFI_LARGEICON = 0x0;    // 'Large icon
	public const uint SHGFI_SMALLICON = 0x1;    // 'Small icon

	[DllImport("shell32.dll")]
	public static extern IntPtr SHGetFileInfo(string pszPath,
								uint dwFileAttributes,
								ref SHFILEINFO psfi,
								uint cbSizeFileInfo,
								uint uFlags);
}

private int GetIconOfFile_Folder(string Path)
{
	IntPtr hImgSmall;    //the handle to the system image list
	SHFILEINFO shinfo = new SHFILEINFO();

	hImgSmall = Win32.SHGetFileInfo(Path, 0, ref shinfo,
								   (uint)Marshal.SizeOf(shinfo),
									Win32.SHGFI_ICON |
									Win32.SHGFI_SMALLICON);

	System.Drawing.Icon myIcon =
		   System.Drawing.Icon.FromHandle(shinfo.hIcon);

	FolderAndFiles_imageList.Images.Add(myIcon);

	return FolderAndFiles_imageList.Images.Count - 1; //Ab 0 (zero) wird angefangen, somit ist die Gesamtzahl n+1
}

Bisher werden nur die aufklappbaren Laufwerke angezeigt. Sie besitzen jedoch noch keine weiteren Knoten. Diese füllen wir indem wir einen EventHandler für das BeforeExpand-Event mittels

FolderAndFiles_treeView.BeforeExpand += FolderAndFiles_treeView_BeforeExpand;

erstellen.

Die dazugehörige Methode FolderAndFiles_treeView_BeforeExpand sieht wie folgt aus:

private void FolderAndFiles_treeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
	e.Node.Nodes.Clear();
	GetFilesAndFolder(e.Node, (string)e.Node.Tag);
}

Das Event ruft also die Methode GetFilesAndFolder auf, welche zuerst die unter dem erweiterten Knoten liegenden Knoten löscht und danach die Verzeichnisse und Dateien bezieht. Diese Methode benötigt zum einen den erweiterten Knoten, sowie das Verzeichnis aus welchem die Ordner und Dateien bezogen werden sollen (dies wurde im Tag gespeichert) als Parameter.
Die Methode selbst sieht wie folgt aus:

private void GetFilesAndFolder(TreeNode tn, string Path)
{
	try
	{
		string[] Directories = Directory.GetDirectories(Path);
		string[] Files = Directory.GetFiles(Path);

		foreach (string dir in Directories)
		{
			TreeNode dirTreeNode = new TreeNode();
			dirTreeNode.Tag = dir;
			dirTreeNode.Text = new DirectoryInfo(dir).Name;
			dirTreeNode.ImageIndex = GetIconOfFile_Folder(dir);
			dirTreeNode.SelectedImageIndex = dirTreeNode.ImageIndex;
			dirTreeNode.Nodes.Add(" ");
			tn.Nodes.Add(dirTreeNode);
		}

		foreach (string file in Files)
		{
			TreeNode fileTreeNode = new TreeNode();
			fileTreeNode.Tag = file;
			fileTreeNode.Text = new FileInfo(file).Name;
			fileTreeNode.ImageIndex = GetIconOfFile_Folder(file);
			fileTreeNode.SelectedImageIndex = fileTreeNode.ImageIndex;
			tn.Nodes.Add(fileTreeNode);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
	}
}

Den schwierigsten Teil haben wir jetzt hinter uns, die Dateien und Verzeichnisse werden bezogen und in der TreeView angezeigt und die jeweiligen Icons bezogen.
Jetzt müssen wir lediglich noch die zwei Buttons belegen.
Ich habe dies so gelöst:
Abbrechen

private void Cancel_button_Click(object sender, EventArgs e)
{
	this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
	this.Close();
}

Annehmen

private void Accept_button_Click(object sender, EventArgs e)
{
	string filePath = (string)FolderAndFiles_treeView.SelectedNode.Tag;
	if (CheckIfPathIsFile(filePath) == true) // Sollte es sich um eine Datei handeln
	{
		this.FilePath = filePath; //Bei FilePath handelt es sich um ein public Property
		this.DialogResult = System.Windows.Forms.DialogResult.OK;
		this.Close();
	}
	else //Sollte es sich um ein Verzeichnis handeln
	{
		FolderAndFiles_treeView.SelectedNode.Expand(); //erweitere den aktuell gewählten Knoten
	}
}

In der Methode des AcceptButton-ClickEvents wird auch geprüft, ob es sich bei dem gewählten Element um eine Datei oder ein Verzeichnis handelt. Dies geschieht mit folgender Methode:

private bool CheckIfPathIsFile(string Path)
{
	FileAttributes attr = File.GetAttributes(Path);
	if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
		return false;
	else
		return true;
}

Es wird also Wahr zurückgegeben, wenn es sich um eine Datei handelt.

Zu guter Letzt müssen wir nur noch den FileDialog aufrufen, dies geschieht mit folgender Methode:

private void OpenSimpleFileDialog()
{
	SimpleOpenFileDialog simpleOpenFileDia = new SimpleOpenFileDialog();
	if(simpleOpenFileDia.ShowDialog() == System.Windows.Forms.DialogResult.OK)
		MessageBox.Show(simpleOpenFileDia.FilePath);
}

Das Ergebnis sieht wie folgt aus:

2013-12-02 14_43_34-SimpleOpenFileDialog

© 2018 Abou Chleih. Alle Rechte vorbehalten.

Thema von Anders Norén.