Freitag, 29. Juni 2012

Juni 2012 / Objektorientiertes PHP & Datenbankzugriff

In einem Früheren Blogeintrag vom Oktober 2010 habe ich bereits einmal über PHP und den Datenbankzugriff geschrieben.
Hier werde ich das ganze jedoch objektorientiert erläutern.
Wie auch schon damals werde ich ab und zu Bilder verwenden, da der Blog HTML interpretieren will.

Datenbank


Als Datenbank wird die MySQL-Datenbank von XAMPP verwendet. Mit phpMyAdmin hat man auch gleich ein grafisches Userinterface und kann seine Datenbanken und Tabellen sehr einfach erstellen.
Wie die Beziehungen genau funktionieren, habe ich aus Zeitgründen nicht angeschaut, sondern in PHP Methoden geschrieben, in denen die Daten aus den dazugehörigen Tabellen automatisch geholt werden.

Objektorientiertes PHP


Mit PHP kann objektorientiert programmiert werden und seit PHP 5 wird es sogar richtig gut unterstützt.
Um eine Klasse verwenden zu können, muss natürlich zuerst eine erstellt werden.
Erstellen wir einmal eine Klasse namens Person:
Dazu wird eine Datei Namens Person.Class.php erstellt und folgender Inhalt eingefügt:

Class Person{
}

Wie in jeder anderen Objektorientierten Sprache, werden Objekte ebenfalls durch Variablen referenziert. Zuerst muss sie jedoch ins aktuelle PHP-File eingebunden werden:

include("Person.Class.php");

Der Parameter von include muss dabei dem Dateinamen bzw. Dateipfad der einzubindenden .php-Datei darstellen. So funktioniert es, wenn sich die beiden Dateien im selben Ordner befinden.

Ein neues Objekt mit der Klasse Person kann nun ganz einfach erstellt werden.

$person = new Person();

Eine Klasse ohne Inhalt bringt uns aber natürlich nichts. Daher können nun Klassenvariablen, Methoden und einen Konstruktor definiert werden, genau gleich wie z.B. in C#:

Variable:
public $vorname; 
public $nachname;
public $alter;

Konstruktor:
public function __construct($vorname, $nachname, $alter)
{
    $this->vorhname = $vorname
    $this->nachname = $nachname
    $this->alter = $alter
}

Methode:




Die Variablen sind wohl selbsterklärend.
Dem Konstruktor müssen nun 3 Werte mitgegeben werden.
Im Konstruktor werden diese dann an die Klassenvariablen $vorname, $nachname und $alter übergeben:
Mit $this rufe ich, wie in C# auch, das Objekt selbst auf, also die aktuelle Instanz der Klasse Person. Dieser "Pfeil" "->" ist mit dem "." in C# zu vergleichen. Damit kann man Methoden bzw. Variablen auf- bzw. abrufen.
Eine neue Instanz der Klasse Person müsste nun also so erstellt werden:

$person = new Person("Till", "Schnegg", 18);

Die Methode ist gibt einfach eine Tabelle mit den Werten des Objekts aus.
das würde dann ca. so aussehen:

<><>
<><>
<><>
Name:Till Schnegg
Alter:18


Um diese Methode aber auch sinnvoll nützen zu können, muss man zuerst eine Lösungsarchitektur erarbeiten. Es ist jedoch zu viel Code, um euch hier ein ganzes Beispiel zu zeigen.
Wichtig zu wissen ist jedoch, dass immer die index.php Datei gelesen wird und daher auch dessen Konstruktor als erstes ausgeführt. Dort kann man dann beginnen mit den nötigen Methodenaufrufen, um die Seite generieren zu lassen.

Objektorientierter Datenbankzugriff


Um auf eine objektorientiert auf eine Datenbank zuzugreifen, muss die mysqli-Klasse verwendet werden:

$db = @new mysqli('localhost', 'root', '', 'personenerfassung');

So greife ich auf die Datenbank "personenerfassung" auf dem localhost zu, wenn ich kein Passwort gesetzt habe.
Um zu überprüfen, ob die Verbindung erfolgreich aufgebaut wurde, kann man folgende Abfrage einfügen:

if(mysqli_connect_errno())
{
    die('Fehler beim Aufbauen der Verbindung zur Datenbank: ' . mysqli_connect_error() . ' (' . mysqli_connect_errno() . ')');
}

Konnte die Verbindung hergestellt werden, kann nun eine SQL-Abfrage gemacht werden:

$result = $db->query("SELECT * FROM `person`")

Und anschliessend überprüft, ob das query korrekt ausgeführt wurde, wenn ja kann auf die Daten zugegriffen werden:

if (!$result)
{
    echo ("Fehler");
}
else
{
    while($row = $result->fetch_assoc())
    {
        echo($row["name"]);
    }
}

Mit der Zeile while($row = $result->fetch_assoc()) wird durch alle aus der Datenbank geholten, in der Variable $result gespeicherten Einträge rotiert.
Das ganze Datenbank-Handling kann natürlich wieder in eine eigene Klasse geschrieben werden, wenn wir es schon objektorientiert machen wollen.

Mittwoch, 30. Mai 2012

Mai 2012 / Joomla

Nutzen eines CMS

Ein CMS (Content-Management-System) ist eine Software um den Inhalt einer Medienform, meistens einer Website zu gestalten. Im falle einer Website ist der Vorteil eines CMS, dass der Ersteller keinerlei kenntnisse von HTML oder PHP haben muss. Er kann den Inhalt einfach zusammenklicken, zumindest fast.

Nachteil für HTML-Kenner

