Mittwoch, 28. Juli 2010

Ton ausschalten / Lautstärke einstellen (Windows XP)

Nachtrag: Die hier vorgestellte Methode funktioniert nur bis Windows XP, nicht mit Vista und 7.
Zum Stummschalten des PCs unter diesen Betriebssystem habe ich diesen Post geschrieben.

Mittels P/Invoke (d.h. über Einbinden einer WinAPI - Funktion) kann man sehr leicht den PC stummschalten (muten) oder allgemein die Lautstärke verändern. Das Ändern der Lautstärke mit dieser Funktion ändert die Einstellung für die WAVE - Lautstärke (der Schieberegler ist auch in der Windows Lautstärkeregelung zu sehen), welche eigentlich alle Sounds auf dem PC betrifft.
Zum Einbinden von API - Funktionen ist die using - Direktive using System.Runtime.InteropServices; nötig, folgende Funktion wird benötigt:

[DllImport("winmm.dll")]
public static extern int waveOutSetVolume(IntPtr device, uint volume);

Der Parameter device bezeichnet das Audiogerät, welches angesprochen werden soll. Ist nur eins vorhanden (Standardfall), kann einfach ein Null - Pointer übergeben werden. volume gibt die Lautstärke an.
Der Wertebereich beträgt hierbei 0 - 65535, wobei bei 0 Stille herrscht und 65535 volle Lautstärke bedeutet.
Folgender Aufruf schaltet beispielsweise den Ton am PC komplett aus:

waveOutSetVolume(IntPtr.Zero, 0);

Dienstag, 27. Juli 2010

Richtig runden mit C#

In diesem Post stelle ich 3 grundlegende Funktionen zum Runden von Zahlen mit C# vor.
Wie auch andere mathematische Funktionen sind diese (logischerweise) in der Klasse Math untergebracht.
Zum "standardmäßigen" Runden gibt es die Funktion Round(). Diese erwartet als 1. Parameter die zu rundende Zahl und als 2. optional die Anzahl der Dezimalstellen nach dem Runden. Wird diese Anzahl nicht übergeben, nimmt die Funktion 0 als Standardwert an, d.h. die Zahl wird auf die nächstgelegene ganze Zahl gerundet. Liegt die Zahl genau zwischen 2 Zahlen, wird die gerade Zahl genommen.
Es gibt aber noch 2 weitere interessante Funktionen: Ceiling() und Floor().
Beide Funktionen runden auf ganze Zahlen, erstere auf die nächsthöhere und zweite auf die nächsttiefere.
Das folgende Beispiel sollte diese einfachen Zusammenhänge verdeutlichen:

            decimal d = (decimal)1.5642; // hier muss "1.5642" manuell über (decimal) in einen Decimal - Wert konvertiert werden, da C# Kommazahlen standardmäßig als double - Wert interpretiert

            decimal RoundedIntegerNumber = Math.Round(d); // auf ganze Zahl runden = 2
            decimal RoundedDecimalNumber = Math.Round(d, 2); // auf 2 Nachkommastellen runden = 1.56

            decimal NextHigherNumber = Math.Ceiling(d); // auf nächsthöhere Zahl runden = 2
            decimal NextLowerNumber = Math.Floor(d); // auf nächsttiefere Zahl runden = 1

Sonntag, 25. Juli 2010

Verknüpfung auf dem Desktop erstellen

Verknüpfungen auf dem Desktop sind meistens einfach "normale" Dateien mit der Endung ".lnk" (für Link). Mit C# solche Dateien anzulegen und mit dem richtigen Inhalt zu füllen ist etwas aufwendiger und geht meines Wissens nach nur über das Einbinden des COM - Objekts Windows Script Host Object Model.
In diesem Post zeige ich deshalb, wie man statt ".lnk" - Dateien ".url" - Dateien erzeugt - sie haben die gleiche Funktionalität, können aber mit .Net Bordmitteln ganz einfach erzeugt werden.
Diese Dateien sind hauptsächlich dazu da, Verknüpfungen auf Internetseiten darzustellen, sie können aber eben auch dazu verwendet werden, Dateiverknüpfungen anzulegen.
Die folgende Funktion erstellt eine Verknüpfung im Pfad shortcutPath, wobei shortcutDest den Pfad zum zu verknüpfenden Programm angibt und shortcutIcon den Pfad zum Icon, welches als Symbol für die Verknüpfung angezeigt werden soll:

private void CreateShortcut(string shortcutPath, string shortcutDest, string shortcutIcon)
{
    StreamWriter sw = new StreamWriter(shortcutPath);
    sw.WriteLine("[InternetShortcut]");
    sw.WriteLine("URL=file:///" + shortcutDest);
    sw.WriteLine("IconIndex=0");
    sw.WriteLine("IconFile=" + shortcutIcon);
    sw.Close();
}

Um nun eine Verknüpfung auf dem Desktop anzulegen, muss die Funktion ungefähr so aufgerufen werden:

CreateShortcut(Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\Meine Verknüpfung.url", "C:\\Programme\\WinRAR\\WinRAR.exe", "C:\\MeinIcon.ico");

Samstag, 24. Juli 2010

Spaßprogramm: Screenshot vom Desktop als Hintergrund und alle Icons löschen - rückgängig machen

So, hier der Code, um die Effekte des Spaßprogramms aus dem vorigen Post rückgängig zu machen:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        [DllImport("user32.dll")]
        private static extern int FindWindow(string className, string windowText);

        [DllImport("user32.dll")]
        private static extern int ShowWindow(int hwnd, int command);

        private void Form1_Load(object sender, EventArgs e)
        {
            // Taskleiste einblenden
            int hwnd = FindWindow("Shell_TrayWnd", "");
            ShowWindow(hwnd, 1);
        
            string BackupDirectory = @"C:\backupdesktopfun\";

            DirectoryInfo ds = new DirectoryInfo(BackupDirectory);

            string DesktopDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\\";

            // alle Dateien und Verzeichnisse wieder auf den Desktop verschieben

            foreach (FileInfo f in ds.GetFiles())
            {
                if (!File.Exists(DesktopDirectory + f.Name))
                    f.MoveTo(DesktopDirectory + f.Name);
            }

            foreach (DirectoryInfo d in ds.GetDirectories())
            {
                if (!Directory.Exists(DesktopDirectory + d.Name))
                    d.MoveTo(DesktopDirectory + d.Name);
            }

            Application.Exit();
        }
    }

