Clearing drawn objects in C# WPF - c#

I have used this code to draw a set of ellipses in C# but I do not know how to erase them. Any help would be greatly appreciated. Here is the code I use to draw:
private void drawEllipseAnimation(double x, double y)
{
Ellipse e = new Ellipse();
e.Fill = Brushes.Yellow;
e.Stroke = Brushes.Black;
e.Height = 10;
e.Width = 10;
e.Opacity = 1;
MainCanvas.Children.Add(e);
Canvas.SetLeft(e, x);
Canvas.SetTop(e, y);
}

MainCanvas.Children.Clear()?

Related

Drawing Ellipse on Canvas with "negative" width/height using mouse events

On MouseDownEvent I set upper left corner of Ellipse I'm trying to draw.
public MyCircle(Point location)
{
ellipseObject = new Ellipse
{
Stroke = Brushes.Black,
StrokeThickness = 2,
Margin = new Thickness(location.X, location.Y, 0, 0)
};
}
Then on MouseMoveEvent I update Width and Height properties and it works fine as long as I don't move mouse above or/and to the left of my Ellipse upper left corner, in that case I'm getting exception that these properties can't be negative (which of course makes perfect sense).
public void Draw(Point location)
{
if (ellipseObject != null)
{
ellipseObject.Width = location.X - ellipseObject.Margin.Left;
ellipseObject.Height = location.Y - ellipseObject.Margin.Top;
}
}
The problem doesn't exist with drawing lines:
public void Draw(Point location)
{
lineObject.X2 = location.X;
lineObject.Y2 = location.Y;
}
I know it's trivial, but I'm completely stuck on this. How should I handle drawing Ellipses?
I had this EXACT problem when trying to create a crop tool. Problem is that you need to create if statements for when the cursor goes negative X or negative Y from your starting point. For starters, you'll need to have a global Point that you mark as your 'start' point. Also specify a global current point position that we'll talk about in a minute.
public Point startingPoint;
public Point currentPoint;
Then, make sure you have an onMouseDown event on whatever control you are trying to put the ellipse on.
private void control_MouseDown(object sender, MouseEventArgs e)
{
startingPoint.X = e.X;
startingPoint.Y = e.Y;
}
Then, you need to create if statements in your MouseMove event to check with point (current mouse position, or starting point) has a lower X/Y value
private void control_MouseMove(object sender, MouseEventArgs e)
{
//The below point is what we'll draw the ellipse with.
Point ellipsePoint;
Ellipse ellipseObject = new Ellipse();
currentPoint.X = e.X;
currentPoint.Y = e.Y;
//Then we need to get the proper width/height;
if (currentPoint.X >= startingPoint.X)
{
ellipsePoint.X = startingPoint.X;
ellipseObject.Width = currentPoint.X - startingPoint.X;
}
else
{
ellipsePoint.X = currentPoint.X;
ellipseObject.Width = startingPoint.X - currentPoint.X;
}
if (currentPoint.Y >= startingPoint.Y)
{
ellipsePoint.Y = startingPoint.Y;
ellipseObject.Height = currentPoint.Y - startingPoint.Y;
}
else
{
ellipsePoint.Y = currentPoint.Y;
ellipseObject.Height = startingPoint.Y - currentPoint.Y;
}
ellipseObject.Stroke = Brushes.Black;
ellipseObject.StrokeThickness = 2;
ellipseObject.Margin = new Thickness(ellipsePoint.X, ellipsePoint.Y, 0, 0);
}
Hope this helps!
Save the origin point separately and set the X and Y properties of the Ellipse's Margin to the mouse position and the Width and Height to the distances between the mouse and origin point.
Untested:
public MyCircle(Point location)
{
ellipseObject = new Ellipse
{
Stroke = Brushes.Black,
StrokeThickness = 2,
Margin = new Thickness(location.X, location.Y, 0, 0)
Tag = new Point(location.X, location.Y)
};
}
public void Draw(Point location)
{
if (ellipseObject != null)
{
Point o = (Point)ellipseObject.Tag;
double x = Math.Min(location.X, o.Left);
double y = Math.Min(location.Y, o.Top);
double width = Math.Abs(Math.Max(location.X, o.Left) - x);
double height = Math.Abs(Math.Max(location.Y, o.Top) - y);
ellipseObject.Margin.X = x;
ellipseObject.Margin.Y = y;
ellipseObject.Width = width;
ellipseObject.Height = height;
}
}

