I'm trying to make sorting visualization algorithm in c# but I got a problem with the canvas refreshing.
I tried to refresh the canvas every time I redraw but its not looks good. I'm sure there is another way to do it and I hope someone can help me.
In this picture you can see the black rectangles that I want to delete from the canvas
This is my code :
private void GenerateArrayButton_Click(object sender, EventArgs e)
{
MyCanvas.Refresh();
Random random = new Random();
int xPosition = 0 , yPosition = MyCanvas.Height/2;
const int k_RectangleWight = 2;
for(int i = 0; i < k_SizeOfArray; i++)
{
int rectangleHeight = random.Next(MyCanvas.Height / 2);
m_UnsortedArray[i] = new Rectangle(xPosition,yPosition, k_RectangleWight, rectangleHeight);
xPosition += 5;
}
draw(m_UnsortedArray, Pens.Black);
}
private void draw(Rectangle[] i_ArrayToDraw, Pen i_PenColor)
{
var graphics = MyCanvas.CreateGraphics();
graphics.DrawRectangles(i_PenColor, i_ArrayToDraw);
graphics.Dispose();
}
private void SortingButton_Click(object sender, EventArgs e)
{
bubbleSort();
draw(m_UnsortedArray, Pens.Green);
}
private void bubbleSort()
{
for(int i = 0; i < m_UnsortedArray.Length; i++)
{
for(int j = 0; j < m_UnsortedArray.Length - 1; j++)
{
if(m_UnsortedArray[j].Height > m_UnsortedArray[j + 1].Height)
{
swap(ref m_UnsortedArray[j], ref m_UnsortedArray[j+1]);
}
}
draw(m_UnsortedArray,Pens.Black);
}
}
private void swap(ref Rectangle i_Rectangle1, ref Rectangle i_Rectangle2)
{
// Swap the position of the rectangle
var location = i_Rectangle1.Location;
i_Rectangle1.Location = i_Rectangle2.Location;
i_Rectangle2.Location = location;
// Swap the position of the current rectangle in the array
var copyRect = i_Rectangle1;
i_Rectangle1 = i_Rectangle2;
i_Rectangle2 = copyRect;
}
}
The drawing canvas in question MyCanvas whether its a PictureBox or a Panel or the Form itself, provides specific events for the painting routines, particularly the Paint event in this context. The event has a PaintEventArgs which provides a free Graphics object to do your drawings. Meaning, you don't need to create extra Graphics objects like in your draw method. Now let's draw those rectangles.
Class level fields:
using System.Threading.Tasks;
//...
public partial class Form1 : Form
{
private const int k_RectangleWight = 2;
private const int k_SizeOfArray = 100; //assign the right value.
private Rectangle[] m_UnsortedArray;
Random rand = new Random();
private Pen MyPen;
Handle the Paint event of the MyCanvas control.
public Form1()
{
InitializeComponent();
//You can add normal event handler instead if you prefer so.
MyCanvas.Paint += (s, e) =>
{
if (MyPen != null)
e.Graphics.DrawRectangles(MyPen, m_UnsortedArray);
};
}
In the GenerateArrayButton_Click event, create the rectangles, assign the drawing pen, and call the Invalidate() method of the drawing canvas.
private void GenerateArrayButton_Click(object sender, EventArgs e)
{
m_UnsortedArray = new Rectangle[k_SizeOfArray];
var xPosition = 0;
var yPosition = MyCanvas.Height / 2;
for(var i = 0; i < k_SizeOfArray; i++)
{
var rectangleHeight = rand.Next(MyCanvas.Height / 2);
m_UnsortedArray[i] = new Rectangle(
xPosition,
yPosition,
k_RectangleWight,
rectangleHeight);
xPosition += 5;
}
MyPen = Pens.Black;
MyCanvas.Invalidate();
}
At this point, you will get something drawn like this:
Now the second part. Your methods for swapping the rectangles:
private async void bubbleSort()
{
for (int i = 0; i < m_UnsortedArray.Length; i++)
{
for (int j = 0; j < m_UnsortedArray.Length - 1; j++)
if (m_UnsortedArray[j].Height > m_UnsortedArray[j + 1].Height)
swap(ref m_UnsortedArray[j], ref m_UnsortedArray[j + 1]);
await Task.Delay(30);
MyCanvas.Invalidate();
}
}
private void swap(ref Rectangle i_Rectangle1, ref Rectangle i_Rectangle2)
{
var location = i_Rectangle1.Location;
i_Rectangle1.Location = i_Rectangle2.Location;
i_Rectangle2.Location = location;
var copyRect = i_Rectangle1;
i_Rectangle1 = i_Rectangle2;
i_Rectangle2 = copyRect;
}
In the click event of the SortingButton, you just need to:
private void SortingButton_Click(object sender, EventArgs e)
{
MyPen = Pens.Green;
bubbleSort();
}
}
... and you will get:
Related
I am trying to simulate a LED display board with c# . I need a control which contains 1536 clickable controls to simulate LEDs (96 in width and 16 in Height). I used a panel named pnlContainer for this and user will add 1536 tiny customized panels at runtime. These customized panels should change their color by click event at runtime. Everything works . But adding this number of tiny panels to the container takes long time ( about 10 secs). What is your suggestion to solve this issue? Any tips are appreciated.
this is my custome panel:
public partial class LedPanel : Panel
{
public LedPanel()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
}
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (this.BackColor == Color.Black)
{
this.BackColor = Color.Red;
}
else
{
this.BackColor = Color.Black;
}
}
}
}
and this is piece of code which adds tiny panels to the pnlContainer :
private void getPixels(Bitmap img2)
{
pnlContainer.Controls.Clear();
for (int i = 0; i < 96; i++)
{
for (int j = 0; j < 16; j++)
{
Custom_Controls.LedPanel led = new Custom_Controls.LedPanel();
led.Name = i.ToString() + j.ToString();
int lWidth = (int)(pnlContainer.Width / 96);
led.Left = i * lWidth;
led.Top = j * lWidth;
led.Width = led.Height = lWidth;
if (img2.GetPixel(i, j).R>numClear.Value)
{
led.BackColor = Color.Red;
}
else
{
led.BackColor = Color.Black;
}
led.BorderStyle = BorderStyle.FixedSingle;
pnlContainer.Controls.Add(led);
}
}
}
Is there any better approach or better control instead of panelto do this?
I agree with what #TaW recommends. Don't put 1000+ controls on a form. Use some sort of data structure, like an array to keep track of which LEDs need to be lit and then draw them in the Paint event of a Panel.
Here's an example. Put a Panel on a form and name it ledPanel. Then use code similar to the following. I just randomly set the values of the boolean array. You would need to set them appropriately in response to a click of the mouse. I didn't include that code, but basically you need to take the location of the mouse click, determine which array entry needs to be set (or unset) and then invalidate the panel so it will redraw itself.
public partial class Form1 : Form
{
//set these variables appropriately
int matrixWidth = 96;
int matrixHeight = 16;
//An array to hold which LEDs must be lit
bool[,] ledMatrix = null;
//Used to randomly populate the LED array
Random rnd = new Random();
public Form1()
{
InitializeComponent();
ledPanel.BackColor = Color.Black;
ledPanel.Resize += LedPanel_Resize;
//clear the array by initializing a new one
ledMatrix = new bool[matrixWidth, matrixHeight];
//Force the panel to repaint itself
ledPanel.Invalidate();
}
private void LedPanel_Resize(object sender, EventArgs e)
{
//If the panel resizes, then repaint.
ledPanel.Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
//clear the array by initializing a new one
ledMatrix = new bool[matrixWidth, matrixHeight];
//Randomly set 250 of the 'LEDs';
for (int i = 0; i < 250; i++)
{
ledMatrix[rnd.Next(0, matrixWidth), rnd.Next(0, matrixHeight)] = true;
}
//Make the panel repaint itself
ledPanel.Invalidate();
}
private void ledPanel_Paint(object sender, PaintEventArgs e)
{
//Calculate the width and height of each LED based on the panel width
//and height and allowing for a line between each LED
int cellWidth = (ledPanel.Width - 1) / (matrixWidth + 1);
int cellHeight = (ledPanel.Height - 1) / (matrixHeight + 1);
//Loop through the boolean array and draw a filled rectangle
//for each one that is set to true
for (int i = 0; i < matrixWidth; i++)
{
for (int j = 0; j < matrixHeight; j++)
{
if (ledMatrix != null)
{
//I created a custom brush here for the 'off' LEDs because none
//of the built in colors were dark enough for me. I created it
//in a using block because custom brushes need to be disposed.
using (var b = new SolidBrush(Color.FromArgb(64, 0, 0)))
{
//Determine which brush to use depending on if the LED is lit
Brush ledBrush = ledMatrix[i, j] ? Brushes.Red : b;
//Calculate the top left corner of the rectangle to draw
var x = (i * (cellWidth + 1)) + 1;
var y = (j * (cellHeight + 1) + 1);
//Draw a filled rectangle
e.Graphics.FillRectangle(ledBrush, x, y, cellWidth, cellHeight);
}
}
}
}
}
private void ledPanel_MouseUp(object sender, MouseEventArgs e)
{
//Get the cell width and height
int cellWidth = (ledPanel.Width - 1) / (matrixWidth + 1);
int cellHeight = (ledPanel.Height - 1) / (matrixHeight + 1);
//Calculate which LED needs to be turned on or off
int x = e.Location.X / (cellWidth + 1);
int y = e.Location.Y / (cellHeight + 1);
//Toggle that LED. If it's off, then turn it on and if it's on,
//turn it off
ledMatrix[x, y] = !ledMatrix[x, y];
//Force the panel to update itself.
ledPanel.Invalidate();
}
}
I'm sure there can be many improvements to this code, but it should give you an idea on how to do it.
#Chris and #user10112654 are right.
here is a code similar to #Chris but isolates the displaying logic in a separate class. (#Chris answered your question when I was writing the code :))))
just create a 2D array to initialize the class and pass it to the Initialize method.
public class LedDisplayer
{
public LedDisplayer(Control control)
{
_control = control;
_control.MouseDown += MouseDown;
_control.Paint += Control_Paint;
// width and height of your tiny boxes
_width = 5;
_height = 5;
// margin between tiny boxes
_margin = 1;
}
private readonly Control _control;
private readonly int _width;
private readonly int _height;
private readonly int _margin;
private bool[,] _values;
// call this method first of all to initialize the Displayer
public void Initialize(bool[,] values)
{
_values = values;
_control.Invalidate();
}
private void MouseDown(object sender, MouseEventArgs e)
{
var firstIndex = e.X / OuterWidth();
var secondIndex = e.Y / OuterHeight();
_values[firstIndex, secondIndex] = !_values[firstIndex, secondIndex];
_control.Invalidate(); // you can use other overloads of Invalidate method for the blink problem
}
private void Control_Paint(object sender, PaintEventArgs e)
{
if (_values == null)
return;
e.Graphics.Clear(_control.BackColor);
for (int i = 0; i < _values.GetLength(0); i++)
for (int j = 0; j < _values.GetLength(1); j++)
Rectangle(i, j).Paint(e.Graphics);
}
private RectangleInfo Rectangle(int firstIndex, int secondIndex)
{
var x = firstIndex * OuterWidth();
var y = secondIndex * OuterHeight();
var rectangle = new Rectangle(x, y, _width, _height);
if (_values[firstIndex, secondIndex])
return new RectangleInfo(rectangle, Brushes.Red);
return new RectangleInfo(rectangle, Brushes.Black);
}
private int OuterWidth()
{
return _width + _margin;
}
private int OuterHeight()
{
return _height + _margin;
}
}
public class RectangleInfo
{
public RectangleInfo(Rectangle rectangle, Brush brush)
{
Rectangle = rectangle;
Brush = brush;
}
public Rectangle Rectangle { get; }
public Brush Brush { get; }
public void Paint(Graphics graphics)
{
graphics.FillRectangle(Brush, Rectangle);
}
}
this is how it's used in the form:
private void button2_Click(object sender, EventArgs e)
{
// define the displayer class
var displayer = new LedDisplayer(panel1);
// define the array to initilize the displayer
var display = new bool[,]
{
{true, false, false, true },
{false, true, false, false },
{false, false, true, false },
{true, false, false, false }
};
// and finally
displayer.Initialize(display);
}
my code for crate rectangle :
Rectangle[] myRectangle = new Rectangle[100];
for(int i=1;i<=100;i++)
{
myRectangle[i] = new Rectangle();
// another code for create myRectangle[i]
}
When MouseEnter Rectangle[X] and I want to do something.What will i do?
Thank you.
Putting this here;
Rectangle[] myRectangle = new Rectangle[100];
for(int i = 0; i < 100; i++)
{
myRectangle[i] = new Rectangle();
myRectangle[i].Tag = i;
myRectangle[i].MouseEnter += MouseEnter;
}
private void MouseEnter(object sender, MouseEventArgs e)
{
Rectangle rect = (Rectangle)sender;
int idx = (int)rect.Tag;
MessageBox.Show(idx.ToString());
}
I have a form set as the IsMdiContainer attribute to true.
I'm trying to make it in the container "snows".
I am trying to adapt a code that works well on a normal WinForms, but not good on MDI container. This is what I managed to do:
public partial class SnowTest : Form
{
int num;
int[] x;
int[] y;
int[] v;
int[] s;
Random random = new Random();
System.Drawing.Graphics graphics;
Rectangle rectangle;
private MdiClient MDIContainer;
private Graphics asd;
public SnowTest()
{
InitializeComponent();
}
private void SnowTest_Load(object sender, EventArgs e)
{
SelectMdiContainer();
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.UpdateStyles();
snow();
Timer1.Start();
}
private void SelectMdiContainer()
{
foreach (Control control in this.Controls)
{
// #2
MdiClient client = control as MdiClient;
if (!(client == null))
{
MDIContainer = client;
break;
}
}
}
private void snow()
{
num = 2000;
x = new int[num];
y = new int[num];
v = new int[num];
s = new int[num];
int i = 0;
for (i = 0; i <= num - 1; i++)
{
Insnow(i);
}
}
private void Insnow(int i)
{
x[i] = random.Next(0, this.Width - 1);
y[i] = random.Next(0, this.Height * 5 / 7);
v[i] = random.Next(5, 20);
s[i] = (random.Next(1, 3) * 100 + random.Next(50, 200)) / 100;
}
private void Timer1_Tick(object sender, EventArgs e)
{
for (int i = 0; i <= num - 1; i++)
{
y[i] = y[i] + v[i];
if (y[i] >= this.Height)
{
Insnow(i);
}
}
Invalidate();
SnowOnContainer();
}
private void SnowOnContainer()
{
graphics = MDIContainer.CreateGraphics();;
int i = 0;
for (i = 0; i <= num - 1; i++)
{
graphics.FillEllipse(Brushes.White, x[i], y[i], s[i], s[i]);
}
}
private void SnowTest_Resize(object sender, EventArgs e)
{
rectangle = new Rectangle(0, 0, this.Width, this.Height);
}
}
The Timer1 is set with a Interval of 100
The script runs and generates the "snow" but it does not remove it after a while and also the whole form creates lag.
What is the best way to achieve my goal?
In the normal form is pretty much structured in the same way, it lacks the function that select the container (SelectMdiContainer()), the function SnowOnContainer() and the global variable MDIContainer.
In the form, however, there is the paint event and consequently below carry as structured
private void SnowTest_Paint(object sender, PaintEventArgs e)
{
graphics = e.Graphics;
int i = 0;
for (i = 0; i <= num - 1; i++)
{
graphics.FillEllipse(Brushes.White, x[i], y[i], s[i], s[i]);
}
}
EDIT
I'm doing some tests. Currently I removed the function SnowOnContainer() and added the Paint event for the container.
private void SelectMdiContainer()
{
foreach (Control control in this.Controls)
{
// #2
MdiClient client = control as MdiClient;
if (!(client == null))
{
MDIContainer = client;
MDIContainer.Paint += OnMdiContainerPaint; //Added this Event
break;
}
}
}
The event is structured as that of the normal form but applied to the container
private void OnMdiContainerPaint(object sender, PaintEventArgs e)
{
graphics = MDIContainer.CreateGraphics();
int i = 0;
for (i = 0; i <= num - 1; i++)
{
graphics.FillEllipse(Brushes.White, x[i], y[i], s[i], s[i]);
}
}
Finally I did some research and I've added this code to avoid Flickering animation. as shown in this topic How to fix the flickering in User controls
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED
return cp;
}
}
In doing so it appears to be working properly with a fluid animation, but as soon as I resize the form, or I reduce to an icon returns lag
I am currently making a tile-based game in c#, but every time I draw the tiles it uses a lot of CPU, and as the tiles get bigger(if i make the game full screen) it consumes even more.
This is my Tile class:
public class Tiles
{
//PRIVATE :
//variabiles
private int XPosition, YPosition;
private Image Texture;
private bool Colidable;
private int SizeW = 32;
private int SizeH = 32;
private Resizer resizer = new Resizer();
//methods
//PUBLIC :
//variabiles
//methods
//CONSTRUCTOR
public Tiles(int _x,int _y,Image _i, int _sW = 32, int _sH = 32, bool _c = false)
{
XPosition = _x;//set position X
YPosition = _y;//set position Y
SizeW = _sW;
SizeH = _sH;
Texture = resizer.ResizeImage(_i, SizeW, SizeH) ;// set texture
Colidable = _c;//set if the tile is colidable,default : false
resizer = null;
}
//DRAW METHOD
//gets graphics object to draw on, adn draws at the position of the tile
public void Draw(Graphics _g)
{
_g.DrawImage(this.Texture, this.XPosition, this.YPosition);
}
//GET PRIVATE MEBERS
//returns if the tile is colidable
public bool getColidable()
{
return this.Colidable;
}
}
and this is how I draw the tiles:
private void DrawMap(Graphics _g)
{
//CALLS THE DRAW METHOD OF EACH TILE
for (int i = 0; i < MAP_WIDTH; i++)
{
for (int j = 0; j < MAP_HEIGHT; j++)
{
Tile[i, j].Draw(_g);
}
}
}
bool TilesUpdate = false;
private void _Window_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.Black);
if (isGameRunning)
{
DrawMap(e.Graphics);
}
else
{
FullRezolutionBtn.Draw(e.Graphics);
BigRezolutionBtn.Draw(e.Graphics);
NormalRezolutionBtn.Draw(e.Graphics);
}
}
private void Update_Tick(object sender, EventArgs e)
{
Invalidate();
}
I want to mention that the map is 20 x 20 tiles and it's cosuming around 50% of the cpu when it's fullscreen.
As I mentioned in the comments, the direction should be to do less painting. One way is to invalidate and paint portions of the drawing canvas only when something related to that portion changes. Windows itself does such optimization for controls/windows.
Here is an example. Look how Gadget class invalidates it's rectangle when some property changes. Then during the paint, only rectangles that intersect with e.ClipRectange are drawn. This highly reduces the number of the drawing operations.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Samples
{
class Gadget
{
public readonly Control Canvas;
public Gadget(Control canvas) { Canvas = canvas; }
private Rectangle bounds;
public Rectangle Bounds
{
get { return bounds; }
set
{
if (bounds == value) return;
// NOTE: Invalidate both old and new rectangle
Invalidate();
bounds = value;
Invalidate();
}
}
private Color color;
public Color Color
{
get { return color; }
set
{
if (color == value) return;
color = value;
Invalidate();
}
}
public void Invalidate()
{
Canvas.Invalidate(bounds);
}
public void Draw(Graphics g)
{
using (var brush = new SolidBrush(color))
g.FillRectangle(brush, bounds);
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form { WindowState = FormWindowState.Maximized };
int rows = 9, cols = 9;
var gadgets = new Gadget[rows, cols];
var rg = new Random();
Color[] colors = { Color.Yellow, Color.Blue, Color.Red, Color.Green, Color.Magenta };
int size = 64;
var canvas = form;
for (int r = 0, y = 8; r < rows; r++, y += size)
for (int c = 0, x = 8; c < cols; c++, x += size)
gadgets[r, c] = new Gadget(canvas) { Color = colors[rg.Next(colors.Length)], Bounds = new Rectangle(x, y, size, size) };
int paintCount = 0, drawCount = 0;
canvas.Paint += (sender, e) =>
{
paintCount++;
for (int r = 0; r < rows; r++)
{
for (int c = 0; c < cols; c++)
{
if (e.ClipRectangle.IntersectsWith(gadgets[r, c].Bounds))
{
gadgets[r, c].Draw(e.Graphics);
drawCount++;
}
}
}
form.Text = $"Paint:{paintCount} Draw:{drawCount} of {(long)paintCount * rows * cols}";
};
var timer = new Timer { Interval = 100 };
timer.Tick += (sender, e) =>
{
gadgets[rg.Next(rows), rg.Next(cols)].Color = colors[rg.Next(colors.Length)];
};
timer.Start();
Application.Run(form);
}
}
}
Not sure how your resizer class works. i think there is problem when you re-size every image every time.
Texture = resizer.ResizeImage(_i, SizeW, SizeH) ;// set texture
i would replace above line like this
Texture = _i;// set texture but do not resize image now
at the same time update the Draw Function of Tile like below.
public void Draw(Graphics _g)
{
//now specify the location and size of the image.
_g.DrawImage(Texture , new Rectangle(this.XPosition, this.YPosition, SizeW, SizeH));
}
hopefully it should improve the performance.
if it flicker then you can use Double Buffer
I have a problem with my C# WinForm project.
In my project I have a function to draw a square, and I have a function that makes buttons at run time. What I want to do is that the button will place on the square.
I try to use 2 arrays; one gets the x location of the square, and the other gets the y location.
The button is placed at the x and y location one by one in columns but its place them diagonal.
int[] locationx = new int[100];
int[] locationy = new int[100];
int monex = 0;
int money = 0;
private void DrawAllSquares()//z,k its many square its going to draw
{
int tempy = y;
for (int i = 0; i < z; i++)
{
DrawingSquares(x, y);
for (int j = 0; j < k - 1; j++)
{
locationy[money] = tempy;
money++;
tempy += 60;
DrawingSquares(x, tempy);
}
x += 120;
locationx[monex] = x;
monex++;
tempy = y;
}
}
private void button2_Click(object sender, EventArgs e)
{
Button myText = new Button();
myText.Tag = counter;
//changeplace();
myText.Location = new Point(locationx[monex2], locationy[money2]);
monex2++;
money2++;
buttonList.AddLast(myText);
myText.Text = Convert.ToString(textBox3.Text);
this.Controls.Add(myText);
buttons[counter] = myText;
myText.BringToFront();
counter++;
}
You need do add created button to Form Controls collection.
private void button2_Click(object sender, EventArgs e)
{
Button myText = new Button();
myText.Tag = counter;
myText.Location = new Point(locationx[monex2], locationy[money2]);
Controls.Add(myText); // Assuming that handler 'button2_Click' is in your Form class.
// rest of your code
}
EDIT:
Button myText = new Button();
myText.Click += button2_Click;