I'm working on a program that functions as a carpark simulator. I want the user interface to display how many spaces are available on each level at any given time. I've attempted to do this using listboxes (one for level number, one for number of spaces on that level), but can't get it to work. For some reason, the listbox that displays the spaces available (listboxSA) always comes up blank and I have no idea why.
The code that creates the listboxes is shown below:
public void updateLevelLabels(Simulator simulator)
{
//constant integers used for label positioning
//y coordinate for first label
const int YSTARTPOINT = 12;
//x coordinate for all labels
const int XSTARTPOINT = 104;
//create new listbox to show level IDs
ListBox listboxLevels = new ListBox();
//position listbox on form
//constant x-coordinate
listboxLevels.Left = XSTARTPOINT;
//constant y-coordinate
listboxLevels.Top = YSTARTPOINT;
//auto-assumes size depending on content
listboxLevels.AutoSize = true;
//create new listbox to show spaces available
ListBox listboxSA = new ListBox();
//set x and y coordinates
//constant x coordinate
listboxSA.Left = XSTARTPOINT + 38;
//constant y coordinate
listboxSA.Top = YSTARTPOINT;
//auto-resizes depending on content
listboxSA.AutoSize = true;
//populate listboxes
for (int i = 0; i < Length(); i++)
{
//identify level at current index
Level lev = At(i);
//add level unique ID to list
listboxLevels.Items.Add(lev.getLevelID());
//add number of spaces (available) on level to list
listboxSA.Items.Add(lev.getNumSpaces().ToString());
}
//place listboxes on form
simulator.Controls.Add(listboxLevels);
simulator.Controls.Add(listboxSA);
}
I've debugged the code and the value for the lev.numSpaces variable is what I'd expect it to be. I've also tried to select the indices of the listboxSA after it's creation and the created indices are selectable (listbox item becomes highlighted), but there is still no text in them.
I honestly have no idea what could be causing this to happen, especially weird considering the same procedure is essentially carried out on both listboxes with a differe get() function.
If anyone can spot what might be throwing it off, I'd really appreciate any advice!
Code of called functions called shown below:
//from `Levels` class
//Levels acts as a public interface for a `List<Level>`
public int Length()
{
//return number of `Level` instances in collection (int)
return levelList.Count;
}
//from `Level` class
//obtain unique identifer of level
public string getLevelID()
{
//return unique Level name
return levelID;
}
//from `Level` class
//obtain number of spaces on level
//all spaces assumed to be free
public int getNumSpaces()
{
//should = value of Levels.Length()
return numSpaces;
}
Thanks in advance,
Mark
You should better check data which you are trying to apply to listbox or seek problem in other place. I made test which is doing the same as your code and there is no problem.
string[] stringArray = new string[] { "one", "two", "three", "four" };
int[] intArray = new int[] { 1, 2, 3, 4 };
private void Addlistboxes()
{
ListBox lb1 = new ListBox();
ListBox lb2 = new ListBox();
lb1.Left = 10;
lb1.Top = 60;
lb1.AutoSize = true;
lb2.Left = 15 + lb1.Width;
lb2.Top = 60;
lb2.AutoSize = true;
for (int i = 0; i < 4; i++)
{
lb1.Items.Add(stringArray[i]);
lb2.Items.Add(intArray[i]);
}
this.Controls.Add(lb1);
this.Controls.Add(lb2);
}`
Related
I am trying to serialize a 2D array of strings to send over a network.
I have a couple ideas but I seem to be blocked by api or efficiency. I really don't want to serialize and send a small 2D array across a network 10 times a second when I only need it when I collide with something and ask for the information.
I have a list of items a player has in his cart. I want to make it okay to steal from that cart, so the other player has to be able to see what's in the cart.
If I use onphotonserializeview I thought I could convert the flatten the 2d array of strings into a 1D array of strings with a delimiter and stream.sendnext the delimited line and reconstitute on the other side, or some variation of that functionality.
public void serialize_merchandise_list() {
int width = 0;
int length = 0;
while((width < cart_list_width) && (length < cart_list_width)) {
width++;
if (width >= cart_list_width) {
length++;
width = 0;
}
// convert to bytes and then to string and append
System.Text.ASCIIEncoding.ASCII.GetBytes(merchandise_items[width, length]));
}
}
But doing that unnecessarily is an operation time nightmare, I want it on demand.
I was going to do a punrpc but I can't get a return value or ref parameter. What is the best approach here?
I want:
collide with cart
do you want to steal from cart --> yes --> show list
else --> go on your way
Afaik a one-dimensional array of strings would be no problem for Photon.
So I guess you could just flatten the array and additionally send the second size - lets name it element size (in contrary to the first one - the element count) as parameters.
Like e.g. lets say your array looks like
string[,] array2D = new string[4,2]{{"a", "b"}, {"c","d"}, {"e","f"}, {"g","h"}};
Then you would know the second (elementSize) dimension using either hardcoded or GetLength(int)
var elementSize = array2D.GetLength(1);
and you would simply flatten the array using
string[] arrayFlat = array2D.Cast<string>().ToArray();
So you would send these two informations via photon (I don't know photon in detail) but afaik you can do this one time without having to send it continiously. The two parameters would be
int elementSize, string[] arrayFlat
in this case with the values
2, string[8]{"a","b","c","d","e","f","g","h"}
So together:
public void SendArray2D()
{
int elementSize = array2D.GetLength(1);
string[] arrayFlat = array2D.Cast<string>().ToArray();
photonView.RPC(nameof(ReceiveArray2D), RpcTarget.All, elementSize, (object) arrayFlat);
}
Then on the receiver part you are getting
int elementSize = 2, string[] arrayFlat = string[8]{"a","b","c","d","e","f","g","h"}
and have to convert it back. You already know the second dimension elementSize = 2 so in order to get also the first one you simply do
var elementCount = arrayFlat.Length / elementSize; // = 4
so you know the 2D array you will have to fill would be
string[,] array2D = new string[elementCount, elementSize]; // = 4,2
and then you can simply iterate it and do something like
for (var x = 0; x < elementCount; x++)
{
for (int y = 0; y < elementSize; y++)
{
array2D[x, y] = arrayFlat[x * elementSize + y];
}
}
So together
[PunRpc]
public void ReceiveArray2D(int elementSize, string[] arrayFlat)
{
var elementCount = arrayFlat.Length / elementSize;
array2D = new string[elementCount, elementSize];
for (var x = 0; x < elementCount; x++)
{
for (int y = 0; y < elementSize; y++)
{
array2D[x, y] = arrayFlat[x * elementSize + y];
}
}
// Could also e.g. call some event like
OnReceivedArray2D?.Invoke (array2D);
}
public event Action<string[,]> OnReceivedArray2D;
So you could attach listeners to the event via
reference.OnReceivedArray2D += SomeHandler;
private void SomeHandler(string [,])
{
// ...
}
Or alternatively you could implement a class that stores your data in a flat array but lets you access it like if it would be a 2D array (in general a 2D array is anyway stored in memory as a flat one)
[Serializable]
public class SerializableArray2D
{
public readonly string[] ArrayFlat;
public readonly ElementSize;
public SerializableArray2D(int elementSize, string[] arrayFlat)
{
ElementSize = elementSize;
ArrayFlat = arrayFlat;
}
public SerializableArray2D(string[,] array2D) : this(array2D.GetLength(1), array2D.Cast<string>().ToArray()) { }
public SerializableArray2D(int elementSize, int elementCount) : this(elementSize, new string[elementCount]){}
public string this[int x, int y]
{
get { return ArrayFlat[x * ElementSize + y]; }
set { ArrayFlat[x * ElementSize + y] = value; }
}
}
usage would be e.g. Initialize it
var array2D = new string[4,2]{{"a", "b"}, {"c","d"}, {"e","f"}, {"g","h"}};
var serializableArray2D = new SerializableArray2D(array2D);
For accessing specific indices like in a 2D array
string example = serializableArray2D[1,1];
serializableArray2D[3,0] = "example";
For sending it
photonView.RPC(nameof(Receive), RpcTarget.All, serializableArray2D.ElementSize, (object)serializableArray2D.ArrayFlat);
And when receiving it
[PunRPC]
public void Receive(int elementSize, string[] arrayFlat)
{
serializableArray2D = new SerializableArray2D(elementSize, arrayFlat);
// Again e.g. call an event
OnReceivedSerializableArray2D?.Invoke(serializableArray2D);
}
public event Action<SerializableArray2D> OnReceivedSerializableArray2D;
So, we have a way to dynamically create an array of x by y of texboxes to input values into it to later on do some matrix calculation, like determine the rank of the matrix.
To generate the array we use this:
protected void Form2_Load(object sender, EventArgs e)
{
for (int row = 0; row < LinhaText; row++)
{
List<TextBox> newLin = new List<TextBox>();
textboxes.Add(newLin);
for (int col = 0; col < ColunText; col++)
{
TextBox newbox = new TextBox();
newbox.Width = textboxWidth;
newbox.Height = textboxHeight;
newbox.Top = (row * (textboxHeight + spacing)) + spacing + vOffset;
newbox.Left = (col * (textboxWidth + spacing)) + spacing + hOffset;
newLin.Add(newbox);
this.Controls.Add(newbox);
}
}
}
This stands on the second form, as in the first one we input the dimensions of the array. When we input the values on the textboxes they should get sent to the third form to get used by the other calculations.
We are wondering how we grab the values and send them to the other form to later on display the results.
Any further information you need, just ask!
I'm assuming that these are not modal windows and that the user can switch between them at any point? If that is the case then I would suggest using a design pattern like MVC or MVVM, separating your data, view, and control logic. By keeping the data layer distinct from the view, you can have all of your windows (views) share the same underlying data. Of course, you'll want to use events to notify the various windows of when the data is changed (see the INotifyPropertyChanged Interface).
I would not pass around textboxes, instead store the numbers in a 2-dimensional array (matrix). Also I would use binding to automatically bind the matrix values to the textboxes. To make binding work, you need a helper class containing your values, because binding needs to bind to a property.
private class Data // Can be a nested private class in Form2.
{
public double Value { get; set; }
public override string ToString() => Value.ToString(); // Not strictly necessary, but
// makes debugging easier.
}
We create a _matrix field as well as a property converting this Data[,] matrix to a double[,] matrix and returning it (in Form2):
private Data[,] _matrix;
public double[,] Matrix
{
get {
var m = new double[LinhaText, ColunText];
for (int row = 0; row < LinhaText; row++) {
for (int col = 0; col < ColunText; col++) {
m[row, col] = _matrix[row, col].Value;
}
}
return m;
}
}
Now create the textboxes and bind them to a Data object (In Form2_Load or in the form constructor after InitializeComponent):
_matrix = new Data[LinhaText, ColunText];
for (int row = 0; row < LinhaText; row++) {
for (int col = 0; col < ColunText; col++) {
var newbox = new TextBox {
Width = textboxWidth,
Height = textboxHeight,
Top = (row * (textboxHeight + spacing)) + spacing + vOffset,
Left = (col * (textboxWidth + spacing)) + spacing + hOffset
};
// Create Data object, add it to the matrix and bind it to the TextBox.
var data = new Data();
_matrix[row, col] = data;
newbox.DataBindings.Add(new Binding("Text", data, "Value", true));
Controls.Add(newbox);
}
}
Note that binding works two way. If you initialize the matrix with numbers, they will be displayed in the textboxes when opening the form. Numbers entered in textboxes will be stored in the matrix.
Now, on the other form, you need a reference to this form. Then you can get the matrix with
double[,] matrix = frmMartixInput.Matrix;
If you have difficulties passing around form references, then use a static property in a static class for the matrix, that can be referenced by all the forms.
public static class Globals
{
public static double[,] Matrix { get; set; }
}
You can also pass data to a form through constructor parameters (either a reference to another form or the matrix itself).
I have an Issue with C# charting.
This is using System.Windows.Forms.DataVisualization.Charting
My series are configured to be indexed as such:
{
Name = name,
Color = color,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType =
System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastLine
};
I have 3 series in this chart
I am adding data to the series as it arrives via an Event Handler
for (int i = 0; i < samples; i++)
{
double time_point = DData[i * 4]
chart1.Series[0].Points[lastSample + i].XValue = time_point;
chart1.Series[0].Points[lastSample + i].YValues[0] = DData[i * 4 + 1];
chart1.Series[1].Points[lastSample + i].XValue = time_point;
chart1.Series[1].Points[lastSample + i].YValues[0] = DData[i * 4 + 2];
chart1.Series[2].Points[lastSample + i].XValue = time_point;
chart1.Series[2].Points[lastSample + i].YValues[0] = DData[i * 4 + 3];
}
lastSample = (lastSample + samples) % maxPlotPoints;
chart1.Invalidate();
The issue is as follows:
If I turn on each series individually (via chart1.Series[0].Enabled), they all work fine
If I turn on more than 1 series at a time, a big red X appears instead oft eh chart, and I have to restart the application to resume streaming charts. This either happens immediately or after a few seconds.
If I set time_point to some other number, like 0, this issue doesn't happen, and all 3 charts can be displayed simultaneously
Next, I understand that this happens when each series has a different X-value for the same Point[] location. But I am explicitly setting all 3 series to use the same exact time_point
My next assumption was that the event handler was executing the tread before the previous thread finishes.
So I added a lock around the graphing call, it did not help
private Object thisLock = new Object();
lock (thisLock)
{
}
MY questions are:
Does anyone know if there is another reason why this may be caused?
Is it at all possible to use just the X-indexes from the first series for the chart but to display all 3 series simultaneuously?
EDIT: This fix did not work, the problem disappeared, then came back after a while
The comment above by TaW helped me fix this issue.
This really seems to be a problem with Microsoft charts
I originally though it was due to EventHandler firing multiple times before it finishes, and I opted out to use a Queue that would be filled with an EventHandler and Dequeued on a separate thread.
The problem was still there.
The solution to the problem was to simply set IsXValueIndexed to false
then to set it to true before drawing the chart
Below is the code used
In case anyone is wondering what this does:
It graphs 3 Values simultaneously on the chart with increasing time.
The chart updates from left to right instead of scrolling (think EKG machine in a hospital)
There is a fixed total number of points that is set to maxPlotPoints
The EventHandler receives some Data packet (made up here) that contains a certain number of points which = samplePoints.
It constructs a DataPoints struct and adds it to a queue
A separate thread MainTask dequeues an element from DataPoints and uses it to populate a 100 point section of each of the three series. That section then increments to the next 100 points, and so on.
This is useful if you want to continuously chart a fixed number of points
Unfortunately, I can't seem to mark the answer above from TaW as correct because it's in a comment. But I would be happy to try if you can explain how to do this. Thanks
private struct DataPoint
{
public double timePoint;
public double X;
public double Y;
public double Z;
}
private struct DataPoints
{
public DataPoints(int samples)
{
dataPoints = new DataPoint[samples];
}
public DataPoint[] dataPoints;
}
Queue queue = new Queue();
int lastSample = 0;
static int maxPlotPoints = 1000;
static int samplePoints = 100;
public ChartForm(UdpMgrControl RefUdpMgr, byte GyroChartNum, string ConfigFile)
{
for ( Row = 0; Row < 3; Row++)
{
var series1 = new System.Windows.Forms.DataVisualization.Charting.Series
{
Name = name,
Color = Color.Blue,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.FastLine
};
//Fill in chart with blank points
for (int i = 0; i < maxPlotPoints; i++)
{
series1.Points.AddY(0);
}
chart1.Series.Add(series1);
}
chart1.Series[0].Enabled = true;
chart1.Series[1].Enabled = true;
chart1.Series[2].Enabled = true;
running = true;
Thread main_thread = new Thread(new ThreadStart(this.MainTask));
}
private void OutputHandler(byte[] DData)
{
if (running)
{
DataPoints data_element = new DataPoints(samplePoints);
for (int i = 0; i < samplePoints; i++)
{
data_element.dataPoints[i].X = DData[i].X;
data_element.dataPoints[i].Y = DData[i].Y;
data_element.dataPoints[i].Z = DData[i].Z;
data_element.dataPoints[i].timePoint = DData[i].timePoint;
}
queue.Enqueue(data_element);
}
}
private void MainTask()
{
while (running)
{
try
{
if (queue.Count > 0)
{
chart1.Series[0].IsXValueIndexed = false;
chart1.Series[1].IsXValueIndexed = false;
chart1.Series[2].IsXValueIndexed = false;
DataPoints data_element = (DataPoints)queue.Dequeue();
for (int i = 0; i < data_element.dataPoints.Length; i++)
{
chart1.Series[0].Points[lastSample + i].XValue = data_element.dataPoints[i].timePoint;
chart1.Series[0].Points[lastSample + i].YValues[0] = data_element.dataPoints[i].X;
chart1.Series[1].Points[lastSample + i].XValue = data_element.dataPoints[i].timePoint;
chart1.Series[1].Points[lastSample + i].YValues[0] = data_element.dataPoints[i].Y;
chart1.Series[2].Points[lastSample + i].XValue = data_element.dataPoints[i].timePoint;
chart1.Series[2].Points[lastSample + i].YValues[0] = data_element.dataPoints[i].Z;
}
//Adjust the next location to end of first
lastSample = (lastSample + samples) % maxPlotPoints;
chart1.Series[0].IsXValueIndexed = true;
chart1.Series[1].IsXValueIndexed = true;
chart1.Series[2].IsXValueIndexed = true;
GC_UpdateDataGrids();
chart1.Invalidate();
}
else
{
Thread.Sleep(10);
}
}
catch (Exception error)
{
LogErrors.AddErrorMsg(error.ToString());
}
}
}
1st of all I will show my code, its very simple.
FileStream fs = new FileStream("Stock.txt", FileMode.Open);
StreamReader sr = new StreamReader(fs, System.Text.Encoding.Default);
string line = "";
string[] Produtos = new string[] { };
int count = 1;
int i = 0;
int x = 25;
int y = 25;
while ((line = sr.ReadLine()) != null)
{
if (!string.IsNullOrEmpty(line))
{
ListagemProdutos myPanel = new ListagemProdutos();
myPanel.Location = new Point(x, y);
this.Controls.Add(myPanel);
y = y + 25;
x = x + 25;
i++;
}
}
Now the problem is, every time I get a line != null, it means I have a product which I will display like this
ProductName ProductPrice ProductQuantity Picture
That is what the usercontrol has, just 3 labels and a picturebox, then I want to call it and fill it until the end of my file. The code works perfect with only a MAIN problem since I'm declaring the instance like this
ListagemProdutos myPanel = new ListagemProdutos();
I think he is overwriting , and instead getting lets say I have 5 products get 5 rows (5 times the user control) I only get the last line.. I think I should do it something like this mypanel+[i] ,so it would be mypanel1,mypanel2 but I am not getting the way to do it, cause we can't just do
ListagemProdutos myPanel+[i] = new ListagemProdutos();
Thank you all for reading this, and sorry for any grammar errors hope you could understand anyway!
EDIT: This is a print showing my problem, in a simpler way. Why I can't have 2 or more instances of my user control? why only shows only 1 always?
http://epvpimg.com/Be8tc
Romero, Instead incrementing both x & y axis at the end of the foreach loop, increment only y axis. May be your control is going out of the window area.
Just a suggestion, adding control the way you are attempting could be cumbersome instead try using a grid control and add new rows to it with desired values.
I'm not sure if the title is correct because I wasn't sure how to explain it. I've encountered many scenarios where being able to dynamically modify part of a variable name such a suffixing integer could save me a great deal of time and keep my code much cleaner but I'm not sure how to do it. Here's an example of my most recent encounter.
I have 9 PictureBox's in a 3 x 3 grid. Each PictureBox has a name of cell followed by it's number so cell1, cell2, cell3 etc. I want to get the background colour of each of these cells and assign them to a variable whilst converting them to strings... something like this:
for (int i = 1; i < 10; i++)
{
string ci = celli.BackColor.ToString();
}
Is there a way I can have the i variable insert only it's numeric value to the placeholder rather than appending an i to the variable name? Can I wrap it in some sort of bracket? I've tried Googleing this but I'm finding it difficult to search for using just keywords.
Thanks in advance.
You are probably using a visual form editor, the best way to do this whould probably be to generate the grid by code (and not visually).
Another solution is to make it a matrix:
PictureBox[,] cell = new PictureBox[,] {
{ cell1, cell2, cell3 },
{ cell4, cell5, cell6 },
{ cell7, cell8, cell9 }
};
string[,] c = new string[3, 3];
for(int y=0; y<3; y++)
for(int x=0; x<3; x++)
c[x, y] = cell[x, y].BackColor.ToString();
Good luck with your code.
You would like to generate a list or collection of all your pictureboxes so that you can access them by specifying their index. One way is to generate the PictureBoxes on runtime:
Like this:
List<PictureBox> myPics = new List<PictureBox>();
int picWidth = 100;
int picHeight = 100;
for (x = 0; x <= this.Width; x += picWidth) {
for (y = 0; y <= this.Height; y += picHeight) {
PictureBox pic = new PictureBox();
pic.Image = pic.Image;
// Your image
pic.Location = new Point(x, y);
this.Controls.Add(pic);
myPics.Add(pic);
}
}
// Do something with myPics...
The other method is that when you do have all the pictureboxes on your form already, you can iterate through all the controls, check which ones are pictureboxes and then check their Name property to identify their index. Then do something with them accordingly.
foreach (void ctrl_loopVariable in this.Controls) {
ctrl = ctrl_loopVariable;
if (ctrl.GetType() == typeof(PictureBox)) {
if (ctrl.Name == "your picture box name to test") {
// Do something here with ctrl
}
}
}
(The above code is converted from VB to C#, excuse conversion issues)
Your intend here is to dynamically reference those controls.
In order to achieve this, there is two options:
You create those controls dynamically
You create dynamic references for the controls created by your form-designer
The first point is explained ny Shreyas Kapur's answer.
The second could be cone like this,
readonly Dictionary<Point,PictureBox> _dynamicMappedBoxes =
new Dictionary<Point,PictureBox>();
// Call this once in the beginning ofr your program
void createDynamicMapping()
{
foreach(PictureBox box in Controls.OfType<PictureBox>())
{
Point coords = getCoordinatesFromName(box);
_dynamicMappedBoxes.Add(coords, box);
}
}
Point getCoordinatesFromName(PictrueBox box)
{
int x = int.Parse(box.Name.SubString(IdontKnow);
int y = int.Parse(box.Name.SubString(IdontKnow);
retrun new Point(x,y);
}
//usage
string colorName = dynamicMappedBoxes[new Point(x,y)].BackColor.ToString();