How to do shape aggregation ?

Apr 23 at 8:16 AM

I have added a custom shape in my project, named as "Tank", based on the propertry (ex: Tank Level) I want to show it's current level with differentiating color code.

For example, If I set tank level property as 50 percent, then what I am trying to achieve is, to add five small boxes inside the existing custom shape and mark it's fillstyle in particular color.

But, so far I couldn't find a way to aggregate the custom shape and also to display the new filling shapes inside it. Can you please advise on this ?

Thanks in advance.
Apr 25 at 1:07 PM
Edited Apr 25 at 1:20 PM
Hi foiazh,

if I get you right, you wrote a custom shape with at least one additional property.

In this case, I would advise to override the methods for calculating, transforming and drawing the shape points in order to draw the fill level directly instead of creating a workaround with aggregated shapes that will probably have some side effects. Moreover, drawing directly is way faster than aggregating 1 to 10 boxes.

Assuming that you derived from RectangleBase, you will have to override these methods:
private Point[] fillLevelPoints = new Point[4];
private float fillLevel = 0.67f;

// ToDo: Add a property for the fillLevel field
// ToDo: Implement saving and loading the fillLevel property to/from repository

protected override bool CalculatePath() {
    if (base.CalculatePath()) {
        // Calculate the (unrotated and untranslated) graphics path for the base 
        // shape (a rectangle)
        Path.AddRectangle(new RectangleF(-Width / 2f, -Height / 2f, Width, Height));

        // Calculate the fill level
        int left = (int)Math.Round(-Width / 2f) + LineStyle.LineWidth;
        int right = left + Width - (2 * LineStyle.LineWidth);
        int bottom = (int)Math.Round(Height / 2f) - LineStyle.LineWidth;
        int top = (int)Math.Round(bottom - (Height * fillLevel)) 
                                    + (2 * LineStyle.LineWidth);

        fillLevelPoints[0].X = left;
        fillLevelPoints[0].Y = top;
        fillLevelPoints[1].X = right;
        fillLevelPoints[1].Y = top;
        fillLevelPoints[2].X = right;
        fillLevelPoints[2].Y = bottom;
        fillLevelPoints[3].X = left;
        fillLevelPoints[3].Y = bottom;

        return true;
    return false;

protected override void TransformDrawCache(int deltaX, int deltaY, int deltaAngle, 
                                int rotationCenterX, int rotationCenterY) {
    base.TransformDrawCache(deltaX, deltaY, deltaAngle, 
                        rotationCenterX, rotationCenterY);

public override void Draw(Graphics graphics) {
    if (DisplayService != null) {
        Brush brush = ToolCache.GetBrush(DisplayService.HintBackgroundStyle);
        graphics.FillPolygon(brush, fillLevelPoints);

        Pen pen = ToolCache.GetPen(DisplayService.HintForegroundStyle, null, null);
        graphics.DrawPolygon(pen, fillLevelPoints);
In the setter of your FillLevel property, you have to call InvalidateDrawCache() and Invalidate() in order to trigger a re-calculation of the shape and a redraw.

That's all (for a very basic implementation).
I would also advise to add another IColorStyle or IFillstyle property for drawing the fill level of the tank in a customizable color (you should not use DisplayService.HintBackgroundStyle).
Apr 27 at 6:16 AM
Thanks for your timely help Kurt, that one is of great help.