594.442 aktive Mitglieder*
3.921 Besucher online*
Kostenfrei registrieren
Einloggen Registrieren

C# Schnippsel, Programmierung häufiger Problemstellungen

Beitrag 16.04.2014, 20:21 Uhr
sharky2014
Level 7 = Community-Professor
*******

Numerische Eingabe in eine Textbox unter WinForms verarbeiten

Anwender soll eine Dezimalzahl eingeben in ein Textfeld mit Fließkomma. Wegen dem Fließkomma scheidet maskedTextBox aus, die kennt nur Festkomma, und die Eingabe ist ungemütlich und hakelig.

Der Anwender darf daher seine Eingabe in eine textBox eingeben, als freien Text, evtl. auch gar keinen (Enter), bzw. irgendeinen beliebigen Unsinn, bzw. asdf, OHNE DASS EIN LAUFZEITFEHLER AUSGELÖST WIRD. Wobei noch das Problem wäre, wenn er statt des erlaubten Kommas 47,11 den unerlaubten Punkt 47.11 eingibt (den keine Online-Banking-Software akzeptiert), was dann werden soll.

Die .NET Funktion Convert.ToInt32 errechnet aus 47.11 = 4711, während sie aus 47,11 den korrekten Wert bestimmt. Ich denke, die Online-Banking-Software benutzt .NET, von daher.

Erstmal ist aber die Frage, wann ist die Eingabe beendet, daß sie geprüft werden kann.

Die Eigenschaft textBox1_text_changed kann hier nicht verwendet werden, weil sie zeichenweise prüft. Damit kann der Anwender seine Eingabe nicht zuende führen.

Um den Anwender erstmal alles eingeben zu lassen, und am Ende zu prüfen, wird festgelegt, daß die Eingabe mit ENTER abgeschlossen wird.

Erst danach wird die Eingabe überprüft.

Um das zu kriegen, muß die Eigenschaft textBox1.KeyPress aktiviert sein, mit der die IDE folgenden Funktionsrumpf auswirft:

private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == 13)
this.textBox1.Text = "Enter";
}

In den nun zur Verdeutlichung reingesetzt wurde, wie man ENTER abruft. ENTER hat den Ascii Wert 13.

Nachdem das geklärt ist, muß der Text in eine Dezimalzahl double umgewandelt werden. Und es muß verhindert werden, daß eine unsinnige Eingabe einen Laufzeitfehler auslöst.

Dazu formulieren wir, daß im Falle ENTER eine Prüfmethode aufgerufen wird, die aus dem string eine Fließkommazahl erzeugt und zurückliefern soll:

public const int ENTER =13;

private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
double d;
if (e.KeyChar == ENTER)
d=string_to_double(this.textBox.Text); // aus dem Text eine Fließkommazahl auslesen
this.textBox1.Text=d.ToString(); // und das Ergebnis der Umwandlung wieder als string anzeigen

}

Jetzt zu dieser Methode. Ein Entwurf wäre ,solche Dinge in einer eigenen Klasse abzulegen. Und häufig benötigte Konstanten ebenso, und zwar static:

static public char EOL = '\n';
static public int ENTER = 13;
static public int ESC = 27;
static public char PUNKT = '.';
static public char KOMMA = ',';
static public char BLANK = ' ';
static public double PI = 3.14159;
static public string cleanSonderzeichen= "!§$%&/()=?+-.,;:€äÄöÖüÜß";


Dann kann man die Methode string_to_double z. B. so definieren:


static public double string_to_double(string s, bool punkt_to_komma)
/*1*/ {
/*2*/ double d = 0.0;
/*3*/ double error = -1.0;
/*4*/ if (s == null) return error;
/*5*/ if (s.Trim().Length == 0) return error;
/*6*/ string kopie = "";
/*7*/ if (punkt_to_komma)
/*8*/ for (int i = 0; i < s.Length; i++)
/*9*/ if (s[i] == PUNKT) kopie += KOMMA;
/*10*/ else kopie += s[i];
/*11*/ try
/*12*/ {
/*13*/ d = Convert.ToDouble(kopie);
/*14*/ return d;
/*15*/ }
/*16*/ catch
/*17*/ {
/*18*/ return error;
/*19*/ }
/*20*/ } // fu


Und sich im Anschluß überlegen: viel zuviel Code.

Erstmal, was das Onlinebanking zwar nicht erlaubt, aber eigentlich logisch ist: Wenn der Anwender bei der Eingabe einer Zahl einen Punkt setzt, was meint er damit? Natürlich ein Dezimaltrennzeichen. Daher schmeißen wir die if-Bedingung raus. Und wir schmeißen die Schleife raus, denn dafür gibt es eine Methode der Klasse String.

Wenn wir in alter C-Manier den Code jetzt mal straffen wollen, kommt folgende sehr viel kompaktere Version heraus:


static public double string_to_double(string s)
/*1*/ {
/*2*/ if (s != null && (s = s.Trim()).Length != 0)
/*3*/ try
/*4*/ {
/*5*/ double d = Convert.ToDouble(s.Replace(PUNKT, KOMMA));
/*6*/ return d;
/*7*/ }
/*8*/ catch{}
/*9*/ return -1.0;
/*10*/ } // fu


Aus 20 mach 10.

Zeile 2: Wir prüfen die null Bedingung und die Stringlänge in einer Zeile, weil wir davon ausgehen, daß von links nach rechts geprüft wird (was zutrifft). Würde die Methode .Length() vor der null Bedinung geprüft werden, wäre es ein Laufzeitfehler, wenn null==true wäre. Paßt hier aber, weil Bedingungen mit if( von links nach rechts abgearbeitet werden

In die Zeile 2 fügen wir noch eine Zuweisung ein, nämlich daß der String s = s.Trim(), also alle Leerzeichen vorn und hinten abgeschnitten werden. Dadurch erwischen wir alle strings, die nur aus Leerzeichen bestehen.

Diesen bereits mit .Trim() bearbeiteten String müssen wir in eine Fließkommazahl umwandeln, aber zuvor noch PUNKT durch KOMMA ersetzen. Das erledigt die Zeile 5 mit der Methode String.Replace(suche nach, ersetze mit). Da der Ausdruck in Klammern gesetzt ist, wird hier zwar auch von links nach rechts, aber der Klammerausdruck vorrangig behandelt, so daß diese Zeile die gesamte Schleife in dem Entwurf zuvor abdeckt. Alle Vorkommen von Punkt werden in Komma umgewandelt, und erst danach die Convert-Methode aufgerufen.

Zeile 8: catch ohne Anweisungen, daher die Klammern nur formal

Zeile 10: unser error-Wert. Würde man für alle solche FUnktionen einen bestimmten Errorwert wünschen, könnte man den public definieren, dann stünde da return ERROR. Ein lokaler Error-Wert wie in dem ersten Entwurf ist jedoch nicht aussagekräftig.

Diese Methode formt also aus jedem x-beliebigen String, egal ob null, leer oder aus unzulässigen Zeichen bestehend, einen Rückgabewert, ohne einen Laufzeitfehler auszulösen.

Der Beitrag wurde von sharky2014 bearbeitet: 16.04.2014, 20:34 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 16.04.2014, 21:00 Uhr
V4Aman
Level 7 = Community-Professor
*******

Hast du langeweile ?


--------------------
Gruß V4Aman


__________________________________________________________________________

Alle sagten: "Das geht nicht." Dann kam einer, der wusste das nicht, und hat's einfach gemacht.
TOP    
Beitrag 17.04.2014, 15:51 Uhr
sharky2014
Level 7 = Community-Professor
*******

Die Klasse B (B= Basisfunktionen) ist static.

Es wird ja überhaupt ein Hype gemacht mit der Instanziierung von Klassen.

Für meine Begriffe sind die Instanziierungen in mehr als 90 Prozent der Fälle völlig überflüssig. Für kaufmännische oder technische Anwendungen jedenfalls könnten 90% der Klassen auch static deklariert werden. Nur, das scheint zu dem Messias-Paradigma der OOP nicht recht zu passen. Man biegt es hin, um der OOP einen Heiligenschein zu verpassen.

Hier ist ein Entwurf einer Klasse B (B=Basisfunktionen), die static ist.

Es heißt immer: von so einer Klasse kann man keine Instanz erzeugen.

Es müßte heißen:

Man braucht davon keine Instanz zu erzeugen. Weil es viel bequmer ist, als eine Instanz zu erzeugen, keine Instanz erzeugen zu müssen, sondern einfach den Buchstaben B zu tippen.

Daher der kurze Name: B

Alle Elemente lassen sich mit B. ansprechen, also B plus den Punkt, dann zeigt Intellisence alle verfügbaren Variablen und Methoden an.

Siehe Abbildung: Alle Methoden und Variablen der eigenen Klasse B lassen sich via Intellisense abrufen.

Noch bequemer geht es ja nun wirklich nicht, oder?

Ist doch komisch, oder? Die Klasse Console ist ebenso static wie die Klasse Math.

Hier die Definition einiger Basismethoden und -Variablen:

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

namespace FiBu3000
{
public static class B // B= Basisfunktionen
{
static private string zugangscode;
static public string Zugangscode {get{return zugangscode;}}

static B()
{
zugangscode = "qwert";
}

static public char EOL = '\n';
static public int ENTER = 13;
static public int ESC = 27;
static public char PUNKT = '.';
static public char KOMMA = ',';
static public char BLANK = (char)32;
static public double PI = 3.14159;
static public string cleanSonderzeichen= "!§$%&/()=?+-.,;:€äÄöÖüÜß";
public const int NUMERROR = 0;
static public string DoubleFormat2 = "#,##0.00";


static public int string_to_int (string s)
{
if (s != null && (s=s.Trim()).Length>0)
try
{
int i= Convert.ToInt32(s);
return i;
}
catch
{
}
return NUMERROR;
} // fu

static public double string_to_double(string s)
{
if (s != null && (s = s.Trim()).Length > 0)
try
{
double d = Convert.ToDouble(s.Replace(PUNKT, KOMMA));
return d;
}
catch
{
}
return NUMERROR;
} // fu
static public bool erlaubtesSonderzeichen(char c)
{
foreach (char zeichen in cleanSonderzeichen)
if (zeichen== c)
{
return true;
}
return false;
}

static public string cleanString(string s)
{
string cleanstr = "";
if (s != null && s.Length > 0)
{
foreach (char charelement in s)
{
if (charelement == BLANK) cleanstr += charelement;
else if (charelement >= '0' && charelement <= '9') cleanstr += charelement;
else if (charelement >= 'a' && charelement <= 'z') cleanstr += charelement;
else if (charelement >= 'A' && charelement <= 'Z') cleanstr += charelement;
else if (erlaubtesSonderzeichen(charelement)) cleanstr += charelement;
}
}
return cleanstr;
}
} // cl
} //ns


Wenn wir in der laufenden Programmierung B. drücken, zeigt uns Intellisense das, was in der Abbildung zu sehen ist. B ist einfacher zu drücken als CLASSBBASISDEFINITIONEN oder sonstwas, das heißt, die selbstdefinierten Eigenschaften haben wir immer mit einem Buchstaben verfügbar, warum, weil der Name so kurz ist und

Weil die Klasse STATIC ist.

Ausgehend von der Bequemlichkeit bei der Programmierung, mit der man möglichst große Fehlersicherheit verbinden will, ist das eine sehr gute Vorgehensweise.

Man sollte immer vermeiden, irgendeine Bearbeitung irgendeines Wertes mehrmals zu schreiben und sie im Programm zu verteilen.

Und natürlich nicht nachprogrammieren, was die .NET Bibliothek bereits verfügbar macht.

Wenn sie es GENAU SO verfügbar macht.

Wenn nicht, ist eine individuelle Methode besser.

Wenn wir die Klasse ansprechen, ist z. B. folgender Code die Folge:

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

private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
string result = "";
if (e.KeyChar == B.ENTER)
{
e.Handled = true;
this.textBox2.Text = (B.string_to_double(this.textBox1.Text)).ToString(B.DoubleFormat2);
this.textBox1.Text = "";
string s;
}
}


