Today im doing the pixels in the paint event to blink.
In form1 i have this code in a timer tick event that the interval is set to 1000ms.
private void timer1_Tick(object sender, EventArgs e)
{
CloudEnteringAlert.cloudColorIndex = (CloudEnteringAlert.cloudColorIndex + 1) % CloudEnteringAlert.cloudColors.Length;
pictureBox1.Invalidate();
}
In the CloudEntering class i have on the top:
public static Brush[] cloudColors = new[] { Brushes.Yellow, Brushes.Transparent };
Then in a paint method this paint method im calling from the form1 pictureBox1 paint event:
foreach (PointF pt in clouds)
{
e.FillEllipse(cloudColors[cloudColorIndex], pt.X * (float)currentFactor, pt.Y * (float)currentFactor, 7f, 7f);
}
So what i see now is one second the pixels are in yellow and one second the pixels are in Transparent.
Now what i want to do is:
Each pixel will start from radius 5.
Each pixel radius will get bigger to 25.
The animation will be from the top to the bottom.
Each pixel that got to radius 25 will start to get smaller back to radius 5.
When the first pixel is started from 5 will get to radius 15 the next one will start to get bigger.
So if the first pixel is start at 5 now its 15 the next one will start to get bigger and the first one will continue to 25 when the first one is 25 it will get smaller. The second one when its 15 the third one will get bigger and so on.
All the pixels will start at radius 5 but only the first one will start get bigger get to 15 then the next one and when the first is 25 it will get smaller .
This is the time between each pixel.
And each pixel radius size change should take 300ms !
How can i do it ?
You want to start by encapsulating all information needed to render a single ellipse into a separate class. That would be something like:
public class Ellipse
{
public PointF Center { get; set; }
public Brush Brush { get; set; }
public float Diameter { get; set; }
public float DiameterDelta { get; set; }
public Ellipse(float x, float y)
{
Center = new PointF(x, y);
Brush = Brushes.Blue;
Diameter = 5;
DiameterDelta = 1;
}
}
The Ellipse.DiameterDelta property is the delta value which will be used for animation, and it can be positive (when going from diameter 5 to diameter 25), or negative (when going backwards). The value of this property (I've used 1 above) together with the your Timer.Interval will influence the speed of your animation.
A better OOP design would probably advocate moving animation-related properties out of this class, but for simplicity sake, it's better to start with this.
In your timer event, you might have something like:
private void timer_Tick(object sender, EventArgs e)
{
// presuming that you made a separate user control
// which has a collection of ellipses in a `Clouds` property
foreach (var c in cloudBox.Clouds)
Animate(c);
cloudBox.Invalidate();
}
private void Animate(Ellipse c)
{
// update diameter
c.Diameter += c.DiameterDelta;
// when you reach bounds, change delta direction
if ((c.DiameterDelta < 0 && c.Diameter <= 5) ||
(c.DiameterDelta > 0 && c.Diameter >= 25))
c.DiameterDelta = -c.DiameterDelta;
}
To get a smooth, non flickering animation, you will most likely have to use a custom control with ControlStyles.AllPaintingInWmPaint and ControlStyles.OptimizedDoubleBuffer set in its constructor, so instead of a PictureBox, I would do something like:
public partial class CloudBox : UserControl
{
public CloudBox()
{
InitializeComponent();
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.ResizeRedraw, true);
}
private readonly List<Ellipse> _clouds = new List<Ellipse>();
public List<Ellipse> Clouds
{
get { return _clouds; }
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
foreach (var cloud in _clouds)
{
e.Graphics.FillEllipse(
cloud.Brush, cloud.Center.X, cloud.Center.Y,
cloud.Diameter, cloud.Diameter);
}
base.OnPaint(e);
}
}
I haven't tested this, but I believe you'll be able to fill in the details. Setting Timer.Interval to 10 or 20 ms should yield a pretty fluid animation IMHO.
[Edit] To instantiate the whole thing with some test data (I don't know where you get it from), you could use something like this in your Form.Load event handler:
for (int i = 0; i < 400; i += 50)
for (int j = 0; j < 400; j += 50)
cloudBox.Clouds.Add(new Ellipse(i, j));
Related
My program can draw lines using canvas.Drawline(). How to click line and change this color (select line)?
private List<Point> coordFirst = new List<Point>();
private List<Point> coordLast = new List<Point>();
public Graphics canvas;
private void Form1_Load(object sender, EventArgs e)
{
canvas=panel1.CreateGraphics();
}
Coordinate line stored in coordFirs & coodLast.
Here is a suitable Line class:
class Line
{
public Color LineColor { get; set; }
public float Linewidth { get; set; }
public bool Selected { get; set; }
public Point Start { get; set; }
public Point End { get; set; }
public Line(Color c, float w, Point s, Point e)
{ LineColor = c; Linewidth = w; Start = s; End = e; }
public void Draw(Graphics G)
{ using (Pen pen = new Pen(LineColor, Linewidth)) G.DrawLine(pen, Start, End); }
public bool HitTest(Point Pt)
{
// test if we fall outside of the bounding box:
if ((Pt.X < Start.X && Pt.X < End.X) || (Pt.X > Start.X && Pt.X > End.X) ||
(Pt.Y < Start.Y && Pt.Y < End.Y) || (Pt.Y > Start.Y && Pt.Y > End.Y))
return false;
// now we calculate the distance:
float dy = End.Y - Start.Y;
float dx = End.X - Start.X;
float Z = dy * Pt.X - dx * Pt.Y + Start.Y * End.X - Start.X * End.Y;
float N = dy * dy + dx * dx;
float dist = (float)( Math.Abs(Z) / Math.Sqrt(N));
// done:
return dist < Linewidth / 2f;
}
}
Define a List for the lines, probably at class level:
List<Line> lines = new List<Line>();
Here is how you can initialize it with a few lines:
for (int i = 0; i < 20; i++) lines.Add(new Line(Color.Black, 4f,
new Point(R.Next(panel1.Width), R.Next(panel1.Height)),
new Point(R.Next(panel1.Width), R.Next(panel1.Height))));
Here is the result of clicking on a crossing:
Whenever you add, change or remove a line you need to make the Panel reflect the news by triggering the Paint event:
panel1.Invalidate();
Here is the Paint event of the Panel:
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
foreach (Line L in lines) L.Draw(e.Graphics);
}
In the MouseClick event you do the test:
private void panel1_MouseClick(object sender, MouseEventArgs e)
{
foreach(Line L in lines)
L.LineColor = L.HitTest(e.Location) ? Color.Red : Color.Black;
panel1.Invalidate();
}
To avoid flicker don't use the basic Panel class as it isn't doublebuffered. Instead use either a PictureBox or a Label (with AutoSize=false) or a doublebuffered Panel subclass:
class DrawPanel : Panel
{ public DrawPanel () { DoubleBuffered = true; } }
Notes:
There is no such thing as a 'Line' in WinForms, only pixels of various colors. So to select a line you need to store it's two endpoints' coordinates and then find out if you have hit it when clicking.
The above example shows how to do it in math.
Instead one could test each line by drawing it onto a bitmap and test the pixel the mouse has clicked. But drawing those bitmaps would have to do math behind the scenes as well and also allocate space for the bitmaps, so the math will be more efficient..
Yes the Line class looks a little long for such a simple thing a s a line but look how short all the event codes now are! That's because the responsiblities are where they belong!
Also note the the first rule of doing any drawing in WinForms is: Never cache or store a Grahics object. In fact you shouldn't ever use CreateGraphics in the first place, as the Graphics object will never stay in scope and the graphics it produces will not persist (i.e. survive a Minimize-maximize sequence)..
Also note how I pass out the e.Graphics object of the Paint event's parameters to the Line instances so they can draw themselves with a current Graphics object!
To select thinner lines it may help to modify the distance check a little..
The Math was taken directly form Wikipedia.
You can change the color of everything on click. By using click event of particular object.
I give you an example for button. If you click on button then panal’s color will be change. You can modify the code as per your requirement.
private List<Point> coordFirst = new List<Point>();
private List<Point> coordLast = new List<Point>();
public Graphics canvas;
private void Form1_Load(object sender, EventArgs e)
{
canvas = panel1.CreateGraphics();
}
private void panel1_Click(object sender, EventArgs e)
{
panel1.BackColor = Color.Blue;
}
private void nonSelectableButton3_Click(object sender, EventArgs e)
{
panel1.BackColor = Color.BurlyWood;
}
I am trying to make a triangle move back and forth over an arc, the triangle shoud rotate while moving.
I have made a picture to explain it better:
https://app.box.com/s/mt9p66zlmtkkgkdvtb5h
The math looks right to me, can anyone tell me what I am doing wrong?
public partial class Form1 : Form
{
bool turn = false;
double angle = 0;
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush solidBlackBrush = new SolidBrush(Color.Black); //En solid svart brush som brukes flere steder
Pen solidBackPen = new Pen(solidBlackBrush);//En solid svart pen som brukes flere steder
//Trekant = Norwegian for Triangle, Trekant is a class that draws a polygon shaped as a Triangle.
Trekant tre = new Trekant();
e.Graphics.DrawArc(solidBackPen, new Rectangle(new Point(50,50), new Size(100,100)) , 180, 180);
//X = a + r*Cos(angle) | Y = b + r*Sin(angle)
double x = (50+(100/2)) + (100/2) * Math.Cos(Trekant.DegreeToRadian(angle));
double y = (50+(100/2)) - (100/2) * Math.Sin(Trekant.DegreeToRadian(angle));
e.Graphics.TranslateTransform((float)x - 15, (float)y - 40);//Flytter 0 slik at pistolen havner på rett sted
e.Graphics.RotateTransform((float)-Trekant.RadianToDegree(Trekant.DegreeToRadian(angle-90)));
tre.Draw(e.Graphics);
}
private void timer1_Tick(object sender, EventArgs e)
{
if (angle == 0)
{
turn = false;
}
if (angle == 180)
{
turn = true;
}
if (turn)
{
angle -= 10;
}
if (!turn)
{
angle += 10;
}
this.Invalidate();
}
}
Without going into coding let's first set up the math..
Let say the half ellipse in the picture has a width of 2w and a height of h. And lets assume you want the movement to happen in n steps.
Then at each step s the rotation angle is s * 180f/n. The rotation point's x stays at w plus whatever offset ox the ellipse has, but will have to move its y vertically from offset oy, first by (w-h) * 2f / n down on each step and then up again by the same amounts..
The Drawing itself moves accordingly.
So you have a TranslateTransform for the rotation point, the RotateTransform, then another TranslateTransform to place the image, then the DrawImage and finally a ResetTransform.
I hope that helps. If that doesn't work, please update the question and we'll can get it right, I'm sure..
I am trying to make a elipse bounce on a rectangle. I am using a timer to move the elipse in both x and y-direction. My idea was to create an if statement to see if the coordinates of the elipse matches the coordinates of the rectangle.
Here is the code I had written so far:
public partial class Form1 : Form
{
Class1 square = new Class1();
public int before;
public int after;
public int c;
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
after = 590 - (b * b) / 100;
before = 100 + (a * a) / 100;
c = a + b;
Graphics g = e.Graphics;
SolidBrush Brush = new SolidBrush(Color.White);
g.FillEllipse(Brush, a, before, 10, 10);
square.Draw(g);
if (k >= square.y && a >= square.x && a <= square.x + 40)
{
a=c;
before= after;
timer1.Start();
timer2.Stop();
}
else if (k >= square.y + 10)
{
timer2.Stop();
MessageBox.Show("You lost");
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
square.x = e.X;
Cursor.Hide();
}
public int a = 0;
public int b = 0;
public void timer2_Tick(object sender, EventArgs e)
{
a += 1;
Invalidate();
}
private void timer1_Tick(object sender, EventArgs e)
{
b += 1;
Invalidate();
}
}
I know that there are problems. And I have some questions:
Is there a easier way to make the elipse " bounce"?
Is the problem solely with the maths of the curve that the elipse is following?
I know the question may be somewhat undefined or abstract but any help is appriciated. And if you want me to be clearer in some ways, let me know! Thanks
A simple way to make the ellipse bounce of the edges is to check its edge points against the bounds, and then just invert the proper direction. So, something like this should work, if you'll pardon the pseudo-code
loop used to animate the ellipse
before moving, check the position:
if right-most position == right wall
invert x velocity
if left-most position == left wall
invert x velocity
if top-most position == top wall
invert y velocity
if bottom-most position == bottom wall
invert y velocity
move ellipse to next position
This is a pretty simple simulation, but it should give you an idea of how to progress and develop a more sophisticated model. Hope this helps!
I found this small application that i've been playing around with for the past little while. I was wondering, if i wanted to simply rotate the image in a circle? or make the entire image just bounce up and down, how would i modify this program to do so? Everything i've tried will just stretch the image - even if i do get it to move to the left or to the right. Any ideas on what i can do? Code is below
public partial class Form1 : Form
{
private int width = 15;
private int height = 15;
Image pic = Image.FromFile("402.png");
private Button abort = new Button();
Thread t;
public Form1()
{
abort.Text = "Abort";
abort.Location = new Point(190, 230);
abort.Click += new EventHandler(Abort_Click);
Controls.Add(abort);
SetStyle(ControlStyles.DoubleBuffer| ControlStyles.AllPaintingInWmPaint| ControlStyles.UserPaint, true);
t = new Thread(new ThreadStart(Run));
t.Start();
}
protected void Abort_Click(object sender, EventArgs e)
{
t.Abort();
}
protected override void OnPaint( PaintEventArgs e )
{
Graphics g = e.Graphics;
g.DrawImage(pic, 10, 10, width, height);
base.OnPaint(e);
}
public void Run()
{
while (true)
{
for(int i = 0; i < 200; i++)
{
width += 5;
Invalidate();
Thread.Sleep(30);
}
}
}
}
So I don't know what you're trying to achieve, but to get the fundies out of the way, WinForms is a GDI+ library and its meant more for GUI stuff, so something like animation will probably be handled better by a graphics library like SFML.
Anyways, there's a million ways to achieve what you want. In terms of moving something around in a circle, you're gonna need a little simple trig. For a bouncing motion, I would say following a sine curve would be the easiest way.
Here's some pseudo-code (not sure if this is syntax-perfect) for bouncing:
Field Definitions:
private double frame = 0;
OnPaint:
Graphics g = e.Graphics;
g.DrawImage(pic, 10, 10 + Math.sin(frame)*10, width, height);
frame+=.01;
base.OnPaint(e);
This way, every time the paint event is triggered, t will increase by .01 radians. The sin has a domain that will oscillate between -1 and 1, and you can multiply that by a factor of 10 to get that bouncing effect.
Frame represents your "keyframe". If you want to speed it up, increase the frame+=__ to a higher value. If you want to increase the range, change the offset Math.sin(frame)*__
I'm trying to implement the following method:
void Ball::DrawOn(Graphics g);
The method should draw all previous locations (stored in a queue) of the ball and finally the current location. I don't know if that matters, but I print the previous locations using g.DrawEllipse(...) and the current location using g.FillEllipse(...).
The question is, that as you could imagine there is a lot of drawing to be done and thus the display starts to flicker much. I had searched for a way to double buffer, but all I could find is these 2 ways:
System.Windows.Forms.Control.DoubleBuffered = true;
SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
while trying to use the first, I get the an error explaining that from in this method the Property DoubleBuffered is inaccessible due to its protection level. While I can't figure how to use the SetStyle method.
Is it possible at all to double buffer while all the access I have is to the Graphics Object I get as input in the method?
Thanks in Advance,
Edit:
I had created the following class
namespace doubleBuffer
{
class BufferedBall : System.Windows.Forms.Form
{
private Ball ball;
public BufferedBall(Ball ball)
{
this.ball = ball;
}
public void DrawOn(Graphics g)
{
this.DoubleBuffered = true;
int num = 0;
Rectangle drawArea1 = new Rectangle(5, 35, 30, 100);
LinearGradientBrush linearBrush1 =
new LinearGradientBrush(drawArea1, Color.Green, Color.Orange, LinearGradientMode.Horizontal);
Rectangle drawArea2 = new Rectangle(5, 35, 30, 100);
LinearGradientBrush linearBrush2 =
new LinearGradientBrush(drawArea2, Color.Black, Color.Red, LinearGradientMode.Vertical);
foreach (Point point in ball.previousLocations)
{
Pen myPen1;
if (num % 3 == 0)
myPen1 = new Pen(Color.Yellow, 1F);
else if (num % 3 == 1)
myPen1 = new Pen(Color.Green, 2F);
else
myPen1 = new Pen(Color.Red, 3F);
num++;
myPen1.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
myPen1.StartCap = System.Drawing.Drawing2D.LineCap.RoundAnchor;
myPen1.EndCap = System.Drawing.Drawing2D.LineCap.AnchorMask;
g.DrawEllipse(myPen1, (float)(point.X - ball.radius), (float)(point.Y + ball.radius), (float)(2 * ball.radius), (float)(2 * ball.radius));
}
if ((ball.Host.ElapsedTime * ball.Host.FPS * 10) % 2 == 0)
{
g.FillEllipse(linearBrush1, (float)(ball.Location.X - ball.radius), (float)(ball.Location.Y + ball.radius), (float)(2 * ball.radius), (float)(2 * ball.radius));
}
else
{
g.FillEllipse(linearBrush2, (float)(ball.Location.X - ball.radius), (float)(ball.Location.Y + ball.radius), (float)(2 * ball.radius), (float)(2 * ball.radius));
}
}
}
}
and the ball drawOn looks like this:
new BufferedBall(this).DrawOn(g);
Is that what you meant? because it is still flickering?
Form class has DoubleBuffered property exposed as protected. http://msdn.microsoft.com/en-us/library/system.windows.forms.control.doublebuffered.aspx but since you derive your form from Form you can use it.
this.DoubleBuffered = true;
That's fine but you have to move this statement to the constructor. Double-buffering requires setup, Windows Forms has to create a buffer, that must be done before the paint event runs. The constructor is ideal.
A simpler way to set style for double buffering of Control-derived classes is to use reflection. See here: http://www.csharp-examples.net/set-doublebuffered/
That would save you the step of subclassing a control just to set a protected property.
You don't need to set DoubleBuffered to true each time you redraw. It is not disabled when drawing finished. Just remove the line from DrawOn and set it in the constructor or Forms Designer and check the results. Setting the value to false produces significant flickering while setting to true doesn't.
I tried your code in a form where a timer forces a redraw every millisecond and noticed no flickering when DoubleBuffered is true:
private int yDir = 1,xDir=1;
int step = 1;
private void timer1_Tick(object sender, EventArgs e)
{
if (ball.Location.Y >= this.Height-50)
yDir = -1 ;
if (ball.Location.X >= this.Width-50)
xDir= -1 ;
ball.MoveBy(xDir*step,yDir*step);
ball.Host.ElapsedTime++;
this.Invalidate();
}
private void DoubleBufferedBall_Paint(object sender, PaintEventArgs e)
{
DrawOn(e.Graphics);
}
Another option, that I'll toss out for you, is to do all your drawing to a Bitmap, and then in the OnPaint method, you simply draw that Bitmap to the form.
Its manual, but it gives you full control. I've used it with some success on some pet projects of mine.
You also might want to look into XNA -- it might be overkill for your project here, but you can use XNA in WinForms as a rendering engine.