I can't destroy InkEdit without memory leaks. The Dispose method helps a little, but still, a lot of unmanaged resources are not freed.
// Create 16 InkEdit components
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
var ie = new InkEdit { Left = x * 50, Top = y * 50, Width = 40, Height = 40 };
panel1.Controls.Add(ie);
}
}
The example was modified to dispose all controls correctly.
// 46 MiB of leaked RAM after every Create/Dispose iteration
while (panel1.Controls.Count > 0)
{
var lastIndex = panel1.Controls.Count - 1;
var control = panel1.Controls[lastIndex];
panel1.Controls.RemoveAt(lastIndex);
control.Dispose();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
With GC.Collect:
Without GC.Collect:
Full sample:
https://github.com/dermeister0/InkEditTest
I used SimpleHelpers.ObjectPool package to reuse the controls.
SimpleHelpers.ObjectPool<InkEdit>.MaxCapacity = 16;
protected override void Dispose(bool disposing)
{
if (disposing)
{
SimpleHelpers.ObjectPool<InkEdit>.Clear();
components?.Dispose();
}
base.Dispose(disposing);
}
private void btnCreate_Click(object sender, EventArgs e)
{
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
InkEdit ie = SimpleHelpers.ObjectPool<InkEdit>.Get(() => new InkEdit());
ie.Left = x * 50;
ie.Top = y * 50;
ie.Width = 40;
ie.Height = 40;
panel1.Controls.Add(ie);
}
}
}
private void btnDisposeAndClear_Click(object sender, EventArgs e)
{
for (int i = panel1.Controls.Count - 1; i >= 0; i--)
{
var control = panel1.Controls[i] as InkEdit;
SimpleHelpers.ObjectPool<InkEdit>.Put(control);
}
}
Related
I have a 2d array of LEDButton : Button.
I want to find out the index [x,y] of each buttons the user clicks.
I am new to Windows Forms and not used to working outside of a console so these GUI objects are very unfamiliar to me.
private void Form1_Load(object sender, EventArgs e)
{
LEDButton[,] leds = new LEDButton[11, 11];
for (int x = 0; x < leds.GetUpperBound(0); x++)
{
listBox1.Items.Add("x = " + x);
for (int y = 0; y < leds.GetUpperBound(1); y++)
{
leds[x, y] = new LEDButton()
{
Name = String.Format("Button{0}{1}", x, y),
TabIndex = 40 * x + y,
Location = new Point(40 * y + 50, 40 * x + 50)
};
leds[x, y].pointx = x;
leds[x, y].pointy = y;
}
}
// add buttons to controls
for (int x = 0; x < leds.GetUpperBound(0); x++)
{
for (int y = 0; y < leds.GetUpperBound(1); y++)
{
Controls.Add(leds[x, y]);
leds[x, y].Click += Form1_Click;
}
}
public class LEDButton : Button
{
public const int LEDWidth = 20;
public const int LEDHeight = 20;
public int pointx = 0;
public int pointy = 0;
public LEDButton()
{
BackColor = Color.FromArgb(0, 64, 0);
ForeColor = Color.Black;
FlatStyle = FlatStyle.Flat;
Size = new Size(LEDWidth, LEDHeight);
UseVisualStyleBackColor = false;
this.Click += LEDButton_Click; //throws error
}
}
I think I found my answer with the help of Lars.
Code should be
private void Form1_Click(object? sender, EventArgs e)
{
LEDButton btn = sender as LEDButton;
listBox2.Items.Add(btn.Name);
}
I'm beginner in programming and I want to create a little "game" with Panels.
(Later maybe I'll change for PictureBox, but for now it's OK)
Code:
private void Form1_Load(object sender, EventArgs e)
{
int size = 20;
int quantity = 10;
Random rnd = new Random();
for (int y = 0; y < quantity; y++)
{
for (int x = 0; x < quantity; x++)
{
Color randomColor = Color.FromArgb(
rnd.Next(256), rnd.Next(256), rnd.Next(256)
);
Panel panel = new Panel
{
Size = new Size(size, size),
Location = new Point(x * size, y * size),
BorderStyle = BorderStyle.FixedSingle,
BackColor = randomColor
};
Controls.Add(panel);
//panel.Click += Panel_Click;
}
}
}
I have two questions:
How can I set 5 pixel distance to each Panel from each other?
Should I place these panel creation in the constructor? I see people prefer that.
You have to include additional 5 pixels padding into Location:
...
int padding = 5;
...
Location = new Point(x * (size + padding), y * (size + padding))
...
Let's extract a method:
private void CreateGameField() {
int size = 20;
int padding = 5;
int quantity = 10;
Random rnd = new Random();
for (int y = 0; y < quantity; ++y)
for (int x = 0; x < quantity; ++x)
new Panel() {
Size = new Size(size, size),
Location = new Point(x * (size + padding), y * (size + padding)),
BorderStyle = BorderStyle.FixedSingle,
BackColor = Color.FromArgb(rnd.Next(256), rnd.Next(256), rnd.Next(256)),
Parent = this, // instead of Controls.Add(panel);
};
}
Then
private void Form1_Load(object sender, EventArgs e) {
CreateGameField();
}
FromLoad event handler is a good place to create the game field; if you want you can place CreateGameField() in the constructor, but put it after InitializeComponent():
public Form1() {
InitializeComponent();
CreateGameField();
}
I'm creating a custom control based on a Panel control and using it to draw a grid of rectangles inside of it. I have made the Columns and Rows as properties to allow them to be changed once the control is added to a form. However if the values are changed in the designer or in the form code the rectangles are never drawn and the form appears blank. I've tried different suggestions such as this.Invalidate() and this.Refresh() in the set portion of the property but I still can't seem to get it to paint the new rectangle grid. I'm sure that I'm just missing something somewhere. Here's the code I'm using:
public class Pixel
{
public Rectangle Bounds { get; set; }
public bool IsOn { get; set; }
public bool IsSelected { get; set; }
}
public class PixelGridControl : Panel
{
private int columns = 99;
private int rows = 63;
private Pixel[,] pixels;
private bool leftMouseIsDown = false;
private bool rightMouseIsDown = false;
public int Columns
{
get { return columns; }
set
{
if (value > 0)
{
columns = value;
CreatePixelGrid();
this.Refresh();
}
else
{
throw new ArgumentOutOfRangeException("Columns", "Must be > 0");
}
}
}
public int Rows
{
get { return rows; }
set
{
if (value > 0)
{
rows = value;
CreatePixelGrid();
this.Refresh();
}
else
{
throw new ArgumentOutOfRangeException("Rows", "Must be > 0");
}
}
}
public PixelGridControl()
{
this.DoubleBuffered = true;
this.ResizeRedraw = true;
CreatePixelGrid();
}
// adjust each column and row to fit entire client area:
protected override void OnResize(EventArgs e)
{
int top = 0;
for (int y = 0; y < Rows; ++y)
{
int left = 0;
int height = (this.ClientSize.Height - top) / (Rows - y);
for (int x = 0; x < Columns; ++x)
{
int width = (this.ClientSize.Width - left) / (Columns - x);
pixels[x, y].Bounds = new Rectangle(left, top, width, height);
left += width;
}
top += height;
}
base.OnResize(e);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
for (int y = 0; y < Rows; ++y)
{
for (int x = 0; x < Columns; ++x)
{
if (pixels[x, y].IsOn)
{
e.Graphics.FillEllipse(Brushes.Gold, pixels[x, y].Bounds);
e.Graphics.DrawEllipse(Pens.Goldenrod, pixels[x, y].Bounds);
}
else
{
e.Graphics.FillRectangle(Brushes.Black, pixels[x, y].Bounds);
e.Graphics.DrawRectangle(Pens.White, pixels[x, y].Bounds);
}
}
}
base.OnPaint(e);
}
private void CreatePixelGrid()
{
// initialize pixel grid:
pixels = new Pixel[Columns, Rows];
for (int y = 0; y < Rows; ++y)
{
for (int x = 0; x < Columns; ++x)
{
pixels[x, y] = new Pixel();
}
}
}
}
The problem is that you don't populate grid cell bounds when Rows or Columns change, thus they remain Rectangle.Empty.
To fix that, move the code from OnResize to a separate method:
private void UpdatePixelGrid()
{
// adjust each column and row to fit entire client area:
int top = 0;
for (int y = 0; y < Rows; ++y)
{
int left = 0;
int height = (this.ClientSize.Height - top) / (Rows - y);
for (int x = 0; x < Columns; ++x)
{
int width = (this.ClientSize.Width - left) / (Columns - x);
pixels[x, y].Bounds = new Rectangle(left, top, width, height);
left += width;
}
top += height;
}
}
and call it from both OnResize and CreatePixelGrid:
protected override void OnResize(EventArgs e)
{
UpdatePixelGrid();
base.OnResize(e);
}
private void CreatePixelGrid()
{
// initialize pixel grid:
pixels = new Pixel[Columns, Rows];
for (int y = 0; y < Rows; ++y)
{
for (int x = 0; x < Columns; ++x)
{
pixels[x, y] = new Pixel();
}
}
UpdatePixelGrid();
}
So i'm basically attempting to create a grid out of pictureboxes through a for loop using the following code:
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i <= 5; i++)
{
for (int j = 0; j <= 5; j++)
{
PictureBox tile = new PictureBox() ;
tile.Size = new Size(49, 49);
tile.BackColor = Color.Firebrick;
tile.Location = new Point((100 + (50 * i)), (100 + (50 * j)));
Debug.WriteLine("PB created with index ["+i+","+j+"]");
}
}
}
But during runtime, the form appears blank, with no pictureboxes generated at all.
What did I do wrong?
You're not adding the control you just created to the form's Controls collection.
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i <= 5; i++)
{
for (int j = 0; j <= 5; j++)
{
PictureBox tile = new PictureBox() ;
tile.Size = new Size(49, 49);
tile.BackColor = Color.Firebrick;
tile.Location = new Point((100 + (50 * i)), (100 + (50 * j)));
Debug.WriteLine("PB created with index ["+i+","+j+"]");
// The control needs to be added to the form's Controls collection
Controls.Add(tile);
}
}
}
How do I use a background worker in this for loop?
int tmax = 10;
int xmax = newbitmap.Width;
int ymax = newbitmap.Height;
for (int t = 0; t <= tmax; t += 1)
{
for (int x = 0; x < xmax; x++)
{
for (int y = 0; y < ymax; y++)
{
if ((x / xmax) > (t / tmax))
{
Color originalco = newbitmap2.GetPixel(x, y);
outp.SetPixel(x, y, originalco);
}
else
{
Color originalco3 = newbitmap.GetPixel(x, y); ;
outp.SetPixel(x, y, originalco3);
}
}
pictureBox1.Image = outp;
}
}
This loop is a wipe transition from right to left, but it doesn't display the transition.
That because the backgroundWorker works in a different thread. You can use backgroundworker.ReportProgress(0, outp)
So:
You need to register to the event BackgroundWorker.ProgressChanged from the events window in Vistual Studio, or with this line:
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
The method:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var outp = (Bitmap)e.UserState;
prictureBox.Image = outp;
}
Your code sould be then:
int tmax = 10;
int xmax = newbitmap.Width;
int ymax = newbitmap.Height;
for (int t = 0; t <= tmax; t += 1)
{
for (int x = 0; x < xmax; x++)
{
for (int y = 0; y < ymax; y++)
{
if ((x / xmax) > (t / tmax))
{
Color originalco = newbitmap2.GetPixel(x, y);
outp.SetPixel(x, y, originalco);
}
else
{
Color originalco3 = newbitmap.GetPixel(x, y); ;
outp.SetPixel(x, y, originalco3);
}
}
backgroundWorker1.ReportProgress(t, outp);
}
}
First, you should use direct pixel manipulation as described here: http://msdn.microsoft.com/en-us/library/5ey6h79d.aspx
Then, you should use an array as lookup for all your threads which line has already been drawn and which hasn't. The threads look for a new line in this array and then draw it. But remember to lock the lookup array!
Your Do work method would look something like this: -
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
Bitmap newbitmap = (Bitmap)e.Argument;
int tmax = 10;
int xmax = newbitmap.Width;
int ymax = newbitmap.Height;
for (int t = 0; t <= tmax; t += 1)
{
for (int x = 0; x < xmax; x++)
{
for (int y = 0; y < ymax; y++)
{
if ((x / xmax) > (t / tmax))
{
Color originalco = newbitmap2.GetPixel(x, y);
outp.SetPixel(x, y, originalco);
}
else
{
Color originalco3 = newbitmap.GetPixel(x, y); ;
outp.SetPixel(x, y, originalco3);
}
}
pictureBox1.Image = outp;
}
}
bgWorker.ReportProgress(0,outp);
}
Then when your worker reports progress, it would raise the following event where you can safely update the UI:
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//UPDATE YOUR UI HERE
}
You can use the ReportProgress method of Background Worker to update the UI.
Read more