Wir sehen hier B.ENTER, B.string_to_double und B.DoubleFormat2 (=2 Nachkomma und führende Null).

Sichert gegen Laufzeitfehler und gegen Tippfehler.

Ist bequem einzutippen.

Der Beitrag wurde von sharky2014 bearbeitet: 17.04.2014, 15:54 Uhr
Angehängte Datei(en)
Angehängte Datei  ClassB.jpg ( 62.77KB ) Anzahl der Downloads: 20
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 17.04.2014, 16:10 Uhr
sharky2014
Level 7 = Community-Professor
*******

QUOTE (sharky2014 @ 17.04.2014, 16:51 Uhr) *
Die Klasse B (B= Basisfunktionen) ist static.


Was macht die Klasse B?

Sie liefert globale Konstanten und schreibgeschützte Variablen.

Sie beschäftigt sich mit der Typumwandlung (cast), mit dem Ziel, jeden Laufzeitfehler sicher abzufangen und anstelle dessen einen Default zurückzuliefern. . Das kriegt man im laufenden Code nur mit vielen Zeilen hin, die man immer wieder neu tippen muß, also redundant, daher ausklammerund eine Methode dafür aufrufen.

Ascii-Code in C#:

static public char BLANK = (char)32;

sagt, daß der Ascii-Wert 32, nämlich Leerzeichen, dem char BLANK zugewiesen wird. Ist identisch mit BLANK=' ', nur daß man in der Proportionalschrift des Editors nicht sicher sein kann, ob '' oder ' ', daher (char) 32. Der Unicode hat als Untermenge den Ascii-Code, so daß der alte Ascii-Code mit 255 Zeichen noch fortbesteht. Erst oberhalb 255 fängt Unicode an.

Den Zugangscode (Login) sollte man natürlich nicht als Zeichenkette ablegen, ist hier nicht ganz ernst gemeint. Vielmehr kan man dafür einen Chiffrier-Algorithmus setzen, der sich nicht auslesen läßt.

Den Umgang mit Sonderzeichen kann man mit einer .NET- Bibliotheksfunktion nicht lösen. Ganz sicher sollte man das Zeichen '\" vermeiden, welches eine Escape-Sequenz einleitet. Beim Umgang mit strings sollte man auch das " unbedingt vermeiden, weil das zu Codierungsfehlern führt. Die Methode soll verhindern, daß durch Sonderzeichen Fehler bei der Stringverarbeitung und beim SPeichern und Zurücklesen der Dateien auftreten kann (WICHTIG!).

Globale Konstante NUMERROR: setzt man die fehlerhafte Umwandlung auf -1, besteht die Gefahr, daß der Wert unerkannt "durchrutscht" und die Rechnung verfälscht. 0 ist weniger gefährlich, wenn auch weniger aussagekräftig.

Die Klasse B kann man in dieser Weise unendlich fortführen, bis alles beieinander ist, was man ständig braucht.

Man kriegt es, weil STATIC, mit einem B.

Sozusagen BDOT. wink.gif


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 17.04.2014, 16:47 Uhr
sharky2014
Level 7 = Community-Professor
*******

QUOTE (V4Aman @ 16.04.2014, 22:00 Uhr) *
Hast du langeweile ?



Ein relativ kompliziertes Ersatzteil, zu drehen und anschließend zu fräsen, mit Gewindebohrungen. Einige Flächen müssen poliert werden. Ein Riesenaufwand. Oder man kauft es.

Ich werd es natürlich nicht kaufen, sondern selber machen.

Sollte so innerhalb der nächsten 14 Tage erledigt sein.

Hab ich Lust dazu?

Sind die 14 Tage schon um?

Also.

Die Osterwoche wird es bringen.

Da du dich keine 5Cent für C# interessierst, hast du etwa Langeweile? wink.gif


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 17.04.2014, 18:37 Uhr
sharky2014
Level 7 = Community-Professor
*******

Die Klasse operationResult.

Es ist festgelegt, daß eine Methode immer nur einen Rückgabewert eines bestimmten Datentyps haben kann.

Soweit die Theorie.

In C# ist das weitgehend aufgeweicht, weil mit dem Parameter out eine Liste von beliebig vielen Rückgabewerten erzeugt werden kann (das ist Quatsch mit Soße, und überflüssig wie sonstwas, aber der Fall).

In C# ist vieles aufgeweicht. Wer weiß schon, daß es in C# einen goto-Befehl gibt (dieser verfemte Spaghetti-Code Befehl aus uralten Basic Zeiten ist tatsächlich implementiert, selbst Java traut sich das nicht, der unbedingte Sprungbefehl goto ist zwar vorgesehen, wird aber von der Laufzeitumgegung (derzeit) noch nicht unterstützt. Man liest, daß um aus den tiefsten Verzweigungen mehrerer Schleifen zu springen goto der Lösungsansatz wäre.

Dagegen ließe sich argumentieren:

Wieso muß man eigentlich so programmieren, daß man in eine Schleifentiefe von zehn oder mehr if-Bedingungen hineingerät? Also unintelligente Programmierung soll mit goto "repariert" werden.

Und wenn es sich nicht vermeiden lassen können sollte, wieso kann man das nicht mit flags regeln? Also Registerwerten, die dann auf true oder false gestellt werden?

Die Nachteile beim goto sind eigentlich nicht der Sprungbefehl als solcher, sondern erstens, daß die anderen Schleifenbedingungen ungeprüft verpuffen, der Programmierer also nur einen Bruchteil der Information erhält, die er eigentlich haben könnte. Zweitens daß solch ein Code EXTREM FEHLERSENSITIV auf Änderungen reagiert. Wird eine Zeile in der Reihenfolge vertauscht, ist Schicht im Schacht.

Nötig ist das alles gar nicht.

Wir können, anstelle den Rückgabewert aus einer Standardklasse zu entnehmen, z. B. int, double ... eine eigene Klasse für die Werterückgabe deklarieren mit beliebig vielen Feldbezeichnern, die wir bei der Rückgabe der aufgerufenen Methode gerne haben wollen.

