Autor Beitrag
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Di 07.06.22 09:49 
Deine ReadResult liest doch auch von der s7? Da müßte genauso die Prämisse gelten das du zu dieser verbunden sein mußt.

Zitat:
Meine erste Frage wäre ob ich den Backgroundworker auch mit einer Schleife und Thread.Sleep aufrufen könnte?

Eher nicht weil Thread.Sleep den Thread anhält. Da das im Moment der UI Thread wäre hälst du deine UI an.
Eine Option wäre die Schleife IM Backgroundworker zu haben den kannst du gefahrlos immer wieder pausieren weil der ja aufgabenspezifisch ist und nicht noch andere Aufgaben hat (wie z.B. die UI zu aktualisieren). Dann mußt du dir aber wieder Gedanken machen wie du synchronisierte Aufrufe aus dem Backgroundworker machst um auf die UI zuzugreifen. Das war gerade der Charme der Lösung Winforms Timer + Backgroundworker. Du konntest erst den UI zugriff machen und dann den Backgroundworker starten.

Übrigens dein Link ist wenig hilfreich bezügl Timer. Das ein anderes System wie z.b. die s7 regelmäßig Bescheid gibt (Signale sendet das sich was geändert hat) ist eher selten. Meist muß man doch selber regelmäßig prüfen. Ob dein System entsprechende Signale (wie auch immer) sendet und du damit teoretisch ohne regelmäßiges prüfen auskommst sollte dir die Dokumentation sagen.
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: Mi 08.06.22 11:02 
Servus,

Vielen Dank für Deine Antworten.

Zitat:
Leider verstehe ich dein Problem mit dem Trigger-Signal nicht so richtig

Also ich kann mit:

