How to optimise Canvas drawing in WPF? - c#

How to optimise Canvas drawing in WPF, when i have 10000 Line like in program "Paint"? When I draw in "Paint" i can use how much I need lines and circles, but in my program agter +-10000 i have lags.
I create a test program with 10000 lines and when I move mouse on canvas rectangle is change position to a cursor.
here code
for (int i=0; i < 10000; i++)
{
Line l = new Line();
l.Stroke = Brushes.Black;
l.StrokeThickness = 1;
l.X1 = 50+ privi;
l.Y1 = 50 + privi;
l.X2 = 100 ;
l.Y2 = 100 + privi;
MainCanvas.Children.Add(l);
privi += 5;
}
and here i moving
if (clicked)
{
Point p = Mouse.GetPosition(MainCanvas);
rect.Margin = new Thickness(p.X-25, p.Y - 25, 0, 0);
}
enter code here
UPDATE
privi = 5;
Rectangle rect = new Rectangle();
rect.Fill = Brushes.Black;
rect.Width = 50;
rect.Height = 50;
MainCanvas.Children.Add(rect)

I don't know if you need lines and rect to be in the same canvas, otherwise I found that adding the rectangle in a second transparent canvas over the MainCanvas improves the reactivity.
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="1080" Width="1024">
<Canvas Name="MainCanvas" MouseMove="MainCanvas_MouseMove">
<Canvas Name="RectCanvas" Background="Transparent" MouseMove="RectCanvas_MouseMove">
</Canvas>
</Canvas>
</Window>
public partial class MainWindow : Window
{
double privi = 5;
Rectangle rect;
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 10000; i++)
{
Line l = new Line();
l.Stroke = Brushes.Black;
l.StrokeThickness = 1;
l.X1 = 50 + privi;
l.Y1 = 50 + privi;
l.X2 = 100;
l.Y2 = 100 + privi;
MainCanvas.Children.Add(l);
privi += 5;
}
rect = new Rectangle();
rect.Width = 50;
rect.Height = 50;
rect.Fill = Brushes.Black;
Canvas.SetLeft(rect, 0);
Canvas.SetTop(rect, 0);
SecondCanvas.Children.Add(rect);
}
private void RectCanvas_MouseMove(object sender, MouseEventArgs e)
{
//if (clicked)
{
Point p = Mouse.GetPosition(MainCanvas);
rect.Margin = new Thickness(p.X - 25, p.Y - 25, 0, 0);
}
}
}

Related

Can we create 3D graph in 4 quadrants in C#.NET winform?

