Freitag, 16. März 2012

Prüfen ob CD eingelegt

In diesem Post möchte ich, aufbauend auf das Prinzip des letzten Posts, zeigen, wie man erkennt, wann eine CD eingelegt wird.

Wie im vorigen Post beschrieben benutzen wir dazu wieder das bekannte WM_DEVICECHANGE Ereignis und die Aufzählung System.IO.DriveInfo.GetDrives().
Wir müssen nun beim Prüfen nur abfragen, ob das betrachtete Laufwerk vom Typ DriveType.CDRom ist und eine lesbare CD enthält, was die Variable IsReady anzeigt. In der WndProc() Funktion wird der vorige Wert dieser Variablen mit dem aktuellen verglichen und so entschieden, ob der Benutzer eine neue CD eingelegt hat (für mehrere CD Laufwerke ist der Code entsprechend anzupassen):


        const int WM_DEVICECHANGE = 0x219;
        bool CDInserted;

        private void CheckCD()
        {
            foreach (System.IO.DriveInfo d in System.IO.DriveInfo.GetDrives())
            {
                if (d.DriveType == System.IO.DriveType.CDRom && d.IsReady)
                    CDInserted = true;
            }
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_DEVICECHANGE)
            {
                bool OldCD = CDInserted;
                CheckCD();
                if (!OldCD && CDInserted)
                    MessageBox.Show("CD eingelegt.");
            }

            base.WndProc(ref m);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            CheckCD();
        }


Bei Interesse im Themengebiet Technologie findet ihr weitere Tipps auch im Programmierer-Forum von Techfacts.

Donnerstag, 15. März 2012

Erkennen ob Wechseldatenträger (wie USB-Sticks) angeschlossen oder entfernt wurden

Im vorigen Post, auf den dieser aufbaut, habe ich erklärt, wie man allgemein mit C# erkennt, falls sich die Hardwareeinstellungen des PCs geändert haben.
In diesem Post geht es speziell darum, zu erkennen, ob Wechseldatenträger, wie z.B. USB-Sticks, angeschlossen oder entfernt wurden.

Hierfür nutzen wir das im vorigen Post besprochene Ereignis WM_DEVICECHANGE zur Notfikation, falls Geräte angeschlossen bzw. entfernt wurden.
Dann benutzen wir die Aufzählung System.IO.DriveInfo.GetDrives(), die alle Laufwerke des Computers enthält, also die internen Festplattenpartitionen aber eben auch Wechseldatenträger. Von jedem Datenträger fragen wir den Typ (DriveType) ab, entspricht dieser dem Typ DriveType.Removable, ist der gerade betrachtete Datenträger ein Wechseldatenträger.
In einer Funktion zählen wir also die Anzahl dieser und durch Differenzenbildung können wir entscheiden, ob ein Wechseldatenträger angeschlossen oder entfernt wurde.

Ich denke der dazugehörige Code sollte selbsterklärend sein:

        private void Form1_Load(object sender, EventArgs e)
        {
            CheckDrives();
        }

        int USBCount;

        private void CheckDrives()
        {
            USBCount = 0;
            foreach (System.IO.DriveInfo d in System.IO.DriveInfo.GetDrives())
            {
                if (d.DriveType == System.IO.DriveType.Removable)
                    USBCount++;
            }
        }

        const int WM_DEVICECHANGE = 0x219;
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_DEVICECHANGE)
            {
                int OldUSBCount = USBCount;
                CheckDrives();
                if (USBCount > OldUSBCount)
                    MessageBox.Show("Wechseldatenträger angeschlossen");
                if (USBCount < OldUSBCount)
                    MessageBox.Show("Wechseldatenträger entfernt");
            }

            base.WndProc(ref m);
        }


Für einen größeren Überblick über die Programmierwelt sei auf den folgenden Link verwiesen, hier kann man sich zum Thema Programmierung austauschen.

Anschluss von Geräten / Wechseldatenträgern erkennen

Im vorigen Post habe ich Grundlegendes zur Funktion WndProc() erklärt.
In diesem Post möchte ich zeigen, wie man diese Funktion dazu benutzen kann, zu erkennen, wenn sich Geräteeinstellungen geändert haben, also z.B. neue Hardware angeschlossen wurde, wie Mäuse, USB - Sticks, oder eine CD eingelegt wurde.
Ändert sich eine Geräteeinstellung, schickt das Betriebssystem unserer C# Anwendung die Nachricht WM_DEVICECHANGE (hexadezimal den Wert 219).

Folgender Code gibt eine Nachricht aus, falls obiger Fall eintritt:

const int WM_DEVICECHANGE = 0x219;
protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_DEVICECHANGE)
        MessageBox.Show("Geräteeinstellungen geändert.");
    base.WndProc(ref m);
}

Im nächsten Post wird gezeigt, wie man explizit erkennt, ob ein USB - Stick angeschlossen wurde, in diesem Post, ob eine CD eingelegt wurde.

Mittwoch, 14. März 2012

WndProc() überschreiben

Das Betriebssystem Windows arbeitet ereignisorientiert, dass heißt, Programme fragen nicht laufend den Zustand bestimmter Systemvariablen ab, sondern werden durch Ereignisse (Nachrichten) ggf. über deren Veränderung informiert.

In diesem Post möchte ich zeigen, wie man die Funktion, welche vom Betriebssystem an die C# Applikation gesandte Nachrichten abfängt, überschreibt, und so in diesen Verarbeitungsprozess eingreifen kann.

Die erwähnte Funktion nennt sich WndProc() und ist Teil jeder Form Klasse. Während die Anwendung läuft, sendet Windows sehr viele Nachrichten an die Anwendung, WndProc() nimmt sie entgegen. Windows benachrichtigt unser Programm quasi über alles, was irgendwie von Interesse sein könnte, vom Bewegen des Mauszeigers über das Aktivieren einer anderen Anwendung bis zum Klick auf den Schließen Button des Formulars. Im Grunde genommen sind alle Aktionen, die der Benutzer auf dem Formular durchführt, Ereignisse, die von Windows registriert, verarbeitet und dann erst der Anwendung zurückgeschickt werden, sodass diese sie dann verarbeiten kann. Der Anwendungsfall, dass der Benutzer auf das "X" zum Schließen unserer C# Anwendung klickt, ruft demnach in etwa folgende Aktionskette hervor: Windows erkennt einen Mausklick im Bereich unserer Anwendung, schickt dieses Ereignis an die Anwendung, welche anhand der Position erkennt, dass "X" gedrückt wurde und sich selbst beendet.

Soviel zur Theorie, nun zur praktischen Umsetzung: Wie schon erwähnt ist WndProc() bereits eine Funktion aus der Form Klasse, wir müssen diese also überschreiben, was mit dem Schlüsselwort override geschieht.
Tippen wir in der Entwicklungsumgebung die ersten Zeichen der Signatur
protected override void WndProc(ref Message m) ein, ergänzt C# dieses zu

protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);
}

Die hinzugefügt Code Zeile besagt, dass nach Durchlauf unserer nun geänderten Funktion die Originalfunktion aufgerufen wird.
Löscht man diese Zeile, kann die Anwendung überhaupt nicht starten, da nun alle Ereignisse in unserer neuen Funktion "enden" und nicht verarbeitet werden.

Um eine Ahnung dafür zu kriegen, welche und wie viele Ereignisse empfangen werden, kann man z.B. eine ListBox dem Formular hinzufügen und folgende Zeile in der Funktion ergänzen: listBox1.Items.Add(m.ToString());

Dieser Post ist nun offensichtlich ganz abstrakt gehalten, ich hoffe bald aber einige interessante Anwendungen zeigen zu können, die sich über dieses Prinzip realisieren lassen.

Nachtrag: Im nächsten Post wird gezeigt, wie man mit dieser Funktion erkennt, ob sich Hardwareeinstellungen geändert haben.
Damit kann man zum Beispiel prüfen ob eine CD eingelegt oder ein USB-Stick eingesteckt wurde.

Montag, 5. März 2012

Beenden einer Konsolenapplikation abfangen

Manchmal wird eine Anwendung, zum Beispiel durch den Benutzer, während einer kritischen Aktion beendet. Möchte man diese Aktion noch zuende führen oder anderweitigen Code zum Beenden ausführen, kann man in Windows Forms-Applikationen einfach die Ereignisse FormClosed und FormClosing benutzen.
Bei Konsolenanwendungen stehen diese nicht zur Verfügung. Um aber doch auf das unerwartete Beenden der Anwendung reagieren zu können, benutzen wir die Funktion SetConsoleCtrlHandler().
Mit dieser können wir bei der Anwendung eine Funktion registrieren, welche aufgerufen wird, falls das Programm beendet wird.
SetConsoleCtrlHandler() erwartet 2 Parameter, der erste ist das Delegate auf die zu nutzende Funktion und der 2 ein Boolean Wert, welcher angibt, ob der Handler hinzugefügt oder entfernt werden soll.
Die Funktion, welche beim Beenden aufgerufen wird, erhält als Parameter eine Enumeration, welche das Ereignis spezifiziert, welches das Beenden hervorruft, und muss immer false zurückgeben.
Im Code legen wir zuerst ein Delegate von der geforderten Signatur an:
private delegate bool EventHandler(CtrlType e)

