I am trying to create a custom wedge shape class in WPF, derived from the abstract Shape class, and be able to define it in XAML just like any other shape.
I've been searching on Google for a complete tutorial on how to do this but all I am finding is stuff on custom controls. What I want is to create a wedge class that allows me to specify inner radius, outer radius, how many sections out of 360 degrees this wedge will be one of (i.e., if I want to fit 24 of these wedges around the circle, this wedge will be the right size to be one of those 24), and its position (which one of those 24 spaces it will occupy). These are all dependency properties, and I've registered them.
The DefiningGeometry property calls a method that does all the logic for calculating points and drawing the shape.
The problem I'm running into is that VS2010 created a style automatically with a setter of property "Template". Then, when I compile, it gives me an error saying:
"Error 3 Cannot find the Style Property 'Template' on the type 'WpfApplication1.Wedge'. Line 8 Position 17. C:\Users\rflint\Desktop\WpfApplication1\WpfApplication1\Themes\Generic.xaml 8 17 WpfApplication1"
If I comment this out everything compiles but the wedge is not shown on the form. How do I implement this Template setter property? Do I need to?
XAML:
<my:Wedge CenterPoint="300,300" InnerRadius="100" OuterRadius="200" Sections="12" Position="0" Stroke="Transparent" Fill="#FFCC7329" />
C#:
protected override Geometry DefiningGeometry
{
get
{
using (StreamGeometryContext context = geometry.Open())
{
DrawWedgeGeometry(context);
}
return geometry;
}
}
private void DrawWedgeGeometry(StreamGeometryContext context)
{
double wedgeAngle = 360/Sections;
double angleA = (Position * wedgeAngle) + (wedgeAngle/2);
double angleB = (Position * wedgeAngle) - (wedgeAngle/2);
Point point1 = getPointOnCircle(CenterPoint, InnerRadius, angleA);
Point point2 = getPointOnCircle(CenterPoint, InnerRadius, angleB);
Point point3 = getPointOnCircle(CenterPoint, OuterRadius, angleB);
Point point4 = getPointOnCircle(CenterPoint, OuterRadius, angleA);
Size innerSize = new Size(InnerRadius, InnerRadius);
Size outerSize = new Size(OuterRadius, OuterRadius);
context.BeginFigure(point1, true, true);
context.ArcTo(point2, innerSize, 90, false, SweepDirection.Clockwise, true, true);
context.LineTo(point3, true, true);
context.ArcTo(point4, outerSize, 90, false, SweepDirection.Counterclockwise, true, true);
}
I've just tried it on VS2012 and it works fine, at least with a simple ellipse geometry:
public sealed class Wedge : Shape
{
public Double Radius
{
get { return (Double)this.GetValue(RadiusProperty); }
set { this.SetValue(RadiusProperty, value); }
}
public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register(
"Radius", typeof(Double), typeof(Wedge), new PropertyMetadata(0.0));
protected override Geometry DefiningGeometry
{
get {return new EllipseGeometry(new Point(0, 0), this.Radius, this.Radius); }
}
}
And the XAML:
<local:Wedge Radius="50" Stroke="Black" Fill="Yellow" StrokeThickness="2" Canvas.Top="100" Canvas.Left="100" />
Let me give a simple solution to your problem:
public class Wedge : Shape
{
public Double StartAngle
{
get { return (Double)GetValue(StartAngleProperty); }
set { SetValue(StartAngleProperty, value); }
}
public static readonly DependencyProperty StartAngleProperty =
DependencyProperty.Register("StartAngle", typeof(Double), typeof(Wedge), new PropertyMetadata(0d));
public Double EndAngle
{
get { return (Double)GetValue(EndAngleProperty); }
set { SetValue(EndAngleProperty, value); }
}
public static readonly DependencyProperty EndAngleProperty =
DependencyProperty.Register("EndAngle", typeof(Double), typeof(Wedge), new PropertyMetadata(0d));
public Point Center
{
get { return (Point)GetValue(CenterProperty); }
set { SetValue(CenterProperty, value); }
}
public static readonly DependencyProperty CenterProperty =
DependencyProperty.Register("Center", typeof(Point), typeof(Wedge), new PropertyMetadata(new Point()));
public Double InnerRadius
{
get { return (Double)GetValue(InnerRadiusProperty); }
set { SetValue(InnerRadiusProperty, value); }
}
public static readonly DependencyProperty InnerRadiusProperty =
DependencyProperty.Register("InnerRadius", typeof(Double), typeof(Wedge), new PropertyMetadata(0d));
public Double OuterRadius
{
get { return (Double)GetValue(OuterRadiusProperty); }
set { SetValue(OuterRadiusProperty, value); }
}
public static readonly DependencyProperty OuterRadiusProperty =
DependencyProperty.Register("OuterRadius", typeof(Double), typeof(Wedge), new PropertyMetadata(0d));
protected override Geometry DefiningGeometry
{
get
{
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext context = geometry.Open())
{
Draw(context);
}
return geometry;
}
}
private void Draw(StreamGeometryContext context)
{
var isStroked = Stroke != null & Stroke != Brushes.Transparent & StrokeThickness > 0;
var isFilled = Fill != null & Fill != Brushes.Transparent;
context.BeginFigure(
GetPointOnCircle(Center, OuterRadius, StartAngle),
isFilled,
true);
context.ArcTo(
GetPointOnCircle(Center, OuterRadius, EndAngle),
new Size(OuterRadius, OuterRadius),
0,
EndAngle - StartAngle > 180,
SweepDirection.Clockwise,
isStroked,
true);
context.LineTo(GetPointOnCircle(Center, InnerRadius, EndAngle), isStroked, true);
context.ArcTo(
GetPointOnCircle(Center, InnerRadius, StartAngle),
new Size(InnerRadius, InnerRadius),
0,
EndAngle - StartAngle > 180,
SweepDirection.Counterclockwise,
isStroked,
true);
context.LineTo(GetPointOnCircle(Center, OuterRadius, StartAngle), isStroked, true);
}
private Point GetPointOnCircle(Point center, double radius, double angle)
{
var px = center.X + radius * Math.Cos(ToRadians(angle));
var py = center.Y + radius * Math.Sin(ToRadians(angle));
return new Point(px, py);
}
public double ToRadians(double angle)
{
return angle * Math.PI / 180;
}
}
Related
The shape is not drawing correctly or not at all - that is, if I click towards the top left of the canvas, it will draw an ellipse but not anywhere else like this. Is the code logically correct? Everything seems good but perhaps I am missing something.
/* in mainwindow.xaml */
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (btnNode.IsChecked == true)
{
CreateNode(e.GetPosition(Canvas));
}
}
private void CreateNode(Point origin)
{
Node n = new Node("new_" + cmbStart.Items.Count, origin) { Fill = new SolidColorBrush(Colors.Tomato), Width = 60, Height = 60 };
Canvas.Children.Add(n);
Canvas.SetLeft(n, origin.X - n.Width/2);
Canvas.SetTop(n, origin.Y - n.Height/2);
}
/* in Node class */
private void Init(Point p)
{
X = p.X;
Y = p.Y;
}
public double Y
{
get { return (double)this.GetValue(YProperty); }
set { this.SetValue(YProperty, value); }
}
public double X
{
get { return (double) this.GetValue(XProperty); }
set { this.SetValue(XProperty, value); }
}
public static readonly DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double), typeof(Node), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double), typeof(Node), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
protected override Geometry DefiningGeometry
{
get
{
Console.WriteLine("rendering at: " + this.X + ", " + this.Y + "---Scale: " + Width);
return new EllipseGeometry(new Point(this.X, this.Y), Width, Height);
}
}
The EllipseGeometry constructor has the radiusX and radiusY parameters to pass radii, not diameters.
If you want to draw a full ellipse into the bounds of your custom control, you should use Width/2 and Height/2 as parameter values:
protected override Geometry DefiningGeometry
{
get
{
return new EllipseGeometry(new Point(X, Y), Width / 2, Height / 2);
}
}
I'm trying to create a UILabel with padding in my Xamarin.iOS app. The most popular solution in native Objective-C apps is overriding drawTextInRect:
- (void)drawTextInRect:(CGRect)rect {
UIEdgeInsets insets = {0, 5, 0, 5};
return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}
As simple as this seems, I can't quite figure out how to translate it to C#. Here's my best stab at it:
internal class PaddedLabel : UILabel
{
public UIEdgeInsets Insets { get; set; }
public override void DrawText(RectangleF rect)
{
var padded = new RectangleF(rect.X + Insets.Left, rect.Y, rext.Width + Insets.Left + Insets.Right, rect.Height);
base.DrawText(padded);
}
}
This does seem to move the label's text, but it doesn't resize the label.
I think the main issue is that I can't find the Xamarin equivalent of UIEdgeInsetsInsetRect.
Any suggestions?
The C# equivalent of the ObjC function UIEdgeInsetsInsetRect is a instance method of UIEdgeInsets named InsetRect and it's not identical to your RectangleF calculations (which is likely your problem).
To use it you can do:
public override void DrawText(RectangleF rect)
{
base.DrawText (Insets.InsetRect (rect));
}
You have to override both DrawText and TextRectForBounds.
If you don't override TextRectForBounds, the text will be clipped.
Actually, you override this method to compensate the space which is occupied by padding and ask iOS to draw the text in a bigger rectangle.
public partial class LabelWithBorder
: UILabel
{
private UIEdgeInsets EdgeInsets = new UIEdgeInsets(5, 5, 5, 5);
private UIEdgeInsets InverseEdgeInsets = new UIEdgeInsets(-5, -5, -5, -5);
public LabelWithBorder(IntPtr handle) : base(handle)
{
}
public override CoreGraphics.CGRect TextRectForBounds(CoreGraphics.CGRect bounds, nint numberOfLines)
{
var textRect = base.TextRectForBounds(EdgeInsets.InsetRect(bounds), numberOfLines);
return InverseEdgeInsets.InsetRect(textRect);
}
public override void DrawText(CoreGraphics.CGRect rect)
{
base.DrawText(EdgeInsets.InsetRect(rect));
}
}
Rather than overriding DrawText() in the subclass of UILabel, override it's intrinsic content size. This way auto-layout takes the padding into consideration. For example here's my derived class of UILabel:
public class PaddedLabel : UILabel
{
private readonly float _top;
private readonly float _left;
private readonly float _right;
private readonly float _bottom;
public PaddedLabel(float top, float left, float right, float bottom)
{
_top = top;
_left = left;
_right = right;
_bottom = bottom;
}
public override CGSize IntrinsicContentSize => new CGSize(
base.IntrinsicContentSize.Width + _left + _right,
base.IntrinsicContentSize.Height + _top + _bottom
);
}
I have created a generic Padding UIView class that wraps any IOS UI element that is derived from UIView.
Basically it nests the desired UIView into another view and takes care of all the padding work.
usage:
var myPaddedView = new PaddedUIView<UILabel>();
myPaddedView.Frame = TheActualFrame;
myPaddedView.Padding = 15f
myPaddedView.NestedView.Text = "Hello padded world"; // all the label Properties are available without side effects
Here is the class:
public class PaddedUIView<T>: UIView where T : UIView, new()
{
private float _padding;
private T _nestedView;
public PaddedUIView()
{
Initialize();
}
public PaddedUIView(RectangleF bounds)
: base(bounds)
{
Initialize();
}
void Initialize()
{
if(_nestedView == null)
{
_nestedView = new T();
this.AddSubview(_nestedView);
}
_nestedView.Frame = new RectangleF(_padding,_padding,Frame.Width - 2 * _padding, Frame.Height - 2 * _padding);
}
public T NestedView
{
get { return _nestedView; }
}
public float Padding
{
get { return _padding; }
set { if(value != _padding) { _padding = value; Initialize(); }}
}
public override RectangleF Frame
{
get { return base.Frame; }
set { base.Frame = value; Initialize(); }
}
}
The correct way to do this seems to have slightly changed since the original answer was given, and it took me awhile to figure out the new correct syntax. Here it is for anyone else who stumbles across this. This code would put padding of 5 on both the left and right sides of the view. This is in Xamarin.iOS
public override void DrawText(CGRect rect)
{
rect.X = 5;
rect.Width = rect.Width - 10; // or whatever padding settings you want
base.DrawText(AlignmentRectInsets.InsetRect(rect));
}
poupou's solution marked as the best answer doesn't work. I found another way to add padding to label.
public class PaddedLabel : UILabel
{
public PaddedLabel(IntPtr intPtr) : base(intPtr)
{
TextAlignment = UITextAlignment.Center;
}
public override CGSize IntrinsicContentSize
{
get
{
var size = base.IntrinsicContentSize;
return new CGSize(size + 16, size);
}
}
}
First I added many properties, but that's not the proper way to pass variables. How should I handle it?
Second, this control will be used like a pictureBox1 in form1 designer (or any other form designer). And in form1, I used some events of the pictureBox1. Now I need to use the events of the picturebox1 in the control, so do I need to create and use all the events in the Control code and not form1?
This is the user control code:
namespace Find_Distance
{
public partial class pictureBox1Control : UserControl
{
public static PictureBox pb1;
// blinking colors: yellow, red, yellow, transparent, repeat...
public static Brush[] cloudColors = new[] { Brushes.Yellow, Brushes.Transparent };
// current color index
public static int cloudColorIndex = 0;
public pictureBox1Control()
{
InitializeComponent();
pb1 = new PictureBox();
this.pictureBox1 = pb1;
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.ResizeRedraw, true);
}
private void pictureBox1Control_Load(object sender, EventArgs e)
{
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.Clear(Color.White);
e.Graphics.DrawImage(pictureBox1Control.pb1.Image, movingPoint);
CloudEnteringAlert.Paint(e.Graphics, currentfactor, _distance);
float distance = _kilometers / (float)1.09;//289617486; // One pixel distance is 1.09 kilometer.
Pen p;
p = new Pen(Brushes.Green);
if (_points == null)
{
return;
}
foreach (Point pt in _pointsint)
{
e.Graphics.FillEllipse(Brushes.Red, pt.X * (float)currentfactor, pt.Y * (float)currentfactor, 2f, 2f);
}
foreach (PointF pt in _movingpoints)
{
if (_Cloud == null)
{
e.Graphics.FillEllipse(Brushes.Red, (pt.X - distance) * (float)currentfactor, pt.Y * (float)currentfactor, 2f, 2f);
}
}
foreach (PointF pt in _pointtocolor)
{
e.Graphics.FillEllipse(Brushes.Yellow, pt.X * (float)currentfactor, pt.Y * (float)currentfactor, 2f, 2f);
}
if (_Cloud != null)
{
foreach (PointF pt in _Cloud)
{
e.Graphics.FillEllipse(cloudColors[cloudColorIndex], pt.X * (float)currentfactor, pt.Y * (float)currentfactor, 2f, 2f);
}
}
else
{
return;
}
foreach (var cloud in _clouds)
{
e.Graphics.FillEllipse(
cloud.Brush, cloud.Center.X, cloud.Center.Y,
cloud.Diameter, cloud.Diameter);
}
}
public static List<Ellipse> _clouds = new List<Ellipse>();
public List<Ellipse> Clouds
{
get { return _clouds; }
}
public static List<PointF> _points = new List<PointF>();
public List<PointF> Points
{
get { return _points; }
}
public static List<Point> _pointsint = new List<Point>();
public List<Point> PointsInt
{
get { return _pointsint; }
}
public static List<PointF> _movingpoints = new List<PointF>();
public List<PointF> MovingPoints
{
get { return _movingpoints; }
}
public static List<PointF> _pointtocolor = new List<PointF>();
public List<PointF> PointtoColor
{
get { return _pointtocolor; }
}
public static List<PointF> _Cloud = new List<PointF>();
public List<PointF> Cloud
{
get { return _Cloud; }
}
public static float _kilometers;
public float Kilometers
{
get { return _kilometers; }
}
public static float _distance;
public float Distance
{
get { return _distance; }
}
public static Point movingPoint;
public static double currentfactor;
public static float distance;
}
}
On this user control designer, I added a pictureBox1.
And this is for example how I'm using it in Form1:
pictureBox1Control.pb1.Image = test.jpg;
But it dosen't show the image on the pictureBox1 in the control.
Instead of using:
pictureBox1Control.pb1.Image = test.jpg;
try using
pictureBox1Control.pb1.Load(#"c:\test.jpg");
Of course, make sure the file exists. Also, you should remove "static" from this line:
public static PictureBox pb1;
If you have 10 windows opened, you might not want to display all the same image. I see that your PictureBox object is not added to the control. In the first method, after you create the PictureBox, use
this.Controls.Add(pb1)
Here is my custom control which is a kind of simple progress bar:
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace eg.BJM.Controls
{
public class HorizontalBarGauge : Canvas
{
public HorizontalBarGauge()
{
IsSigned = true;
MinValue = -100;
MaxValue = 100;
Value = 25;
MarkerValue = -1;
BorderColor = Colors.Black;
MarkerColor = Colors.Red;
BarColor = Colors.SkyBlue;
BaseColor = Colors.DodgerBlue;
ValueColor = Colors.White;
}
#region Dependecy Properties
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(HorizontalBarGauge));
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof(int), typeof(HorizontalBarGauge));
public static readonly DependencyProperty MaxValueProperty =
DependencyProperty.Register("MaxValue", typeof(int), typeof(HorizontalBarGauge));
public static readonly DependencyProperty MarkerValueProperty =
DependencyProperty.Register("MarkerValue", typeof(int), typeof(HorizontalBarGauge));
public static readonly DependencyProperty MarkerColorProperty =
DependencyProperty.Register("MarkerColor", typeof(Color), typeof(HorizontalBarGauge));
public static readonly DependencyProperty BaseColorProperty =
DependencyProperty.Register("BaseColor", typeof(Color), typeof(HorizontalBarGauge));
public static readonly DependencyProperty BarColorProperty =
DependencyProperty.Register("BarColor", typeof(Color), typeof(HorizontalBarGauge));
public static readonly DependencyProperty BorderColorProperty =
DependencyProperty.Register("BorderColor", typeof(Color), typeof(HorizontalBarGauge));
public static readonly DependencyProperty BorderThicknessProperty =
DependencyProperty.Register("BorderThickness", typeof(double), typeof(HorizontalBarGauge));
public static readonly DependencyProperty IsSignedProperty =
DependencyProperty.Register("IsSigned", typeof(bool), typeof(HorizontalBarGauge));
public static readonly DependencyProperty ShowValueProperty =
DependencyProperty.Register("ShowValue", typeof(bool), typeof(HorizontalBarGauge));
public static readonly DependencyProperty ValueColorProperty =
DependencyProperty.Register("ValueColor", typeof(Color), typeof(HorizontalBarGauge));
#endregion
[Description("Determines whether the bar shows [min,max] or [-max,+max]")]
public bool IsSigned
{
get { return (bool)GetValue( IsSignedProperty ); }
set
{
SetValue( IsSignedProperty, value );
if (value)
{
MinValue = -MaxValue;
}
else
{
MinValue = 0;
}
reassertValues();
InvalidateVisual();
}
}
[Description("Get/set the current value to show.")]
public int Value
{
get { return (int)GetValue(ValueProperty); }
set
{
SetValue( ValueProperty, value );
InvalidateVisual();
}
}
[Description("Get/set the minimum value to show.")]
public int MinValue
{
get { return (int)GetValue(MinValueProperty); }
set
{
SetValue(MinValueProperty, value);
reassertValues();
InvalidateVisual();
}
}
[Description("Get/set the maximum value to show.")]
public int MaxValue
{
get { return (int)GetValue(MaxValueProperty); }
set
{
value = Math.Max(value, 1);
SetValue(MaxValueProperty, value);
reassertValues();
InvalidateVisual();
}
}
[Description("Get/set the marker value to show. Negative => no marker.")]
public int MarkerValue
{
get { return (int)GetValue(MarkerValueProperty); }
set
{
SetValue(MaxValueProperty, value);
reassertValues();
InvalidateVisual();
}
}
[Description("Get/set the color of the marker.")]
public Color MarkerColor
{
get { return (Color)GetValue(MarkerColorProperty); }
set
{
SetValue(MarkerColorProperty, value);
Brush penBrush = new SolidColorBrush( value );
_markerPen = new Pen( penBrush, 3 );
}
}
[Description("Get/set the value text color.")]
public Color ValueColor
{
get { return (Color)GetValue(ValueColorProperty); }
set
{
SetValue(ValueColorProperty, value);
_textBrush = new SolidColorBrush(value);
}
}
[Description("Get/set the base color.")]
public Color BaseColor
{
get { return (Color)GetValue(BaseColorProperty); }
set
{
SetValue(BaseColorProperty, value);
_bodyBrush = new SolidColorBrush( value );
}
}
[Description("Get/set the bar color.")]
public Color BarColor
{
get { return (Color)GetValue(BarColorProperty); }
set
{
SetValue(BarColorProperty, value);
_barBrush = new SolidColorBrush( value );
}
}
[Description("Get/set the border color.")]
public Color BorderColor
{
get { return (Color)GetValue(BorderColorProperty); }
set
{
SetValue(BorderColorProperty, value);
var brush = new SolidColorBrush( value );
_borderPen = new Pen( brush, BorderThickness );
}
}
[Description("Get/set the border thickness.")]
public double BorderThickness
{
get { return (double)GetValue(BorderThicknessProperty); }
set
{
SetValue(BorderThicknessProperty, value);
var brush = new SolidColorBrush( BorderColor );
_borderPen = new Pen( brush, value );
}
}
[Description("Get/set whether values are displayed in the control.")]
public bool ShowValue
{
get { return (bool)GetValue( ShowValueProperty ); }
set { SetValue( ShowValueProperty, value );}
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
// Background.
drawingContext.DrawRectangle(_bodyBrush, null, _rectangle);
// Bar.
int value = Value;
if (value != 0)
{
Rect barRect;
if (IsSigned)
{
double frac = Convert.ToDouble(value)/Convert.ToDouble(MaxValue*2);
if (value < 0)
{
barRect = new Rect(_rectangle.Width/2-frac*_rectangle.Width, 0, _rectangle.Width/2, _rectangle.Height);
}
else
{
barRect = new Rect( _rectangle.Width/2, 0, frac*_rectangle.Width, _rectangle.Height );
}
drawingContext.DrawRectangle( _barBrush, null, barRect );
}
else
{
double x = (_rectangle.Width*value)/(MaxValue - MinValue);
if (x > 0)
{
barRect = new Rect( 0, 0, x, _rectangle.Height );
drawingContext.DrawRectangle( _barBrush, null, barRect );
}
}
}
// Marker.
if (MarkerValue >= 0)
{
if (IsSigned)
{
double frac = Convert.ToDouble( MarkerValue )/Convert.ToDouble( MaxValue );
double markerX1 = 0.5*_rectangle.Width*(1 + frac);
Point p0 = new Point( markerX1, 0 );
Point p1 = new Point( markerX1, _rectangle.Height );
drawingContext.DrawLine( _markerPen, p0, p1 );
double markerX2 = 0.5*_rectangle.Width*(1 - frac);
Point p2 = new Point( markerX2, 0 );
Point p3 = new Point( markerX2, _rectangle.Height );
drawingContext.DrawLine( _markerPen, p2, p3 );
}
else
{
double markerX = (_rectangle.Width*MarkerValue)/(MaxValue - MinValue);
Point p0 = new Point( markerX, 0 );
Point p1 = new Point( markerX, _rectangle.Height );
drawingContext.DrawLine( _markerPen, p0, p1 );
}
}
// Border.
drawingContext.DrawRectangle(null, _borderPen, _rectangle);
// Text.
if (ShowValue)
{
FormattedText text = new FormattedText( value.ToString(), CultureInfo.CurrentCulture,
FlowDirection.LeftToRight, s_typeface, 12, _textBrush );
Point pos = new Point( (_rectangle.Width - text.Width)/2, (_rectangle.Height - text.Height)/2 );
drawingContext.DrawText( text, pos );
}
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
_rectangle = new Rect( sizeInfo.NewSize );
InvalidateVisual();
}
private int clamp(int value)
{
return (value < MinValue ? MinValue : value > MaxValue ? MaxValue : value);
}
private void reassertValues()
{
// Make sure max and min are correct.
int oldMin = MinValue;
int newMin = oldMin;
int oldMax = MaxValue;
int newMax = oldMax;
if (oldMin > oldMax)
{
newMin = oldMax;
newMax = oldMin;
}
if (newMin != oldMin)
{
SetValue(MinValueProperty, newMin);
}
if (newMax != oldMax)
{
SetValue(MaxValueProperty, newMax);
}
// Make sure value is in [min,max].
int oldValue = Value;
int newValue = clamp(oldValue);
if (newValue != oldValue)
{
Value = newValue;
}
// Make sure marker is correctly placed.
int marker = MarkerValue;
if (marker >= 0)
{
int newMarker = clamp(marker);
if (newMarker != marker)
{
SetValue(MarkerValueProperty, newMarker);
}
}
}
private Pen _borderPen;
private Pen _markerPen;
private Brush _barBrush;
private Brush _bodyBrush;
private Rect _rectangle;
private Brush _textBrush;
private static readonly Typeface s_typeface = new Typeface( "Verdana" );
}
}
Here is the XAML which binds to it:
<controls:HorizontalBarGauge Grid.Row="1" Grid.Column="1"
Margin="8" Width="130" Height="18"
IsSigned="False"
MinValue="0"
MaxValue="100"
BorderThickness="2"
BorderColor="MidnightBlue"
ShowValue="True"
Value="{Binding StepperPosition}"
/>
When the controls initialize, the control shows the correct value. But when I change the "StepperPosition" property in later code, nothing changes. Breakpointing the code shows the ValueProperty setter does not get called even though the StepperPosition does and fires the OnPropertyChanged( "StepperPosition" ). The INPC is in a base class. So I think I've ruled out the usual causes of the binding not refreshing, and am now a bit stumped.
Monday. Right, moved on a bit. Can now refresh the visuals by hooking in to the DepedencyPropertyChanged callbacks. #HighCore this is what they look like:
The bar has two modes: speed (which needs to be +/-) and position (which is always +ve). The red line in the speed gauge shows a max speed set elsewhere. They are updated in near-real-time as they are indicators for stepper motor state.
So I created this class Sprite.cs:
class Sprite : INotifyPropertyChanged
{
double _Speed;
RectangleGeometry _SpriteRectangleGeometry;
Path _SpritePath;
public Sprite()
{
_SpriteRectangleGeometry = new RectangleGeometry();
_SpriteRectangleGeometry.Rect = new Rect(0, 0, 50, 50);
Speed = 50;
_SpritePath = new Path();
Color = Brushes.Black;
_SpritePath.Data = _SpriteRectangleGeometry;
}
public Sprite(double xpos, double ypos, double height, double width, double speed, SolidColorBrush color)
{
_SpriteRectangleGeometry = new RectangleGeometry();
_SpriteRectangleGeometry.Rect = new Rect(xpos, ypos, width, height);
this.Speed = speed;
_SpritePath = new Path();
this.Color = color;
_SpritePath.Data = _SpriteRectangleGeometry;
}
public double XPos
{
get { return _SpriteRectangleGeometry.Rect.X; }
set
{
_SpriteRectangleGeometry.Rect = new Rect(value, YPos, Width, Height);
//Notify the binding that the value has changed.
this.OnPropertyChanged("XPos");
}
}
public double YPos
{
get { return _SpriteRectangleGeometry.Rect.Y; }
set
{
_SpriteRectangleGeometry.Rect = new Rect(XPos, value, Width, Height);
//Notify the binding that the value has changed.
this.OnPropertyChanged("YPos");
}
}
public double Speed
{
get { return _Speed; }
set { _Speed = value; }
}
public double Width
{
get { return _SpriteRectangleGeometry.Rect.Width; }
set { _SpriteRectangleGeometry.Rect = new Rect(XPos, YPos, value, Height); }
}
public double Height
{
get { return _SpriteRectangleGeometry.Rect.Height; }
set { _SpriteRectangleGeometry.Rect = new Rect(XPos, YPos, Width, value); }
}
public SolidColorBrush Color
{
get { return (SolidColorBrush)_SpritePath.Fill; }
set { _SpritePath.Fill = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string strPropertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(strPropertyName));
}
}
What I want to do now is add an instance of Sprite to the Xaml, but when i do i get this error:
The value of type 'Sprite" cannot be added to collection or dictionary of type UIElementCollection
Any advice?
The Sprite should derive from the UIElement class to be added to UIElementCollection. Also you could wrap it with ContentControl and provide a DataTemplate which would create some UIElement for your sprite object.
You have to add it to the resources section rather than just inline (and make sure it has a key)
<src:Sprite x:Key="data"/>
You also need to have declared your namespace at the top of the file