How to animate a custom shape object in c# WPF - c#

I am relatively new to c# and WPF, maybe that’s why I cannot find the (probably very obvious) answer to my problem. I have been trying and googling but with no success.
I have a custom shape class that returns 3 RectangleGeometries in a GeometryGroup. The 3 corresponding rectangles can be displayed in a Canvas in MainWindow as expected. I would now like to animate each of the rectangles individually, say drop the first one to the bottom of the canvas, rotate the second one and animate the width of the third one.
My own research says the key are Dependency Properties. So I registered them but I couldn’t get them to do any changes on the rectangles.
Preferably, I would do all this in code behind. Only the Canvas
has been added in XAML. Can it be done? Here is some code to work with.
Thank you in advance
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Test1
{
public partial class MainWindow : Window
{
CustomShape customShape = new CustomShape();
public MainWindow()
{
InitializeComponent();
customShape.Fill = Brushes.Blue;
cnvMain.Children.Add(customShape);
}
}
class CustomShape : Shape
{
private Rect rect1, rect2, rect3;
private RectangleGeometry rg1, rg2, rg3;
private GeometryGroup allRectangleGeometries = new GeometryGroup();
//Constructor
public CustomShape()
{
makeCustomShape();
}
private void makeCustomShape()
{
rect1 = new Rect(50, 20, 100, 50);
rg1 = new RectangleGeometry(rect1);
allRectangleGeometries.Children.Add(rg1);
rect2 = new Rect(200, 20, 60, 20);
rg2 = new RectangleGeometry(rect2);
allRectangleGeometries.Children.Add(rg2);
rect3 = new Rect(300, 20, 200, 80);
rg3 = new RectangleGeometry(rect3);
allRectangleGeometries.Children.Add(rg3);
}
protected override Geometry DefiningGeometry
{
get
{
return allRectangleGeometries;
}
}
}
}

Looks like I found an answer myself.
I implemented 3 Dependency Properties and a Callback method that is executed every time a property changes.
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Test1
{
public partial class MainWindow : Window
{
CustomShape customShape = new CustomShape();
public MainWindow()
{
InitializeComponent();
customShape.Fill = Brushes.Blue;
cnvMain.Children.Add(customShape);
}
private void ButtonAnimate_Clicked(object sender, RoutedEventArgs e)
{
DoubleAnimation rec1Animation = new DoubleAnimation(500, TimeSpan.FromSeconds(1));
customShape.BeginAnimation(CustomShape.Rec1YProperty, rec1Animation);
DoubleAnimation rec2Animation = new DoubleAnimation(360, TimeSpan.FromSeconds(1));
customShape.BeginAnimation(CustomShape.Rec2RotateProperty, rec2Animation);
DoubleAnimation rec3Animation = new DoubleAnimation(400, TimeSpan.FromSeconds(1));
customShape.BeginAnimation(CustomShape.Rec3WidthProperty, rec3Animation);
}
}
class CustomShape : Shape
{
private Rect rect1, rect2, rect3;
private RectangleGeometry rg1, rg2, rg3;
private GeometryGroup allRectangleGeometries = new GeometryGroup();
public double Rec1Y
{
get { return (double)GetValue(Rec1YProperty); }
set { SetValue(Rec1YProperty, value); }
}
public static readonly DependencyProperty Rec1YProperty =
DependencyProperty.Register("Rec1Y", typeof(double), typeof(CustomShape), new PropertyMetadata(20d, new PropertyChangedCallback(OnAnyPropertyChanged)));
public double Rec2Rotate
{
get { return (double)GetValue(Rec2RotateProperty); }
set { SetValue(Rec2RotateProperty, value); }
}
public static readonly DependencyProperty Rec2RotateProperty =
DependencyProperty.Register("Rec2Rotate", typeof(double), typeof(CustomShape), new PropertyMetadata(0d, new PropertyChangedCallback(OnAnyPropertyChanged)));
public double Rec3Width
{
get { return (double)GetValue(Rec3WidthProperty); }
set { SetValue(Rec3WidthProperty, value); }
}
public static readonly DependencyProperty Rec3WidthProperty =
DependencyProperty.Register("Rec3Width", typeof(double), typeof(CustomShape), new PropertyMetadata(200d, new PropertyChangedCallback(OnAnyPropertyChanged)));
//Constructor
public CustomShape()
{
makeCustomShape();
}
private void makeCustomShape()
{
rect1 = new Rect(50, Rec1Y, 100, 50);
rg1 = new RectangleGeometry(rect1);
allRectangleGeometries.Children.Add(rg1);
rect2 = new Rect(200, 20, 60, 20);
rg2 = new RectangleGeometry(rect2);
rg2.Transform = new RotateTransform(Rec2Rotate, 230, 30);
allRectangleGeometries.Children.Add(rg2);
rect3 = new Rect(300, 20, Rec3Width, 80);
rg3 = new RectangleGeometry(rect3);
allRectangleGeometries.Children.Add(rg3);
}
private static void OnAnyPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
CustomShape customShape = source as CustomShape;
customShape.allRectangleGeometries.Children.Clear();
customShape.makeCustomShape();
}
protected override Geometry DefiningGeometry
{
get
{
return allRectangleGeometries;
}
}
}
}

