Related
In this program im writing I use a function to create multiple instances of a PictureBox. This is the code:
public void serverCard()
{
//Definitions
PictureBox cardBack = new PictureBox();
//Specifics for card
cardBack.Size = new Size(cardSizeX, cardSizeY);
cardBack.BackColor = Color.White;
cardBack.Left = startX;
cardBack.Top = startY;
serverArea.Controls.Add(cardBack);
//differences in pos
startX += cardBack.Width + 5;
if (startX > this.Width - cardSizeX)
{
startY += cardBack.Height + 5;
startX = 5;
}
}
How would I access a specific instance of the PictureBox.
For Example: I create 5 PictureBoxes called "cardBack" using this function. I want to change the position of the second Picture Box that was created, how would I go about this.
1) You could either give each PictureBox a different name (may be "cardBack" + ID_in_int)
int picBox_ID = 1;
public void serverCard()
{
PictureBox cardBack = new PictureBox();
cardBack.Name = "cardBack" + picBox_ID;
picBox_ID++;
and pull them out of the Controls by name:
PictureBox temp = serverArea.Controls.OfType<PictureBox>().FirstOrDefault(x=>x.Name == "cardBack2");
2) or you could have a separate collection of type: List<PictureBox> where you would store them additionally
List<PictureBox> picCollection = new List<PictureBox>();
public void serverCard()
{
PictureBox cardBack = new PictureBox();
picCollection.Add(cardBack);
and access them in the way you want. May be the order could be of interest.
3) another possibility could be to create a new class that has a property of type PictureBox and another property int ID. You could have a collection filled with these objects and each object could have a unique ID and the corresponding PictureBox. You can still put the picture boxes into the Controls and filter the collection according to your needs.
Create a method that will return instance of PictureBox
public PictureBox CreatePictureBox ()
{
// your code from question here
}
then define a field in your form
private Dictionary<string, PictureBox> pboxes = new Dictionary<string, PictureBox>();
Any time you want to create a new PictureBox put it inside pboxes collection:
pboxes.Add("box1", CreatePictureBox());
Now you can access to your boxes like this:
pboxes["box1"].Width += 20;
I'm trying to make a point buy system using numeric up/downs. Here's the idea:
There are six numeric up/downs, one for each trait (Strength, Dexterity, Constitution, Intelligence, Wisdom and Charisma). Each trait begins at 10 points. You can't bring a trait below 7 or above 18.
I'm a total noob, but I managed to do this:
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
numericUpDown1.Maximum = 18;
numericUpDown1.Minimum = 7;
}
I did this one six times. In my form, there is now six numeric up/downs. Now I'm trying to do something which is way too much for my minuscule knowledge.
I want a system in which the value of the six numeric up downs is combined and cannot be exceeded, which means that in this case, we would have 60 points and couldn't increase any score unless we decreased one. I would add 15 points to that "Point pool", so the user doesn't have to decrease a stat straight away, in order to increase another one.
Example: I have 1 point left, and my scores go as follows: 15, 15, 14, 10, 10, 10. I increase the third score by 1 point. I now have this:
15, 15, 15, 10, 10, 10.
Now I have nothing left, but I want my fourth score at 15 points. In order to achieve this, I have to decrease the fifth and sixth score until I have 5 points freed up. I now have this:
15, 15, 15, 15, 7, 8.
Having a Lil' box in my form to display how many points are left would be a cherry on top.
I did my best to explain this. Please take note that English is not my native language and I sometimes do struggle with it.
I'm clueless as to how I can achieve this, as I barely have any knowledge of C#. What would be the code missing ?
It would easier if you create a Character class
You could define defaults for each property in constructor, and individual methods to increase or decrease its points.
public class Character
{
private int totalPointsMax = 60;
private int maxPoints = 18;
private int minPoints = 7;
public int Strength { get; set; }
public int Dexterity { get; set; }
public int Constitution { get; set; }
public int Intelligence { get; set; }
public int Wisdom { get; set; }
public int Charisma { get; set; }
public Character()
{
// create new Character defaults...
Strength = 10;
Dexterity = 10;
Constitution = 10;
Intelligence = 10;
Wisdom = 10;
Charisma = 10;
}
private int GetTotalCharacterPoints()
{
return Strength + Dexterity + Constitution + Intelligence + Wisdom + Charisma;
}
//example of method to increase Strength
public void IncreaseStrength()
{
int availablePoints = totalPointsMax - GetTotalCharacterPoints();
if (availablePoints > 0 && Strength < maxPoints)
Strength++;
}
//example of method to decrease Strength
public void DecreaseStrength()
{
if (Strength >= minPoints)
Strength--;
}
//missing the other increase/decrease methods for the rest of features...
}
You just need to instance at beginning and your UI buttons only need to invoke the CharacterFoo.IncreaseStrength() or CharacterFoo.DecreaseWisdom() ... etc.
Also, with this option you can allways reuse this in any part of the game..
(ex: if your character finds any special potion .. then CharacterFoo.IncreaseStrength() )
Hope this helps...
There's a couple of ways to do it.
Firstly, move these outside of the event function:
numericUpDown1.Maximum = 18;
numericUpDown1.Minimum = 7;
My recommendation would be to set a variable with the maximum points:
private int MAX_POINTS = 60;
Then when one of them is changed, you can call another method that adds up all of the current boxes, and determines whether it is over the limit or not:
private bool TotalOfStatsIsOverLimit() {
int total = GetTotalOfStats();
return total > MAX_POINTS;
}
private int GetTotalOfStats() {
int total = 0;
// TODO: Go through each number box and add to the total
return total;
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e) {
if(TotalOfStatsIsOverLimit()) {
// TODO: Decrease the value of the stat box that was updated
}
}
One advantage of doing it this way, is that you can reuse the same event method numericUpDown1_ValueChanged for all 6 of your stat boxes.
If I were you, I would go with a class for character since it will greatly simplify your future works, though if you are new, it may be harder at the start.
Still, you can follow up the basic approach as follows:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private const int totalPoints = 60 + 15; // sum of each trait plus the pool bonus
public Form1()
{
InitializeComponent();
foreach (Control control in this.Controls)
{
if (control is NumericUpDown)
{
NumericUpDown numControl = control as NumericUpDown;
numControl.Minimum = 7;
numControl.Maximum = 18;
numControl.Value = 10;
numControl.ValueChanged += nud_ValueChanged;
lblPointsLeft.Text = "15"; // bonus points
}
}
}
private void nud_ValueChanged(object sender, EventArgs e)
{
int sum = (int)(nudCha.Value + nudCon.Value + nudDex.Value + nudInt.Value + nudStr.Value + nudWis.Value);
int pointsLeft = totalPoints - sum;
NumericUpDown nudSender = (NumericUpDown)sender;
if (pointsLeft < 0)
{
MessageBox.Show("No points left");
// restore last change
// undo the last change
nudSender.Value = nudSender.Value - 1;
pointsLeft++;
}
lblPointsLeft.Text = pointsLeft.ToString();
}
}
}
Here's some Pseudocode:
You want to create a variable to hold your current points. Create some labels to hold that variable, and make sure you do AJAX calls, otherwise every time you update you are going to be calling this from the server again. This is probably better done in Javascript/Jquery.
int pointsUsed = numericUpDown1.value + numericUpDown2.value + numericUpDown6.value; //add all 6 of your values.
//for your textbox:
label1.text = "points left is:"
label2.text = 75 - pointsUsed;
private void checkBox1_Click(Object sender, EventArgs e)
{//to add points
if (pointsUsed < 75)
{
numericUpDown1.Value += 1;
pointsUsed += 1;
}
}
Check MSDN NumericUpDown for more info.
This question already has answers here:
Random number generator only generating one random number
(15 answers)
Closed 8 years ago.
I m trying to fill a Canvas with some random shapes( Ellipse and Rectangle). I need to pick a random color to fill the shapes with it. The problem that I have always the same random color for all the shape.
When I debug my code I get random colors. Below is my Code :
public MainWindow()
{
InitializeComponent();
this.Loaded += delegate { InitializeSourceCanvas(); };
}
private void InitializeSourceCanvas()
{
var rnd = new Random();
const int height = 30, width = 30;
for (int i = 0; i < 25; i++)
{
var shape = rnd.Next(10) > 4 ? (Shape)new Ellipse() : (Shape)new Rectangle();
shape.Width = height;
shape.Height = width;
shape.Stroke = new SolidColorBrush(Colors.Black);
shape.StrokeThickness = 1;
shape.Fill = PickRandomBrush();
Canvas.SetLeft(shape, rnd.NextDouble() * (_source.ActualWidth - width));
Canvas.SetTop(shape, rnd.NextDouble() * (_source.ActualHeight - height));
_source.Children.Add(shape);
}
}
private Brush PickRandomBrush()
{
Brush result = Brushes.Transparent;
Random rnd = new Random();
Type brushesType = typeof(Brushes);
PropertyInfo[] properties = brushesType.GetProperties();
int random = rnd.Next(properties.Length);
result = (Brush)properties[random].GetValue(null, null);
return result;
}
Like Nuke suggests, new your Random object outside the for loop so it's only instantiated once, and then pass the same one in each time.
Random rnd = new Random();
for (int i = 0; i < 25; i++)
{
...
shape.Fill = PickRandomBrush(rnd);
...
}
Then edit your PickRandomBrush method to look like,
private Brush PickRandomBrush(Random rnd)
{
Brush result = Brushes.Transparent;
Type brushesType = typeof(Brushes);
PropertyInfo[] properties = brushesType.GetProperties();
int random = rnd.Next(properties.Length);
result = (Brush)properties[random].GetValue(null, null);
return result;
}
Edit:
k, sraboy made a good point about running this code in quick succession--if called fast enough this will have the same seed. Here's a solution that's not elegant, but reasonably guaranteed to be unique:
Random rnd = new Random(Guid.NewGuid().ToString().GetHashCode());
(this would replace the first line)
From MSDN:
[..] different Random objects that are created in close succession by a call to the default constructor will have identical default seed values and, therefore, will produce identical sets of random numbers. This problem can be avoided by using a single Random object to generate all random numbers.
The Random is because you are not binding the function to a timer, meaning it has the same conditions each time it is created.
One solution would be to tie the random to the Systemtime.Now
Another solution would be to use the same Random instead of creating a new one.
I'm developing a theatre reservation software. I'm using Windows Forms, the seats is represented by a 2-dimensioned array. And I draw the buttons as following:
public void DrawSeats()
{
// pnl_seats is a Panel
pnl_seats.Controls.Clear();
// Here I store all Buttons instance, to later add all buttons in one call (AddRange) to the Panel
var btns = new List<Control>();
// Suspend layout to avoid undesired Redraw/Refresh
this.SuspendLayout();
for (int y = 0; y < _seatZone.VerticalSize; y++)
{
for (int x = 0; x < _seatZone.HorizontalSize; x++)
{
// Check if this seat exists
if (IsException(x, y))
continue;
// Construct the button with desired properties. SeatSize is a common value for every button
var btn = new Button
{
Width = SeatSize,
Height = SeatSize,
Left = (x * SeatSize),
Top = (y * SeatSize),
Text = y + "" + x,
Tag = y + ";" + x, // When the button clicks, the purpose of this is to remember which seat this button is.
Font = new Font(new FontFamily("Microsoft Sans Serif"), 6.5f)
};
// Check if it is already reserved
if (ExistsReservation(x, y))
btn.Enabled = false;
else
btn.Click += btn_seat_Click; // Add click event
btns.Add(btn);
}
}
// As said before, add all buttons in one call
pnl_seats.Controls.AddRange(btns.ToArray());
// Resume the layout
this.ResumeLayout();
}
But already with a seat zone of 20 by 20 (400 buttons), it spent almost 1 minute to draw it, and in debug I checked that the lack of performance, is the instantiation of the buttons.
There is a way to make it faster? Perhaps disable all events during the instatiation or another lightweight Control that has the Click event too?
UPDATE:
lbl was a test, the correct is btn, sorry.
UPDATE 2:
Here is the IsException and ExistsReservations methods:
private bool IsException(int x, int y)
{
for (var k = 0; k < _seatsExceptions.GetLength(0); k++)
if (_seatsExceptions[k, 0] == x && _seatsExceptions[k, 1] == y)
return true;
return false;
}
private bool ExistsReservation(int x, int y)
{
for (var k = 0; k < _seatsReservations.GetLength(0); k++)
if (_seatsReservations[k, 0] == x && _seatsReservations[k, 1] == y)
return true;
return false;
}
Suppose that you change your arrays for reservations and exclusions to
public List<string> _seatsExceptions = new List<string>();
public List<string> _seatsReservations = new List<string>();
you add your exclusions and reservations in the list with something like
_seatsExceptions.Add("1;10");
_seatsExceptions.Add("4;19");
_seatsReservations.Add("2;5");
_seatsReservations.Add("5;5");
your checks for exclusions and reservations could be changed to
bool IsException(int x, int y)
{
string key = x.ToString() + ";" + y.ToString();
return _seatsExceptions.Contains(key);
}
bool ExistsReservation(int x, int y)
{
string key = x.ToString() + ";" + y.ToString();
return _seatsReservations.Contains(key);
}
of course I don't know if you are able to make this change or not in your program. However consider to change the search on your array sooner or later.
EDIT I have made some tests, and while a virtual grid of 20x20 buttons works acceptably well (31 millisecs 0.775ms on average), a bigger one slows down noticeably. At 200x50 the timing jumps to 10 seconds (1,0675 on average). So perhaps a different approach is needed. A bound DataGridView could be a simpler solution and will be relatively easy to handle.
I also won't use such a myriad of controls to implement such a thing. Instead you should maybe create your own UserControl, which will paint all the seats as images and reacts on a click event.
To make it a little easier for you i created such a simple UserControl, that will draw all the seats and reacts on a mouse click for changing of the state. Here it is:
public enum SeatState
{
Empty,
Selected,
Full
}
public partial class Seats : UserControl
{
private int _Columns;
private int _Rows;
private List<List<SeatState>> _SeatStates;
public Seats()
{
InitializeComponent();
this.DoubleBuffered = true;
_SeatStates = new List<List<SeatState>>();
_Rows = 40;
_Columns = 40;
ReDimSeatStates();
MouseUp += OnMouseUp;
Paint += OnPaint;
Resize += OnResize;
}
public int Columns
{
get { return _Columns; }
set
{
_Columns = Math.Min(1, value);
ReDimSeatStates();
}
}
public int Rows
{
get { return _Rows; }
set
{
_Rows = Math.Min(1, value);
ReDimSeatStates();
}
}
private Image GetPictureForSeat(int row, int column)
{
var seatState = _SeatStates[row][column];
switch (seatState)
{
case SeatState.Empty:
return Properties.Resources.emptySeat;
case SeatState.Selected:
return Properties.Resources.choosenSeat;
default:
case SeatState.Full:
return Properties.Resources.fullSeat;
}
}
private void OnMouseUp(object sender, MouseEventArgs e)
{
var heightPerSeat = Height / (float)Rows;
var widthPerSeat = Width / (float)Columns;
var row = (int)(e.X / widthPerSeat);
var column = (int)(e.Y / heightPerSeat);
var seatState = _SeatStates[row][column];
switch (seatState)
{
case SeatState.Empty:
_SeatStates[row][column] = SeatState.Selected;
break;
case SeatState.Selected:
_SeatStates[row][column] = SeatState.Empty;
break;
}
Invalidate();
}
private void OnPaint(object sender, PaintEventArgs e)
{
var heightPerSeat = Height / (float)Rows;
var widthPerSeat = Width / (float)Columns;
e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
for (int row = 0; row < Rows; row++)
{
for (int column = 0; column < Columns; column++)
{
var seatImage = GetPictureForSeat(row, column);
e.Graphics.DrawImage(seatImage, row * widthPerSeat, column * heightPerSeat, widthPerSeat, heightPerSeat);
}
}
}
private void OnResize(object sender, System.EventArgs e)
{
Invalidate();
}
private void ReDimSeatStates()
{
while (_SeatStates.Count < Rows)
_SeatStates.Add(new List<SeatState>());
if (_SeatStates.First().Count < Columns)
foreach (var columnList in _SeatStates)
while (columnList.Count < Columns)
columnList.Add(SeatState.Empty);
while (_SeatStates.Count > Rows)
_SeatStates.RemoveAt(_SeatStates.Count - 1);
if (_SeatStates.First().Count > Columns)
foreach (var columnList in _SeatStates)
while (columnList.Count > Columns)
columnList.RemoveAt(columnList.Count - 1);
}
}
This will currently draw forty rows and columns (so there are 800 seats) and you can click on each seat to change its state.
Here are the images i used:
EmtpySeat:
ChoosenSeat:
FullSeat:
If you anchor this control and resize it or you click on a seat to change its state there can be some minor lacking for the repainting if you further increase the number of rows or columns, but that is still somewhere far below one second. If this still hurts you, you have to improve the paint method and maybe check the ClipRectangle property of the paint event and only paint the really needed parts, but that's another story.
Rather than using actual button controls, just draw the image of the seats then when the user clicks on a seat translate the mouse X,Y coordinates to determine which seat was clicked. This will be more efficient. Of course, the drawback is that you have to write the method to translate x,y coordinates to a seat, but that really isn't that difficult.
EDIT; it has been pointed out to me this will not work in Windows Forms!
Well, you are Sequentially working through it.
if one iteration costs 1 sec, the full process will take 400*1 in time.
Perhaps you should try and make a collection of your objects, and process it 'parallel'.
try the .Net framework (4 and above) 'parallel foreach' method:
http://msdn.microsoft.com/en-s/library/system.threading.tasks.parallel.foreach(v=vs.110).aspx
edit: so, if you have a list buttonnames, you can say
buttonNames.ForEach(x=>CreateButton(x));
while your CreateButton() method is as following:
private Button CreateButton(string nameOfButton) { Button b = new
Button(); b.Text = nameOfButton; //do whatever you want...
return b; }
I'm in process of developing a new front end for an old open source fluid modeling engine written in C. I am using C# and WPF. The apps network generation requires the user to draw a network of pipes, reservoirs, nodes, tanks, etc. The app is more or less a fancy version of paint ;)
Right now I have the graphics set up as follows. I have an embedded win-forms Panel that has mouseclick, mousemove, and paint events. Mouse click saves the clicked coordinates to an array in the singleton class and then triggers the paint event via invalidate();. The paint event then loops through the array and draws all the node coordinates in the array via: g.FillEllipse(x,y,20,20); The user can click a node and bring up a menu via a function I wrote called DoesPointExist(xCord, yCord);. It loops through the coordinates array and returns true if both xcord and ycord are within 5px of the clicked coordinates. A somewhat archaic solution but it seems to work well enough.
This has worked pretty well for me thus far. But in the future I am going to have to give each node (or circle on the Panel) more and more properties. Nothing fancy, just numerical values like elevation that must be associated with each node. Also, I'm going to need to add in an option to delete nodes at some point.
I could probably do this by setting all values of the deleted row to 0 and placing in if statement in the paintevents loop to not draw deleted points, or even figure out how to just get rid of the row period and shift all the others down.
My question is there a more intelligent and OOP type way to go about this? Looping through and array seems a little old fashioned, there must be a better way to make use of C# features. Is there some sort of object or class I could setup to make this process simpler? The array is fine but by the end its going to be 40-50 columns. My background is more based in functional type programming with lower level languages like C. My program seems very bare of objects and classes other than a singleton glass for global data.
I know there are different ways to skin the cat; but it's important that the code I write will be accessible and easy to modify for future engineers so I'd like to add as much within the OOP paradigm as possible. My current code is very functional... but not very tidy.
Paint event code:
private void wfSurface_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g;
Graphics h;
g = wfSurface.CreateGraphics();
h = wfSurface.CreateGraphics();
epanet epa = epanet.GetInstance();
SolidBrush black = new SolidBrush(System.Drawing.Color.Black);
SolidBrush blue = new SolidBrush(System.Drawing.Color.Pink);
SolidBrush green = new SolidBrush(System.Drawing.Color.Green);
System.Drawing.Pen line = new System.Drawing.Pen(System.Drawing.Color.FromArgb(255, 0, 0, 0));
//Loop to draw vertical grid lines
for (int f = 50; f < 1100; f += 50)
{
e.Graphics.DrawLine(line, f, 0, f, 750);
}
//Loop to draw vertical grid lines
for (int d = 50; d < 750; d += 50)
{
e.Graphics.DrawLine(line, 0, d, 1100, d);
}
//Loop nodes, tanks, and resevoirs
for (int L = 1; L < index; L += 1)
{
g.FillEllipse(black, Convert.ToInt32(epa.newNodeArray[L, 0] - 8), Convert.ToInt32(epa.newNodeArray[L, 1] - 8), 19, 19);
h.FillEllipse(blue, Convert.ToInt32(epa.newNodeArray[L, 0] - 6), Convert.ToInt32(epa.newNodeArray[L, 1] - 6), 15, 15);
}
for (int b = 1; b < resIndex; b += 1)
{
g.FillRectangle(green, Convert.ToInt32(epa.ResArray[b, 0] - 8), Convert.ToInt32(epa.ResArray[b, 1] - 8), 16, 16);
}
for (int c = 1; c < tankIndex; c += 1)
{
g.FillRectangle(black, Convert.ToInt32(epa.tankArray[c, 0] - 8), Convert.ToInt32(epa.tankArray[c, 1] - 8), 20, 20);
g.FillRectangle(green, Convert.ToInt32(epa.tankArray[c, 0] - 6), Convert.ToInt32(epa.tankArray[c, 1] - 6), 16, 16);
}
}
Code for click event:
private void wfSurface_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
{
//Initialize epanet and save clicked coordinates to singleton class
epanet epa = epanet.GetInstance();
epa.xCord = e.X;
epa.yCord = e.Y;
//Check if point exists, if does open property window, doesn't do whatever drawing control is selected
if (epa.DoesPointExist(e.X, e.Y, index) == false)
{
switch (epa.controlSelected)
{
case "Node":
epa.newSetCords(index, e.X, e.Y);
wfSurface.Invalidate();
index += 1;
break;
case "Res":
epa.setResCords(resIndex, e.X, e.Y);
wfSurface.Invalidate();
resIndex += 1;
wfPanel.Cursor = Cursors.Arrow;
break;
case "Tank":
epa.setTankCords(tankIndex, e.X, e.Y);
wfSurface.Invalidate();
tankIndex += 1;
break;
case "Pointer":
break;
default:
//epa.newSetCords(index, e.X, e.Y);
wfSurface.Invalidate();
break;
}
}
else if (epa.DoesPointExist(e.X, e.Y, index) == true)
{
MessageBox.Show("Point Already Exists");
if (epa.propOpen == false)
{
// Open control properties in right pannel
}
}
Code for singleton class:
public class epanet
{
private static epanet instance = new epanet();
private epanet() { }
public static epanet GetInstance()
{
return instance;
}
//Microsoft.Win32.SaveFileDialog save = new Microsoft.Win32.SaveFileDialog();
//Network Node Data
public int nodeIndex { get; set; }
public int newNodeIndex { get; set; }
public double xCord { get; set; }
public double yCord { get; set; }
public double x1Cord { get; set; }
public double y1Cord { get; set; }
public int selectedPoint { get; set; }
//public List<double> nodeList = new List<double>();
//Saving Data
public int fileCopyNum { get; set; }
public string filename { get; set; }
public string path { get; set; }
public string fullFileName { get; set; }
//Window Condition Data
public bool drawSurfStatus { get; set; }
public bool windowOpen { get; set; }
public bool OpenClicked { get; set; }
public bool saveASed { get; set; }
public bool newClicked { get; set; }
public bool propOpen { get; set; }
//Drawing Controls
public string controlSelected { get; set; }
//Declare Array to store coordinates
public double[,] nodeArray = new double[100000, 3];
public double[,] newNodeArray = new double[100000, 7];
public double[,] ResArray = new double[100000, 7];
public double[,] tankArray = new double[100000, 7];
public void newSetCords(int newNodeIndex, double xCord, double yCord)
{
newNodeArray[newNodeIndex, 0] = xCord;
newNodeArray[newNodeIndex, 1] = yCord;
newNodeArray[nodeIndex, 2] = nodeIndex;
}
public void setResCords(int newNodeIndex, double xCord, double yCord)
{
ResArray[newNodeIndex, 0] = xCord;
ResArray[newNodeIndex, 1] = yCord;
ResArray[nodeIndex, 2] = nodeIndex;
}
public void setTankCords(int newNodeIndex, double xCord, double yCord)
{
tankArray[newNodeIndex, 0] = xCord;
tankArray[newNodeIndex, 1] = yCord;
tankArray[nodeIndex, 2] = nodeIndex;
}
public void setCords(int nodeIndex, double xCord, double yCord)
{
nodeArray[nodeIndex, 0] = xCord;
nodeArray[nodeIndex, 1] = yCord;
//nodeArray[nodeIndex, 2] = nodeIndex;
}
public bool DoesPointExist(double xcord, double ycord, int index)
{
int count = 1;
bool outcome = false;
while (count < index)
{
if (Math.Abs(xcord - newNodeArray[count, 0]) < 20)
{
if (Math.Abs(ycord - newNodeArray[count, 1]) < 20 )
{
outcome = true;
selectedPoint = count;
index = 0;
}
}
count += 1;
}
return outcome;
}
As I said everything works completely fine. I'm just looking for some feedback on if there's a more professional way to go about doing this.
If you've done much programming in C, you should be familiar with the concept of a linked-list. It's much more useful than an array, because it lets you delete from any point in the list in constant time. In C#, you'll want to look into System.Collections.Generic.List to make use of it.
Beyond that, standard object-oriented design is to look through your functional requirements and look for nouns, and those should become your classes. The biggest noun I see in your question is "node." So instead of having an array of vertices, have a linked list of nodes. Each node can have properties such as Coordinates and Elevation, and is then extensible if you need more. Note, if you find your Node class getting cumbersome, that's a huge sign that you should split off other classes. Any logical grouping should pretty coherently present itself.
Look through your epanet class that you included. You have functionality to handle files (filename, is it save-as'd, etc). That should be turned in to a class (in a separate file) just to help keep the files maintainable. The sequence of arrays you have, all of the same primary dimension define your general node structure. Put those and your function that operate on those into your Node class. You already have logic that works; it's just not intuitive to the next reader. Spend a bit of time questioning your placement of all your code, and asking yourself if there are any logical groupings you can put together.
Your general flow is fine if you want to limit yourself to Winforms/GDI+, and you can obviously get along that way, but since you're programming in WPF, you may want to step up to actually using it while your project is still young. C# Transition between GDI+ and WPF gives a little bit of a heads-up as how to do so, particularly suggestion 5 of the accepted answer. There are tons of other references on the web as well. A quick Google search brought up Manual rendering of a WPF User Control and of course MSDN. I'll leave WPF advice up to others who know it better than I.
If you don't know yet if you want to use GDI or WPF, you definitely need to have a GDIRenderer class. Ideally, you should have an IRenderer interface and a GDIRenderer class that implements that interface, though I do find that practically, it's more convenient to make the interface when you have a second object that implements it. In any case, your Renderer class(es) should take a List of Node as input. (You currently have them global, but it's generally good to only let one part of code have access to what it needs to, i.e. avoid globals.) In your current code, GDIRenderer would simply contain and register your OnPaint function. But by doing this, you isolate all of your rendering into a single class (which should be in a file of its own). Then, should you decide to move on to WPF, you can substitue the GDIRenderer for a WPFRenderer, which could contain all the code needed to handle the OnRender functionality. You want to do all you can to separate your business logic from the way you render.