I want to create a 3D Graph in 4 quadrants form in C#.NET. For instant, I could do as shown below. If you see the corner of the chart begins with (-150, -200). I wish to start with (0,0) and the extend into 4 quadrant form.
Kindly enlighten me how can I transform this 3D graph in 4 quadrant form ?
Below is the corresponding code:
void prepare3dChart(Chart chart, ChartArea ca)
{
ca.Area3DStyle.Enable3D = true;
Series s = new Series();
chart.Series.Add(s);
s.ChartType = SeriesChartType.Bubble;
s.MarkerStyle = MarkerStyle.Diamond;
s["PixelPointWidth"] = "100";
s["PixelPointGapDepth"] = "1";
chart.ApplyPaletteColors();
addTestData(chart);
}
void addTestData(Chart chart)
{
Random rnd = new Random(9);
double x = 0, y = 0, z = 0;
for (int i = 0; i < 100; i++)
{
AddXY3d(chart.Series[0], x, y, z);
x = Math.Sin(i / 11f) * 88 + rnd.Next(3);
y = Math.Cos(i / 10f) * 88 + rnd.Next(5);
z = (Math.Sqrt(i * 2f) * 88 + rnd.Next(6));
}
}
int AddXY3d(Series s, double xVal, double yVal, double zVal)
{
int p = s.Points.AddXY(xVal, yVal, zVal);
s.Points[p].Color = Color.Transparent;
return p;
}
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Chart chart = sender as Chart;
if (chart.Series.Count < 1) return;
if (chart.Series[0].Points.Count < 1) return;
ChartArea ca = chart.ChartAreas[0];
List<List<PointF>> data = new List<List<PointF>>();
foreach (Series s in chart.Series)
data.Add(GetPointsFrom3D(ca, s, s.Points.ToList(), e.ChartGraphics));
renderLines(data, e.ChartGraphics.Graphics, chart, true);
renderPoints(data, e.ChartGraphics.Graphics, chart, 6);
}
List<PointF> GetPointsFrom3D(ChartArea ca, Series s,
List<DataPoint> dPoints, ChartGraphics cg)
{
var p3t = dPoints.Select(x => new Point3D((float)ca.AxisX.ValueToPosition(x.XValue),
(float)ca.AxisY.ValueToPosition(x.YValues[0]),
(float)ca.AxisY.ValueToPosition(x.YValues[1]))).ToArray();
ca.TransformPoints(p3t.ToArray());
return p3t.Select(x => cg.GetAbsolutePoint(new PointF(x.X, x.Y))).ToList();
}
void renderLines(List<List<PointF>> data, Graphics graphics, Chart chart, bool curves)
{
for (int i = 0; i < chart.Series.Count; i++)
{
if (data[i].Count > 1)
using (Pen pen = new Pen(Color.FromArgb(64, chart.Series[i].Color), 2.5f))
if (curves) graphics.DrawCurve(pen, data[i].ToArray());
else graphics.DrawLines(pen, data[i].ToArray());
}
}
void renderPoints(List<List<PointF>> data, Graphics graphics, Chart chart, float width)
{
for (int s = 0; s < chart.Series.Count; s++)
{
Series S = chart.Series[s];
for (int p = 0; p < S.Points.Count; p++)
using (SolidBrush brush = new SolidBrush(Color.FromArgb(64, S.Color)))
graphics.FillEllipse(brush, data[s][p].X - width / 2,
data[s][p].Y - width / 2, width, width);
}
}
I want my 3D graph to have 4 quadrants like this:
Thanks #TaW. I got the code right.
void prepare3dChart(Chart chart, ChartArea ca)
{
ca.Area3DStyle.Enable3D = true;
ca.BackColor = Color.Transparent;
ca.AxisX.Minimum = -300;
ca.AxisX.Maximum = 300;
ca.AxisY.Minimum = -300;
ca.AxisY.Maximum = 300;
ca.AxisX.Crossing = 0; // move both axes..
ca.AxisY.Crossing = 0; // to the middle
ca.AxisX.Interval = 50;
ca.AxisY.Interval = 50;
ca.AxisX.MajorGrid.LineColor = Color.LightGray;
ca.AxisY.MajorGrid.LineColor = Color.LightGray;
chart.Series.Clear();
Series s = new Series();
chart.Series.Add(s);
s.ChartType = SeriesChartType.Bubble;
s.MarkerStyle = MarkerStyle.Diamond;
s["PixelPointWidth"] = "100";
s["PixelPointGapDepth"] = "1";
chart.ApplyPaletteColors();
addTestData(chart);
}
You can style the chart easily to look like this by moving both axes to the middle; this is done by setting their Crossings:
The code used is this:
ChartArea ca = chart1.ChartAreas[0];
ca.BackColor = Color.Transparent;
ca.AxisX.Minimum = -300;
ca.AxisX.Maximum = 300;
ca.AxisY.Minimum = -300;
ca.AxisY.Maximum = 300;
ca.AxisX.Crossing = 0; // move both axes..
ca.AxisY.Crossing = 0; // to the middle
ca.AxisX.Interval = 100;
ca.AxisY.Interval = 100;
ca.AxisX.MajorGrid.LineColor = Color.LightGray;
ca.AxisY.MajorGrid.LineColor = Color.LightGray;
Note that the real problem with MSChart in 3d-mode always remains the z-axis because there really isn't one. (If anybody is interested in how one can simulate it by having a lot of series see here. My example uses 32 series..)
You could draw it yourself, using the built-in function for the 3d conversions (TransformPoints) but this is a lot of tedious work, especially when it comes to labelling..
The updated code :
void prepare3dChart(Chart chart, ChartArea ca)
{
ca.Area3DStyle.Enable3D = true;
ca.BackColor = Color.Transparent;
ca.AxisX.Minimum = -300;
ca.AxisX.Maximum = 300;
ca.AxisY.Minimum = -300;
ca.AxisY.Maximum = 300;
ca.AxisX.Crossing = 0; // move both axes..
ca.AxisY.Crossing = 0; // to the middle
ca.AxisX.Interval = 50;
ca.AxisY.Interval = 50;
ca.AxisX.MajorGrid.LineColor = Color.LightGray;
ca.AxisY.MajorGrid.LineColor = Color.LightGray;
chart.Series.Clear();
Series s = new Series();
chart.Series.Add(s);
s.ChartType = SeriesChartType.Bubble;
s.MarkerStyle = MarkerStyle.Diamond;
s["PixelPointWidth"] = "100";
s["PixelPointGapDepth"] = "1";
chart.ApplyPaletteColors();
addTestData(chart);
}

