I have class, that creates Shapes for me (I tried to create some kind of "class factory" but im not sure if this is correct term for that I have created.
Problem is described in comments in my code.
public static Ellipse SomeCircle()
{
Ellipse e = new Ellipse();
double size = 10;
e.Height = size;
e.Width = size;
e.Fill = new SolidColorBrush(Colors.Orange);
e.Fill.Opacity = 0.8;
e.Stroke = new SolidColorBrush(Colors.Black);
// i want to have something like this here:
// canvas1.Children.Add(e);
// but I cant access non-static canvas1 from here
// I need this to place my ellipse in desired place
// (line below will not work if my Ellipse is not placed on canvas
// e.Margin = new Thickness(p.X - e.Width * 2, p.Y - e.Height * 2, 0, 0);
return e;
}
I have no idea how to workaround this.
I don't want to pass that canvas by parameter in my whole application...
Since you do not want to pass your Canvas around as a parameter, you could try creating an Extension Method which would act on your Canvas Object.
namespace CustomExtensions
{
public static class Shapes
{
public static Ellipse SomeCircle(this Canvas dest)
{
Ellipse e = new Ellipse();
double size = 10;
e.Height = size;
e.Width = size;
e.Fill = new SolidColorBrush(Colors.Orange);
e.Fill.Opacity = 0.8;
e.Stroke = new SolidColorBrush(Colors.Black);
dest.Children.Add(e);
return e;
}
}
}
Usage remember to add the CustomExtensions Namespace to your usings.
canvas1.SomeCircle();
Related
I have a project that I need to make an image follow a spline.
I build the spline using Graphics.DrawCurve through an array of Points.
I'm trying to use PointAnimationUsingPath but I can't seem to get it to work. Apparently it doesn't work in C# with Windows form.
Can someone give me a light on how to do this?
Thank you All.
-----EDIT-----
Change to a WPF UserControl as recommend in comments.
Still need some help as the shape does not move exactly following the dots, below my code:
public partial class SplineBox : UserControl
{
Point[] finalPoint;
public SplineBox()
{
InitializeComponent();
}
public void MoveShape(Point[] _path)
{
// Create a NameScope for the page so that
// we can use Storyboards.
NameScope.SetNameScope(this, new NameScope());
// Create the EllipseGeometry to animate.
EllipseGeometry animatedEllipseGeometry =
new EllipseGeometry(new Point(10, 100), 15, 15);
// Register the EllipseGeometry's name with
// the page so that it can be targeted by a
// storyboard.
this.RegisterName("AnimatedEllipseGeometry", animatedEllipseGeometry);
// Create a Path element to display the geometry.
Path ellipsePath = new Path();
ellipsePath.Data = animatedEllipseGeometry;
ellipsePath.Fill = Brushes.Blue;
ellipsePath.Margin = new Thickness(15);
SplineCanvas.Children.Add(ellipsePath);
this.Content = SplineCanvas;
// Create the animation path.
PathGeometry animationPath = new PathGeometry();
PathFigure pFigure = new PathFigure();
pFigure.StartPoint = _path[0];
PolyBezierSegment pBezierSegment = new PolyBezierSegment();
for (int p = 1; p < _path.Length; p++)
{
pBezierSegment.Points.Add(_path[p]);
}
pFigure.Segments.Add(pBezierSegment);
animationPath.Figures.Add(pFigure);
// Freeze the PathGeometry for performance benefits.
animationPath.Freeze();
// Create a PointAnimationgUsingPath to move
// the EllipseGeometry along the animation path.
PointAnimationUsingPath centerPointAnimation = new PointAnimationUsingPath();
centerPointAnimation.PathGeometry = animationPath;
centerPointAnimation.Duration = TimeSpan.FromSeconds(5);
centerPointAnimation.RepeatBehavior = RepeatBehavior.Forever;
// Set the animation to target the Center property
// of the EllipseGeometry named "AnimatedEllipseGeometry".
Storyboard.SetTargetName(centerPointAnimation, "AnimatedEllipseGeometry");
Storyboard.SetTargetProperty(centerPointAnimation,
new PropertyPath(EllipseGeometry.CenterProperty));
// Create a Storyboard to contain and apply the animation.
Storyboard pathAnimationStoryboard = new Storyboard();
pathAnimationStoryboard.RepeatBehavior = RepeatBehavior.Forever;
pathAnimationStoryboard.AutoReverse = true;
pathAnimationStoryboard.Children.Add(centerPointAnimation);
// Start the Storyboard when ellipsePath is loaded.
ellipsePath.Loaded += delegate (object sender, RoutedEventArgs e)
{
// Start the storyboard.
pathAnimationStoryboard.Begin(this);
};
}
public void Paint(ScreenObject _spline)
{
List<Point> points = new List<Point>();
if (true)
{
var spline = _spline;
foreach (System.Windows.Point point in spline.SplineAnchors)
{
Point tempP = new Point((int)point.X, (int)point.Y);
points.Add(tempP);
}
finalPoint = points.ToArray();
//Pen pen = new Pen(Color.FromArgb(255, 0, 0, 255), 1);
//e.Graphics.DrawCurve(pen, finalPoint);
foreach (Point p in finalPoint)
{
// Create a red Ellipse.
Ellipse myEllipse = new Ellipse();
// Create a SolidColorBrush with a red color to fill the
// Ellipse with.
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
// Describes the brush's color using RGB values.
// Each value has a range of 0-255.
mySolidColorBrush.Color = Color.FromArgb(255, 100, 255, 0);
myEllipse.Fill = mySolidColorBrush;
myEllipse.StrokeThickness = 2;
myEllipse.Stroke = Brushes.Black;
// Set the width and height of the Ellipse.
myEllipse.Width = 10;
myEllipse.Height = 10;
myEllipse.Margin = new Thickness(p.X - 5, p.Y - 5, 0, 0);
//e.Graphics.DrawRectangle(pen, new Rectangle(p.X - 5, p.Y - 5, 10, 10));
//e.Graphics.FillRectangle(Brushes.Red, new Rectangle(p.X - 5, p.Y - 5, 10, 10));
SplineCanvas.Children.Add(myEllipse);
}
}
}
}
I have been working with windows forms for a short while and have noticed that button controls always appear one pixels smaller in each direction than I am trying to make them.
To illustrate, the TextBoxes and Button in the image bellow are set to the exact same size but are different sizes.
wrong size buttons
Here is the code that I used to generate the buttons:
public Form1() {
InitializeComponent();
this.Size = new Size(216, 239)
TB1 = new TextBox();
TB1.Multiline = true;
TB1.Size = new Size(100, 100);
TB1.Location = new Point(100, 0);
Controls.Add(TB1);
TB2 = new TextBox();
TB2.Multiline = true;
TB2.Size = new Size(100, 100);
TB2.Location = new Point(0, 100);
Controls.Add(TB2);
TestButton = new Button();
TestButton.Text = "sdfsdf";
TestButton.Size = new Size(100, 100);
TestButton.Location = new Point(100, 100);
Controls.Add(TestButton);
}
From the Image you can see that there is white space around the button. I have tried changing the Control.Margin and Control.Padding but this extra space around the button is unaffected by those.
In order to make the button appear 100x100 (the way I want it) you have to move it one pixel up and to the left and make it two pixels wider and taller. (TextButton.Size = new Size(102, 102); TextButton.Location = new Point(99, 99);)
What I would like to do is make the buttons actually the size I set them to be. Because of the number of buttons in my program, it is undesirable to manually increase the size of each button and I am looking for a more elegant long term solution that I can use going forwards.
I have tried to create a wrapper class around the button class called MyButton but it doesn't work with polymorphism (explained bellow):
class MyButton : Button {
public MyButton() : base() {}
public new Size Size {
get;
set {
int width = value.Width + 2; // make it two wider
int height = value.Height + 2; // make it two taller
Size size = new Size(width, height);
base.Size = size;
}
}
public new Point Location {
get;
set {
Console.WriteLine("running"); // used to make sure this is actually being run
int x = value.X - 1; // move it one left
int y = value.Y - 1; // move it one up
Point point = new Point(x, y);
base.Location = point;
}
}
}
When I create a MyButton object and use myButtonObject.Size = ... it works perfectly and the button sizing and location works out. However, at another place in my code I have a function that takes a Control object as input and here my code from the MyButton class is not being used.
MyButton btn1 = new MyButton();
btn1.Size = new Size(100, 100);
btn.Location = new Point(100, 100);
// ^ this works great and the button is the right size
public void ControlFormatter(Control control) {
control.Size = new Size(100, 100);
control.Location = new Point(100, 100);
}
MyButton btn2 = new MyButton();
ControlFormatter(btn2);
// ^ this doesn't work
Using the Console.WriteLine("running") print statement that I put in MyButton.Location.Set, I can tell that when control.Location is called inside ControlFormatter() the code that I wrote is not run (I can only assume that it is using the default Control.Location property and thus making the buttons the wrong size.
I guess I'm asking two things
Is there an easier/better/cleaner way to make the buttons the right size?
How can I make it so that ControlFormatter() uses MyButton.Size when it can and not Control.Size?
Thanks, also I'm fairly new to C# so grace is appreciated.
I opted for the quicker and dirtier fix of testing whether or not the Control was a Button in my ControlFormatter() function.
public void ControlFormatter(Control control) {
int width = 100;
int height = 100;
if (control is Button) {
width -= 2;
height -= 2;
}
control.Size = new Size(width, height);
control.Position = new Point(); // you get the jist
}
Here's what I used to solve this problem:
public class Snake {
List<Rectangle> bodyParts = new List<Rectangle>();
}
Snake snk = new Snake();
snk.bodyParts.Add(new Rectangle(760,25,8,8))
//the Exception Occurs here
//snk.bodyParts[0].Y = snk.bodyPArts[0].Y-10;
//Here's my approach
snk.bodyParts[0] = new Rectangle(bodyParts[0].X,bodyParts[0].Y-10,8,8);
Exception: Cannot modify the return value of 'System.Collections.Generic.List.this[int]'
My question is: Are they any alternative/better ways to manage this exception ?
Thanks.
EDIT: I got my answer, can you please close this question.
Rather than working directly with Rectangles, how about adding a BodyPart class, with some manipulation methods:
public class BodyPart
{
private Rectangle rectangle;
public BodyPart(Rectangle rectangle)
{
this.rectangle = rectangle;
}
public void MoveY(int value)
{
rectangle.Y += value;
}
public void MoveX(int value)
{
rectangle.X += value;
}
}
public class Snake
{
public List<BodyPart> Parts = new List<BodyPart>();
}
public class AppSnake
{
public void Run()
{
var snake = new Snake();
snake.Parts.Add(new BodyPart(new Rectangle(760, 25, 8, 8)));
snake.Parts[0].MoveY(-10);
}
}
Rectangle, RectangleF, Point, PointF, Size and SizeF are value types (struct type). This means that you can not/should not change an individual part of the structure.
The reason is that unlike classes each variable keeps its own copy of the structure. When you type list[0].X = 10, the indexer list[0] returns a copy of the rectangle and not the original value. The correct way is to assign a new rectangle list[0] = A which copies all the values from A into the array.
Please read more on value types and structs before attempting to write code that uses them.
The quickest way to fix your code without completely changing it around is by adding methods that manipulate all the body parts in predefined ways:
public class Snake
{
public List<Rectangle> Parts { get; } = new List<Rectangle>();
public void MoveX(int delta)
{
for(int i = 0; i < Parts.Count; i++)
{
// Read the current rectangle from the list
var rect = Parts[i];
// Change the coordinate.
rect.X += delta;
// Write the modified rectangle back into the list
Parts[i] = rect;
}
}
public void MoveY(int delta)
{
for(int i = 0; i < Parts.Count; i++)
{
// Read the current rectangle from the list
var rect = Parts[i];
// Change the coordinate.
rect.Y += delta;
// Write the modified rectangle back into the list
Parts[i] = rect;
}
}
}
Use Rectangle.Location and Rectanagle.Size properties e.g.:
snk.bodyParts[0].Location = new Point(newX, newY);
snk.bodyParts[0].Size = new Size(newWidth, newHeight);
I have a uniform grid that has rectangles dynamically added to it. I want to remove a particular rectangle, but I am getting the following error when trying to pass it to the Remove method:
Cannot convert from 'System.Drawing.Rectangle' to 'System.Windows.UIElement'
My code is:
Rectangle swatch = (Rectangle)ug_Thumbnails.FindName("s_" + _instance);
ug_Thumbnails.Children.Remove(swatch);
I tried casting, and got an error saying that you couldn't do it.
EDIT: Per request, here's the code to create the rectangle:
System.Windows.Shapes.Rectangle swatch = new System.Windows.Shapes.Rectangle();
swatch.Width = 50;
swatch.Height = 50;
swatch.Margin = new Thickness(0, 5, 5, 0);
swatch.StrokeThickness = 1;
swatch.Stroke = System.Windows.Media.Brushes.Gray;
swatch.Name = "s_" + name.ToString();
double groupsize = 100 / colors.Count();
DrawingBrush blackBrush = new DrawingBrush();
DrawingGroup checkersDrawingGroup = new DrawingGroup();
List<SolidColorBrush> brushes = colors;
double location = 0;
for (int i = 0; i < colors.Count(); i++)
{
GeometryDrawing drawing = new GeometryDrawing(brushes[i], null,
new RectangleGeometry(new Rect(location, 0, groupsize, groupsize)));
checkersDrawingGroup.Children.Add(drawing);
location += groupsize;
}
blackBrush.Drawing = checkersDrawingGroup;
swatch.Fill = blackBrush;
swatch.MouseUp += new MouseButtonEventHandler(loadSwatchResources);
ug_Thumbnails.Children.Add(swatch);
You need to use the Rectangle in System.Windows.Shapes when trying to reference a rectangle in WPF. This is specifically for rectangles in WPF and as such is a bit different than the System.Drawing rectangle class. You should be able to cast to this version of rectangle since it derives from FrameworkElement. See http://msdn.microsoft.com/en-us/library/system.windows.shapes.rectangle(v=vs.110).aspx for more info.
I'm actually trying to add in a asynchronous way some UIElement to my Canvas define in my MainPage.
As I understand the basic way to add UIElement to a Canvas is to add it on his UIElementCollection, for example with a Line I should do like that:
Line line = new Line();
// Line attributes
line.Stroke = new SolidColorBrush(Colors.Purple);
line.StrokeThickness = 15;
Point point1 = new Point();
point1.X = 0;
point1.Y = 0;
Point point2 = new Point();
point2.X = 480;
point2.Y = 720;
line.X1 = point1.X;
line.Y1 = point1.Y;
line.X2 = point2.X;
line.Y2 = point2.Y;
// Line attributes
MyCanvas.Children.Add(line);
Let's imagine that I have a Class call Graphics that needs to access this Canvas in order to draw on it.
public class Graphics
{
public void drawLine()
{
//Using Dispatcher in order to access the main UI thread
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
Line line = new Line();
// Line attributes
/**
* Here I want to access the Canvas of the MainPage
* I have no Idea what to put here !!!
*
**/
});
}
}
In the place "I have no Idea what to put here !!!" I tried to access directly to the MainPage Canvas --> FAIL
I tried to declare a public static UIElementCollection in the MainPage in order ta add my UIElement then pass it to the Canvas but not possible because UIElementCollection has no constructor --> FAIL
But those ideas seems to be dirty coding and not really elegant. So through my research I see that MVVM should do the magic. But all the tutorial I found were doing the data-biding through the xaml file which can't be use in my case.
So I have 2 questions:
First: How is use the UIElementCollection of the Canvas? (is there a hidden method called who draws it, like Paint or Repaint in JAVA?)
Second: If I want to follow the MVVM pattern can I consider the MainPage as my View, the Graphics class as my ViewModel and the UIElement as my Model?
This is a really basic example but should get you going in the correct direction.
Graphics.cs
public class Graphics
{
public ObservableCollection<UIElement> UIElements { get; set; }
int poisiton = 0;
private Timer backgroundTimer;
public Graphics()
{
this.UIElements = new ObservableCollection<UIElement>();
this.backgroundTimer = new Timer(new TimerCallback((timer) => {
Deployment.Current.Dispatcher.BeginInvoke(() => this.GenerateLine());
}), null, 2000, 3000);
}
private void GenerateLine()
{
Line line = new Line();
// Line attributes
line.Stroke = new SolidColorBrush(Colors.Purple);
line.StrokeThickness = 15;
Point point1 = new Point();
point1.X = this.poisiton;
point1.Y = this.poisiton;
Point point2 = new Point();
point2.X = this.poisiton;
point2.Y = this.poisiton + 30;
line.X1 = point1.X;
line.Y1 = point1.Y;
line.X2 = point2.X;
line.Y2 = point2.Y;
// Line attributes
this.poisiton += 10;
UIElements.Add(line);
}
}
MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
this.Loaded += MainPage_Loaded;
// Sample code to localize the ApplicationBar
//BuildLocalizedApplicationBar();
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var graphics = new Graphics();
this.ContentPanel.DataContext = graphics;
}
MainPage.xaml
<Canvas x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ItemsControl ItemsSource="{Binding UIElements}">
</ItemsControl>
</Canvas>
I hope this helps.