Spread Windows Forms 11.0 Product Documentation
Creating a Custom Compound Shape
Spread Windows Forms 11.0 Product Documentation > Spread Designer Guide > Designing Shapes > Advanced Topics for Shapes > Creating a Custom Compound Shape

One of the inherent capabilities of all shape classes is the ability to have other shape objects embedded in them. This is a great way of making compound shapes. This topic explains how to create a custom compound shape that combines several built-in shapes and serves as a watermark, which can identify information or corporate identity subtly (without obstructing or obscuring the spreadsheet) as shown here. With this example, you can see how to create a custom compound shape.

Example of a compound shape on a spreadsheet

To create this custom compound shape, define a custom shape class, called CompanyWatermark, and set the properties to customize this appearance. This custom shape combines several elements: a background gradient fades from a specified color to transparency; an embedded shape contains an image of the company logo; a separate embedded text shape spells the company name; and a text shape provides additional information such as Web site address or company motto or security classification.

Deriving the Custom Class

Start by deriving a class from the standard RectangleShape class. This creates a rectangular palette in which to place the embedded shapes. You could just as easily use other shapes such as ellipses, or polygons, but your embedded shapes may be "clipped" (because embedded shapes do not exceed the boundaries of the parent shape). Here is the code:

C#
Copy Code
public class CompanyWatermark : FarPoint.Win.Spread.DrawingSpace.RectangleShape
Visual Basic
Copy Code
Public Class CompanyWatermark
Inherits FarPoint.Win.Spread.DrawingSpace.RectangleShape

Setting Properties

Then create properties for accessing the internal shapes. One of the benefits of creating a compound shape is that you can hide many of the unnecessary properties of the embedded shapes. You can expose, through custom properties, only the pertinent information. The first property is the "CompanyColor" property. This is the main color in the background gradient, which gradually fades to transparency in the main shape. It is also alpha-blended so as not to fully obscure the sheet below. With the gradient class, other properties such as Style, which determines the gradient direction and type of gradient, can also be set. This example uses a Style of GradientStyle.TopDown.

The next property is an image property called "CompanyLogo". This property sets the graphic to be displayed in the embedded logo shape. This example locks the logo shape to the upper left corner of the watermark. You could very simply extend this with properties to allow alignment of the logo.

Embedding Text Shapes

The most important part of the watermark is the company name. You can create this with an embedded TextShape object to represent the company name. This has been exposed on the watermark shape as the "CompanyName" property, which is a string. There are many customizable features of the TextShape class that can be exposed such as Font or ForeColor, though they are not exposed here. Also, for simplicity of this sample, the company name shape is locked to be centered in the watermark shape.

And, finally, there is a secondary TextShape object that holds some additional text information in the watermark. This could be a company motto, slogan, Web site address or even words like "CONFIDENTIAL" or "Copyright 2005". Anchor this text to the bottom left corner of the watermark shape for this example. Many additional properties can be set on this embedded shape. This example only exposes items of interest. The text for this shape is exposed as the "CompanyText" property.

Concluding Remarks

In the figure above, you see the full implementation of the CompanyWatermark class with the other functions that were described. To create the pictured watermark, choose "System.Drawing.Colors.SeaGreen" as the "CompanyColor", a custom bitmap as the "CompanyLogo", "SunRay Finance" as the "CompanyName", and "CONFIDENTIAL" as the "CompanyText".

Since Spread can scroll the viewport panes, override the TopChange and LeftChange events of Spread to move the watermark with the current sheet so that it always appears across the top and is as wide as the spreadsheet control. Also override the Top and Left properties of the watermark class to move the embedded objects to be visible always within the watermark.

One of the features of the TextShape class is to stretch the text always to the dimensions of the shape using the Font associated with it. This has been changed in the example with the MeasureText method. The text string is measured using the control’s Graphics object and current Font and then sets the TextShape dimensions accordingly. This guarantees that the shape keeps the size locked to the specified font and font size.

Using Code

The following code adds the compound shape:

C#
Copy Code
private void Form1_Load(object sender, EventArgs e)
        {
            CompanyWatermark test = new CompanyWatermark(0, 0, 550, 200);
            fpSpread1_Sheet1.AddShape(test);
        }

The following code is from the WatermarkShape.cs file and creates the compound shape.

C#
Copy Code

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;
using FarPoint.Win;
using FarPoint.Win.Spread.DrawingSpace;
using System.Xml;
using System.Reflection;

namespace WatermarkShape
{
    [Serializable()]
    class CompanyWatermark : RectangleShape, ISerializable
    {
        private Color mCompanyColor = Color.Green;
        private Image mCompanyLogo = WatermarkShape.Properties.Resources.SampleLogo;
        private string mCompanyName = "[Company Name]";
        private string mCompanyText = "[Company Text]";

        internal CompanyWatermark() { } // required for ISerializeSupport
        public CompanyWatermark(int left, int top, int width, int height) : base()
        {
            CanRotate = false;
            SetBounds(left, top, width, height);
            UpdateChildren();
        }
        public CompanyWatermark(SerializationInfo info, StreamingContext context) : base(info, context)
        {
            mCompanyColor = (Color)info.GetValue("cc", typeof(Color));
            mCompanyLogo = (Image)info.GetValue("cl", typeof(Image));
            mCompanyName = info.GetString("cn");
            mCompanyText = info.GetString("ct");
            UpdateChildren();
        }
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue("cc", mCompanyColor);
            info.AddValue("cl", mCompanyLogo);
            info.AddValue("cn", mCompanyName);
            info.AddValue("ct", mCompanyText);
        }
        public Color CompanyColor
        {
            get
            {
                return mCompanyColor;
            }
            set
            {
                mCompanyColor = value;
                UpdateChildren();
            }
        }