Da ich mich selbst mit HTML, PHP und JS zumindest ein bischen auskenne, ist es nicht ganz einfach ohne Grossen Zeitaufwand den Überblick im Code zu behalten.
Möchte man z.B. eine kleine Funktion einbauen, muss man zuerst einmal herausfinden, wie das ganze Datei-System dahinter aufgebaut ist, wie die eigenen Funktionen genutzt werden können usw.
Weitere Funktionen können auch durch vorhandene Plugins integriert werden.
Den Code innerhalb dieser Plugins ist zwar schnell gefunden, vorhandenen Code zu verstehen ist für mich jedoch schwieriger als meine eigene kleine Funktion zu schreiben.

Verwendung von Joomla / Unserer Website

Joomla ist genau ein solches CMS für Websites, welches wir für unsere neue Website verwenden.
Es ist ziemlich einfach aufgebaut. Sobald man sich eingeloggt hat findet man eine Funktions-Übersicht mit passenden Icons:


Diese kann man direkt nutzen um die gewünschte Aktion durchzuführen.
Es ist jedoch auch eine Normale Navigation, welche ich bevorzuge vorhanden:


An unserer Website müssen grundsätzlich bloss die 2 Menüs oder der Inhalt geändert werden.

Menüs:

Um die Menüs zu bearbeiten, muss über den Navigationspunkt "Menüs" das zu bearbeitende Menu ausgewählt werden:



Das Main Menu ist die Haupt-Navigation unserer Website, das Head Menu befindet sich oben rechts und hat bloss 2 Einträge, wird also selten geändert.

Nach dem Auswählen des Menüs, werden alle Menu-Punkte in einer Art Baum-Struktur dargestellt:


"Produkte" ist ein Hauptmenü-Punkt, befindet sich also auf der obersten Ebene. Als Menütyp ist Menüalias definiert, was bedeutet, dass dieser Menüpunkt keinen eigenen Inhalt anzeigt, sondern auf einen anderen Menüpunkt, der einen Inhalt(Beitrag) anzeigt verlinkt.
"SPS-Steuerungen" ist ein Untermenu-Punkt, der ebenfalls als Menüalias definiert ist.
"Siemens" und "Saia" sind beides Untermenu-Punkte von "SPS-Steuerungen" und sind als Beiträge -> einzelner Beitrag definiert. Das bedeutet, dass sie einen Beitrag als Inhalt anzeigen.
Alle weiteren Punkte sind wie "SPS-Steuerungen" ein Untermenu-Punkt. Sie sind jedoch als Beiträge definiert.

Um einen neuen Menupunkt einzufügen muss auf den Neu-Button geklickt werden:

Anschliessend muss ein Menutyp gewählt und der Menutitel angegeben werden:


Ebenfalls muss der Übergeordnete Menu-Punkt ausgewählt werden:


So wird definiert, wo sich der Menupunkt befindet, ob es ein Hauptmenu- oder Untermenu-Punkt ist.
Um nun einen Beitrag anzuzeigen, muss als Menutyp "Einzelner Beitrag" ausgewählt werden. Dann erscheint auf der rechten Seite eine Option "Beitrag wählen", wo man den anzuzeigenden Beitrag auswählen kann:


Möchte man jedoch nichts anzeigen, sondern bloss auf einen anderen Menupunkt verweisen, muss man als Menutyp "Menüalias" auswählen. Dann erscheint wieder an derselben stelle die Option Menüeintrag, wo der anzuzeigende Menupunkt ausgewählt werden kann:


Beiträge:

Um die vorhandenen Beiträge zu bearbeiten oder einen neuen zu erstellen, muss über den Navigationspunkt "Inhalt" Beiträge bzw. neuer Beitrag ausgewählt werden:



Will man einen vorhandenen Beitrag bearbeiten, muss man diesen bloss aus der angezeigten Liste auswählen:


Will man einen neuen Beitrag erstellen, muss man nur einen Titel und einen Alias vergeben. Bei uns wird der Titel normalerweise Gross und der Alias genau gleich nur klein geschrieben:


Anschliessend kann man den Beitrag bearbeiten bzw. komplett neu erstellen.
Dazu bietet Joomla standardmässig einen Editor namens TinyMCE an. Ich habe jedoch einen etwas benutzerfreundlicheren Editor namens JCE installiert. So sieht die Maske aus:


Das Ganze ist ähnlich aufgebaut wie z.B. im Word. Man kann den Text ausrichten, Schriftart, -grösse & -farbe einstellen, Tabellen erstellen, Bilder einfügen usw.

Für HTML- und CSS-Kenner gibt es natürlich die Code-Ansicht, in welcher der Code für den aktuellen Beitrag angezeigt wird. Die Umschaltung findet man direkt oberhalb der JCE-Maske bei [Toggle Editor]:


Um die Änderungen zu speichern, muss man oben rechts im Bild auf Speichern bzw. auf Speichern & Schliessen klicken:

Freitag, 20. April 2012

April 2012 / C# Code hinter Reports

C# Report-Möglichkeiten

Aktuell Entwickle ich ein Programm Namens iVBA, welches über eine Access-Datenbank Benutzer, Logins und weiteres speichert.
Aus diesen Benutzer- und Login-Daten müssen Reports erstellt werden können. In C# gibt es verschiedene Möglichkeiten, Reports zu erstellen und anzuzeigen.
- Es gibt die normalen "Reports" welche vom Microsoft Report Viewer dargestellt werden können
- Es gibt die sogenannten "CrystalReports" welche vom CrystalReportViewer dargestellt werden können.
Um die Vor- und Nachteile der beiden Varianten habe ich mich nicht gekümmert.
Ich habe mich für die Microsoft-Variante entschieden und mit dem MicrosoftReportViewer gearbeitet.