Spaßprogramm: Screenshot vom Desktop als Hintergrund und alle Icons löschen

Änderung 30.08.10: Zu diesem Post gibt's jetzt auch ein Youtube Video auf meinem Kanal.

Zuallererst: Achtung! Mit dem hier gezeigten Beispielcode können u.a. Daten verloren gehen, auf jeden Fall kommt die Reihenfolge der Icons auf dem Desktop durcheinander - ich übernehme keinerlei Verantwortung für irgendwelche negativen Folgen.

Und jetzt mal worum es geht: Heute möchte ich euch ein kleines Spaßprogramm zeigen, mit dem man andere Leute wunderbar veräppeln kann.
Das Programm macht einen Screenshot vom Desktop, versteckt dann die Taskleiste und verschiebt alle Icons vom Desktop.
Anschließend legt das Programm den Screenshot als Wallpaper fest. So hat es den Anschein, als ob alle Icons und die Taskleiste noch ganz normal vorhanden wären, allerdings sind sie nicht mehr anklickbar!
Die Dateien und Symbole auf dem Desktop werden nicht gelöscht, sondern in ein Backupverzeichnis geschoben. Der ganze Spaß (bis auf den Hintergrund, der muss manuell geändert werden) kann mit dem Programm aus dem nächsten Post rückgängig gemacht werden.

Alle Tricks, die in diesem Programm benutzt wurden, habe ich in den vorigen Posts genauer beschrieben, deswegen verzeit ihr mir hoffentlich, wenn dieser Quellcode nicht sehr ausführlich kommentiert ist. Den Code könnt ihr einfach in eine Windows Forms-Anwendung hineinkopieren und dabei den Code des Formulars Form1 überschreiben. Auf dem Formular muss ein Timer (timer1) vorhanden sein:

    public partial class Form1 : Form
    {

        [DllImport("user32.dll")]
        private static extern int FindWindow(string className, string windowText);

        [DllImport("user32.dll")]
        private static extern int ShowWindow(int hwnd, int command);


        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern Int32 SystemParametersInfo(UInt32 uiAction, UInt32 uiParam, String pvParam, UInt32 fWinIni);
        private static UInt32 SPI_SETDESKWALLPAPER = 20;
        private static UInt32 SPIF_UPDATEINIFILE = 0x1;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Shown(object sender, EventArgs e)
        {
            // alle Fenster minimieren
            Type typeShell = Type.GetTypeFromProgID("Shell.Application");
            object objShell = Activator.CreateInstance(typeShell);
            typeShell.InvokeMember("MinimizeAll", System.Reflection.BindingFlags.InvokeMethod, null, objShell, null);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            int StartX, StartY;
            int Width, Height;

            StartX = 0;
            StartY = 0;
            Width = Screen.PrimaryScreen.Bounds.Width;
            Height = Screen.PrimaryScreen.Bounds.Height;

            Bitmap Screenshot = new Bitmap(Width, Height);

            // warten, dass alle Fenster minimiert werden
            for (int i = 0; i < 1000; i++)
                if (i % 100 == 0)
                    System.Threading.Thread.Sleep(100);

            // Screenshot vom Bildschirm erstellen
            Graphics G = Graphics.FromImage(Screenshot);

            G.CopyFromScreen(StartX, StartY, 0, 0, new Size(Width, Height), CopyPixelOperation.SourceCopy);

            Screenshot.Save("C:\\1.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
            Screenshot.Dispose();

            G.Dispose();

            string filename = "C:\\1.bmp";

            // Taskleiste verstecken
            int hwnd = FindWindow("Shell_TrayWnd", "");
            ShowWindow(hwnd, 0);

            SystemParametersInfo(20, 0, filename, 0x01 | 0x02);
            
            DirectoryInfo ds = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Desktop));

            // in dieses Verzeichnis werden alle Dateien vom Desktop hin verschoben
            string BackupDirectory = @"C:\backupdesktopfun\";

            if (!Directory.Exists(BackupDirectory))
                Directory.CreateDirectory(BackupDirectory);

            foreach (FileInfo f in ds.GetFiles())
            {
                if (!File.Exists(BackupDirectory + f.Name))
                    f.MoveTo(BackupDirectory + f.Name);
            }

            foreach (DirectoryInfo d in ds.GetDirectories())
            {
                if (!Directory.Exists(BackupDirectory + d.Name))
                    d.MoveTo(BackupDirectory + d.Name);
            }

            Application.Exit();
        }
    }

Dienstag, 20. Juli 2010

Alle Fenster minimieren / Desktop anzeigen

Um mit einem C# - Programm den gleichen Effekt wie die Taskleistenverknüpfung "Desktop anzeigen" zu erhalten, alle Fenster also zu minimieren, müssen wir ein wenig tiefer in die Systemarchitektur eindringen und ein älteres COM - Objekt benutzen.
Das Component Object Model ist eine Plattformtechnik zur Verwaltung von Objekten. In .Net werden diese Komponenten eigentlich nicht mehr benutzt, doch, wie man sieht, ab und zu braucht man diese trotzdem noch. COM - Objekte können durch ihre GUID (Globally Unique Identifier) - Nummer angesprochen werden, die ein Objekt eindeutig referenziert.
Diese Nummer ist jedoch für uns Menschen nicht verständlich und schwer zu merken, deshalb gibt es weiterhin die ProgID (Programmatic IDentifier), welche leichter verständlich ist (z.B. in der Form Projekt.Klasse).
Die ProgIDs sind in der Registry mit den GUIDs verknüpft.

Um jetzt alle Programme zu minimieren, brauchen wir den COM - Typ Shell.Application. .Net kann einen entsprechenden Typ aus der Registry mit der Funktion Type.GetTypeFromProgID() suchen. Ausgehend von dem gefundenen Typ kann über die Funktion Activator.CreateInstance() ein Objekt des Typs instanziert werden. Mit InvokeMember() kann schließlich eine Funktion des Objekts aufgerufen werden. Genug der Theorie, hier einfach der Code zur Minimierung:

