C# Snow effect on MDI Container - c#

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

Related

Problem with refreshing canvas in windows form application

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:

Resizing and positioning panels in another panel

I'm giving my panels inside another panel (this panel is in a usercontrol) a fixed location and a maximum size that changes with the size of the panel there in. Neither the resize or location works properly. The resize does happen but its to quickly. The location is fine if you only have 1 pinpanel for output and input. When you have more then 1 the locations are fixed but you need to resize the panel to resize to see the other panels. Could you point me in the right direction if you see the problem?
I have a panel drawPanel in this case that i use as a sort of background for the usercontrol. Inside this drawPanel i'm placing pinpanels. I want these pinpanels to resize with the usercontrol and give them a fixed location
private void OnClickPinPanel(object source, EventArgs e)
{
if (source is PinPanel p)
{
int index;
if ((index = Array.IndexOf(inputPins, p)) >= 0)
{
ClickedPinPanel?.Invoke(index, true);
}
else
{
ClickedPinPanel?.Invoke(Array.IndexOf(outputPins, p), false);
}
}
//else log here
}
private void CreatePinPanels(bool isInput)
{
int x = 0;
int y = -(int)(this.Width * 0.05)/2;
if (isInput)
{
for (int i = 0; i < inputPins.Length; i++)
{
y += (i + 1) * (this.Height / inputPins.Length + 1);
inputPins[i] = new PinPanel()
{
Location = new Point(x, y),
Size = new Size((int)(this.Width * 0.05), (int)(this.Width * 0.05)),
Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right,
};
inputPins[i].Click += OnClickPinPanel;
}
}
else
{
x += this.Width - (int)(this.Width * 0.1);
for (int i = 0; i < outputPins.Length; i++)
{
y += (i + 1) * (this.Height / inputPins.Length+1);
outputPins[i] = new PinPanel()
{
Size = new Size((int)(this.Width * 0.1), (int)(this.Width * 0.1)),
Location = new Point(x, y),
Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right
};
outputPins[i].Click += OnClickPinPanel;
}
}
}
The result i get now is that the pinpanels get a fixed location but when you have more then 1 pinpanel, the location is wrong its like if he thinks that the usercontrol is bigger then it is Reality. In order to see all the pins i have to resize and get this After resize
I want it to look like this
expectations
Try something like this out.
Here is my test rig:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
numericUpDown1.Value = someChip1.NumberInputPins;
numericUpDown2.Value = someChip1.NumberOutputPins;
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
someChip1.NumberInputPins = (int)numericUpDown1.Value;
}
private void numericUpDown2_ValueChanged(object sender, EventArgs e)
{
someChip1.NumberOutputPins = (int)numericUpDown2.Value;
}
}
Sample chip with 5 inputs and 3 outputs:
Here is my PinPanel UserControl (just draws an ellipse/pin the size of the control):
public partial class PinPanel : UserControl
{
public PinPanel()
{
InitializeComponent();
}
private void PinPanel_Paint(object sender, PaintEventArgs e)
{
Rectangle rc = new Rectangle(new Point(0, 0), new Size(this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1));
e.Graphics.DrawEllipse(Pens.Black, rc);
}
private void PinPanel_SizeChanged(object sender, EventArgs e)
{
this.Refresh();
}
}
And finally, my SomeChip UserControl:
public partial class SomeChip : UserControl
{
public event PinPanelClick ClickedPinPanel;
public delegate void PinPanelClick(int index, bool input);
private PinPanel[] inputPins;
private PinPanel[] outputPins;
private int _NumberInputPins = 2;
public int NumberInputPins
{
get {
return _NumberInputPins;
}
set
{
if (value > 0 && value != _NumberInputPins)
{
_NumberInputPins = value;
CreatePinPanels();
this.Refresh();
}
}
}
private int _NumberOutputPins = 1;
public int NumberOutputPins
{
get
{
return _NumberOutputPins;
}
set
{
if (value > 0 && value != _NumberOutputPins)
{
_NumberOutputPins = value;
CreatePinPanels();
this.Refresh();
}
}
}
public SomeChip()
{
InitializeComponent();
}
private void SomeChip_Load(object sender, EventArgs e)
{
CreatePinPanels();
RepositionPins();
}
private void SomeChip_SizeChanged(object sender, EventArgs e)
{
this.Refresh();
}
private void SomeChip_Paint(object sender, PaintEventArgs e)
{
int PinHeight;
// draw the input pin runs
if (inputPins != null)
{
PinHeight = (int)((double)this.Height / (double)_NumberInputPins);
for (int i = 0; i < NumberInputPins; i++)
{
int Y = (i * PinHeight) + (PinHeight / 2);
e.Graphics.DrawLine(Pens.Black, 0, Y, this.Width / 4, Y);
}
}
// draw the output pin runs
if (outputPins != null)
{
PinHeight = (int)((double)this.Height / (double)_NumberOutputPins);
for (int i = 0; i < NumberOutputPins; i++)
{
int Y = (i * PinHeight) + (PinHeight / 2);
e.Graphics.DrawLine(Pens.Black, this.Width - this.Width / 4, Y, this.Width, Y);
}
}
//draw the chip itself (takes up 50% of the width of the UserControl)
Rectangle rc = new Rectangle(new Point(this.Width / 4, 0), new Size(this.Width / 2, this.Height - 1));
using (SolidBrush sb = new SolidBrush(this.BackColor))
{
e.Graphics.FillRectangle(sb, rc);
}
e.Graphics.DrawRectangle(Pens.Black, rc);
RepositionPins();
}
private void CreatePinPanels()
{
if (inputPins != null)
{
for (int i = 0; i < inputPins.Length; i++)
{
if (inputPins[i] != null && !inputPins[i].IsDisposed)
{
inputPins[i].Dispose();
}
}
}
inputPins = new PinPanel[_NumberInputPins];
for (int i = 0; i < inputPins.Length; i++)
{
inputPins[i] = new PinPanel();
inputPins[i].Click += OnClickPinPanel;
this.Controls.Add(inputPins[i]);
}
if (outputPins != null)
{
for (int i = 0; i < outputPins.Length; i++)
{
if (outputPins[i] != null && !outputPins[i].IsDisposed)
{
outputPins[i].Dispose();
}
}
}
outputPins = new PinPanel[_NumberOutputPins];
for (int i = 0; i < outputPins.Length; i++)
{
outputPins[i] = new PinPanel();
outputPins[i].Click += OnClickPinPanel;
this.Controls.Add(outputPins[i]);
}
}
private void OnClickPinPanel(object sender, EventArgs e)
{
PinPanel p = (PinPanel)sender;
if (inputPins.Contains(p))
{
ClickedPinPanel?.Invoke(Array.IndexOf(inputPins, p), true);
}
else if (outputPins.Contains(p))
{
ClickedPinPanel?.Invoke(Array.IndexOf(inputPins, p), false);
}
}
private void RepositionPins()
{
int PinRowHeight, PinHeight;
if (inputPins != null)
{
PinRowHeight = (int)((double)this.Height / (double)_NumberInputPins);
PinHeight = (int)Math.Min((double)(PinRowHeight / 2), (double)this.Height * 0.05);
for (int i = 0; i < inputPins.Length; i++)
{
if (inputPins[i] != null && !inputPins[i].IsDisposed)
{
inputPins[i].SetBounds(0, (int)((i * PinRowHeight) + (PinRowHeight /2 ) - (PinHeight / 2)), PinHeight, PinHeight);
}
}
}
if (outputPins != null)
{
PinRowHeight = (int)((double)this.Height / (double)_NumberOutputPins);
PinHeight = (int)Math.Min((double)(PinRowHeight / 2), (double)this.Height * 0.05);
for (int i = 0; i < outputPins.Length; i++)
{
if (outputPins[i] != null && !outputPins[i].IsDisposed)
{
outputPins[i].SetBounds(this.Width - PinHeight, (int)((i * PinRowHeight) + (PinRowHeight / 2) - (PinHeight / 2)), PinHeight, PinHeight);
}
}
}
}
}