Da ich meine Datenbank-Verbindung und all meine Abfragen, wie in früheren Posts von mir beschrieben, per Code definiert habe, wollte ich es auch diesmal genau gleich machen.
Ich fand zuerst bloss Anleitungen, wie ich mir mit Hilfe von Visual Studio ein Dataset erstellen und es dem Report zuweisen kann. Dazu hätte ich jedoch eine neue Verbindung zur Datenbank aufbauen müssen, was mir gar nicht gefiel.

Report-Objekte

Anstatt also direkt die Datenbank als Quelle für meine Reports zu nehmen, erstellte ich meine eigenen Klassen, welche die selben Eigenschaften wie die Datenbank-Tabellen enthielten und fügte diese als Datenquellen hinzu.

Klassen erstellen:

Wir gehen nun davon aus, dass wir eine Tabelle namens "User" haben, welche folgende Eigenschaften hat:
  • id
  • vorname
  • nachname
  • email
  • inaktiv
  • created
Zuerst erstellte ich mir eine neue Klasse mit dem Namen ReportObjects. Innerhalb dieser Klasse erstellte ich dann für jede nötige Tabelle eine Daten-Klasse und eine Klasse, die diese als Liste enthält.
In unserem Fall also eine User-Daten-Klasse und eine User-Listen-Klasse, den Inhalt werde ich später erläutern:

public class ReportObjects
{
    public class User{}
    public class UserList{}
}

Die Klasse User soll nun also die Tabelle darstellen, bzw. die Datensätze darin. Dazu werden in der Klasse folgende Eigenschaften definiert:

private int id;
private string vorname;
private string nachname;
private string email;
private bool inaktiv;
private DateTime created;

Jede dieser Eigenschaften muss natürlich von aussen erreichbar sein, zumindest um diese lesen zu können:

public int Id
{
    get { return id; }
}

Dasselbe wird natürlich für jede Eigenschaft wiederholt, ich denke das kennt ihr.
Um die Eigenschaften zu setzten definieren wir einfach einen Konstruktor:

Public User (int id, string vorname, string nachname, string email, bool inaktiv, DateTime created)
{
    this.id = id;
    this.vorname = vorname;
    this.nachname = nachname;
    this.email = email;
    this.inaktiv = inaktiv;
    this.created = created;
}

Nun ist unsere User-Daten-Klasse also fertig.
Also erstellen wir die User-Listen-Klasse. Die Realisierung dieser Klasse ist eigentlich den Bedürfnissen anzupassen. Hier jedoch meine Variante.
Zuerst erhält die klasse folgende Klassen-Variablen:

private List userList;
private DataTable userSource;

Die userList enthält später alle User, die userSource enthält die Datenquelle für die User, also Datensätze aus der User-Tabelle unserer Datenbank.
Zuerst habe ich 2 Konstruktoren definiert. Der 1. ist dafür da, eine Liste zu erstellen, ohne die userSource bereits zu definieren, der 2. um sie bereits zu übergeben.

public UserList()
{
    userList = new List();
}

public UserList (DataTable userSource)
{
    this.userSource = userSource;
    userList = new List();
    AddUsersToList();                        <-- Wird noch erklärt/definiert
}

Die Methode AddUsersToList() fügt alle Datensätze in der userSource der UserList hinzu und sieht so aus:

private void AddUsersToList()
{
    userList.Clear(); <-- Löscht alle User-Objekte in der UserList
    foreach (DataRow zeile in userSource.Rows)
    {
        userList.Add( new User (
            (int)zeile[0],
            zeile[1].ToString(),
            zeile[2].ToString(),
            zeile[3].ToString(),
            (bool)zeile[4],
            (DateTime)zeile[5]));
    }
}

Nun wären also alle User in der Liste. Hätte ich jedoch den 1. Konstruktor verwendet, wäre noch gar nichts als userSource definiert und die AddUseresToList-Methode noch gar nicht ausgeführt.
Deshalb erstellen wir noch die Methode SetUserSource() welche die userSource setzt:

public void SetUserSource(DataTable userSource)
{
    this.userSource = userSource;
    AddUsersToList();   <-- Fügt sofort alle User der UserList hinzu
}

Sobald wir also entweder den 1. Konstruktor und anschliessend SetUserSource() oder den 2. Konstruktor aufgerufen haben, ist die Liste gefüllt. Um die Liste auch holen zu können müssen wir noch die GetUser-Methode definieren:

public List GetUser()
{
    return userList;
}

Damit kann also auf die Liste in der Klasse zugegriffen werden.

Datenquellen hinzufügen

Kompilieren Sie zuerst ihr Projekt indem sie über "Erstellen" auf "Projektmappe erstellen" klicken.
Navigieren Sie Dann zu: Daten --> Neue Datenquelle hinzufügen --> Objekt --> Weiter
Nun erhalten Sie eine Baum-Struktur. Öffnen Sie ihren eigenen Namespace, diesem Untergeordnet einen Weiteren Knoten mit ihre Namespace . Nun sollten Sie den Knoten ReportObjects sehen. Öffnen Sie diesen und wählen sie User aus.

Bericht erstellen & Anzeigen

Sie brauchen natürlich auch noch einen Report und einen "MicrosoftReportViewer" um diesen anzuzeigen.
Das Ganze kann natürlich immer etwas anders realisiert werden, jedoch zeige ich hier eine Möglichkeit auf.
Erstellen Sie dazu am besten einen Unterordner und fügen sie ihm einen Bericht hinzu(Endung .rdlc nicht rpt).
Diesen können Sie nun so designen, wie Sie möchten.
Um nun die Daten aus unserer User-Datenquelle anzeigen zu können, müssen Sie bei den Datenquellen (falls Sie diese nicht sehen: Daten/Datenquellen anzeigen) die gewünschte Eigenschaft auswählen und per Drag & Drop auf das Formular ziehen.
Fügen Sie die Felder einfach so ein, wird jedoch nur die Eigenschaft des 1. Objekts in der Datenquelle angezeigt.
Möchten Sie eine Auflistung aller Objekte in der Datenquelle, können Sie über die Toolbox eine Liste hinzufügen und die Eigenschaften per Drag & Drop in die Liste ziehen. Dann wird für jedes Objekt ein Listen-Eintrag generiert.

