I don't know what setting I'm missing, in the screenshot it looks like I set the Segments with a negative X axis, but it doesn't.
.xaml
<Window x:Class="Playground.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"
mc:Ignorable="d" Title="Test" Height="220" Width="200" Loaded="Window_Loaded">
<Canvas Width="100" Height="100" Background="Black" Margin="50">
<Path Stroke="Red" StrokeThickness="2" Name="myPath" />
</Canvas>
</Window>
.cs
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var _pathFigure = new PathFigure() { Segments = new PathSegmentCollection() };
var _pathCollection = new PathFigureCollection();
_pathCollection.Add(_pathFigure);
myPath.Data = new PathGeometry() { Figures = _pathCollection };
var pts = new List<Point>()
{
new Point(50,50),
new Point(1,50),
new Point(50,50),
};
_pathFigure.Segments.Clear();
_pathFigure.StartPoint = pts.First();
foreach (var p in pts)
{
var segment = new LineSegment();
segment.Point = new Point(p.X, p.Y);
_pathFigure.Segments.Add(segment);
}
}
Screenshot:
This is caused by the miter join between the two segments. The miter point between the lines is the crossing point between them. However, when they are parallel, like in your case, this crossing point goes into infinity. WPF has a certain setting - StrokeMiterLimit, which guarantees that the miter extension will not exceed a certain limit, which is X/2 times the line stroke thickness. By default it is 10, so you have "error" that is 10/2 times the stroke thickness, which is 2, therefore - 10. You may set it to 1, or use StrokeLineJoin.Bevel, or StrokeLineJoin.Round, if you don't actually need a miter join.
Related
I want to rotate a line L by moving the end point (X2,Y2) while keeping the start point (X1,Y1) fixed at (0,0).
The below code is making the line rotate along the mid point of the line L.
Can you help me how to do this.
//XAML
<Window x:Class="SPDisplay.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:SPDisplay"
mc:Ignorable="d"
Title="PPI Display" Height="1000" Width="1000">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Name="sp">
<Line Stroke="Black" StrokeThickness="2" Margin="0" Name="lineSweep"
X1="0" Y1="0" X2="0" Y2="0"/>
</StackPanel>
</Window>
//C# code
using System;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
Storyboard sb = new Storyboard();
DoubleAnimation animateY1 = new DoubleAnimation();
animateY1.From = 0;
animateY1.To = 0;
animateY1.Duration = TimeSpan.Parse("0:0:5");
DoubleAnimation animateX1 = new DoubleAnimation();
animateX1.From = 0;
animateX1.To = 0;
animateX1.Duration = TimeSpan.Parse("0:0:5");
DoubleAnimation animateY2 = new DoubleAnimation();
animateY2.From = 200;
animateY2.To = 0;
animateY2.Duration = TimeSpan.Parse("0:0:5");
DoubleAnimation animateX2 = new DoubleAnimation();
animateX2.From = 0;
animateX2.To = 200;
animateX2.Duration = TimeSpan.Parse("0:0:5");
sb.Children.Add(animateY1);
sb.Children.Add(animateX1);
sb.Children.Add(animateY2);
sb.Children.Add(animateX2);
Storyboard.SetTargetName(animateY1, "lineSweep");
Storyboard.SetTargetProperty(animateY1, new PropertyPath(Line.Y1Property));
Storyboard.SetTargetName(animateX1, "lineSweep");
Storyboard.SetTargetProperty(animateX1, new PropertyPath(Line.X1Property));
Storyboard.SetTargetName(animateY2, "lineSweep");
Storyboard.SetTargetProperty(animateY2,new PropertyPath(Line.Y2Property));
Storyboard.SetTargetName(animateX2, "lineSweep");
Storyboard.SetTargetProperty(animateX2, new PropertyPath(Line.X2Property));
sb.Begin(lineSweep);
The animation is actually doing what you want. The problem is the container you've chosen. If you set Background="Red" for your StackPanel you'll see what's happening. Try Canvas instead of StackPanel.
Let's say I want to animate rotate an UI element 45 degrees clockwise each time I press a button.
I have defined 8 visual states setting different RotateTransform values between them. So, whenever I press the button I move to the next visual state:
VisualStateManager.GoToElementState(MyElement, "Position2", True)
etc.
The problem is that when moving from the VisualState 8 to VisualState 1, the element rotates backward. Seems logical, since it is moving from 315º to 0º.
The question is, how could achieve to goal of always moving forward when pressing the button?
A basic example with animation could look like this:
<Grid>
<Rectangle Fill="Black" Width="100" Height="100" RenderTransformOrigin=".5,.5">
<Rectangle.RenderTransform>
<RotateTransform x:Name="rotation"/>
</Rectangle.RenderTransform>
</Rectangle>
<Button Content="Rotate" VerticalAlignment="Bottom" Click="Button_Click"/>
</Grid>
with this Button Click handler:
private void Button_Click(object sender, RoutedEventArgs e)
{
const double rotSpeed = 180; // °/s, i.e. 45° in 0.25 seconds
var newAngle = Math.Floor(rotation.Angle / 45 + 1) * 45; // integer multiple of 45°
var duration = TimeSpan.FromSeconds((newAngle - rotation.Angle) / rotSpeed);
rotation.BeginAnimation(
RotateTransform.AngleProperty, new DoubleAnimation(newAngle, duration));
}
I wrote a simple example for you in C#; VB should be very similar.
In your Main Window put a Rectangle and a Button. Then, set the RenderTransformOrigin for the Rectangle like in the Xaml sample.
In your code behind, declare your RotateTransform and attach it to the Rectangle as shown below. Everytime the Button is pressed, the Angle property of the RotateTransform is increased of 45.
#EDIT:
I like #Clemens formula to increase angle (it avoid overflows). I edited my answer.
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle x:Name="myRect" RenderTransformOrigin=".5,.5" Grid.Row="0" Width="100" Height="100" Fill="LightBlue" >
</Rectangle>
<Button Grid.Row="1" Width="100" Height="50" Margin="10" Click="Button_Click">Rotate</Button>
</Grid>
Window Code-Behind:
public partial class MainWindow : Window
{
private TransformGroup _transformGroup;
private RotateTransform _rotateTrsf;
public MainWindow()
{
InitializeComponent();
_transformGroup = new TransformGroup();
_rotateTrsf = new RotateTransform();
_transformGroup.Children.Add(_rotateTrsf);
SetupAnimation();
myRect.RenderTransform = _transformGroup;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_rotateAnimation.To = Math.Floor(_rotateTrsf.Angle / 45 + 1) * 45;
_rotateTrsf.BeginAnimation(RotateTransform.AngleProperty, _rotateAnimation);
_rotateAnimation.From = _rotateAnimation.To;
}
private void SetupAnimation()
{
_rotateAnimation = new DoubleAnimation();
_rotateAnimation.From = 0.0;
_rotateAnimation.To = 45;
_rotateAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.3));
}
}
So one of my latest side projects is developing a application detection and populating assistant. Programmatically I am absolutely fine populating the backend code for what I want accomplished. But I've run into a road block on the GUI. I need a GUI that is a Quarter circle that extends from the task bar to the bottom right of a standard windows operating system. When the user doubleclicks on the application, the circle rotates into view. I can do this with a typical windows form that has a transparent background and a fancy background image. But the square properties of the form will still apply when the user has the application open. And I do not want to block the user from higher priority apps when the circle is open.
I'm not really stuck on any one specific programming language. Although, I would prefer that it not contain much 3d rendering as it is supposed to be a computing assistant and should not maintain heavy RAM/CPU consumption whilst the user is browsing around.
Secondarily, I would like the notches of the outer rings to be mobile and extend beyond the gui a mere centimeter or so.
I would not be here if I hadn't had scoured the internet for direction on this capability. But what I've found is application GUI's of this nature tend to be most used in mobile environments.
So my questions are: How can I accomplish this? What programming language can I write this in? Is this a capability currently available? Will I have to sacrifice user control for design?
I wrote out some code doing something close to what you described.
I’m not sure to understand how you do want the circle to appear, so I just let a part of it always visible.
And I didn’t get the part about the mobile outer ring.
Creating and placing the window
The XAML is very simple, it just needs a grid to host the circle’s pieces, and some attributes to remove window decorations and taskbar icon:
<Window x:Class="circle.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Circle"
Width="250"
Height="250"
AllowsTransparency="True"
Background="Transparent"
MouseDown="WindowClicked"
ShowInTaskbar="False"
WindowStyle="None">
<Grid Name="Container"/>
</Window>
To place the window in the bottom right corner, you can use SystemParameters.WorkArea in the constructor:
public MainWindow()
{
InitializeComponent();
var desktopDim = SystemParameters.WorkArea;
Left = desktopDim.Right - Width;
Top = desktopDim.Bottom - Height;
}
Creating the shape
I build the circle as a bunch of circle pieces that I generate from code behind:
private Path CreateCirclePart()
{
var circle = new CombinedGeometry
{
GeometryCombineMode = GeometryCombineMode.Exclude,
Geometry1 = new EllipseGeometry { Center = _center, RadiusX = _r2, RadiusY = _r2 },
Geometry2 = new EllipseGeometry { Center = _center, RadiusX = _r1, RadiusY = _r1 }
};
var sideLength = _r2 / Math.Cos((Math.PI/180) * (ItemAngle / 2.0));
var x = _center.X - Math.Abs(sideLength * Math.Cos(ItemAngle * Math.PI / 180));
var y = _center.Y - Math.Abs(sideLength * Math.Sin(ItemAngle * Math.PI / 180));
var triangle = new PathGeometry(
new PathFigureCollection(new List<PathFigure>{
new PathFigure(
_center,
new List<PathSegment>
{
new LineSegment(new Point(_center.X - Math.Abs(sideLength),_center.Y), true),
new LineSegment(new Point(x,y), true)
},
true)
}));
var path = new Path
{
Fill = new SolidColorBrush(Colors.Cyan),
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 1,
RenderTransformOrigin = new Point(1, 1),
RenderTransform = new RotateTransform(0),
Data = new CombinedGeometry
{
GeometryCombineMode = GeometryCombineMode.Intersect,
Geometry1 = circle,
Geometry2 = triangle
}
};
return path;
}
First step is to build two concentric circles and to combine them in a CombinedGeometry with CombineMode set to exclude. Then I create a triangle just tall enough to contain the section of the ring that I want, and I keep the intersection of these shapes.
Seeing it with the second CombineMode set to xor may clarify:
Building the circle
The code above uses some instance fields that make it generic: you can change the number of pieces in the circle or their radius; it will always fill the corner.
I then populate a list with the required number of shape, and add them to the grid:
private const double MenuWidth = 80;
private const int ItemCount = 6;
private const double AnimationDelayInSeconds = 0.3;
private readonly Point _center;
private readonly double _r1, _r2;
private const double ItemSpacingAngle = 2;
private const double ItemAngle = (90.0 - (ItemCount - 1) * ItemSpacingAngle) / ItemCount;
private readonly List<Path> _parts = new List<Path>();
private bool _isOpen;
public MainWindow()
{
InitializeComponent();
// window in the lower right desktop corner
var desktopDim = SystemParameters.WorkArea;
Left = desktopDim.Right - Width;
Top = desktopDim.Bottom - Height;
_center = new Point(Width, Height);
_r2 = Width;
_r1 = _r2 - MenuWidth;
Loaded += (s, e) => CreateMenu();
}
private void CreateMenu()
{
for (var i = 0; i < ItemCount; ++i)
{
var part = CreateCirclePart();
_parts.Add(part);
Container.Children.Add(part);
}
}
ItemSpacingAngle define the blank between two consecutive pieces.
Animating the circle
The final step is to unfold the circle. Using a rotateAnimation over the path rendertransform make it easy.
Remember this part of the CreateCirclePart function:
RenderTransformOrigin = new Point(1, 1),
RenderTransform = new RotateTransform(0),
The RenderTransform tells that the animation we want to perform is a rotation, and RenderTransformOrigin set the rotation origin to the lower right corner of the shape (unit is percent).
We can now animate it on click event:
private void WindowClicked(object sender, MouseButtonEventArgs e)
{
for (var i = 0; i < ItemCount; ++i)
{
if (!_isOpen)
UnfoldPart(_parts[i], i);
else
FoldPart(_parts[i], i);
}
_isOpen = !_isOpen;
}
private void UnfoldPart(Path part, int pos)
{
var newAngle = pos * (ItemAngle + ItemSpacingAngle);
var rotateAnimation = new DoubleAnimation(newAngle, TimeSpan.FromSeconds(AnimationDelayInSeconds));
var tranform = (RotateTransform)part.RenderTransform;
tranform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);
}
private void FoldPart(Path part, int pos)
{
var rotateAnimation = new DoubleAnimation(0, TimeSpan.FromSeconds(AnimationDelayInSeconds));
var tranform = (RotateTransform)part.RenderTransform;
tranform.BeginAnimation(RotateTransform.AngleProperty, rotateAnimation);
}
Not actually answering this, but I liked your question enough that I wanted to get a minimal proof of concept together for fun and I really enjoyed doing it so i thought I'd share my xaml with you:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing" x:Class="WpfApplication1.Window2"
Title="Window2" Height="150" Width="150" Topmost="True" MouseLeftButtonDown="Window2_OnMouseLeftButtonDown"
AllowsTransparency="True"
OpacityMask="White"
WindowStyle="None"
Background="Transparent" >
<Grid>
<ed:Arc ArcThickness="40"
ArcThicknessUnit="Pixel" EndAngle="0" Fill="Blue" HorizontalAlignment="Left"
Height="232" Margin="33,34,-115,-116" Stretch="None"
StartAngle="270" VerticalAlignment="Top" Width="232" RenderTransformOrigin="0.421,0.471"/>
<Button HorizontalAlignment="Left" VerticalAlignment="Top" Width="41" Margin="51.515,71.385,0,0" Click="Button_Click" RenderTransformOrigin="0.5,0.5">
<Button.Template>
<ControlTemplate>
<Path Data="M50.466307,88.795148 L61.75233,73.463763 89.647286,102.42368 81.981422,113.07109 z"
Fill="DarkBlue" HorizontalAlignment="Left" Height="39.606"
Stretch="Fill" VerticalAlignment="Top" Width="39.181"/>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</Window>
And it looks like this:
Question: How do I find the maximum and minimum x-axis value of a RadCartesianChart's current viewport using DateTimeCategoricalAxis?
I can easily find the maximum/minimum values of the dataset and, if I were to use a DateTimeContinousAxis(unfortunately not an option), I could simply use ActualRange and ActualVisibleRange. However using DateTimeCategoricalAxis is a completely different story, since I cannot accurately find the datapoints within the current viewport. I even tried using the PlotAreaClip but to no avail. Not to mention, PlotAreaClip does not take into account the zoom size, which becomes a problem.
Any help would be great appreciated!
Here is a simplified version of my code:
XAML:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<telerik:RadCartesianChart ZoomChanged="Zooming">
<telerik:RadCartesianChart.Behaviors>
<telerik:ChartPanAndZoomBehavior DragMode="Pan"
PanMode="Both"
ZoomMode="None" />
</telerik:RadCartesianChart.Behaviors>
<telerik:RadCartesianChart.Series>
<telerik:OhlcSeries ItemsSource="{Binding Bars}"
OpenBinding="Open"
HighBinding="High"
LowBinding="Low"
CloseBinding="Close"
CategoryBinding="Date">
</telerik:OhlcSeries>
</telerik:RadCartesianChart.Series>
<telerik:RadCartesianChart.HorizontalAxis>
<telerik:DateTimeCategoricalAxis DateTimeComponent="Ticks"
ShowLabels="False"
PlotMode="OnTicks"
MajorTickInterval="100">
</telerik:DateTimeCategoricalAxis>
</telerik:RadCartesianChart.HorizontalAxis>
<telerik:RadCartesianChart.VerticalAxis>
<telerik:LinearAxis HorizontalLocation="Right"
RangeExtendDirection="Both"
Minimum="{Binding PriceMinimum, Mode=OneWay}"
Maximum="{Binding PriceMaximum, Mode=OneWay}"
MajorStep="{Binding PriceMajorStep, Mode=OneWay}" />
</telerik:RadCartesianChart.VerticalAxis>
<telerik:RadCartesianChart.Grid>
<telerik:CartesianChartGrid MajorXLinesRenderMode="All"
MajorLinesVisibility="XY" />
</telerik:RadCartesianChart.Grid>
</telerik:RadCartesianChart>
</Grid>
</UserControl>
CODE BEHIND:
public void Zooming(object sender, ChartZoomChangedEventArgs e)
{
RadCartesianChart chart = sender as RadCartesianChart;
if (chart != null)
{
//PlotAreaClip.Location.X (PlotAreaClip.X also works) to get left side of clip
//PlotAreaClip.Right to get right side of clip
DataTuple dtMin = chart.ConvertPointToData(new Point(chart.PlotAreaClip.Location.X - Math.Abs(chart.PanOffset), chart.PlotAreaClip.Y), chart.HorizontalAxis, chart.VerticalAxis);
DataTuple dtMax = chart.ConvertPointToData(new Point(chart.PlotAreaClip.Right - Math.Abs(chart.PanOffset), chart.PlotAreaClip.Y), chart.HorizontalAxis, chart.VerticalAxis);
object xMin = dtMin.FirstValue;
object xMax = dtMax.FirstValue;
//these numbers are VERY inconsistent, especially when you zoom in!
}
}
The Telerik control being mentioned can be found here.
If anyone have any suggestions, I would greatly appreciate it! Thank you!
After doing some digging, I found this thread with the following solution:
private DataPoint[] FindFirstLastVisiblePoints(CategoricalSeries series)
{
DataPoint firstPoint = null;
DataPoint lastPoint = null;
RadRect plotArea = this.radChart1.PlotAreaClip;
foreach (DataPoint point in series.DataPoints)
{
if (point.LayoutSlot.IntersectsWith(plotArea))
{
if (firstPoint == null)
{
firstPoint = point;
}
lastPoint = point;
}
}
return new DataPoint[] { firstPoint, lastPoint };
}
Basically, this method takes the current PlotAreaClip and returns the first and last datapoint within the current ViewPort. I really hope this helps others in the future!
I have some very simple code that 'correctly' draws a short vertical black line on a 1024x768 blue canvas in WPF (well in Silverlight 4).
<UserControl x:Class="SimpleCanvas.MainPage"
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"
mc:Ignorable="d"
Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot" Background="White">
<Canvas x:Name="PathCanvas" Width="1024" Height="768" Background="Blue"/>
</Grid>
</UserControl>
and here's the code-behind
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
var myPathFigure = new PathFigure
{
StartPoint = new Point(492, 748)
};
var line1 = new LineSegment
{
Point = new Point(492, 708)
};
myPathFigure.Segments.Add(line1);
var myPathGeometry = new PathGeometry();
myPathGeometry.Figures.Add(myPathFigure);
var myPath = new Path
{
Data = myPathGeometry,
Stretch = Stretch.Fill,
Stroke = new SolidColorBrush(Color.FromArgb(0xFF, 0x0, 0x0, 0x0)),
StrokeThickness = 10
};
PathCanvas.Children.Add(myPath);
}
Now if I change the end-point of the line segment, so that instead of just changing the Y from the start-point I also change the X, albeit by only one pixel, the whole line is rendered in the top left of the canvas. Here's the revised code-behind.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
var myPathFigure = new PathFigure
{
StartPoint = new Point(492, 748)
};
var line1 = new LineSegment
{
Point = new Point(491, 708)
};
myPathFigure.Segments.Add(line1);
var myPathGeometry = new PathGeometry();
myPathGeometry.Figures.Add(myPathFigure);
var myPath = new Path
{
Data = myPathGeometry,
Stretch = Stretch.Fill,
Stroke = new SolidColorBrush(Color.FromArgb(0xFF, 0x0, 0x0, 0x0)),
StrokeThickness = 10
};
PathCanvas.Children.Add(myPath);
}
If both rendered in the centre-bottom of the canvas, or both rendered in the top left of the canvas, I could understand. But I do not understand why the first code block causes the line to render centre-bottom and the second code block causes the line to render top-left.
Note that I'm not using Canvas.Top or Canvas.Left.
Any insight gratefully received!
The problem here is that you are creating the Path with the Stretch property set to Stretch.Fill. I imagine you want to leave the Path with Stretch set to the default, Stretch.None.
The reason why the first code-block causes the line to be rendered in the centre of the canvas is because both points in its single LineSegment have the same X-coordinate, and hence the Path has a width of 0. Clearly an element with width 0 can't be stretched horizontally. Silverlight could attempt to stretch your Path vertically, since it has nonzero height, but it seems it chooses not to.
Similarly, if you draw a horizontal line (so the Path has height 0), Silverlight doesn't stretch the line either.
In your second code-block, when you've change the X-coordinate, even by 1 pixel, you give the Path a non-zero width and height. Silverlight can then stretch it to fill the entire Canvas.
Note that the Path control itself, as the container for the various path segments, is being stretched, not the individual line segments that comprise it.