Averaging serial data stream values - c#

I have a stream of data at approx. 500 Hz coming from serial port. The data are sent by a microprocessor which is controlling a machine. The data are filtered out and shown in different text boxes by a class that strips out the header characters used by the transmission protocol.
This is the code that assigns the four variables I get to the different text boxes.
private void SetText(string text)
{
if (this.txtOutput.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.BeginInvoke(d, new object[] { text });
}
else
{
txtOutput.AppendText(text + "\r\n");
string a = "", b = "", c = "", d="";
string invia = text.ToString();
Stripper strp = new Stripper();
strp.Distri(invia, out a, out b, out c, out d);
if (a != "")
{
textBox7.Text = a; //currentRes
}
if (b != "")
{
textBox2.Text = b; //temperature
}
if (d != "" )
{
textBox3.Text = d; //motor current
}
if (c == "1\r") //motor RPM
{
timer3.Start();
}
}
}
The problem I am facing is that I get hundred of values "a", "b" and "d" per second and obviously the relevant text boxes are flickering. Moreover I would like to average the value for each variable taking 100 samples of each type before showing them in the relevant text box. This would avoid flickering and would give a more accurate reading.
How can I average each of the variable "a", "b" and "d" to x samples without freezing the application which meanwhile has to provide control for other features?
Samples code will be really appreciated.

average the value for each variable taking 100 samples ... would give a more accurate reading
So where does this number 100 come from, just from the GUI design or is it somehow related to the data?
And do you want to average independent (adjacent) blocks or do you need a moving average?
A simple approach:
class Accum
{
private int sum, count;
public event AverageChanged;
public void Add(int value)
{
sum += value;
count += 1;
if (count >= 100)
{
OnAvarerageChanged(sum/count);
sum = 0;
count = 0;
}
}
....
}
You will need to create 3 instances of this on your form. Since all SerialPort data will happen on the same thread you'll only need to synchronize when handling the event.
Update:
The easiest but less general approach:
class Accum
{
private int sum, count;
//public event AverageChanged;
public void Add(int value)
{
sum += value;
count += 1;
if (count >= 100)
{
OnAvarerageChanged(sum/count);
sum = 0;
count = 0;
}
}
public Label MyLabel { get; set; }
private void OnAvarerageChanged(int av)
{
SetTextCallback d = new SetTextCallback(SetText);
string text = av.ToString();
MyLabel.BeginInvoke(d, new object[] { text });
}
private void SetText(string text)
{
this.MyLabel.Text = text;
}
}

Related

Can I find wins and draws faster

I have a time sensitive application where I get the rank of each object and work out who won or tied for first. With my current algorithm I get some lag in my program so am hoping to speed it up. In this demo version I just get a random number for the ranks as the real rank calc requires too much code to post.
Can anybody suggest how I might do this more quickly as the Update method can get called tens of thousands of times for a single run?
class Program
{
static int runOuts = 0;
static Dictionary<RankedObject, int> dictionaryWins = new Dictionary<RankedObject, int>();
static Dictionary<RankedObject, int> dictionaryDraws = new Dictionary<RankedObject, int>();
static Random random = new Random();
static List<RankedObject> rankedObjects = new List<RankedObject>();
static void Main(string[] args)
{
// create some objects for demo
rankedObjects.Add(new RankedObject());
rankedObjects.Add(new RankedObject());
rankedObjects.Add(new RankedObject());
// add each object to the win/draw dictionaries so we can count wins/draws for each
foreach (RankedObject rankedObj in rankedObjects)
{
dictionaryWins.Add(rankedObj, 0);
dictionaryDraws.Add(rankedObj, 0);
}
// calculate wins/draws many times
for (int i = 0; i < 10000; i++)
{
Update();
}
// set equity results in each ranked combo and print results
foreach (RankedObject rankedCombo in rankedObjects)
{
rankedCombo.WinEquity = dictionaryWins[rankedCombo] / (double)runOuts;
rankedCombo.DrawEquity = dictionaryDraws[rankedCombo] / (double)runOuts;
Console.WriteLine(rankedCombo);
}
}
private static void Update()
{
// keep a list of ranks for each object so we can get max/winning value easily
List<int> ranks = new List<int>();
// get a rank for each object
foreach (RankedObject rankedCombo in rankedObjects)
{
int rank = random.Next(3);
ranks.Add(rank);
rankedCombo.Rank = rank;
}
// get the maximum rank and how many times it occurs, so we know if their is a tie for the win
int max = ranks.Max();
int maxOccurences = 0;
foreach (int i in ranks)
{
if (i == max)
{
maxOccurences++;
}
}
// loop over each object to record if the object won or tied for win
foreach (RankedObject rankedObj in rankedObjects)
{
if (rankedObj.Rank == max && maxOccurences == 1) // current rankedObj was winner
{
dictionaryWins[rankedObj] += 1;
}
else if (rankedObj.Rank == max && maxOccurences > 1) // current rankedObj Tied for win
{
dictionaryDraws[rankedObj] += 1;
}
}
runOuts++;
}
}
class RankedObject
{
int rank;
double winEquity;
double drawEquity;
public int Rank { get => rank; set => rank = value; }
public double WinEquity { get => winEquity; set => winEquity = value; }
public double DrawEquity { get => drawEquity; set => drawEquity = value; }
public override string ToString()
{
return "Win Equity: " + winEquity + ", Draw Equity: " + drawEquity;
}
}

