SCADA Valve: Overriding SetBoundsCore and GetBoundsCore

Code samples and article discussion

Moderators: Frank Hileman, Anne Szyjan

Post Reply
User avatar
Frank Hileman
Site Admin
Posts: 1400
Joined: Sun Jul 25, 2004 8:16 pm
Location: California
Contact:

SCADA Valve: Overriding SetBoundsCore and GetBoundsCore

Post by Frank Hileman » Tue Apr 10, 2007 4:51 pm

This post describes the purpose of the SetBoundsCore and GetBoundsCore methods in a Picture component.

SetBoundsCore is passed a rectangle. If you wish to leave the Picture bounds unchanged, you return false from the function.

So the first thing you might do is see if the passed in rectangle is equal to the current Picture Bounds. But what is the Picture Bounds? That depends on GetBoundsCore. SetBoundsCore is the Bounds property set function, and GetBoundsCore is the Bounds property get function.

The default behavior, when not overridden, is to put a ScalingTranslation on the Picture. This does not go into the Transformation property, but you can see the effect by the difference between the Picture Bounds and the Bounds of the children. See "Sub Pictures have an extra transformation" in the Users Guide for more information, and here: http://www.vgdotnet.org/forums/viewtopic.php?t=39

Override SetBoundsCore to change the default behavior. If you have only a single object in your Picture, you might simply change the Bounds of that object to match the passed in one. This is usually not the case.

GetBoundsCore is only needed if you want to return a "fake" bounds that is not equivalent to the union of the child bounds. For example, you may want some decorations to stick out of your Picture, yet not be considered as part of the Bounds. To do this, modify the "base object", perhaps a Rectangle, to be the same as the passed in bounds in SetBoundsCore. Reposition your decorating objects relative to that. In GetBoundsCore, return only the Bounds of the base object, ignoring the decorations.

You must be consistent between SetBoundsCore and GetBoundsCore. The Get must always return what the Set was passed, with these exceptions:
  • You may limit the width or height to a min or max. Do not try to set both the width and height of any children to 0.
  • You may force the rectangle to always maintain a fixed aspect ratio.
  • You may prohibit any resizing, and return false.
If there is not a logical relationship between SetBoundsCore and GetBoundsCore, the designer will go insane when trying to resize your object.

Here is a SCADA valve sample. Depending on the ifdef, it either enforces no resize at all, or a resize of the Valve ports only. The ports are two lines on each side of the Valve body. By overriding GetBoundsCore we make it appear as if the Valve Bounds does not include the text label.

Code: Select all

// Copyright © 2005 Prodige Software Corporation. All rights reserved.
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using Prodige.Drawing;
using Prodige.Drawing.Styles;

namespace ResizableComponents.UI
{
	/// <summary>
	/// A SCADA-style valve with a custom resizing algorithm.
	/// </summary>
	public class Valve : Picture
	{
		private Prodige.Drawing.Polyline body;
		private Prodige.Drawing.Polyline leftPort;
		private Prodige.Drawing.Polyline rightPort;
		private Prodige.Drawing.Group mainGroup;
		private Prodige.Drawing.Rectangle label;
		private bool open;
	
		public Valve()
		{
			InitializeComponent();	// required by the designer
		}

		/// <summary>
		/// The text to display in this valve.
		/// </summary>
		[DefaultValue("valve")]
		[Description("Text to display in this valve.")]
		public string Text
		{
			get { return label.Text; }
			set { label.Text = value; }
		}

