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)

Samstag, 14. Januar 2012

Drucken mit C#

In diesem Post möchte ich euch zeigen, wie man mit C# den Drucker ansteuern kann.
Hierfür gibt es bestimmt einige Möglichkeiten, ich benutze hier die Graphics Eigenschaft des Druckereignisses, das auszudruckende Resultat muss dann in dieser gezeichnet werden.

Zuerst muss System.Drawing.Printing eingebunden werden.
Die folgende Zeile initialisiert uns das benötigte Objekt, welches das zu druckende Dokument repräsentiert.

PrintDocument PrintDoc = new PrintDocument();

Installierte Drucker sind in PrinterSettings.InstalledPrinters gespeichert. In dieser Liste liegen die Namen der Drucker als Strings vor, zum Setzen des zu benutzenden Druckers benutzen wir PrintDoc.PrinterSettings.PrinterName = PrinterSettings.InstalledPrinters[Index].
Gedruckt wird das Dokument über den Befehl PrintDoc.Print().
Vorher fügen wir dem Druckdokument allerdings eine Ereignisbehandlung für sein Druckereignis hinzu: PrintDoc.PrintPage += new PrintPageEventHandler(PrintPage).
Hierbei ist PrintPage eine beliebige Funktion mit folgender Signatur: void PrintPage(object sender, PrintPageEventArgs e).
Diese wird nun aufgerufen, wann immer ein Dokument gedruckt wird.
In dieser können wir dann das gewünschte Druckresult erstellen, indem wir es in e.Graphics zeichnen. Es können z.B. auch Bilder eingelesen werden und dort hinein gezeichnet werden.
Zum Abschluss der komplette Quellcode, welcher eine Linie und einen String druckt:

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.Printing;


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

        private void Print()
        {
            PrintDocument PrintDoc = new PrintDocument();

            List<string> Printers = new List<string>();
            foreach (string p in PrinterSettings.InstalledPrinters)
                Printers.Add(p);

            // Alle verfügbaren Drucker sind in Printers gespeichert, man könnte den Benutzer daraus auswählen lassen.
            // In diesem Beispiel wird einfach immer Drucker 0 gewählt.
            PrintDoc.PrinterSettings.PrinterName = PrinterSettings.InstalledPrinters[0];

            PrintDoc.PrintPage += new PrintPageEventHandler(PrintPage);
            PrintDoc.Print();
        }

        void PrintPage(object sender, PrintPageEventArgs e)
        {
            e.Graphics.DrawLine(new Pen(Color.Black), new Point(0, 0), new Point(100, 100));
            e.Graphics.DrawString("Ich kann jetzt mit C# drucken", new Font("Times New Roman", 12), new SolidBrush(Color.Black), new Point(45, 45));

        }
    }


}

Donnerstag, 5. Januar 2012

String mit Zeichen füllen

Es gibt einen netten kleinen Trick, mit dem man schnell einen String mit einem beliebigen Zeichen auffüllen kann.
Die Klasse string stellt nämlich den folgenden Konstruktor bereit:
new string(meinzeichen, anzahl).
Hierdurch wird ein neuer String bestehend aus anzahl Zeichen mit dem Wert meinzeichen angelegt.
Folgendes Beispiel erzeugt einen String bestehend aus 30 Trennstrichen:

string filled = new string('-', 30);

Freitag, 30. Dezember 2011

Ein gutes Jahr 2012!

Ich wünsche allen Lesern einen guten Rutsch und ein frohes neues Jahr 2012.
In C# ausgedrückt:

Form1.Designer.cs:

namespace WindowsFormsApplication1
{
    partial class Form1
    {
        /// <summary>
       /// Erforderliche Designervariable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
       /// Verwendete Ressourcen bereinigen.
        /// </summary>
        /// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Vom Windows Form-Designer generierter Code

        /// <summary>
       /// Erforderliche Methode für die Designerunterstützung.
       /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.textBox2 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            //
            // textBox1
            //
            this.textBox1.Location = new System.Drawing.Point(12, 12);
            this.textBox1.Multiline = true;
            this.textBox1.Name = "textBox1";
            this.textBox1.ReadOnly = true;
            this.textBox1.Size = new System.Drawing.Size(374, 328);
            this.textBox1.TabIndex = 0;
            //
            // timer1
            //
            this.timer1.Enabled = true;
            this.timer1.Interval = 300;
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
            //
            // textBox2
            //
            this.textBox2.Location = new System.Drawing.Point(433, 346);
            this.textBox2.Name = "textBox2";
            this.textBox2.Size = new System.Drawing.Size(12, 20);
            this.textBox2.TabIndex = 1;
            //
            // Form1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(393, 349);
            this.Controls.Add(this.textBox2);
            this.Controls.Add(this.textBox1);
            this.Name = "Form1";
            this.Text = "Grüße vom C# Blog";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Timer timer1;
        private System.Windows.Forms.TextBox textBox2;
    }
}