        public Image CompanyLogo
        {
            get
            {
                return mCompanyLogo;
            }
            set
            {
                mCompanyLogo = value;
                UpdateChildren();
            }
        }

        public string CompanyName
        {
            get
            {
                return mCompanyName;
            }
            set
            {
                mCompanyName = value;
                UpdateChildren();
            }
        }

        public string CompanyText
        {
            get
            {
                return mCompanyText;
            }
            set
            {
                mCompanyText = value;
                UpdateChildren();
            }
        }

        public override void Rotate(float angle)
        {
            base.Rotate(angle);
            foreach (PSShape shape in ContainedObjects)
                shape.RotationAngle = angle;
        }
        public override bool Deserialize(XmlNodeReader r)
        {
            bool inCompanyColor = false;
            bool inCompanyLogo = false;

            XmlDocument doc = new XmlDocument();
            XmlNode node;
            XmlNodeReader r2;
            XmlNodeReader r3;
            XmlNodeReader r4;

            r.MoveToElement();
            node = doc.ReadNode(r);
            r2 = new XmlNodeReader(node);
            r3 = new XmlNodeReader(node);
            r4 = new XmlNodeReader(node);

            PSObject.Deserialize(this, r2);
            PSShape.Deserialize(this, r3);
            while (r4.Read())
            {
                switch (r.NodeType)
                {
                    case XmlNodeType.Element:
                        if (r.Name.Equals("CompanyColor"))
                            inCompanyColor = true;
                        else if (r.Name.Equals("CompanyLogo"))
                            inCompanyLogo = true;
                        else if (r.Name.Equals("CompanyName"))
                            mCompanyName = Serializer.DeserializeString(r);
                        else if (r.Name.Equals("CompanyText"))
                            mCompanyText = Serializer.DeserializeString(r);
                        break;
                    case XmlNodeType.Text:
                        if (inCompanyColor)
                            mCompanyColor = Serializer.DeserializeColorValue(r.Value);
                        else if (inCompanyLogo)
                            mCompanyLogo = Serializer.DeserializeImage(r.Value);
                        break;
                    case XmlNodeType.EndElement:
                        if (inCompanyColor && r.Name.Equals("CompanyColor"))
                            inCompanyColor = false;
                        else if (inCompanyLogo && r.Name.Equals("CompanyLogo"))
                            inCompanyLogo = false;
                        break;
                }
            }
            return true;
        }
        public override bool Serialize(XmlTextWriter w)
        {
            w.WriteStartElement("PSShape");
            w.WriteAttributeString("class", this.GetType().FullName);
            w.WriteAttributeString("assembly", this.GetType().Assembly.FullName);

            PSShape.Serialize(this, w);
            Serializer.SerializeColor(mCompanyColor, "CompanyColor", w);
            Serializer.SerializeImage(mCompanyLogo, "CompanyLogo", w);
            Serializer.SerializeString(mCompanyName, "CompanyName", w);
            Serializer.SerializeString(mCompanyText, "CompanyText", w);

            w.WriteEndElement();  // object
            return true;
        }
        private void UpdateChildren()
        {
            ShapeOutlineThickness = 0;
            if( ContainedObjects.Count == 0 )
            {   // create the child shapes
                // gradient effect for company color
                BackColor = Color.Transparent;
                Gradient.Sections = new GradientSection[] { new GradientSection(mCompanyColor, 255, 200), new GradientSection(Color.White, 255, 255) };
                Gradient.Style = GradientStyle.LinearTopDown;
// company logo is aligned to left and uses image width and height to align company text below
                RectangleShape logo = new RectangleShape();
                logo.BackColor = Color.Transparent;
                logo.ShapeOutlineThickness = 0;
                logo.SetBounds(0, 0, mCompanyLogo.Width, mCompanyLogo.Height);
                logo.Picture = mCompanyLogo;
                logo.PictureTransparencyColor = Color.White;
                ContainedObjects.Add(logo);
                TextShape name = new TextShape();
                name.Text = mCompanyName;
                name.ForeColor = Color.Orange;
                name.ShapeOutlineColor = Color.Black;
                name.ShapeOutlineStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                name.ShapeOutlineThickness = 1;
                ContainedObjects.Add(name);
                name.SetBounds(logo.Width + 5, (logo.Height - name.Height) / 2, name.Width, name.Height);
                TextShape text = new TextShape();
                text.Text = mCompanyText;
                text.ForeColor = Color.Red;
                text.ShapeOutlineColor = Color.Black;
                text.ShapeOutlineStyle = System.Drawing.Drawing2D.DashStyle.Solid;
                text.ShapeOutlineThickness = 1;
                ContainedObjects.Add(text);
                text.SetBounds(0, logo.Height + 5, text.Width, text.Height);
            }
            else
            {   // update the child shapes
                RectangleShape logo = (RectangleShape)ContainedObjects[0];
                TextShape name = (TextShape)ContainedObjects[1];
                TextShape text = (TextShape)ContainedObjects[2];
                Gradient.Sections = new GradientSection[] { new GradientSection(mCompanyColor, 50), new GradientSection(Color.White, 50) };
                logo.Picture = mCompanyLogo;
                name.Text = mCompanyName;
                text.Text = mCompanyText;
            }
        }
    }
}