Scale lines in wpf grid

I have user control:
<UserControl x:Class="WpfExt.ColorLine.TimeLineControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="40" d:DesignWidth="400">
<Grid x:Name="DynamicGrid">
</Grid>
</UserControl>
In code behind:
public readonly static DependencyProperty TotalHoursProperty = DependencyProperty.Register("TotalHours",
typeof(int),
typeof(TimeLineControl),
new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsParentMeasure, TotalHoursChangedCallback));
private static void TotalHoursChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
((TimeLineControl)dependencyObject).ChangeTimeLineItems((int)e.NewValue);
}
public int TotalHours
{
get { return (int)GetValue(TotalHoursProperty); }
set { SetValue(TotalHoursProperty, value); }
}
private void ChangeTimeLineItems(int totalHours)
{
if (totalHours < 1 || totalHours > 24) throw new InvalidOperationException("TotalHours should be in 1..24 range.");
DynamicGrid.Children.Clear();
AddExtremeLine(0, HorizontalAlignment.Left);
for (var index = 1; index < totalHours * 2 - 1; index += 2)
{
AddLine(index, 30);
AddLine(index + 1, 40);
}
AddLine(totalHours * 2 - 1, 30);
AddExtremeLine(totalHours * 2, HorizontalAlignment.Right);
}
private void AddExtremeLine(int column, HorizontalAlignment horizontalAlignment)
{
var columnDefinition = new ColumnDefinition();
var gridLength = new GridLength(0.5, GridUnitType.Star);
columnDefinition.Width = gridLength;
DynamicGrid.ColumnDefinitions.Add(columnDefinition);
var line = new Line
{
X1 = 0,
X2 = 0,
Y1 = 20,
Y2 = 40,
Stroke = Brushes.Black,
StrokeThickness = 1,
HorizontalAlignment = horizontalAlignment
};
Grid.SetColumn(line, column);
DynamicGrid.Children.Add(line);
}
private void AddLine(int column, double y2)
{
var columnDefinition = new ColumnDefinition();
var gridLength = new GridLength(1, GridUnitType.Star);
columnDefinition.Width = gridLength;
DynamicGrid.ColumnDefinitions.Add(columnDefinition);
var line = new Line
{
X1 = 0,
X2 = 0,
Y1 = 20,
Y2 = y2,
Stroke = Brushes.Black,
StrokeThickness = 1,
HorizontalAlignment = HorizontalAlignment.Center
};
Grid.SetColumn(line, column);
DynamicGrid.Children.Add(line);
}
So if i use it like:
<colorLine:TimeLineControl Height="100" TotalHours="6" />
It looks like:
So question is:
How can i change my code, to make my lines scale to fill controls height, but keep existing behavior when width changes (only space between them will scale, but not lines).
If it is only about a control that draws ticks in the shown manner I think I'd do it differently and create a very small custom control. This is all you need and very obvious how it works. You can add dependency properties as you like (like the hours of course). Just by using this way it fills up all given space.
public class HourTicks : FrameworkElement
{
static HourTicks ()
{
}
protected override void OnRender (DrawingContext drawingContext)
{
int hours = 10;
Pen pen = new Pen(Brushes.Black, 1);
drawingContext.DrawLine (pen, new Point (0, 0), new Point (0, ActualHeight));
drawingContext.DrawLine (pen, new Point (ActualWidth, 0), new Point (ActualWidth, ActualHeight));
int numTicks = hours * 2;
double delta = ActualWidth / numTicks;
double x = delta;
for (int i = 0; i <numTicks; i++)
{
drawingContext.DrawLine (pen, new Point (x, 0), new Point (x, ActualHeight / 3));
x += delta;
drawingContext.DrawLine (pen, new Point (x, 0), new Point (x, ActualHeight / 2));
x += delta;
}
}
}
Change the setting of the Y1, Y2's to something based on RenderSize.Height. i.e. something like:
Y1 = Grid.RenderHeight - 40;
Y2 = Grid.RenderHeight - 20;
Obviously the 20 & 40 switch as your now 'measuring' the amount of white space to leave, not the length of the line.

