Visual Basic (Windows Forms) Kommandozeile

Hallo Experten,

ich habe ein Programm bekommen, das auf einem Windows Forms Formular basiert. Das übliche: ein Menü und ein paar Buttons triggern die verschiedenen Programmfunktionen. Das Hauptfenster heißt „Form1“, die gesamte Initialisierung hängt in Form1_Load.

Einige Funktionen laufen recht lang, weshalb der Programmierer eine Statuszeile auf das Form geklebt hat. Außerdem gibt es für solche Langläufer einen „Abbrechen“ Button, der eine spezielle Exception schmeißt, die dann Funktionen per Try/Catch abbricht. Klappt alles wunderbar.

Die Aufgabe ist nun, einige Programmfunktionen per Kommandozeilenparameter automatisch ablaufen zu lassen. Ich möchte dazu das Form1 sichtbar haben, damit die Fortschrittsanzeige mit dem Statusbalken funktioniert. Nach Ende der Bearbeitung soll das Programm sich beenden. Der „Abbrechen“ Button soll ebenfalls funktionieren.

Nun die Frage: gibt es in den Tiefen der Windows Forms eine geeignete Stelle um meine Kommandozeilenbehandlung einzuklinken? Form1_Load ist dazu offenbar ungeeignet, weil Form1 da noch nicht sichtbar ist.

Wichtig: die Eventschleife von Form1 muss zu dem Zeitpunkt laufen, sonst wird die Statuszeile nicht aktualisiert, und der Abbrechen Button nicht bearbeitet. Verzwickte Sache, das Form soll also irgendwie „scheintot“ sein, ziemlich tot aber nicht so ganz :smile:

Wenns mit einer Eventroutine nicht klappt, hat jemand einen anderen Ansatz wie ich das realisieren kann? Ein spezielles verborgenes Control z.B., das ich irgendwo auf das Form packe wäre denkbar.

Danke für Hinweise …

Armin.

Hallo Armin,

ich habe nicht ganz verstanden, wann und wozu Du die Kommandozeile brauchst. Davon hängt der Weg ab, wie (nicht ob) man das realisieren kann.

Du kannst das Programm von der Kommandozeile starten und Parameter übergeben.

Die Parameter werden der Form1 in der Variablen ‚Command‘ übergeben.

Oder möchtest Du zur Laufzeit des Programms ein Konsolenfenster öffnen, dort Kommandozeilen eintippen und die Ergebnisse, die Du da bekommst im Programm verarbeiten? Das ist etwas komplizierter, weil es kaum Gründe gibt, das so zu verwenden.

http://www.activevb.de/tipps/vb6tipps/tipp0342.html

In den meisten Fällen wird dann im Konsolenfenster etwas getan, das VB ohne Konsole auch gekonnt hätte. :smile:

Gruß Rainer

ich habe nicht ganz verstanden, wann und wozu Du die
Kommandozeile brauchst. Davon hängt der Weg ab,
wie (nicht ob) man das realisieren kann.

Das habe ich befürchtet :smile: Viel Text, viel Verwirrung. Aber da ich nicht genau weiß wo ich suchen muss …

Also genauer: das Programm importiert - neben anderen Funktionen zur manuellen Bearbeitung von Datensätzen - tausende Datensätze aus csv Dateien in eine Datenbank. Dazu hat es einen Button „Import“, der wiederum führt zu einem Dateiauswahlfenster, der Benutzer wählt eine csv Datei, und dann gehts los. Das ganze läuft - je nach Datei - zwischen einigen Minuten und einigen Stunden, und der Benutzer kann abbrechen wenns ihm zu lang dauert, und später den Import nochmal anschubsen - der Import ist in der Hinsicht sehr tolerant programmiert und überspringt bereits vorhandene Datensätze.

Die Dateiauswahl und die Importroutine hängen im Moment in der Button_Click Routine des „Import“ Buttons. Innerhalb der Importschleife für die einzelnen Datensätze sind strategisch günstig Ausgaben für die Statuszeile und jede Menge application.doevent() Anweisungen platziert, die machen die Statuszeile und den „Abbrechen“ Button lebendig.

Der „Abbrechen“ Button, der normalerweise ausgeblendet ist, wird vor Beginn der Importschleife „visible“ gemacht, und schmeißt wenn er gedrückt wird eine Exception, die weiter draußen per Catch zum Abbruch der Importschleife führt. Danach - oder wenn alle Datensätze bearbeitet wurden - wird der „Abbrechen“ Button wieder ausgeblendet.

Jetzt gibt es neuerdings Dateien, die man vollautomatisch jede Nacht importieren will. Dazu muss der Dateiname per Kommandozeilenparameter übergeben werden, und dann muss derselbe Ablauf passieren wie wenn der Benutzer die Datei von Hand gewählt hätte. Und damit der Benutzer den Vorgang - zumindest in der Testphase - am Bildschirm mitverfolgen kann möchte ich gerne, dass die Statuszeile „lebt“, und aus demselben Grund auch, dass der „Abbrechen“ Button funktioniert.

Wie ich Kommandozeilenparameter behandeln muss weiß ich. Der Umbau der Importroutine - so, dass ihr der Dateiname, der bisher manuell gewählt wird, per Parameter übergeben wird - ist kein Problem. Mir fehlt konkret die Idee, wie ich den Automatismus in das bestehende Form sauber einpflanze, damit Statuszeile und „Abbrechen“ Button funktionieren.

…Armin

Hallo,

application.doevent() Anweisungen platziert

das klingt nach Excel VBA?

draußen per Catch zum Abbruch der Importschleife führt.

Und das nach VB-NET.

Jetzt gibt es neuerdings Dateien, die man vollautomatisch jede
Nacht importieren will. Dazu muss der Dateiname per
Kommandozeilenparameter übergeben werden,

Warum? Das verstehe ich nicht.
Für solche Zwecke lege ich eine Verknüpfung zur .exe an, schreibe dort die Parameter und starte diese Verknüpfung per Taskplaner. Wozu brauchst Du da das Konsolenfenster?

und dann muss
derselbe Ablauf passieren wie wenn der Benutzer die Datei von
Hand gewählt hätte. Und damit der Benutzer den Vorgang -
zumindest in der Testphase - am Bildschirm mitverfolgen kann
möchte ich gerne, dass die Statuszeile „lebt“, und aus
demselben Grund auch, dass der „Abbrechen“ Button
funktioniert.

Ja, kein Problem.
Der übergebene Parameter landet (in VB6 zumindest) in der Variablen ‚Command‘ und abhängig davon, ob da etwas übergeben wurde oder nicht, kannst Du das Programm verzweigen.

Wie ich Kommandozeilenparameter behandeln muss weiß ich. Der
Umbau der Importroutine - so, dass ihr der Dateiname, der
bisher manuell gewählt wird, per Parameter übergeben wird -
ist kein Problem. Mir fehlt konkret die Idee, wie ich den
Automatismus in das bestehende Form sauber einpflanze, damit
Statuszeile und „Abbrechen“ Button funktionieren.

Hmmm. Wenn ein Parameter vorhanden ist, wird der Pfad übergeben, sonst das Fenster zur Pfadauswahl geöffnet, das ist Dir klar.
Dann wird die Importroutine gestartet, als hätte jemand auf den Button geklickt … (Call Command1_Click) … und ab da läuft das Programm ohne Unterschied, wie sonst auch.

Hilf mir doch mal vom Schlauch, warum soll der Abbrechen Button nicht funktionieren, wenn das Programm automatisch gestertet wurde, wenn er doch beim manuellen Start funktioniert? Das ist doch die selbe Stelle im Code. Der Unterschied ist nur, wie der Code gestartet wurde, ab da gibt es keinen Unterschied zum manuellen aufruf mehr, das Programm sieht genau so aus und verhält sich auch so. Daß es sich am Ende anders verhalten soll, nämlich bei manueller Bedienung offen bleiben und bei automatischem start beendet werden, musst Du selbst programmieren.

Aber ein Flag einfügen, bei datenübergabe das Flag auf true setzen und dann … If Flag = True Then End … ist sicher nicht Dein Problem.

Ich glaube, ich habe immer noch nicht verstanden, wo Du ein Problem hast. Was ich bis jetzt geschreiben habe, weiß Du alles selbst.

Gruß Rainer

Nachtrag …
Hallo,

ich habe mal mit den Fingern auf der Tastatur ‚gedacht‘, das ist oft deutlicher.

Dim Datei As String
Dim Flag As Boolean

Private Sub cmdStart\_Click()
 Dim i As Long, ff As Integer, txt As String
 ff = FreeFile
 Open Datei For Input As #ff
 While Not EOF(ff)
 Line Input #ff, txt
 'Zeile verarbeiten, Statuszeile aktualisieren
 DoEvents
 Wend
 Close #ff
End Sub

Private Sub cmdStop\_Click()
 'Abbrechen vorbereiten
 If Flag = True Then
 End
 End If
End Sub

Private Sub Form\_Load()
 If Command = "" Then
 Datei = CommonDialog1.FileName
 Else
 Datei = Command
 Flag = True
 'eventuell Schaltflächen deaktivieren, ausblenden ...
 Call cmdStart\_Click
 End If
End Sub

Das würde sich so verhalten, wie Du es möchtest. Hilft das?

Gruß Rainer

Hallo Rainer,

application.doevent() Anweisungen platziert

das klingt nach Excel VBA?

nix nix, kein Excel-Vba *freu*, klares Vb oder .Net oder so :smile:

Wahrscheinlich liegt die Lösung am fehlenden Multithrading *wildrat*

http://msdn.microsoft.com/de-de/library/bd65th41.aspx

Gruß
Reinhard

Hallo Reinhard,

application.doevent() Anweisungen platziert

das klingt nach Excel VBA?

nix nix, kein Excel-Vba *freu*, klares Vb oder .Net oder so

-)

OK, VB6 ist das nicht, Du sagst, VBA auch nicht … Dann warte ich mal auf Nimrals Kommentar, denn dann müsste das VB.Net sein und ich verschiebe. :smile:

Wahrscheinlich liegt die Lösung am fehlenden Multithrading
*wildrat*

http://msdn.microsoft.com/de-de/library/bd65th41.aspx

Nein, Multithreading brauchen wir dafür nicht. Da sind genug Doevents im Code, das genügt für diesen Zweck.

Gruß Rainer

Hallo,

application.doevent() Anweisungen platziert

das klingt nach Excel VBA?

Gibt es so auch in VB .Net. Normalerweise sind die Programme da so strukturiert, dass die Eventschleife des Formulars leer durchläuft bis der Anwender auf ein Menü oder einen Button klickt. Dann rennt irgendeine benutzerroutine los. Alle Formularcontrols sind tot, bis die Benutzerroutine zurückkehrt. Das hat zur Folge, dass irgendwelche Veränderungen an Controls, z.B. der Text in der Statuszeile oder ein Fortschrittsbalken nicht angezeigt werden, obwohl man sie programmtechnisch tadellos auf Werteäsetzen kann. Es wird erst der letzte Wert (100%) angezeigt, wenn die eigene Routine fertig ist und das Programm wieder zur Ereignisschleife des Formulars zurückkehrt. Wenn man Ewährend der Laufzeit der eigenen Routine einen Refresh des Bildschirms wünscht, damit Fortschrittsblken laufen und Statustexte sichtbar werden, muss man das zu Fuß tun, indem man immer mal wieder application.doevents() aufruft. Das hat aber mit meinem aktuellen Problem wenig zu tun.

draußen per Catch zum Abbruch der Importschleife führt.

Und das nach VB-NET.

Treffer, versenkt :smile:

Jetzt gibt es neuerdings Dateien, die man vollautomatisch jede
Nacht importieren will. Dazu muss der Dateiname per
Kommandozeilenparameter übergeben werden,

Warum? Das verstehe ich nicht.
Für solche Zwecke lege ich eine Verknüpfung zur .exe an,
schreibe dort die Parameter und starte diese Verknüpfung per
Taskplaner. Wozu brauchst Du da das Konsolenfenster?

Von Konsole war ja auch nie die Rede. Kommandozeilenparameter wollte ich. Da sind wir uns einig :smile:

Dann wird die Importroutine gestartet, als hätte jemand auf
den Button geklickt … (Call Command1_Click) … und ab da
läuft das Programm ohne Unterschied, wie sonst auch.

Wir nähern uns dem Kern :smile: Genau das hatte ich vor. Das Problem: WO hänge ich den eigenen Aufruf rein, damit die Statuszeile des Formulars und der Abbrechen Button verwendbar bleiben. Die Ereignisschleife wäre wohl der ideale Einhängpunkt, aber die ist AFAIK in VB.Net out of reach für den Programmierer. Klemme ich den Aufruf in Form_Load, klappts nicht weil die Eventschleife des Formulars noch nicht läuft. Klemme ich den Aufruf ins Formular, krallt sich die Eventschleife des Formulars höchstwahrscheinlich den Ablauf und wartet drauf, dass ich endlich ein Control anklicke, was ich aber nicht tun will :smile:

Es geht nicht um die Routine und die Parameter, es geht um das „WO einhängen“. So ein Formular hat ja 1000 überschreibbare Methoden, und es gibt in vb dutzende exotische Controls die ich noch nie angeschaut habe. Ich suche einen Wegweiser der mir die Sucherei abkürzt.

Hilf mir doch mal vom Schlauch, warum soll der Abbrechen
Button nicht funktionieren, wenn das Programm automatisch
gestertet wurde, wenn er doch beim manuellen Start
funktioniert?

Wegen der Eventschleife des Formulars. Starte ich sie, übernimmt sie die Kontrolle. Statusanzeige und Abbrechen Button gehen, aber wie wartet dann auf irgendeine manuelle Aktion von mir. Starte ich sie nicht, läuft mein Import automatisch, klar, aber dann werden alle Controls nicht beachtet, also keine Stausanzeige und kein Abbrechen Button, und der Application.Doevents() Trick hilft mir nicht, weil kein Formular läuft, undd amit auch keine Formular-Eventschleife die ich triggern könnte.

Vor Jahren hatte ich ein ähnliches Problem in Delphi zu lösen. Formulartechnisch arbeitet es - wie wohl alle Windows Programmiersprachen - mit demselben Event-Konzept. Damals habe ich mir so beholfen dass ich in der Load Routine des Formulars einen eigens generierten Event in die Eventschleife packe der genau dem Event entspricht der ausgelöst wird, wenn man ein bestimmtes Menü anwählt. Durch diese Art der „Selbstbefriedigung“ lief mein Formular damals automatisiert los. Am Ende meiner Routine habe ich damals den Event für den Menüpunkt „Programm beenden“ mit demselben Trick an das Formular geschickt, und das Programm hat sich brav selber beendet. Das könnte ich eventuell wieder so machen, aber ich bin irgendwie sicher dass es dafür eine bessere Lösung gibt. Außerdem gab es manchmal Event-Chaos wenn andere Events in die Eventschleife des Formulars geraten sind, das Ganze war also nicht 100% betriebssicher.

Ich habe damals keine bessere Lösung gefunden. Jetzt habe ich den Ehrgeiz, es diesmal besser zu machen :smile:

Wirds jetzt deutlicher wo mein Problem liegt?

…Armin

Hallo,

ich habe mal mit den Fingern auf der Tastatur ‚gedacht‘, das
ist oft deutlicher.

Tut es nicht, das war auch mein erster Versuch. Die Routine läuft natürlich. Aber in der Form_Load Anweisung kann ich das nicht so machen, weil die Statuszeile und der Abbrechen Button bei dieser Vorgehensweise nicht funktionieren.

Programmtechnisch sind wir auf dem richtigen Weg, aber Form_Load ist nicht der richtige Punkt, den Code einzuhängen.

…Armin

Hallo Armin,

ahhh, jetzt. :smile: Dir fehlt genau die Zeile, die ich vergessen habe.
Me.Show

Private Sub Form\_Load()
 Me.Show
 If Command = "" Then
 Datei = CommonDialog1.FileName
 Else
 Datei = Command
 Flag = True
 'eventuell Schaltflächen deaktivieren, ausblenden ...
 Call cmdStart\_Click
 End If
End Sub

Gruß Rainer

Hallo Armin,

Und das nach VB-NET.

Treffer, versenkt :smile:

OK, nach .Net verschoben. :smile:

Gruß Rainer

Hallo Rainer, Reinhard,

allein schon die Aussage

Try/Catch, application.doevent() schliesst auf .Net :wink:

@ Reinhard

So schön auch Threads sind. Unter .NEt sind sie zwar einfach zu starten, aber sobald sie Werte bearbeiten oder sogar auf Steuerelemente zugreifen muessen, fangen die Probleme an.

Simples Bsp.

'Formular mit nen Button und ner progressbar (pb)

'Bsp. welches nicht laufen wird
Option Infer On
Option Strict On

Public Class Form1

 Private MyTimer As Timer
 Private th As Threading.Thread

 Private Sub Tick(ByVal sender As Object, ByVal e As EventArgs)
 Me.Text = CStr(IIf(Me.Text = "0", "1", "0"))
 End Sub

 Private Sub Form1\_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 MyTimer = New Timer
 With MyTimer
 .Interval = 100
 .Enabled = False
 AddHandler .Tick, AddressOf Tick
 End With
 End Sub

 Private Sub Button1\_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
 MyTimer.Enabled = True 'wir lassen die Form was machen
 th = New Threading.Thread(AddressOf Setprogressbar) 'Wir instanzieren den Thread
 th.Start() 'wir starten den Thread
 End Sub


 Private Sub Setprogressbar()
 Do
 If pb.Value = pb.Maximum Then
 pb.Value = 0 'hier bekommen wir eine Exception (Fehler)
 Else
 pb.Value = pb.Value + 1 'oder auch hier bekommen wir die Exception (Fehler)
 End If
 Threading.Thread.Sleep(100)
 Loop

 End Sub
End Class

Und nun das selbe Bsp. was läuft. Schau dir mal die Aenderung an. Damit siehst du das es nicht so einfach ist! Wobei auch hier nur 1 Steuerelement bearbeitet wird. Stell Dir mal ne Form vor mit 100 Steuerelementen. Auwei sagsch nur

Option Infer On
Option Strict On

Public Class Form1

 Private MyTimer As Timer
 Private th As Threading.Thread

 Private Sub Tick(ByVal sender As Object, ByVal e As EventArgs)
 Me.Text = CStr(IIf(Me.Text = "0", "1", "0"))
 End Sub

 Private Sub Form1\_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 MyTimer = New Timer
 With MyTimer
 .Interval = 100
 .Enabled = False
 AddHandler .Tick, AddressOf Tick
 End With
 End Sub


 Private Sub Button1\_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
 MyTimer.Enabled = True 'wir lassen die Form was machen
 th = New Threading.Thread(AddressOf Setprogressbar) 'Wir instanzieren den Thread
 th.Start() 'wir starten den Thread
 End Sub


 Delegate Sub SetValueDelegate()

 Private Sub SetValue()
 If pb.InvokeRequired Then
 pb.Invoke(New SetValueDelegate(AddressOf SetValue))
 Else
 If pb.Value = pb.Maximum Then
 pb.Value = 0
 Else
 pb.Value += 1
 End If
 End If
 End Sub

 Private Sub Setprogressbar()
 Do
 Threading.Thread.Sleep(100)
 SetValue()
 Loop
 End Sub
End Class

MfG Alex

Hi Alex,

allein schon die Aussage

Try/Catch, application.doevent() schliesst auf .Net :wink:

Du siehst, mit .Net bin ich immer noch nicht ganz warm.
Ist die Lösung oben richtig? Geht Me.Show mit .Net wie gewohnt?

Gruß Rainer