Drawing complex controls
Hi,
I'm working on a fairly complex custom control for displaying data on a radial chart (similar to what can be found here https://www.syncfusion.com/wpf-controls ... adar-chart).
I need to construct a path geometry that shows the data points, as well as add labels around the chart.
My initial thought was to build a path object inside a canvas, then add new textblocks as children and position them with the canvas left and top attached properties. Is this a good approach?
Would it ever be recommended to just override the OnRender instead?
Thanks
I'm working on a fairly complex custom control for displaying data on a radial chart (similar to what can be found here https://www.syncfusion.com/wpf-controls ... adar-chart).
I need to construct a path geometry that shows the data points, as well as add labels around the chart.
My initial thought was to build a path object inside a canvas, then add new textblocks as children and position them with the canvas left and top attached properties. Is this a good approach?
Would it ever be recommended to just override the OnRender instead?
Thanks
Re: Drawing complex controls
Your approach is good, I understand you will have a UserControl with a XAML, and the different dependency properties of the user control will change the aspect.
But I think, moving this to small pieces, like different custom controls for rendering every part using OnRender and then having a master control orchestrating everything is a better approach and also more compatible with having different styles.
Please, let us know if you need help with this.
But I think, moving this to small pieces, like different custom controls for rendering every part using OnRender and then having a master control orchestrating everything is a better approach and also more compatible with having different styles.
Please, let us know if you need help with this.
Re: Drawing complex controls
For example, this is a RadialGauge control we created to be used from Noesis Studio (if you need the source code of this, please let me know, it is still work in progress).
The default template is like this:
For example, the RadialTicks control used in the above template is implemented deriving from FrameworkElement and with this OnRender method:
I hope this helps!
The default template is like this:
Code: Select all
<ControlTemplate TargetType="noesis:RadialGauge">
<Grid>
<Viewbox>
<Grid Width="200" Height="200">
<noesis:RadialBorder
MinAngle="{TemplateBinding MinAngle}" MaxAngle="{TemplateBinding MaxAngle}"
Minimum="{TemplateBinding Minimum}" Maximum="{TemplateBinding Maximum}"
Start="{TemplateBinding Minimum}"
End="{TemplateBinding Maximum}"
BorderWidth="{TemplateBinding ScaleWidth}"
Padding="{TemplateBinding ScalePadding}"
Brush="{TemplateBinding ScaleBrush}"/>
<noesis:RadialBorder
MinAngle="{TemplateBinding MinAngle}" MaxAngle="{TemplateBinding MaxAngle}"
Minimum="{TemplateBinding Minimum}" Maximum="{TemplateBinding Maximum}"
Start="{TemplateBinding Minimum}"
End="{TemplateBinding Value}"
BorderWidth="{TemplateBinding ScaleWidth}"
Padding="{TemplateBinding ScalePadding}"
Brush="{TemplateBinding TrailBrush}"/>
<noesis:RadialTicks
MinAngle="{TemplateBinding MinAngle}" MaxAngle="{TemplateBinding MaxAngle}"
Minimum="{TemplateBinding Minimum}" Maximum="{TemplateBinding Maximum}"
Brush="{TemplateBinding TickBrush}"
Spacing="{TemplateBinding TickSpacing}"
Padding="{TemplateBinding TickPadding}"
TickWidth="{TemplateBinding TickWidth}"
TickLength="{TemplateBinding TickLength}"/>
<noesis:RadialNeedle
MinAngle="{TemplateBinding MinAngle}" MaxAngle="{TemplateBinding MaxAngle}"
Minimum="{TemplateBinding Minimum}" Maximum="{TemplateBinding Maximum}"
Value="{TemplateBinding Value}">
<Rectangle
Fill="{TemplateBinding NeedleBrush}"
Width="{TemplateBinding NeedleWidth}"
Height="{TemplateBinding NeedleLength}"
VerticalAlignment="Top"/>
</noesis:RadialNeedle>
</Grid>
</Viewbox>
</Grid>
</ControlTemplate>
Code: Select all
void RadialTicks::OnRender(Noesis::DrawingContext* context)
{
Brush* brush = GetBrush();
float minimum = GetMinimum();
float maximum = GetMaximum();
float width = GetTickWidth();
float height = GetTickLength();
float padding = GetPadding();
float spacing = Max(GetSpacing(), fabsf(minimum - maximum) * 0.001f);
float minAngle = GetMinAngle();
float maxAngle = GetMaxAngle();
Ptr<Pen> pen = MakePtr<Pen>();
pen->SetBrush(brush);
pen->SetThickness(width);
Size area = GetRenderSize();
float orgX = area.width * 0.5f;
float orgY = area.height * 0.5f;
for (float i = minimum; i <= maximum; i += spacing)
{
float angle = ValueToAngle(i, minimum, maximum, minAngle, maxAngle);
Point p0(orgX, padding);
Point p1(orgX, padding + height);
Transform2 mtx = Transform2::Identity();
mtx.RotateAt(angle * DegToRad, orgX, orgY);
context->DrawLine(pen, p0 * mtx, p1 * mtx);
}
}
Re: Drawing complex controls
Hi, thanks. I like this approach.
The code you've provided is very helpful.
Although, I'm stuck on using the OnRender with text. I don't quite understand how to use FormattedText to draw labels on each point.
For context, here is my OnRender function so far. :
The code you've provided is very helpful.
Although, I'm stuck on using the OnRender with text. I don't quite understand how to use FormattedText to draw labels on each point.
For context, here is my OnRender function so far. :
Code: Select all
void RadarSeries::OnRender(DrawingContext* drawingContext)
{
Brush* fill = GetFill();
Brush* stroke = GetStroke();
float strokeThickness = GetStrokeThickness();
Ptr<Pen> pen = *new Pen();
pen->SetBrush(stroke);
pen->SetThickness(strokeThickness);
Size area = GetRenderSize();
float radius = Math::min(area.width, area.height) * 0.5f;
float centerX = area.width * 0.5f;
float centerY = area.height * 0.5f;
int numSides = 6; // Items.Count
Ptr<PathGeometry> geo = *new PathGeometry();
Ptr<PathFigure> figure = *new PathFigure();
figure->SetIsClosed(true);
float angleStep = (float)2PI / numSides;
float angle = 0.0f;
float maximum = GetMaximum();
ItemCollection* items = GetItems();
{
Ptr<BaseComponent> item = items->GetItemAt(0);
const TypeClass* type = item->GetClassType();
const TypeProperty* prop = type->FindProperty(Symbol(GetValuePath()));
float value;
prop->Get(item.GetPtr(), &value);
float fillRadius = value / maximum * radius;
figure->SetStartPoint(Point(centerX + fillRadius * cosf(angle), centerY + fillRadius * sinf(angle)));
angle += angleStep;
}
for (int i = 1; i < numSides; i++)
{
Ptr<BaseComponent> item = items->GetItemAt(i);
const TypeClass* type = item->GetClassType();
const TypeProperty* prop = type->FindProperty(Symbol(GetValuePath()));
float value;
prop->Get(item.GetPtr(), &value);
float fillRadius = value / maximum * radius;
Ptr<LineSegment> segment =
*new LineSegment(Point(centerX + fillRadius * cosf(angle), centerY + fillRadius * sinf(angle)), true);
figure->GetSegments()->Add(segment);
angle += angleStep;
}
geo->GetFigures()->Add(figure);
drawingContext->DrawGeometry(fill, pen, geo);
}
Re: Drawing complex controls
This is the OnRender source code for RadialNumbers class.
Code: Select all
void RadialNumbers::OnRender(Noesis::DrawingContext* context)
{
FontFamily* font = GetFontFamily();
float minimum = GetMinimum();
float maximum = GetMaximum();
float padding = GetPadding();
float spacing = Max(GetSpacing(), fabsf(minimum - maximum) * 0.001f);
float fontSize = GetFontSize();
float minAngle = GetMinAngle();
float maxAngle = GetMaxAngle();
bool rotated = GetRotated();
Brush* brush = GetBrush();
Size area = GetRenderSize();
float orgX = area.width * 0.5f;
float orgY = area.height * 0.5f;
for (float i = minimum; i <= maximum; i += spacing)
{
float angle = ValueToAngle(i, minimum, maximum, minAngle, maxAngle);
String str;
StringFormat(i, str);
Ptr<FormattedText> text = MakePtr<FormattedText>(str.Str(), font, fontSize, brush);
Rect rect = text->GetBounds();
if (rotated)
{
Transform2 mtx = Transform2::Identity();
mtx = PreTrans(orgX - rect.width * 0.5f, padding - rect.height * 0.5f, mtx);
mtx.RotateAt(angle * DegToRad, orgX, orgY);
context->PushTransform(MakePtr<MatrixTransform>(mtx));
context->DrawText(text, rect);
context->Pop();
}
else
{
Transform2 mtx = Transform2::Identity();
mtx.RotateAt(angle * DegToRad, orgX, orgY);
Point o = Point(orgX, padding) * mtx;
rect.x = o.x - rect.width * 0.5f;
rect.y = o.y - rect.height * 0.5f;
context->DrawText(text, rect);
}
}
}
Re: Drawing complex controls
Hey, thanks for sharing.
Looks like the version I'm on (3.17) doesn't have any parameters to the constructor of FormattedText.
I was able to reference this thread to get text drawing viewtopic.php?p=13503&hilit=FormattedText#p13503
But I'm not too sure about my usage of calls to Measure and Layout. Calling Measure was the only way I was able to get the bounds of the text first before calling Layout.
Looks like the version I'm on (3.17) doesn't have any parameters to the constructor of FormattedText.
I was able to reference this thread to get text drawing viewtopic.php?p=13503&hilit=FormattedText#p13503
But I'm not too sure about my usage of calls to Measure and Layout. Calling Measure was the only way I was able to get the bounds of the text first before calling Layout.
Code: Select all
float labelAngle = 0;
for (int i = 0; i < numSides; i++)
{
Ptr<FormattedText> text = *new FormattedText();
text->BuildTextRuns(
"Label",
nullptr,
font,
FontWeight_Normal,
FontStretch_Normal,
FontStyle_Normal,
fontSize,
0.0f,
nullptr,
foreground,
nullptr,
TextDecorations_None,
1);
text->Measure(TextAlignment_Left, TextWrapping_Wrap, TextTrimming_None, FLT_MAX, FLT_MAX, 0.0f, LineStackingStrategy_MaxHeight);
Rect rect = text->GetBounds();
text->Layout(
TextAlignment_Left, TextWrapping_Wrap, TextTrimming_None, rect.width, rect.height, 0.0f, LineStackingStrategy_MaxHeight);
Point p = Point(centerX + radius * cosf(labelAngle), centerY + radius * sinf(labelAngle));
rect.x = p.x - rect.width * 0.5f;
rect.y = p.y - rect.height * 0.5f;
drawingContext->DrawText(text, rect);
labelAngle += angleStep;
}Re: Drawing complex controls
This is what the constructor of FormattedText is calling in 3.2.X
Please, let me know if this helps.
Code: Select all
void FormattedText::CalculateMetrics(const char* text, FontFamily* fontFamily,
FontWeight weight, FontStretch stretch, FontStyle style,
float fontSize, Brush* foreground, FlowDirection flowDirection,
float maxWidth, float maxHeight, float lineHeight,
TextAlignment textAlignment, TextTrimming textTrimming)
{
BuildTextRuns(text, nullptr, fontFamily, weight, stretch, style, fontSize, 0.0f, nullptr,
foreground, nullptr, flowDirection, TextDecorations_None, 0);
TextWrapping textWrapping = textTrimming == TextTrimming_None ?
TextWrapping_Wrap : TextWrapping_WrapWithOverflow;
Size sz = Measure(textAlignment, textWrapping, textTrimming, maxWidth, maxHeight, lineHeight,
LineStackingStrategy_MaxHeight, flowDirection);
Layout(textAlignment, textWrapping, textTrimming, sz.width, sz.height, Thickness(), lineHeight,
LineStackingStrategy_MaxHeight, flowDirection, false);
}
Re: Drawing complex controls
This does help, thank you!
I really appreciate the support.
I really appreciate the support.
Who is online
Users browsing this forum: No registered users and 2 guests