Sobald Sie die 1. Eigenschaft auf das Formular gezogen haben, wird im Hintergrund ein DataSet für den Bericht definiert. Über diesen werden die Daten beim anzeigen geholt. Das ist sehr wichtig und werde ich später noch genauer erläutern. Wenn ich also vom DataSet des Berichts schreibe, ist genau das gemeint.

Damit Sie den Report anzeigen können, müssen Sie eine neue Form hinzufügen. Der ReportViewer wird später per Code hinzugefügt.

Öffnen Sie nun die Form in der Code-Ansicht.
Fügen sie als KlassenVariable eine UserList hinzu:

private ReportObjects.UserList userlist = new ReportObjects.UserList();

Im Konstruktor fügen sie nun einen MicrosoftReportViewer hinzu, wählen den Report aus der angezeigt werden soll und füllen das DataSet des ausgewählten Reports.

public form ()
{
    InitializeComponent();

    //Fügt den ReportViewer hinzu.
    ReportViewer reportViewer = new ReportViewer();
    reportViewer.ProcessingMode = ProcessingMode.Local;
    reportViewer.Dock = DockStyle.Fill;
    this.Controls.Add(reportViewer);

    //Setzt den zu ladenden Report
    reportViewer.LocalReport.ReportEmbeddedResource =
    "Namespace.Reports.reportname.rdlc";

    //Füllen Sie spätestens hier die UserList mit Daten aus der
    //Datenbank. Bei mir z.B. so:
    userList.SetUserSource(dbHandler.GetNewDataTable(
    "SELECT * FROM [User]"))

    //Hier wird die Source für das DataSet gesetzt.
    //Der Name des ReportDataSource(1. Parameter) ist entscheidend!
    //Er besteht immer aus dem Namespace und
    //verbunden mit einem _ dem Namen der Datenquelle.
    //In unserem Fall "Namespace_User"
    //Der 2. Parameter ist die DataSource.
    //In unsere, Fall die Liste im userList-Objekt
    reportViewer.LocalReport.DataSources.Add(
    new ReportDataSource("Namespace_User", userList.GetUser()));

    //Zeigt den Report an
    reportViewer.RefreshReport();
}

In meinem Fall habe ich das Ganze noch mit Parametern ausgestattet, in dem ich den Report übergebe, welcher geladen werden sollte, sowie meine Datenbank-Verbindung. Natürlich braucht es dann noch andere Auswertungen, welchen DataSet nun mit welchen Daten gefüllt werden muss, aber alles kann ich hier auch nicht beschreiben.
Die Rapporte kann man über die Where-Klausel der SQL-Abfrage Filtern.
Nun müssen Sie nur noch die Form aufrufen und der Report, wie auch immer Sie definieren wollen, welcher es nun sein sollte, wird angezeigt.

Parameter

Es kann sein, dass Sie in ihrem Report einige Parameter verwenden. Diese können Sie in der Eigenschaft ReportParameters des Reports definieren.
Diese werden in diesem Fall natürlich auch per Code, spätestens vor dem reportViewer.RefreshReport() gesetzt.
Wir gehen davon aus, dass der Report einen Integer und einen Bool als Parameter braucht.
Um diese zu Übergeben müssen Sie zuerst einen Array von ReportParameter deklarieren:

ReportParameter[] parameter = new ReportParameter[2];

Nun müssen Sie die einzelnen ReportParameter ins Array einfügen:

parameter[0] = new ReportParameter("ParameterName", "53", false);
parameter[1] = new ReportParameter("ParameterName", "True", false);

Äusserst speziell finde ich, dass Parameter bloss als strings übergeben werden können. Deshalb wird der Integer und der Bool hier auch als string übergeben.
Um die Parameter dann doch als Integer bzw. Bool verwenden zu können, müssen Sie im Bericht bei Verwendung des Wertes einfach mit der VB-Syntax einen Ausdruck definieren:
String in Integer: CInt(Parameters!Parametername.Value)
String in Bool: CBool(Parameters!Parametername.Value)
Alle Umwandlungen und weitere Funktionen findet man im Ausdrucks-Generator.

Schlussendlich müssen die Parameter noch dem Report zugewiesen werden:

reportViewer.LocalReport.SetParameters(parameter);

Subreports

Subreports sind etwas sehr nützliches und wurden von mir auch verwendet. Wie ich oben bereits geschrieben habe speichern wir hier Logins. Ein Benutzer hat mehrere Logins auf verschiedene Applikationen.
Das heisst ich haben einen User der hat z.B. mehrere Logins auf eines unserer Produkte.
Möchte ich nun für jeden Benutzer den ich habe alle Logins auf dieses Produkt auflisten, erstelle ich einfach genau gleich wie beim Hauptreport in der Klasse ReportObjects eine Daten-Klasse und eine Listen-Klasse für dieses Login.
Ebenfalls füge ich die Datenquelle hinzu und designe einen neuen Report. Diesen werde ich jedoch nun als Subreport verwenden. Als Parameter werde ich im Unterbericht natürlich die UserID definieren, damit ich auch nur die Logins des aktuellen Users ausgebe.
Dazu werde ich aus der Toolbox einen "Unterbericht"-Steuerelement in den Hauptreport ziehen. Natürlich platziere ich dieses wieder in der Liste, da ich für jeden User einen Sub-Report mit dessen Logins ausgeben will.
Mit einem Rechtsklick auf dieses Steuerelement kann ich meinen erstellten SubReport auswählen und die Parameter auswählen. Als Parametername muss ich den korrekten Namen des Parameters angeben, hier also "UserID". Als Parameterwert die ID des aktuellen Users also "=Fields!Id.Value".