Form1.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 WindowsFormsApplication1
{
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
        }

        string[] images = new string[22];
        private void init()
        {
            images[0] = NewLine(23) + "".PadLeft(60, ' ') + "|";

            for (int j = 1; j < 14; j++)
            {
                images[j] = NewLine(23 - j) + "".PadLeft(60, ' ') + "|" + NewLine(1);
                images[j] += "".PadLeft(60, ' ') + "|";
            }

            images[14] = NewLine(9) + "".PadLeft(60, ' ') + ".";
            images[15] = NewLine(8) + "".PadLeft(58, ' ') + "....." + NewLine(1) + "".PadLeft(58, ' ') + "....." + NewLine(1) + "".PadLeft(58, ' ') + ".....";

            images[16] = Explode(7, 54, 7);
            images[17] = Explode(9, 52, 5);
            images[18] = Explode(11, 50, 3);
            images[19] = Explode(13, 48, 1);
            images[20] = Explode(7, 54, 7);
            images[21] = "Frohes Neues!";
        }

        private string Explode(int size, int left, int height)
        {
            string result = "";
            result += NewLine(height);
            result += "".PadLeft(left + 2 + (size - 5) + 1, ' ') + "|" + NewLine(1);

            for (int j = 5; j <= size; j++)
            {
                result += "".PadLeft(left + 2 + (size - 5) + 1 - (j - 4) - 1, ' ');
                for (int z = 0; z < 5  + (j - 5) * 2; z++)
                {
                    result += ".";
                }
                result += NewLine(1);
            }

            result += "".PadLeft(left, ' ') + "-";
            for (int j = 0; j <= 5 + (size - 5) * 2; j++)
            {
                result += ".";
            }
            result += "-";
            result += NewLine(1);

            for (int j = size; j >= 5; j--)
            {
                result += "".PadLeft(left + 2 + (size - 5) + 1 - (j - 4) - 1, ' ');
                for (int z = 0; z < 5 + (j - 5) * 2; z++)
                {
                    result += ".";
                }
                result += NewLine(1);
            }

            result += "".PadLeft(left + 2 + (size - 5) + 1, ' ') + "|" + NewLine(1);

            return result;
        }

        private string NewLine(int nr)
        {
            string result = "";
            for (int i = 0; i < nr; i++)
            {
                result += Environment.NewLine;
            }
            return result;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (i < images.Length)
            {
                textBox2.Focus();
                textBox1.Text = images[i];
                i++;
            }
        }

        int i = 0;
        private void Form1_Load(object sender, EventArgs e)
        {
            init();
        }
    }
}

(mein grafisches Vorstellungsvermögen ist nicht sehr gut)

Freitag, 16. Dezember 2011

Danke

Wie aufmerksame wie unaufmerksame Leser sicherlich schon bemerkt haben, erscheinen leider seit längerem keine Posts mehr von mir und auch die Antworten auf eure Kommentare dauern leider sehr lange, da ich ziemlich viel zu tun habe im Moment.

Ich versuche jedoch, wenigstens die Kommentare so gut ich kann zu beantworten und freue mich auch definitiv über jeden Eintrag und jedes Interesse an diesem Blog.

Dafür möchte ich mich hier herzlich bei allen Lesern bedanken, ich hoffe dieser Blog hilft ein wenig und bereitet ein bisschen Freude beim Lesen.

Freitag, 1. Juli 2011

Maus steuern

Auf User Anfragen erscheint heute ein Post zum Thema, wie man mit C# die Maus steuern kann, sie also komplett auf dem ganzen Bildschirm bewegen und z.B. Klicks simulieren kann.
Ich habe schon einen Post zum Simulieren von Tastendrücken geschrieben, welches ganz einfach mit der Funktion SendKeys() umgesetzt werden kann.
Die Maus zu steuern ist nicht mehr ganz so einfach, es gibt (noch) keine eingebaute .Net Funktion, wir müssen auf nicht verwaltete System Funktionen zurückgreifen.
Kernstück ist die P/Invoke Funktion SendInput(). Mit dieser Funktion können verschiedene Kommandos an den ausführenden Computer gesendet werden, zum Beispiel direkte Hardwareeingaben, Tastatur- und eben Mausereignisse (im folgenden Inputs genannt).
Die Funktion erwartet 3 Parameter: Der 1. beschreibt die Anzahl der übergebenen Inputs, der 2. enthält eine Referenz auf die tatsächlichen Inputdaten, der 3. speichert die Größe eines einzelnen Inputs.
Den 2. Parameter muss vom Typ einer Struktur sein, welche mindestens den Typ des Inputs (also Hardware, Tastatur, Maus ...) sowie die Daten des Inputs enthält.
Die Struktur habe ich Input genannt.
Mausinputdaten selber haben mehrere Daten, wie die X - und Y - Koordinate, welche ich in der Struktur MouseInput zusammengefasst habe.

