I have WPF application and Circle ProgressBar:
<Grid>
<Path x:Name="pathRoot" Stroke="{Binding SegmentColor, ElementName=userControl}"
StrokeThickness="{Binding StrokeThickness, ElementName=userControl}"
HorizontalAlignment="Left" VerticalAlignment="Top" Height="100" Width="100">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathFigure">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="arcSegment" SweepDirection="Clockwise" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
public partial class CircularProgressBar : UserControl
{
public CircularProgressBar()
{
InitializeComponent();
Angle = (Percentage * 360) / 100;
RenderArc();
}
public int Radius
{
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
public Brush SegmentColor
{
get { return (Brush)GetValue(SegmentColorProperty); }
set { SetValue(SegmentColorProperty, value); }
}
public int StrokeThickness
{
get { return (int)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public double Percentage
{
get { return (double)GetValue(PercentageProperty); }
set { SetValue(PercentageProperty, value); }
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Percentage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PercentageProperty =
DependencyProperty.Register("Percentage", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(65d, new PropertyChangedCallback(OnPercentageChanged)));
// Using a DependencyProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register("StrokeThickness", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(5));
// Using a DependencyProperty as the backing store for SegmentColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SegmentColorProperty =
DependencyProperty.Register("SegmentColor", typeof(Brush), typeof(CircularProgressBar), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
// Using a DependencyProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(60, new PropertyChangedCallback(OnPropertyChanged)));
// 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(CircularProgressBar), new PropertyMetadata(120d, new PropertyChangedCallback(OnPropertyChanged)));
private static void OnPercentageChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.Angle = (circle.Percentage * 360) / 100;
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.RenderArc();
}
public void RenderArc()
{
Point startPoint = new Point(Radius, 0);
Point endPoint = ComputeCartesianCoordinate(Angle, Radius);
endPoint.X += Radius;
endPoint.Y += Radius;
pathRoot.Width = Radius * 2 + StrokeThickness;
pathRoot.Height = Radius * 2 + StrokeThickness;
pathRoot.Margin = new Thickness(StrokeThickness, StrokeThickness, 0, 0);
bool largeArc = Angle > 180.0;
Size outerArcSize = new Size(Radius, Radius);
pathFigure.StartPoint = startPoint;
if (startPoint.X == Math.Round(endPoint.X) && startPoint.Y == Math.Round(endPoint.Y))
endPoint.X -= 0.01;
arcSegment.Point = endPoint;
arcSegment.Size = outerArcSize;
arcSegment.IsLargeArc = largeArc;
}
private Point ComputeCartesianCoordinate(double angle, double radius)
{
// convert to radians
double angleRad = (Math.PI / 180.0) * (angle - 90);
double x = radius * Math.Cos(angleRad);
double y = radius * Math.Sin(angleRad);
return new Point(x, y);
}
}
This is my XAML with my CircleProgressBar:
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="54,507,54,75" Grid.Column="1" Height="138">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Height="153" Width="155">
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
SegmentColor="#FF878889" StrokeThickness="10" Percentage="100" />
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
Percentage="33" SegmentColor="#FF5591E8" StrokeThickness="10" />
</Grid>
</StackPanel>
Inside this controller i locate Label that show my the ProgressBar percentage but this label is not all the time in the middle on the circle
<Label Name="lblCircleProgress" Content="0%" Margin="116,561,100,127" Foreground="White" VerticalAlignment="Center" Grid.Column="1" />
So my question is how to implement this label to became in the middle of my Circle all the time after the percentage changing ?
Simplify your grid - and place everything inside it.
This way everything will center horizontally and vertically within the same visual context.
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Height="153" Width="155">
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
SegmentColor="#FF878889" StrokeThickness="10" Percentage="100" />
<DesignInControl:CircularProgressBar HorizontalAlignment="Center" VerticalAlignment="Center"
SegmentColor="#FF5591E8" StrokeThickness="10" Percentage="33" />
<Label Name="lblCircleProgress" Content="0%" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
Add it in the Grid also, delete those margin and set HorizontalAlignment to Center. I see you already have the VerticalAlignment set, so that should work and you should see it in the center of the Grid, and taking into consideration that your CircularProgressBar is inside the Grid too, it should look as expected.
Related
This is a WPF question.
I am trying to track / drag the vertices of a PathGeometry on a Canvas using Thumb Controls to drag the vertices.
It seems that the PathGeometry is scaled differently than the Thumb positions relative to the Canvas.
How can I compute the scaling ratio? Once I have that, I can use a ScaleTransform to correct it.
Thanks in advance.
Here is my XAML. I have a scale value of 3 hard coded, but it doesn't work if my window size changes:
MyControl.XAML
<UserControl x:Class="WPF_Discovery_Client.ColorOpacityControl"
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:WPF_Discovery_Client"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="{x:Type Thumb}" x:Key="RoundThumb">
<Style.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="10" />
<Setter Property="BorderThickness" Value="1" />
</Style>
</Style.Resources>
</Style>
</UserControl.Resources>
<Grid>
<Canvas Background="aqua" x:Name="MyCanvas" SizeChanged="MyCanvas_SizeChanged" Loaded="MyCanvas_Loaded" >
<Path Stroke="Black" StrokeThickness="1" Height="450" Stretch="Fill" Width="800" >
<Path.Fill>
<LinearGradientBrush ColorInterpolationMode="ScRgbLinearInterpolation" StartPoint="0,0" EndPoint="1,0">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0.17" Color="Orange"/>
<GradientStop Offset="0.34" Color="Yellow"/>
<GradientStop Offset="0.51" Color="Green"/>
<GradientStop Offset="0.68" Color="Blue"/>
<GradientStop Offset="0.85" Color="Indigo"/>
<GradientStop Offset="1.0" Color="Violet"/>
</LinearGradientBrush>
</Path.Fill>
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="MyPath" IsClosed="True" StartPoint="{Binding BottomLeftCorner, Mode=TwoWay}">
<PathFigure.Segments>
<PathSegmentCollection>
<LineSegment Point="{Binding LeftVertex, Mode=TwoWay}"/>
<LineSegment Point="{Binding MiddleVertex, Mode=TwoWay}" />
<LineSegment Point="{Binding RightVertex, Mode=TwoWay}" />
<LineSegment Point="{Binding BottomRightCorner, Mode=TwoWay}" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
<Path.RenderTransform>
<ScaleTransform ScaleX="2.0" ScaleY="2.0"/>
</Path.RenderTransform>
</Path>
<Thumb Name="LeftThumb" Style="{DynamicResource RoundThumb}" Background="White"
Width="20" Height="20" DragDelta="LeftThumb_DragDelta"
DragStarted="LeftThumb_DragStarted" DragCompleted="LeftThumb_DragCompleted"/>
<Thumb Name="MiddleThumb" Style="{DynamicResource RoundThumb}" Background="White"
Width="20" Height="20" DragDelta="MiddleThumb_DragDelta"
DragStarted="MiddleThumb_DragStarted" DragCompleted="MiddleThumb_DragCompleted"/>
<Thumb Name="RightThumb" Style="{DynamicResource RoundThumb}" Background="White"
Width="20" Height="20" DragDelta="RightThumb_DragDelta"
DragStarted="RightThumb_DragStarted" DragCompleted="RightThumb_DragCompleted"/>
</Canvas>
</Grid>
</UserControl>
And here is the code behind for the control:
MyControl.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Globalization;
using System.Windows.Controls.Primitives;
using System.ComponentModel;
namespace WPF_Discovery_Client
{
/// <summary>
/// Interaction logic for ColorOpacityControl.xaml
/// </summary>
public partial class ColorOpacityControl : UserControl, INotifyPropertyChanged
{
const int ThumbRadius = 10;
// Bottom corners
public Point BottomRightCorner
{
get { return (Point)GetValue(BottomRightCornerProperty); }
set { SetValue(BottomRightCornerProperty, value); }
}
// Using a DependencyProperty as the backing store for BottomRightCorner. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BottomRightCornerProperty =
DependencyProperty.Register("BottomRightCorner", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(0, 100)));
public Point BottomLeftCorner
{
get { return (Point)GetValue(BottomLeftCornerProperty); }
set { SetValue(BottomLeftCornerProperty, value); }
}
// Using a DependencyProperty as the backing store for BottomLeftCorner. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BottomLeftCornerProperty =
DependencyProperty.Register("BottomLeftCorner", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(0, 200)));
// Thumb center locations
public Point LeftVertex
{
get { return (Point)GetValue(LeftVertexProperty); }
set { SetValue(LeftVertexProperty, value); }
}
// Using a DependencyProperty as the backing store for LeftVertex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LeftVertexProperty =
DependencyProperty.Register("LeftVertex", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(0,266)));
public Point MiddleVertex
{
get { return (Point)GetValue(MiddleVertexProperty); }
set { SetValue(MiddleVertexProperty, value); }
}
// Using a DependencyProperty as the backing store for MiddleVertex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MiddleVertexProperty =
DependencyProperty.Register("MiddleVertex", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(100, 100)));
public Point RightVertex
{
get { return (Point)GetValue(RightVertexProperty); }
set { SetValue(RightVertexProperty, value); }
}
// Using a DependencyProperty as the backing store for RightVertex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RightVertexProperty =
DependencyProperty.Register("RightVertex", typeof(Point), typeof(ColorOpacityControl), new PropertyMetadata(new Point(100, 50)));
public ColorOpacityControl()
{
InitializeComponent();
DataContext = this;
}
private void LeftThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
LeftThumb.Background = Brushes.Red;
}
private void LeftThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
LeftThumb.Background = Brushes.White;
}
private void LeftThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
//Move the Thumb to the mouse position during the drag operation
var yadjust = MyCanvas.ActualHeight + e.VerticalChange;
var xadjust = MyCanvas.ActualWidth + e.HorizontalChange;
if ((xadjust >= 0) && (yadjust >= 0))
{
// Compute new thumb location
double X = Canvas.GetLeft(LeftThumb) + e.HorizontalChange;
double Y = Canvas.GetTop(LeftThumb) + e.VerticalChange;
// Move thumb
Canvas.SetLeft(LeftThumb, X);
Canvas.SetTop(LeftThumb, Y);
// Compute center of thumb as vertex location
LeftVertex = new Point(X + LeftThumb.Width, Y + LeftThumb.Height);
NotifyPropertyChanged("LeftVertex");
}
}
private void MiddleThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
MiddleThumb.Background = Brushes.Green;
}
private void MiddleThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
MiddleThumb.Background = Brushes.White;
}
private void MiddleThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
//Move the Thumb to the mouse position during the drag operation
var yadjust = MyCanvas.ActualHeight + e.VerticalChange;
var xadjust = MyCanvas.ActualWidth + e.HorizontalChange;
if ((xadjust >= 0) && (yadjust >= 0))
{
// Compute new thumb location
double X = Canvas.GetLeft(MiddleThumb) + e.HorizontalChange;
double Y = Canvas.GetTop(MiddleThumb) + e.VerticalChange;
// Move thumb
Canvas.SetLeft(MiddleThumb, X);
Canvas.SetTop(MiddleThumb, Y);
// Compute center of thumb as vertex location
MiddleVertex = new Point(X + MiddleThumb.Width, Y + MiddleThumb.Height);
NotifyPropertyChanged("MiddleVertex");
}
}
private void RightThumb_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
RightThumb.Background = Brushes.Yellow;
}
private void RightThumb_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
RightThumb.Background = Brushes.White;
}
private void RightThumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
//Move the Thumb to the mouse position during the drag operation
var yadjust = MyCanvas.ActualHeight + e.VerticalChange;
var xadjust = MyCanvas.ActualWidth + e.HorizontalChange;
if ((xadjust >= 0) && (yadjust >= 0))
{
// Compute new thumb location
double X = Canvas.GetLeft(RightThumb) + e.HorizontalChange;
double Y = Canvas.GetTop(RightThumb) + e.VerticalChange;
// Move thumb
Canvas.SetLeft(RightThumb, X);
Canvas.SetTop(RightThumb, Y);
// Compute center of thumb as vertex location
RightVertex = new Point(X + ThumbRadius, Y + ThumbRadius);
NotifyPropertyChanged("RightVertex");
}
}
private void MyCanvas_SizeChanged(object sender, SizeChangedEventArgs e)
{
// Adjust bottom left corners
BottomLeftCorner = new Point(0, MyCanvas.ActualHeight);
NotifyPropertyChanged("BottomLeftCorner");
// Adjust botton right corner
BottomRightCorner = new Point(MyCanvas.ActualWidth, MyCanvas.ActualHeight);
NotifyPropertyChanged("BottomRightCorner");
}
private void InitializeVertices()
{
// Initialize bottom left corner
BottomLeftCorner = new Point(ThumbRadius, MyCanvas.ActualHeight - ThumbRadius);
NotifyPropertyChanged("BottomLeftCorner");
// Initialize bottom right corner
BottomRightCorner = new Point(MyCanvas.ActualWidth - ThumbRadius, MyCanvas.ActualHeight - ThumbRadius);
NotifyPropertyChanged("BottomRightCorner");
// Initialize right vertex
RightVertex = new Point(MyCanvas.ActualWidth - ThumbRadius, ThumbRadius);
NotifyPropertyChanged("RightVertex");
// Initialize left vertex
LeftVertex = BottomLeftCorner;
NotifyPropertyChanged("LeftVertex");
// Initialize middle vertex
MiddleVertex = new Point(MyCanvas.ActualWidth * 0.5, MyCanvas.ActualHeight * 0.5);
NotifyPropertyChanged("MiddleVertex");
// Initialize Left Thumb
Canvas.SetLeft(LeftThumb, LeftVertex.X - ThumbRadius);
Canvas.SetTop(LeftThumb, LeftVertex.Y - ThumbRadius);
// Initialize Right Thumb
Canvas.SetLeft(RightThumb, RightVertex.X - ThumbRadius);
Canvas.SetTop(RightThumb, RightVertex.Y - ThumbRadius);
// Initialize Middle Thumb
Canvas.SetLeft(MiddleThumb, MiddleVertex.X - ThumbRadius);
Canvas.SetTop(MiddleThumb, MiddleVertex.Y - ThumbRadius);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion
private void MyCanvas_Loaded(object sender, RoutedEventArgs e)
{
InitializeVertices();
}
}
}
As you can see, I am setting both the vertices and the thumbs to the same position relative to the canvas. However, if you run the code, you can see that the initial triangle with the gradient fill is much smaller than it needs to be. I want the 3 thumbs to coincide with the 2 vertices and the midpoint.
Moreover, I notice that I have a Height and Width specified for the Path, which I am not sure if I need. If I make it larger to match the size of the canvas, the triangle will grow. Should I set Height and with to *?
I am new to WPF graphics, so any help would be appreciated.
Well.. after banging my head on the wall for a couple days, I finally figured out how to fix this issue. Hopefully this will save someone a lot of time and hassle down the road.
The problem was that I had the Path width/height set to the size of the parent window, and the Stretch property set to Fill.
Once I made the following change, the scaling came out correct.
<Canvas Background="Black" x:Name="MyCanvas" SizeChanged="MyCanvas_SizeChanged" Loaded="MyCanvas_Loaded">
<Path x:Name="MyPath" Stroke="Black" StrokeThickness="1" Height="{Binding MyCanvas.ActualHeight}" Width="{Binding MyCanvas.ActualWidth}" Stretch="None" HorizontalAlignment="Left" VerticalAlignment="Top">
I have created two separate usercontrols, they are meant to work together.
The first one is a simple usercontrol with a thumb attached to it, the thumb makes the control move around by dragging, this is simple and working.
XAML:
<Canvas>
<Thumb x:Name="Thumb" Width="15" Height="15" DragDelta="Thumb_DragDelta"/>
</Canvas>
Code-Behind: A dependency property called Position, when Setter is called it updates the usercontrol's margin.
public partial class ThumbPoint : UserControl
{
public Point Position
{
get { return (Point)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); this.Margin = new Thickness(value.X, value.Y, 0, 0); }
}
// Using a DependencyProperty as the backing store for Position. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position", typeof(Point), typeof(ThumbPoint), new PropertyMetadata(new Point()));
public ThumbPoint()
{
InitializeComponent();
Position = new Point(0, 0);
}
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Position = new Point(Position.X + e.HorizontalChange, Position.Y + e.VerticalChange);
}
}
The second UserControl is called StraightLine, its composed of a Line control
XAML:
<Canvas>
<Line x:Name="Line" Stroke="Gray" StrokeThickness="1"/>
</Canvas>
Code-Behind: A dependency property called StartPosition, when Setter is called it updates the Line X1 and Y1 (starting position of the line).
public partial class StraightLine : UserControl
{
public Point StartPosition
{
get { return (Point)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value); Line.X1 = value.X; Line.Y1 = value.Y; }
}
// Using a DependencyProperty as the backing store for StartPosition. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StartPositionProperty =
DependencyProperty.Register("StartPosition", typeof(Point), typeof(StraightLine), new PropertyMetadata(new Point()));
public StraightLine()
{
InitializeComponent();
Line.X1 = 0;
Line.Y1 = 0;
Line.X2 = 300;
Line.Y2 = 200;
}
}
Here I am trying to bind them together on the mainwindow.xaml:
<Canvas>
<local:ThumbPoint x:Name="ThumbPoint"/>
<local:StraightLine StartPosition="{Binding Position, ElementName=ThumbPoint}"/>
</Canvas>
Desired effect: DependencyProperty StartPosition of the StraightLine should be updated.
Whats happening: It's not being updated so only the ThumbPoint is moving.
binding doesn't use common property wrappers for DP (public Point StartPosition), it uses SetValue() directly, so code in setter isn't invoked.
What is needed is propertyChangedCallback:
public Point StartPosition
{
get { return (Point)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value); }
}
public static readonly DependencyProperty StartPositionProperty =
DependencyProperty.Register("StartPosition", typeof(Point), typeof(StraightLine), new PropertyMetadata(new Point(), OnStartPositionChanged));
private static void OnStartPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StraightLine c = (StraightLine) d;
c.Line.X1 = c.StartPosition.X;
c.Line.Y1 = c.StartPosition.Y;
}
in ThumbPoint public Point Position property has the same issue but it works there because you use setter directly: Position = new Point()
Alternatively bind X1 and Y1 value in xaml:
<Canvas>
<Line x:Name="Line" Stroke="Gray" StrokeThickness="1"
X1="{Binding StartPosition.X, RelativeSource={RelativeSource AncestorType=StraightLine}}"
Y1="{Binding StartPosition.Y, RelativeSource={RelativeSource AncestorType=StraightLine}}"/>
</Canvas>
(or use ElementName instead of RelativeSource)
In my application i am using Circular progress-bar.
So in case i want to use this controller in several places how can i set the Radius property in my XAML instead of using the current value which is all the time 100 ? (in class CircularProgressBar)
This is my Circular progress bar:
<UserControl x:Class="myApplication.CircularProgressBar"
x:Name="userControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid>
<Path x:Name="pathRoot" Stroke="{Binding SegmentColor, ElementName=userControl}"
StrokeThickness="{Binding StrokeThickness, ElementName=userControl}"
HorizontalAlignment="Left" VerticalAlignment="Top" Height="100" Width="100">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathFigure">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="arcSegment" SweepDirection="Clockwise" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
</Grid>
</UserControl>
public partial class CircularProgressBar : UserControl
{
public CircularProgressBar()
{
InitializeComponent();
Angle = (Percentage * 360) / 100;
RenderArc();
}
public int Radius
{
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
public Brush SegmentColor
{
get { return (Brush)GetValue(SegmentColorProperty); }
set { SetValue(SegmentColorProperty, value); }
}
public int StrokeThickness
{
get { return (int)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public double Percentage
{
get { return (double)GetValue(PercentageProperty); }
set { SetValue(PercentageProperty, value); }
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
public enum Modes
{
Full = 360,
Half = 180,
Intermediate = 250
}
public Modes CircularMode
{
get { return (Modes)GetValue(CircularModeProperty); }
set { SetValue(CircularModeProperty, value); }
}
public static readonly DependencyProperty CircularModeProperty =
DependencyProperty.Register("CircularMode", typeof(Modes), typeof(CircularProgressBar), new PropertyMetadata(Modes.Full));
// Using a DependencyProperty as the backing store for Percentage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PercentageProperty =
DependencyProperty.Register("Percentage", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(65d, new PropertyChangedCallback(OnPercentageChanged)));
// Using a DependencyProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register("StrokeThickness", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(1));
// Using a DependencyProperty as the backing store for SegmentColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SegmentColorProperty =
DependencyProperty.Register("SegmentColor", typeof(Brush), typeof(CircularProgressBar), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
// Using a DependencyProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(100, new PropertyChangedCallback(OnPropertyChanged)));
// 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(CircularProgressBar), new PropertyMetadata(120d, new PropertyChangedCallback(OnPropertyChanged)));
private static void OnPercentageChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.Angle = (circle.Percentage * (int)circle.CircularMode) / 100;
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.RenderArc();
}
public void RenderArc()
{
Point startPoint = new Point(Radius, 0);
Point endPoint = ComputeCartesianCoordinate(Angle, Radius);
endPoint.X += Radius;
endPoint.Y += Radius;
pathRoot.Width = Radius * 2 + StrokeThickness;
pathRoot.Height = Radius * 2 + StrokeThickness;
pathRoot.Margin = new Thickness(StrokeThickness, StrokeThickness, 0, 0);
bool largeArc = Angle > 180.0;
Size outerArcSize = new Size(Radius, Radius);
pathFigure.StartPoint = startPoint;
if (startPoint.X == Math.Round(endPoint.X) && startPoint.Y == Math.Round(endPoint.Y))
endPoint.X -= 0.01;
arcSegment.Point = endPoint;
arcSegment.Size = outerArcSize;
arcSegment.IsLargeArc = largeArc;
}
private Point ComputeCartesianCoordinate(double angle, double radius)
{
// convert to radians
double angleRad = (Math.PI / 180.0) * (angle - 90);
double x = radius * Math.Cos(angleRad);
double y = radius * Math.Sin(angleRad);
return new Point(x, y);
}
}
This is the hard code 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(int), typeof(CircularProgressBar), new PropertyMetadata(100, new PropertyChangedCallback(OnPropertyChanged)));
100 is just the default value of the Radius property. You should be able to set it to any int value you want just like you set any other dependency property:
<local:CircularProgressBar Radius="200" ... />
I have Circular Progress-Bar:
<Grid>
<Path x:Name="pathRoot" Stroke="{Binding SegmentColor, ElementName=userControl}"
StrokeThickness="{Binding StrokeThickness, ElementName=userControl}"
HorizontalAlignment="Left" VerticalAlignment="Top" Height="100" Width="100">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathFigure">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="arcSegment" SweepDirection="Clockwise" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
</Grid>
/// <summary>
/// Interaction logic for CircularProgressBar.xaml
/// </summary>
public partial class CircularProgressBar : UserControl
{
public CircularProgressBar()
{
InitializeComponent();
Angle = (Percentage * 360) / 100;
RenderArc();
}
public int Radius
{
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
public Brush SegmentColor
{
get { return (Brush)GetValue(SegmentColorProperty); }
set { SetValue(SegmentColorProperty, value); }
}
public int StrokeThickness
{
get { return (int)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public double Percentage
{
get { return (double)GetValue(PercentageProperty); }
set { SetValue(PercentageProperty, value); }
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a DependencyProperty as the backing store for Percentage. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PercentageProperty =
DependencyProperty.Register("Percentage", typeof(double), typeof(CircularProgressBar), new PropertyMetadata(65d, new PropertyChangedCallback(OnPercentageChanged)));
// Using a DependencyProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register("StrokeThickness", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(1));
// Using a DependencyProperty as the backing store for SegmentColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SegmentColorProperty =
DependencyProperty.Register("SegmentColor", typeof(Brush), typeof(CircularProgressBar), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
// Using a DependencyProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RadiusProperty =
DependencyProperty.Register("Radius", typeof(int), typeof(CircularProgressBar), new PropertyMetadata(80, new PropertyChangedCallback(OnPropertyChanged)));
// 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(CircularProgressBar), new PropertyMetadata(120d, new PropertyChangedCallback(OnPropertyChanged)));
private static void OnPercentageChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.Angle = (circle.Percentage * 360) / 100;
}
private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
CircularProgressBar circle = sender as CircularProgressBar;
circle.RenderArc();
}
public void RenderArc()
{
Point startPoint = new Point(Radius, 0);
Point endPoint = ComputeCartesianCoordinate(Angle, Radius);
endPoint.X += Radius;
endPoint.Y += Radius;
pathRoot.Width = Radius * 2 + StrokeThickness;
pathRoot.Height = Radius * 2 + StrokeThickness;
pathRoot.Margin = new Thickness(StrokeThickness, StrokeThickness, 0, 0);
bool largeArc = Angle > 180.0;
Size outerArcSize = new Size(Radius, Radius);
pathFigure.StartPoint = startPoint;
if (startPoint.X == Math.Round(endPoint.X) && startPoint.Y == Math.Round(endPoint.Y))
endPoint.X -= 0.01;
arcSegment.Point = endPoint;
arcSegment.Size = outerArcSize;
arcSegment.IsLargeArc = largeArc;
}
private Point ComputeCartesianCoordinate(double angle, double radius)
{
// convert to radians
double angleRad = (Math.PI / 180.0) * (angle - 90);
double x = radius * Math.Cos(angleRad);
double y = radius * Math.Sin(angleRad);
return new Point(x, y);
}
}
This controller works find but i want to change a little this shape and i am wonder if it possible to change this Circle into Half Circle, i try to change several properties like Angle but my Circle hasn't changed.
Is it possible to do that or i need to change the whole class ?
I have 2 TextBoxes in a Usercontrol (InfoControl) bound to a Point property in a VM implementing INotifyPropertyChanged.
I have another UserControl (DesignerControl) which contains a draggable rectangle.
The Rectangle has its Canvas.Left and Canvas.Bottom bound to a ConvPoint property of the same VM.
ConvPoint Property (0 => ActualWidth) is a converted version of the Point Property (0 => 1)
when i drag my rectangle, the ConvPoint VM Property and the values in the textboxes are instantly updated, but when i update my textboxes with new values, the VM Point Property is instantly updated but the Rectangle is positioned only when i drag the rectangle again and not instantly.
A bit of code to explain:
First, my ViewModel's Position Property
public class MyVM : ViewModelBase
{
private DependencyPoint position;
public DependencyPoint Position
{
get { return this.position; }
set
{
this.position = value;
RaisePropertyChanged("Position");
}
}
public DependencyPoint ConvPosition
{
get { return new Point(this.Position.X * MainVM.ActualWidth, this.Position.Y * MainVM.AcutalHeight);}
set
{
Point p = new Point(value.X/MainVM.ActualWidth,value.Y/MainVM.ActualHeight);
this.position = p;
RaisePropertyChanged("ConvPosition");
}
}
}
Edit:
I'm using for this a class DependencyPoint to have a notification on the X and Y properties:
public class DependencyPoint : DependencyObject
{
public enum PointOrder
{
isStartPoint,
isEndPoint,
isPoint1,
isPoint2
}
public DependencyPoint()
{
}
public DependencyPoint(Double x, Double y, PointOrder po)
{
this.X = x;
this.Y = y;
this.Order = po;
}
public DependencyPoint(DependencyPoint point)
{
this.X = point.X;
this.Y = point.Y;
this.Order = point.Order;
}
public DependencyPoint(Point point, PointOrder po)
{
this.X = point.X;
this.Y = point.Y;
this.Order = po;
}
public Point ToPoint()
{
return new Point(this.X, this.Y);
}
public PointOrder Order
{
get { return (PointOrder)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
// Using a DependencyProperty as the backing store for Order. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(PointOrder), typeof(DependencyPoint), new UIPropertyMetadata(null));
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(DependencyPoint), new UIPropertyMetadata((double)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(DependencyPoint), new UIPropertyMetadata((double)0.0));
}
then in my InfoControl:
<Grid Grid.Row="0" DataContext="{Binding Position}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Margin="3,3,0,1" Grid.Row="0" LastChildFill="True">
<TextBlock Foreground="White" Padding="0,3,0,0" Margin="2,0,0,0" DockPanel.Dock="Left" Text="StartPoint.X :"/>
<TextBox FontWeight="DemiBold" Foreground="Black" Background="#efefef" Width="Auto" Margin="7,0,0,0" Text="{Binding X, Converter={StaticResource StDConverter}, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
</DockPanel>
<DockPanel Margin="3,3,0,1" Grid.Row="1" LastChildFill="True">
<TextBlock Foreground="White" Padding="0,3,0,0" Margin="2,0,0,0" DockPanel.Dock="Left" Text="StartPoint.Y :"/>
<TextBox FontWeight="DemiBold" Foreground="Black" Background="#efefef" Width="Auto" Margin="7,0,0,0" Text="{Binding Y, Converter={StaticResource StDConverter}, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
</DockPanel>
</Grid>
And in the DesignerControl:
<UserControl>
<Canvas>
<Rectangle x:Name="Point" Width="10" Height="10" Canvas.Bottom="{Binding ConvPosition.Y, Mode=TwoWay}" Canvas.Left="{Binding ConvPosition.X, Mode=TwoWay}" />
</Canvas>
</UserControl>
I have access to the actualWidth and my Rectangle is well positioned in the Canvas.
I know my properties in the VM or a bit dirty but i don't know an other way to do it properly and manage the conversion too.
any ideas?
Because ConvPosition depends from Position you should raise a NotificationChanged for ConvPosition in Position setter.
public Point Position
{
get { return this.position; }
set
{
this.position = value;
RaisePropertyChanged("Position");
RaisePropertyChanged("ConvPosition");
}
}
ConvPosition you can change to this
public Point ConvPosition
{
get { return new Point(this.Position.X * MainVM.ActualWidth, this.Position.Y * MainVM.AcutalHeight); }
set
{
this.Position = new Point(value.X/MainVM.ActualWidth, value.Y/MainVM.ActualHeight);
}
}
EDIT
I think two additional properties in MyVM would be the simpliest solution.
public double X
{
get { return Position.X; }
set
{
Position = new Point(value, Position.Y);
RaisePropertyChanged("X");
}
}
public double Y
{
get { return Position.Y; }
set
{
Position = new Point(Position.X, value);
RaisePropertyChanged("Y");
}
}
Only change in Grid:
<Grid DataContext="{Binding}">