Abou Chleih

{the magic lies between the brackets}

Menü Schließen

Schlagwort: Winforms

Programmiertechnisch Windows Forms-Anwendungen erstellen

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

  • winforms
  • wpf

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

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

 public partial class MyForm:Form 

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

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

Nun erstellen wir ein Objekt dieser Form:

 MyForm myFrmObj = new MyForm(); 

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

 TextBox txtContent = new TextBox(); 

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

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

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

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

myFrmObj.Controls.Add(txtContent);

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

Was jetzt noch wichtig ist, ist die Option

txtContent.AutoSize = true;

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

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

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

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

public MyForm()
{
     InitializeComponent();
}

Mehr Informationen zum Handlern und Events findet man im MSDN.

Generelle Infos findet man auf der Supportseite von Microsoft.

[C#] Sortieren einer DataTable-Spalte nach einer Spalte eines anderen DataTable

Ich hatte die Aufgabe eine Spalte einer DataTable bzw. die DataTable(1) an sich nach der Spalte einer anderen DataTable(2) zu sortieren ohne die Reihenfolge der zweiten DataTable(2) zu verändern.
Um zu verdeutlichen, wie die Aufgabenstellung genau aussah, habe ich hier Screenshots der DataTables visualisiert in einem DataGridView im Ist/Soll-Zustand:

Die Sort-Methode des DataGridView, ebenso wie die Sort-Eigenschaft eines DataViews,

DataTable.DefaultView.Sort = "Spaltenname ASC"

bieten nur die Möglichkeit eine Spalte auf(Ascending)- bzw. absteigend(Descending) zu sortieren.

So musste ich mir dann einen eigenen Weg finden, die Aufgabe zu lösen. Die Lösung möchte ich euch natürlich nicht vorenthalten.

 private void sortList(string pExcelDataColumnName, string pSortByListColumnName)
        {
            excelDataTable.Columns.Add("TempOrderColumn", typeof(Int32)); //Dient später zum Sortieren der Zeilen

            foreach (DataRow row in excelDataTable.Rows)
            {
                foreach (DataColumn column in excelDataTable.Columns)
                {
                    if (column.ColumnName == pExcelDataColumnName)
                    {
                        object tempRowIndex = GetRowIndexOfCellValue(row
, pSortByListColumnName); row["TempOrderColumn"] = tempRowIndex; } } } DataView dv = excelDataTable.DefaultView; dv.Sort = "TempOrderColumn ASC"; excelDataTable = dv.ToTable(); excelData_dataGridView.Refresh(); }

In dieser Methode erstellen wir erstmal eine weitere Spalte, welche uns später zum Sortieren dient, danach durchlaufen wir alle Zellen der unsortierten Liste(zweimal foreach) und fragen ab, ob sich einer der Zellen in der zuvor definierten Spalte befindet(Parameter der Methode).
Sollte dies der Fall sein, so rufen wir folgende Methode auf und übergeben den Inhalt der aktuellen Zelle, sowie den Namen der zu vergleichenden Spalte:

private object GetRowIndexOfCellValue(object pCellValue, string pSortByListColumnName)
        {
            foreach (DataRow row in sortByDataTable.Rows)
            {
                foreach (DataColumn column in sortByDataTable.Columns)
                {
                    if (column.ColumnName == pSortByListColumnName && pCellValue.Equals(row
)) { return sortByDataTable.Rows.IndexOf(row); } } } return -1; }

Diese Methode durchläuft sämtliche Zellen der Liste, nach welcher wir sortieren wollen und prüft ob sich die Zelle in der als Parameter übergeben Spalte befindet(indem sie den übergeben Spaltennamen mit jenem der aktuellen Spalte(Column) vergleicht) und ob der Inhalt der Zelle mit jenem Inhalt übereinstimmt, welcher als Parameter übergeben wurde.

Sollte beide Abfragen ‚wahr‘ sein, so geben wir den Index der aktuellen Zeile zurück. Dafür verwenden wir die folgende Methode

return sortByDataTable.Rows.IndexOf(row);

Der Rückgabewert wird dann mittels

row["TempOrderColumn"] = tempRowIndex;

in die temporäre Spalte geschrieben und zu guter Letzt mit folgendem Code sortiert:

            DataView dv = excelDataTable.DefaultView;
            dv.Sort = "TempOrderColumn ASC";
            excelDataTable = dv.ToTable();
            excelData_dataGridView.Refresh();

Dies ist nötig, da man einer DataTable nicht direkt sortieren kann und somit den Umweg über das Erstellen eines DataViews gehen muss, diesen dann mittels

dv.Sort = "Spaltenname ASC" //ASC steht für 'ascending' aufsteigend
//DESC steht für 'descending' absteigend

sortiert und das DataView dann wieder in die DataTable wandelt.

Das Ergebnis sieht wie folgt aus:
2013-08-23 15_51_20-Excel nach Liste sortieren

Nun muss ich nur noch eine Excel-Tabelle aus der DataTable bzw dem DataGridView erstellen. Wie man dies bewerkstelligt, zeige ich in meinem nächsten Blogeintrag

Threadübergreifende Zugriffe(Invoke)

Zu Threading verweise ich auf: Threading mit :Net 2.0

Oftmals ist es nötig aus externen Threads heraus auf die GUI zuzugreifen. Da diese meist in dem Hauptthread der Anwendung läuft bzw. in einem eigenständigen GUI-Thread, wird der direkte Zugriff auf das Control während des Debuggings(nicht bei einer kompilierten Anwendung!) mit einer InvalidOperationException quittiert.

Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement richTextBox1 erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.

In .NET gilt nämlich, dass nur der Thread, welcher das Control erstellt hat, auf dieses zugreifen darf. Hier werde ich nun zwei mögliche Arten beschreiben, die es ermöglichen das Control zu verändern.

1. Möglichkeit

Umleitung in den Thread, welcher das Control erstellt hat.

Dazu brauchen wir einen Delegaten

Zuerst einmal wird der Import des Namespaces
System.Threading

Danach erstellen wir ein nicht initialisiertes Objekt eines Threads:

Thread demoThread;

Gefolgt von einer Methode, welche das Control anspricht.

private void DemoThreadMethod()
        {
            for (int i = 0; i < 100; i++)
            {
                // Sicher auf das Steuerelement zugreifen
                // Dazu wird ein s.g. generischer Delegat benötigt,
                //in welchem ein voller GUI-Zugriff möglich ist
                if (this.resultLabel.InvokeRequired)
                {
                    MethodInvoker UpdateLabel = delegate
                    {
                        resultLabel.Text = i.ToString();
                    };
                    Invoke(UpdateLabel);
                }
                else
                {
                    // Der Zugriff erfolgt in demselben Thread
                    this.resultLabel.Text = i.ToString();
                }

                // Kleine Pause zur Veranschaulichung der Threadaktivität
                Thread.Sleep(50);
            }
        }

Zu guter Letzt müssen wir nur noch den Thread initialisieren:

            demoThread = new Thread(DemoThreadMethod);
            demoThread.Start(); 

Um den Unterschied zwischen threaded und unthreaded sehen möchte, muss einfach die Methode DemoThreadMethod direkt aufrufen.
Da dadurch das Label vom Mainthread aus geändert werden soll, gibt InvokeRequired „false“ zurück.

Beim Beenden des Programms muss darauf geachtet werden, dass der Thread in welchem der Invoke ausgeführt wird, beendet wird. Ansonsten kommt es zu einem ObjectDisposedException, da der Thread noch läuft, das Objekt(der Invoke des Labels), auf welches er aber zugreift bereits durch das Beenden verworfen wurde.

2. Möglichkeit

Über die Komponente BackgroundWorker.

Der BackgroundWorker selbst bietet bereits eine Methode um die GUI zu aktualisieren: Die ReportProgress-Methode

Um diese zu verwenden muss die Eigenschaft WorkerReportsProgress auf ‚true‘ gesetzt werden, ansonsten tritt eine InvalidOperationException auf.

Die Methode ruft das ProgressChanged-Event auf, welche eine Methode aufruft, welche sich im Thread befindet, der den BackgroundWorker aufgerufen hat.

Folgender Code ruft die oben beschrieben Vorgehensweise auf.
Zuerst benötigen wir zwei Eventhandler, welche unsere Methoden aufrufen. Diese platziert man am besten im Konstruktor

backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);

Folgende Methode wird aufgerufen, wenn der BackgroundWorker mittels RunWorkerAsync gestartet wird.

void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
	for (int i = 0; i <= maxAmount; i++)
	{
		if (backgroundWorker.CancellationPending)
		{
			e.Cancel = true;
		}
		int percentage = (int)((i/(double)maxAmount) * 100);
		this.backgroundWorker.ReportProgress(percentage); //Ruft das ProgressChanged-Event auf, welches die unten stehende Methode aufruft (definiert beim EventHandler)
 }
 e.Result = "Finished";
}

Folgende Methode wird im Thread aufgerufen, welche den BackgroundWorker gestartet hat. Meistens also im Thread, in welchem sich die GUI befindet

void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    BackgroundWorker_progressBar.Value = e.ProgressPercentage;
}

© 2018 Abou Chleih. Alle Rechte vorbehalten.

Thema von Anders Norén.