DCSIMG
January 2008 - Posts - Design .Net

Design .Net

Designers, Controls And everything between them.

January 2008 - Posts

Create your own Designer or Introduction To Custom Designers - Part 2: UI, Glyphs

In my last post I have demonstrated how to create your own Designer, in this post I will continue on demonstrating on how to create your own Designer.

My goal in this series of posts is to explain how to create and extended Designers. For this reason I have created a sample. This plain sample is a UserControl that contains an PictureBox and a Label. Using many of this controls can be a bit frustrating, since we can change the Image of the PictureBox without encapsulate the Image Property. As I mention in my last post we would like to avoid this. This is just a simple example of how to use and extend your Control Designer.

This is how it looks:

control101

The UI in your Designer is implemented by Glyphs, each on of your Glyphs is basically a UI access point that during designing you can use to manipulate your control with.

Step 1: Create a Glyph, first you must inherit from the Glyph class, our Implementation to the Glyph should contains reference to the ControlDesigner, Adorner and the various services. This will help our work in designing our Control.

public class PictureSelectionGlyph : Glyph

{

    //References to services that we will need.

    private BehaviorService m_BehaviorService = null;

    private IComponentChangeService m_ComponentChangeService = null;

    private ISelectionService m_SelectionService = null;

    //The designer reference

    private IDesigner m_ComponentDesigner = null;

    //The adorner reference.

    private Adorner m_adorner = null;

    //The control that is being designed.

    private Control m_relatedControl = null;

    // This defines the bounds used for hit testing.

    protected Rectangle m_HitTestBoundsValue;

    // This is the cursor returned if hit test is positive.

