Monday, October 15, 2007

In previous articles I've shown how to host the Windows Forms designer outside of Visual Studio, which can be very useful in some sitations.  I had some down time before catching a plane (read: didn't want to start something bigger) so I started looking into the WPF designer.  The WPF designer, code-named "Cider", will not be hostable outside Visual Studio in the first release.  I will be providing feedback through the appropriate channels...

I'm going to go back and finish my ClickOnce thoughts next.

Monday, October 15, 2007 11:32:28 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, May 30, 2007

Hosting a Designer Surface in Windows Forms Part 1

{if you are just tuning in, you may want to review:

Designer[0]

}

The most important things we’ll be working with is our class that implements System.ComponentModel.Design.IDesignerHost.  This class is the hub of many designer activities such as creating the designed components, Siting Components, and managing designer transactions.  In my case I went a slightly different route for transactions but this class is still very important.  Review the description (http://msdn2.microsoft.com/en-us/library/system.componentmodel.design.idesignerhost(vs.80).aspx) and a trivial sample implementation on MSDN.

Let’s take a look at a few lines of code after where we left off last, after LoadToolBoxItems() from the last article:

            //Setup the control that will hold all the template junk:

            _form = (BlankDesignForm)_designerHost.CreateComponent(typeof(BlankDesignForm));

            _form.Size = new Size(612, 792);

            _form.TopLevel = false;

            _form.Text = "Print Template";

            _form.FormBorderStyle = FormBorderStyle.None;

            _form.Font = new Font("Tahoma", 9);

            _baseFont = _form.Font;

            _form.Location = new Point(5, 5);

 

            _rootDesigner = (IRootDesigner)_designerHost.GetDesigner(_form);

            _designerView = (Control)_rootDesigner.GetView(ViewTechnology.Default);

            _designerView.Dock = DockStyle.Fill;

            _viewHostPnl.Controls.Add(_designerView);

Usually the under the cover plumbing interacts with the designer telling it what was selected in the IToolBoxService implementation and therefore what Type of Component to create.  We manually create a BlankDesignForm because it is the canvas that will contain the visual representation of all other designed Components.  Note that we can guarantee that the type of IDesigner created in this case is an implementation of IRootDesigner as opposed to IDesigner.  The first designed component is the Design Time Root.  An IRootDesigner, as opposed to other classes buried in the framework like System.Windows.Forms.Design.LabelDesigner gives us what we need to display the entire Designer Surface in our program, through its GetView() method.

The BlankDesignForm exists in order to gain some things specific to our problem domain such as drawing the dotted line around the page’s print margin you saw in the last article, but also to lose some functionality inherent to Windows Forms.  I’m talking about scaling.  While not explicitly related to the Designer Surface discussion I did run into this again during these articles and it’s a nice thing to know about anyway.

In order to be more Accessible, Windows Forms will try to scale the overall size of forms and their children in one of two ways in .Net 2.  The first is DPI scaling, meaning if the screen resolution changes from the design time resolution.  The second and more curious is font scaling.  If you have a Form that is 800x600 at design time with 8.25pt Microsoft Sans Serif, and at run-time you set its Font to be 9pt Tahoma, the form and all children will resize by roughly the difference between 8.25 and 9pt.  I say roughly because the behavior does not appear to be perfectly mathematically dependable in a transitive fashion.  If I go from 8.25 à 9 à 63 à 12 à 8.25 I will not end up with a form that’s quite right with respect to its original self.  The child controls may not be positioned in exactly their original places or the size may not be quite right.  I found workarounds for some situations but ultimately the resizing of ScrollableControl and its various viewports and bounds defeated me, it’s a long story for another article.  It’s too bad this doesn’t work, since if it did this would be a swell way to implement Zoom-In and Zoom-Out in pre-WPF applications.  This is all a very long winded way of saying that I needed my program to be somewhat skinable with colors, font family, font size, set at run time by processing a skin file.  Since the WYSIWYG-ness of my final printed document is dependent upon the convention that a 612pixel by 792pixel form represents the same space and proportions as an 8.5” by 11” document printed at 72dpi using my Compact Framework printer driver (http://www.fieldsoftware.com) a little Font scaling error is too much Font scaling error but the GUI skinning could not be given up.  The BlankDesignForm’s main contribution to my mental stability, then, is:

        protected override void ScaleControl(SizeF factor, BoundsSpecified specified)

        {

            //Don't scale!

        }

So the designed area where every pixel counts will not participate in the magical Scaling-based-on-font woo that takes place from within the bowels of Component.OnFontChanged.

In the code figure above everything is obvious except for our IDesignerHost.CreateComponent(Type)  implementation from the DefaultDesignerHost class. 

        /// <summary>

        /// Instructs the DefaultDesignerHost to instantiate a new instance of componentClass, which

        /// is also assumed to be added to the list of components that this DefaultDesignerHost is

        /// designing for through an IContainer implementation.  The component is Sited after listeners are given a chance to cancel this operation via

        /// the ComponentAdding method of the contextual IComponentChangeSerice instance.

        /// </summary>

        /// <param name="componentClass">valid Type</param>

        /// <param name="name">Component Name/Site Name</param>

        /// <returns></returns>

        public IComponent CreateComponent(Type componentClass, string name)

        {

            IComponent newComponent = (IComponent)Activator.CreateInstance(componentClass);

 

            IComponentChangeService changeSvc = (IComponentChangeService)_parent.GetService(typeof(IComponentChangeService));

            changeSvc.ComponentAdding(new ComponentEventArgs(newComponent));

 

            ISite site = new DefaultDesignSite(newComponent, this, name);

            newComponent.Site = site;

Any and all design time classes will attach to the events exposed by an IComponentChangeService implementation if they care about what goes on with Components at design-time. The first issue is the code in red.  It doesn’t work.  Take a look at the IComponentChangeService interface events:

 

Name

Description

ComponentAdded

Occurs when a component has been added.

ComponentAdding

Occurs when a component is in the process of being added.

ComponentChanged

Occurs when a component has been changed.

ComponentChanging

Occurs when a component is in the process of being changed.

ComponentRemoved

Occurs when a component has been removed.

ComponentRemoving

Occurs when a component is in the process of being removed.

ComponentRename

Occurs when a component is renamed.

 

… and the public methods …

 

Name

Description

OnComponentChanged

Announces to the component change service that a particular component has changed.

OnComponentChanging

Announces to the component change service that a particular component is changing.

 

Cruising MSDN shows that there is no public interface that forces any class to implement an OnComponentAdding or OnComponentAdded method.  While I don’t mind being left with a few design problems of my own to solve, this seems a little unfortunate since I am now forced to go outside the Design Time framework that’s been provided and implement something specific to my own Design Time environment.  It seems that it would be ideal to be able to give someone my ISelectionService or IComponentChangeService implementations without depending on behaviors that are outside the published contracts.  Of course I could just be missing something too :D.  Semantics being very important I chose the saucy name ITemplateDesignerComponentChangeService to represent my interface with the necessary methods added.  At least I didn’t just tack a “2” on the end of the original interface, yeah; I’m talking to you Sun.  Talk about lazy; nothing is worse than trying to determine if I should use SomeType or SomeType2.  Apparently this was considered a completely super practice in Java, but thankfully I don’t write Java anymore.

    interface ITemplateDesignerComponentChangeService : IComponentChangeService

    {

        void OnComponentAdding(ComponentEventArgs e);

        void OnComponentAdded(ComponentEventArgs e);

        void OnComponentRemoving(ComponentEventArgs e);

        void OnComponentRemoved(ComponentEventArgs e);

    }

I add an instance of a class implementing this new interface to my SerivceContainer.  Now if there are any classes buried in the framework that are not my own they will have access to an IComponentChangeService implementation for the purpose of event subscription, and my own code can ask for a Service Type implementing ITemplateDesignerComponentChangeService in order to access the extra methods.  The first few lines of our CreateComponent method now look like:

            IComponent newComponent = (IComponent)Activator.CreateInstance(componentClass);

 

            ITemplateDesignerComponentChangeService changeSvc = (ITemplateDesignerComponentChangeService)_parent.GetService(typeof(IComponentChangeService));

            changeSvc.OnComponentAdding(new ComponentEventArgs(newComponent));

 

            ISite site = new DefaultDesignSite(newComponent, this, name);

            newComponent.Site = site;

In the next article, we’ll start looking at more Service class implementations, where we tell the design-time framework about our Service implanting types, and the interactions between all of these pieces of the Designer Surface puzzle.

Wednesday, May 30, 2007 8:00:07 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, April 24, 2007

Hosting a Designer Surface in Windows Forms Part Zero

Note that my 1st article in this series begins with Zero because I am partial to c-family languages.  Yes, I'm hilarious.  My apologies to those with smaller screen sizes, but the screenshots need to be readable.

The first step we take is to create  UserControl that will contain the entire UI representation of the designer surface.  You could put all of this on a Form but in my case It'd be handy to have several of these open at once using a tab page interface.  On the UserControl I place a Panel with AutoScroll=true and a PropertyGrid.  The PropertyGrid should look familiar as its essentially what you've come to know and love from VS2005.  I also add another UserControl I’ve created called DefaultToolboxService.  This control has a Checkbox and a ListBox control on it.  So far I’ve got something like this:

 

As you can tell my initial goal is simple: I want to select an item in my ListBox and have it be automagically created when I click on my designer area. The next step will be to start tying in some of the plumbing to allow this to happen.  This is a good place to introduce the notion of Designer Services.

Designer Services

From the class library documentation:

Services are a foundation of the .NET Framework design-time architecture. Services provide design-time objects access to specific features and methods implemented by a service object that provides a service or services.  Let’s look at a few lines of code from the constructor of the TemplateDesignerControl:

        public TemplateDesignerControl()

        {

            InitializeComponent();

            _serviceContainer = new ServiceContainer();

            _serviceContainer.AddService(typeof(INameCreationService), new DefaultNameCreationService());

            _serviceContainer.AddService(typeof(IUIService), new DefaultUIService());

            _designerHost = new DefaultDesignerHost(_serviceContainer);

 

            _serviceContainer.AddService(typeof(IToolboxService), _toolboxSvc);

            _toolboxSvc.DesignPanel = _viewHostPnl;

            LoadToolboxItems();

ServiceContainer is a framework class that’s already implemented for me.  Obviously this instance is going to hold all the Services I need for my designer.  INameCreationService and IUIService we’ll come back to in the next article so for now we’ll look at DefaultToolBoxService.  DefaultToolBoxService was instantiated in InitializeComponent() because it is also a UserControl as mentioned above.  Now might be a good time to start modeling these relationships:

We’ll continue to flesh this diagram out as we move along.

Obviously the declaration for DefaultToolboxService, then, is...

public class DefaultToolBoxService : UserControl, IToolboxService

...because it is both a visual Control and an implementation of an important Service class.  In my case, these do not need to vary independently but the implementations could be separate.  If you wish, you can review (http://msdn2.microsoft.com/en-us/library/system.drawing.design.itoolboxservice(vs.80).aspx) the IToolBoxService interface before moving on.  The implementation is straightforward: Add the types of items you’d like to create to the ListBox and implement the IToolboxService accordingly.  The design-time environment, which in our case is also the run-time environment, can query what item is selected and attempt to create an instance of the appropriate class.  Adding items is accomplished like so:

        protected void LoadToolboxItems()

        {

            ToolboxItem labelItem = new ToolboxItem(typeof(CarSpot.TemplateDesigner.Controls.CustomLabel));

            labelItem.DisplayName = "Text";

            _toolboxSvc.AddToolboxItem(labelItem);

Running the code so far you would see a Toolbox, a Panel with nothing in it, and a PropertyGrid with no selected object.  In the next article we’ll start to look at our IDesignerHost implementation and the sequence of events that occur when I want to see a designer view of my “designed document” with a CarSpot.TemplateDesigner.Controls.CustomLabel on it.

Tuesday, April 24, 2007 11:28:37 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, April 03, 2007

Why would you want to take something very technical, such as the Visual Studio designer environment, and put it into the hands of end users?  I suppose it depends on who your end users are.  In my case, I had a controlled group of people who needed a WYSIWYG way to design window stickers for cars, a tool with deep integration into our backend and handheld systems.  When I was given the first example background PDF I realized this was doable.  I can create an image that is exactly %print resolution% x 8.5" x 11", and my handheld ONLY supports printing at 72dpi.  Easy enough: make a background image that's 612x792 pixels.  1 pixel on screen=1 point on my printed document, and I'm on my way to creating a window sticker designer.  Here's a simple example of the beta of my new designer using the VS2005 surface:

While the Visual Studio designer surface is not Visio, it does work well for things that need to be shown in spatial relations to other things, and in some cases allows user interface design elements to be chosen after deployment, and by someone besides me.  You can see here I have the basic elements you see in VS2005: a toolbox, a property grid, and a surface I am dragging and editing controls on.  An XML file format describes the locations of various things on the form.  So, how did I build this?  Its about 6000 lines of not very straightforward code before you get into anything specific to your problem domain.  Speaking of problem domains, could you use DSL tools for this type of thing?  Maybe, but I have quite a bit of control by hosting this myself and of course I'm not distributing Visual Studio to non-programmers.  In a future project, I may look into what it would take to host the DSL tools outside of visual studio, but I expect its fairly similar to what I'm doing here.  Anyway, as far as building this, I thought I'd see if the VS Class Diagram tool could reverse engineer some relationships for me before I get into code.  No such luck:

 

 

I gave up after this point, although these are some of the most important classes,  so I'll need to invest some time into StarUML or just jump into code.  Its a long road to getting a Form to show up in designable fashion, stay tuned.

Tuesday, April 03, 2007 2:15:00 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I'm getting ready to bust out a series of articles on Hosting the Visual Studio Designer Surface.  Controls, Designers for Controls, drag 'n drop life, ISite, and the myriad jungle of nonsense needed to make the Property Grid do just what you want it to.

One complaint you'll hear over and over again is this: surely there must be a better way to create my controls' contract with their designer than Class and Property level attributes?

/// <summary>
/// Show a dotted line warning the sticker margin vs. the page margin
/// </summary>
[Browsable(true)]
[Description("Vertical margin size in fractions of an inch")]
[EditorBrowsable(EditorBrowsableState.Always)]
[DefaultValue(.25f)]
[DisplayName("V. Margin")]
public float VerticalStickerMargin
{
   get { return _verticalMargin; }
set
{
_verticalMargin = value;
Invalidate();
}
}

Next up, some designer classes.

 

Tuesday, April 03, 2007 1:43:26 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, January 12, 2006

Many applications have scaling, or zoom-in, zoom-out features of some kind.  Acrobat reader, Word, Graphics programs, etc.  We have these great Skin and Theme features in ASP.NET 2.0 but not so in Windows forms.  You can enable XP visual styles though, check out this Help article:

ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VSADD.v10.en/dv_vstechart/html/vbtchUsingWindowsXPVisualStylesWithControlsOnWindowsForms.htm

While trying to determine a good design approach to creating a skin-able windows app I stumbled across some odd behavior which I assumed must be a bug:

  1. Create a windows forms app and throw some controls on it.  Leave everything the default size/font/color since I am going to apply a skin to it anyway.
  2. At runtime, change the font to the "skin" font.
  3. If the design-time font was good ole MS sans-serif 8.25pt and I assigned a font with a different size (Verdana, 12pt) the form would suddenly get tremendously larger.

My forms extend from a BaseForm, so this must be a Visual Ineritence bug, I thought, and the client decided not to spend $$ on the skinable feature just yet anyway.  When a request came in to allow parts of the application to Scale via zoom-in, zoom-out, I came back to this.  My odd resizing forms are exhibiting behavior as designed.

ScaleMode

Windows Forms in .Net 2.0 are designed to automatically scale under one of two events: the Font assigned to the form changes, or the screen resolution changes.  There is an AutoScaleMode property on a Form, with the enumerated values being

  • None
  • Inherit
  • Font
  • DPI

It seems I could find my way through this in order to meet my scaling requirement and also revisit the Skin feature in future.  A Form keeps track of what dimensions it was designed to regardless of other resizing and such.  This is stored in the AutoScaleDimensions property.  From the set_Font method of a Form, the Scale and ultimately ScaleControl methods will be called.  These can be overridden to alter scaling behavior, or just observed to determine how to manipulate scaling.  So, I have an MDI application and the area I want to scale will be contained in a "ScalableForm" like so:

Now, a simple way to give the user the ability to zoom out, I create a simple ScaleData class.

public class ScaleData
{
public ScaleData(float size, string desc)
{
_fontSize = size;
_display = desc;
}
private float _fontSize;

public float FontSize
{
get { return _fontSize; }
set { _fontSize = value; }
}
private string _display;

public string Display
{
get { return _display; }
set { _display = value; }
}
}

... and populate some scale values in the ScalableForm constructor:

public ScalableForm()
{
InitializeComponent();
ScaleSizes = new List<ScaleData>();
ScaleSizes.Add(new ScaleData(this.Font.Size / 2f, "50%"));
ScaleSizes.Add(new ScaleData(this.Font.Size, "100%"));//This will be the design-time size
ScaleSizes.Add(new ScaleData(this.Font.Size * 2f, "200%"));

...

There is certainly no need to pre-calculate a limited set of zoom values but it removes the need for calculations later to get back to 100% .  Now I provide a menu item that will scale the currently active child Form and the code is very simple:

private void toolStripMenuItem2_Click(object sender, EventArgs e)
{
  _scale.Font = new Font(_scale.Font.FontFamily,_scale.ScaleSizes[0].FontSize);
}

So telling it to zoom to 50% looks like:

A couple of other things bear mentioning.  Note that "a bunch of text in a label" appears the same size in both cases , although the Location of the Label was scaled down.  By default the ScaleChildren called from ContainerControl will not scale controls set to be automatically sized, so in this case I would need to tell my label not to auto size itself.  Also, the documentation for scaling claims you can have a control on this form not ever scale itself by extending a control class and providing the following:

public class UnscalableComboBox : ComboBox
{
   protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
   {
      base.SetBoundsCore(x, y, 121, 21, BoundsSpecified.None);
   }
}

However, neither this, not setting Locked=true, not setting MaximumSize and MinimumSize would stop a control from being scaled when I changed the font.  For now I put the scaling function in the menu (rather than a combo box on this form somewhere) but will continue to investigate this feature as I work on this project.

 

Thursday, January 12, 2006 10:51:44 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, November 14, 2005

I did a brown bag demo of some VS 2005 stuff today and found that a construct I use a lot : try/catch/finally is not in the standard code snippets so I set about creating it.  I was somewhat disappointed not to be able to find an interface within visual studio to do this, so I found the code snippet files and copied one of them.  I created a snippet as follows:

<?xml version="1.0" encoding="utf-8" ?>

<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">

<CodeSnippet Format="1.0.0">

<Header>

<Title>trycf</Title>

<Shortcut>trycf</Shortcut>

<Description>Code snippet for try-catch-finally</Description>

<Author>DamonPayne.com</Author>

<SnippetTypes>

<SnippetType>Expansion</SnippetType>

<SnippetType>SurroundsWith</SnippetType>

</SnippetTypes>

</Header>

<Snippet>

<Declarations>

<Literal>

<ID>expression</ID>

<ToolTip>Exception type</ToolTip>

<Default>System.Exception</Default>

</Literal>

</Declarations>

<Code Language="csharp"><![CDATA[

try

{

$selected$

}

catch($expression$)

{

//Handle exception

}

finally

{

$end$

}]]>

</Code>

</Snippet>

</CodeSnippet>

</CodeSnippets>

and saved it in a file called trycf.snippet.  It's fairly easy to follow what to do, if you want to be able to tab among items you create a <Declaration> and then refer to it by its Id tag ala : $mystuff$.  There is a built in $selected$ Declaration in case you use ctrl+k,s to "surround with"

  I thought I might make several of these so I made a folder c:\Projects\code snippets and went to Tools-->Code Snippets Manager to tell visual studio about my new snippet.  Clicking on "Add" let me browse and find my custom directory, however  the behavior in the IDE then changes to the following:

... and I didn't like this, I want to avoid extra keystrokes although I suppose categories might be useful for some people.  I'm trying to use snippets to stave off carpal tunnel here people.  You can highlight the Visual C# category and chose "import" though and it appears in line with the other snippets.

Monday, November 14, 2005 3:03:29 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, November 04, 2005

I had a small master page issue which I fixed quickly but which made me want to complain.

One of the great things about .NET is the great design time experience.  Dropping designer support for nested master pages irked me, and there are some other quirks as well.  In one case, I have a page using <div> and the "float" style attributes for layout.  As soon as the divs are nested three deep or so the content no longer renders in the designer.  It seems to be specifically related to the style as I will explain along with the next issue.

In my master page for the site I import a style sheet like so:

<style type="text/css">@import url(kcstyle.css);</style>

Obviously this is a relative path.  If you create sub-directories beneath your site obviously this won't work so I changed to an absolute path like so:

<style