ausblenden C#-Quelltext
1:
2:
 if (s7Client.Connected)
{


Überprüfen, ob das Tool sich mit der SPS verbunden hat.

Die SPS selbst weiß gar nicht, dass dieses Tool überhaupt existiert. Wird sie auch nie.
Das Tool ist dafür da, sich mit der SPS zu verbinden, Bits zu lesen, die IO-Operationen durchzuführen und dann Bits auf 0 oder 1 zu setzen.

Nun gut ich habe es geschafft ein Bit in der SPS zu setzen, wenn das Tool die Verbindung aufgebaut hat.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
 private void TimerCopyAllFiles_Tick(object sender, EventArgs e)
{
 S7VariablenResult s7VariablenResult = ReadResult();

 if (s7Client.Connected)
 {
   CPUIsConnected();
 }
 else
 {
   btnConnectToSPS.Enabled = true;
 }

   ShowS7VariablenStatus(s7VariablenResult);
   ShowPlcStatus();

if (BW_CopyAllFiles.IsBusy != true)
{
if (s7VariablenResult.TrigSaveFiles && (s7VariablenResult.TypLinks || s7VariablenResult.TypRechts) && !s7VariablenResult.FMBauteil)
{
  TimerCopyAllFiles.Enabled = false;
  BW_CopyAllFiles.RunWorkerAsync();
}
}
}


ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
 private void CPUIsConnected()
{
  byte[] buffersize = new byte[1];

  S7.SetBitAt(ref buffersize, 0, uivalues.ConnectToPLCBit, true);

  s7Client.DBWrite(uivalues.DBNr, uivalues.ConnectToPLCByte, buffersize.Length, buffersize);
}


Das ist schon ein Anfang, aber leider nicht ausreichend. Zum Beispiel, das Tool wird geschlossen. Dann ist das Bit aber trotzdem immer noch in der SPS gesetzt.
Die SPS macht weiterhin ihre Arbeit so wie die Bits gesetzt werden oder auch nicht.
Was ich jetzt brauche, ist eine watchdog timer (Sekundentakt) der vom Tool ausgesendet wird. Und dieses Signal 0 oder 1 kann ich dann in der SPS überwachen.
Ich habe jetzt in der S7-Schnittstelle (Sharp7.cs) auch einen Timer gefunden, mal sehen, ob das funktioniert.

Zitat:
Ich sehe aber jetzt ersteinmal (bis auf evtl. höhere Präzision) nichts, was gegen deinen bisherigen Code spricht

Ok, das freut mich, dass ich das so lassen kann. Wäre ohne Eure Hilfe gar nicht möglich gewesen, vielen Dank dafür.
Was meinst Du mit der höheren Präzision?

@Ralf

Vielen Dank auch an Dich.

Zitat:
Deine ReadResult liest doch auch von der s7?

Ich denke das, was ich oben beschrieben habe, müssten vielleicht Deine Frage beantworten.

Zitat:
Das war gerade der Charme der Lösung Winforms Timer + Backgroundworker.

Jetzt so, mit dem neuen Hintergrundwissen über Thread ist es so wohl am besten.
Ich hab halt keine Erfahrung und damit weiß ich nicht wann ich was benutzen kann.
Also viel lesen, testen und fragen, wenn es nicht weiter geht.

So, jetzt noch eine andere Sache, auf die ich gekommen bin, wo ich denke, dass sie zur Qualität beiträgt.
Bei mir ist alles auf einer Form (MainForm) untergebracht. Und alles, was zum Konfigurieren gehört, würde ich gerne auf eine andere Form (ConfigurationForm) auslagern. Ich habe den Button auf der MainForm erstellt.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
 private void Configuration_Click(object sender, EventArgs e)
{
  this.Hide();
  ConfigurationForm configurationForm = new ConfigurationForm();
  configurationForm.ShowDialog();
  if (configurationForm.DialogResult == DialogResult.OK)
  {
     configurationForm.Close();
  }

     this.Show();
}


ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
namespace PDE
{
    public partial class ConfigurationForm : Form
    {

        public ConfigurationForm()
        {
            InitializeComponent();
        }        

        private void ApplyConfiguration_Click(object sender, System.EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
            this.Close();
        }

        private void CancelConfiguration_Click(object sender, System.EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;
            this.Close();
        }

        public string TextInTextBoxAufForm2
        {
            get { return this.txtAufForm2.Text; }
            set { this.txtAufForm2.Text = value; }
        }
    }
}



Die MainForm wird versteckt und die ConfigurationForm wird angezeigt.
Es werden alle Einträge gemacht und wenn fertig, dann die Form mit dem Button schließen.
Das einzige, was sich ändern soll, ist, dass die Werte von den UI-Elementen anstatt von der MainForm jetzt von der ConfigurationForm geholt werden.

Könnt Ihr mir anhand der Textbox "txtAufForm2" sagen, wie ich von der MainForm mit Property und/oder Methoden den Wert erhalte?
Oben ist ein Beispiel mit der Textbox. Die Form aufrufen funktioniert. Die Buttons funktionieren auch.

Was natürlich mein größter Alptraum ist, sind die ganzen Arrays, die ich in der MainForm habe.

@ TH69
PS: Du hast doch auch über Kommunikation mit 2 Forms geschrieben.
Wenn, man den Link auswählt, kommt immer die Meldung, die Seite kann nicht aufgerufen werden.

Grüße Tom
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mi 08.06.22 11:22 
Nur ersteinmal kurz (weil ich jetzt weg muß), der Artikel ist jetzt hier in den "C# & .NET Tutorials": Kommunikation von 2 Forms

Edit:
OK, jetzt verstehe ich dein Trigger Signal Problem besser. Dann schau mal, ob du es mit dem S7-Timer hinbekommst.

UserTom hat folgendes geschrieben:
Was meinst Du mit der höheren Präzision?

Das von mir beschriebene (beschränkte) Timer-Intervall. Aber bei dem Trigger-Signal kommt es wohl nicht auf Millisekunden an, daher sollte es so funktionieren.

UserTom hat folgendes geschrieben:
Könnt Ihr mir anhand der Textbox "txtAufForm2" sagen, wie ich von der MainForm mit Property und/oder Methoden den Wert erhalte?

Da du die ConfigurationForm modal mit ShowDialog aufrufst:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
private void Configuration_Click(object sender, EventArgs e)
{
  this.Hide();
  ConfigurationForm configurationForm = new ConfigurationForm();
  /*var result =*/ configurationForm.ShowDialog();
  if (configurationForm.DialogResult == DialogResult.OK) // alternativ: if (result == DialogResult.OK)
  {
     // configurationForm.Close(); // Dies ist bei ShowDialog überflüssig, da das Fenster automatisch bei OK oder Cancel geschlossen wird.

     var text = configurationForm.TextInTextBoxAufForm2; // Eigenschaft auslesen
  }

  this.Show();
}

Genau dabei sollte dir auch mein Artikel helfen, damit du die Übergabe von Daten zwischen verschiedenen Forms und/oder Klassen besser verstehst.

Bei den vielen Eigenschaften solltest du evtl. besser gleich ein Objekt einer Configuration-Klasse (die ich ja einfach UIValues genannt habe), an die ConfigurationForm übergeben, damit diese direkt (bei OK) befüllt wird.
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: Mi 08.06.22 18:52 
Hallo TH69,

Vielen Dank für den Beispielcode.

Ich denke den habe ich verstanden wie ich den Inhalt einer Textbox auf der ConfigurationForm in eine Variable in der MainForm bekomme.

Ich habe das jetzt mal richtig ausprobiert und habe die Textbox txtChooseTargetPath von der Mainform runtergenommen und auf die neue Form gesetzt.

Diesen Code angepasst:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
namespace PDE
{
    public partial class ConfigurationForm : Form
    {        
        public ConfigurationForm()
        {
            InitializeComponent();
        }        

        private void ApplyConfiguration_Click(object sender, System.EventArgs e)
        {
            this.DialogResult = DialogResult.OK;
            this.Close();
        }

        private void CancelConfiguration_Click(object sender, System.EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;
            this.Close();
        }
############################################################
        public string TextChooseTargetPath
        {
            get { return this.txtChooseTargetPath.Text; }
            set { this.txtChooseTargetPath.Text = value; }
        }
############################################################
    }
}


In der MainForm auch:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
 private void Configuration_Click(object sender, EventArgs e)
        {
            this.Hide();
            ConfigurationForm configurationForm = new ConfigurationForm();
            var result = configurationForm.ShowDialog(this);
            if (result == DialogResult.OK)
            {
                var textChooseTargetPath = configurationForm.TextChooseTargetPath; // Eigenschaft auslesen
                
            }

            this.Show();
        }


Dadurch habe ich in der MainForm einige Fehler gemeldet bekommen, da die Textbox jetzt nicht mehr vorhanden ist.
Ich habe txtChooseTargetPath.Text an einigen stellen genutzt.

Zum Beispiel hier:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
 private void ApplyPathConfig_Click(object sender, EventArgs e)
        {
##########################################################################            
            if (string.IsNullOrEmpty(txtChooseTargetPath.Text)) <--- txtChooseTargetPath.Text ist rot unterstrichen
##########################################################################
            {
                DialogResult result = MessageBox.Show("Ziel-Pfad fehlt?""Confirmation", MessageBoxButtons.YesNo);
                if (result == DialogResult.Yes)
                {
                    SaveLocalPathConfigSettings();
                    SaveNetPathConfigSettings();

                    MessageBox.Show("Konfiguration gespeichert.");
                }
                else if (result == DialogResult.No)
                {
                    return;
                }
            }

            txtChooseTargetPath.Enabled = string.IsNullOrEmpty(txtChooseTargetPath.Text);

            SaveLocalPathConfigSettings();
            SaveNetPathConfigSettings();


Ich dachte da muss jetzt die neue Variable "textChooseTargetPath" rein aber da habe ich mich getäuscht.
Innerhalb der Methode wird diese Variable nicht erkannt, weil ich irgendetwas noch machen muss. Aber was?
Hier bin ich schon wieder aufgeschmissen.

Zitat:
Genau dabei sollte dir auch mein Artikel helfen,

Leider nicht so wie ich gehofft hatte.

Zitat:
Bei den vielen Eigenschaften solltest du evtl.

Also das bedeutet das ich die UIValues.cs noch mit diesen Textboxen, Comboboxen und Checkboxen entweder ergänzen muss oder ich mache eine neue Klasse und nenne sie UIPathValues. Weil diese UI-Elemente sind nur für den Kopierablauf. Ich sollte die UIValues.cs in UISPSValues.cs ändern.

Werden Objekte wie folgt instanziert?? Ein Beispielcode aus dem Internet und von mir ein bisschen angepasst.

Die Klasse mit den UI-Elementen...

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
public class UIPathValues
{
  public string ChooseTargetPath { get; set; }
....
....
....
usw.
  
}


Dann instanzieren in der MainForm?

ausblenden C#-Quelltext
1:
2:
3:
UIPathValues myUIPathValues = new UIPathValues()
myUIPathValues.Data1 = "test";
myUIPathValues.Data2 ="fall";


In der ConfigurationForm muss man den Konstruktor entsprechend anpassen und setzt eine lokale Variable:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
public partial class ConfigurationForm : Form
{
  private UIPathValues _uipathvalues = null;

  public ConfigurationForm(UIPathValues uipathvalues) 
  {
    _uipathvalues = uipathvalues;
  }
}


Dann gleich noch eine Sache und ich denke das ist die schwierigste Sache für mich.
Du weißt ja über den Button wird die ConfigurationForm geöffnet und man trägt alles ein.
Bevor natürlich die Form geschlossen wird muss noch der Button "In den Settings speichern" gedrückt werden.
Diese Click-Event von diesem Button ist in der MainForm und diese Event möchte ich natürlich in die neue Form verlagern. Was muss ich alles in der MainForm machen das dieser Button auf der neuen Form genauso funktioniert als ob er noch in der MainForm wäre???

Da habe ich mir aber etwas eingebrockt. Das das so kompliziert wird (für mich) hätte ich nicht gedacht.
Vielen Dank nochmal für deine tolle Unterstützung.

Grüße Tom
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mi 08.06.22 20:32 
So wie du die neue Klasse UIPathValues sowie den ConfigurationForm-Konstruktor angepaßt hast, genau so meinte ich es. :zustimm:

Jetzt mußt du nur noch in der MainForm davon ein Objekt als privaten Member erstellen:
ausblenden C#-Quelltext
1:
private UIPathValues myUIPathValues = new UIPathValues();					

Und wenn jetzt die Configuration-Klasse die Eigenschaften (bzw. Felder) der UIPathValues füllt, dann kannst du in der MainForm z.B mittels myUIPathValues.textChooseTargetPath darauf zugreifen (die lokale Variable var text = ... war nur Beispielcode, da ich ja nicht genau wußte, in welchem Objekt du die Daten speichern möchtest).

Bedenke, daß sich auch dann das ganze Auslesen und Schreiben der Settings ändert und du dann auf die UIPathValues-Werte zugreifen mußt (solange du diese Methoden dazu in der MainForm beläßt).

Macht denn so die Ereignismethode ApplyPathConfig_Click überhaupt noch einen Sinn, denn dies (also die Settings speichern) sollte doch genau dann nach dem ConfigurationForm.ShowDialog()-Aufruf passieren?
Oder sollen die Änderungen mittels des ConfigurationDialog nicht sofort gespeichert werden, sondern mittels eines zusätzlichen Buttons?

Meintest du dies mit deinen letzten Sätzen?

Du solltest dir am besten mal ein Datenfluß-Diagramm skizzieren, damit dir genau klar wird, welche Daten (Klassen-Objekte) du bei den einzelnen Aktionen benötigst.
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: Do 09.06.22 12:36 
Servus TH69,

Vielen Dank für deine Antworten.

Ich denke, hier hören wir auf. Ich habe gestern alle UIPathValues auf die neue Form verschoben und hatte ein Desaster.
Auf beiden Formen hatte ich logischerweise sehr viele Fehler, fast 100 jeweils.

Dann ist mir eingefallen, auf einer Internetseite gibt es einen Artikel, ob man wirklich eine SubForm braucht.
Aber das geht auch mit einem Tabcontrol und man kann die Tabpage mit Enabled = false sperren.
Es bleibt alles auf der MainForm.

Zitat:
Bedenke, dass sich auch dann das ganze Auslesen und Schreiben der Settings ändert

Das war einer der Auslöser, warum ich abgebrochen habe. Für mich war das nicht mehr handelbar.

Zitat:
Macht denn so die Ereignismethode ApplyPathConfig_Click überhaupt noch einen Sinn

Nein ich denke nicht, da hätte sich was bessere gefunden.

Zitat:
Du solltest dir am besten mal ein Datenfluß-Diagramm skizzieren

Kenne ich gar nicht, hast du vielleicht ein Beispiel auf Basis C#. Welche Software nimmst du?
Oder bist du noch sehr traditionell und erstellst so etwas mit Papier und Bleistift.

Eine letzte Frage, um Spaghetticode zu vermeiden.
Ich würde gerne alle Steuerelemente auf der Tabpage 2 prüfen, ob sie leer sind oder ob etwas aktiviert ist.

Du weißt ja selber, mit dem folgenden Code könnte ich das auch prüfen, aber ich würde für
jedes "Control/Array" wieder eine Kopie von der Methode erstellen.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
 private bool AnyAppIsActivated()
        {
            if (!cbActivateLocalApps.Any(cb => cb.Checked) && !cbActivateNetApps.Any(cb => cb.Checked))
            {
                return true;
            }

            return false;
        }


Ich denke, das muss nicht sein.
Aber wie frage ich in einer Schleife alle Controls ab, ob sie leer sind oder aktiviert?

So schonmal vorab vielen Dank für die ganze Hilfe, echt super von euch beiden.

Grüße Tom
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 09.06.22 13:26 
UserTom hat folgendes geschrieben:
Kenne ich gar nicht, hast du vielleicht ein Beispiel auf Basis C#. Welche Software nimmst du?
Oder bist du noch sehr traditionell und erstellst so etwas mit Papier und Bleistift.

Das bleibt dir überlassen - ich meinte direkt auf Papier (oder mit einem Texteditor am Computer).
Wichtig ist nur zu verstehen, wie Daten herumgereicht werden, insb. wenn es mehr und mehr Daten werden und dann noch zwischen verschiedenen Forms (es sollte möglichst dann immer nur eine Codestelle geben, in welcher die Daten erzeugt und zugewiesen werden, nicht über mehrere Klassen verteilt, sondern stattdessen eben Referenzen herumreichen).
Bei größeren Projekten verwendet man dann sogar Schnittstellen (Interfaces), welche z.B. nur lesenden Zugriff erlauben, um das Schreiben von außerhalb der Klasse zu unterbinden.

Und wegen den vielen Fehlern beim Refaktorieren: das ist meistens normal so (gerade wenn man Codeteile aus einer Klasse entfernt bzw. in eine andere Klasse verschiebt).
Am besten dann größere Codeteile ersteinmal auskommentieren und sich an die Behebung der wichtigsten Fehler machen.

Generell würde man auch, statt alle Elemente auf die Form zu setzen, diese in passende UserControls (Benutzersteuerrelemente) aufteilen (so daß man nicht mehr den gesamten UI-Code in einer Klasse hat - ich hatte mal ein Projekt übernommen [für einen spezialisierten Editor], welcher aus über 10 TabPages bestand und der gesamte Code dazu war in der MainForm untergebracht: mehr als 30.000 Zeilen :hair:, so daß ich diesen auch entsprechend in passende UserControls (je TabPage mindestens 1) ausgelagert habe. Mein aktuelles Hobby-Projekt besteht auch nur aus 2 Forms, aber ~50 UserControls.).
Als Einsteig ist auch Arten von benutzerdefinierten Steuerelementen (Windows Forms .NET) lesenswert.

Und wegen den Control-Arrays - du kannst doch das Array (bzw. die Arrays) als Parameter der Methode hinzufügen:
ausblenden C#-Quelltext
1:
2:
3:
4:
private bool IsAnyChecked(CheckBox[] checkboxes)
{
  return checkboxes.Any(cb => cb.Checked);
}

Wenn du mehrere Arrays verknüpfen willst, dann gibt es einen eleganten Weg mittels des Schlüsselworts params:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
private bool IsAnyChecked(params CheckBox[] checkboxes)
{
  foreach (var cbs in checkboxes)
    if (cbs.Any(cb => cb.Checked))
       return true;
  
  return false;
}

(ob die Logik jetzt 100%ig stimmt, habe ich nicht geprüft, da nur hier im Editor geschrieben)
Bei dem Aufruf kannst du dann beliebig viel Arrays übergeben, z.B.
ausblenden C#-Quelltext
1:
IsAnyChecked(cbActivateLocalApps, cbActivateNetApps);					

Je nach Abfrage, kann es aber erforderlich sein, verschiedene Hilfsmethoden zu entwickeln, z.B. wenn andere Eigenschaften abgefragt werden sollen. Dabei möglichst immer den allgemeinsten Typ nehmen, z.B. bei Text dann Control (anstatt z.B. nur für TextBox).

Edit: Man kann die Methode auch ganz kurz nur mit LINQ implementieren:
ausblenden C#-Quelltext
1:
2:
3:
4:
private bool IsAnyChecked(params CheckBox[] checkboxes)
{
  return checkboxes.SelectMany(cbs => cbs).Any(cb => cb.Checked);
}
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: Mi 15.06.22 12:36 
Hallo Th69,

Vielen Dank für deine Antwort.
Ich hatte ein paar Tage Urlaub und melde mich deswegen jetzt erst.

Also ich bin mir Sicher das ich keine weiteren Formulare brauche.
Ich habe mir mal andere Professionelle Programme angeschaut die sich auch mit einer SPS Verbinden können. Oder Netzwerkprogramme. Oder das Programm einer unserer Applikationen riesig und hat sehr viel zum konfigurieren. Und alle sind mit dem Prinzip von TabControls aufgebaut öder ähnlichem.

Das mit dem Diagramm sollte ich mir noch anschauen. Ich habe keine Ahnung sowas zu erstellen. Also ich bin mir sicher ich habe noch nicht verstanden

Zitat:
wie Daten herumgereicht werden,

Sonst wüste ich sowas umzusetzen.

Zitat:
diese in passende UserControls (Benutzersteuerrelemente) aufteilen

Das wäre genial wenn ich das könnte, ich denke es erspart viel Code. Aber das wäre wieder die gleiche Situation wie mit den Formularen. Ich müsste alles umschreiben.
So ein User Control würde bei mir aus einer Combobox, einer Textbox, einem Panel und 3 Checkboxen bestehen und das Waagerecht angeordnet.
Klar das wäre echt super wenn ich nur noch 8 User Controls bräuchte. OK 16 weil ich 2 Tab Pages habe.
Aber wie bekomme ich die Daten darein?
Ich habe jeweils für alle 8 Steuerelement Typen (Combobox, Textbox,...) ein Array angelegt.
Und natürlich wieder das schlimmste die komplette Application.Settings würde so nicht mehr funktionieren.

Zitat:
mehr als 30.000 Zeilen

Wahnsinn bei so vielen Zeilen wird man ja irre werden. :nut:. Spitzen Leistung aber mit deinem Knowhow kein Problem.

Zitat:
Wenn du mehrere Arrays verknüpfen willst, dann gibt es einen eleganten Weg mittels des Schlüsselworts params:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
private bool IsAnyChecked(params CheckBox[] checkboxes)
{
  foreach (var cbs in checkboxes)
    if (cbs.Any(cb => cb.Checked))
       return true;
  
  return false;
}


Bei diesem Code wird bei cbs.Any das Any rot unterstrichen.
Die Fehlermeldung sagt das Checkbox keine Definition für Any hat.

Wie könnte ich eine Abfrage erstellen ob irgendein Steuerelement auf einer TabPage verändert wurde?
Ich habe an so etwas gedacht?
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
foreach(Control c in currentTab.Controls)
{
    if(c is TextBox)
        // check for text change
    if(c is CheckBox)
        //check for check change
    etc...
}

Ich möchte aber nicht CurrentTab nutzen sondern ein TabPage bestimmen können!

Dann möchte ich mit Directory.Exists und einem Timer die Ordner überwachen ob sie existieren.
Mit einem Panel und der Farbe Grün für vorhanden und Rot nicht vorhanden. Ich denke das funktioniert aber ich denke das der neue Timer in einen separaten Thread gehört. Mit welchem Timer könnte ich das in einem separaten Thread laufen lassen?
Darauf hin habe ich den FileSystemWatcher entdeckt. Aber mit dem kann ich keine Ordner überwachen.
Was meint Ihr dazu? Irgendetwas was Sinn macht?

Vielen Dank nochmal für Eure Hilfe.

Grüße Tom

Moderiert von user profile iconTh69: Beitragsformatierung überarbeitet.
Moderiert von user profile iconTh69: C#-Tags hinzugefügt
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mi 15.06.22 13:13 
Da habe ich noch eine Array-Klammer vergessen (es soll ja ein Array von Arrays übergeben werden):
ausblenden C#-Quelltext
1:
private static bool IsAnyChecked(params CheckBox[][] checkboxes)					
(habe ich gerade selber ausprobiert und da noch ein fehlendes static vom Code Analyzer vorgeschlagen wurde, habe ich es auch noch hinzugefügt)

Und bzgl.
UserTom hat folgendes geschrieben:
Wie könnte ich eine Abfrage erstellen ob irgendein Steuerelement auf einer TabPage verändert wurde?
Ich habe an so etwas gedacht?

Das widerspricht der ereignisorientierten Programmierung bei WinForms - benutze daher die TextChanged bzw. CheckedChange o.ä.)-Ereignisse.

Und dann noch zu dem Umbau und den Settings:
Das ist eigentlich kein Problem - erzeuge dafür dann einfach in den UserControls passende Methoden zum Lesen und Schreiben der Settings und rufe diese dann von der Hauptform aus auf. Statt dann Get/SaveLocalControlTexts etc. für jeweils alle Arrays aufzurufen, übergebe die aktuelle Settings-Nummer als Parameter und lese/schreibe dann die einzelnen Werte des UserControls:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
public void ReadSettings(Settings settings, int nr)
{
  string name = "Setting" + nr; // <- hier richtigen Settings-Eigenschaftennamen eintragen
  checkBox.Checked = (bool)settings[name];
  // ...
}

(so ähnlich sind ja auch deine bisherigen Get/SaveLocalControlTexts-Methoden implementiert)

Oder du fügst zu der Settings-Klasse diese einzelnen Methoden (je Control-Typ) hinzu und rufst sie dann vom UserControl (bzw. den verschiedenen UserControls) auf.

Wenn du mit meiner Erklärung nicht klarkommst, dann zeige mal deine bisherige "Settings.cs"-Datei (am besten als Anhang).

Edit: Und bzgl. FileSystemWatcher habe ich noch den längeren (englischen) Artikel (+ Code) When files and folders do not exist, FileSystemWatcher cannot monitor file changes? It's fine if you listen recursively gefunden.
Und beim FileSystemWatcher brauchst du selber keinen eigenen Timer zu benutzen, das macht die Klasse intern (nur wenn du in den Ereignismethoden dann wiederum UI-Zugriffe hast, so müßtest du dann Control.Invoke(...) benutzen).

Wenn du jedoch nur einen (oder mehrere) Verzeichnisse auf Vorhandensein überprüfen willst, dann ist wohl ein System.Timers.Timer die beste Wahl.
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: Mi 15.06.22 17:06 
Hallo Th69,

Vielen Dank für deine Antwort.

Zitat:
Code Analyzer vorgeschlagen wurde,

Nur wegen der Interesse welche Analyzer benutzt du? Bei mir wurde das mit static nicht vorgeschlagen.

Zitat:
Das widerspricht der ereignisorientierten Programmierung

OK passt.

Zitat:
Und dann noch zu dem Umbau und den Settings:

Ich habe mal um ganz klein anzufangen ein neues WinForm Projekt erstellt.
Darin habe ich ein User Control erstellt. Im Entwurf habe ich dann die Steuerelemente aus dem eigentlichen Projekt kopiert (siehe Bild) und eingefügt.
Das UC ist ja ein komplettes Steuerelement und die einzelnen Steuerelemente da drin sind ja nicht mehr anklickbar, z.B. die Combobox die bekommt ihre Daten von einer Textbox (Multiline).
Wie kann ich jetzt auf die Combobox im User Control zugreifen?
Das ist doch das gleiche Prinzip wie die Kommunikation zwischen 2 Forms?

Das mit dem FileSystemWatcher schaue ich mir mal an.

Die Settings.cs habe ich auch mit angehängt.

Vielen Dank für die ganzen Tipps.

Grüße Tom
Einloggen, um Attachments anzusehen!
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mi 15.06.22 18:17 
Ich benutze z.Z. VS 2019 und bei einem .NET 5-Projekt einfach den Standard-Code Analyzer (als Hilfe dazu wird mir Übersicht über die Analyse von .NET-Quellcode angeboten).

Das UserControl ist im Designer-Modus innerhalb einer anderen Form (oder eines anderen UserControls) nicht editierbar, damit man nicht indirekt Änderungen vornehmen kann - zur Laufzeit ist dieses aber vom Anwender aus komplett bedienbar.

Und für die Übergabe von Daten habe ich extra in meinem Artikel Kommunikation von 2 Forms die Abschnitte "Typische Anfängerfehler / 2. Zugriff auf private Controls einer anderen Form" sowie die Lösung dann mittels "1. Eigenschaften". Man erstellt also eine passende Schnittstelle für dieses UserControl.

Und bei deiner "Settings.cs": vergleiche mal den Code von GetLocalControlTexts und GetNetControlTexts (ebenso bei den anderen Methoden GetLocal/NetCheckBoxes sowie die SaveLocal/Net-Methoden)!

Du brauchst jetzt also nur jeweils zum Lesen und Schreiben je eine Methode für jedes Control (bzw. Control-Eigenschaft) zu erstellen (auch wenn es dann nur je eine Code-Zeile ist).
Probiere das einfach in deinem WinForms-Testprojekt aus.
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: Do 16.06.22 20:18 
Hallo Th69,

Vielen Dank für deine Antwort.

Zitat:
Ich benutze z.Z. VS 2019 und bei einem .NET 5-Projekt

Benutze ich auch aber mit Framework 4.8.

Das mit dem User Control muss ich mir in Ruhe anschauen.
Das mit der Schnittstelle habe ich noch nicht begriffen.
Und was auch ein Problem ist das ich nicht weiß ob ich die ganzen Arrays noch brauche für ein User Control.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
public partial class MainForm : Form
    {
        private const string DEFAULTTARGETPATH = @"D:\S7_Export\AppData";
        private S7Client s7Client;
        private ComboBox[] cboLocalApps;
        private TextBox[] tbSourceFilePathLocalApps;
        private CheckBox[] cbActivateLocalApps;
        private CheckBox[] cbLocalSubFolderUses;


ausblenden C#-Quelltext
1:
2:
 cboLocalApps = new ComboBox[] { cboLocalApp1, cboLocalApp2, cboLocalApp3, cboLocalApp4, cboLocalApp5, cboLocalApp6, cboLocalApp7, cboLocalApp8 };
            tbSourceFilePathLocalApps = new TextBox[] { txtSourceFilePathLocalApp1, txtSourceFilePathLocalApp2, txtSourceFilePathLocalApp3, txtSourceFilePathLocalApp4, txtSourceFilePathLocalApp5, txtSourceFilePathLocalApp6, txtSourceFilePathLocalApp7, txtSourceFilePathLocalApp8 };


Ich denke nicht, weil ja die einzelnen Steuerelemente alle wegfallen. Das alles ist sehr verwirrend.

Ich habe da ein Youtube Video über User Control gefunden, mal sehen ob mir das hilft.

Zitat:
Und bei deiner "Settings.cs": vergleiche mal den Code

Unabhängig vom User Control eine Frage zu meinem aktuellen Projekt.
Ist es möglich das in meiner Settings.cs doppelter Code ist der nicht sein muss?

Z.B.
Zitat:
GetLocalControlTexts und GetNetControlTexts

Der Inhalt dieser Methoden ist identisch. Das heißt ich könnte eine Methode weglöschen
und die andere umbenennen in GetControlTexts. Und mit den anderen Methoden auch oder?

ausblenden C#-Quelltext
1:
2:
3:
4:
 private void MainForm_Load(object sender, EventArgs e)
{
     GetLocalPathConfigSettings();
     GetNetPathConfigSettings();


ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
public void GetLocalPathConfigSettings()
{
    var settings = Settings.Default;

    txtChooseTargetPath.Text = settings.TargetPath;

###################################################################
    settings.GetControlTexts(cboLocalApps, "LocalApp");
###################################################################

    settings.GetControlTexts(tbSourceFilePathLocalApps, "LocalSourceFilePath");
    settings.GetCheckBoxes(cbActivateLocalApps, "LocalAppAktiv");
    settings.GetCheckBoxes(cbLocalSubFolderUses, "LocalSubFolderUse");
    settings.GetCheckBoxes(cbActivateCode2LocalApps, "ActivateCode2LocalApp");

    txtLocalApplication.Text = settings.LocalApplication;
        }


Vielen Dank für deine Hilfe.

Grüße Tom
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 16.06.22 20:49 
Statt der Einzel-Arrays brauchst du jetzt nur noch ein Array für die UserControls.
Am besten sogar die UserControls dynamisch per Code erzeugen, aber du kannst auch, wie bisher, die UserControls direkt per Designer auf der Form platzieren und dann das Array daraus erzeugen.

Und nun benötigst du ja passenden Zugriff, um Daten zwischen der Form und dem UserControl auszutauschen - und dafür brauchst du eben Eigenschaften und/oder Methoden (und dies wird einfach als Schnittstelle bezeichnet).

Und bei den Settings meinte ich genau das von dir Beschriebene. :zustimm:
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: So 19.06.22 23:21 
Servus Th69,

Vielen Dank für deine Antwort.

Also das Übungsprojekt hat jetzt eine Form1 erstmal mit 2 UserControls (8 ist maximal)
Und für den Anfang eine Multiline Textbox. (Siehe Foto)
Die Daten aus der Textbox sollen in allen Combobox vom UserControl auswählbar sein.

Du hast gesagt:

Zitat:
Statt der Einzel-Arrays brauchst du jetzt nur noch ein Array für die UserControls.

Meinst du so:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
namespace WindowsFormsApp11
{
    public partial class Form1 : Form
    {
        private UserControl[] userControls;

        public Form1()
        {
            InitializeComponent();

            userControls = new UserControl[] { userControl1, userControl2 };
        }

       
}
}


Das ist der Code aus meinem eigentlichen Projekt um die Comboboxen von der Multiline Texbox zu füllen:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
const int MAXLOCALAPPS = 8;
            for (int n = 0; n < MAXLOCALAPPS; n++)
            {
                cboLocalApps[n].Items.AddRange(txtLocalApplication.Lines);
            }


Kommt der Code in die UserControl.cs:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
namespace WindowsFormsApp11
{
    public partial class UserControl : System.Windows.Forms.UserControl
    {
        public UserControl()
        {
            InitializeComponent();
        }

        private void UserControl_Load(object sender, EventArgs e)
        {
####################################
Hier an diese Stelle
####################################
        }
    }
}


Zu viele Fragen, weil ich die Zusammenhänge nicht richtig begreife.
Vieles davon ist geraten und das bringt mir nichts.

Das gleiche Thema mit der Textbox die im UserControl ist, die bekommt die Daten vom folderBrowserDialog.

Kannst du mir bitte an Hand der Combobox und der Multiline Textbox zeigen wie das funktioniert.

Vielen Dank nochmal für alles.

Grüße Tom
Einloggen, um Attachments anzusehen!
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mo 20.06.22 07:13 
Ja, so meinte ich das Array für die UserControls.

Und schreibe dir eine Methode (oder Eigenschaft) in der UserControl-Klasse, um die ComboBox zu füllen:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
public FillValues(string[] lines) // oder noch eleganter: IEnumerable<string> (so daß es für alle möglichen String-Collections funktioniert)
{
  // evtl. noch: comboBox.Items.Clear();
  comboBox.Items.AddRange(lines);
}

Und rufe sie dann von der Hauptform mittels userControl.FillValue(textBox.Lines) auf (bzw. in einer Schleife über alle UserControls).

Und den FolderBrowserDialog kannst du auch auf das UserControl packen und dort direkt die Methode zum Aufruf und Setzen der Pfade übernehmen.
Wenn du jedoch nur einen FolderBrowserDialog für die gesamte Anwendung haben möchtest (damit du nur einmalig die Eigenschaften dafür setzen mußt und diese dann zur Laufzeit immer gleich sind), dann übergebe nur den Pfad an das UserControl.
Wie in meinem Artikel sehen dann die Eigenschaften so aus:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
public string Description
{
    get { return labelDescription.Text; }
    set { labelDescription.Text = value; }
}

(entsprechend Name, Control und Control-Eigenschaft anpassen).
Ist zwar ein bißchen mehr Source-Code als direkt die Controls zu benutzen, aber du hast eine klare Schnittstelle erzeugt.
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: Di 21.06.22 17:55 
Hallo Th69,

Vielen Dank für deine Antwort.
Da bin ich ja froh das ich wenigstens etwas richtig gemacht habe.

Du sagtest mal das der Code schon ganz ordentlich sei.

Wenn ich dieses Tool über die exe-Datei starte und auf dem PC mit der echten SPS verbinde dann funktioniert das Tool trotz Backgroundworker doch noch nicht.

Hier zu Hause nutze ich einen Simulator und der ist natürlich min. 50 mal schneller als die echte SPS.
Mit dem SPS Simulator funktioniert die Software so wie sie soll.

In Verbindung mit der echten SPS passiert nach dem Trigger Signal gar nichts.
Nach 1-2 Minuten taucht im Label "lblAllFilesCopied" 0% auf.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
private void BW_CopyAllFiles_ProgressChanged(object sender, ProgressChangedEventArgs e)
{            
   progressBar1.Value = e.ProgressPercentage;
   lblAllFilesCopied.Text = "Processing......" + progressBar1.Value.ToString() + "%";
}


Ich habe in einem SPS Forum mal nachgefragt woran das liegen könnte.
Es scheint an der S7 400 CPU zu liegen, das die Leseanfragen vom Tool während des kopieren in der Netzwerkkommunikation, auch mitten im OB1 Zyklus eingebunden werden.

Das wurde bedeuten das das lesen vom: S7VariablenResult s7VariablenResult = ReadResult(); zu lange dauert. Vielleicht wegen der Progress Bar Schleife?

Das heißt der Backgroundworker verhindert nur das blockieren vom Tool.
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
        private void BW_CopyAllFiles_DoWork(object sender, DoWorkEventArgs e)
        {
            var bw = sender as BackgroundWorker;

            S7VariablenResult s7VariablenResult = ReadResult();

            //Ordner erstellen
            string createTargetPath = Path.Combine(DEFAULTTARGETPATH, s7VariablenResult.FolderName);
            Directory.CreateDirectory(createTargetPath);

            for (int l = 0; l < 100; l++)
            {
                if (bw.CancellationPending)
                {
                    // Set the e.Cancel flag so that the WorkerCompleted event
                    // knows that the process was cancelled.
                    e.Cancel = true;
                    bw.ReportProgress(0);
                    return;
                }
                else
                {
                    for (int i = 0; i < cbActivateLocalApps.Length; i++)
                    {
                        if (cbActivateLocalApps[i].Checked == true)
                        {
                            string praegeCodeNrFolderName = s7VariablenResult.FolderName;
                            string praegeCodeNrFileName = s7VariablenResult.PraegeCodeNr;
                            string sourceFilePath = uivalues.SourceFilePathLocalApps[i];
                            string targetPath = DEFAULTTARGETPATH;

                            MoveFilesfromLocal(praegeCodeNrFolderName, praegeCodeNrFileName, sourceFilePath, targetPath);

Was könnte man tun um das zu optimieren?
Gibt es hier vielleicht jemand der schon mal mit C# und SPS gearbeitet hat und kennt das Problem?

Grüße Tom

Moderiert von user profile iconTh69: Beitragsformatierung überarbeitet.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Di 21.06.22 18:44 
Ja, die aktuelle Version deines Codes halte ich für gut.

Du meinst, der s7Client.DBRead-Aufruf dauert 1-2 Minuten?
Wenn es keine asynchrone Lesemethode gibt, dann kannst du nur die Anzeige verbessern, d.h. z.B. vor dem ReadResult-Aufruf
ausblenden C#-Quelltext
1:
bw.ReportProgress(-1);					

aufrufen und in der zugehörigen Ereignismethode dann bei 0 einen anderen Text anzeigen ("Verbindung zur SPS wird hergestellt" bzw. "Lese Daten von der SPS" o.ä.).

Solange du keinen Aufruf von ReadResult() außerhalb des BackgroundWorkers hast, sollte die UI dann auch nicht blockieren.

Edit: Falls (laut Doku) kein negativer Wert (sondern nur 0-100 bzw. im deutschen steht 1-100) erlaubt sind, dann verwende 0 als Parameter dafür. Oder du zeigst den Label.Text vor dem Aufruf des BackgroundWorkers an und erst beim Kopieren wird mit ReportProgress(...) der Kopierfortschritt angezeigt.

PS: Wie groß ist denn DBSize beim s7Client.DBRead-Aufruf?


Zuletzt bearbeitet von Th69 am Mi 22.06.22 07:45, insgesamt 1-mal bearbeitet
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: Di 21.06.22 21:22 
Servus,

Zitat:
Du meinst, der s7Client.DBRead-Aufruf dauert 1-2 Minuten?


Ich meine:
Mit dem SPS Simulator ist, wenn das Trigger Signal kommt, innerhalb von 5s alles kopiert
und die GUI hat auch alle angezeigt.

Mit der echten CPU Verbindung ist die GUI erstmal nicht das Problem.

Meine Vermutung ist das = ReadResult() zu lange brauch von der SPS die Variablen zu lesen.


ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
 private void BW_CopyAllFiles_DoWork(object sender, DoWorkEventArgs e)
{
     var bw = sender as BackgroundWorker;
###############################################################
     S7VariablenResult s7VariablenResult = ReadResult();
##################################################################
     //Ordner erstellen
     string createTargetPath = Path.Combine(DEFAULTTARGETPATH, s7VariablenResult.FolderName);
     Directory.CreateDirectory(createTargetPath);


Bei meinem ersten Versuch mit der echten SPS kam es noch nicht mal dazu das ein Ordner erstellt wurde.
Dann irgendwann 1-2 Minuten später taucht die 0% im Label auf. Da das ja Nutzlos ist, habe ich abgebrochen.
Ich habe eine Vermutung. ReadResult liest den ganzen Datenbaustein ein. Buffersize ist 64.
Das ist auch die Größe von dem DB. Über Return s7VariablenResult werden alle Werte übergeben.
Ich denke das das viel zu lange dauert. Außerdem sind auch viele andere Werte mit drin die
hierfür nicht gebraucht werden. Ich muss den DB umschreiben passend nur für dieses Tool.
Außerdem wird dieses "new S7VariablenResult()" mit dran schuld sein.
Immer ein neues Objekt ist bestimmt nicht hilfreich. Hier kommt deine SPS.cs vielleicht in Frage die ich aber nicht integriert bekomme.
Mir würde es reichen das die ReadResult so umprogrammiert wird das wir auf das new verzichten können und auch den
return nicht mehr brauchen. Wenn es möglich ist ohne SPS.cs. Aber nur falls du auch der Meinung bist das
das sinnvoll ist und das auslesen der Variablen verschnellert.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
 private S7VariablenResult ReadResult()
{
     S7VariablenResult s7VariablenResult = new S7VariablenResult();
     // Reads the buffer.

     byte[] dbbuffersize = new byte[uivalues.DBSize];

     s7Client.DBRead(uivalues.DBNr, uivalues.DBOffset, uivalues.DBSize, dbbuffersize);

     s7VariablenResult.PraegeCodeNr = S7.GetStringAt(dbbuffersize, uivalues.PraegeCodeDBPos);
     s7VariablenResult.FolderName = S7.GetStringAt(dbbuffersize, uivalues.FolderNameDBPos);
     s7VariablenResult.PraegeCode2Nr = S7.GetStringAt(dbbuffersize, uivalues.PraegeCode2DBPos);
     s7VariablenResult.TrigSaveFiles = S7.GetBitAt(dbbuffersize, uivalues.TrigSaveFilesByte, uivalues.TrigSaveFilesBit);
     s7VariablenResult.TypLeft = S7.GetBitAt(dbbuffersize, uivalues.TypLeftByte, uivalues.TypLeftBit);
     s7VariablenResult.TypRight = S7.GetBitAt(dbbuffersize, uivalues.TypRightByte, uivalues.TypRightBit);
     s7VariablenResult.RobotIsEmpty = S7.GetBitAt(dbbuffersize, uivalues.RobotIsEmptyByte, uivalues.RobotIsEmptyBit);
     s7VariablenResult.FilesAreCopied = S7.GetBitAt(dbbuffersize, uivalues.FilesAreCopiedByte, uivalues.FilesAreCopiedBit);
     s7VariablenResult.ConnectedToPLC = S7.GetBitAt(dbbuffersize, uivalues.ConnectedToPLCByte, uivalues.ConnectedToPLCBit);

     return s7VariablenResult;
}


Ich hoffe du verstehst das alles was ich da geschrieben habe.
Ich werde mal den Datenbaustein überdenken.

Vielen Dank für dein Hilfe.

Grüße Tom
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mi 22.06.22 08:24 
UserTom hat folgendes geschrieben:
Meine Vermutung ist das = ReadResult() zu lange brauch von der SPS die Variablen zu lesen.

Und die einzige Methode dadrin, die das verursachen kann ist der s7Client.DBRead-Aufruf (alles andere ist erstmal zweitrangig).
Trotzdem sind 1-2 Minuten für 64 Bytes viel zu lang, da muß noch etwas anderes daran Schuld sein (falsche Verbindung o.ä.).

Hast du denn ein anderes Testprogramm? Ansonsten schau mal in Snap7, dort gibt es laut Demo Images ein C#-Testprogramm, das im Download Sharp7 enthalten sein sollte (bzw. die zu kompilierende Solution). Dort dann sharp7-full-1.0.0.7z auswählen, nicht den grünen Download-Button (der nur eine VB6 Datei herunterlädt).

PS: Du solltest die Variable dbbuffersize in dbbuffer umbenennen.
UserTom Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 49



BeitragVerfasst: Mi 22.06.22 09:58 
Hallo Th69,

Vielen Dank für deine Antwort.

Zitat:
Und die einzige Methode dadrin, die das verursachen kann ist der s7Client.DBRead-Aufruf

Ja und das wiederrum steht alles in der Schnittstellen Datei Sharp7.cs
Irgendwo sitzt der Fehler nur wo genau.

Zitat:
Dort dann sharp7-full-1.0.0.7z auswählen

Das kenne ich schon. Da ist mir was aufgefallen, dass der Wert für Buffer sehr hoch gesetzt wird.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
namespace CSClient
{

    public partial class MainForm : Form
    {
        private S7Client Client;
        private byte[] Buffer = new byte[65536];


Ich werde das mal mit dem hohen Wert ausprobieren.
Im Bild DBRead sieht man die Werte vom DBRead im Simulator gibt es keine Probleme wenn der Wert so hoch ist.
Im angehängten Bild "Buffer_Beispiel" sieht man aber, dass der Buffer die DB-Größe ist.
Im Bild Buffer_Beispiel gibt es einen Hinweis. snap7.sourceforge.net/sharp7.html

Zitat:
Every time a component is tested this struct is filled by the PLC and we want to read it into a .NET struct, picking the fields and adjusting their byte-order (the PLC is Big-Endian, the PC is Little-Endian, so the bytes must be reversed).

Weißt du, wo ich die Bytes vertauschen müsste, wenn überhaupt?

Zitat:
PS: Du solltest die Variable dbbuffersize in dbbuffer umbenennen.

Habe ich.

Vielen Dank für deine Hilfe.

Grüße Tom
Einloggen, um Attachments anzusehen!