Nun wird der Hauptreport zwar versuchen die Subreports zu erstellen, das wird jedoch nicht funktionieren, da wir den DataSet der Subreports keine Datasource zugewiesen haben.

Um das zu tun registrieren wir im Konstruktor einen neuen Event namens SubReportProcessingEvent:

reportViewer.LocalReport.SubreportProcessing +=
new SubreportProcessingEventHandler
(SubreportProcessingEventHandler);

Natürlich müssen wir die Event-Methode definieren:

void SubreportProcessingEventHandler
(object sender, SubreportProcessingEventArgs e)
{

}

In dieser Prozedur muss genau wie oben beim Hauptreport die DataSource fürs DataSet des Reports definiert werden. Wichtig ist das vorher die vorhandenen Daten gelöscht werden.
Per e.Parameters[index].Value[index] kann auf die Parameter-Werte zugegriffen werden:

e.DataSources.Clear()
loginList.SetLoginSource(dbHandler.GetNewDataTable(
"SELECT * FROM Logins WHERE userID=" + e.Parameters[0].Values[0]));
e.DataSources:Add(new ReportDataSource(
"Namespace_Login", loginList.GetLogins()));

Falls mehrere Unterberichte vorhanden sind können die Namen über die Eigenschaft e.ReportPath ausgelesen werden. So weiss man genau für welchen Bericht nun das DataSet gesetzt werden muss.

Freitag, 30. März 2012

März 2012 / ASP.NET MVC 3

MVC - Model View Controller

MVC steht für Model View Controller und ist eine Architektur in der Software-Entwicklung. Sie besteht aus 3 Einheiten nämlich Model(Datenmodell), View(Präsentation - UI) und Controller(Programmsteuerung).
Ziel der Architektur ist es, spätere Änderungen oder Erweiterungen zu vereinfachen und die Wiederverwendbarkeit einzelner Klassen & Komponenten.

Model:

Zumindest in ASP.NET MVC 3, welches ich verwendet habe, gibt es eigentlich 2 Arten von Models. Die 1. Art heisst "Domainmodel", ist komplett von View und Controller unabhängig und wird verwendet, um die Datenbankstruktur hinter der Anwendung zu kreieren.
Dazu wird das Entity Framework verwendet, welches "CodeFirst" ermöglicht verwendet. Dazu jedoch später mehr.
Die 2. Art heisst "Viewmodel" und wiederspiegelt die View. Das heisst es ist der View angepasst, so dass alle nötigen Informationen von der View an den Controller weitergegeben werden kann, diese Daten werden jedoch nicht direkt sondern gefiltert und über ein Domainmodel in die Datenbank gespeichert. Diese Models sind sozusagen von der View abhängig und müssen somit bei Änderungen ebenfalls angepasst werden.

Controller

Der Controller ist die Steuerung der Applikation und verwaltet eine oder mehrere Views. Von diesen nimmt er die Benutzerinteraktionen entgegen, wertet diese aus und reagiert entsprechend. Er ist ebenfalls dafür zuständig, die erhaltenen Daten falls nötig zu manipulieren und/oder zu speichern/ändern/löschen.

View

Die View ist lediglich für die Darstellung zuständig. Hier wird also die Grafische Oberfläche definiert und mit den vom Controller, als Model erhaltenen Daten gefüllt. Die View selbst enthält aber keine Logik, sondern Sendet die Daten bloss an den Controller weiter. Eine View hat zwingend immer einen Controller, damit diese Überhaupt angezeigt werden kann, kann jedoch ohne Model auskommen.

ASP.NET MVC 3

ASP.NET MVC 3 ist ein Framework zum erstellen von Websites, basierend auf ASP.NET und dem .NET Framework. Die Website wird natürlich mit der MVC-Architektur erstellt, was durch dieses Framework hervorragend unterstützt wird. Es basiert auf dem .NET Framework 4.0 was eine Verwendung von Visual Studio 2010 nötig macht, da die früheren Visual Studio Versionen diese .NET Version nicht unterstützen.

Entity Framework

MVC 3 integriert das Entity Framework, welches CodeFirst unterstützt. Dass ermöglicht uns aus den Models der Webanwendung die Datenbank kreieren zu lassen.
Die Models werden also wie ganz normale Klassen erstellt und mit Eigenschaften versehen. Möchte ich z.B. eine Webanwendung mit verschiedenen Alben erstellen kann ich das Model folgendermassen realisieren:

public class Album
{
    public int AlbumId { get; set; }
    public string Titel { get; set; }
    public int Preis { get; set; }
}

Das Entity Framework erstellt daraus eine Tabelle, welche die Eigenschaften der Klasse als Spalten verwendet.
Es verwendet das sogenannte ORM (object relational mapping), was aus objektorientierten Datenmodellen relationale Datenbanken generiert. Das erleichtert das Programmieren ungemein, da für das Programm alles komplett objektorientiert bleibt.Die SQL-Befehle werden ebenfalls durch das Entity Framework generiert, was dem Entwickler ebenfalls viel Arbeit abnimmt.

Strongly-typed View