How to Add Rectangle one after other vertically on Canvas in WPF using C#?

I have a Canvas in my WPF application. I am adding the Rectangle on button click. The Width is Fixed but, Height is value entered by user in TextBox/GridCell.
When i add rectangle on Canvas with specifying Height. it adds the rectangle but, it doesnt appear one after other. Any idea?
In .xaml.cs:
int width=200;
Reactangle rect;
static int val=0;
Protected void Add()
{
rect = new Rectangle();
rect.Stroke = Brushes.Red;
rect.StrokeThickness = 1;
rect.Height = Convert.ToInt32(txtheight.Text);
rect.Width = width;
Canvas.SetLeft(rect,100);
Canvas.SetTop(rect,rect.Height);
rect.Tag = val;
canvasboard.Children.Add(rect);
val=val+1;
}
This adds Rectangle but not Exactly one after other on canvas.
<Canvas Name="canvasboard" Background="White" Margin="2">
</Canvas>
<TextBox Name="txtheight" Width="150"/>
Note: I cant use WrapPanel or StackPanel for this form. and want to make changes in existing code.
Help Appreciated!
If all your adding is sequential elements vertically into this Canvas, you can do it without having to add a new variable in class scope as well.
private void Add() {
rect = new Rectangle {
Stroke = Brushes.Red,
StrokeThickness = 1,
Height = Convert.ToDouble(txtheight.Text),
Width = width
};
Canvas.SetLeft(rect, 100);
double canvasTop = 0.0;
if (canvasboard.Children.Count > 0) {
var lastChildIndex = canvasboard.Children.Count - 1;
var lastChild = canvasboard.Children[lastChildIndex] as FrameworkElement;
if (lastChild != null)
canvasTop = Canvas.GetTop(lastChild) + lastChild.Height + 1;
}
Canvas.SetTop(rect, canvasTop);
rect.Tag = val++;
canvasboard.Children.Add(rect);
}
Try storing a local variable that maintains the combined height of all Rectangles:
private double _top = 0;
protected void Add()
{
var rect = new Rectangle();
rect.Stroke = Brushes.Red;
rect.StrokeThickness = 1;
rect.Height = double.Parse(txtheight.Text);
rect.Width = 20;
Canvas.SetLeft(rect, 100);
Canvas.SetTop(rect, _top);
_top += rect.Height;
rect.Tag = val;
canvasboard.Children.Add(rect);
val = val + 1;
}

Drawing Multiple Rectangles c#

What would be the best way to draw 25 rectangles (5*5) in c#?
I later need to be able to reach a specific rectangle and change its color, for instance change the color to red if the user inputs the incorrect word.
Would it be more suitable to create an array of rectangles in this case?
This is what i have so far
Graphics g = pictureBox1.CreateGraphics();
int x =0;
int y= 0;
int width = 20;
int height = 20;
for (int i = 0; i < 25; i++)
{
if (i <= 4)
{
g.FillRectangle(Brushes.Blue, x, y, width, height);
x += 50;
}
else if (i > 4)
{
y = 50;
g.FillRectangle(Brushes.Blue, x, y, width, height);
x += 50;
}
}
This should get you started, not the complete code. You will need to add a PictureBox control and use the default name (picurebox1). EDIT: Need to add a button too :)
public partial class Form1 : Form
{
public List<Rectangle> listRec = new List<Rectangle>();
Graphics g;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Rectangle rect = new Rectangle();
rect.Size = new Size(100,20);
for (int x = 0; x < 5; x++)
{
rect.X = x * rect.Width;
for (int y = 0; y < 5; y++)
{
rect.Y = y * rect.Height;
listRec.Add(rect);
}
}
foreach (Rectangle rec in listRec)
{
g = pictureBox1.CreateGraphics();
Pen p = new Pen(Color.Blue);
g.DrawRectangle(p, rec);
}
}
public void ChangeColor(Rectangle target, Color targetColor)
{
Pen p = new Pen(targetColor);
g.DrawRectangle(p, target.X, target.Y, target.Width, target.Height);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.D0: ChangeColor(listRec[0], Color.Red);
break;
case Keys.D1: ChangeColor(listRec[1], Color.Red);
break;
//..more code to handle all keys..
}
}
}
If you're not concerned with performance or the looks, then the easiest approach would be to create a List of Lists of Panels in your Form_Load event as one of the comments mentions.
List<List<Panel>> panelGrid = new List<List<Panel>>();
for (var i = 0; i < 5; i++)
{
var panelRow = new List<Panel>();
for (var j = 0; j < 5; j++)
{
panelRow.Add(new Panel());
// add positioning logic here
}
panelGrid.Add(panelRow);
}
Then you will be able to reference each individual one at a later stage...
If you have to use Graphics class (which is the better approach), then you should setup something similar however replacing Panel with a class of your own. Then in Form_Paint event you would iterate through the list of objects and render them.
class MyPanel
{
public Size size;
public Color color;
}
...
foreach (var myPanelRow in myPanelGrid)
foreach (var myPanel in myPanelRow)
g.FillRectangle(myPanel.color, myPanel.size); // this obviously won't work as is, but you get the idea
Then when you need to change a color, you do something like:
myPanelsGrid[0][0].color = Color.Blue;
myForm.Invalidate();
The second line will result in Paint in event being called again.

