Related
I am trying to use the final revision of this control from Stackexchange here:
https://codereview.stackexchange.com/questions/197197/countdown-control-with-arc-animation
When I use the code it counts down just a single second and finishes. I am not sure what issue is.
Hopefully someone can help out - thanks.
The code I am using is this:
Arc.cs
public class Arc : Shape
{
public Point Center
{
get => (Point)GetValue(CenterProperty);
set => SetValue(CenterProperty, value);
}
public static readonly DependencyProperty CenterProperty =
DependencyProperty.Register(nameof(Center), typeof(Point), typeof(Arc),
new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.AffectsRender));
public double StartAngle
{
get => (double)GetValue(StartAngleProperty);
set => SetValue(StartAngleProperty, value);
}
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register(nameof(StartAngle), typeof(double), typeof(Arc),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
public double EndAngle
{
get => (double)GetValue(EndAngleProperty);
set => SetValue(EndAngleProperty, value);
}
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register(nameof(EndAngle), typeof(double), typeof(Arc),
new FrameworkPropertyMetadata(90.0, FrameworkPropertyMetadataOptions.AffectsRender));
public double Radius
{
get => (double)GetValue(RadiusProperty);
set => SetValue(RadiusProperty, value);
}
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register(nameof(Radius), typeof(double), typeof(Arc),
new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));
public bool SmallAngle
{
get => (bool)GetValue(SmallAngleProperty);
set => SetValue(SmallAngleProperty, value);
}
public static readonly DependencyProperty SmallAngleProperty =
DependencyProperty.Register(nameof(SmallAngle), typeof(bool), typeof(Arc),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
static Arc() => DefaultStyleKeyProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(typeof(Arc)));
protected override Geometry DefiningGeometry
{
get
{
double startAngleRadians = StartAngle * Math.PI / 180;
double endAngleRadians = EndAngle * Math.PI / 180;
double a0 = StartAngle < 0 ? startAngleRadians + 2 * Math.PI : startAngleRadians;
double a1 = EndAngle < 0 ? endAngleRadians + 2 * Math.PI : endAngleRadians;
if (a1 < a0)
a1 += Math.PI * 2;
SweepDirection d = SweepDirection.Counterclockwise;
bool large;
if (SmallAngle)
{
large = false;
d = (a1 - a0) > Math.PI ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
}
else
large = (Math.Abs(a1 - a0) < Math.PI);
Point p0 = Center + new Vector(Math.Cos(a0), Math.Sin(a0)) * Radius;
Point p1 = Center + new Vector(Math.Cos(a1), Math.Sin(a1)) * Radius;
List<PathSegment> segments = new List<PathSegment>
{
new ArcSegment(p1, new Size(Radius, Radius), 0.0, large, d, true)
};
List<PathFigure> figures = new List<PathFigure>
{
new PathFigure(p0, segments, true)
{
IsClosed = false
}
};
return new PathGeometry(figures, FillRule.EvenOdd, null);
}
}
}
Countdown.xaml
<UserControl x:Class="WpfApp.Countdown"
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"
xmlns:local="clr-namespace:WpfApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="450" Loaded="Countdown_Loaded">
<Viewbox>
<Grid Width="100" Height="100">
<Border Background="#222" Margin="5" CornerRadius="50">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Label Foreground="#fff" Content="{Binding SecondsRemaining}" FontSize="50" Margin="0, -10, 0, 0" />
<Label Foreground="#fff" Content="sec" HorizontalAlignment="Center" Margin="0, -15, 0, 0" />
</StackPanel>
</Border>
<uc:Arc
x:Name="Arc"
Center="50, 50"
StartAngle="-90"
EndAngle="-90"
Stroke="#45d3be"
StrokeThickness="5"
Radius="45" />
</Grid>
</Viewbox>
</UserControl>
Countdown.xaml.cs
public partial class Countdown : UserControl
{
public Duration Duration
{
get => (Duration)GetValue(DurationProperty);
set => SetValue(DurationProperty, value);
}
public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register(nameof(Duration), typeof(Duration), typeof(Countdown), new PropertyMetadata(new Duration()));
public int SecondsRemaining
{
get => (int)GetValue(SecondsRemainingProperty);
set => SetValue(SecondsRemainingProperty, value);
}
public static readonly DependencyProperty SecondsRemainingProperty =
DependencyProperty.Register(nameof(SecondsRemaining), typeof(int), typeof(Countdown), new PropertyMetadata(0));
public event EventHandler Elapsed;
private readonly Storyboard _storyboard = new Storyboard();
public Countdown()
{
InitializeComponent();
DoubleAnimation animation = new DoubleAnimation(-90, 270, Duration);
Storyboard.SetTarget(animation, Arc);
Storyboard.SetTargetProperty(animation, new PropertyPath(nameof(Arc.EndAngle)));
_storyboard.Children.Add(animation);
DataContext = this;
}
private void Countdown_Loaded(object sender, EventArgs e)
{
if (IsVisible)
Start();
}
public void Start()
{
Stop();
_storyboard.CurrentTimeInvalidated += Storyboard_CurrentTimeInvalidated;
_storyboard.Completed += Storyboard_Completed;
_storyboard.Begin();
}
public void Stop()
{
_storyboard.CurrentTimeInvalidated -= Storyboard_CurrentTimeInvalidated;
_storyboard.Completed -= Storyboard_Completed;
_storyboard.Stop();
}
private void Storyboard_CurrentTimeInvalidated(object sender, EventArgs e)
{
ClockGroup cg = (ClockGroup)sender;
if (cg.CurrentTime == null) return;
TimeSpan elapsedTime = cg.CurrentTime.Value;
SecondsRemaining = (int)Math.Ceiling((Duration.TimeSpan - elapsedTime).TotalSeconds);
}
private void Storyboard_Completed(object sender, EventArgs e)
{
if (IsVisible)
Elapsed?.Invoke(this, EventArgs.Empty);
}
}
Your control is not properly initialized. You are currently not handling the property changes of the Duration property.
The dependency property values are applied after the control is instantiated (the constructor has returned): the XAML engine creates the element instance and then assigns the resources (e.g. a Style) and local values.
Therefore, your control will currently configure the animation (in the constructor) using the property's default Duration value (which is Duration.Automatic).
Generally, you must always assume that control properties are changing, e.g., via data binding or animation. To handle this scenarios you must register a dependency property changed callback - at least for every public property that has a direct impact on the behavior of the control.
SecondsRemaining should be a read-only dependency property.
You should use a TextBlock instead of a Label to display text.
To fix your issue, you must register a property changed callback for the Duration property to update the DoubleAnimation that depends on the value. Then store the actual DoubleAnimation in a private property, so that you can change its Duration on property changes:
public partial class Countdown : UserControl
{
public Duration Duration
{
get => (Duration)GetValue(DurationProperty);
set => SetValue(DurationProperty, value);
}
// Register the property changed callback
public static readonly DependencyProperty DurationProperty = DependencyProperty.Register(
nameof(Duration),
typeof(Duration),
typeof(Countdown),
new PropertyMetadata(new Duration(), OnDurationChanged));
// Store the DoubleAnimation in order to modify the Duration on property changes
private Timeline Timeline { get; set; }
public Countdown()
{
InitializeComponent();
// Store the DoubleAnimation in order to modify the Duration on property changes
this.Timeline = new DoubleAnimation(-90, 270, Duration);
Storyboard.SetTarget(this.Timeline, this.Arc);
Storyboard.SetTargetProperty(this.Timeline, new PropertyPath(nameof(Arc.EndAngle)));
_storyboard.Children.Add(this.Timeline);
DataContext = this;
}
// Handle the Duration property changes
private static void OnDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var this_ = d as Countdown;
this_.Timeline.Duration = (Duration)e.NewValue;
}
}
First,I need to draw a vertical line and a horizontal line. I used the GeometryGroup to realize this.It looks like this
The custom class code:
public class QuadrantGate1 : Shape
{
#region Constructors
/// <summary>
/// Instantiate a new instance of a line.
/// </summary>
public QuadrantGate1()
{
}
#endregion
#region Dynamic Properties
public double VerticalY1
{
get { return (double)GetValue(VerticalY1Property); }
set { SetValue(VerticalY1Property, value); }
}
// Using a DependencyProperty as the backing store for VerticalY1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalY1Property =
DependencyProperty.Register("VerticalY1", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double VerticalY2
{
get { return (double)GetValue(VerticalY2Property); }
set { SetValue(VerticalY2Property, value); }
}
// Using a DependencyProperty as the backing store for VerticalY2. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalY2Property =
DependencyProperty.Register("VerticalY2", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(256.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double VerticalX
{
get { return (double)GetValue(VerticalXProperty); }
set { SetValue(VerticalXProperty, value); }
}
// Using a DependencyProperty as the backing store for VerticalX1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalXProperty =
DependencyProperty.Register("VerticalX", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(128.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double HorizontalX1
{
get { return (double)GetValue(HorizontalX1Property); }
set { SetValue(HorizontalX1Property, value); }
}
// Using a DependencyProperty as the backing store for HorizontalX1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalX1Property =
DependencyProperty.Register("HorizontalX1", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double HorizontalX2
{
get { return (double)GetValue(HorizontalX2Property); }
set { SetValue(HorizontalX2Property, value); }
}
// Using a DependencyProperty as the backing store for HorizontalX2. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalX2Property =
DependencyProperty.Register("HorizontalX2", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(256.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double HorizontalY
{
get { return (double)GetValue(HorizontalYProperty); }
set { SetValue(HorizontalYProperty, value); }
}
// Using a DependencyProperty as the backing store for HorizontalY. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalYProperty =
DependencyProperty.Register("HorizontalY", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(128.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
#endregion
#region Protected Methods and Properties
protected override Geometry DefiningGeometry
{
get
{
_geometryGroup = new GeometryGroup();
_geometryGroup.FillRule = FillRule.Nonzero;
DrawTwoLinesGeometry(_geometryGroup);
_path = new Path();
_path.Data = _geometryGroup;
return _geometryGroup;
}
}
private void DrawTwoLinesGeometry(GeometryGroup geometryGroup)
{
try
{
_lineGeometry1 = new LineGeometry()
{
StartPoint = new Point { X = HorizontalX1, Y = HorizontalY },
EndPoint = new Point { X = HorizontalX2, Y = HorizontalY }
};
_lineGeometry2 = new LineGeometry()
{
StartPoint = new Point { X = VerticalX, Y = VerticalY1 },
EndPoint = new Point { X = VerticalX, Y = VerticalY2 }
};
_geometryGroup.Children.Add(_lineGeometry1);
_geometryGroup.Children.Add(_lineGeometry2);
}
catch (Exception e)
{
}
}
#endregion
#region Private Methods and Members
private GeometryGroup _geometryGroup;
private LineGeometry _lineGeometry1;
private LineGeometry _lineGeometry2;
private Path _path;
#endregion
}
The xaml code:
Second,I need to move the two lines. When the mouse is on the horizontal line,the horizontal line can be moved up or down,the vertical line stays still.
When the mouse is on the vertical line,the vertical line can be moved left or right,the horizontal line stays still.
When the mouse is on the cross point of the two lines,both of the two lines can be moved,and the horizontal line moved up and down,the vertical line moved left and right.
I want to move two lines first,so the custom class code:
public class QuadrantGate1 : Shape
{
#region Constructors
/// <summary>
/// Instantiate a new instance of a line.
/// </summary>
public QuadrantGate1()
{
this.MouseDown += QuadrantGate_MouseDown;
this.MouseMove += QuadrantGate_MouseMove;
this.MouseLeftButtonUp += QuadrantGate_MouseLeftButtonUp;
}
#endregion
#region Dynamic Properties
public double VerticalY1
{
get { return (double)GetValue(VerticalY1Property); }
set { SetValue(VerticalY1Property, value); }
}
// Using a DependencyProperty as the backing store for VerticalY1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalY1Property =
DependencyProperty.Register("VerticalY1", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double VerticalY2
{
get { return (double)GetValue(VerticalY2Property); }
set { SetValue(VerticalY2Property, value); }
}
// Using a DependencyProperty as the backing store for VerticalY2. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalY2Property =
DependencyProperty.Register("VerticalY2", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(256.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double VerticalX
{
get { return (double)GetValue(VerticalXProperty); }
set { SetValue(VerticalXProperty, value); }
}
// Using a DependencyProperty as the backing store for VerticalX1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VerticalXProperty =
DependencyProperty.Register("VerticalX", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(128.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double HorizontalX1
{
get { return (double)GetValue(HorizontalX1Property); }
set { SetValue(HorizontalX1Property, value); }
}
// Using a DependencyProperty as the backing store for HorizontalX1. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalX1Property =
DependencyProperty.Register("HorizontalX1", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double HorizontalX2
{
get { return (double)GetValue(HorizontalX2Property); }
set { SetValue(HorizontalX2Property, value); }
}
// Using a DependencyProperty as the backing store for HorizontalX2. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalX2Property =
DependencyProperty.Register("HorizontalX2", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(256.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
public double HorizontalY
{
get { return (double)GetValue(HorizontalYProperty); }
set { SetValue(HorizontalYProperty, value); }
}
// Using a DependencyProperty as the backing store for HorizontalY. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HorizontalYProperty =
DependencyProperty.Register("HorizontalY", typeof(double), typeof(QuadrantGate1), new FrameworkPropertyMetadata(128.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
#endregion
#region Protected Methods and Properties
protected override Geometry DefiningGeometry
{
get
{
_geometryGroup = new GeometryGroup();
_geometryGroup.FillRule = FillRule.Nonzero;
DrawTwoLinesGeometry(_geometryGroup);
_path = new Path();
_path.Data = _geometryGroup;
return _geometryGroup;
}
}
private void DrawTwoLinesGeometry(GeometryGroup geometryGroup)
{
try
{
_lineGeometry1 = new LineGeometry()
{
StartPoint = new Point { X = HorizontalX1, Y = HorizontalY },
EndPoint = new Point { X = HorizontalX2, Y = HorizontalY }
};
_lineGeometry2 = new LineGeometry()
{
StartPoint = new Point { X = VerticalX, Y = VerticalY1 },
EndPoint = new Point { X = VerticalX, Y = VerticalY2 }
};
_geometryGroup.Children.Add(_lineGeometry1);
_geometryGroup.Children.Add(_lineGeometry2);
}
catch (Exception e)
{
}
}
#endregion
#region Events Methods
private void QuadrantGate_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource.GetType() == typeof(QuadrantGate1))
{
this.mouseBefore = e.GetPosition(this);
QuadrantGate1 quadrantGate = (QuadrantGate1)e.OriginalSource;
startBefore.X = VerticalX;
startBefore.Y = HorizontalY;
quadrantGate.CaptureMouse();
}
}
GeometryCollection GeometryGroupChildren(GeometryGroup geometryGroup)
{
GeometryCollection geometries = new GeometryCollection();
if (geometryGroup == null)
return null;
else
{
geometries = geometryGroup.Children;
return geometries;
}
}
private void QuadrantGate_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (e.OriginalSource != null && e.OriginalSource.GetType() == typeof(QuadrantGate1))
{
QuadrantGate1 quadrantGate = (QuadrantGate1)e.OriginalSource;
Point p = e.GetPosition(this);
VerticalX = startBefore.X + (p.X - mouseBefore.X);
HorizontalY = startBefore.Y + (p.Y - mouseBefore.Y);
}
}
}
private void QuadrantGate_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource.GetType() == typeof(QuadrantGate1))
{
QuadrantGate1 quadrantGate = (QuadrantGate1)e.OriginalSource;
quadrantGate.ReleaseMouseCapture();
}
}
#endregion
#region Private Methods and Members
private GeometryGroup _geometryGroup;
private LineGeometry _lineGeometry1;
private LineGeometry _lineGeometry2;
private Path _path;
Point mouseBefore;
Point startBefore;
#endregion
}
But the two lines don't move as I want.The movement of lines is inconsistent with mouse.
The strange part is if one of these lines _geometryGroup.Children.Add(_lineGeometry1);or this _geometryGroup.Children.Add(_lineGeometry2); is commented out, the rest line can be moved sucessfully. This is the horizontal line:
,
and the vertical line:
By implementing your code, I see render issue and you could also see if you just do the mouse down & minimize the screen, open it again you can see the line moved to a different location.
Instead of GeometryGroup, could you try StreamGeometryContext, below is the code DefiningGeometry get Property.
protected override Geometry DefiningGeometry
{
get
{
StreamGeometry geometry = new StreamGeometry();
geometry.FillRule = FillRule.EvenOdd;
using (StreamGeometryContext ctx = geometry.Open())
{
ctx.BeginFigure(new Point(HorizontalX1, HorizontalY), true /* is filled */, true /* is closed */);
ctx.LineTo(new Point(HorizontalX2, HorizontalY), true /* is stroked */, false /* is smooth join */);
ctx.BeginFigure(new Point(VerticalX, VerticalY1), true /* is filled */, true /* is closed */);
ctx.LineTo(new Point(VerticalX, VerticalY2), true /* is stroked */, false /* is smooth join */);
}
//geometry.Freeze();
_path = new Path();
_path.Data = geometry;
return geometry;
}
}
I've been interested in WPF bindings in c# and tried to bind a control to the mouse position, but after getting a frustrating "The name "[control]" does not exist in the namespace "clr-namespace:[namespace]" every time I pasted the xaml code into the editor I decided it wasn't worth the time to investigate the quirk.
Now I am attempting to simply implement a binded Arc drawing example from Stack Overflow and am getting the same eror again. (All the code for a short runnable example can be found there)
So I've combed through all the Stack Overflow solution to this issue (it actually seems rather widespread) with what appear to be sporadic and inexpiable workarounds and fixes.
This question says that
If nothing else is possible, comment the lines which use the namespace, rebuild, and then build the full project again.
I also tried rebuilding the project, reopening Visual Studio. Nothing
helped. I finally commented xaml, rebuilt the project, uncommented
xaml and it finally worked! Strange issue.
This one said to set the project to release mode and another answer-er on the same question said to define the assembly: (xmlns:Local="clr-namespace:MusicPlayer.Controls;assembly=MusicPlayer") which did not work for me either.
This person recommended changing the build target platform (x86 - x64)
I've tried pretty much all of these solution to no avail. ReSharper seems to know that Arc class exists in the namespace assigned to Local, but Visual Studio does not.
<Local:Arc Center="{Binding Path=PreviousMousePositionPixels}"
Stroke="White"
StrokeDashArray="4 4"
SnapsToDevicePixels="True"
StartAngle="0"
EndAngle="{Binding Path=DeltaAngle}"
SmallAngle="True"
Radius="40"/>
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
public sealed class Arc : Shape
{
public Point Center
{
get { return (Point)GetValue(CenterProperty); }
set { SetValue(CenterProperty, value); }
}
// Using a DependencyProperty as the backing store for Center. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CenterProperty =
DependencyProperty.Register("Center", typeof(Point), typeof(Arc)
, new FrameworkPropertyMetadata(new Point(0, 0), FrameworkPropertyMetadataOptions.AffectsRender));
public double StartAngle
{
get { return (double)GetValue(StartAngleProperty); }
set { SetValue(StartAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for StartAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register("StartAngle", typeof(double), typeof(Arc)
, new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
public double EndAngle
{
get { return (double)GetValue(EndAngleProperty); }
set { SetValue(EndAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for EndAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register("EndAngle", typeof(double), typeof(Arc)
, new FrameworkPropertyMetadata(Math.PI/2.0, FrameworkPropertyMetadataOptions.AffectsRender));
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
// Using a DependencyProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(double), typeof(Arc)
, new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));
public bool SmallAngle
{
get { return (bool)GetValue(SmallAngleProperty); }
set { SetValue(SmallAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for SmallAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SmallAngleProperty =
DependencyProperty.Register("SmallAngle", typeof(bool), typeof(Arc)
, new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
static Arc()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(typeof(Arc)));
}
protected override Geometry DefiningGeometry
{
get
{
var a0 = StartAngle < 0 ? StartAngle + 2 * Math.PI : StartAngle;
var a1 = EndAngle < 0 ? EndAngle + 2 * Math.PI : EndAngle;
if (a1<a0)
{
a1 += Math.PI * 2;
}
SweepDirection d = SweepDirection.Counterclockwise;
bool large;
if (SmallAngle)
{
large = false;
double t = a1;
if ((a1-a0)>Math.PI)
{
d = SweepDirection.Counterclockwise;
}
else
{
d = SweepDirection.Clockwise;
}
}else{
large = (Math.Abs(a1 - a0) < Math.PI);
}
Point p0 = Center + new Vector(Math.Cos(a0), Math.Sin(a0)) * Radius;
Point p1 = Center + new Vector(Math.Cos(a1), Math.Sin(a1)) * Radius;
List<PathSegment> segments = new List<PathSegment>(1);
segments.Add(new ArcSegment(p1, new Size(Radius, Radius), 0.0, large, d, true));
List<PathFigure> figures = new List<PathFigure>(1);
PathFigure pf = new PathFigure(p0, segments, true);
pf.IsClosed = false;
figures.Add(pf);
Geometry g = new PathGeometry(figures, FillRule.EvenOdd, null);
return g;
}
}
}
Define Arc inside a namespace as suggested by RobCroll. Check below code, I have included it in the Arc_Learning namespace which is referred in XAML as local.
<Window x:Class="Arc_Learning.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Arc_Learning"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:Arc
Stroke="White"
StrokeDashArray="4 4"
SnapsToDevicePixels="True"
StartAngle="0"
EndAngle="{Binding Path=DeltaAngle}"
SmallAngle="True"
Radius="40"/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
namespace Arc_Learning
{
public sealed class Arc : Shape
{
public Point Center
{
get { return (Point)GetValue(CenterProperty); }
set { SetValue(CenterProperty, value); }
}
// Using a DependencyProperty as the backing store for Center. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CenterProperty =
DependencyProperty.Register("Center", typeof(Point), typeof(Arc)
, new FrameworkPropertyMetadata(new Point(0, 0), FrameworkPropertyMetadataOptions.AffectsRender));
public double StartAngle
{
get { return (double)GetValue(StartAngleProperty); }
set { SetValue(StartAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for StartAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register("StartAngle", typeof(double), typeof(Arc)
, new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
public double EndAngle
{
get { return (double)GetValue(EndAngleProperty); }
set { SetValue(EndAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for EndAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register("EndAngle", typeof(double), typeof(Arc)
, new FrameworkPropertyMetadata(Math.PI / 2.0, FrameworkPropertyMetadataOptions.AffectsRender));
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
// Using a DependencyProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(double), typeof(Arc)
, new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));
public bool SmallAngle
{
get { return (bool)GetValue(SmallAngleProperty); }
set { SetValue(SmallAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for SmallAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SmallAngleProperty =
DependencyProperty.Register("SmallAngle", typeof(bool), typeof(Arc)
, new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
static Arc()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(typeof(Arc)));
}
protected override Geometry DefiningGeometry
{
get
{
var a0 = StartAngle < 0 ? StartAngle + 2 * Math.PI : StartAngle;
var a1 = EndAngle < 0 ? EndAngle + 2 * Math.PI : EndAngle;
if (a1 < a0)
{
a1 += Math.PI * 2;
}
SweepDirection d = SweepDirection.Counterclockwise;
bool large;
if (SmallAngle)
{
large = false;
double t = a1;
if ((a1 - a0) > Math.PI)
{
d = SweepDirection.Counterclockwise;
}
else
{
d = SweepDirection.Clockwise;
}
}
else {
large = (Math.Abs(a1 - a0) < Math.PI);
}
Point p0 = Center + new Vector(Math.Cos(a0), Math.Sin(a0)) * Radius;
Point p1 = Center + new Vector(Math.Cos(a1), Math.Sin(a1)) * Radius;
List<PathSegment> segments = new List<PathSegment>(1);
segments.Add(new ArcSegment(p1, new Size(Radius, Radius), 0.0, large, d, true));
List<PathFigure> figures = new List<PathFigure>(1);
PathFigure pf = new PathFigure(p0, segments, true);
pf.IsClosed = false;
figures.Add(pf);
Geometry g = new PathGeometry(figures, FillRule.EvenOdd, null);
return g;
}
}
}
}
try to add local reference to your window as follow:
in the opening tag of your window add this reference
xmlns:local="clr-namespace:CustomControlTest"
in code behind C# Code you missing the namespace definition
add the missing namespace line before the class definition so your code should be like this:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
namespace CustomControlTest
{
public sealed class Arc : Shape
{
public Point Center
{
get { return (Point)GetValue(CenterProperty); }
set { SetValue(CenterProperty, value); }
}
// Using a DependencyProperty as the backing store for Center. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CenterProperty =
DependencyProperty.Register("Center", typeof(Point), typeof(Arc)
, new FrameworkPropertyMetadata(new Point(0, 0), FrameworkPropertyMetadataOptions.AffectsRender));
public double StartAngle
{
get { return (double)GetValue(StartAngleProperty); }
set { SetValue(StartAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for StartAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register("StartAngle", typeof(double), typeof(Arc)
, new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
public double EndAngle
{
get { return (double)GetValue(EndAngleProperty); }
set { SetValue(EndAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for EndAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register("EndAngle", typeof(double), typeof(Arc)
, new FrameworkPropertyMetadata(Math.PI/2.0, FrameworkPropertyMetadataOptions.AffectsRender));
public double Radius
{
get { return (double)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
// Using a DependencyProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(double), typeof(Arc)
, new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));
public bool SmallAngle
{
get { return (bool)GetValue(SmallAngleProperty); }
set { SetValue(SmallAngleProperty, value); }
}
// Using a DependencyProperty as the backing store for SmallAngle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SmallAngleProperty =
DependencyProperty.Register("SmallAngle", typeof(bool), typeof(Arc)
, new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
static Arc()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(typeof(Arc)));
}
protected override Geometry DefiningGeometry
{
get
{
var a0 = StartAngle < 0 ? StartAngle + 2 * Math.PI : StartAngle;
var a1 = EndAngle < 0 ? EndAngle + 2 * Math.PI : EndAngle;
if (a1<a0)
{
a1 += Math.PI * 2;
}
SweepDirection d = SweepDirection.Counterclockwise;
bool large;
if (SmallAngle)
{
large = false;
double t = a1;
if ((a1-a0)>Math.PI)
{
d = SweepDirection.Counterclockwise;
}
else
{
d = SweepDirection.Clockwise;
}
}else{
large = (Math.Abs(a1 - a0) < Math.PI);
}
Point p0 = Center + new Vector(Math.Cos(a0), Math.Sin(a0)) * Radius;
Point p1 = Center + new Vector(Math.Cos(a1), Math.Sin(a1)) * Radius;
List<PathSegment> segments = new List<PathSegment>(1);
segments.Add(new ArcSegment(p1, new Size(Radius, Radius), 0.0, large, d, true));
List<PathFigure> figures = new List<PathFigure>(1);
PathFigure pf = new PathFigure(p0, segments, true);
pf.IsClosed = false;
figures.Add(pf);
Geometry g = new PathGeometry(figures, FillRule.EvenOdd, null);
return g;
}
}
}
}
add this namespace CustomControlTest and then it contains the class
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);
}
}
I have a program that loads pictures from an erp document handling source and allows them to be moved and resized by touch or mouse. There is a class called Picture:
using System.Windows;
using System.Windows.Controls;
namespace DocumentHandlingTouch
{
/// <summary>
/// Interaction logic for Picture.xaml
/// </summary>
public partial class Picture : UserControl
{
public Picture()
{
InitializeComponent();
DataContext = this;
}
public string ImagePath
{
get { return (string)GetValue(ImagePathProperty); }
set { SetValue(ImagePathProperty, value); }
}
public string ImageName
{
get { return (string)GetValue(ImageNameProperty); }
set { SetValue(ImageNameProperty, value); }
}
public static readonly DependencyProperty ImageNameProperty =
DependencyProperty.Register("ImageName", typeof(string), typeof(Picture), new UIPropertyMetadata(""));
// Using a DependencyProperty as the backing store for ImagePath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ImagePathProperty =
DependencyProperty.Register("ImagePath", typeof(string), typeof(Picture), new UIPropertyMetadata(""));
public string OriginalImagePath
{
get { return (string)GetValue(OriginalImagePathProperty); }
set { SetValue(OriginalImagePathProperty, value); }
}
public static readonly DependencyProperty OriginalImagePathProperty =
DependencyProperty.Register("OriginalImagePath", typeof(string), typeof(Picture), new UIPropertyMetadata(""));
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AngleProperty =
DependencyProperty.Register("Angle", typeof(double), typeof(Picture), new UIPropertyMetadata(0.0));
public double ScaleX
{
get { return (double)GetValue(ScaleXProperty); }
set { SetValue(ScaleXProperty, value); }
}
// Using a DependencyProperty as the backing store for ScaleX. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScaleXProperty =
DependencyProperty.Register("ScaleX", typeof(double), typeof(Picture), new UIPropertyMetadata(1.0));
public double ScaleY
{
get { return (double)GetValue(ScaleYProperty); }
set { SetValue(ScaleYProperty, value); }
}
// Using a DependencyProperty as the backing store for ScaleY. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScaleYProperty =
DependencyProperty.Register("ScaleY", typeof(double), typeof(Picture), new UIPropertyMetadata(1.0));
public double X
{
get { return (double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
// Using a DependencyProperty as the backing store for X. This enables animation, styling, binding, etc...
public static readonly DependencyProperty XProperty =
DependencyProperty.Register("X", typeof(double), typeof(Picture), new UIPropertyMetadata(0.0));
public double Y
{
get { return (double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
// Using a DependencyProperty as the backing store for Y. This enables animation, styling, binding, etc...
public static readonly DependencyProperty YProperty =
DependencyProperty.Register("Y", typeof(double), typeof(Picture), new UIPropertyMetadata(0.0));
}
}
Here is the class that loads the pictures. It uses a textbox under the picture to display the filename:
private void LoadPictures()
{
_canvas.Children.Clear();
double x = -10;
double y = 0;
foreach (DictionaryEntry filePath in files)
{
try
{
Picture p = new Picture();
ToolTip t = new ToolTip();
t.Content = filePath.Value;
p.ToolTip = t;
///////////////////Heres where the textblock is set////////////////
TextBlock tb = new TextBlock();
tb.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
tb.FontWeight = FontWeights.UltraBlack;
tb.TextWrapping = TextWrapping.WrapWithOverflow;
tb.Width = filePath.Value.ToString().ToString().Length * 12;// 125;
tb.Height = 25;
tb.Text = filePath.Value.ToString();
Canvas.SetTop(tb, y+ 25);
Canvas.SetLeft(tb, x + 25);
//////////////////////////////////////////////////////////////////
//External Program
if (Path.GetExtension(filePath.Key.ToString()) == ".pdf")
{
var path = new Uri("pack://application:,,,/Document%20Handling%20Viewer;component/Resources/pdf.png", UriKind.Absolute);
p.ImagePath = path.ToString();
p.OriginalImagePath = filePath.Key.ToString();
p.ImageName = filePath.Value.ToString();
}
else
{
p.ImagePath = filePath.Key.ToString();
p.OriginalImagePath = filePath.Key.ToString();
p.ImageName = filePath.Value.ToString();
}
p.Width = 100;
p.X = x;
p.Y = y;
x = x + p.Width + 5;
if (x + p.Width >= System.Windows.SystemParameters.PrimaryScreenWidth)
{
y = y + 150;
x = 0;
}
Dispatcher.Invoke((Action)(() => _canvas.Children.Add(p)));
}
catch (Exception ex)
{
MessageBox.Show("Error:" + ex.Message, "File Load Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
The problem is the textblock doesn't change its style...its not bold, its not vertically placed on the bottom and it doesnt wrap. It just hugs the picture in a plain old font.
What am I doing wrong here?
Edit I commented out the TextBlock and the image path still shows up. Is it the ImagePath dependency property that's showing up? i'm confused
You forgot to add textblock to canvas:
_canvas.Children.Add(tb);
Edit
if you have textblock in Picture usercontrol, you must not create new in code behind.
in Picture.xaml you should define name of the textblock:
<TextBlock x:Name="TextBlock1 />
in the c# code replace line: TextBlock tb = new TextBlock(); with:
TextBlock tb = p.TextBlock1;