Spread Windows Forms 15.0
Spread Windows Forms 15.0 Product Documentation / Spread Designer Guide / Designing Shapes / Advanced Topics for Shapes / Creating a Custom Compound Shape
In This Topic
    Creating a Custom Compound Shape
    In This Topic

    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.

    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). 

    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.

    Note: 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.

    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);
            }
    Visual Basic
    Copy Code
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim test As New CompanyWatermark(0, 0, 550, 200)
        FpSpread1_Sheet1.AddShape(test)
    End Sub
    

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

    C#
    Copy Code
    using System;
    using System.Drawing;
    using System.Runtime.Serialization;
    using FarPoint.Win;
    using FarPoint.Win.Spread.DrawingSpace;
    using System.Xml;
    public 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;
            }
        }
    }
    
    Visual Basic
    Copy Code
    Imports System.Runtime.Serialization
    Imports System.Xml
    Imports FarPoint.Win
    Imports FarPoint.Win.Spread.DrawingSpace
    <Serializable()> Public Class CompanyWatermark
        Inherits RectangleShape
        Implements ISerializable
        Private mCompanyColor As Color = Color.Green
        Private mCompanyLogo As Image = My.Resources.SampleLogo
        Private mCompanyName As String = "[Company Name]"
        Private mCompanyText As String = "[Company Text]"
        Friend Sub New()
        End Sub
        ' required for ISerializeSupport
        Public Sub New(left As Integer, top As Integer, width As Integer, height As Integer)
            MyBase.New()
            CanRotate = False
            SetBounds(left, top, width, height)
            UpdateChildren()
        End Sub
        Public Sub New(info As SerializationInfo, context As StreamingContext)
            MyBase.New(info, context)
            mCompanyColor = DirectCast(info.GetValue("cc", GetType(Color)), Color)
            mCompanyLogo = DirectCast(info.GetValue("cl", GetType(Image)), Image)
            mCompanyName = info.GetString("cn")
            mCompanyText = info.GetString("ct")
            UpdateChildren()
        End Sub
        Public Overrides Sub GetObjectData(info As SerializationInfo, context As StreamingContext)
            MyBase.GetObjectData(info, context)
            info.AddValue("cc", mCompanyColor)
            info.AddValue("cl", mCompanyLogo)
            info.AddValue("cn", mCompanyName)
            info.AddValue("ct", mCompanyText)
        End Sub
        Public Property CompanyColor() As Color
            Get
                Return mCompanyColor
            End Get
            Set
                mCompanyColor = Value
                UpdateChildren()
            End Set
        End Property
        Public Property CompanyLogo() As Image
            Get
                Return mCompanyLogo
            End Get
            Set
                mCompanyLogo = Value
                UpdateChildren()
            End Set
        End Property
        Public Property CompanyName() As String
            Get
                Return mCompanyName
            End Get
            Set
                mCompanyName = Value
                UpdateChildren()
            End Set
        End Property
        Public Property CompanyText() As String
            Get
                Return mCompanyText
            End Get
            Set
                mCompanyText = Value
                UpdateChildren()
            End Set
        End Property
        Public Overrides Sub Rotate(angle As Single)
            MyBase.Rotate(angle)
            For Each shape As PSShape In ContainedObjects
                shape.RotationAngle = angle
            Next
        End Sub
        Public Overrides Function Deserialize(r As XmlNodeReader) As Boolean
            Dim inCompanyColor As Boolean = False
            Dim inCompanyLogo As Boolean = False
            Dim doc As New XmlDocument()
            Dim node As XmlNode
            Dim r2 As XmlNodeReader
            Dim r3 As XmlNodeReader
            Dim r4 As XmlNodeReader
            r.MoveToElement()
            node = doc.ReadNode(r)
            r2 = New XmlNodeReader(node)
            r3 = New XmlNodeReader(node)
            r4 = New XmlNodeReader(node)
            PSObject.Deserialize(Me, r2)
            PSShape.Deserialize(Me, r3)
            While r4.Read()
                Select Case r.NodeType
                    Case XmlNodeType.Element
                        If r.Name.Equals("CompanyColor") Then
                            inCompanyColor = True
                        ElseIf r.Name.Equals("CompanyLogo") Then
                            inCompanyLogo = True
                        ElseIf r.Name.Equals("CompanyName") Then
                            mCompanyName = Serializer.DeserializeString(r)
                        ElseIf r.Name.Equals("CompanyText") Then
                            mCompanyText = Serializer.DeserializeString(r)
                        End If
                        Exit Select
                    Case XmlNodeType.Text
                        If inCompanyColor Then
                            mCompanyColor = Serializer.DeserializeColorValue(r.Value)
                        ElseIf inCompanyLogo Then
                            mCompanyLogo = Serializer.DeserializeImage(r.Value)
                        End If
                        Exit Select
                    Case XmlNodeType.EndElement
                        If inCompanyColor AndAlso r.Name.Equals("CompanyColor") Then
                            inCompanyColor = False
                        ElseIf inCompanyLogo AndAlso r.Name.Equals("CompanyLogo") Then
                            inCompanyLogo = False
                        End If
                        Exit Select
                End Select
            End While
            Return True
        End Function
        Public Overrides Function Serialize(w As XmlTextWriter) As Boolean
            w.WriteStartElement("PSShape")
            w.WriteAttributeString("class", Me.[GetType]().FullName)
            w.WriteAttributeString("assembly", Me.[GetType]().Assembly.FullName)
            PSShape.Serialize(Me, 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
        End Function
        Private Sub UpdateChildren()
            ShapeOutlineThickness = 0
            If ContainedObjects.Count = 0 Then
                ' 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
                Dim logo As 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)
                Dim name As 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)
                Dim text As 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
                Dim logo As RectangleShape = DirectCast(ContainedObjects(0), RectangleShape)
                Dim name As TextShape = DirectCast(ContainedObjects(1), TextShape)
                Dim text As TextShape = DirectCast(ContainedObjects(2), TextShape)
                Gradient.Sections = New GradientSection() {New GradientSection(mCompanyColor, 50), New GradientSection(Color.White, 50)}
                logo.Picture = mCompanyLogo
                name.Text = mCompanyName
                text.Text = mCompanyText
            End If
        End Sub
    End Class
    
    See Also