在Windows 10的UWP(通用Windows应用)中创建自定义形状控件 [英] Create custom Shape Control in UWP (Universal Windows Apps), Windows 10
问题描述
我想创建一个自定义的Shape
控件,该控件根据某些自定义属性绘制不同的形状,例如Polygon
,Ellipse
,Rectangle
等.
I want to create a custom Shape
control, that paints different shapes like Polygon
, Ellipse
, Rectangle
, etc, depending on some custom properties.
我能够像这样创建自定义模板控件ColorShape
:
I was able to create a custom template control ColorShape
like this:
<Style TargetType="local:CustomShape">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomShape">
<ContentControl x:Name="shapeParent">
</ContentControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
然后,覆盖OnTemplateChanged
方法,并在shapeParent
ContentControl
And then, override the OnTemplateChanged
method, and insert a corresponding Shape
control inside the shapeParent
ContentControl
但是我想要实际上是扩展Shape
,所以我可以用相同的方式来处理所有形状(框架和自定义).
But what I'd like is to actually extend the Shape
, so I can treat all the shapes, framewok and custom, in the same way.
在WPF中,我们能够扩展Shape
并覆盖属性DefiningGeometry
.
在UWP中,不存在任何要覆盖的DefiningGeometry
属性.
In WPF we were able to extend the Shape
and override the property DefiningGeometry
.
In UWP it doesn´t exist any DefiningGeometry
property to override.
如何创建自定义Shape
控件并定义相应的Geometry?
How is it possible to create a custom Shape
control and define the corresponding Geometry?
推荐答案
我发现在UWP中创建自定义形状的唯一方法是扩展 Data
属性.
The only way I found to create custom shapes in UWP is to extend the Path
class and set its Data
property.
在布置相关部分(如LayoutUpdated
事件或ArrangeOverride
方法)时,请勿完成更新Data
属性以解决其他依赖项属性(例如Width
)变化的问题.
Updating the Data
property to account for changes in other dependency properties (like Width
) must not be done in layouting relevant sections, like the LayoutUpdated
event or the ArrangeOverride
method.
设置Data
会导致另一个布局运行,因此将其设置为在此期间调用的任何内容都会导致异常:
Setting Data
leads to another layout run, so setting it in anything that is called during that would lead to an exception:
检测到布局周期.布局无法完成
Layout cycle detected. Layout could not complete
我使用的方法是为属性更改的事件注册处理程序,并在其中更新Data
.
The way I use is to register handler for property changed events and update Data
in them.
我写了一个博客文章对此进行了更详细的说明.
I have written a blog post that explains it in a bit more detail.
这是我使用的示例:
public class CandlestickShape : Path
{
public double StartValue
{
get { return Convert.ToDouble(GetValue(StartValueProperty)); }
set { SetValue(StartValueProperty, value); }
}
public static readonly DependencyProperty StartValueProperty =
DependencyProperty.Register("StartValue", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
public double EndValue
{
get { return Convert.ToDouble(GetValue(EndValueProperty)); }
set { SetValue(EndValueProperty, value); }
}
public static readonly DependencyProperty EndValueProperty =
DependencyProperty.Register("EndValue", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
public double MinValue
{
get { return Convert.ToDouble(GetValue(MinValueProperty)); }
set { SetValue(MinValueProperty, value); }
}
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
public double MaxValue
{
get { return Convert.ToDouble(GetValue(MaxValueProperty)); }
set { SetValue(MaxValueProperty, value); }
}
public static readonly DependencyProperty MaxValueProperty =
DependencyProperty.Register("MaxValue", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
/// <summary>
/// Defines how many Pixel should be drawn for one Point
/// </summary>
public double PixelPerPoint
{
get { return Convert.ToDouble(GetValue(PointsPerPixelProperty)); }
set { SetValue(PointsPerPixelProperty, value); }
}
public static readonly DependencyProperty PointsPerPixelProperty =
DependencyProperty.Register("PixelPerPoint", typeof(double), typeof(CandlestickShape), new PropertyMetadata(0));
public CandlestickShape()
{
this.RegisterPropertyChangedCallback(CandlestickShape.WidthProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.StartValueProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.EndValueProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.MinValueProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.MaxValueProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
this.RegisterPropertyChangedCallback(CandlestickShape.PointsPerPixelProperty, new DependencyPropertyChangedCallback(RenderAffectingPropertyChanged));
}
private void RenderAffectingPropertyChanged(DependencyObject o, DependencyProperty e)
{
(o as CandlestickShape)?.SetRenderData();
}
private void SetRenderData()
{
var maxBorderValue = Math.Max(this.StartValue, this.EndValue);
var minBorderValue = Math.Min(this.StartValue, this.EndValue);
double topLineLength = (this.MaxValue - maxBorderValue) * this.PixelPerPoint;
double bottomLineLength = (minBorderValue - this.MinValue) * this.PixelPerPoint;
double bodyLength = (this.EndValue - this.StartValue) * this.PixelPerPoint;
var fillColor = new SolidColorBrush(Colors.Green);
if (bodyLength < 0)
fillColor = new SolidColorBrush(Colors.Red);
bodyLength = Math.Abs(bodyLength);
var bodyGeometry = new RectangleGeometry
{
Rect = new Rect(new Point(0, topLineLength), new Point(this.Width, topLineLength + bodyLength)),
};
var topLineGeometry = new LineGeometry
{
StartPoint = new Point(this.Width / 2, 0),
EndPoint = new Point(this.Width / 2, topLineLength)
};
var bottomLineGeometry = new LineGeometry
{
StartPoint = new Point(this.Width / 2, topLineLength + bodyLength),
EndPoint = new Point(this.Width / 2, topLineLength + bodyLength + bottomLineLength)
};
this.Data = new GeometryGroup
{
Children = new GeometryCollection
{
bodyGeometry,
topLineGeometry,
bottomLineGeometry
}
};
this.Fill = fillColor;
this.Stroke = new SolidColorBrush(Colors.Black);
}
protected override Size ArrangeOverride(Size finalSize)
{
double height = (MaxValue - MinValue) * PixelPerPoint;
return new Size(this.Width, height);
}
protected override Size MeasureOverride(Size availableSize)
{
double height = (MaxValue - MinValue) * PixelPerPoint;
return new Size(this.Width, height);
}
}
这篇关于在Windows 10的UWP(通用Windows应用)中创建自定义形状控件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!