Type typeShell = Type.GetTypeFromProgID("Shell.Application"); // COM - Objekt per ProgID aus Registry referenzieren und laden
object objShell = Activator.CreateInstance(typeShell); // Instanz von Shell.Application anlegen
typeShell.InvokeMember("MinimizeAll", System.Reflection.BindingFlags.InvokeMethod, null, objShell, null); // Funktion MinimizeAll aufrufen

Beim Aufruf von InvokeMember() gibt der erste Parameter an, das die Funktion MinimizeAll benutzt werden soll, der 2. ein Binding Flag (hier besagt es, dass die Funktion aufgerufen werden soll) und der 4., dass dabei das Objekt objShell benutzt werden soll.

Dateien / Ordner kopieren

In den vorigen Posts haben wir gesehen, wie man prüft, ob Dateien oder Ordner bereits existieren, wie man diese Elemente verschiebt und wie man alle Elemente in einem Ordner aufzählt.
Das Kopieren von Dateien ist ebenfalls mit einem Befehl erledigt:

System.IO.File.Copy("C:\\Quelldatei.txt", "C:\\Testordner\\Zieldatei.txt", true);

Die Pfade müssen vorhanden sein, beispielsweise führt das Fehlen des Ordners "Testordner" zu einer Exception.
Der 3. Parameter ist optional und gibt an, ob die Datei überschrieben werden soll, falls sie schon existiert (true bedeutet überschreiben, wird der Parameter nicht übergeben wird false angenommen).
Ironischerweise ist aber das Kopieren eines Ordners um einiges komplizierter als das Verschieben.
Das Verschieben eines Elements bedeutet einfach einen Eingriff in das Dateisystem, dem Computer wird lediglich mitgeteilt, dass sich der Pfad zum Element geändert hat.
Beim Kopieren muss die Datei oder der Ordner dupliziert werden, es muss also der Inhalt kopiert werden und das neu entstandene Element muss im Dateisystem "angemeldet" werden. Bei Ordnern führt das zu dem Problem, dass die komplette Ordnerhierarchie samt Unterordnern etc. nachgebildet werden muss - in C# gibt es hierfür keine eigene Funktion!
Deshalb werden wir hier jetzt eine basteln.
Die Funktion arbeitet rekursiv, sie ruft sich zuerst immer selbst mit allen Unterordnern auf und bildet so die Ordnerstrukturen nach. Dann werden alle Dateien im aktuellen Ordner kopiert (using System.IO; vorausgesetzt):

        private void CopyDirectory(string sourceDirectory, string destDirectory)
        {
            DirectoryInfo ds = new DirectoryInfo(sourceDirectory); // diese Klasse wird zum Auslesen des Verzeichnisinhaltes benötigt

            // Zielverzeichnis anlegen falls noch nicht vorhanden
            if (!Directory.Exists(destDirectory))
                Directory.CreateDirectory(destDirectory);

            // Rekursiv CoypDirectory() mit allen Unterverzeichnissen aufrufen,
            // auch im Zielordner die Verzeichnisstruktur beibehalten.
            foreach (DirectoryInfo d in ds.GetDirectories())
            {
                CopyDirectory(d.FullName, destDirectory + "\\" + d.Name);
            }

            // Alle Dateien in ds durchlaufen, GetFiles() liefert jedoch kein File - Objekt sondern ein FileInfo - Objekt zurück.
            // Dieses besitzt aber ähnliche Eigenschaften, MoveTo() und CopyTo() sind z.B. auch vorhanden.
            foreach (FileInfo f in ds.GetFiles())
            {
                f.CopyTo(destDirectory + "\\" + f.Name, true);
            }
        }

Ein Beispielaufruf könnte so aussehen:
CopyDirectory("C:\\Quellordner", "C:\\Zielordner");

Montag, 19. Juli 2010

Alle Dateien / Ordner in Ordner auflisten

Zum Ausgeben aller Dateien oder Ordner in einem bestimmten Ordner wird in C# die Klasse System.IO.DirectoryInfo benötigt.
Diese wird mit dem gewünschten Ordner initialisiert und bietet dann zum Ausgeben des Ordnerinhalts 2 Funktionen: GetFiles() und GetDirectories(). Erstere liefert ein Array von System.IO.FileInfo Objekten zurück, welche alle im Ordner vorhandenen Dateien repräsentieren. Zweitere liefert ein Array vom Typ System.IO.DirectoryInfo zurück, in dem alle Unterordner des gewählten Ordners gespeichert sind.
Folgendes Beispielkonsolenprogramm gibt auf der Konsole alle Dateien und Ordner im Order "C:\Testordner" aus:

            System.IO.DirectoryInfo ParentDirectory = new System.IO.DirectoryInfo("C:\\Testordner");

            foreach (System.IO.FileInfo f in ParentDirectory.GetFiles())
            {
                Console.WriteLine("Datei: " + f.Name);
            }

            foreach (System.IO.DirectoryInfo d in ParentDirectory.GetDirectories())
            {
                Console.WriteLine("Ordner: " + d.Name);
            }

Auf Udo's Blog gibt es außerdem eine kleine Erweitung, um rekursiv auch Dateien aus Unterordnern aufzulisten.

Samstag, 17. Juli 2010

Datei / Ordner verschieben

Zum Verschieben einer Datei oder eines Ordners mit C# wird die Funktion Move() der Klassen System.IO.File oder System.IO.Directory benötigt:

System.IO.File.Move("C:\\Quelldatei.txt", "C:\\Testordner\\Zieldatei.txt");
System.IO.Directory.Move("C:\\Quellordner", "C:\\Testordner\\Zielordner");

Bei beiden Varianten gibt der erste Parameter den Pfad zur zu verschiebenden Datei / zum verschiebenden Ordner an und der zweite den Pfad zur Zieldatei / zum Zielordner.
Der Pfad muss hierbei existieren (im Beispiel muss z.B. der Ordner C:\Testordner vorhanden sein), ansonsten wirft die Funktion eine Exception.

Prüfen ob Datei / Ordner bereits vorhanden ist

In diesem und in den nächsten Posts werde ich ein paar Operationen zeigen, die mit Dateien und Ordner durchgeführt werden können. In C# werden hierfür meistens die Klassen System.IO.File und System.IO.Directory verwendet.
Dieser Post zeigt, wie man testen kann, ob eine Datei bzw. ein Ordner schon existiert:

bool OrdnerExisitiert = System.IO.Directory.Exists("C:\\test");
bool DateiExistiert = System.IO.File.Exists("C:\\testdatei.txt");

Donnerstag, 15. Juli 2010

Taskleiste verstecken

Wollen wir mit C# die Taskleiste ein- / oder ausblenden (meistens wollen Programmierer diese verstecken), benötigen wir die WinAPI.
Um das Fensterhandle (sozusagen die Erkennungsmarke) der Taskleiste zu erhalten, wird die Funktion FindWindow() benötigt. Näheres auf diesem Blog zu dieser Funktion und warum wir in diesem Fall nicht die .Net Klasse System.Diagnostics.Process verwenden können, könnt ihr im vorigen Post lesen.
Wir können die Taskleiste nun mit unserem Programm "greifen" und manipulieren, als nächstes wollen wir sie ein- / ausblenden. Hierzu wird die API - Funktion ShowWindow() benötigt.
Sie erwartet zwei Parameter: Als erstes einen Integer Wert, welcher das Fensterhandle angibt und als zweites einen weiteren Integer Wert, der die durchzuführende Aktion beschreibt. Alle möglichen Werte können hier nachgelesen werden. Wir benötigen die Werte 0 zum Verstecken und 1 zum Anzeigen des Fensters, weitere mögliche sind zum Beispiel 3 (Maximieren) und 6 (Minimieren).
Folgendes Beispielprogramm versteckt die Taskleiste beim Laden des Formulars (System.Runtime.InteropServices für den DLL - Import muss mittels using eingebunden sein):

        [DllImport("user32.dll")]
        private static extern int FindWindow(string className, string windowText);

        [DllImport("user32.dll")]
        private static extern int ShowWindow(int hwnd, int command);

        private void Form1_Load(object sender, EventArgs e)
        {
            int TaskbarWindowHandle = FindWindow("Shell_TrayWnd", "");
            ShowWindow(TaskbarWindowHandle, 0);
        }

Fenster auswählen mit FindWindow()

Mit .Net Mitteln kann man einen bestimmten laufenden Prozess über die Funktionen System.Diagnostics.Process.GetProcessesByName() und System.Diagnostics.Process.GetProcessesById() auswählen. Man erhält so eine Instanz der Klasse Process, die den gewünschten Prozess speichert. Diese Variable kann dann manipuliert und ausgelesen werden, so kann man beispielsweise den Prozess beenden oder die ID auslesen.
Leider reichen diese Mittel nicht immer aus. Ein Prozess kann unter einem Namen verschiedene Fenster kapseln, eine C# - Anwendung kann beispielsweise mehrere Formulare aufrufen. Möchten wir ein einzelnes Fenster gezielt - und nicht den ganzen Prozess - ansprechen, müssen wir auf die WinAPI zurückgreifen.
In dieser gibt es die Funktion FindWindow().
Sie erwartet zwei Strings als Parameter: Der erste Parameter wird meistens className genannt und bezeichnet den Klassennamen des gewünschten Prozesses / des gewünschten Fensters. Der zweite Parameter, windowText, bezeichnet den Titel des auszuwählenden Fensters. Wird keiner angegeben, werden alle Fenster ausgewählt.
Ein Beispiel folgt im nächsten Post, dort wird die Taskleiste ausgewählt und ausgeblendet. Über System.Diagnostics.Process.GetProcessesByName() kann die Taskleiste nicht ausgewählt werden, da sie sich im Prozess "explorer.exe" "verbirgt", sie kann nur gezielt durch Angabe des richtigen Klassennamens separiert und ausgewählt werden.

Mittwoch, 14. Juli 2010

Desktophintergrund ändern

Dieser Post zeigt, wie man mit C# den Desktophintergrund (das Wallpaper) ändert. Hierzu muss man die WinAPI - Funktion SystemParametersInfo() einbinden.
Dessen Signatur sieht so aus:
SystemParametersInfo(UInt32 uiAction, UInt32 uiParam, String pvParam, UInt32 fWinIni)

Die Funktion ist vielseitig einsetzbar zum Setzen von Systemparametern, der erste Parameter gibt die auszuführende Aktion an. Mögliche Werte des Parameters können hier nachgeschlagen werden, 4 bezeichnet beispielsweise das Aktivieren / Deaktivieren des Systemwarntons und 20 wird zum Setzen des Desktophintergrunds benötigt.
Als 2. Parameter kann bei manchen Aufrufen ein Integer - Parameter übergeben werden, dieser wird hier nicht benötigt. Wir benötigen jedoch den 3. Parameter, der in diesem Fall den Pfad zur Datei angibt, die als Desktophintergrund benutzt werden soll.
Die Datei muss ein Bild im Format .bmp sein, andere Formate müssen zuerst in dieses konvertiert werden.
Der letzte Parameter fWinIni gibt an, wie das System die Änderungen übernehmen soll. In diesem Fall wird 0x01 übergeben, was bedeutet, dass der neue Wert des Parameters in das Benutzerprofil geschrieben werden soll. Mögliche Werte des Parameters können hier nachgeschlagen werden.

Eine Beispielimplementierung (using System.Runtime.InteropServices; vorrausgesetzt):

        [DllImport("user32.dll")]
        private static extern Int32 SystemParametersInfo(UInt32 uiAction, UInt32 uiParam, String pvParam, UInt32 fWinIni);

        private static UInt32 SPI_SETDESKWALLPAPER = 20;
        private static UInt32 SPIF_UPDATEINIFILE = 0x1;

        private void button1_Click(object sender, EventArgs e)
        {
            string Filename = "C:\\1.bmp";
            SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, Filename, SPIF_UPDATEINIFILE);
        }
Auf Udo's Blog gibt es eine nette Anwendung dieses Posts, und zwar ein Programm welches tageszeitabhängig den Desktop Hintergrund wechselt.

Dienstag, 13. Juli 2010

Double Buffering mit C#

Die Standardklasse für Grafikoperationen bei Windows Forms-Anwendungen Graphics ist für umfangreichere Grafikaufgaben leider schlichtweg zu langsam.
Bei Anwendungen, bei denen viel gezeichnet wird, fängt das Formular an zu flimmern und zu ruckeln, die Anwendung läuft stockend.
Das höchster der Gefühle für die Grafikprogrammierung sind externe Programmbibliotheken wie DirectX oder XNA, soweit möchte ich heute zwar nicht ausholen, dafür euch aber eine andere nützliche Technik vorstellen: Das (Double) Buffering. Bei "normalen" Zeichenmethoden wird jedes zu zeichnende Element einzeln auf das Formular gezeichnet, der Benutzer sieht, wie das Bild sich langsam aufbaut. Dadurch kann es vorkommen, dass das Formular sogar mitten im Prozess neugezeichnet werden muss, der Prozess verlangsamt sich noch mehr. Beim Buffering wird ein sogenannter Buffer verwendet. Dieser speichert die zu zeichnenden Elemente zwischen, statt direkt auf das Formular, werden sie erst einmal auf den Buffer gezeichnet, abschließend wird der komplette Buffer auf das Formular übertragen. So werden alle Elemente quasi als ein Bild gleichzeitig übertragen, der Benutzer sieht direkt das volle Bild und keinen langsamen Aufbau, die Anwendung läuft flüssig. Wird sogar der Buffer nocheinmal gebuffert, spricht man von DoubleBuffering.
Um den Effekt dieser Technik zu demonstrieren, habe ich ein kleines Beispielprogramm geschrieben.
Das Programm zeichnet nach einem Klick auf einen Button viele Kreise und Schriftzüge zufällig auf das Formular, die erste Version benutzt nur Standard Graphics - Methoden:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.Bounds = Screen.PrimaryScreen.Bounds;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Graphics G = this.CreateGraphics();
            Random Rnd = new Random();

            for (int i = 0; i < 1000; i++)
            {
                G.DrawEllipse(new System.Drawing.Pen(Color.Red), Rnd.Next(this.Width), Rnd.Next(this.Height), Rnd.Next(100), Rnd.Next(100));
            }

            for (int i = 0; i < 1000; i++)
            {
                G.DrawString("Test test test", new System.Drawing.Font("Arial", 12), new SolidBrush(Color.Green), new PointF(Rnd.Next(this.Width), Rnd.Next(this.Height)));
            }
        }
    }
}

Der Zeichenprozess dauert seine Zeit, man sieht, wie die einzelnen Elemente nacheinander auf das Formular gezeichnet werden.

Eine automatische Möglichkeit, DoubleBuffering zu aktivieren, ist es, dem Formular dementsprechende Styles hinzuzufügen, die folgende Zeile kann z.B. in die Funktion Form1_Load() kopiert werden:
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);

Sie bewirkt, dass wie oben beschrieben ein Buffer verwendet wird.
Der Zeichenprozess wird schon deutlich beschleunigt.
Wer aber mehr Kontrolle über die verwendeten Buffers etc. haben möchte, muss diese manuell programmieren.
Die letzte Version des Programms verwendet manuell angelegte Buffer Objekte. Die Zeichenelemente werden zuerst in den Buffer gezeichnet und nach Abschluss der Zeichenprozedur geschlossen auf das Formular:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // benötigte Bufferobjekte
        BufferedGraphicsContext currentContext;
        BufferedGraphics myBuffer;

        private void Form1_Load(object sender, EventArgs e)
        {
            this.Bounds = Screen.PrimaryScreen.Bounds;
      
            // Buffer auf Formular initialisieren
            currentContext = BufferedGraphicsManager.Current;
            myBuffer = currentContext.Allocate(this.CreateGraphics(), this.DisplayRectangle);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Random Rnd = new Random();

            // Elemente zuerst auf den Buffer zeichnen

            for (int i = 0; i < 1000; i++)
            {
                myBuffer.Graphics.DrawEllipse(new System.Drawing.Pen(Color.Red), Rnd.Next(this.Width), Rnd.Next(this.Height), Rnd.Next(100), Rnd.Next(100));
            }

            for (int i = 0; i < 1000; i++)
            {
                myBuffer.Graphics.DrawString("Test test test", new System.Drawing.Font("Arial", 12), new SolidBrush(Color.Green), new PointF(Rnd.Next(this.Width), Rnd.Next(this.Height)));
            }

            // abschließend den kompletten Buffer "rendern", d.h. auf das Formular zeichnen
            myBuffer.Render();
        }
    }
}

Montag, 12. Juli 2010

Matrix Screensaver

Mit dem Wissen aus dem vorigen Post, wie Screensaver zu programmieren sind, habe ich mich dran gesetzt, einen Matrix Screensaver zu programmieren.
In dem Screensaver werden die angezeigten Buchstaben als Instanzen einer Klasse verwaltet, die eine Funktion zum Zeichnen der Buchstaben implementiert.
In bestimmten Zeitabständen wird eine ganze Reihe mit Buchstaben erzeugt und alle Buchstaben werden regelmäßig nach unten bewegt.
Die Zeichnung erfolgt nicht direkt auf das Formular, sondern in einen Buffer. Erst, wenn alle Buchstaben neu in diesen gezeichnet wurden, wird der komplette Buffer auf das Formular gerendert.
Ich werde hier einfach mal die .scr - Datei und die Sourcefiles posten, wer möchte kann das ganze ja gerne mal ausprobieren.
Ganz im Matrixstyle laufen bei dem Bildschirmschoner grüne Buchstaben über den Bildschirm:

Den kommentierten Quellcode könnt ihr euch hier herunterladen, die ausführbare Screensaver - Datei gibt's hier.
Eine Erläuterung, wie man Bildschirmschoner mit C# erstellt und wie diese in Windows einzubinden sind, gibt es im vorigen Post.

Nachtrag: Ein Video zu diesem Projekt gibt's jetzt auch auf Youtube.

Samstag, 10. Juli 2010

Bildschirmschoner (Screensaver) erstellen

Ein Bildschirmschoner (engl. Screensaver) ist eigentlich nur eine ausführbare .exe - Datei, die als Endung .scr besitzt und ein paar besondere Programmeigenschaften hat.
Diese Tatsache nutzen wir jetzt aus, um uns mit C# selber einen Bildschirmschoner zusammenzubasteln.
Das Screensaver - Programm erwartet beim Starten einen Parameter, ob der Bildschirmschoner gestartet ("/s") oder konfiguriert ("/c") werden soll.
Da Windows Forms-Anwendungen nur schwer mit Startparametern gestartet werden können, legen wir ein neues C# - Projekt vom Typ einer Konsolenanwendung an.
Zu dem Projekt fügen wir dann ein neues Element, ein Formular (Windows Form) hinzu.
In der Main - Funktion der Konsolenanwendung fragen wir den übergebenen Parameter ab, entspricht dieser dem String "/s", starten wir das hinzugefügte Formuar (im Beispiel ist dessen Name ScreenSaverForm):