collision detection not all sides of rectangle will correctly collide

The program has a ball, bat and rectangle. I have managed to get the ball to correctly collide with the bat, that's fine. The problem is with the rectangle.
With the rectangle from what I have attempted so far it does detect collisions from the top side and left side, but I am struggling to get the same affect with the bottom and right side.
Any pointers as to where I am going wrong I would much appreciate. I have included code below for clarity. Also, when the ball does collide correctly, it rebounds towards the top left.
Graphics paper;
SolidBrush brush;
private Random randomNum;
Rectangle ball, bat, brick;
int x, y, yChange, xChange, batX;
public Form1()
{
InitializeComponent();
paper = picBox.CreateGraphics();
randomNum = new Random();
bat = new Rectangle();
brick = new Rectangle();
}
private void MoveBall()
{
timer1.Interval = randomNum.Next(80, 200);
timer1.Enabled = true;
x = x + xChange;
y = y + yChange;
if (x >= picBox.Width)
xChange = -xChange;
if (y >= picBox.Height)
yChange = -yChange;
if (x <= 0)
xChange = -xChange;
if (y <= 0)
yChange = -yChange;
}
private void DrawBall()
{
brush = new SolidBrush(Color.Red);
ball = new Rectangle(x, y, 14, 14);
paper.FillEllipse(brush, ball);
}
private void DrawBat()
{
int batXpos, batYpos;
batXpos = batX - 25; batYpos = picBox.Height - 40;
bat = new Rectangle(batXpos, batYpos, 100, 20);
brush = new SolidBrush(Color.Green);
paper.FillRectangle(brush, bat);
}
private void DrawBricks()
{
brick = new Rectangle(200, 100, 100, 50);
brush = new SolidBrush(Color.Pink);
paper.FillRectangle(brush, brick);
}
private void CheckCollision()
{
int ballTop, ballRight, ballDown, ballLeft;
ballTop = ball.Y + 7; ballDown = ball.Y + 14 + (ball.X + 7); //THESE WERE JUST SOME ATTEMPTS TO GET
ballRight = ball.X + 14 + (ball.Y + 7); ballLeft = ball.Y + 7; //LOCATION OF THE BALL AND BRICK X AND Y
//COORDS BUT STILL HAD PROBLEMS
int brickTL, brickTR, brickBL, brickBR;
brickTL = brick.X; brickTR = brick.X + 100;
brickBL = brick.Y + 50; brickBR = brick.X + 100 + (brick.Y + 50);
if (ball.IntersectsWith(bat)) //NO PROBLEMS HERE
{
yChange = -10;
}
if (ball.IntersectsWith(brick)) //WORKS FINE FOR DETECTION ON THE TOP SIDE
{
xChange = -5;
}
if (ball.IntersectsWith(brick)) //WORKS FINE FOR THE RIGHT SIDE
{
yChange = -5;
}
if ((ballTop >= brickBR) && (x < picBox.Width))
{
yChange = 5;
}
}
private void btnStart_Click(object sender, EventArgs e)
{
timer1.Interval = randomNum.Next(80, 200);
timer1.Enabled = true;
x = randomNum.Next(5, picBox.Width);
y = randomNum.Next(5, picBox.Height);
xChange = randomNum.Next(5, 15);
yChange = randomNum.Next(5, 15);
}
private void timer1_Tick(object sender, EventArgs e)
{
paper.Clear(Color.Gray);
DrawBall();
MoveBall();
DrawBat();
CheckCollision();
DrawBricks();
}
private void picBox_MouseMove(object sender, MouseEventArgs e)
{
batX = e.X;
}
I imagine your conditionals are getting hit just fine.. but you are not actually affecting the ball movement.
When the ball is moving up it will have a negative delta, and when moving left will also have a negative delta.
When a collision is detected you need to reverse the sign of the delta.. like:
xChange *= -1; and yChange *= -1;

Animated bar in progress bar winform