Ein Beispiel wäre dieser in dem anderen Thread schon erwähnte Rückgabetyp:

public class operationResult
{
public int datacount; // übermittelt irgendeinen Wert, der interessiert
public string infostring; // Eine Liste aller Fehler
public string strhinweis; // Eine Liste von Warnungen oder Hinweisen, die keine Fehler sind
// Zweckmäßigerweise getrennt durch EOL=EndofLine, damit das lesbar bleibt
public bool hinweis; // Frage ob ein Hinweis vorliegt
public bool erfolg; // wenn fehlerfrei, dann erfolg==true, keine Weiterbehandlung erforderlich

}


Alles, was man mitteilen will, kann man da reinpacken. Und die Klasse beliebig erweitern

Man könnte auch einen Feldbezeicher "ist_müller_milch_noch_im_geschaeft" hineinpacken.

Alles was man will. Wozu dann out nötig sein soll, erschließt sich nicht wirklich. Ebenso ist der ref-Parameter völlig entbehrlich. Der ref Parameter ist deshalb völliger Quatsch (und wurde bestimmt nur eingeführt, weil es den in Ansi-C gibt), weil sowieso alle Verweistypen als Referenz übergeben werden. Das sind alles Zugeständnisse an andere Programmierer-Welten, die die Sprache versauen. Mit der OOP wird der große Hype gemacht, und dann gibt man ref frei. Verstehe das, wer will. Man denkt vielleicht an Niklaus Wirth (den Erfinder von Pascal) und seinen Fehler, von dort auf auf Oberon zu wechseln, welches ihn ins Aus beförderte. Pascal lebte dann noch ein bißchen und erlosch dann. Anders gesagt, was immer mal vorhanden war, soll als Untermenge weiterbestehen. Nun sind die MS-Programmentwickler sicher um Lichtjahre schlauer als ich, aber als Konzept halte ich das trotzdem für falsch.

Wenn man diese Wildwest-Funktionen nicht beachtet, läßt es sich ohne die sehr gut in der Welt von DOTNET und C# zurechtkommen.

Der Beitrag wurde von sharky2014 bearbeitet: 17.04.2014, 18:46 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 17.04.2014, 18:49 Uhr
V4Aman
Level 7 = Community-Professor
*******

Für deinen Text hab ich immer Zeit wink.gif


--------------------
Gruß V4Aman


__________________________________________________________________________

Alle sagten: "Das geht nicht." Dann kam einer, der wusste das nicht, und hat's einfach gemacht.
TOP    
Beitrag 17.04.2014, 19:13 Uhr
sharky2014
Level 7 = Community-Professor
*******

QUOTE (V4Aman @ 17.04.2014, 19:49 Uhr) *
Für deinen Text hab ich immer Zeit wink.gif


WIrklich schade, daß du die Feinheiten zu C# nicht verstehst.

V4A .... gut zu drehen, beim Fräsen eklig. Mit HSS kommt man da nicht wirklich weiter.

Ich mach das Teil über Ostern.

Programmierung ist für mich Entspannung. Oder die Flucht vor der Werkstatt. Ist auch erlaubt. Im Betrieb ist derzeit ziemliche Hektik angesagt. Nach 17 Uhr schleicht man da so raus.

Man muß nicht alles erklären.

Viele Dinge sind einfach, wie sie sind.


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 18.04.2014, 14:05 Uhr
sharky2014
Level 7 = Community-Professor
*******

Der Designer

Der Designer enthält die Daten aller visuellen Elemente der Form. Er arbeitet fast unbeachtet im Hintergrund und man stößt höchstens mal zufällig auf ihn, wenn man einen Funktionsrumpf gelöscht hat und der Compiler meckert.

Der meckert, weil das Element im Designer den Verweis auf einen Ereignishandler hat, der im Code gelöscht wurde. Man kann nun entweder den Ereignishandler im Designer auch löschen (dann ist Ruhe), oder fügt den Funktionsrumpf des Ereignisses durch Eigenschaften->Ereignis unter Blitz auswählen->Doppelklick rechte Spalte wieder hinzu.

Ganz unbestritten ist das Drag&Drop Verfahren, mit dem man mittels der Maus die Elemente auf das Formular zieht, eine sehr gute Hilfe.. Jedoch wenn man viele Elemente exakt auf 1 Pixel ausrichten will, gibt es Probleme. Macht man das mit der Maus, erscheinen Hilfslinien. Die sind aber nicht immer da, wo man möchte. Will man mit dem Pfeiltasten pixelweise verschieben, erscheinen diese Hilfslinien überhaupt nicht.

Um an Oberkante in y oder linksbündig in x auszurichten, sind die Hilfslinien ausreichend. Das geht pixelgenau.

Ein Problem entsteht allerdings, wenn man die an der linken Kante ausgerichteten Elemente in exakt gleichen Abständen untereinander platzieren will. Dafür gibt es kein Raster, so daß die Elemente zwar ziemlichh genau, aber eben nicht pixelgenau ausgerichtet sind (Siehe Abbildung desginer1.jpg. In der Abbildung wurde das etwas übertrieben, zwecks Verdeutlichung). Es geht um die vertikalen Abstände der Elemente zueinander. Diese sollen gleich sein, sind es aber nicht.

Die Koordinaten finden sich wie folgt im Designer, nämlich in der Methode InitializeComponent(), unter der Eigenschaft element.Location:

private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.button3 = new System.Windows.Forms.Button();
this.richTextBox1 = new System.Windows.Forms.RichTextBox();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.radioButton1 = new System.Windows.Forms.RadioButton();
this.radioButton2 = new System.Windows.Forms.RadioButton();
this.radioButton3 = new System.Windows.Forms.RadioButton();
this.radioButton4 = new System.Windows.Forms.RadioButton();
this.radioButton5 = new System.Windows.Forms.RadioButton();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(80, 50);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(150, 50);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
//
// button2
//
this.button2.Location = new System.Drawing.Point(80, 150);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(150, 50);
this.button2.TabIndex = 1;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
//
// button3
//
this.button3.Location = new System.Drawing.Point(80, 244);
this.button3.Name = "button3";
this.button3.Size = new System.Drawing.Size(150, 50);
this.button3.TabIndex = 2;
this.button3.Text = "button3";
this.button3.UseVisualStyleBackColor = true;
//
// richTextBox1
//
this.richTextBox1.Location = new System.Drawing.Point(572, 50);
this.richTextBox1.Name = "richTextBox1";
this.richTextBox1.Size = new System.Drawing.Size(258, 243);
this.richTextBox1.TabIndex = 3;
this.richTextBox1.Text = "";
//
// groupBox1
//
this.groupBox1.Controls.Add(this.radioButton5);
this.groupBox1.Controls.Add(this.radioButton4);
this.groupBox1.Controls.Add(this.radioButton3);
this.groupBox1.Controls.Add(this.radioButton2);
this.groupBox1.Controls.Add(this.radioButton1);
this.groupBox1.Location = new System.Drawing.Point(290, 50);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(241, 226);
this.groupBox1.TabIndex = 4;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "groupBox1";
//
// radioButton1
//
this.radioButton1.AutoSize = true;
this.radioButton1.Location = new System.Drawing.Point(7, 26);
this.radioButton1.Name = "radioButton1";
this.radioButton1.Size = new System.Drawing.Size(126, 24);
this.radioButton1.TabIndex = 0;
this.radioButton1.TabStop = true;
this.radioButton1.Text = "radioButton1";
this.radioButton1.UseVisualStyleBackColor = true;
//
// radioButton2
//
this.radioButton2.AutoSize = true;
this.radioButton2.Location = new System.Drawing.Point(7, 66);
this.radioButton2.Name = "radioButton2";
this.radioButton2.Size = new System.Drawing.Size(126, 24);
this.radioButton2.TabIndex = 1;
this.radioButton2.TabStop = true;
this.radioButton2.Text = "radioButton2";
this.radioButton2.UseVisualStyleBackColor = true;
//
// radioButton3
//
this.radioButton3.AutoSize = true;
this.radioButton3.Location = new System.Drawing.Point(7, 100);
this.radioButton3.Name = "radioButton3";
this.radioButton3.Size = new System.Drawing.Size(126, 24);
this.radioButton3.TabIndex = 2;
this.radioButton3.TabStop = true;
this.radioButton3.Text = "radioButton3";
this.radioButton3.UseVisualStyleBackColor = true;
//
// radioButton4
//
this.radioButton4.AutoSize = true;
this.radioButton4.Location = new System.Drawing.Point(7, 142);
this.radioButton4.Name = "radioButton4";
this.radioButton4.Size = new System.Drawing.Size(126, 24);
this.radioButton4.TabIndex = 3;
this.radioButton4.TabStop = true;
this.radioButton4.Text = "radioButton4";
this.radioButton4.UseVisualStyleBackColor = true;
//
// radioButton5
//
this.radioButton5.AutoSize = true;
this.radioButton5.Location = new System.Drawing.Point(7, 194);
this.radioButton5.Name = "radioButton5";
this.radioButton5.Size = new System.Drawing.Size(126, 24);
this.radioButton5.TabIndex = 4;
this.radioButton5.TabStop = true;
this.radioButton5.Text = "radioButton5";
this.radioButton5.UseVisualStyleBackColor = true;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1210, 592);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.richTextBox1);
this.Controls.Add(this.button3);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.ResumeLayout(false);

}