    protected Cursor hitTestCursor = Cursors.Hand;

 

All of these arguments are pass to the glyph by the designer so all the extra work we have to do is to keep reference to it.

public PictureSelectionGlyph(BehaviorService behaviorService, IComponentChangeService changeService,

            ISelectionService selectionService,IDesigner relatedDesigner,Adorner anchorAdorner): base(new PictureSelectorBehavior(relatedDesigner))

{

Step 2: Painting our Glyph. The glyph is a UI that is painted by GDI+, so we have to make sure that the glyph is painted in the right way (Location, Size...). First we must decide how we want our glyph to look like. Keep in mind that glyphs are only the access points and should not be to fancy that will shadow the control. this is how it should look like:

You may have notice that we have to calculate the location and size of our Glyph. In my example I have painted the glyph in the middle of the control that been Designed.

//This method renders the PictureSelectionGlyph as a filled rectangle,

//in the center of the designed control.

public override void Paint(PaintEventArgs e)

{

     using (Brush b = new SolidBrush(Color.DarkRed))

     {

         Pen p = new Pen(b, 2f);

         e.Graphics.DrawRectangle(p, Bounds);

         using (Brush fillBrush = new LinearGradientBrush(this.Bounds, Color.DarkRed, Color.Gold, 180f))

         {

             e.Graphics.FillRectangle(fillBrush, Bounds);

         }

     }

}

 

Step 3: Assigning Behavior to our Glyph.

Great! now We have painted a glyph, what is next? Behavior  is the class that responsible for handling and manipulating our Control.

As you might have noticed in the Glyph class Constructor we have Created a new instance of PictureSelectorBehavior, this class is the behavior class of the Glyph. If Glyph is the looks then the Behavior is the Brains.

//This Behavior specifies mouse and keyboard handling when

//Glyph is active. This happens when PictureSelectionGlyph.GetHitTest returns a non-null value.

internal class PictureSelectorBehavior : Behavior

{

   private IDesigner m_relatedDesigner = null;

   private Control m_relatedControl = null;

   private OpenFileDialog m_openFileDialog;

   internal PictureSelectorBehavior(IDesigner relatedDesigner)

   {

      this.m_relatedDesigner = relatedDesigner;

      this.m_relatedControl = relatedDesigner.Component as Control;

   }

Our behavior should inherits from Behavior and Initialize in Glyph Constructor

We would like to keep reference for our Designer, Control this will help us manipulate our Designed Control.

All that's left is to decide how our control react, in my case I had overridden the OnMouseDoubleClick:

public override bool OnMouseDoubleClick(Glyph g, MouseButtons button, Point mouseLoc)

{

    if (button != MouseButtons.Left)

       return false;

   PropertyDescriptor bounds = TypeDescriptor.GetProperties(g)["Bounds"];

   if (((Rectangle)bounds.GetValue(g)).Contains(mouseLoc))

   {

       if(BehaviorOenFileDialog.ShowDialog(m_relatedControl)  == DialogResult.OK)

       {

           PropertyDescriptor imageLocationPropertyDescriptor = TypeDescriptor.GetProperties(m_relatedControl)["ImageLocation"];

               imageLocationPropertyDescriptor.SetValue(m_relatedControl, BehaviorOenFileDialog.FileName);

       }

       return true;

   }

   return base.OnMouseDown(g, button, mouseLoc);

}

In the OnMouseDoubleClick we check that the event occurs on the Glyph bounds if so we perform the desire action, In my case I open a dialog that will ask us to choose the physical location of the Image In our PictureBox  - Sweet!.

Note: If you want that the Image will be saved in the Resources of our project use PropertyDescriptor to set the new value of the Image property of PictureBox.

This is how it looks in Design mode:

designer101

DoubleClick to open the dialog and select a new Image.

* Source Code will be available soon.

Next post will feature new abilities such as Verbs and SmartTags.

*njoy!

Create your own Designer or Introduction To Custom Designers - Part 1: Designer

Working with Winforms Designer and UserControls has always been a bit complicated, we could never get an easy access to the UserControls unique properties when designing our Control. We only had one option to access Properties via the Properties Window.

To understand better how Designers works please visit The Perfect Host.

In this post I will demonstrate how we can easily create and adapt a Custom Designer for a New UserControl

This figure demonstrate how a Button is Contained by a Form At RunTime.

When we are working In DesignTime each control we design has an Designer Attribute  attached to it.

For example this is how a Button Designer attribute is defined:

[Designer("System.Windows.Forms.Design.ButtonBaseDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), SRDescription("DescriptionButton"), ClassInterface(ClassInterfaceType.AutoDispatch), ComVisible(true)]

public class Button : ButtonBase, IButtonControl

{

In this example I have created a UserControl that contains a PictureBox. I may use this Control in many different Forms  or other UserControl. The main issue with my UserControl is that to access the PictureBox and define in each UserControl a different Image is a bit tricky. I can encapsulate the Image property from the PictureBox and use it as I uses my UserContol using the Properties Window.

The following example demonstrate how to access the Image Property from the PictureBox.

public class PictureDemoControl : UserControl

{

    private PictureBox pictureBox1;

    public Image PictureImage

    {

        get { return pictureBox1.Image; }

        set { pictureBox1.Image = value;}

    }

This is how we can edit the Image property using Properties Window:

fig04

This solution is nice but does not enable access to the Image property using anything other then Properties windows in Design-Time, beside that now the Image Property is accessible to all.

To avoid this we can create and define a new Designer for the user control, this designer will enable easy access in Design-Time.

Step 1: Create your own designer.

Your Designer should Inherit from ControlDesigner this will let you extend the basic Designer Behavior. Your custom ControlDesigner  will contain at least one Adorner, the designer should also initialize this services: IComponentChangeService, ISelectionService, BehaviorService. All this services are essential and will provide us access to the Designed Control.    

The Designer also contains Glyphs, Glyph is the UI that will be painted when your component is selected. Using the glyph you can basically manipulate and design your Control in Design-Time. I strongly recommend that you will not use Adorner for each one of your Glyphs - that may be too costly. Try dividing your Glyphs into logical groups and then assign each group with a different Adorner .

This is how your Designer Class should look like:

    [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]

    internal class MyPictureDesigner : ControlDesigner

    {

        // This adorner holds the glyphs

        private Adorner m_pictureSelectorAdorner = null;

        // References to designer services

        private IComponentChangeService m_componentChangeService = null;

        private ISelectionService m_selectionChangeService = null;

        private BehaviorService m_behaviorService = null;

        private void InitializeServices()

        {

            // Reference to IComponentChangeService.

            this.m_componentChangeService = GetService(typeof(IComponentChangeService)) as IComponentChangeService;

            //Reference  to ISelectionService.

            this.m_selectionChangeService = GetService(typeof(ISelectionService)) as ISelectionService;

            //Reference  to BehaviorService.

            this.m_behaviorService = GetService(typeof(BehaviorService)) as BehaviorService;

            this.m_pictureSelectorAdorner = new Adorner();

            this.m_behaviorService.Adorners.Add(this.m_pictureSelectorAdorner);

        }

        public override void Initialize(IComponent component)

        {

            base.Initialize(component);

            InitializeServices();

            /*this is where we will Assign the Adorner with Glyph*/

More about Glyphs and Behavior at Part 2.

Happy New Year!