System.Windows.Forms.Application.Run(new ScreensaverForm());

Damit dieses Formular im Vollbildmodus läuft, wie man es von Bildschirmschonern gewöhnt ist, setzen wir beim Aufrufen dessen in der Funktion Load() die folgenden Eigenschaften:

        private void ScreensaverForm_Load(object sender, EventArgs e)
        {
            FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; // Rahmen entfernen
            this.Bounds = Screen.PrimaryScreen.Bounds; // Größe über kompletten Bildschirm strecken
            Cursor.Hide(); // Mauszeiger ausblenden
            TopMost = true; // Formular als oberstes Programm definieren, so wird auch die Taskleiste ausgeblendet

        }

Führt man dieses Programm in diesem Zustand testweise aus, ist die Konsolenanwendung sowie die Taskleiste immer noch sichtbar.
Dieses Problem lässt sich beheben, in dem man in den Projekteigenschaften (Rechtsklick auf das Projekt im Projektmappen - Explorer) unter "Anwendung" als Ausgabetyp "Windows-Anwendung" eingestellt.
Nun muss nur noch definiert werden, wann der Bildschirmschoner aufhören soll zu laufen, was der Fall bei irgendeiner Benutzerinteraktion ist.
In den Funktionen ScreensaverForm_KeyDown(), ScreensaverForm_MouseDown() und private void ScreensaverForm_MouseMove() führen wir also dementsprechende Aktionen aus.
Nun der komplette Quellcode, zuerst von der Konsolenanwendung StartupProgramm.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ScreenSaver
{
    class StartupProgramm
    {
        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                // Konfiguration
                if ((args[0].ToLower().Trim().Substring(0, 2) == "/c")) // längere Umwandlung nötig da config - Parameter in der Form "/c:xxxxx" übergeben wird
                {
                    // alternativ hier Formular mit Konfigurationsmöglichkeiten laden
                    System.Windows.Forms.MessageBox.Show("Keine Einstellungen verfügbar.");
                }
                // Start
                else if (args[0].ToLower() == "/s")
                {
                    // Bildschirmschonerformular laden
                    System.Windows.Forms.Application.Run(new ScreensaverForm());
                }
            }
        }
    }
}

Und der Quellcode des Formulars ScreenSaverForm.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ScreenSaver
{
    public partial class ScreensaverForm : Form
    {

        Point PrevPos; // vorherige Position des Mauszeigers

        public ScreensaverForm()
        {
            InitializeComponent();
        }

        private void ScreensaverForm_Load(object sender, EventArgs e)
        {
            FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; // Rahmen entfernen
            this.Bounds = Screen.PrimaryScreen.Bounds; // Größe über kompletten Bildschirm strecken
            Cursor.Hide(); // Mauszeiger ausblenden
            TopMost = true; // Formular als oberstes Programm definieren, so wird auch die Taskleiste ausgeblendet

        }

        private void ScreensaverForm_MouseMove(object sender, MouseEventArgs e)
        {
            // Falls vorheriger Punkt gesetzt wurde, neue Position mit alter vergleichen.
            if (!PrevPos.IsEmpty)
            {
                // wurde die Maus bewegt, Formular beenden
                if (PrevPos.X != e.X || PrevPos.Y != e.Y)
                    Close();
            }
            // Vorherige Position setzen (ist nur beim 1. Aufruf der Fall).
            // Close() alleine als Anweisung in dieser Funktion würde den falschen Effekt erzielen,
            // da das Ergeignis MouseMove beim Starten des Formulars immer einmal ausgelöst wird.
            PrevPos = new Point(e.X, e.Y);
        }

        private void ScreensaverForm_MouseDown(object sender, MouseEventArgs e)
        {
            Close();
        }

        private void ScreensaverForm_KeyDown(object sender, KeyEventArgs e)
        {
            Close();
        }
    }
}

Das Formular ist noch leer, kann aber ganz nach Belieben mit Steuerelementen und Funktionalitäten gefüllt werden, bei der Erstellung eines Bildschirmschoners sind der Kreativität hierüber keine Grenzen gesetzt.
Damit Windows diesen Bildschirmschoner erkennt, muss die .exe - Datei des Projekts in den Ordner C:\WINDOWS\system32 kopiert werden und die Endung in ".scr" geändert werden.
Einen komplett ausgebauten Screensaver mit Konfigurationstool etc. findet ihr im nächsten Post, dort könnt ihr euch meinen selbst programmierten Matrix Screensaver herunterladen.

Programm Vollbild laufen lassen

Anfänglich betrachtet ist man vielleicht leicht versucht zu sagen, in C# könnte man keine "richtigen" Programme entwerfen, wie z.B. mit C++, weil Windows - Programme alle gleich aussehen, mit dem vom .Net Studio vordefinierten Rahmen etc.
Dabei ganz man diesen Rahmen ganz leicht ausblenden und das Formular Vollbild auf dem kompletten Bildschirm laufen lassen, wobei auch die Taskleiste im unteren Bereich verdeckt ist.
3 Eigenschaften müssen dafür (am besten beim in der Funktion Form_Load() oder im Konstruktor) gesetzt werden, folgendes Beispiel erklärt diese:

private void Form1_Load(object sender, EventArgs e)
{
    this.FormBorderStyle = FormBorderStyle.None; // Rahmen ausblenden
    this.TopMost = true; // Formular ganz im Vordergrund, also auch vor der Taskleiste anzeigen
    this.Bounds = Screen.PrimaryScreen.Bounds; // Formulargröße auf Größe des Bildschirms festlegen
}

Freitag, 9. Juli 2010

Prozess beenden

In diesem Post stand, wie man mit C# Prozesse startet, heute möchte ich euch zeigen, wie man sie wieder beendet.
Im .Net Studio gibt es dazu zwei Funktionen, beide aus der Klasse System.Diagnostics.Process.
Die erste Funktion heißt CloseMainWindow(). Sie schließt den Prozess auf die "sanfte" Art, hängt er also oder wartet auf eine Benutzereingabe (d.h. ein Dialogfeld befindet sich im Vordergrund, wie z.B. "Speichern vor Beenden?"), wird die Beendingung nicht erzwungen.
Das Beenden eines Prozesses kann man mit der Funktion Kill() erzwingen. Die beiden Funktionen lassen sich mit "Task beenden" und "Prozess beenden" aus dem Taskmanager vergleichen.
Um Zugriff auf einen laufenden Prozess zu kriegen, benötigt man z.B. die Funktion Process.GetProcessesByName(). Diese erwartet einen String als Parameter, der den Prozessnamen angibt und liefert ein Array vom Typ System.Diagnostics.Process zurück, in dem alle Prozesse mit übereinstimmendem Namen gespeichert sind.
Die folgende Funktion versucht, den übergebenen Prozess zuerst mittels CloseMainWindow() zu beenden, wartet dann 500ms und beendet diesen - sofern noch nicht erfolgt - per Kill().

        private void TerminateProcess(string processName)
        {
            Process[] MatchingProcesses = Process.GetProcessesByName(processName);

            foreach (Process p in MatchingProcesses)
            {
                p.CloseMainWindow();
            }

            System.Threading.Thread.Sleep(500);

            MatchingProcesses = Process.GetProcessesByName(processName);

            foreach (Process p in MatchingProcesses)
            {
                p.Kill();
            }
        }

Beim Aufrufen der Funktion ist zu beachten, dass der Prozessnamen ohne die Endung ".exe" übergeben werden muss, also so z.B.:

private void Form1_Load(object sender, EventArgs e)
{
    TerminateProcess("firefox");
}

Donnerstag, 8. Juli 2010

Bildschirmauflösung abfragen

Der folgende Code zeigt, wie man die aktuelle Bildschirmauflösung ausliest:

int Width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width;
int Height = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;
MessageBox.Show("Die Bildschirmauflösung beträgt: " + Width.ToString() + " * " + Height.ToString());

Mittwoch, 7. Juli 2010

Screenshot erstellen

Heute möchte ich euch mal zeigen, wie man mit der Programmiersprache C# einen Screenshot erstellt.
Ich fange direkt mit dem Quellcode einer dafür geschriebenen Funktion an, den ich im Anschluss erläutern werde:

private Bitmap TakeScreenshot(bool onlyForm)
{
int StartX, StartY;
int Width, Height;

StartX = (onlyForm) ? (this.Left) : (0);
StartY = (onlyForm) ? (this.Top) : (0);
Width = (onlyForm) ? (this.Width) : (Screen.PrimaryScreen.Bounds.Width);
Height = (onlyForm) ? (this.Height) : (Screen.PrimaryScreen.Bounds.Height);

Bitmap Screenshot = new Bitmap(Width, Height);
Graphics G = Graphics.FromImage(Screenshot);

G.CopyFromScreen(StartX, StartY, 0, 0, new Size(Width, Height), CopyPixelOperation.SourceCopy);
return Screenshot;
}

Die zum Erstellen eines Bildschirmabdrucks verwendete Funktion ist CopyFromScreen() aus der Klasse Graphics.
Mit der hier benutzten Überladung akzeptiert sie 6 Parameter. Als erste beiden werden hier StartX und StartY übergeben, sie bezeichnen die Koordinaten der linken oberen Ecke des abfotografierten Bereichs auf dem Bildschirm. Als Parameter 3 und 4 wird hier beide Male "0" übergeben, diese Parameter bezeichnen die Koordinaten der linken oberen Ecke des Screenshots auf der Leinwand. Werden hier also andere Werte übergeben, ist der Screenshot im resultierenden Bitmap verschoben. Der 5. Parameter legt die Größe des Screenshots fest und der 6. gibt an, dass die Punkte originalgetreu auf die Leinwand kopiert werden sollen.
Die geschriebene Funktion TakeScreenshot() erwartet eine Boolean Variable als Parameter. Ist sie auf false gesetzt, wird der komplette Bildschirm gedruckt (die Leinwandgröße und die Werte StartX und StartY werden entsprechend dem Formular gesetzt), andernfalls wird nur das aktive Formular gedruckt. Zur Fragezeichen - Schreibweise siehe hier.
Auf folgende Weise kann dann z.B. ein Screenshot des kompletten Bildschirms in eine PictureBox geladen werden:
pictureBox1.Image = TakeScreenshot(false);

Montag, 5. Juli 2010

Freien Speicherplatz und Gesamtgröße eines Laufwerks herausfinden

Die Laufwerke des Computers lassen sich in C# wie im Post Partitionen aufzählen beschrieben mit der Klasse DriveInfo aufzählen. Diese Klasse stellt außerdem weitere Laufwerkinformationen zur Verfügung, wie zum Beispiel den freien Speicherplatz und die Gesamtgröße des Laufwerks.
Das folgende Beispielprogramm liest alle Laufwerke ein, iteriert über diese, sucht so das angegebene Laufwerk und zeigt anschließend Speicherplatzinformationen zu diesem an (System.IO muss mittels using eingebunden sein):

DriveInfo[] Drives = DriveInfo.GetDrives(); // alle Laufwerke auslesen

foreach (DriveInfo d in Drives)
{
if (d.Name == "C:\\") // Größe von C (durch gewünschtes Laufwerk ersetzen) ausgeben
Console.WriteLine((d.TotalFreeSpace / (1024 * 1024 * 1024)) + " GB von insgesamt " + (d.TotalSize / (1024 * 1024 * 1024)) + " GB frei.");
}

Die Klasse DriveInfo besitzt einerseits die hier verwendete Eigenschaft TotalFreeSpace und andererseits die Eigenschaft AvailableFreeSpace. Der Unterschied besteht darin, dass erste den gesamten verfügbaren freien Speicherplatz auf dem Laufwerk angibt, und zweite nur den verfügbaren freien Speicherplatz, den der aktuelle Benutzer zur Verfügung hat.

Sonntag, 4. Juli 2010

Tastatureingaben auf das Formular umleiten / Tastenvorschau