I'm writing an extended progress bar control at present, 100% open source and I've created some basic styles with gradients and solid colours.
One of the options I wanted to add was animation to the bar, prety much like the windows 7 and vista green progress bar. So I need to add a moving "Glow" to the % bar, however my attempt at this looks terrible.
My method is to draw an ellipse with set size and move it's x position until it reaches the end were the animation starts again.
First of does anyone have nay links or code to help me achieve the current windows 7 glow effect using GDI or some similar method?
I have several other animations that will also be added the the bar hence the GDI.
private void renderAnimation(PaintEventArgs e)
{
if (this.AnimType == animoptions.Halo)
{
Rectangle rec = e.ClipRectangle;
Rectangle glow = new Rectangle();
//SolidBrush brush = new SolidBrush(Color.FromArgb(100, Color.White));
//int offset = (int)(rec.Width * ((double)Value / Maximum)) - 4;
int offset = (int)(rec.Width / Maximum) * Value;
if (this.animxoffset > offset)
{
this.animxoffset = 0;
}
glow.Height = rec.Height - 4;
if (this.animxoffset + glow.X > offset)
{
glow.Width = offset - (this.animxoffset + 50);
}
else
{
glow.Width = 50;
}
glow.X = this.animxoffset;
LinearGradientBrush brush = new LinearGradientBrush(glow, Color.FromArgb(0, Color.White), Color.FromArgb(100, Color.White), LinearGradientMode.Horizontal);
e.Graphics.FillEllipse(brush, this.animxoffset, 2, glow.Width, glow.Height);
brush.Dispose();
string temp = offset.ToString();
e.Graphics.DrawString(temp + " : " + glow.X.ToString(), DefaultFont, Brushes.Black, 2, 2);
animTimer = new System.Timers.Timer();
animTimer.Interval = 10;
animTimer.Elapsed += new System.Timers.ElapsedEventHandler(t_Elapsed);
animTimer.Start();
}
}
void t_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
this.animTimer.Stop();
this.animxoffset += 2;
Invalidate();
}
This is just an example glow iterating through a pen array.
You could also use a transparent image (although it could have a performance impact).
Pen[] gradient = { new Pen(Color.FromArgb(255, 200, 200, 255)), new Pen(Color.FromArgb(150, 200, 200, 255)), new Pen(Color.FromArgb(100, 200, 200, 255)) };
int x = 20;
int y = 20;
int sizex = 200;
int sizey = 10;
int value = 25;
//draw progress bar basic outline (position - 1 to compensate for the outline)
e.Graphics.DrawRectangle(Pens.Black, new Rectangle(x-1, y-1, sizex, sizey));
//draw the percentage done
e.Graphics.FillRectangle(Brushes.AliceBlue, new Rectangle(x, y, (sizex/100)*value, sizey));
//to add the glow effect just add lines around the area you want to glow.
for (int i = 0; i < gradient.Length; i++)
{
e.Graphics.DrawRectangle(gradient[i], new Rectangle(x - (i + 1), y - (i + 1), (sizex / 100) * value + (2 * i), sizey + (2 * i)));
}

Silverlight graphics pixel side position?

I try to port simple game to silverlight (SameGame). The problem is that my old source code used pixel sizes to allight game marks to board. I draw simple grid using lines and game mark (using rectangle). How i can set rentacle position correctly?
Example 20 20 pixels to upper left corner).
private void DrawGrid()
{
LayoutRoot.Children.Clear();
Rectangle r = new Rectangle();
r.Width = 20;
r.Height = 20;
r.Fill = new SolidColorBrush(Color.FromArgb(255, 0, 255, 0));
r.Stroke = new SolidColorBrush(Color.FromArgb(255, 0, 255, 0));
r.SetValue(Canvas.LeftProperty, (double)0);
r.SetValue(Canvas.TopProperty, (double)0);
LayoutRoot.Children.Add(r);
Color GridColor = Color.FromArgb(0xFF, 0x00, 0x00, 0x00);
for (int y = 0; y < 11; y++)
{
Line l = new Line();
l.X1 = 0;
l.Y1 = 30 * y - 1;
l.X2 = 20 * 30;
l.Y2 = 30 * y - 1;
l.Stroke = new SolidColorBrush(GridColor);
l.StrokeThickness = 1;
LayoutRoot.Children.Add(l);
}
for (int x = 0; x < 21; x++)
{
Line l = new Line();
l.X1 = 30 * x;
l.Y1 = 0;
l.X2 = 30 * x;
l.Y2 = 10 * 30;
l.Stroke = new SolidColorBrush(GridColor);
l.StrokeThickness = 1;
LayoutRoot.Children.Add(l);
}
}
You need to put a Canvas below the LayoutRoot Grid or change LayoutRoot to a Canvas.
Then add the recangle to the Canvas.Children.

Categories

Resources