Design Considerations For a Paint-Like Windows Application - c#

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.

Related

When I make a matrix of points in form_load it works, but when i make it in a separate class, and make an object of it in form_load it doesn`t work

So I need to make a matrix of points to spread buttons over an area, I wanted to make a class that has the matrix itself because the buttons wont appear on screen all the time and they will move in the grid etc.
anyway, here`s what the constructor for that class does:
public Table()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
_Grid[i, j] = new Point(i * 90, j * 90);
}
}
}
90 is the width of the buttons
then I have a property that allows getting the grid:
public Point[,] Grid
{
get;
}
then i do the form_load like this:
private void Form1_Load(object sender, EventArgs e)
{
Table tab = new Table();
Point[,] grid = tab.Grid;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Button btn = new Button();
btn.Width = 88;
btn.Height = 88;
btn.Location = grid[i, j]; // it returns a NullReferernceException here
panel1.Controls.Add(btn);
}
}
}
adding the location gives me a nullreference, but if i copy the code from the constructor and make the grid matrix in form_load get it, it works.(button width and height is 88 so there`s a nice little space between the buttons)
im obviously still learning, and you can see im stuck in the very beginning of this project because i cant even make all the buttons appear where they should.
It's quite a guess, but I suspect you've done something like this in Table
private Point[,] _Grid = new blahblahblah
public Point[,] Grid
{
get;
}
These two things are not referring to the same thing in memory - you've filled _Grid and it's valid object, but the property Grid is referencing some invisible backing store variable created by the compiler; having a name that is similar is not enough to wire them together
Change Table to be like:
public Point[,] Grid { get; } = new blahblahblah
Delete any mention of _Grid in favor of Grid
Change the constructor so it sets up the array via the property (thus writing values into the invisible backing field created by the compiler):
Grid[i, j] = new Point(i * 90, j * 90);
And then retrieving tab.Grid will retrieve a populated thing that is not null
Footnote, we name private members like _camelCase, not _PascalCase and if you want to explicitly declare a read only property that gets a class member field, it would look like:
private Point[,] _grid = blahblahblah;
public Point[,] Grid { get { return _grid; }}
Or:
private Point[,] _grid = blahblahblah;
public Point[,] Grid { get => _grid; }
i.e. you have to concretely wire them together by having the prop go and get that field by name, and return it
We can get into "properties should not return arrays" another time :D

How to randomly select a shape to draw several using a loop?

I got stuck when i want to display my objects in a random way .
Let's say i got this pannel and let's say the rectangle and ellipse are the objects
What do i need to do so i can have them display in this form.
Do i need to use Random n = new Random ? or there is another way.
This is one of my attemps but i dont know how to integrate the random function to display them.
do
{
dr.DrawEllipse(Bpen, i + 30, 25, i + 30, 25);
i += 50;
}
while (i <= this.Width);
You can use an array of available items like { squareType, circleType }.
And then random 0..Length on that array to select and draw the relevant shape.
For example you can use this enum:
public enum Shapes
{
Circle,
Square
}
And declare these members:
static readonly Array ShapesValues = Enum.GetValues(typeof(Shapes));
static readonly int ShapesCount = ShapesValues.Length;
And this RNG:
static readonly Random random = new Random();
So you can write:
int posX = 0;
do
{
switch ( ShapesValues.GetValue(random.Next(ShapesCount)) )
{
case Shapes.Circle:
// ...
break;
case Shapes.Square:
// ...
break;
default:
throw new NotImplementedException();
}
posX += 50;
}
while ( posX <= Width );
By doing this, you can define and manage any necessary shape in a clean and maintainable way.
If the shapes can have different sizes, consider handling this with intermediate variables.
You could also write this to make the code more secure:
static readonly ReadOnlyCollection<Shapes> ShapesValues
= Array.AsReadOnly(Enum.GetValues(typeof(Shapes)).Cast<Shapes>().ToArray());
static readonly int ShapesCount = ShapesValues.Count;
And:
switch ( ShapesValues[random.Next(ShapesCount)] )

Point buy system for a tabletop rpg

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.

How can I instantiate large number of buttons in Windows Forms?

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; }

List Operations without ForLoops

I have a class with the following members:
X
Y
Width
Height
One can create a rectangle with these parameters.
Now my problem is I have a list of this class, List<MyClass>.
I need to compare each object of the list with all the remaining objects in such a way that if the currentObject.Location(X, Y) falls in the rectangle(X, Y, Width, Height) of the other object, I need to delete the other object from the list.
I implemented it with for loops.
But the major problem is: performance.
My minimum list count is 300000.
Is there any procedure to improve the performance for this task uisng any of the .Net versions including LINQ?
`public class RectBase
{
private int _rectId;
private PointF _rectLocation;
private SizeF _rectSize;
public RectBase()
{
_rectId = -911;
_rectLocation = new PointF(0, 0);
_rectSize = new SizeF(0, 0);
}
public RectBase(int id, PointF loc, SizeF size)
{
_rectId = id;
_rectLocation = loc;
_rectSize = size;
}
public bool IsIntersected(RectBase otherRectObject)
{
RectangleF currentRect = new RectangleF(_rectLocation, _rectSize);
if (currentRect.Contains(otherRectObject.RectLocation))
return true;
else
return false;
}
public int RectId
{
get { return _rectId; }
set { _rectId = value; }
}
public PointF RectLocation
{
get { return _rectLocation; }
set { _rectLocation = value; }
}
public SizeF RectSize
{
get { return _rectSize; }
set { _rectSize = value; }
}
}
public class RectProcessor
{
List<RectBase> _rectList;
int maxCount = 300000;
public RectProcessor()
{
_rectList = new List<RectBase>();
FillList();
}
private void FillList()
{
// Adding the items to the list with dummy values
for (int i = 0; i < maxCount; i++)
{
int id = i+1;
PointF loc = new PointF(id, id);
SizeF sz = new SizeF(id, id);
RectBase obj = new RectBase(id, loc, sz);
_rectList.Add(obj);
}
}
private void RemoveIntersectedObjects()
{
List<RectBase> filteredList = new List<RectBase>();
bool isIntersected = false;
for (int i = 0; i < maxCount; i++)
{
for (int j = 0; j < maxCount; j++)
{
if (_rectList[i].IsIntersected(_rectList[j]))
{
isIntersected = true;
break;
}
}
if (!isIntersected)
{
filteredList.Add(_rectList[i]);
}
isIntersected = false;
}
}
}
`
The problem isn't eliminating for loops, at least in the way that you're thinking of it. Rewriting this in LINQ is just going to hide the for loops but they'll still be there. And that's the fundamental problem. Your algorithm, as written, is O(n^2) and that's why you see a ridiculous explosion in time as you go from 20,000 elements to 300,000 elements. You're doing 400,000,000 comparisons in the first case, and 90,000,000,000 in the second case and it will continue to grow like O(n^2).
So, the question you really want to ask is: is there an algorithm with time complexity better than O(n^2) for this problem?
Frankly, I don't know the answer to that question. I suspect that the answer is no: you can't know if a point is contained in some rectangle without comparing it to all the rectangles, and you have to inspect all the points. But maybe there's a clever way to do it such as computing the convex hull of all the rectangles and use that somehow?
This problem is an example of the field of computational geometry.

Categories