how can I split a panel to clickable segments in c# winform?

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

PrintDocument HasMorePages don't work

I want to print the data from data grid. The code works well for the first page, but the commented lines do not works well and don't move to next page. Can anyone help fix this?
private void DrawFactorA4(object sender, PrintPageEventArgs ev)
{
for (int j = 0; j < GrdRDocument.Rows.Count; j++)
{
i += 2;
//draw data grid
s++;
if(s == 10)
{
//ev.HasMorePages = true; //this line doesn't work
s = 0;
i = 0;
}
else
{
ev.HasMorePages = false;
}
}
}
_
private void BtnPrint_Click(object sender, EventArgs e)
{
printFont = new Font("Arial", 12);
IEnumerable<PaperSize> paperSizes =
pd.PrinterSettings.PaperSizes.Cast<PaperSize>();
sizeA4 = paperSizes.First<PaperSize>(size => size.Kind == PaperKind.A4);
pd.DefaultPageSettings.Landscape = true;
pd.DefaultPageSettings.PaperSize = sizeA4;
pd.PrintPage += new PrintPageEventHandler(this.DrawFactorA4);
printPreviewDialog.Document = pd;
printPreviewDialog.ShowDialog();
}
Stop and read what you have:
printFont = new Font("Arial", 12);
Fonts are unmanaged resources; here you're instantating one and never disposing it. Maybe that's harmless in this particular situation, but this is a bad habit to get in to.
pd.PrintPage += new PrintPageEventHandler(this.DrawFactorA4);
DrawFactorA4 is going to be called for every page in your document. Inside DrawFactorA4:
for (int j = 0; j < GrdRDocument.Rows.Count; j++)
You iterate through every Row in GrdRDocument, regardless the number of rows or the size of your page. That is wrong; you have to stop after the page is filled. By the way, I hope GrdRDocument is a local copy of immutable data and you're not passing UI controls to the printing thread.
s++;
if(s == 10)
{
//ev.HasMorePages = true; //this line doesn't work
s = 0;
Your commented line would work fine. The problem is you set ev.HasMorePages = true and then ignore it; you set s = 0 and keep iterating; next iteration s!=10 so you:
ev.HasMorePages = false;
Read the PrintDocument docs; it has an example of printing more than one page. You should create a class to store all the unmanaged resources and page state. Make it IDisposable so they get disposed. Iterate through only the rows or whatever you want to print on each page. Something like:
class PrintStuff : IDisposable
{
readonly IEnumerable<Whatever> data;
readonly PrintDocument pd;
Font font;
private int currentIndex;
public PrintStuff(IEnumerable<Whatever> data)
{
this.data = data;
pd = new PrintDocument();
pd.BeginPrint += OnBeginPrint;
pd.PrintPage += OnPrintPage;
pd.EndPrint += OnEndPrint;
}
public void Print()
{
pd.Print();
}
public void Dispose()
{
pd.Dispose();
}
private void OnBeginPrint(object sender, PrintEventArgs args)
{
font = new Font(FontFamily.GenericSansSerif, 12F);
currentIndex = 0;
}
private void OnEndPrint(object sender, PrintEventArgs args)
{
font.Dispose();
}
private void OnPrintPage(object sender, PrintPageEventArgs args)
{
var x = Convert.ToSingle(args.MarginBounds.Left);
var y = Convert.ToSingle(args.MarginBounds.Top);
var lineHeight = font.GetHeight(args.Graphics);
while ((currentIndex < data.Count())
&& (y <= args.MarginBounds.Bottom))
{
args.Graphics.DrawWhatever(data.ElementAt(currentIndex), font, Brushes.Black, x, y);
y += lineHeight;
currentIndex++;
}
args.HasMorePages = currentIndex < data.Count();
}
}

Trouble with memory in C#

I want to draw a loading circle (.gif) on the screen, while the applications is loading something big. But I can't run the circle as fast as I want, because I get memory issues. Does anyone know how to solve these (on 75 ms instead of 1000ms)? And how to remove the circle when done (it doesn't disappear any more).
EDIT: The load function is executed when the window size changes.
public Main()
{
InitializeComponent();
StartUp();
WindowState = FormWindowState.Maximized;
}
bool onrun;
bool done;
System.Threading.Timer timer;
GifImage Circle;
Point center;
void StartUp()
{
onrun = true;
done = false;
timer = new System.Threading.Timer(new System.Threading.TimerCallback(Animate));
timer.Change(0, 500);
}
void Animate(object sender)
{
if (onrun == true)
{
Circle = new GifImage("circleAnim.gif");
Circle.ReverseAtEnd = false;
int width = Screen.PrimaryScreen.Bounds.Width;
int height = Screen.PrimaryScreen.Bounds.Height;
center = new Point((width / 2) - 150, (height / 2) - 150);
onrun = false;
}
else if (done == true)
{
timer.Dispose();
}
Image i = Circle.GetNextFrame();
System.Drawing.Graphics GraphicsObject = Graphics.FromHwnd(IntPtr.Zero);
try
{
GraphicsObject.DrawImage(i, center);
i.Dispose();
GraphicsObject.Dispose();
}
catch { }
}
private void Load(object sender, EventArgs e)
{
int hduizend = 100000;
for (int i = 1; i <= 100000000; i++)
{
hduizend /= 2;
hduizend *= 2;
}
done = true;
}
EDIT2 (error):
"Attempted to read or write protected memory. This is often an indication that other memory is corrupt".
Also the gif image itself is not the problem:
public class GifImage
{
private Image gifImage;
private FrameDimension dimension;
private int frameCount;
private int currentFrame = -1;
private bool reverse;
private int step = 1;
public GifImage(string path)
{
gifImage = Image.FromFile(path);
dimension = new FrameDimension(gifImage.FrameDimensionsList[0]);
frameCount = gifImage.GetFrameCount(dimension);
}
public bool ReverseAtEnd
{
get { return reverse; }
set { reverse = value; }
}
public Image GetNextFrame()
{
currentFrame += step;
if (currentFrame >= frameCount || currentFrame < 1)
{
if (reverse)
{
step *= -1;
currentFrame += step;
}
else
currentFrame = 0;
}
return GetFrame(currentFrame);
}
public Image GetFrame(int index)
{
gifImage.SelectActiveFrame(dimension, index);
return (Image)gifImage.Clone();
}
}
assign the gif to a PictureBox and just show the Picturebox when needed. It will take care of the animation. You just need to take care of when it needs to be shown and of positioning it then.
private PictureBox pictureBox = new PictureBox();
Image animatedPicture = Image.FromFile(path);
...
int width = Screen.PrimaryScreen.Bounds.
int height = Screen.PrimaryScreen.Bounds.Height;
center = new Point((width / 2) - 150, (height / 2) - 150);
pictureBox.Location = center;
pictureBox.Visible = true;

Categories