Dann legen wir eine Variable mit dem eben erzeugten Typ an:
static EventHandler ConsoleCloseHandler
.
Dieser weisen wir die zu benutzende Funktion zu und registrieren diese bei der Anwendung:
ConsoleCloseHandler += new EventHandler(Console_Closed);
SetConsoleCtrlHandler(ConsoleCloseHandler, true);

In der Funktion Console_Closed() ist der Grund des Beendes im Argument gespeichert, dieser kann abgefragt und verwendet werden.
Die von mir benutzte Aufzählung enthält 4 Ereignisse: Das Schließen der Konsole über die Tastenkombination Strg + C, das herkömmliche Schließen (z.B. durch Klicken von "X"), das Schließen wegen Abmeldung des Benutzers und das Schließen wegen Herunterfahrens.
Ich hoffe der folgende Quellcode verdeutlicht das Prinzip:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class Program
    {
        [DllImport("Kernel32")]
        private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add);

        enum CtrlType
        {
            CTRL_C_EVENT = 0,
            CTRL_CLOSE_EVENT = 2,
            CTRL_LOGOFF_EVENT = 5,
            CTRL_SHUTDOWN_EVENT = 6
        }

        private delegate bool EventHandler(CtrlType e);
        static EventHandler ConsoleCloseHandler;

        static void Main(string[] args)
        {
            ConsoleCloseHandler += new EventHandler(Console_Closed);
            SetConsoleCtrlHandler(ConsoleCloseHandler, true);

            while (true)
            {
                // Endlosschleife
            }
        }

        private static bool Console_Closed(CtrlType e)
        {
            switch (e)
            {
                case CtrlType.CTRL_C_EVENT:
                    Console.WriteLine("Ctrl + C");
                    Console.ReadLine();
                    break;
                case CtrlType.CTRL_LOGOFF_EVENT:
                    Console.WriteLine("Log Off");
                    Console.ReadLine();
                    break;
                case CtrlType.CTRL_SHUTDOWN_EVENT:
                    Console.WriteLine("Shutdown");
                    Console.ReadLine();
                    break;
                case CtrlType.CTRL_CLOSE_EVENT:
                    Console.WriteLine("Close");
                    Console.ReadLine();
                    break;
                default:
                    Console.WriteLine("other");
                    Console.ReadLine();
                    break;
            }

            return true;
        }
    }
}

Freitag, 2. März 2012

Windows Forms- und Konsolen Anwendungen neu starten

In diesem Post möchte ich kurz zeigen, wie man eine C# Anwendung neu starten kann.
Für Windows Forms-Anwendungen reicht hierfür ein einziger Befehl:
Application.Restart();

Was eigentlich dahinter steckt, oder falls jemand diesen Vorgang noch etwas "personalisieren" will, ist folgendes:
Die aktuelle Anwendung wird als eigener Prozess neu gestartet und die alte Instanz wird geschlossen, somit lautet der Code für Windows Forms-Anwendungen:

// neuen Prozess erzeugen
System.Diagnostics.Process NewInstance = new System.Diagnostics.Process();
// diesen mit Anwendung verknüpfen
NewInstance.StartInfo.FileName = Application.ExecutablePath;
// neue Instanz starten
NewInstance.Start();
// aktuelle Instanz beenden
Application.Exit();

Diese Methode ist besonders für Konsolenanwendungen interessant, da bei diesen kein Befehl Application.Restart() verfügbar ist.
Obiger Code funktioniert auch bei diesen einwandfrei, allerdings muss eine Zeile angepasst werden, da nämlich Application.ExecutablePath logischwerweise auch nicht verfügbar ist:
NewInstance.StartInfo.FileName = System.Threading.Thread.GetDomain().BaseDirectory + System.Threading.Thread.GetDomain().FriendlyName; (so fragt man über die Klasse Threading Anwendungspfad und Anwendungsname ab)