How to recognize Event with onSensorChanged by comparing two float values

I want to read data from my Accelerometer Sensor and compare them if the first number of the float changes. I have some problem to unregister the listener or pause in order to compare and see if the average/Note has increased by a full Int for example "Note: 5.677" increases to "Note:6.234" then it supposed to be recognized as an event.
I have converted them into Ints so I can check if they are equal or not.
I already have tried to delay it with Thread.Sleep and pause but doesnt work.
Maybe because of Lock(_synlock) ?
public void OnSensorChanged(SensorEvent e)
{
lock (_syncLock)
{
avg = e.Values.Average();
_sensorTextView.Text = string.Format("x={0:f}, y={1:f}, z={2:f}", e.Values[0], e.Values[1], e.Values[2]);
_sensorTextView2.Text = string.Format("Note: {0}", avg);
note1 = e.Values.Average();
//Thread.Sleep(500);
//base.OnPause();
//_sensorManager.UnregisterListener(this);
//base.OnResume();
//_sensorManager.RegisterListener(this, _sensorManager.GetDefaultSensor(SensorType.Accelerometer), SensorDelay.Ui);
//_sensorTextView.Text = string.Format("x={0:f}, y={1:f}, z={2:f}", e.Values[0], e.Values[1], e.Values[2]);
//avg = e.Values.Average();
//_sensorTextView2.Text = string.Format("Note: {0}", avg);
//note2 = e.Values.Average();
}
int noteInt1 = Convert.ToInt32(note1);
int noteInt2 = Convert.ToInt32(note2);
//Thread.Sleep(2000); "zeige alle 2 sekunden an"
List<double> eventnumbers = new List<double> { };
if (noteInt1 != noteInt2)
{
avg = e.Values.Average();
//Console.WriteLine("bye");
_sensorTextView2.Text = string.Format("Note: {0}", avg);
//foreach(SensorEvent e){
eventnumbers.Add(new double()); //Value of avg is the value of the eventnumber element
eventnumbers.ForEach(Console.WriteLine);
}
}
UPDATE :
public void OnSensorChanged(SensorEvent e)
{
lock (_syncLock)
{
_sensorTextView.Text = string.Format("x={0:f}, y={1:f}, y={2:f}", e.Values[0], e.Values[1], e.Values[2]);
//_sensorTextView2.Text = string.Format("Note: {0}", e.Values.Average());
avg = e.Values.Average();
}
eventCounter();
}
public void eventCounter()
{
eventnumbers.Add(avg); //add number to our buffer
Console.WriteLine(avg);
eventnumbers.Add(avg+1); //adding a number different of our first one to see if the method works
Console.WriteLine(avg+1);
while (eventnumbers.Count >= 1) //as long as two elements included
{
for (int puffer = 0; puffer <= 10; puffer++) //buffer of 10 Elements
{
for (int i = 0; i < eventnumbers.Count; i++) //compare first number of our buffer
{
note1 = eventnumbers[i]; //remember value of first element
Console.WriteLine(note1);
Console.WriteLine("i ist:" + i);
for (int j = 1; j <= eventnumbers.Count; j++) //with the second number of our buffer
{
Console.WriteLine("j ist:" + j);
note2 = eventnumbers[j]; //remember value of second element
Console.WriteLine(note2);
}
}
while (eventnumbers.Count >= 1) //as long as two elements included
{
try
{
noteInt1 = Convert.ToInt32(note1); //double to Int
noteInt2 = Convert.ToInt32(note2);
if (noteInt1 != noteInt2) //we parse to int in order to compare if event has changed
{
_sensorTextView2.Text = string.Format("Note: {0}", avg); //if yes update our displaying mark/number
eventcounters.Add(avg); //add element to elementcounter
}
break; //
}
catch (System.ArgumentOutOfRangeException) //for the case if there are less than 2 elements to compare
{
eventcounters.ForEach(Console.WriteLine); //return all marks(numbers) from our eventcounter
}
break;
} break;
}
//throw new NotImplementedException();
}
eventnumbers.Clear();
}
My Problem is that I do not get another (different) value when I call e.Value.Average(). I need to do it outside the method and call onSensorChanged another time but i dont know how to do that since I need to create another event, right? thats why I have used e1 and e2 but I think I miss something with the initialization i guess.
This is how I have tried so far creating two events:
public class MainActivity : Activity, ISensorEventListener
{
static readonly object _syncLock = new Object();
SensorManager _sensorManager;
TextView _sensorTextView;
TextView _sensorTextView2;
double note1;
double note2;
double avg;
SensorEvent e1;
SensorEvent e2;
my one create():
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.activity_main);
_sensorManager = (SensorManager)GetSystemService(SensorService);
_sensorTextView = FindViewById<TextView>(Resource.Id.accelerometer_text);
_sensorTextView2 = FindViewById<TextView>(Resource.Id.accelerometer_note);
e1 = (SensorEvent)GetSystemService(ISensorEventListener);
e2 = (SensorEvent)GetSystemService(ISensorEventListener);
displayMark();
ISensorEventListener is marked a wrong btw
public void displayMark()
{
OnSensorChanged(e1);
note1 = e1.Values.Average();
OnSensorChanged(e2);
note2 = e2.Values.Average();
int noteInt1 = Convert.ToInt32(note1);
int noteInt2 = Convert.ToInt32(note2);
Console.WriteLine(noteInt1);
Console.WriteLine(noteInt2);
List<double> eventnumbers = new List<double> { };
if (noteInt1 != noteInt2)
{
avg = e2.Values.Average();
Console.WriteLine("bye");
_sensorTextView2.Text = string.Format("Note: {0}", avg);
//foreach(SensorEvent e){
eventnumbers.Add(new double()); //Value of avg is the value of the eventnumber element
}
eventnumbers.ForEach(Console.WriteLine);
I'm just going to deal with the general approach here. Rather than trying to wait in an event for some new data I would use the events to record a value and then compare the new one to the last one. So outside the function would be a variable something like:
private int previousValue;
Then when the function is called it compares the 'new' value to this previous one:
public void OnSensorChanged(SensorEvent e)
{
// Can't tell if this is required with information available
lock (_syncLock)
{
// Capture the 'new' data to a local variable
var newValue = e.Values.Average();
if (newValue != previousValue)
{
// Value has changed by required amount so do something here
}
// Update the previous so next we we use this as our reference value
previousValue = newValue;
}
This isn't a full solution but should be a better starting point for developing one.

Monitoring the FPS of a Direct X Application

I am looking to create an external application that monitors the 'FPS' of a DirectX application (like FRAPS without the recording). I have read several Microsoft articles on performance measuring tools - but I am looking to get the feedback (and experience) of the community.
My question: what is the best method for obtaining the FPS of a DirectX application?
Windows has some Event Tracing for Windows providers related to DirectX profiling. The most intresting ones are Microsoft-Windows-D3D9 and Microsoft-Windows-DXGI, which allow tracing of the frame presentation events. The simplest way to calculate FPS is to count the number of PresentStart events withing a time interval and divide that by the length of the interval.
To work with ETW in C#, install Microsoft.Diagnostics.Tracing.TraceEvent package.
The following code sample displays FPS of running processes:
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using Microsoft.Diagnostics.Tracing.Session;
namespace ConsoleApp1
{
//helper class to store frame timestamps
public class TimestampCollection
{
const int MAXNUM = 1000;
public string Name { get; set; }
List<long> timestamps = new List<long>(MAXNUM + 1);
object sync = new object();
//add value to the collection
public void Add(long timestamp)
{
lock (sync)
{
timestamps.Add(timestamp);
if (timestamps.Count > MAXNUM) timestamps.RemoveAt(0);
}
}
//get the number of timestamps withing interval
public int QueryCount(long from, long to)
{
int c = 0;
lock (sync)
{
foreach (var ts in timestamps)
{
if (ts >= from && ts <= to) c++;
}
}
return c;
}
}
class Program
{
//event codes (https://github.com/GameTechDev/PresentMon/blob/40ee99f437bc1061a27a2fc16a8993ee8ce4ebb5/PresentData/PresentMonTraceConsumer.cpp)
public const int EventID_D3D9PresentStart = 1;
public const int EventID_DxgiPresentStart = 42;
//ETW provider codes
public static readonly Guid DXGI_provider = Guid.Parse("{CA11C036-0102-4A2D-A6AD-F03CFED5D3C9}");
public static readonly Guid D3D9_provider = Guid.Parse("{783ACA0A-790E-4D7F-8451-AA850511C6B9}");
static TraceEventSession m_EtwSession;
static Dictionary<int, TimestampCollection> frames = new Dictionary<int, TimestampCollection>();
static Stopwatch watch = null;
static object sync = new object();
static void EtwThreadProc()
{
//start tracing
m_EtwSession.Source.Process();
}
static void OutputThreadProc()
{
//console output loop
while (true)
{
long t1, t2;
long dt = 2000;
Console.Clear();
Console.WriteLine(DateTime.Now.ToString() + "." + DateTime.Now.Millisecond.ToString());
Console.WriteLine();
lock (sync)
{
t2 = watch.ElapsedMilliseconds;
t1 = t2 - dt;
foreach (var x in frames.Values)
{
Console.Write(x.Name + ": ");
//get the number of frames
int count = x.QueryCount(t1, t2);
//calculate FPS
Console.WriteLine("{0} FPS", (double)count / dt * 1000.0);
}
}
Console.WriteLine();
Console.WriteLine("Press any key to stop tracing...");
Thread.Sleep(1000);
}
}
public static void Main(string[] argv)
{
//create ETW session and register providers
m_EtwSession = new TraceEventSession("mysess");
m_EtwSession.StopOnDispose = true;
m_EtwSession.EnableProvider("Microsoft-Windows-D3D9");
m_EtwSession.EnableProvider("Microsoft-Windows-DXGI");
//handle event
m_EtwSession.Source.AllEvents += data =>
{
//filter out frame presentation events
if (((int)data.ID == EventID_D3D9PresentStart && data.ProviderGuid == D3D9_provider) ||
((int)data.ID == EventID_DxgiPresentStart && data.ProviderGuid == DXGI_provider))
{
int pid = data.ProcessID;
long t;
lock (sync)
{
t = watch.ElapsedMilliseconds;
//if process is not yet in Dictionary, add it
if (!frames.ContainsKey(pid))
{
frames[pid] = new TimestampCollection();
string name = "";
var proc = Process.GetProcessById(pid);
if (proc != null)
{
using (proc)
{
name = proc.ProcessName;
}
}
else name = pid.ToString();
frames[pid].Name = name;
}
//store frame timestamp in collection
frames[pid].Add(t);
}
}
};
watch = new Stopwatch();
watch.Start();
Thread thETW = new Thread(EtwThreadProc);
thETW.IsBackground = true;
thETW.Start();
Thread thOutput = new Thread(OutputThreadProc);
thOutput.IsBackground = true;
thOutput.Start();
Console.ReadKey();
m_EtwSession.Dispose();
}
}
}
Based on the source code of PresentMon project.
Fraps inserts a DLL into every running application and hooks specific DX calls to figure out the framerate and capture video, pretty sure that you'll have to do something similar. After a bit of poking around I found a Github project that does some basic DX hooking for doing captures and overlays, so that might be a good spot to start out with. Though I've not used it personally so I can't totally vouch for the quality.
http://spazzarama.com/2011/03/14/c-screen-capture-and-overlays-for-direct3d-9-10-and-11-using-api-hooks/
Building on https://stackoverflow.com/a/54625953/12047161:
I had more success not using the stopwatch as the event triggers seems to be asynchronous with the actual frames. I kept getting batches of 20-50 frames all at once, making the estimated FPS fluctuate between 50 and 250% of the actual value.
Instead i used TimeStampRelativeMSec
//handle event
m_EtwSession.Source.AllEvents += data =>
{
//filter out frame presentation events
if((int) data.ID == EventID_DxgiPresentStart && data.ProviderGuid == DXGI_provider)
{
int pid = data.ProcessID;
long t;
t = watch.ElapsedMilliseconds;
//if process is not yet in Dictionary, add it
if (!frames.ContainsKey(pid))
{
frames[pid] = new TimestampCollection();
string name = "";
var proc = Process.GetProcessById(pid);
if (proc != null)
{
using (proc)
{
name = proc.ProcessName;
}
}
else name = pid.ToString();
frames[pid].Name = name;
}
frames[pid].Add((long)data.TimeStampRelativeMSec);
}
};
property from the TraceEvent class, and calculate FPS by rounding the average time between an arbitrary number of past entries:
public double GetFrameTime(int count)
{
double returnValue = 0;
int listCount = timestamps.Count;
if(listCount > count)
{
for(int i = 1; i <= count; i++)
{
returnValue += timestamps[listCount - i] - timestamps[listCount - (i + 1)];
}
returnValue /= count;
}
return returnValue;
}
This method gave me far more accurate (Compared to, as available, in-game counters) of several different games i've tried.

String concatenate/shorten algorithm

I want to publish server-messages on Twitter, for our clients.
Unfortunately, Twitter only allows posting 140 Chars or less. This is a shame.
Now, I have to write an algorithm that concatenates the different messages from the server together, but shortens them to a max of 140 characters.
It's pretty tricky.
CODE
static string concatinateStringsWithLength(string[] strings, int length, string separator) {
// This is the maximum number of chars for the strings
// We have to subtract the separators
int maxLengthOfAllStrings = length - ((strings.Length - 1) * separator.Length);
// Here we save all shortenedStrings
string[] cutStrings = new string[strings.Length];
// This is the average length of all the strings
int averageStringLenght = maxLengthOfAllStrings / strings.Length;
// Now we check how many strings are longer than the average string
int longerStrings = 0;
foreach (string singleString in strings)
{
if (singleString.Length > averageStringLenght)
{
longerStrings++;
}
}
// If a string is smaller than the average string, we can more characters to the longer strings
int maxStringLength = averageStringLenght;
foreach (string singleString in strings)
{
if (averageStringLenght > singleString.Length)
{
maxStringLength += (int)((averageStringLenght - singleString.Length) * (1.0 / longerStrings));
}
}
// Finally we shorten the strings and save them to the array
int i = 0;
foreach (string singleString in strings)
{
string shortenedString = singleString;
if (singleString.Length > maxStringLength)
{
shortenedString = singleString.Remove(maxStringLength);
}
cutStrings[i] = shortenedString;
i++;
}
return String.Join(separator, cutStrings);
}
Problem with this
This algorithm works, but it's not very optimized.
It uses less characters than it actually could.
The main problem with this is that the variable longerStrings is relative to the maxStringLength, and backwards.
This means if I change longerStrings, maxStringLength gets changed, and so on and so on.
I'd have to make a while loop and do this until there are no changes, but I don't think that's necessary for such a simple case.
Can you give me a clue on how to continue?
Or maybe there already exists something similar?
Thanks!
EDIT
The messages I get from the server look like this:
Message
Subject
Date
Body
Message
Subject
Date
Body
And so on.
What I want is to concatenate the strings with a separator, in this case a semi-colon.
There should be a max length. The long strings should be shortened first.
Example
This is a subject
This is the body and is a bit lon...
25.02.2013
This is a s...
This is the...
25.02.2013
I think you get the idea ;)
Five times slower than yours (in our simple example) but should use maximum avaliable space (no critical values checking):
static string Concatenate(string[] strings, int maxLength, string separator)
{
var totalLength = strings.Sum(s => s.Length);
var requiredLength = totalLength - (strings.Length - 1)*separator.Length;
// Return if there is enough place.
if (requiredLength <= maxLength)
return String.Concat(strings.Take(strings.Length - 1).Select(s => s + separator).Concat(new[] {strings.Last()}));
// The problem...
var helpers = new ConcatenateInternal[strings.Length];
for (var i = 0; i < helpers.Length; i++)
helpers[i] = new ConcatenateInternal(strings[i].Length);
var avaliableLength = maxLength - (strings.Length - 1)*separator.Length;
var charsInserted = 0;
var currentIndex = 0;
while (charsInserted != avaliableLength)
{
for (var i = 0; i < strings.Length; i++)
{
if (charsInserted == avaliableLength)
break;
if (currentIndex >= strings[i].Length)
{
helpers[i].Finished = true;
continue;
}
helpers[i].StringBuilder.Append(strings[i][currentIndex]);
charsInserted++;
}
currentIndex++;
}
var unified = new StringBuilder(avaliableLength);
for (var i = 0; i < strings.Length; i++)
{
if (!helpers[i].Finished)
{
unified.Append(helpers[i].StringBuilder.ToString(0, helpers[i].StringBuilder.Length - 3));
unified.Append("...");
}
else
{
unified.Append(helpers[i].StringBuilder.ToString());
}
if (i < strings.Length - 1)
{
unified.Append(separator);
}
}
return unified.ToString();
}
And ConcatenateInternal:
class ConcatenateInternal
{
public StringBuilder StringBuilder { get; private set; }
public bool Finished { get; set; }
public ConcatenateInternal(int capacity)
{
StringBuilder = new StringBuilder(capacity);
}
}

word search puzzle game

I am trying to create a word search game. the problem is I am unable to insert words into a TableLayoutPanel. When I wrote this, I got a compile error that says "no overload for method 'placewords' takes '5' arguments.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Random r = new Random();
for (int a = 0; a < tableLayoutPanel1.ColumnCount; a++)
{
for (int b = 0; b < tableLayoutPanel1.RowCount; b++)
{
Label nl = new Label();
int x = r.Next(65, 90);
char c = (char)x;
nl.Text = c.ToString();
tableLayoutPanel1.Controls.Add(nl, a, b);
}
}
}
private void newGameToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Restart();
}
private void PlaceWords()
{
string[] words = { "byte", "char" };
Random rn = new Random();
foreach (string p in words)
{
String s = p.Trim();
bool placed = false;// continue trying to place the word in // the matrix until it fits
while (placed == false)// generate a new random row and column
{
int nRow = rn.Next(30);// generate a new random x & y direction vector
int nCol = rn.Next(30);// x direction: -1, 0, or 1
int nDirX = 0; // y direction -1, 0, or 1
int nDirY = 0; // (although direction can never be 0, 0, this is null)
while (nDirX == 0 && nDirY == 0)
{
nDirX = rn.Next(3) - 1;
nDirY = rn.Next(3) - 1;
}
placed =PlaceWords(s.ToUpper(),nRow,nCol,nDirX,nDirY);
}
}
}
Your PlaceWords method doesn't accept that many parameters, in fact, it accepts no parameters.
Further more, the way it looks, your PlaceWords is a recursive function that won't exit, leading to a stack overflow.
To fix this, you need to create a second PlaceWords function that accepts all 5 parameters, and does whatever PlaceWords does, and returns a boolean.
It looks like your nested for loops in Form1_Load should be placing random characters into tableLayoutPanel1. You then need to call PlaceWords() which will determine the location and direction to place each word in the words list. Near the end of PlaceWords you are calling PlaceWords(s.ToUpper(),nRow,nCol,nDirX,nDirY) which is supposed to actually put the word into tableLayoutPanel1. This second PlaceWords with 5 parameters should have a different name (I suggest PlaceString); it should not be trying to call the same Placewords method that it is in.
You need to then write the method PlaceString that will look like:
public bool PlaceString(string s, int nRow, int nCol, int nDirX, int nDirY)
{
/* whatever code you need to put the string into tableLayoutPanel1 */
}

Categories

Resources