Add Point to Canvas

I'm coding in Microsoft Visual Studio 2010 Express for Windows Phone. I need to add a point onto a Canvas, but I can't...
for (float x = x1; x < x2; x += dx)
{
Point poin = new Point();
poin.X = x;
poin.Y = Math.Sin(x);
canvas1.Children.Add(poin);
}
Studio says:
Error 2 Argument 1: cannot convert from 'System.Windows.Point' to 'System.Windows.UIElement'
My question is: how do I add a point onto a Canvas?
From your code snippet I assume you're trying to draw a curve. To do this, you can look into GraphicsPath. Instead of drawing individual points, you can use the points as coordinates, which you connect through lines. Then, in your code, you can create a GraphicsPath using the AddLine method.
This could then be drawn onto a bitmap, for example.
EDIT
Sample (not tested):
GraphicsPath p = new GraphicsPath();
for (float x = x1; x < x2; x += dx)
{
Point point = new Point();
point.X = x;
point.Y = Math.Sin(x);
Point point2 = new Point();
point2.X = x+dx;
point2.Y = Math.Sin(x+dx);
p.AddLine(point, point2);
}
graphics.DrawPath(p);
Another way would be to use the WPF Path class, which would work about the same, but is a real UI element which you can add to the children of a Canvas.
EDIT
People have pointed out that the above code is Windows Forms code. Well, here's what you can do in WPF:
myPolygon = new Polygon();
myPolygon.Stroke = System.Windows.Media.Brushes.Black;
myPolygon.Fill = System.Windows.Media.Brushes.LightSeaGreen;
myPolygon.StrokeThickness = 2;
myPolygon.HorizontalAlignment = HorizontalAlignment.Left;
myPolygon.VerticalAlignment = VerticalAlignment.Center;
PointCollection points = new PointCollection();
for (float x = x1; x < x2; x += dx)
{
Point p = new Point(x, Math.Sin(x));
points.Add(p);
}
myPolygon.Points = points;
canvas1.Children.Add(myPolygon);
If it is 'just a single point you want to add, you can add a tiny rectangle or ellipse to the canvas.
If you want to set a lot of points or a couple points many times, I suggest you create an array of pixel data (colors) and write those to a WriteableBitmap
The Point you used is not a UIElement but a struct, please use Line instead.
Line lne = new Line();
lne.X1 = 10;
lne.X2 = 11;
lne.Y1 = 10;
lne.Y2 = 10;
canvas1.Children.Add(lne);
You get the idea...
Edit
changed:
lne.X2 = 10 to lne.X2 = 11
As per the error, the children of the Canvas control must be derivatives of the System.Windows.UIElement class: System.Windows.Point is not. To achieve what you are doing, you would be best looking into using the geometry within WPF. See here for an article on how to do so.
Try adding a ellipse
Ellipse myEllipse = new Ellipse();
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Color.FromArgb(255, 255, 255, 0);
myEllipse.Fill = mySolidColorBrush;
myEllipse.StrokeThickness = 2;
myEllipse.Stroke = Brushes.White;
myEllipse.Width = 200;
myEllipse.Height = 100;
Canvas.SetTop(myEllipse,50);
Canvas.SetLeft(myEllipse,80);
myCanvas.Children.Add(myEllipse);

Performance issue when adding controls to a panel in C#