Hier sind sozusagen die statischen Eigenschaften aufgeführt. Fügt man Ereignisse hinzu, wie Click oder sonstwas, werden diese an die einzelnen Elemente angehängt, als EventHandler Methode.

Kann man im Designer was ändern? WinForms sagt: NEIN!

/// <summary>
/// Erforderliche Methode für die Designerunterstützung.
/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
/// </summary>



Das ist zutreffend, wenn man versuchen würde, die Positionen der Elemente mit Variablen zu bestimmen. Dann ist Alarm.

Trotzdem ist es nicht ganz richtig, man kann. Darf aber nur Zahlenwerte einsetzen (damit täuschen wir den Compiler, denn seine Zahlenwerte sind so gut wie unsere).

Die Ausrichtung der Radiobuttons stellt sich wie folgt dar (Auszug aus dem obigen kompletten Code):

// radioButton1
//
this.radioButton1.Location = new System.Drawing.Point(7, 26);
//
// radioButton2
//
this.radioButton2.Location = new System.Drawing.Point(7, 66);
//
// radioButton3
//
this.radioButton3.Location = new System.Drawing.Point(7, 100);
//
// radioButton4
//
this.radioButton4.Location = new System.Drawing.Point(7, 142);
//
// radioButton5
//
this.radioButton5.Location = new System.Drawing.Point(7, 194);
//

Wir sehen, die linke Kante (x-Wert) paßt exakt, der Wert ist 7. Nur daß die Differenzen in y lauten: 40,34, 42, 52. Daher paßt es nicht.

Es hindert uns nun niemand, das manell anzupassen. Dafür ändern wir den ersten y-Wert statt von 26 auf 30 (Kopfrechnen schwach). Und schlagen für die folgenden Buttons jeweils 40 in y auf:

// radioButton1
//
this.radioButton1.Location = new System.Drawing.Point(7, 30);
//
// radioButton2
//
this.radioButton2.Location = new System.Drawing.Point(7, 70);
//
// radioButton3
//
this.radioButton3.Location = new System.Drawing.Point(7, 110);
//
// radioButton4
//
this.radioButton4.Location = new System.Drawing.Point(7, 150);
//
// radioButton5
//
this.radioButton5.Location = new System.Drawing.Point(7, 190);
//


Ergebnis sieht man in der 2. Abbildung designer2.jpg:

Die Buttons sind nun perfekt ausgerichtet. Sofern man dann im Entwurfsformular mit der Maus nicht dran herumpfuscht, bleibt es auch so.

Desweiteren kann man andere Eigenschaften auf diese Weise festlegen, z. B. die Größe element.Size
Angehängte Datei(en)
Angehängte Datei  designer1.jpg ( 61.46KB ) Anzahl der Downloads: 25
Angehängte Datei  designer2.jpg ( 58.17KB ) Anzahl der Downloads: 18
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 18.04.2014, 17:53 Uhr
sharky2014
Level 7 = Community-Professor
*******

Sichere Rückumwandlung von Strings aus zusammengesetzten Datenfeldern in die ursprünglichen Datenfelder.

Wir haben eine strukturierte Liste mit gemischten Datentypen dieser Art:

public class Person // gemischter Datensatz
{
public int nr;
public string name;
public double stundenlohn;
}

Die Personen sagen wir mal sind in einer Liste vom Typ List<Person> Personal gespeichert. Jeder Datensatz der Liste Personal ist vom Typ Person.

Um die gemischten Datensätze der Liste Personal am Bildschirm anzeigen zu können, müssen die Datenfelder in das Format string gebracht werden.

Wenn man den ganzen Datensatz in einer Zeile string betrachten will, lautet der Code etwa so:

string zeile = Person.nr.ToString() + Person.name + Person.stundenlohne.ToString(); // Einfachversion, ohne Formatierung

Nehmen wir an, diese Liste würde nun in einer WinForms listBox angezeigt werden, damit der Anwender den Datensatz durch Mausklick wählen kann.

Dann haben wir 2 Listen:

Liste 1 = Personal mit den strukturierten Datensätzen int nr, string name, double stundenlohn.

Liste 2 = listBox1 mit den Strings, die aus den Datensätzen gebildet wurden, z. B. "1 Müller 26.50";

Ist Liste2 eine exakte Kopie der Liste 1, dann stimmen die Indizes beider Listen überein, und man kann, wenn der Anwender z. B. den Datensatz 3 anklickt, die zugehörigen Daten in der anderen Liste über den Index finden

int index = listBox1.SelectedIndex;
Person Auswahl = Personal[index];

Wenn sich allerdings der Bestand der listBox durch Sortierung oder Löschen von Datensätzen ändert, kann das gründlich schief gehen bis zum Programmabsturz. Man müßte, um das zu verhindern, beide Listen immer identisch halten. Das ist aber je nachdem gar nicht gewünscht, weil durch Datenbearbeitung Datensätze hinzugefügt und gelöscht werden sollen, ohne daß diese sofort in die ursprüngliche Liste übernommen werden. Dazwischen kann es ja erforderlich sein, die Daten zu prüfen.

Dann sind die Listen irgendwann zur Laufzeit nicht mehr identisch, und der Zugriff mit dem ermittelten Index scheidet aus.

So oder so: Der direkte Zugriff ArrayoderListe [nummer] ist immer gefährlich, wegen Laufzeitfehlern und logischen Fehlern.

Das ist fast noch gefährlicher, als mit Pointern zu arbeiten.

Besser wäre es, man würde einen Suchbegriff aus dem gewählten String der Liste wählen, z. B. die Kunden-Nummer, und mit dieser Nummer die andere Liste durchsuchen, bis der Datensatz gefunden wurde oder nicht.

Algorithmus:

Aus dem gewählten String das Datenfeld Nummer wieder heraussuchen, diese Nummer in einen int32 umwandeln und mit dem int Wert Nummer nun die ursprüngliche Liste durchsuchen.

Damit wären wir beim Thema dieses Schnippels:

Wie finde ich aus einem String, welche zuvor aus verschiedenen Datentypen zusammengesetzt wurde, die ursprünglichen Datenfelder wieder heraus?

Wenn der String so aufgebaut ist:

1234Müller26.50 // Nummer, Name, Stundenlohn

könnte man mit Substring(0,4)( = nimmt die ersten vier Zeichen des Strings) die Nummer heraussuchen und die anderen Felder ebenso.

Stellen wir gleich einen anderen Datensatz darunter:

1234 Müller 26.50
5 Meyer 18.50

und wir sehen, mit dem Substring(0,4) würden wir in die Tonne fassen.

Möglichkeit: Datenfelder erhalten bestimmte Feldlängen, sieht dann so aus:

1234 Müller 26.50
___5 Meyer 18.50 (Leerzeichen mit _ simuliert, weil der Editor des Forums die führenden Blanks nicht beachtet)

Jetzt könnten wir mit unserem Substring, der die ersten 4 Felder abgreift, richtig liegen.

Möglicherweise ändert sich aber im Laufe der Programmentwicklung die Formatierung der Felder, und es sieht irgendwann so aus:

__1234 Müller 26.50
_____5 Meyer 18.50 (die _ sollen Leerzeichen darstellen)

Dann liegen wir mit unserem Substring daneben, denn die ersten 4 Felder sind jetzt __12, und nicht die Nummer.

Anders gesagt, bei jeder Umformatierung der Ausgabe müssen wir alle Funktionen finden (!) und aktualisieren, die mit Substring auf diese Felder zugreifen könnten. Das ist umständlich wie sonstwas und sehr fehleranfällig.

Die "richtige" Lösung für das Problem ist, daß man die Datenfelder mit einem Trennzeichen voneinander trennt. Das Programm kann dann den String splitten anhand der Trennzeichen.

Die Strings könnten etwa so aussehen:

12345 ^ Name___ ^ Stundenlohn
____4^Name2___ ^Stundenlohn

Das ist übrigens beim Datenimport von einerTextdatei nach Excel genau so gelöst. Gibt man Excel das Trennzeichen an, importiert es die Textdatei als Tabelle, formt die Werte je nachdem numerisch um und man kann mit der importierten Tabelle sofort rechnen.

Das eklige ist nur, daß die Trennzeichen bei der Ausgabe der Strings auf dem Bildschirm erscheinen und die Anzeige verhunzen.

Das kann man in C# ausschalten mit der Methode String.Replace, welche die nicht gewünschten Trennzeichen durch ein Leerzeichen ersetzt. Die Trennzeichen sollen wohlgemerkt drin bleiben, man soll sie nur nicht sehen, wenn der String ausgegeben wird:

char TRENNZEICHEN = '^';
char BLANK = (char) 32;

ConsoleWriteLine(zeile.Replace(TRENNZEICHEN,BLANK);

Bzw., wenn sie in die listBox in WinForms geschrieben werden sollen, wird der String dementsprechend "gesäubert":

this.listBox1.Items.Add(zeile.Replace(TRENNZEICHEN,BLANK));

Damit ist auch die listBox1 "sauber".

Wie findet man nun die Nummer?

C# Methode, um den String nach Trennzeichen aufzuteilen:

string[] datenfeld = zeile.Split(TRENNZEICHEN);

In dem Array datenfeld sind nun alle Datenfelder abgelegt, welche der Datensatz Zeile verfügbar macht.

Man muß nur die Reihenfolge der Felder kennen, ihre Länge spielt dann keine Rolle mehr. Es sollten nun

datenfeld[0] einen string der Nummer,
datenfeld[1] einen string Name
datenfeld[2] einen string stundenlohn enthalten.

Die Methode String.Split() übergibt nur den Text zwischen den Trennzeichen, die Trennzeichen als solche werden nicht übergeben. So daß man nun

int ERROR=0;

if (datenfeld[0]==null) return ERROR;
try
{
int nummer = Convert.ToInt32(datenfeld[0])
return nummer;
}
catch
{
return ERROR;
}

einen sicheren Zugriff auf die Nummer hat und mit der Nummer die strukturierte Liste auf Treffer durchsuchen kann.

Das ist möglicherweise ein wenig aufwendiger als der direkte Indezugriff mit der Einbildung, beide Listen seien zu jedem Zeitpunkt identisch (sehr fraglich!), ,
aber eine sehr viel stabilere Programmierung. Selbst wenn man die Felder in der Reihenfolge irgendwann vertauscht, wird damit das Programm nicht abstürzen, sondern melden, daß der Datensatz nicht gefunden wurde.

Grundsätzlich sollte man Indexzugriff mit einem definierten Index immer vermeiden.

In C# ist es fast niemals erforderlich.





Der Beitrag wurde von sharky2014 bearbeitet: 18.04.2014, 17:56 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 21.04.2014, 17:08 Uhr
sharky2014
Level 7 = Community-Professor
*******

ProgressBar

Programmierung kurz und bündig

Was man nicht nur hierzu im Internet dazu findet, ist oft dermaßen umständlich ...

Hier die Kurzversion:

Ein ProgressBar ist wichtig, um dem Anwender zu signalisieren, daß das Programm arbeitet und sich nicht aufgehängt hat. . Das betrifft alle Funktionen, die eben ein wenig länger dauern.

Der Code:

using ...

namespace sonstwas
{
public partial class FormDatenimport : Form // die Klasse Form, also ein WindowsForms Formular
{

public FormDatenimport() // Der Konstruktor der Form
{
InitializeComponent();
}

private void btStart_Click(object sender, EventArgs e) // Mit dem Button Start wird der Datenimport gestartet
{
var info = new B.Cpruef();
info = Datenimport();

// Cpruef ist eine Klasse, die bei Fehlern eine detaillierte Auflistung zurückliefert,
// wenn was schief gelaufen ist. Daher ist der Rückgabewert der Methode Datenimport CPruef anstelle etwa von bool

}

public B.Cpruef Datenimport() // diese Methode liest die Daten aus den beiden Dateien ein und verarbeitet sie
{
int ads = File.ReadAllLines(D.DN_KontenAlt).Length // Anzahl Datensätze ads = Summe Datensätze Datei 1
+ File.ReadAllLines(D.DN_BuchungenAlt).Length; // + Datensätze Datei 2

this.progressBar1.Maximum = ads; // Die Eigenschaft .Maximum wird auf die Gesamtzahl der Datensätze festgelegt
this.progressBar1.Step = 1; // SChrittweite ist 1, der Balken soll also pro Datensatz einen vorrücken

// Datei1 wurde eingelesen in eine Liste quelle
foreach (string element in quelle)
{
// Datensätze verarbeiten.
this.progressBar1.PerformStep(); // Nach jedem Datensatz Fortschrittsbalken um 1 Step weiterführen
}
// Datei 2 dito
foreach (string element in quelle)
{
// Datensätze verarbeiten
this.progressBar1.PerformStep();
}
} // Datenimport

} // class Form
} // namespace


Wir haben hier den Vorteil, daß wir die Gesamtzeit des Vorgangs relativ genau abschätzen können anhand der Datensätze, die mit File.ReadAllLines...Length ermittelt wurden. Vorsicht bei FileInfo,Length, der Rückgabewert sind nicht die Anzahl der Datensätze sondern die Größe der Datei in Bytes, was hier nicht passen würde.

Dadurch ist der progressBar ungefähr 1:1 im Bilde. Er sollte nicht, wie das immer wieder zu sehen ist, minutenlang auf 99 Prozent hängenbleiben.
Angehängte Datei(en)
Angehängte Datei  Progressbar.jpg ( 38.06KB ) Anzahl der Downloads: 28
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 21.04.2014, 23:05 Uhr
Frankyxxx
Level 2 = Community-Facharbeiter
**

bla bla bla m8.gif brauch doch kein mensch
TOP    
Beitrag 23.04.2014, 09:51 Uhr
nixalsverdruss
Level 7 = Community-Professor
*******

QUOTE (Frankyxxx @ 21.04.2014, 23:05 Uhr) *
bla bla bla m8.gif brauch doch kein mensch


lass ihn doch.
er hat immer hin von seiner antiquarischen Entwicklungsumgebung Abstand genommen.
TOP    
Beitrag 23.04.2014, 19:55 Uhr
sharky2014
Level 7 = Community-Professor
*******

Ausgewähltes Element einer List <T> finden

Eine List<T> mit strukturierten Datensätzen ist grundsätzlich nicht sichtbar.

Sichtbar werden die Daten des Datensatzes erst, wenn sie in einen String umgewandelt und an der Bedieneroberfläche angezeigt werden. Auf der Konsole oder in andere Ausgabeelementen wie listBox oder textBox oder was auch immer.

Das Prinzip ist so:

Daten --> Umwandlung in String (Zeichenkette) zum Zwecke der Sichtbarmachtung -> Anzeigeelement, z. B. listBox -> der Anwender sieht die Daten.

Was der Anwender sieht, ist aber erstens nur eine Wertekopie der Daten, zweitens nur ein Auszug daraus, nämlich die Kopien der Felder, die sichtbar gemacht wurden. Der Datensatz kann über den sichtbaren Bereich hinaus noch weitere Informationen enthalten, welche nicht angezeigt werden.

Die Sichtbarmachung dient dazu, den Anwender eine Auswahl treffen zu lassen.

Wenn der Anwender nun anhand der Kopien der Felder eine Auswahl getroffen hat, wie können wir daraus auf den im HIntergrund stehenden "realen" Datenbereich zurückgreifen?

Die sichtbaren Elemente einer listBox z. B. liefern das Ergebnis this.listBox.SelectedIndex oder this.listBox.SelectedItem.

Damit können wir den Datensatz identifizieren.

Nun umfaßt die Datenmenge, welche in listBox zu sehen ist, je nachdem aber nur eine Teilmenge der Daten, die in der dahinterstehenden Liste aller Daten verfügbar ist. Die listBox kann z., B. anders sortiert sein oder gefiltert.

Daher sind die Indizes des Containers, welche eine Liste der ausgwählten Daten enthält, nicht identisch mit den Indizes der dahinterstehenden Liste mit allen verfügbaren Daten. Der Datensatz, der in der Listbox mit dem Index 5 angezeigt wird, kann in der Liste aller Daten z. B. den Index 2000 haben.

Um das abzugleichen, braucht es ein Bindeglied, welches die Identifikation des Datensatzes eindeutig macht.

Z. B. eine Identifikationsnummer, wie Kundennummer oder Artikelnummer oder sonstwas, was eindeutig ist, also nicht mehrfach vergeben werden darf.

Verzichtet man auf das, müßte man alle Datenfelder Zeichen für Zeichen auf Identität überprüfen (bitte nicht!).

Nehmen wir an, wir hätten aus dem listBox.SelectedItem diese Identifkationsnummer herausgefunden, also z. B. eine Umwandlung eines Substrings in eine int32-Zahl, und würden nun anhand dieser int32 die strukturierte Liste durchsuchen.

List<T> speichert keine Werte, sondern nur Adressen!

Und wenn wir foreach (<T> element in List<T>) versuchen, die Nummer zu finden, mit

if (element.nr==suchwert) haben wir das Problem, daß List<T> keinen Index herausrückt, der uns den Datensatz bezeichnet, an dem die Suchbedingung erfüllt ist.

Was man dazu findet, im Internet, sind recht komplexe (bzw. umständliche) Konstruktionen mit Delegates etc. etc.

Es gibt eine sehr einfache Methode, diesen gesuchten Index auszumachen, mit der guten alten for (int i - Schleife.

Das Beispiel bezieht sich auf den Wunsch des Anwenders, einen in der listBox sichtbaren Eintrag zu löschen. Dazu muß das Programm anhand er Ident-Nr. den Datensatz in der dahinterstehenden strukturierten Liste finden und entfernen.

Der Anwender trifft seine Auswahl in der listBos und drückt den Button "Datensatz löschen":


private void btLoeschen_Click(object sender, EventArgs e)
{
bool gefunden=false;
int index=0;
DialogResult r;
string identnrstring = this.listBoxBuchungen.SelectedItem.ToString().Substring(0, B.IDENTNRLEN);
// Feldlänge IDENTNRLEN des Identifizierers global festgelegt

int identnr = B.string_to_int(identnrstring); // Funktion static ,um einen String ohne Laufzeitfehler in int zu verwandeln
for (int i = 0; i < Buchungsliste.Count(); i++)
{
if (Buchungsliste[i].IdentNummer == identnr)
{
index = i; // Jetzt haben wir den Index, den List<T> als Methode nicht herausrückt
gefunden = true; // Und wissen auch, daß wir nicht im Niemandsland stehen
break; // raus
}
}
if (gefunden)
{
DialogResult result =
MessageBox.Show("Markierten Buchungssatz löschen?", "", MessageBoxButtons.YesNo,
MessageBoxIcon.Exclamation);
if (result == DialogResult.Yes)
{
Buchungsliste.RemoveAt(index); //Eintrag löschen mit direkter Indizierung
buchungen_in_listbox(mindat, maxdat);
// Die Daten werden wieder neu in die Listbox zurückgeschrieben, ohne den gelöschten Satz
}

}
else MessageBox.Show("Datensatz nicht gefunden");

}


In dem Entwurfsformular FiBu könnte das dann z. B. aussehen wie in der Abbildung

Der Beitrag wurde von sharky2014 bearbeitet: 23.04.2014, 20:09 Uhr
Angehängte Datei(en)
Angehängte Datei  DatensatzLoeschen.jpg ( 270.88KB ) Anzahl der Downloads: 18
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 25.04.2014, 15:56 Uhr
sharky2014
Level 7 = Community-Professor
*******

Strukturierung einer Form -------------------------------------------- Beispiel

Wie in der Abb. zu sehen, wird es sehr schnell komplex. Dort sind sehr viele Funktionalitäten beisammen.

Z.B. das Zusammenspiel der Radio-Buttons, welche bei der Buchführung Ein- und Ausgaben unterscheiden, dabei den MWSt-Satz automatisch erzeugen.

Für den Fall, daß die Automatik ausgeschaltet wird, muß die Zuweisung an Konto oder Gegenkonto geklärt sein.

Darüber hinaus ist eine Liste abrufbar, in der sich ständig wiederholende Buchungen befinden. Wenn man also einen Leasing-Vertrag bedient, wählt man den hier aus und die Buchung ist mit einem Click erledigt.

Für die Konten gibt es eine Filter-Funktion (hier nicht angeklickt) mit Kontenklassen oder nach Alphabet.

Daneben muß der Buchungssatz geprüft werden auf logische Konsistenz und wenn das erfolgt ist, die Option Speichern (hier nicht sichtbar) sichtbar gemacht werden. Sie ist hier nicht sichtbar, weil zuerst der Datensatz übernommen werden muß, dazu wird er zuvor geprüft.

Und so weiter und so fort. Davor hängen natürlich Datei-Lesezugriffe, dahinter Speicherzugriffe, und die Listboxen müssen erstmal aufgefüllt und ständig aktualisiert bzw. sortiert werden. So eine kleine Form kann schon eine Menge Holz bedeuten.

Was mit einem kleinen Formular anfängt, wird SEHR SCHNELL UNÜBERSICHTLICH, insbesondere die Rückbezüglichkeiten innerhalb der Windows-Click-Welt können sehr große Probleme erzeugen, wenn man das nicht von Anfang an, sozusagen gnadenlos, klar strukturiert.

Klar gibt es beim Programmieren keine rote Linie. Es ist alles sehr individuell.

Ich will hier trotzdem ein paar Tipps geben, wie man in der Windows Welt die Übersicht behält in dem Sinne, daß ständige spätere Erweiterungen das Formular nicht vermüllen.

Leider gibt es keine (sinnvolle) Möglichkeit, den Zugriff auf die Elemente einer Form von außen zu steuern, z. B. durch eine Klasse. Die Anweisungen für das, was sichtbar gemacht werden soll, textBox, listBox, Button etc. etc., müssen sich im Formular befinden. Man kann das nicht auslagern. Und so wird das Formular immer länger und länger und länger .....

Vorgeschlagene Struktur:

FormSonstwas (Windows Form)

public partial class formName : Form // Parent der Mutterklasse Form
{

BLOCK 1:

formName() // der Konstruktor
{
initializeComponent();
// hier gehören alle Anweisungen rein, die beim Aufruf des Formulars einmalig ausgeführt werden müssen
// i.d.R. werden das die Initialisierung von Daten sein, die z. B. aus Dateien ausgelesen werden

}


BLOCK 2:

// Hier eine Generalmethode zur Abhandlung aller Ereignisse innerhalb der Form:

private void Generalmethode(object sender)
{
// die kann anhand des Parameters sender feststellen, wer wo wie was geklickt hat
// und sich ihren Reim darauf machen, was nun zu tun ist
}

BLOCK 3:

//Nun müssen wir die Ereignishandler der Buttons, Radiobuttons, Listboxen etc. etc. unterbringen
// Das gibt je nachdem eine lange, lange Liste, daher umso wichtiger, daß man alles zusammenfaßt
// möglichst sortiert nach Art der Elemente
// was sollen wir da reinschreiben?
// erstmal nur den Aufruf der Generalmethode, sonst gar nichts:

private void btTuDies_Click(object sender, EventArgs e)
{
Generalmethode(this.btTuDies); // Aufruf der Generalmethode, sonst erstmal gar nichts
}

private void btTuDas_Click(object sender, EventArgs e)
{
Generalmethode(this.btTuDas);
}

private void listBoxDatensonstwas_DoubleClick(object sender, EventArgs e)
{
Generalmethode(this.ListBoxDatensonstwas);
}

// Alle diese Elemente melden also, daß sie aufgerufen werden, und machen sonst erstmal gar nix

BLOCK4:

// Nun folgt eine noch längere Aneinanderreihung von allen Methoden, welche die Generalmethode benötigt, um übersichtlich zu bleiben
// Z. b. in einer Datei irgendeinen Datensatz finden, gehört in einer Unterfunktion (Methode), die hier zu finden ist

private void SucheXy()
{
}
private double Suche Kontonummer(string Listboxstring)
{

}

}// Schlußklammer der partial formName
}// Schlußklammer Namespace

Wir haben dadurch unsere Form, die leider Gottes sehr sehr lang werden wird, immerhin in 4 Blöcke strukturiert, die bei der Übersicht enorm helfen.

Eine interessante Frage ist, wieviel Funktionalität die Elemente über diejenige hinaus bekommen sollen, daß sie der Generalmethode schlicht melden, angeklickt worden zu sein?

Meine Empfehlung wäre: so wenig wie möglich. Soviel wie nötig erscheint, ist bei genauerer Überlegung selten bis nie nötig. Im Grunde brauchen diese Buttons keine Funktionalität. Der Grund ist, daß beim Ausbau des Programms sehr leicht Zirkelschlüsse auftreten, Werte werden geändert, ohne daß man weiß, wo es herkommt. Läßt man die Buttons dumm, können die einem nicht dazwischenfunken, und man erfüllt das Paradigma der OOP auch innerhalb der Klasse: Datenkapselung soviel wie möglich, Späte Bindung.

Die Datenübergabe zwischen Klassen und Forms, hin und zurück:

Da wird ein großes Bohei gemacht, wie sich die Forms und Klassen untereinander mitteilen sollen oder können oder dürfen.

Wenn z. B. die Form3, aufgerufen durch Form2, welche durch Form1 aufgerufen wurde, feststellen würde, der geplante Vorgang läßt sich so nicht ausführen, wie soll dann die Rückmeldung erfolgen?

Oder wenn die Form3 dann noch eine Funktion einer Klasse aufruft, und erst diese die Feststellung macht, wie soll der Informationsfluß zurück erfolgen?

Wie sollen die nachgeordneten Forms und KlassenMethoden überhaupt feststellen, welche Parameter erforderlich sind für die angeforderte Methode?

In welche Datenstrukturen sollen sie ihre Informationen hineinschreiben, wenn diese Datenstrukturen gekapselt sind?

Viele Fragen, und ja, jedes Programm ist anders und eine generelle Lösung für alles gibt es nicht.

Um diese ganze Kasperei um diese Dinge aber mal aufzubrechen: die Lösung ist sehr einfach.

Wir können eine Klasse definieren, auf die alle Klassen und Forms einen Zugriff haben

(BITTE NICHT UMGEKEHRT, dann gibt es Zirkular-Referenzen),

also eine Klasse, die ich als das Schwarze Brett bezeichnen möchte,

in der alle Klassen ihre Nachrichten hinterlassen.

Die Klasse macht man am besten static, damit nicht verschiedene Instanzen zur Verwirrung führen.

Z.B. nennen wir diese Klasse mal

public static class REGISTER
{

}

Sie enthält sozusagen Flags über den Zustand des Programms, die man mit get/set auch noch kapseln kann, wenn man will.

Ein Beispiel wäre die fortlaufende Numerierung von Datensätzen, welche in verschiedenen Forms und Klassen bearbeitet werden müssen. Nehmen wir einen Datensatz hinzu, muß die Numerierung um 1 erhöht werden. Die entsprechende Datei oder Liste kann aber von den nachgeordneten Forms und Klassen gar nicht ausgelesen werden (private). Woher nimmt nun eine Methode, aufgerufen von der Form3, diese Information, die nur in der Form2 verfügbar ist?

Ganz einfach:

Form2 teilt der public static class REGISTER mit, daß

public long IDENTNR einen bestimmten Wert hat. Wie der zustande gekommen ist, weiß nur die Form2, weil sie Zugriff auf die entsprechende Datei hat, die Form 3 weiß es nicht.

Aber nimmt die Form3 mit einer Methode einen Datensatz auf, kann sie durch Zugriff auf die Klasse REGISTER den richtigen Wert entnehmen (und den Zähler anschließend um 1 heraufsetzen, gehört sich ja so, bzw. kann man das in der Methode auch automatisch implementieren, daß beim Zugriff auf den Getter der Setter den Wert um 1 nach oben setzt).

So daß dann, wenn irgendeine Form oder Methode einen Datensatz hinzufügen will, dasselbe verfügbar ist.

Der Beitrag wurde von sharky2014 bearbeitet: 25.04.2014, 16:04 Uhr
Angehängte Datei(en)
Angehängte Datei  formular.jpg ( 313.54KB ) Anzahl der Downloads: 11
 


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 25.04.2014, 16:45 Uhr
sharky2014
Level 7 = Community-Professor
*******

Hier haben wir ein Beispiel für einen Konstruktor der Form2, welche die Buchungen erledigt.

Der Kommentar erfolgt zeilenweise

public FormBuchung() // der Konstruktor
{
R.SPERRE = false; // ein globales Element SPERRE, wenn was nicht geht, z.B. Dateien fehlen, abgelegt in der Klasse R (R=Register)
var info = new B.Cpruef(); // eine Klasse, welche alle Fehler zurückliefert, die aufgetreten sind
InitializeComponent(); // der WinForms Standard-Konstruktor
Buchungsjahr = R.Geschaeftsjahr; // entnommen aus der static class R
mindat = new DateTime(Buchungsjahr, 1, 1);
maxdat = new DateTime(Buchungsjahr, 12, 31);
this.dateTimePicker1.MinDate = mindat; // DateTimePicker impfen auf das laufende Geschäftsjahr
this.dateTimePicker1.MaxDate = maxdat;
if (Buchungsjahr == DateTime.Now.Year) this.dateTimePicker1.Value = DateTime.Now;
else this.dateTimePicker1.Value = new DateTime(Buchungsjahr, 12, 31); // vergangende Jahre darf man eigentlich gar nicht buchen außer Abschluß- und Umbuchungen, aber sei es drum
CBuchungen.fuelleDauerbuchungen(Dauerbuchungsliste); // das sind wiederkehrende Buchungen wie Miete, Ratenzahlung etc., die dann mit 1 Klick erledigt sind
if (!D.lies_kontenrahmen_binaer(Kontenliste)) // die erforderlichen Daten müssen natürlich erstmal rein
info.fehlerstring += "Fehler: Kontenliste konnte nicht gelesen werden" + B.EOL;
string dateiname = D.findeBuchungsdateiname(Buchungsjahr); // Buchungen nach Jahren in versch. Dateien abgelegt
if (!D.lies_buchungen_binaer(dateiname,Buchungsliste))
info.fehlerstring += "Fehler: Buchungsliste konnte nicht gelesen werden" + B.EOL;
if (info.fehlerstring.Length > 0) info.fehler = true;
if (info.fehler)
{
MessageBox.Show(info.fehlerstring); // gibt alle gefundenen Fehler aus
R.SPERRE = true; // Form wird dann beim ersten Aufruf von General abgewürgt
// Der Konstruktor arbeitet, bevor die Form sichtbar ist. Egal was der feststellt, die Form wird trotzdem erscheinen
// daher das FLAG SPERRE, er wird zwar erscheinen, aber nicht funktionieren
}
else
{
this.btSpeichern.Visible = false; // kann erst speichern, wenn eine EIngabe vorliegt
setze_lfd_nr(); // gesucht wird in der Buchungsliste abgelegt in R.BUCHUNGSIDENTNR
Konten_in_Listbox(Kontenliste); // KOntenliste in die Listbox kopieren
Buchungen_in_Listbox(Buchungsliste); // dito
Dauerbuchungen_in_Combobox(Dauerbuchungsliste); // und unsere wiederkehrenden Buchungen in die Kombobox kopieren
clearBsatz();
zeigeBsatz();

}

Nachdem das erledigt ist im Kostruktor, kann es losgehen


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 25.04.2014, 17:02 Uhr
sharky2014
Level 7 = Community-Professor
*******

Der Konstruktor übergibt dann das Kommando der Click-Welt der Windows Ereignisse.

Diese machen erstmal nur eines, die Generalmethode aufzurufen, so daß beim ersten und jedem nachfolgenden Click diese Methode aktiv wird.

Hier ist sie, und angesichts der recht komplexen Funktionalität (bislang, kommt noch mehr dazu) halte ich sie für "schlank", so schlank, daß man noch den Überblick über das hat, was passiert: Bestimmte Codezeilen gehören noch aussortiert. Der Code:


// *************************** Generalmethode

private void general(object sender)
{

// Beginn:

if (R.SPERRE) { this.Hide(); return; } // ALso dann war das nix
if (sender == rbBank||sender==rbKasse||sender==rbEinnahme||sender==rbAusgabe)
rbKeineVorgaben.Checked = false; // wieder in Betrieb nehmen
if (sender == this.rbKeineVorgaben) // Ohne Vorgaben wird die Automatik ausgeschaltet über Deaktivierung der Radiobuttons
{
this.rbBank.Checked = false;
this.rbKasse.Checked = false;
this.rbAusgabe.Checked = false;
this.rbEinnahme.Checked = false;


}


if (sender == this.btBuchungneu) R.freigabe = true; // Aktivieren der Buchung über Buchung neu
if (sender == this.cbDauerbuchungen) R.freigabe = true; // oder man kann eine Dauerbuchung auswählen, dann auch aktiv

this.btVerwerfen.Visible = false; // wo es nix zu verwerfen gibt, ist der Button unsichtbar
this.btUebernehmen.Visible = false; // es soll nur neue Buchung sichtbar sein

if (R.freigabe) // Freigabe heißt, es kann nun ein Buchungssatz erzeugt werden
{
this.btVerwerfen.Visible = true;
this.btUebernehmen.Visible = true;


Bsatz.datum = this.dateTimePicker1.Value;
Bsatz.strdatum = Bsatz.datum.ToShortDateString();
Bsatz.mwstproz = sucheMwstprozent(); // Unterfunktion, sucht MWSt anhand der aktivierten Radiobuttons
Bsatz.ukonto = sucheUkonto(); // steuerliche Zuordnung gemäß dem Kontenrahmen SKR03
// Bsatz.brutto = Handeingabe // hier machen wir erstmal nix
Bsatz.mwst = B.errechne_steuer(Bsatz.brutto, Bsatz.mwstproz); // errechnet die MWSt vom Brutto zurück
Bsatz.netto = B.errechne_netto(Bsatz.brutto, Bsatz.mwstproz); // und das netto
// Text Handeingabe
if (!this.rbKeineVorgaben.Checked) checke_rbuttons_bank_kasse(); // Radiobuttons Bank Kasse Eingabe Ausgabe = Automatik

if (sender == this.listBoxKonten) // jetzt wird durch Doppelklick, ein Konto ausgewählt
{
if (this.rbKeineVorgaben.Checked)
if (next == KONTO) next = GKONTO; // ist das nun das Konto oder das Gegenkonto? next heißt, zu buchen auf Konto oder Gegenkonto
else next = KONTO; // Switcht bei jedem Eintrag um

if (next == KONTO) Bsatz.konto = stringkonto_to_intkonto(this.listBoxKonten.SelectedItem.ToString());
if (next == GKONTO) Bsatz.gkonto = stringkonto_to_intkonto(this.listBoxKonten.SelectedItem.ToString());

}
if (sender == this.cbDauerbuchungen) dauerbuchung(); // Unterfunktion, welche die fertig angelegten Buchungssätze rausrückt
if (sender == this.btUebernehmen) buchungsabschluss(); // Unterfunktion, prüft zuvor auf logische Konsistenz

} // if R.freigabe
if (sender == this.btSpeichern) buchungenSpeichern(); // Buchungen aus der Liste werden nun gespeichert

if (sender==this.btEnde) // Falls diese Buchungen in der Liste sind,
if (this.btSpeichern.Visible) // KÖnnte man auch auslagern weil viel Text, aber selbsterklärend
{
DialogResult result = MessageBox.Show(
"Ungespeicherte Buchungssätze gefunden\n"
+"Sollen die Daten gespeichert werden?",
"Achtung Datenverlust möglich:",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (result==DialogResult.Yes) buchungenSpeichern();
}




zeigeBsatz();

}


Mit den Programmbeispielen will ich darauf hinweisen, wie man den Code so strukturieren kann, nämlich in den Zentralmethoden kurz und bündig wie möglich, daß man noch einen Überblick hat, was da läuft.

Wenn man sich darauf einläßt, jedem Click-Ereignis der WindowsWelt irgendeinen Code zuzuweisen, wird man bald feststellen, daß die Übersicht von vornherein nicht vorhanden war und auch niemals wiederkehrt.

Strukturierung ist alles.


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 27.04.2014, 16:24 Uhr
sharky2014
Level 7 = Community-Professor
*******

FiBu (Finanzbuchhaltung)

Problem:

beim Buchen will man den Girostand des Kontos sehen, um vergleichen zu können, mit dem Kontoauszug, ob die Buchung wertemäßig korrekt eingegeben bzw. erfaßt wurde. Das will man sofort am Bildschirm sehen, sobald die Buchung erfolgt ist.

Wie errechnet sich der Girostand?

Man kann ihn nicht mit dem ersten Datensatz des neuen Jahres gegenrechnen, weil das Programm ja irgendwann diesen ersten Datensatz erzeugen muß. Daher ist es ohne Zugriff auf die Vorjahreswerte nicht möglich. Der letzte Datensatz aus dem Vorjahr, sofern dabei die Bankverbindung betroffen ist (und nicht etwas Korrekturbuchungen oder Abschreibungen oder Kassenbestände gebucht wurden) weist eine Betrag auf, welcher gleichbedeutend ist mit dem Eröffnungswert im Folgejahr.

Man könnte diesen Wert ablegen unter dem entsprechenden Konto als Abschlußwert. Dummerweise muß man dann das Vorjahr komplett schließen, d. h., der Anwender darf nach Abschluß keinen Buchungssatz mehr eingeben. Sowas ist unrealistisch, weil es immer möglich sein muß, den Abschluß auch aufzuheben, schon allein, um Fehler nachträglich zu beheben oder wenn der Abschluß voreilig erfolgte.

Diese Methode, Daten statisch abzulegen, die man dynamisch auslesen könnte, ist eine Form der Redundanz. Es besteht dann die Gefahr, daß die abgelegten Werte an verschiedenen Orten abgelegt und nicht in allen Fällen aktualisiert werden und daher fehlerhaft sind.

Sehr viel besser ist es, was man braucht, bei jedem Aufruf nach dem Stand der Dateien neu zu errechnen.

Das erfolgt hier so:

private void setze_Bank1Eroeffnung()
{
// todo: Liste vom Vorjahr auf den letzten Datensatz Bank absuchen
// und den Wert der Klasse R übergeben

var Vorjahresliste = new List<CBuchungen.Buchung>();
double wert = 0.0;
int vorjahr=R.Geschaeftsjahr-1;
string datei=D.findeBuchungsdateiname(vorjahr);
if (File.Exists(datei)) // Datei vorhanden?
{
if (D.lies_buchungen_binaer(datei, Vorjahresliste)) // kann gelesen werden?
{
foreach (CBuchungen.Buchung element in Vorjahresliste) // absuchen
{
while (element.konto == BANK1 || element.gkonto == BANK1||element.ukonto==BANK1)
// nur Datensätze, die den Stand des Giros berühren
//Konto, Gegenkonto, Umsatzsteuerkonto

wert = element.giro; // der letzte Wert bleibt hängen
}
}
}
R.Bank1Eroeffnung=wert; // und wird an Klasse R. übergeben

}


Man kann das nicht so lösen, daß man auf den letzten Datensatz springt. Die 50 letzten Datensätze könnten steuerliche Umbuchungen sein, von denen keine einzige den Stand des Giros berührt. Daher muß die Datei von vorn bis hinten abgesucht werden. Bei 2000 Datensätzen dauert das auf meinem Notebook Millisekunden.

Man sieht hier, die Methode liefert keine Wert zurück (den double Girostand), sondern sie ist void.

Wie schon gesagt wurde, eine sehr gute Methode, solche Werte unterzubringen, mit denen das Programm von verschiedenen Stellen aus Zugriff benötigt, ist die Benutzung einer statischen Klasse (hier R = Registereinträge), auf die alle Forms und andere Klassen einen Zugriff haben. Die Klasse R selbst macht keinen Zugriff auf irgendeine andere Klasse (das ist wichtig, um Zirkularreferenzen auszuschließen).

Der Beitrag wurde von sharky2014 bearbeitet: 27.04.2014, 16:29 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    
Beitrag 30.04.2014, 18:23 Uhr
sharky2014
Level 7 = Community-Professor
*******

Programm hängt sich auf
Ist nur mit dem Task-Manager zu beenden


Jetzt gilt es, den Punkt zu finden. Wenn sehr viele Funktionen zusammenlaufen, ist das mit dem Debugger und dem 1-Step-Verfahren aussichtslos. Weil mit dem 1-Step-Verfahren ja auch die ganzen Unterfunktionen abgearbeitet werden, z. B., wenn eine Datei eingelesen wird mit 2000 Datensätzen ... das bringt nix. Die infrage kommende Methode ist in diesem Falle in dem Konstruktur der Form eingebunden, der sehr viele Methoden nacheinander aufruft, welche wiederum voneinander abhängig sind in dem Sinne, daß die Reihenfolge des Aufrufs eine Rolle spielt.

Um das einzugrenzen, lassen wir den Debugger mal beiseite und benutzen folgende Methode, indem wir nämlich den Konstruktor zerlegen in Blöcke:


code ...

MessageBox.Show("Hallo 1");

code ...

MessageBox.Show("Hallo 2");

code ...

MessageBox.Show("Hallo 3");

code ...

Man bekommt so den Bereich recht schnell eingegrenzt, in dem das Unglück passiert.

Vorliegender Fall:

Programm hängt sich auf ohne Laufzeitfehler. Da beginnt man an seinen Fähigkeiten zu zweifeln.

Ergebnis der Recherche: Es ist der Code, der im vorherigen Beitrag gepostet wurde:

QUOTE
if (File.Exists(datei)) // Datei vorhanden?
{
if (D.lies_buchungen_binaer(datei, Vorjahresliste)) // kann gelesen werden?
{
foreach (CBuchungen.Buchung element in Vorjahresliste) // absuchen
{
while (element.konto == BANK1 || element.gkonto == BANK1||element.ukonto==BANK1)
// nur Datensätze, die den Stand des Giros berühren
//Konto, Gegenkonto, Umsatzsteuerkonto
wert = element.giro; // der letzte Wert bleibt hängen
}
}
}
R.Bank1Eroeffnung=wert; // und wird an Klasse R. übergeben




Was ist an dem oben zu sehenden Code schlicht falsch?

Es ist (gottseidank) ein Fehler in der Logik. Das sind die Fehler, die mir am liebsten sind, weil man damit die Gewißheit hat, gegenüber der Laufzeitumgebung keine Sünden begangen zu haben.

Der Knackpunkt ist die while-Schleife. Nicht ganz einfach zu sehen, aber logisch:

Innerhalb der Foreach-Schleife ist es so gewünscht, daß solange (while) Bedingung erfüllt ist, der Wert zugewiesen wird.

Allerdings, nachdem die Bedingung der while-Schleife erfüllt ist, gibt die Laufzeitumgebung nicht mehr an die foreach-Schleife weiter.

Damit hängt sich das Programm auf, und zwar in einer Endlosschleife.

Wenn die Methode (wie hier der Fall) im Konstruktor der aufgerufenen Form aufgerufen wird, erscheint die aufgerufene Form gar nicht erst, und die Fehlermeldung vom Task-Manager ("Keine Rückmeldung") wird für die aufrufende Form gemeldet (was natürlich Quatsch ist, denn der Fehler liegt in der aufgerufenen Form).

Also, Hinweis: wenn sowas passiert, ist Endlosschleife ist in Betracht zu ziehen. Weder der Compiler noch die Laufzeitumgebung können dies erkennen, solange da keine Memory-Ressourcen verbraten werden, die zum out of memory führen.

Abhilfe schafft hier:

Statt while(Bedingung)

zu codieren:

if (Bedingung)

Dann ist das Problem erledigt.

Der Beitrag wurde von sharky2014 bearbeitet: 30.04.2014, 18:35 Uhr


--------------------
A programmer is just a tool which converts caffeine into code
TOP    



1 Besucher lesen dieses Thema (Gäste: 1)
0 Mitglieder: