Forum: PC-Programmierung C#: textBox und serialPort1


von Serge (Gast)


Lesenswert?

Auf Form1 ist ein textBox- und ein serialPort1 Objekt.

Kommt ein Zeichen von der seriellen Schnittstelle, so soll es gleich in 
der Textbox erscheinen. Also verwende ich das dafür vorgesehene Event.

Der Witz ist nun, wenn ein Zeichen von der seriellen Schnittstelle 
kommt, dann erhalte ich eine Fehlermeldung.

Wie kann ich das Problem lösen/umgehen?

Event:
1
        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
2
        {
3
            textBox1.AppendText(serialPort1.ReadChar().ToString());
4
        }

Fehlermeldung:
Ein Ausnahmefehler des Typs "System.InvalidOperationException" ist in 
System.Windows.Forms.dll aufgetreten.

Zusätzliche Informationen: Cross-thread operation not valid: Control 
'textBox1' accessed from a thread other than the thread it was created 
on.

von Felix A. (madifaxle)


Lesenswert?

Versuche es mal wie folgt:

In deiner Empfangsroutine dieses Stück Code:
1
new_message = "Hallo Welt.";
2
this.BeginInvoke(new EventHandler(updateStatusBox));

Und dann die Routine selbst:
1
// Infos in die Statusbox schreiben
2
private void updateStatusBox(object sender, EventArgs e)
3
{
4
    Byte i;
5
6
    // Auf Anzahl Strings prüfen, wenn nicht voll dann anhängen
7
    if (status_text_content < 8)
8
    {
9
        status_text[status_text_content++] = new_message;
10
    }
11
}

Ich kann dir aber nicht sagen, wie man Werte direkt mit 
"this.beginInvoke" übergeben kann.

Nachtrag:
den "status_text[...]" kannst du jetzt durch deinen Textfeldzugriff 
ersetzen.

: Bearbeitet durch User
von bluppdidupp (Gast)


Lesenswert?

Das Problem ist, das DataReceived in einem eigenen Thread läuft, und 
nicht in dem Thread aufgerufen wird, in dem die GUI-Elemente erstellt 
wurden. Auf Windows.Forms Controls (Textboxen, etc.) muss aber in der 
Regel aus dem gleichen Thread heraus zugegriffen werden, worin die 
Steuerelemente auch erstellt wurden.
Das kriegt man hin, indem man per "Invoke" den Code im Thread des 
Elements aufruft.

Eine sehr einfache Möglichkeit sieht man z.b. hier:
http://stackoverflow.com/questions/6816416/methodinvoker-vs-control-invoke
...das sähe hier dann z.B. so aus:
1
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
2
{
3
4
  string appendText=serialPort1.ReadChar().ToString();
5
  
6
  this.Invoke((MethodInvoker)delegate
7
  {
8
     // <Hier der Code der im GUI-Thread läuft>
9
     textBox1.AppendText(appendText);
10
     // </Hier der Code der im GUI-Thread läuft>
11
  });
12
}
...das "this" wäre in diesem Falle die Form. Das Invoke() ruft den Code 
dann im Thread in dem diese Form erzeugt wurde auf.

von c-hater (Gast)


Lesenswert?

Serge schrieb:
> Auf Form1 ist ein textBox- und ein serialPort1 Objekt.

> Kommt ein Zeichen von der seriellen Schnittstelle, so soll es gleich in
> der Textbox erscheinen. Also verwende ich das dafür vorgesehene Event.
>
> Der Witz ist nun, wenn ein Zeichen von der seriellen Schnittstelle
> kommt, dann erhalte ich eine Fehlermeldung.

Das ist normal, die Events des Serialport erfolgen im Kontext 
zusätzlicher Threads, innerhalb solcher Events darf man nicht am GUI 
rumpfuschen, denn dieses ist nicht thread-sicher programmiert.

Die Lösung ist, die Events mit dem Main-Thread, der das GUI "betreibt" 
zu synchronisieren. Dafür bieten freundlicherweise fast alle 
GUI-Elemente die Methoden InvokeRequired, Invoke und BeginInvoke an.

Du brauchst also pro SerialPort-Event zwei Eventhandler, einen, der 
asynchron im Kontext des SerialPort-Threads läuft (und ggf. eine 
Vorverarbeitung der gelieferten Daten leistet) und einen zweiten, der 
synchron im Kontext des Haupthreads läuft.

>
1
> 
2
>         private void serialPort1_DataReceived(object sender, 
3
> System.IO.Ports.SerialDataReceivedEventArgs e)
4
>         {
5
>             textBox1.AppendText(serialPort1.ReadChar().ToString());
6
>         }
7
> 
8
>

Das ist sowieso ziemlicher Mist, selbst wenn die Events des SerialPort 
von Hause aus synchronisiert wären.

Das kann nur dann funktionieren, wenn nur alle Jubeljahre mal ein 
einzelnes Zeichen eintrödelt. Sobald der Sender schneller sendet, als 
der ReadTimeout bestimmt, geht das Konstrukt den Bach runter. Oder auch 
dann, wenn der ReadTimeout zwar senderseitig nicht unterschritten wird, 
aber die Verarbeitung des Zeichens länger dauert und deshalb das nächste 
Zeichen vom Sender schon da ist, bevor die Verarbeitung des aktuellen 
abgeschlossen ist.

Aber tun wir mal so, als wären diese Gegenindikationen nicht gegeben und 
die Routine würde auf Grund der Randbedingungen prinzipiell 
funktionieren  können, das einzige Problem wäre also wirklich nur die 
Thread-Synchronisation. Dann wäre die Lösung:

>
1
private delegate void SerialPortCharReceivedHandler(object sender, char data);
2
3
private SerialPortCharReceivedSync(object sender, char data)
4
{
5
  textBox1.AppendText(data.ToString());
6
}
7
8
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
9
{
10
  char data=(sender as SerialPort).ReadChar();
11
  Invoke(new SerialPortCharReceivedHandler(SerialPortCharReceivedSync, new object[] {sender,data});
12
}
13
>

von Borislav B. (boris_b)


Lesenswert?

Warum nicht per Lambda?
1
private void mySerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
2
{
3
   SerialPort sp = (SerialPort)sender;
4
   string s = sp.ReadExisting();
5
6
   this.Invoke(() => { textbox1.AppendText(s) });
7
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.