Benutzer mit den meisten Antworten
Nachricht aus Klasse an eine Form übergeben

Frage
-
Hi zusammen,
im Moment verzweifele ich an einer kleinen Schulaufgabe an der ich schon seit einigen Tagen sitze. Dabei ist das Programm gar nicht mal so groß.
Ich habe viel gegoogelt und auch sehr viel gefunden. Sicherlich hilfreich wenn man es versteht. Ich glaube ich bin zu doof dafür...
Daher die Frage ob mir jemand speziell bei meinem Problem helfen kann.
Aufgabe ist es aus einer Form Threads zu starten welche das klassische Produzenten-Konsumenten-Problem beschreibt. Threads und so habe ich alles schon hinbekommen. Aber wie schaffe ich es, dass der Thread eine Textnachricht an eine Listbox in der Userform übergeben wird?
Habe schon mit Get/Set probiert, habe die listbox auch schon auf public gesetzt aber das war mehr stochern im Nebel.
der Code:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace GPI13_2_KonsumentenProduzenten { public partial class frmKonsumentenProduzenten : Form { public frmKonsumentenProduzenten() { InitializeComponent(); } private void frmKonsumentenProduzenten_Load(object sender, EventArgs e) { int aktLagerbestand = 0; Lager lg = new Lager(aktLagerbestand); Random r = new Random(); System.Windows.Forms.Timer t1 = new System.Windows.Forms.Timer(); System.Windows.Forms.Timer t2 = new System.Windows.Forms.Timer(); Thread ent = new Thread(new ThreadStart(lg.Entnehmen)); Thread prod = new Thread(new ThreadStart(lg.Produzieren)); lbAktivitat.Items.Add("Aktueller Lagerbestand: " + aktLagerbestand); ent.Start(); prod.Start(); } public string nachricht //--> Völlig falsch oder? { get { return } set { lbAktivitat.Items.Add(value); } } } } class Lager { int lagerbestand; //Soll laut Aufgabe private sein Random r = new Random(); public Lager(int lagerbestand) { this.lagerbestand = lagerbestand; } public void Entnehmen() { while (true) { lock (this) { int ent = r.Next(2, 100); //Zahlen ZWISCHEN 1 und 100 while (lagerbestand - ent < 0) Monitor.Wait(this); lagerbestand -= ent; //Jetzt soll der Lagerbestand in der Listbox ausgegeben werden... Monitor.PulseAll(this); } Thread.Sleep(r.Next(50, 1000)); } } public void Produzieren() { while (true) { lock (this) { int prod = r.Next(2, 100); //Zahlen ZWISCHEN 1 und 100 while (lagerbestand + prod > 10000) Monitor.Wait(this); lagerbestand += prod; //Hier soll auch der Lagerbestand in der Listbox ausgegeben werden... Monitor.PulseAll(this); } Thread.Sleep(r.Next(50, 1000)); } } }
Freue mich über eure Antwort
Gruß
Matze
Antworten
-
Hallo,
wenn eine Klasse eine andere Klasse über etwas informieren will, benutzt diese ein Event. Dieses musst du dafür in Lager implementieren:
public class Lager{ public event EventHandler MyEvent;
Das Auslösen geht dann wie der Aufruf einer normalen Methode:
var evt = thgis.MyEvent; if(evt!=null)//Event abonniert? Wenn ja -> Aufrufen evt(this, EventArgs.Empty);
Statt EventHandler kannst du auch EventHandler<T> nutzen, wobei T eine von EventArgs abgeleitete Klasse ist. So kannst du eigene Daten übergeben. Beim Aufrufen musst du dann entsprechend auch eine Instanz der Klasse mit übergeben.
Beim Anlegen einer Lager-Instanz kannst du nun das EVent abonnieren:
Lager lg=new Lager(); lg.MyEvent += myEventhandler; private void myEventhandler(Object sender, EventArgs e){ //Hier hast nun vollen Zugriff auf die Form. }
Ein vollständiges Beispiel (eigentlich für den Datenaustausch zwischen Forms gedacht) findest du hier auf meiner Webseite. Das Prinzip ist genau das gleiche, nur dass du statt einem zweiten Fenster nur eine Klasse besitzt.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets- Als Antwort markiert Idhaun Mittwoch, 8. Juli 2015 13:13
-
Hallo,
den Dispatcher brauchst du nur in WPF Projekten. Soweit ich weiß benutzt du aber Windows Forms. Das letzten Endes der selbe Mechanismus dahinter steckt ist dabei erstmal unwichtig.
Ich habe dir weiter oben schon mal diesen Code Schnipsel gegeben:
this.Invoke(new Action(()=>{ //Der Code läuft nun Synchron zur GUI //Du kannst hier problemlos auf alles zugreifen was außerhalb der geschweiften Klammern steht. }));
Dort wo der zweizeilige Kommentar steht musst du alle Zeilen hinein schreiben die auf die GUI-Elemente (Controls) zugreifen. So verhinderst du den fehlerhafte Threadübergreifenden Zugriff.
Ich kann dir übrigens nur Zustimmen das die Aufgabe nicht wirklich Anfängertauglich ist. Man muss eigentlich schon alles über die syntaktischen Elemente C#s wissen bevor man so etwas ohne Probleme hin bekommt.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets- Als Antwort markiert Idhaun Mittwoch, 8. Juli 2015 13:13
Alle Antworten
-
Hallo,
wenn eine Klasse eine andere Klasse über etwas informieren will, benutzt diese ein Event. Dieses musst du dafür in Lager implementieren:
public class Lager{ public event EventHandler MyEvent;
Das Auslösen geht dann wie der Aufruf einer normalen Methode:
var evt = thgis.MyEvent; if(evt!=null)//Event abonniert? Wenn ja -> Aufrufen evt(this, EventArgs.Empty);
Statt EventHandler kannst du auch EventHandler<T> nutzen, wobei T eine von EventArgs abgeleitete Klasse ist. So kannst du eigene Daten übergeben. Beim Aufrufen musst du dann entsprechend auch eine Instanz der Klasse mit übergeben.
Beim Anlegen einer Lager-Instanz kannst du nun das EVent abonnieren:
Lager lg=new Lager(); lg.MyEvent += myEventhandler; private void myEventhandler(Object sender, EventArgs e){ //Hier hast nun vollen Zugriff auf die Form. }
Ein vollständiges Beispiel (eigentlich für den Datenaustausch zwischen Forms gedacht) findest du hier auf meiner Webseite. Das Prinzip ist genau das gleiche, nur dass du statt einem zweiten Fenster nur eine Klasse besitzt.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets- Als Antwort markiert Idhaun Mittwoch, 8. Juli 2015 13:13
-
Hi Tom,
vielen Dank für deine Antwort. Ich habe das mal stumpf bei mir implementiert aber das klappt irgendwie noch nicht so. Ich werde anhand deiner Informationen noch ein bisschen weiter forschen und wenn es dann geklappt hat oder nicht da gebe ich dann morgen Bescheid drüber.
Gruß
Matze
-
Okay sieht soweit ganz gut aus mit einem kleinen Fehler noch...
System.InvalidOperationException wurde nicht behandelt.
_HResult=-2146233079
_message=Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement lbAktivitat erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.
HResult=-2146233079
IsTransient=false
Message=Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement lbAktivitat erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.
Source=System.Windows.Forms
StackTrace:
bei System.Windows.Forms.Control.get_Handle()
bei System.Windows.Forms.ListBox.NativeAdd(Object item)
bei System.Windows.Forms.ListBox.ObjectCollection.AddInternal(Object item)
bei System.Windows.Forms.ListBox.ObjectCollection.Add(Object item)
bei GPI13_2_KonsumentenProduzenten.frmKonsumentenProduzenten.MyEventHandler(Object sender, DataTransferEventArgs e) in c:\Users\Matthias\Documents\Visual Studio 2013\Projects\GPI13_2_KonsumentenProduzenten\GPI13_2_KonsumentenProduzenten\Form1.cs:Zeile 41.
bei GPI13_2_KonsumentenProduzenten.Lager.Produzieren() in c:\Users\Matthias\Documents\Visual Studio 2013\Projects\GPI13_2_KonsumentenProduzenten\GPI13_2_KonsumentenProduzenten\Form1.cs:Zeile 88.
bei System.Threading.ThreadHelper.ThreadStart_Context(Object state)
bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
bei System.Threading.ThreadHelper.ThreadStart()
InnerException:
Der kommt nicht damit klar, dass die Nachricht Threadübergreifend geschickt werden kann. Ich frage mich so langsam was uns unser Lehrer eigentlich für eine seltsame Aufgabe gegeben hat... Der will uns wohl ärgern -.-*
An dem Fehler muss ich wohl noch forschen...
Hier nochmal der Code:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace GPI13_2_KonsumentenProduzenten { public partial class frmKonsumentenProduzenten : Form { public frmKonsumentenProduzenten() { InitializeComponent(); } private void frmKonsumentenProduzenten_Load(object sender, EventArgs e) { int aktLagerbestand = 0; Lager lg = new Lager(aktLagerbestand); lg.MyEvent += MyEventHandler; Random r = new Random(); System.Windows.Forms.Timer t1 = new System.Windows.Forms.Timer(); System.Windows.Forms.Timer t2 = new System.Windows.Forms.Timer(); Thread ent = new Thread(new ThreadStart(lg.Entnehmen)); Thread prod = new Thread(new ThreadStart(lg.Produzieren)); lbAktivitat.Items.Add("Aktueller Lagerbestand: " + aktLagerbestand); ent.Start(); prod.Start(); } public void MyEventHandler(object sender, DataTransferEventArgs e) { lbAktivitat.Items.Add(e.Data); //<-- Hier wird mir die Ausnahme ausgeworfen... } } class Lager { int lagerbestand; Random r = new Random(); public event EventHandler<DataTransferEventArgs> MyEvent; public Lager(int lagerbestand) { this.lagerbestand = lagerbestand; } public void Entnehmen() { while (true) { lock (this) { int ent = r.Next(2, 100); //Zahlen ZWISCHEN 1 und 100 while (lagerbestand - ent < 0) Monitor.Wait(this); lagerbestand -= ent; var evt = this.MyEvent; if (evt != null) evt(this, new DataTransferEventArgs(lagerbestand.ToString())); Monitor.PulseAll(this); } Thread.Sleep(r.Next(50, 1000)); } } public void Produzieren() { while (true) { lock (this) { int prod = r.Next(2, 100); //Zahlen ZWISCHEN 1 und 100 while (lagerbestand + prod > 10000) Monitor.Wait(this); lagerbestand += prod; var evt = this.MyEvent; if (evt != null) evt(this, new DataTransferEventArgs(lagerbestand.ToString())); Monitor.PulseAll(this); } Thread.Sleep(r.Next(50, 1000)); } } } //Klasse von Tom Lambert, http://code-13.net/Articles/?id=31 public class DataTransferEventArgs : EventArgs//Klasse von EventArgs ableiten, damit wir die Daten transportieren können. { public DataTransferEventArgs(string data)//Konstruktor { this.Data = data; } public readonly string Data;//Durch readonly vor Veränderung schützen. } }
Gruß
Matze
EDIT: Die Lösung mit der Datentransferklasse ist Super :-)- Bearbeitet Idhaun Sonntag, 5. Juli 2015 12:37
-
Hallo,
der Fehler des Threadübergreifenden Zugriffs ist überall in der GUI Entwicklung anzutreffen. Das Problem ist, dass man immer nur aus dem Thread auf die GUI zugreifen kann, der auch das jeweilige Fenster erstellt hat. Will man aus einem anderen Thread das Fenster aktualisieren muss man dafür über die Invoke (bzw. Dispatcher.Invoke in WPF und Apps) Methode gehen:
this.Invoke(new Action(()=>{ //Der Code läuft nun Synchron zur GUI //Du kannst hier problemlos auf alles zugreifen was außerhalb der geschweiften Klammern steht. }));
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets -
Hi,
Sorry das ich mich so spät melde, hatte viel zu tun.
Es hat leider nicht geklappt. Ich kann nicht auf die Invoke Eigenschaft zugreifen. Habe mal gegoogled dafür benötige ich wohl System.Windows.Threading.Dispatcher. Aber das bekomme ich irgendwie nicht. Brauche ich noch einen Verweis auf einen anderen Namensraum?
Die Aufgabe ist schon echt schwer für einen Anfänger. Ich weiß nicht so recht ob ich da überhaupt noch auf dem richtigen weg bin...
Gruß
Matze
-
Hallo,
den Dispatcher brauchst du nur in WPF Projekten. Soweit ich weiß benutzt du aber Windows Forms. Das letzten Endes der selbe Mechanismus dahinter steckt ist dabei erstmal unwichtig.
Ich habe dir weiter oben schon mal diesen Code Schnipsel gegeben:
this.Invoke(new Action(()=>{ //Der Code läuft nun Synchron zur GUI //Du kannst hier problemlos auf alles zugreifen was außerhalb der geschweiften Klammern steht. }));
Dort wo der zweizeilige Kommentar steht musst du alle Zeilen hinein schreiben die auf die GUI-Elemente (Controls) zugreifen. So verhinderst du den fehlerhafte Threadübergreifenden Zugriff.
Ich kann dir übrigens nur Zustimmen das die Aufgabe nicht wirklich Anfängertauglich ist. Man muss eigentlich schon alles über die syntaktischen Elemente C#s wissen bevor man so etwas ohne Probleme hin bekommt.
Tom Lambert - .NET (C#) MVP
Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets- Als Antwort markiert Idhaun Mittwoch, 8. Juli 2015 13:13
-
Du suchst sicherlich so etwas hier:
public delegate void SetStatusTextCallback(string pValue); public void SetStatusText(string pValue) { try { if (this.lbl_Status.InvokeRequired) { SetStatusTextCallback d = new SetStatusTextCallback(SetStatusText); this.Invoke(d, new object[] { pValue }); } else { this.lbl_Status.Text = pValue; } } catch (Exception ex) { } }
Grüße Benutzer0000 -
Hallo zusammen,
auch wenn dieser Thread hier schon etwas älter ist, versuche ich's mal :). Wenn ich das Beispiel hier nachbastle, bekomme ich in der "Entnehmen" Methode eine "SynchronizationLockException" an der Stelle "Monitor.Wait(this)"...
Manchmal läuft das Programm für ein paar Sekunden und es wird mir in meiner Listbox abwechselnd entnommen/produziert angezeigt (was es ja auch soll) und manchmal bricht es sofort ab. Muss deshalb noch irgendwas synchronisiert oder abgefangen werden?
Vielen Dank im Voraus
public void Entnehmen() { while (true) { int entnahme = r.Next(1, 101); while (bestand < entnahme) Monitor.Wait(this); lock (this) { bestand -= entnahme; dataEvent?.Invoke(this, new DataTransferEvent( entnahme.ToString() + " entnommen. " + "Aktueller Bestand: " + bestand.ToString())); Monitor.PulseAll(this); } Thread.Sleep(50); } }
- Bearbeitet BlackBird951 Samstag, 17. Dezember 2016 22:38