Angenommen, ihr möchtet verschiedene Tastenkombinationen global im Formular definieren. Zum Beispiel soll das Programm beim Druck auf die Esc - Taste beendet werden oder über "Strg + F" eine Suchfunktion aufgerufen werden.
Auf dem Formular befinden sich aber wahrscheinlich Steuerelemente wie Textboxen o.ä., die dem Formular den Fokus "wegnehmen". Drückt der Benutzer "Esc" und ist momentan ein anderes Steuerelement ausgewählt, wird die Tastendruckbehandlung dieses Steuerelements aufgerufen und nicht die des Formulars, wo der entsprechende Code zum Beenden des Programms steht.
Eine Möglichkeit wäre nun, alle Tastaturereignisse in eine Funktion umzuleiten, doch so wären steuerelementspezifische Ereignisse schwierig zu behandeln.
Glücklicherweise lässt sich dieses Problem ganz leicht in C# lösen, wir müssen einfach die Eigenschaft KeyPreview des Formulars auf true setzen. Jetzt werden alle Tastendrücke auch dem Formular gemeldet.
Dem Formular werden die Tastendrücke jedoch nur mitgeteilt, wenn es aktiv ist, Tastendrücke in anderen Anwendungen werden nicht eingefangen.
Diese Funktion zu realisieren ist wesentlich schwieriger, hierfür braucht man globale Tastaturhooks, die tief auf Systemebene in die Nachrichtenstruktur eingreifen und Ereignisse an das Formular weiterleiten.

Samstag, 3. Juli 2010

RAM Auslastung anzeigen

Um mit C# die RAM Auslastung des Computers anzuzeigen, benutzen wir die Klasse PerformanceCounter. Eine genaue Erklärung dieser gibt es im vorigen Post zum Auslesen der CPU Auslastung.
Zum Auslesen der Arbeitsspeicherauslastung wird in diesem Fall jedoch "Memory" als CategoryName festgelegt und "Available MBytes" als CounterName.
Außerdem braucht NextValue() keinen Referenzpunkt, das einmalige Aufrufen der Funktion liefert bereits das richtige Ergebnis.
Ein PerformanceCounter mit oben beschriebenen Eigenschaften liest nicht exakt die RAM Auslastung aus, sondern genauer gesagt die Größe des RAMs, die noch nicht von Programmen belegt ist (in MB), d.h. den freien Speicherplatz im Arbeitsspeicher.
Im Taskmanager wird diese Größe im Reiter "Systemleistung" unter "Physikalischer Speicher - Verfügbar" angezeigt.
Und jetzt das Beispielprogramm:

// hiervor die üblichen using Anweisungen
using System.Diagnostics;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

        }

        PerformanceCounter RAMCounter = new PerformanceCounter(); // Instanz der Klasse PerformanceCounter

        private void Form1_Load(object sender, EventArgs e)
        {
            // RAMCounter mit beschriebenen Werten initialisieren
            RAMCounter.CategoryName = "Memory";
            RAMCounter.CounterName = "Available MBytes";
        }

        // beim Klick auf einen Button wird im Beispielprogramm die RAM - Auslastung abgefragt
        private void button1_Click(object sender, EventArgs e)
        {
            GetRAMUsage();
        }

        private void GetRAMUsage()
        {
            // das Ergebnis von NextValue() wird per MessageBox angezeigt
            MessageBox.Show((RAMCounter.NextValue()).ToString());
        }
    }
}

Freitag, 2. Juli 2010

CPU Auslastung auslesen

Um mit C# die Auslastung des Prozessors auszulesen, bedienen wir uns der Klasse PerformanceCounter.
Mit dieser können allgemein Leistungsdaten gemessen werden und, wenn wir dieser Klasse die richtigen Eigenschaften übergeben, eben auch die der CPU.
Bei einer Instanz der Klasse PerformanceCounter müssen folgende Eigenschaften gesetzt werden, damit die Auslastung des Prozessors ausgelesen werden kann:
  • CategoryName = "Processor". Hiermit geben wir dem Programm die grobe Kategorie der zu überwachenden Leistungsdaten mit.
  • CounterName = "% Processor Time". Mit dieser Eigenschaft wird der genaue Name des Leistungsindikators festgelegt.
  • InstanceName = "_Total". Kategorien können in verschiedene Instanzen aufgeteilt werden, mit dieser weisen wir das Programm an, die Instanz "_Total" zu nehmen, mit welcher immer die am meisten gefragten Daten aus dieser Kategorie gemeint sind.

Zum Abrufen der Leistungsdaten brauchen wir schließlich die Funktion NextValue().
Bei manchen Leistungsindikatoren aber, und beim Abrufen der Prozessorauslastung ist das der Fall, braucht NextValue() einen Referenzpunkt zum Vergleichen der aktuellen Daten mit dem vorigen Wert.
Da die beiden Aufrufe der Funktionen zeitlich etwas versetzt erfolgen müssen, habe ich im folgenden Beispielprogramm die Abfrage der CPU - Auslastung in einen eigenen Thread verpackt, damit der Fluss des Hauptprogramms nicht unterbrochen wird:

// hiervor die üblichen using Anweisungen
using System.Diagnostics;
using System.Threading;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

        }

        PerformanceCounter CPUCounter = new PerformanceCounter(); // Instanz der Klasse PerformanceCounter
        Thread CPUWatcher; // Thread zum Abfragen der CPU - Auslastung

        private void Form1_Load(object sender, EventArgs e)
        {
            // CPUCounter mit beschriebenen Werten initialisieren
            CPUCounter.CategoryName = "Processor";
            CPUCounter.CounterName = "% Processor Time";
            CPUCounter.InstanceName = "_Total";
          
        }

        // beim Klick auf einen Button wird im Beispielprogramm die CPU - Auslastung abgefragt
        private void button1_Click(object sender, EventArgs e)
        {
            // hierfür wird der Thread CPUWatchner (neu) initialisiert und gestartet
            CPUWatcher = new Thread(new ThreadStart(GetCPUUsage));
            CPUWatcher.Start();
        }

        // im Thread CPUWatcher wird diese Funtkion gestartet
        private void GetCPUUsage()
        {
            // die Funktion ruft NextValue() 2 mal mit einem Abstand von jeweils 1 Sekunde auf,
            // das Ergebnis wird per MessageBox angezeigt
            CPUCounter.NextValue();
            System.Threading.Thread.Sleep(1000);
            MessageBox.Show(CPUCounter.NextValue().ToString());
        }
    }
}