Eine Strongly-typed View hat immer ein Model als Parameter, was ja bekanntlich nicht bei jeder View der Fall ist. Dafür muss folgender Code in die 1. Zeile der View:
@model Namespace.Models.Album
Diese Views ermöglichen, dass direkt auf die Eigenschaften des übergebenen Models zugegriffen werden kann. Das erleichtert die Programmierung stark und ermöglicht vollen IntelliSense Support. Somit kann, bei einer Strongly-typed View auf Album mit folgendem Code auf den Preis des Models zugegriffen werden:
@Model.Preis
Es können ebenfalls mehrere Models übergeben werden, wodurch auch auf alle Models zugegriffen werden kann. Dazu muss die 1. Zeile der View mit IEnumerable modifiziert werden:
@model IEnumerable
Anschliessend kann z.B. auf die Anzahl übergebener Models mit @Model.Count() zugegriffen, oder alle mit einer foreach-Schlaufe aufgelistet werden.
@foreach (var album in Model)
{
    @Model.Titel
}

Validierung der Daten

Wird z.B. eine View erzeugt, um neue Alben zu erfassen, müssen natürlich gewisse Validierungs-Regeln wie die maximale Zeichenlänge oder der Datentyp der einzelnen Eigenschaften definiert werden. Das schöne hier ist, dass alle Validierungs-Regeln direkt im Model definiert werden können. Somit muss eine spätere Änderung auch bloss dort gemacht werden.
Es werden viele Validierungs-Regeln angeboten, wie Required, DataType, Display, StringLength usw.
Die Regeln werden immer in [] vor der Eigenschaft definiert. So könnte es z.B. beim Albumtitel aussehen:

[Required]
[StringLength(100, ErrorMessage = "Error", MinimumLength = 2]
public string Titel { get; set;}

Mehr über MVC - Nützliche Tutorials

MVC ist ein riesiges Gebiet und bietet unglaublich viele Möglichkeiten.
Um mehr über MVC 3 zu erfahren besuchen sie folgende Websites:
http://www.asp.net/mvc/mvc3
http://www.asp.net/mvc/overview/getting-started
Ich selbst habe bisher 2 Tutorials gemacht, welche beide sehr nützlich sind.
Hier wird eine Film-Sammlung Programmiert:
http://www.asp.net/mvc/tutorials/getting-started-with-aspnet-mvc3/getting-started-with-mvc3-part1-cs
Hier wird ein MusicStore Programmiert:
http://www.asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-1
Das 2. Tutorial finde ich wirklich super, da alles von Grund auf neu gemacht wird und nicht wie im 1. Bereits mit vorhandenem gearbeitet wird.

Montag, 27. Februar 2012

Februar 2012 / C# AccessDB auf Text- / Check-boxen

Zugriff auf Access-DB mit C#
 
Zu diesem Thema habe ich bereits vor einigen Monaten im August 2011 einen Blogeintrag verfasst. In diesem Eintrag findet man die Anleitung, um eine Tabelle aus einer Access-Datenbank zu holen, und wieder zu speichern.

Heute werde ich beschreiben, wie man eine Tabelle mit Hilfe eines BindingNavigators an Textboxen bzw. bei einem Boolean an Checkboxen bindet.
Der BindingNavigator findet sich in der Toolbox von Visual Studio und kann einfach auf die nötige Form gezogen werden. Er hilft beim Navigieren durch Tabellen und sieht so aus:
Grundsätzlich wird die Verbindung mit der Datenbank genau gleich hergestellt. Bloss wird die BindingSource direkt an den BindingNavigator über das BindingSource-Property gebunden:

bindingNavigator.BindingSource = bSource.Source;

BindingSource an Text- / Checkboxen

Sobald die BindingSource also mit dem BindingNavigator verknüpft ist, müssen die einzelnen Tabellen-Spalten an die Textboxen gehängt werden. Dazu wird folgendes verwendet:

textBox.DataBindings.Add(new Binding("Text", bindingNavigator.BindingSource,"Spaltenname"));

Der 1. Parameter definiert die Eigenschaft der TextBox, an welche die Spalte gebunden werden soll, welche logischerweise "Text" ist. Der 2. die BindingSource und der 3. den Spaltennamen der BindingSource.

An eine Checkbox wird das ganze genau gleich gebunden:
checkBox.DataBindings.Add(new Binding("Checked", bindingNavigator.BindingSource, "Spaltenname"));

Speichern der Änderungen

Das speichern funktioniert wieder genau gleich. Es wird einfach nur
dAdapter.Update(dTable);
ausgeführt und schon ist die Tabelle gespeichert.
Was ich noch nie geschrieben habe ist, dass vor dem Speichern noch der Befehl:
bSource.EndEdit();
aufgerufen werden sollte, damit auch wirklich alle Änderungen in den Textboxen bzw. bei Datagridviews in den Zellen übernommen werden.
Hier zu beachten sind ebenfalls wieder die Error-Handlings, welche in diesem Blogeintrag vom Januar 2012 beschrieben sind.

Kombination aus Datagridview und Textboxen

Es ist ebenfalls möglich eine Kombination aus Datagridview und Textboxen zu verwenden, wobei das Datagridview bloss als Navigation durch die Tabelle und die Textboxen als Bearbeitungs-Felder dienen.
Der Vorteil, gegenüber dem BindingNavigator ist natürlich, dass man mit dem Datagridview eine viel bessere Übersicht hat und den gesuchten Datensatz viel schneller findet.
Dazu wird die BindingSource wie bereits beschrieben an das Datagridview gebunden.
Anschliessend werden die einzelnen Zellen direkt an die Textboxen gebunden.
Das wird am besten im RowEnter-Event des Datagridviews gemacht:
textBox.DataBindings.Add(new Binding("Text", Datagridview[Spaltenindex, Zeilenindex], "Value");
Der Spaltenindex ist klar, der Zeilenindex wird am besten über Datagridview.SelectedRows[0].Index genommen.

Wichtig ist das bei jedem Zeilenwechsel bzw. vor jeder erneuten Bindung, die alte Bindung gelöscht wird. Dazu wird folgender Befehl verwendet:
textbox.DataBindings.Clear();

Montag, 30. Januar 2012

Januar 2012 / C# AccessDB auf DataGridView Teil 2

Zugriff auf Access-DB mit C#

Zu diesem Thema habe ich bereits vor einigen Monaten im August 2011 einen Blogeintrag verfasst. In diesem Eintrag findet man die Anleitung, um eine Tabelle aus einer Access-Datenbank zu holen, und wieder zu speichern.

Falsche Daten abfangen
Sobald nun eine Verbindung zur Datenbank besteht, und mind. ein DataGridView mit einer Tabelle verbunden ist, sollte man die eingegebenen Daten überprüfen.
Gibt man nämlich in ein Feld, in welches nur Zahlen geschrieben werden sollten Buchstaben ein, so wird das DataError-Event ausgelöst.

Eine ziemlich einfache Lösung wäre z.B. bloss folgende Zeile einzufügen:
MessageBox.Show("Fehlerhafte Dateneingabe!");
So wird dem Benutzer, bei einer Falscheingabe mitgeteilt, dass er einen Fehler gemacht hat und er kann das Feld nicht verlassen, bis die eingegebenen Daten das korrekte Format haben.

Es gibt natürlich noch andere Möglichkeiten, auf dieses Event zu reagieren, aber ich denke es ist schon mal nicht schlecht.

ID eines neuen Datensatzes
Da die ID normalerweise von der Datenbank selbst generiert wird und sie im DataGridView, so hoffe ich zumindest, nicht angezeigt wird, fällt einem dieses Problem zuerst gar nicht wirklich auf.
Hier noch der Code um eine Spalte auszublenden:

DataGridView.Columns["Spaltenname"].Visible = false;

Das Problem ist, dass in der Datenbank zwar eine ID vorhanden ist, das DataGridView diese jedoch nicht eingetragen hat bzw. diese gar nicht kennt. Wenn also ein neuer Datensatz erstellt wird, dieser mit Update() in die Datenbank gespeichert wurde und der Benutzer anschliessend diesen Datensatz erneut bearbeiten & speichern möchte, wird eine Exception ausgegeben, da der Datensatz keine ID hat.
Um das zu verhindern, muss man die ID von der Datenbank holen.

Dazu muss man unsere Verbindung ein kleines bisschen umbauen:
Anstatt wie im anderen Blog-Eintrag angegeben den DataAdapter direkt mit dem Abfrage und dem Connecting-string(strConnect) zu instanziieren, wird zuerst eine Connection mit dem Connecting-string erstellt und der DataAdapter dann mit der Connection instanziiert:
Aus:

OleDbDataAdapter dAdapter = new OleDbDataAdapter(strAbfrage, strConnect); dAdapter = new OleDbDataAdapter(strAbfrage, strConnect);

wird also:

OleDbConnection dbConnection = new OleDbConnection(strConnect);
dbConnection.Open();
OleDbDataAdapter dAdapter = new OleDbDataAdapter(strAbfrage, dbConnection);

Das macht für die Verbindung nun eigentlich keinen wirklich grossen Unterschied, aber um das Problem zu beheben, muss eine Connection verwendet werden.

Jetzt muss in derselben Methode, in der Sie die Verbindung aufbauen, folgende Zeile eingefügt werden:

dAdapter.RowUpdated += new OleDbRowUpdatedEventHandler(dAdapter_RowUpdated);

Das fügt einen OleDbRowUpdatedEventHandler hinzu, was so viel heisst wie: wir können nun das RowUpdated-Event abfangen.
Wir erstellen also folgende Prozedur:

private void dAdapter_RowUpdated (object sender, OleDbRowUpdatedEventArgs e)
{
    if (e.StatementType == StatementType.Insert)
    {
        OleDbCommand cmdNewID = new OleDbCommand("SELECT @@IDENTITY", dbConnection);
        e.Row[idColumn] = (int) cmdNewID.ExecuteScalar();
    }
}

Hier wird also zuerst abgefragt, ob es sich um ein Insert-Statement handelt.
Wenn das Zutrifft, wird die ID in die Zelle im Datagridview geschrieben, wobei e das Datagridview und idColumn den Namen oder den Index der Spalte darstellt.
Problem gelöst.

Exeption bei Checkboxen Es kam vor, dass wenn ich wahllos auf den Checkboxen herumgedrückt habe, ich irgendwann eine Exception bekam. Ich fand heraus, dass es genau bei folgendem Vorgang stattfand:
Sobald ich einen neuen Datensatz erstellt und anschliessend, nachdem ich den Datensatz bereits gespeichert hatte, eine Checkbox gesetzt habe.

Der Grund dafür ist, dass die Datenbank automatisch alle Werte der nicht gesetzten Checkboxen als false speichert, was auch richtig ist. Das DataGridView hingegen, speichert den Wert dieser Checkboxen als null ab. Beim Speichern eines Datensatzes überprüft der DataAdapter immer, ob die aktuellen Daten in der Datenbank und die beim letzten Mal gelesenen bzw. gespeicherten Daten übereinstimmen.
Da die Datenbank nun überall false, das geholte DataTable jedoch überall null gespeichert hat, wird eine Exception ausgegeben.

Die Lösung dieses Problems ist es, im DefaulValuesNeeded-Event des DataGridViews alle null-Werte in bool-Zellen, also Checkboxen, auf false zu ändern.
Hier ein Teil meiner Methode:

for (int i = 0; i < dgv.Columns.Count; i++)
{
    if (dgv.SelectedCells[0].OwningRow.Cells[i].ValueType == typeof(Boolean) && dgv.SelectedCells[0].OwningRow.Cells[i].Value == null)
    {
        dgv.SelectedCells[0].OwningRow.Cells[i].Value = false;
    }
}

dgv repräsentiert hier das DataGridView.

Samstag, 31. Dezember 2011

Dezember 2011 / C#: Multithreading & ShowWindow aus WinAPI

Multithreading in C#

Was ist Multithreading und wann wir es gebraucht?
Unter Multithreading versteht man das abarbeiten mehrerer Threads gleichzeitig innerhalb eines Prozesses. Dadurch können mehrere arbeiten Parallel und auf einander abgestimmt ausgeführt werden.
Das heisst innerhalb eines Programmes können 2 Threads Daten austauschen aber auch gleichzeitig unabhängig voneinander arbeiten.

Dazu ein Beispiel:
Ich erstelle ein GUI mit einem grossen Textfeld, in welches der Benutzer irgendeinen Text eingeben kann. Ebenfalls kann man über das Menu die Farben des Form- und des Textbox-Hintergrundes ändern. Per Knopfdruck soll das Programm dann den eingegebenen Text mit dem Bubblesort(sehr langsam) alphabetisch sortieren.
Bei einem langen Text kann das schon einige Sekunden dauern. Möchte der Benutzer nun das Programm schliessen oder die Hintergrundfarben ändern, kann er das nicht, da die gesamte Rechenleistung des Threads beim Sortieren gebraucht wird.
Hier kommt das Multithreading zum Einsatz. Wird die Sortierung in einem 2. Thread erledigt, kann der Benutzer parallel dazu das GUI bedienen.

Multithreading in C#:
In C# kann ziemlich einfach ein weiterer Thread erstellt & gestartet werden.
Zuerst sollte man das using von System.Threading hinzufügen:
using System.Threading;

Sobald das gemacht ist kann man direkt mit Thread arbeiten und einen neuen Thread instanziieren:
Thread neuerThread = new Thread(this.Methode)

Als Parameter muss eine Methode mitgegeben werden, welche im neuen Thread ausgeführt werden soll.
Wichtig ist dabei, dass man keine Klammern hinter die Methode stellt, sonst gibt es eine Fehlermeldung.

Nun muss der neue Thread noch gestartet werden und auch das ist ganz simpel:
neuerThread.Start();

ShowWindow aus WinAPI

Die Windows API(Windows-Anwendungs-Programmierschnittstelle) bietet Windows-Programmierern viele Funktionen welche in DLL-Dateien gespeichert sind.
Eine solche Funktion ist ShowWindow, welche dazu verwendet wird Fenster zu Maxi-/Minimieren, zu verstecken, wieder anzuzeigen usw.
Ich stiess auf diese Funktion, als ich nach einer Möglichkeit gesucht habe, einen Virtuellen Computer dauerhaft versteckt im Hintergrund laufen zu lassen.
Um diese Funktion nutzen zu können muss die DLL user32.dll eingebunden werden und einer Variabel zugewiesen werden:
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool ShowWindow(IntPtr hwnd, int nCmdShow);
 
Wie man sieht hat ShowWindow 2 Parameter ein IntPtr Objekt und ein int.
IntPtr enthält in diesem Fall ein Window Handle, welches durch eine Zahl repräsentiert wird.
Mit dem int wird übergeben, was mit dem Fenster gemacht werden soll. Diese werden am besten direkt als globale Konstante deklariert. Die Liste der Möglichkeiten befindet sich am Ende des Posts.
 
Nun muss man natürlich das Window Handle von dem Prozess haben, auf dessen Fenster man Einfluss nehmen möchte. In meinem Fall war das der VMPlayer.
Dazu gäbe es eine GetWindow-Funktion, welche ebenfalls von der WinAPI bereitgestellt wird.
Besser ist es jedoch, das Window Handle über den Prozess zu holen und das funktioniert so:
IntPtr vmWindow = IntPtr.Zero;
 
Process[] processes = Process.GetProcessesByName("vmplayer");
 
foreach (Process p in processes)
{
    vmWindow = p.MainWindowHandle;
}
 
Zuerst wird hier vmWindow als IntPtr instanziiert.
Anschliessend wird an processes alle Prozesse mit dem Namen vmplayer übergeben. Da bei mir nur 1 virtueller PC läuft, wird auch nur 1 Prozess zurückgegeben.
Anschliessend wird das MainWindowHandle des gefundenen Prozesses an vmWindow übergeben.
 
Nun kann man über ShowWindow das Fenster verstecken, maximieren usw.
Hier wird das Fenster versteckt, vorausgesetzt SW_HIDE wurde als Konstante mit dem Wert 0 deklariert:
ShowWindow(vmWindow, SW_HIDE);
 
Hier noch die Liste mit den Möglichkeiten:
  • private const int SW_HIDE = 0 ' Versteckt das Fenster
  • private const int SW_SHOWNORMAL = 1 ' Zeigt das Fenster und aktiviert dies
  • private const int SW_SHOWMINIMIZED = 2 ' Zeigt das Fenster an und Minimiert es
  • private const int SW_MAXIMIZE = 3 ' Maximiert das Fenster
  • private const int SW_SHOWNOACTIVATE = 4 ' Zeigt das Fenster an ohne es zu aktivieren
  • private const int SW_SHOW = 5 ' Zeigt das Fenster an, auch wenn es versteckt ist
  • private const int SW_MINIMIZE = 6 ' Minimiert das Fenster
  • private const int SW_SHOWMINNOACTIVE = 7 ' Minimiert das Fenster aber aktiviert es nicht
  • private const int SW_SHOWNA = 8 ' Zeigt das Fenster an aber aktiviert es nicht
  • private const int SW_RESTORE = 9 ' Stellt das Fenster wieder her