Entwickler-Ecke
WinForms - Wert(e) grafisch darstellen
Stefan99 - Mi 06.04.11 09:53
Titel: Wert(e) grafisch darstellen
Hallo,
ich möchte Frequenzwerte eines Sensors darstellen.
Die Werte kann ich über eine dll vom Sensor auslesen (als float).
Ich habe mir das so vorgestellt, dass ich mir Wertepaare generiere (Frequenzwert, Zeitstempel) und diese dann wie
ein Oszibild in Echtzeit darstelle...
geht das?
wenn ja, wie? :-)
ich weiß nicht, wie ich anfangen soll...
Dank Euch
Stefan99 - Mi 06.04.11 13:13
dein 'nur' ist zutreffend ;-)
Dr. Hallo - Mi 06.04.11 15:00
na, zeig doch mal her was du bisher so codiert hast um die wertepaare auszulesen...
Stefan99 - Do 07.04.11 07:12
so - bin schon ein schönes Stück weitergekommen.
Hab das jetzt so gemacht, dass ab 20 Werten beim Hinzufügen eines Neuen, der erste gelöscht wird - dann sieht das irgendwie etwas komisch aus, weil der Graph nicht "im Ursprung" anfängt, sonder eine Lücke entsteht (s. Screenshot).
Ein derzeitiges Problem ist, dass der Graph ziemlich doll flackert.
Wie kann ich die y-Achse "fest" machen? Damit immer 0..1000Hz angezeigt werden?
Hier mein Code:
Anmerkung: die Device_Dll stellt die serielle Kommunikation mit meinem Gerät dar...
mit der Funktion ReadCurrentAndFourDynamicVariables_C() erhalte ich Werte zurück, der erste Wert als float ist meine Frequenz. den Wert "time" ist bisher lediglich ein Zähler...
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: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126:
| using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; using System.IO.Ports; using System.Threading; using ZedGraph;
namespace Heart_tool { public partial class Form1 : Form { public Form1() {
InitializeComponent();
button4.Enabled = false;
comboBox1.Items.Clear(); foreach (string s in System.IO.Ports.SerialPort.GetPortNames()) { comboBox1.Items.Add(s); if (s == "COM1") comboBox1.Text = s; else comboBox1.SelectedIndex = 0; } }
Device_Dll myDevice = new Device_Dll(); bool bRunRead = false;
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { Device_Dll.ComPort = comboBox1.SelectedIndex; }
private void textBox11_TextChanged(object sender, EventArgs e) { Device_Dll.ShortAddress = Convert.ToByte(textBox11.Text); }
private void button1_Click(object sender, EventArgs e) {
int[] returnValues = new int[10]; myDevice.ReadVersion_C(Device_Dll.ComPort, Device_Dll.ShortAddress, returnValues);
textBox1.Text = returnValues.ToString<int>("\r\n"); }
private void button2_Click(object sender, EventArgs e) { float[] returnValues = new float[4]; myDevice.ReadCurrentAndFourDynamicVariables_C(Device_Dll.ComPort, Device_Dll.ShortAddress, returnValues); textBox1.Text = returnValues.ToString<float>("\r\n"); }
private void button3_Click(object sender, EventArgs e) { button4.Enabled = true; bRunRead = true; float[] returnValues = new float[4]; double newTime = 0; double newValue = 0; while (bRunRead) { myDevice.ReadCurrentAndFourDynamicVariables_C(Device_Dll.ComPort, Device_Dll.ShortAddress, returnValues); Thread.Sleep(250); newValue = (double)returnValues[0]; textBox2.Text = returnValues[0].ToString(); textBox2.Update(); CreateGraph(zedGraphControl1, newTime++, newValue); SetSize();
Application.DoEvents(); } }
private void button4_Click(object sender, EventArgs e) { bRunRead = false; button4.Enabled = false; }
private void Form1_Load(object sender, EventArgs e) { CreateGraph(zedGraphControl1,0,0); SetSize(); }
private void Form1_Resize(object sender, EventArgs e) { SetSize(); this.Invalidate(); }
private void SetSize() { zedGraphControl1.Location = new Point(10, 10); zedGraphControl1.Size = new Size(650, 250); }
static PointPairList list1 = new PointPairList();
private void CreateGraph(ZedGraphControl zgc, double time, double value) { GraphPane myPane = zgc.GraphPane; myPane.Title.Text = "Frequency"; myPane.XAxis.Title.Text = "time [ms]"; myPane.YAxis.Title.Text = "Frequency [Hz]"; list1.Add(time, value); if (list1.Count > 20) list1.RemoveAt(0);
LineItem myCurve = myPane.AddCurve("", list1, Color.Red, SymbolType.None); zgc.IsAntiAlias = true;
zgc.AxisChange(); zgc.Invalidate(); } } } |
norman2306 - Do 07.04.11 08:48
Ich habe auch mal mit ZGraph gearbeitet. Persönlich ist das nicht meine erste Wahl. Ich empfehle das .NET eigene Chart. Das findest du unter System.Windows.Forms.DataVisualization.Charting.Chart in der System.Windows.Forms.DataVisualization-DLL. Alternativ empfehle ich WPF. Dort funktioniert das Data-Binding besser und das Ganze ist noch animiert.
Aber erstmal zur Forms Variante:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| public Form1() { . . .
chart1.AntiAliasing = AntiAliasingStyles.All; chart1.ChartAreas.Add("MyCurve"); chart1.ChartAreas["MyCurve"].AxisX.Title = "t [s]"; chart1.ChartAreas["MyCurve"].AxisY.Title = "[bps]"; } |
Danach musst du noch eine Datenquelle haben. Würde als Klassenvariable zwei Felder definieren:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| class MyClass { BindingList<double> x = new BindingList<double>(); BindingList<double> y = new BindingList<double>(); . . . } |
Diese weist du mittels Daten-Bindung deinem Chart zu:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
| public Form1() { . . .
var serie = new Series(); serie.ChartType = SeriesChartType.Spline; serie.Color = Color.Blue; serie.Points.DataBindXY(x, y); chart1.Series.Add(serie);
. . . } |
Die Achsen-Steuerung kannst du über das über chart1.ChartAreas["MyCurve"].AxisX bzw. chart1.ChartAreas["MyCurve"].AxisY anstellen.
Bsp:
C#-Quelltext
1: 2: 3:
| chart1.ChartAreas["MyCurve"].AxisY.Maximum = 1000; chart1.ChartAreas["MyCurve"].AxisY.Interval = 100; chart1.ChartAreas["MyCurve"].AxisY.IsLogarithmic = true; |
in deiner Schleife machst du dann
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| while (bRunRead) { Thread.Sleep(250); myDevice.ReadCurrentAndFourDynamicVariables_C(Device_Dll.ComPort, Device_Dll.ShortAddress, returnValues); x.Add(0.25); y.Add((double)returnValues[0]); } |
Ich würde dir im übrigen empfehlen, statt einer while-Schleife einen Timer zu nehmen oder einen Thread aufzumachen.
Hier mal die Thread-Variante:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
| private void button3_Click(object sender, EventArgs e) { Thread DoWork = new Thread(Worker); DoWork.Start(); }
void Worker() { DateTime Start = DateTime.Now; while (bRunRead) { myDevice.ReadCurrentAndFourDynamicVariables_C(Device_Dll.ComPort, Device_Dll.ShortAddress, returnValues); x.Add((DateTime.Now - Start).TotalSeconds); y.Add((double)returnValues[0]);
if(x.Count > 20) x.RemoveAt(0); if(y.Count > 20) y.RemoveAt(0);
Thread.Sleep(250); } } |
Stefan99 - Do 07.04.11 11:43
Danke dir!
Muss ich mir mal genauer anschauen... das mit dem Thread statt der while Schleife ist übrigens supi - jetzt flackert au nix mehr :-)
Bezügl. WPF - muss ich dann eine WPF-Applikation erstellen?
oO wieder was anderes - jetzt hab ich erst mit C# angefangen ;-)
norman2306 - Do 07.04.11 12:12
Jop, da brauchst du eine WPF-Application. Aber wenn du gerade erst mit C#-Sharp angefangen hast, dann bleib lieber erstmal bei WinForms. Später lohnt sich aber der Einstieg in WPF auf jeden Fall.
Dr. Hallo - Do 07.04.11 12:41
hier noch ein bsp einer echtzeitkurve mit einer völlig anderen componente...
Dr. Hallo - Do 07.04.11 12:44
aber ich die von norman beschriebene methode ist schon die beste und sicherste...
Stefan99 - Do 07.04.11 12:49
Dr Hallo,
ich seh kein weiteres Beispiel :-)
Dr. Hallo - Do 07.04.11 13:05
ich hab das als projekt hochgeladen, wenn du willst kannst dus dir runterladen und ansehen.
(müsste eigendlich gehen!!) ;-) oder auch nicht :-( bin etwas wirr heute
vg
Stefan99 - Mo 11.04.11 14:01
so jetzt läuft das Dingens mit der Chart-Funktion - sieht schonmal sehr viel besser aus :-)
jetzt hab ich hier aber noch etwas störendes während der Darstellung und zwar:
jedesmal, wenn ein neuer X-Achsenabschnitt (derzeit alle 2sec) erzeugt wird, sprich ein vertikaler Einheitenstrich, bleibt das
Chart ganz kurz stehen macht dann Sprunghaft weiter - verloren gehen keine Daten, sieht nur etwas unschön aus.
weiß jemand, was ich da noch ändern/ hinzufügen könnte?
Danke
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| while(FillChart){ newTime = (DateTime.Now - Start).TotalSeconds; Random rdm = new Random(); newValue = Convert.ToDouble( rdm.Next(600) ); AddPoint( Math.Round(newTime,2), newValue); } . . private void AddPoint(double Time, double Value) { chart1.Series["Series1"].Points.AddXY(Time, Value); } |
norman2306 - Mo 11.04.11 14:35
Läuft in einem Thrad oder? Füge mal ein Thread.Sleep(60) in deine while-Schleife ein.
Stefan99 - Mo 11.04.11 14:44
nein das ging nicht mehr - sonst meckert der rum, dass er einen Aufruf in keinem externen Thread machen kann oder so...
ein Thread.Sleep bringt auch keine Besserung... Vielleicht liegts am Debuggen oder mein Rechner ist zu langsam *ggg*
hier (s. Anhang) nochmals kurz die Symptome erklärt :-)
also das Chart bleibt kurz (wenn neuer vertikaler Strich gezeichnet wird) stehen, und das Chart baut sich dann nach-und-nach wieder zur "vollen Größe" auf...
norman2306 - Mo 11.04.11 15:08
Das stimmt. Dann musst du mit Invoke arbeiten:
Thread:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| while(FillChart) { newTime = (DateTime.Now - Start).TotalSeconds; Random rdm = new Random(); newValue = Convert.ToDouble( rdm.Next(600) ); this.Invoke((Action<double, double>)AddPoint, new object[2] { Math.Round(newTime,2), newValue }); Thread.Sleep(60); } |
Stefan99 - Di 12.04.11 07:01
Wow - geil - DANKE!
Im Moment fällt mir nur noch eine Kleinigkeit auf:
ich habe einen <Beenden-Button>, dieser reagiert aber erst beim 2. Drücken.
Sieht so aus, als würde das Form erst beim ersten klick aktiviert und beim 2. dann erst
der Button bestätigt.
Sicher kannst du mir dabei auch helfen :-)
Merci
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!