Reputation: 1353
I am trying to paint an arc using WPF, but I somehow cannot figure out how to do this. I have searched a lot and came through Path property of WPF but i found that this is not good enough in my case. My requirement is to show the Field of View of CCTV camera on a wpf.
There are following inputs from the user: 1. Center point of circle. 2. Radius of Circle. 3. Angle(0-360)
I want to allow user to draw a region of a circle or a full circle and can change the region by changing the angle and radius. Note: 1. Center is a fixed location on the canvas. 2. User can change the figure(region) dynamically.
Suppose user will provide angle 45 degree then the Circle will look like as in below image:
Upvotes: 1
Views: 1178
Reputation: 16991
After spending some quality time with my old Algebra text-book, I've got a fully working solution for you.
This uses the WpfLib Nuget package (Disclaimer: I am the author of this package) for INotifyPropertyChanged
support, but you should easily be able to whatever change event system you want. The full solution can be downloaded from BitBucket.
The XAML:
<Window
x:Class="ViewingAngle.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:local="clr-namespace:ViewingAngle"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance local:MainWindowVm}"
mc:Ignorable="d">
<DockPanel Margin="4">
<StackPanel DockPanel.Dock="Bottom">
<TextBlock Margin="4,4,4,0" Text="{Binding Path=FieldOfView, StringFormat='Field of View: {0}'}" />
<Slider
Margin="4"
Maximum="360"
Minimum="0"
TickFrequency="5"
TickPlacement="Both"
Value="{Binding Path=FieldOfView}" />
<TextBlock Margin="4,4,4,0" Text="{Binding Path=TargetAngle, StringFormat='Target Angle: {0}'}" />
<Slider
Margin="4"
Maximum="360"
Minimum="0"
TickFrequency="5"
TickPlacement="Both"
Value="{Binding Path=TargetAngle}" />
</StackPanel>
<Viewbox Margin="4" Stretch="Uniform">
<Canvas Width="500" Height="500">
<Path
Fill="Blue"
Stroke="Blue"
StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="250,250">
<LineSegment Point="{Binding Path=StartPoint}" />
<ArcSegment
IsLargeArc="{Binding Path=IsLargeArc}"
Point="{Binding Path=EndPoint}"
Size="250,250"
SweepDirection="Clockwise" />
<LineSegment Point="250,250" />
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
</Viewbox>
</DockPanel>
</Window>
The View-Model:
using System;
using System.Windows;
using AgentOctal.WpfLib;
namespace ViewingAngle
{
class MainWindowVm : ViewModel
{
private const int Radius = 250;
private const int CenterX = 250;
private const int CenterY = 250;
public MainWindowVm()
{
FieldOfView = 45;
TargetAngle = 90;
}
private int _fieldOfView;
public int FieldOfView
{
get { return _fieldOfView; }
set
{
SetValue(ref _fieldOfView, value);
RecalculateArc();
}
}
private int _targetAngle;
public int TargetAngle
{
get { return _targetAngle; }
set
{
SetValue(ref _targetAngle, value);
RecalculateArc();
}
}
private double GetRadians(int angle)
{
return angle * Math.PI / 180;
}
private void RecalculateArc()
{
var targetAngle = GetRadians(_targetAngle);
var fieldOfView = GetRadians(_fieldOfView);
var halfFieldOfView = fieldOfView / 2;
var startAngle = targetAngle - halfFieldOfView;
var endAngle = targetAngle + halfFieldOfView;
double angleDiff = endAngle - startAngle;
IsLargeArc = angleDiff >= Math.PI;
StartPoint = new Point(CenterX + Radius * Math.Cos(startAngle), CenterY + Radius * Math.Sin(startAngle));
EndPoint = new Point(CenterX + Radius * Math.Cos(endAngle), CenterY + Radius * Math.Sin(endAngle));
}
private Point _startPoint;
public Point StartPoint
{
get { return _startPoint; }
set { SetValue(ref _startPoint, value); }
}
private Point _endPoint;
public Point EndPoint
{
get { return _endPoint; }
set { SetValue(ref _endPoint, value); }
}
private bool _isLargeArc;
public bool IsLargeArc
{
get { return _isLargeArc; }
set { SetValue(ref _isLargeArc, value); }
}
}
}
All the magic happens in RecalculateArc
which gets called whenever TargetAngle
or FieldOfView
are changed. All the math has to happen in radians, so the first thing it does is convert the values. It calculates new StartPoint
and EndPoint
values for the ArcSegment
using some fairly simple algebra involving Sine and Cosine (though I still had to look the math up, because who remembers this stuff after high-school?).
In XAML, I placed some sliders bound to TargetAngle
, and FieldOfView
to allow you to control the angles. It also contains the Canvas
where the graphics are drawn, this is inside a ViewBox
, just to make the indicator fill the available space.
The Canvas
contains a Path
, that is made up of one PathFigure
. The PathFigure
starts at 250,250 (the center of the 500 x 500 Canvas
), draws a LineSegment
out to the arc's starting point, which is bound to StartPoint
. An ArcSegment
is then added, that ends at EndPoint
. IsLargeArc
is just used to let the drawing system know which "half" of the arc to draw. Another LineSegment
that end back at the center is added to finish off the PathFigure
.
Upvotes: 2
Reputation: 960
Here is a simple way to do it. Of course you'll need to put your own code to make it dynamic, but it is quite easy to control: in my example, I put two different figures so you will have two "field of view":
<Ellipse Fill="Blue" Width="100" Height="100">
<Ellipse.Clip>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="50,50" IsClosed="True">
<LineSegment Point="100,50"/>
<LineSegment Point="100,0"/>
</PathFigure>
<PathFigure StartPoint="50,50" IsClosed="True">
<LineSegment Point="80,0"/>
<LineSegment Point="30,0"/>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Ellipse.Clip>
</Ellipse>
Upvotes: 1