Related

Create custom Shape Control in UWP (Universal Windows Apps), Windows 10

I want to create a custom Shape control, that paints different shapes like Polygon, Ellipse, Rectangle, etc, depending on some custom properties.
I was able to create a custom template control ColorShape like this:
<Style TargetType="local:CustomShape">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomShape">
<ContentControl x:Name="shapeParent">
</ContentControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And then, override the OnTemplateChanged method, and insert a corresponding Shape control inside the shapeParent ContentControl
But what I'd like is to actually extend the Shape, so I can treat all the shapes, framewok and custom, in the same way.
In WPF we were able to extend the Shape and override the property DefiningGeometry.
In UWP it doesn´t exist any DefiningGeometry property to override.
How is it possible to create a custom Shape control and define the corresponding Geometry?
The only way I found to create custom shapes in UWP is to extend the Path class and set its Data property.
Updating the Data property to account for changes in other dependency properties (like Width) must not be done in layouting relevant sections, like the LayoutUpdated event or the ArrangeOverride method.
Setting Data leads to another layout run, so setting it in anything that is called during that would lead to an exception:
Layout cycle detected. Layout could not complete
The way I use is to register handler for property changed events and update Data in them.
I have written a blog post that explains it in a bit more detail.
This is the example I used:
public class CandlestickShape : Path
{
public double StartValue
{
get { return Convert.ToDouble(GetValue(StartValueProperty)); }
set { SetValue(StartValueProperty, value); }
}
public static readonly DependencyProperty StartValueProperty =
DependencyProperty.Register("StartValue", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
public double EndValue
{
get { return Convert.ToDouble(GetValue(EndValueProperty)); }
set { SetValue(EndValueProperty, value); }
}
public static readonly DependencyProperty EndValueProperty =
DependencyProperty.Register("EndValue", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
public double MinValue
{
get { return Convert.ToDouble(GetValue(MinValueProperty)); }
set { SetValue(MinValueProperty, value); }
}
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
public double MaxValue
{
get { return Convert.ToDouble(GetValue(MaxValueProperty)); }
set { SetValue(MaxValueProperty, value); }
}
public static readonly DependencyProperty MaxValueProperty =
DependencyProperty.Register("MaxValue", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
/// <summary>
/// Defines how many Pixel should be drawn for one Point
/// </summary>
public double PixelPerPoint
{
get { return Convert.ToDouble(GetValue(PointsPerPixelProperty)); }
set { SetValue(PointsPerPixelProperty, value); }
}
public static readonly DependencyProperty PointsPerPixelProperty =
DependencyProperty.Register("PixelPerPoint", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
public CandlestickShape()
{
this.RegisterPropertyChangedCallback(CandlestickShape.WidthProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.StartValueProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.EndValueProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.MinValueProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.MaxValueProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.PointsPerPixelProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
}
private void RenderAffectingPropertyChanged(DependencyObject o, DependencyProperty e)
{
(o as CandlestickShape)?.SetRenderData();
}
private void SetRenderData()
{
var maxBorderValue = Math.Max(this.StartValue, this.EndValue);
var minBorderValue = Math.Min(this.StartValue, this.EndValue);
double topLineLength = (this.MaxValue - maxBorderValue) * this.PixelPerPoint;
double bottomLineLength = (minBorderValue - this.MinValue) * this.PixelPerPoint;
double bodyLength = (this.EndValue - this.StartValue) * this.PixelPerPoint;
var fillColor = new SolidColorBrush(Colors.Green);
if (bodyLength < 0)
fillColor = new SolidColorBrush(Colors.Red);
bodyLength = Math.Abs(bodyLength);
var bodyGeometry = new RectangleGeometry
{
Rect = new Rect(new Point(0, topLineLength), new Point(this.Width, topLineLength + bodyLength)),
};
var topLineGeometry = new LineGeometry
{
StartPoint = new Point(this.Width / 2, 0),
EndPoint = new Point(this.Width / 2, topLineLength)
};
var bottomLineGeometry = new LineGeometry
{
StartPoint = new Point(this.Width / 2, topLineLength + bodyLength),
EndPoint = new Point(this.Width / 2, topLineLength + bodyLength + bottomLineLength)
};
this.Data = new GeometryGroup
{
Children = new GeometryCollection
{
bodyGeometry,
topLineGeometry,
bottomLineGeometry
}
};
this.Fill = fillColor;
this.Stroke = new SolidColorBrush(Colors.Black);
}
protected override Size ArrangeOverride(Size finalSize)
{
double height = (MaxValue - MinValue) * PixelPerPoint;
return new Size(this.Width, height);
}
protected override Size MeasureOverride(Size availableSize)
{
double height = (MaxValue - MinValue) * PixelPerPoint;
return new Size(this.Width, height);
}
}

How to access the picturebox from a new class?

Here is my "Card" class:
public class Card
{
public Card()
{
}
public void Generate()
{
frmMain0 frmMain0 = new frmMain0();
Player Spieler = new Player();
//some code here
Graphics g = frmMain0.pbCard.CreateGraphics();
SolidBrush bWhite = new SolidBrush(Color.White);
Font fntDebug = new Font("Arial", 8);
g.DrawString("fsdfsdfcdsfcdscfdsfsdfsdfs", fntDebug, bWhite, 10, 10);
}
}
And here is the main Form:
public partial class frmMain0 : Form
{
public frmMain0()
{
InitializeComponent();
}
private void frmMain0_Load(object sender, EventArgs e)
{
Card Karte = new Card();
Karte.Generate();
}
}
Visual Studio lets me debug the code but I don't see the text on my picture, what am I doing from here?

Horizontal divider of the StatusStrip

In a Windows.Forms application, I want to change the color of the horizontal divider line of the StatusStrip, or make this line invisible. Any ideas?
This is the I'm referring to:
file: Program.cs
using System;
using System.Data;
using System.Drawing;
using System.Data.Linq;
using System.Windows.Forms;
namespace test {
class Program {
[STAThread]
static void Main() {
Application.Run(new FormMain());
}
}
}
file: FormMain.cs
using System;
using System.Data;
using System.Drawing;
using System.Data.Linq;
using System.Windows.Forms;
namespace test {
class Vars {
public class Colors {
public static Color BackRed = Color.FromArgb(040, 000, 000);
public static Color ForeRed = Color.FromArgb(240, 120, 120);
public static Color BackGrn = Color.FromArgb(000, 040, 000);
public static Color ForeGrn = Color.FromArgb(120, 240, 120);
public static Color BackBlu = Color.FromArgb(000, 000, 040);
public static Color ForeBlu = Color.FromArgb(120, 120, 240);
}
}
class FormMain : Form {
MenuStrip menuStrip = new MenuStrip();
StatusStrip statusStrip = new StatusStrip();
public FormMain() {
this.FormMain_Setup();
}
private void FormMain_Setup() {
this.Top = 20;
this.Left = 20;
this.Width = 1200;
this.Height = 675;
this.BackColor = Vars.Colors.BackBlu;
this.ForeColor = Vars.Colors.ForeBlu;
this.MaximizeBox = false;
this.StartPosition = FormStartPosition.Manual;
this.FormBorderStyle = FormBorderStyle.Fixed3D;
this.KeyDown += FormMain_KeyDown;
this.FormMain_MenuStrip_Setup();
this.FormMain_StatusStrip_Setup();
}
private void FormMain_StatusStrip_Setup() {
this.statusStrip.Height = 30;
this.statusStrip.AutoSize = false;
this.statusStrip.BackColor = Vars.Colors.BackRed;
this.statusStrip.ForeColor = Vars.Colors.ForeRed;
this.statusStrip.SizingGrip = false;
this.Controls.Add(statusStrip);
}
private void FormMain_MenuStrip_Setup() {
this.menuStrip.Height = 30;
this.menuStrip.AutoSize = false;
this.menuStrip.BackColor = Vars.Colors.ForeGrn;
this.menuStrip.ForeColor = Vars.Colors.BackGrn;
this.Controls.Add(menuStrip);
}
private void FormMain_KeyDown(object sender, KeyEventArgs e) {
this.FormMain_Exit();
}
private void FormMain_Exit() {
this.Close();
}
}
}
I found this 6+ years old question while I was googling around. I don't think it remains a problem for the OP. Just for the future readers.
When you add a StatusStrip instance by the designer or code, you will see a thin horizontal line over the top of the control.
You can get rid of this line by explicitly set the StatusStrip.BackColor property to any color. In the designer, change the color to anything and set it back to the inherited one (the Form's) and it will disappear. Alternatively, in code, set the property to itself:
private void FormMain_StatusStrip_Setup()
{
this.statusStrip.BackColor = this.statusStrip.BackColor;
//...
}
See Winforms ToolStrip.BackColor returns wrong color for more details about this behavior.
In your case, obviously the BackColor trick has no effect, the line remains as we can see in your image. This could be a result of a custom ToolStripRenderer if you have one assigned to the StatusStrip.Renderer property which uses the default values and ways to render the strips including the borders.
Consider this example:
public class FormMain
{
public FormMain() : base()
{
this.BackColor = Color.Black;
this.statusStrip.Renderer = new MyCustomRenderer();
}
}
public class MyCustomRenderer : ToolStripProfessionalRenderer
{
public MyCustomRenderer() : base(new MyColorTable()) { }
}
public class MyColorTable : ProfessionalColorTable
{
public override Color StatusStripGradientBegin => Color.Black;
public override Color StatusStripGradientEnd => Color.Black;
// ...
}
Here, you need to override the renderer's OnRenderToolStripBorder method to prevent drawing the border of the StatusStrip.
public class MyCustomRenderer : ToolStripProfessionalRenderer
{
public MyCustomRenderer() : base(new MyColorTable()) { }
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
{
if (!(e.ToolStrip is StatusStrip)) base.OnRenderToolStripBorder(e);
}
}
Or maybe to draw the line with a color of your choice:
protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e)
{
if (e.ToolStrip is StatusStrip)
e.Graphics.DrawLine(Pens.Red, 0, 0, e.ToolStrip.Width, 0);
else
base.OnRenderToolStripBorder(e);
}
When I add Application.EnableVisualStyles(); into Main() that problem line disappeared.
namespace test {
class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles(); // this line added
Application.Run(new FormMain());
}
}
}

StreamGeometry vs DrawingContext.DrawLine in WPF C#

I'm going to draw hundreds of lines in real-time. I have chosen the Visual Layer to do this. But I see that there are two different ways to draw a line here. Which one you suggest to get a better performance and speed?
1. DrawingContext.DrawLine
public class DrawingTypeOne : FrameworkElement
{
private readonly VisualCollection _visuals;
public DrawingTypeOne(double thickness)
{
var myPen = new Pen
{
Thickness = 1,
Brush = Brushes.White,
};
myPen.Freeze();
_visuals = new VisualCollection(this);
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
dc.DrawLine(myPen, new Point(0,0) , new Point(100,100));
_visuals.Add(drawingVisual);
}
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
}
}
2. StreamGeometry
public class DrawingTypeTwo : FrameworkElement
{
private readonly VisualCollection _visuals;
public DrawingTypeTwo()
{
_visuals = new VisualCollection(this);
var geometry = new StreamGeometry();
using (var gc = geometry.Open())
{
gc.BeginFigure(new Point(0, 0), true, true);
gc.LineTo(new Point(100,100), true, false);
}
geometry.Freeze();
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
dc.DrawGeometry(Brushes.Red, null, geometry);
}
_visuals.Add(drawingVisual);
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
}
}
Like I said you only need one visual and inside you can have all your lines.
Take a look at this:
First we define multiple drawings inside our drawing context:
class EllipseAndRectangle : DrawingVisual
{
public EllipseAndRectangle()
{
using (DrawingContext dc = RenderOpen())
{
// Black ellipse with blue border
dc.DrawEllipse(Brushes.Black,
new Pen(Brushes.Blue, 3),
new Point(120, 120), 20, 40);
// Red rectangle with green border
dc.DrawRectangle(Brushes.Red,
new Pen(Brushes.Green, 4),
new Rect(new Point(10, 10), new Point(80, 80)));
}
}
}
This is that one speical visual or element hosting all the drawings:
public class EllAndRectHost : FrameworkElement
{
private EllipseAndRectangle _ellAndRect = new EllipseAndRectangle();
// EllipseAndRectangle instance is our only visual child
protected override Visual GetVisualChild(int index)
{
return _ellAndRect;
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
}
And this is how you can use all those things in XAML:
<local:EllAndRectHost Margin="30" ... />
I was talking about the DrawingVisual class you could inherit from instead of creating 100 visuals for 100 lines.
Regarding your question, first approach is faster. Because the second approach is in the end doing the same the first does just its wrapped nicely. DrawLine is the lowest end. You can't go any deeper than DrawLine. DrawGeometry is calling DrawLine and some other internal stuff.

A rectangle drawn with DrawingContext.DrawRectangle blocks mouse

The WPF program below puts up a window which looks like this:
Mouse-movement outside the black square causes the window title to be updated with the mouse's position. The updating stops when the mouse enters the square.
I'd like for MouseMove to continue to trigger even when the mouse is over the square. Is there a way to do this?
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Wpf_Particle_Demo
{
class DrawingVisualElement : FrameworkElement
{
public DrawingVisual visual;
public DrawingVisualElement() { visual = new DrawingVisual(); }
protected override int VisualChildrenCount { get { return 1; } }
protected override Visual GetVisualChild(int index) { return visual; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var canvas = new Canvas();
Content = canvas;
var element = new DrawingVisualElement();
canvas.Children.Add(element);
CompositionTarget.Rendering += (s, e) =>
{
using (var dc = element.visual.RenderOpen())
dc.DrawRectangle(Brushes.Black, null, new Rect(0, 0, 50, 50));
};
MouseMove += (s, e) => Title = e.GetPosition(canvas).ToString();
}
}
}
By far the simplest way is to use the "tunneling" event on the window, PreviewMouseDown. It is delivered to the window first and works its way up the hierarchy. So it doesn't matter at all which other elements you have in the window. In code:
public partial class Window1 : Window {
public Window1() {
InitializeComponent();
this.PreviewMouseMove += new MouseEventHandler(Window1_PreviewMouseMove);
}
void Window1_PreviewMouseMove(object sender, MouseEventArgs e) {
this.Title = e.GetPosition(this).ToString();
}
}
You will need to Capture the mouse this will allow your Canvas to continue to respond to the MouseMove Event, Try something like this it will update your coordinates as long as the Mouse is Pressed
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var canvas = new Canvas();
Content = canvas;
var element = new DrawingVisualElement();
canvas.Children.Add(element);
CompositionTarget.Rendering += (s, e) =>
{
using (var dc = element.visual.RenderOpen())
dc.DrawRectangle(Brushes.Black, null, new Rect(0, 0, 50, 50));
};
Mouse.Capture(canvas);
MouseDown += (s, e) => Mouse.Capture((UIElement)s);
MouseMove += (s, e) => Title = e.GetPosition(canvas).ToString();
MouseUp += (s, e) => Mouse.Capture(null);
}
Second Method
public MainWindow()
{
InitializeComponent();
var canvas = new Canvas();
Content = canvas;
DrawingVisualElement element = new DrawingVisualElement();
Grid myElement = new Grid();
canvas.Children.Add(myElement);
CompositionTarget.Rendering += (s, e) =>
{
using (var dc = element.visual.RenderOpen())
{
dc.DrawRectangle(Brushes.Black, null, new Rect(100, 0, 50, 50));
}
DrawingImage myImage = new DrawingImage(element.visual.Drawing);
myElement.Height = myImage.Height;
myElement.Width = myImage.Width;
myElement.Background = new ImageBrush(myImage);
};
MouseMove += (s, e) => Title = e.GetPosition(canvas).ToString();
}
Using a Hook be sure to put a using System.Windows.Interop;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var canvas = new Canvas();
Content = canvas;
var element = new DrawingVisualElement();
canvas.Children.Add(element);
CompositionTarget.Rendering += (s, e) =>
{
using (var dc = element.visual.RenderOpen())
{
dc.DrawRectangle(Brushes.Black, null, new Rect(0, 0, 50, 50));
}
};
this.SourceInitialized += new EventHandler(OnSourceInitialized);
}
void OnSourceInitialized(object sender, EventArgs e)
{
HwndSource source = (HwndSource)PresentationSource.FromVisual(this);
source.AddHook(new HwndSourceHook(HandleMessages));
}
IntPtr HandleMessages(IntPtr hwnd, int msg,IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == 0x200)
Title = Mouse.GetPosition(this).ToString(); // because I did not want to split the lParam into High/Low values for Position information
return IntPtr.Zero;
}
}
The answer is so much easier. All you need is to set element.IsHitTestVisible=false;

Categories