Zuerst möchte ich nun das Bewegen der Maus beschreiben: Wir brauchen also eine Instanz der Struktur Input mit den gewünschten Zielmauskoordinaten.
Als Typ des Inputs setzen wir 0, welches ein Mausereignis symbolisiert.
Die Mausdaten repräsentiert durch eine Instanz von MouseInput lassen wir uns über die Funktion CreateMouseInput() erstellen.
Diese erhält alle benötigten Parameter und setzt diese im zurückgegeben Mausereignis.
Zum Bewegen der Maus sind nur die Werte für X und Y wichtig (Zielkoordinaten) sowie für DwFlags. Dieses Feld kann bestimmte Flags aufnehmen, wie zum Beispiel den Druck eines Mausbuttons.
In diesem Fall übergeben wir aber 2 vorher definierte Konstanten (so haben wir leicht zu merkende Namen anstatt Zahlen), MOUSEEVENTF_ABSOLUTE und MOUSEEVENTF_MOVE, um anzuzeigen, dass wir die Maus bewegen wollen. Erstere sagt aus, dass wir ein Mausereignis nicht nur im aktuellen Fenster herbeiführen wollen, sondern auf dem ganzen Bildschirm, zweite zeigt die gewünschte Bewegung der Maus an.

Das Simulieren eines (Links-)Klicks funktioniert ähnlich.
Wir müssen wieder einen Input mit den entsprechenden Daten erstellen, verwenden diesmal aber ein Array der Länge 2, da wir zuerst das Drücken der linken Maustaste und anschließend das Loslassen dieser simulieren wollen.
Über die Funktion CreateMouseInput() lassen wir uns dafür 2 MouseInputs erstellen und übergeben für den Wert von DwFlags die entsprechenden Konstanten.

Ich hoffe, diese kurze Erläuterung sowie der folgende Code helfen:


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.Runtime.InteropServices;


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

        // P/Invoke Funktion u.a. zum Steuern der Maus
        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize);

        /// <summary>
       /// Struktur für Mausdaten
        /// </summary>
        struct MouseInput
        {
            public int X; // X - Koordinate
            public int Y; // Y - Koordinate
            public uint MouseData; // Mausdaten, z.B. für Mausrad
            public uint DwFlags; // weitere Mausdaten, z.B. für Mausbuttons
            public uint Time; // Zeit des Events
            public IntPtr DwExtraInfo; // weitere Informationen
        }

        /// <summary>
       /// Oberstruktur für InputDaten der Funktion SendInput
        /// </summary>
        struct Input {
            public int Type; // Typ des Inputs, 0 für Maus  
            public MouseInput Data; // Mausdaten
        }

        // Konstanten für Mausflags
        const uint MOUSEEVENTF_LEFTDOWN = 0x0002; // linken Mausbutton drücken
        const uint MOUSEEVENTF_LEFTUP = 0x0004; // linken Mausbutton loslassen
        const uint MOUSEEVENTF_ABSOLUTE = 0x8000; // ganzen Bildschirm ansprechen, nicht nur aktuelles Fenster
        const uint MOUSEEVENTF_MOVE = 0x0001; // Maus bewegen

        private MouseInput CreateMouseInput(int x, int y, uint data, uint time, uint flag)
        {
            // aus gegebenen Daten Objekt vom Typ MouseInput erstellen, welches dann gesendet werden kann
            MouseInput Result = new MouseInput();
            Result.X = x;
            Result.Y = y;
            Result.MouseData = data;
            Result.Time = time;
            Result.DwFlags = flag;
            return Result;
        }

        private void SimulateMouseClick()
        {
            // Linksklick simulieren: Maustaste drücken und loslassen
            Input[] MouseEvent = new Input[2];
            MouseEvent[0].Type = 0;
            MouseEvent[0].Data = CreateMouseInput(0, 0, 0, 0, MOUSEEVENTF_LEFTDOWN);

            MouseEvent[1].Type = 0; // INPUT_MOUSE;
            MouseEvent[1].Data = CreateMouseInput(0, 0, 0, 0, MOUSEEVENTF_LEFTUP);

            SendInput((uint)MouseEvent.Length, MouseEvent, Marshal.SizeOf(MouseEvent[0].GetType()));
        }

        private void SimulateMouseMove(int x, int y) {
            Input[] MouseEvent = new Input[1];
            MouseEvent[0].Type = 0;
            // Maus bewegen: Flags ABSOLUTE (ganzen Bildschirm verfügbar machen) und MOVE (bewegen)
            MouseEvent[0].Data = CreateMouseInput(x, y, 0, 0, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE);
            SendInput((uint)MouseEvent.Length, MouseEvent, Marshal.SizeOf(MouseEvent[0].GetType()));
        }


         private void button1_Click(object sender, EventArgs e)
         {
             // Maus testweise bewegen
             SimulateMouseMove(0, 0);
             // und anschließend klicken
             SimulateMouseClick();
         }
    }
}