		/// <summary>
		/// Specifies whether this valve is open or closed.
		/// </summary>
		[DefaultValue(false)]
		[Description("Specifies whether this valve is open or closed.")]
		public bool Open
		{
			get { return open; }
			set
			{
				if (value == open)
					return;
				open = value;
				body.Style = open ? "Open" : "Closed";
			}
		}


#if WIDEN_PORTS
		protected override bool SetBoundsCore(FRectangle bounds, BoundsChanges changes)
		{
			// a custom resizing algorithm, to change child objects, instead
			// of scaling
			// only ports get wider; height cannot change
			bounds.Height = mainGroup.Height;

			// don't allow new width to be smaller than body
			float extra = bounds.Width - body.Width;
			if (extra < 0)
			{
				bounds.Width = body.Width;
				extra = 0;
			}
			if (bounds == mainGroup.Bounds)
				return false;
			Vector change = bounds.Location - mainGroup.Location;
			mainGroup.Location += change;
			label.Location += change;

			// center label in x dimension
			label.Location = new Vector(bounds.Left + bounds.Width/2 - label.Width/2,
				label.Location.Y);
			// offset body by half extra
			float halfExtra = extra/2;
			// don't set width and height of polyline both to 0 -- throws exceptions during display
			if (halfExtra == 0)
				halfExtra = 1;
			body.Location = new Vector(mainGroup.Location.X + halfExtra, body.Location.Y);
			// change port lengths
			leftPort.Width = halfExtra;
			rightPort.Width = halfExtra;
			rightPort.Location = new Vector(bounds.Right - halfExtra, rightPort.Location.Y);
			return true;
		}
#else
		protected override bool SetBoundsCore(FRectangle bounds, BoundsChanges changes)
		{
			// a custom resizing algorithm, to change child objects, instead
			// of scaling
			// width and height cannot be changed, only location
			bounds.Width = mainGroup.Width;
			bounds.Height = mainGroup.Height;
			if (bounds == mainGroup.Bounds)
				return false;
			Vector change = bounds.Location - mainGroup.Location;
			mainGroup.Location += change;
			label.Location += change;
			return true;
		}
#endif


		protected override FRectangle GetBoundsCore()
		{
			// label bounds is ignored
			return mainGroup.Bounds;
		}


		#region Designer generated code
		/// <summary> 
		/// Required for designer support - do not modify with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.body = new Prodige.Drawing.Polyline();
			this.leftPort = new Prodige.Drawing.Polyline();
			this.rightPort = new Prodige.Drawing.Polyline();
			this.label = new Prodige.Drawing.Rectangle();
			this.mainGroup = new Prodige.Drawing.Group();
			((System.ComponentModel.ISupportInitialize)(this)).BeginInit();
			// 
			// body
			// 
			this.body.Closed = true;
			this.body.DrawAction = Prodige.Drawing.ShapeDrawAction.Fill;
			this.body.Points.AddRange(new Prodige.Drawing.Vector[] {
																	   new Prodige.Drawing.Vector(20F, 100F),
																	   new Prodige.Drawing.Vector(20F, 120F),
																	   new Prodige.Drawing.Vector(62F, 100F),
																	   new Prodige.Drawing.Vector(62F, 120F)});
			this.body.Style = "Closed";
			// 
			// leftPort
			// 
			this.leftPort.DrawAction = Prodige.Drawing.ShapeDrawAction.Edge;
			this.leftPort.Points.AddRange(new Prodige.Drawing.Vector[] {
																		   new Prodige.Drawing.Vector(0F, 110F),
																		   new Prodige.Drawing.Vector(20F, 110F)});
			// 
			// rightPort
			// 
			this.rightPort.DrawAction = Prodige.Drawing.ShapeDrawAction.Edge;
			this.rightPort.Points.AddRange(new Prodige.Drawing.Vector[] {
																			new Prodige.Drawing.Vector(62F, 110F),
																			new Prodige.Drawing.Vector(80F, 110F)});
			// 
			// label
			// 
			this.label.DrawAction = Prodige.Drawing.ShapeDrawAction.Fill;
			this.label.Location = new Prodige.Drawing.Vector(-10F, 120F);
			this.label.Size = new Prodige.Drawing.FSize(100F, 50F);
			this.label.Style = "Label";
			this.label.Text = "valve";
			// 
			// mainGroup
			// 
			this.mainGroup.Elements.AddRange(new Prodige.Drawing.Element[] {
																			   this.leftPort,
																			   this.body,
																			   this.rightPort});
			// 
			// Valve
			// 
			this.Elements.AddRange(new Prodige.Drawing.Element[] {
																	 this.label,
																	 this.mainGroup});
			this.Styles.Add(new Prodige.Drawing.Styles.Style("Closed", null, new Prodige.Drawing.Styles.SolidFill(System.Drawing.Color.Red), null, null));
			this.Styles.Add(new Prodige.Drawing.Styles.Style("Open", null, new Prodige.Drawing.Styles.SolidFill(System.Drawing.Color.Lime), null, null));
			this.Styles.Add(new Prodige.Drawing.Styles.Style("Label", null, new Prodige.Drawing.Styles.SolidFill(0F), null, null));
			((System.ComponentModel.ISupportInitialize)(this)).EndInit();

		}
		#endregion
	}
}
More examples:
http://www.vgdotnet.org/forums/viewtopic.php?t=96
Also look at the TransparentButton class in the TransparentButtons sample installed by VG.net.

Post Reply