WPF 圆型布局(测量过程)
这个例子来自书上。
记录过程。
主要是数学上极坐标,WPF中的测量过程
简单来说在一个具有固定轴的坐标系内,一个由原点射出的向量并与固定轴有一定角度且在向量上确定长度的这么个东西。
可以参考:
知乎https://www.zhihu.com/question/318613418/answer/640194590
B站https://www.bilibili.com/video/BV1Sb411n7FG?t=177
极坐标与直角坐标系转换。
极坐标中某一点是M,也就是M(ρ,θ)。
将M连接至原点成为一个线段L1,将此线段放置直角坐标系,其中M点变为点M1(X,Y)。
此时我们可以利用三角函数确定X,Y
X=ρ*cosθ=L1*X[点在X轴的垂线坐标]/L1
Y=ρ*sinθ=L1*Y[点在Y轴的垂线坐标]/L1
L1也可以理解为半径
那么直角坐标转换为极坐标则是M(X,Y),同样我们需使用圆的标准方程(之前的极坐标转直角中的L1,本来就应该是R【半径】,不过我不太喜欢这么快,先用线段,这么好理解) R2=X2+Y2
另外还有三角函数tan,对边比邻边 ,(坐标系内)y/x
ρ=根号下X2+Y2
θ=arctan=arctany/x
剩下就是WPF的测量过程
没什么好说,第一步是测量,第二步是排列。
第一步主要是Measure方法,但是主要是通过MeasureOverride方法来确定,这个方法本身是预计UI的大小,Measure是没有返回值,但是有形参,是这个控件的父控件留给这个控件的可用空间,
如果控件重写measureoverride方法,这个方法的形参就是Measure的形参,同时也会和Measure形成递归布局更新,
第二步是Arrange,是最终确认UI控件的大小,通常是通过ArrageOvrride方法确认,过程和第一步差不多,只不过形参和返回值不同。因为要定位,所以是Rect。
对于Measure是父控件给子控件赋值通知子控件你可用大小
MeasureOverride是测量自身大小并返回通知父控件我预计用这么大(DesiredSize)
对于Arrange是父控件决定子控件的位置和大小
ArrangeOverride是确定自身大小和位置然后返回最终的大小(finalsize【在这之前会有rendersize】)
具体过程还可以参考https://www.cnblogs.com/powertoolsteam/archive/2011/01/10/1932036.html
差不多就这么多
class Test : Panel { public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(Test), new PropertyMetadata(0.0, new PropertyChangedCallback(OnValueChanged))); public double Radius { get => Convert.ToDouble(GetValue(RadiusProperty)); set => SetValue(RadiusProperty, value); } private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as Test).Radius = Convert.ToDouble(e.NewValue); (d as Test).InvalidateArrange(); } protected override Size MeasureOverride(Size availableSize) { double MaxElementWidth = 0; foreach(UIElement item in Children) {
//给定子控件的可用空间 item.Measure(availableSize); MaxElementWidth = Math.Max(item.DesiredSize.Width, MaxElementWidth); } double PanelWidth = 2 * this.Radius+2 * MaxElementWidth; double width = Math.Min(PanelWidth, availableSize.Width); double height = Math.Min(PanelWidth, availableSize.Height); return new Size(width, height); } protected override Size ArrangeOverride(Size finalSize) { double Degree = 0; double DegreeSrep = (double)360 / this.Children.Count; double X = this.DesiredSize.Width / 2; double Y = this.DesiredSize.Height / 2; foreach(UIElement Item in Children) { //角度转弧度 double angle = Math.PI * Degree / 180.0; //转换为直角坐标系 r*cos double x = Math.Cos(angle) * this.Radius; //转换为直角坐标系 r*sin double y = Math.Sin(angle) * this.Radius; RotateTransform rotate = new RotateTransform(); rotate.Angle = Degree; rotate.CenterX = 0; rotate.CenterY = 0; Item.RenderTransform = rotate; //决定子控件的位置和大小 Item.Arrange(new Rect(X + x, Y + y, Item.DesiredSize.Width, this.DesiredSize.Height)); Degree += DegreeSrep; } return finalSize; } }
xaml
<Grid> <local:Test Radius="50"> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> <TextBlock Text="abc"/> </local:Test> </Grid>