I am creating a panel and then adding some labels/buttons to it to form a grid. The issue is that if I add more than say 25x25 items to the panel, there is a terrible performance hit. I can resize the form ok but when I scroll the panel to see all the labels the program lags, the labels/buttons tear or flicker, and sometimes it can make the program unresponsive. I have tried adding the controls to a "DoubleBufferedPanel" that I created. This seems to have no effect. What else could I do? Sorry for such a large code listing. I didn't want to waste anyone's time.
namespace GridTest
{
public partial class Form1 : Form
{
private const int COUNT = 50;
private const int SIZE = 50;
private Button[,] buttons = new Button[COUNT, COUNT];
private GridPanel pnlGrid;
public Form1()
{
InitializeComponent();
pnlGrid = new GridPanel();
pnlGrid.AutoScroll = true;
pnlGrid.Dock = DockStyle.Fill;
pnlGrid.BackColor = Color.Black;
this.Controls.Add(pnlGrid);
}
private void Form1_Load(object sender, EventArgs e)
{
int x = 0;
int y = 0;
int offset = 1;
for (int i = 0; i < COUNT; i++)
{
for (int j = 0; j < COUNT; j++)
{
buttons[i, j] = new Button();
buttons[i, j].Size = new Size(SIZE, SIZE);
buttons[i, j].Location = new Point(x, y);
buttons[i, j].BackColor = Color.White;
pnlGrid.Controls.Add(buttons[i, j]);
x = x + SIZE + offset;
}
x = 0;
y = y + SIZE + offset;
}
}
}
}
Also, the GridPanel class:
namespace GridTest
{
public class GridPanel : Panel
{
public GridPanel()
: base()
{
this.DoubleBuffered = true;
this.ResizeRedraw = false;
}
}
}
If you must add controls at run time, based on some dynamic or changing value, you might want to consider creating an image on the fly, and capturing mouse click events on its picturebox. This would be much quicker and only have one control to draw rather than hundreds. You would lose some button functionality such as the click animation and other automatic properties and events; but you could recreate most of those in the generation of the image.
This is a technique I use to offer users the ability to turn on and off individual devices among a pool of thousands, when the location in a 2-dimensional space matters. If the arrangement of the buttons is unimportant, you might be better offering a list of items in a listview or combobox, or as other answers suggest, a datagridview with button columns.
EDIT:
An example showing how to add a graphic with virtual buttons. Very basic implementation, but hopefully you will get the idea:
First, some initial variables as preferences:
int GraphicWidth = 300;
int GraphicHeight = 100;
int ButtonWidth = 60;
int ButtonHeight = 20;
Font ButtonFont = new Font("Arial", 10F);
Pen ButtonBorderColor = new Pen(Color.Black);
Brush ButtonTextColor = new SolidBrush(Color.Black);
Generating the image:
Bitmap ControlImage = new Bitmap(GraphicWidth, GraphicHeight);
using (Graphics g = Graphics.FromImage(ControlImage))
{
g.Clear(Color.White);
for (int x = 0; x < GraphicWidth; x += ButtonWidth)
for (int y = 0; y < GraphicHeight; y += ButtonHeight)
{
g.DrawRectangle(ButtonBorderColor, x, y, ButtonWidth, ButtonHeight);
string ButtonLabel = ((GraphicWidth / ButtonWidth) * (y / ButtonHeight) + x / ButtonWidth).ToString();
SizeF ButtonLabelSize = g.MeasureString(ButtonLabel, ButtonFont);
g.DrawString(ButtonLabel, ButtonFont, ButtonTextColor, x + (ButtonWidth/2) - (ButtonLabelSize.Width / 2), y + (ButtonHeight/2)-(ButtonLabelSize.Height / 2));
}
}
pictureBox1.Image = ControlImage;
And responding to the Click event of the pictureBox:
// Determine which "button" was clicked
MouseEventArgs em = (MouseEventArgs)e;
Point ClickLocation = new Point(em.X, em.Y);
int ButtonNumber = (GraphicWidth / ButtonWidth) * (ClickLocation.Y / ButtonHeight) + (ClickLocation.X / ButtonWidth);
MessageBox.Show(ButtonNumber.ToString());
I think you won't be able to prevent flickering having 625 buttons (btw, windows) on the panel. If such layout is mandatory for you, try to use the DataGridView, bind it to a fake data source containing the required number of columns and rows and create DataGridViewButtonColumns in it. This should work much more better then your current result ...

Categories

Resources