Divil
Well-known member
- Joined
- Nov 17, 2002
- Messages
- 2,748
[edit]Read this article too..[/edit]
Introduction
Your user interface is the gateway between your program and the user. It can make the difference between the user being able to use your program, and the alternative. Different people attach different levels of importance to the interface and its components, but being able to make such a component is a good skill to have.
I wrote the basics of an Outlook Bar style control as the basis of this article. Hopefully examining and playing with the source will help you gain a good understanding of the concepts of authoring custom controls. I will attempt to explain roughly what I have done in the rest of the article.
All this said, I am far better at writing code than I am at writing articles. Im hoping the code will be fairly self-explanatory.
.NET
The framework ships with excellent managed wrappers for GDI+. Using these wrappers is pretty easy, and there is a lot of information out there on doing so.
This article is not meant to teach how to write classes or how to use the System.Drawing namespace, rather how to piece together the components which make a good Windows Forms Control.
What is a Windows Forms Control?
Everything put on a form or in a container in .NET derives from the Control class. It has methods for hiding and showing, setting location and size and pretty much every other useful thing an interface element should be able to do. It can be as simple as a coloured square on the form, or a label, or as complex as a listview or hierarchical display of information like the Treeview class.
Starting off - Creating your control
I suggest you create a new Windows Forms project to host your control while it is in development. Once the IDE has started, you will have a blank form which you can use for testing the control. Go to the Project menu, and choose "Add User Control". The the purposes of this article, I called it OutlookBar. You should then have a blank control open in the designer window. Go to the code for the class, and close that designer window.
.NET is very versatile in what you can put in User Controls. The designers can be quite useful when it comes to making a control that basically groups together other controls, but when you are making your own custom control from scratch they are useless since all drawing will be done by yourself.
The first thing you need to do with a brand new control that you will be drawing yourself is to set the appropriate drawing styles for it. To do this, you use the SetStyle protected method of the Control class. In the constructor for your control, you will set the following styles:
ControlStyles.AllPaintingInWmPaint - this lets the control know that you will only be painting it in response to the Paint event or when you override the OnPaint method. This allows .NET to optimize painting.
ControlStyles.DoubleBuffer - nobody wants to see your control flicker as it draws itself, so turning this on in combination with AllPaintingInWmPaint lets the framework use fast, native double-buffering techniques using DIB sections.
ControlStyles.ResizeRedraw - you wont always need this one, but in this case we will. This style makes the framework invoke a redraw when the user reduces the size of the control. If we were writing a textbox, this would be useless as the contents of it dont need to be repainted when it gets smaller, only when it gets bigger (a paint will always be invoked in this case). Since this control has centred headings and icons, clearly changing the size of it will always mean a redraw is required.
The OutlookBarCategory class
All the buttons on an Outlook Bar fall under seperate categories, or headings. This will be a simple class which exposes a couple of properties - Title, to set the heading text, and Buttons - to add and remove buttons to and from the category.
The OutlookBarCategoryCollection class
See this class for an example of writing a strongly-typed collection. .NET makes this remarkably easy for us by providing a CollectionBase class for us to derive from. Only a small collection of methods need to be implemented to get a strongly-typed collection going.
The extra code in these classes is to bubble an event back to the main control when the user changes a property and it needs to be redrawn. There is more than one way of doing this, but I chose to use events in this case.
The OutlookBarButton and OutlookBarButtonCollection classes
These are the same in design as the above two classes.
Testing your control on a form
When you add a User Control to your project and build the project, that usercontrol will appear in the toolbox when you have a Windows Forms Designer open. You can draw the control on the form like you would any other. If you make a change in your controls code, all you have to do is switch back to the form and hit rebuild, and the control will be refreshed.
Writing the drawing code
Once you have the object model of your control set up (which usually wont be as complex as this one!) you can write the drawing code. This is undoubtedly the best part of writing custom controls.
In the drawing code for this example, we loop through all categories before the selected one and draw their headings. We then draw the selected ones heading and all its buttons. Lastly, the other category headings are drawn aligned with the bottom of the control. This mimics the behaviour of the Outlook Bar quite nicely.
Getting the codedom serializers working
If your control has anything but the simplest of properties on it, you may find yourself needing to write additional code to get the codedom serializers to maintain its state properly. CodeDom is a technology in .NET that is meant to represent code without actually applying syntax to it. .NET uses this to generate a CodeDom Graph to generate your control once the user has configured its properties, then turns this CodeDom Graph in to syntax for the particular .NET language you are using. But I digress.
This article proved invaluable in learning how to use attributes and a couple of extra classes to show the codedom serializers, good as they are, just how to serialize and deserialize our control to and from a codedom graph.
DesignerSerializationVisibility(DesignerSerializationVisibility.Content) is the attribute we need to apply to both properties that expose strongly-typed collections. When we specify Content for this attribute, it forces those codedom serializers to recurse on themselves and delve in to the collection for further serialization. If we didnt do this, the control would forget the categories and buttons the user had set up at design time.
This brings up an interesting point. If you are writing a control which will be passed around and used by people other than yourself, you really need to do this. .NET provides great design time support and extensibility, and once youre familiar with it its easy to implement. However, if you are writing a user interface element specifically for a project, which will be compiled in and used only in that project, you can forget all that stuff. Instead of setting up the control at design time youll probably be setting it up in your code, and thus no serialization attributes and code are needed.
Once the DesignerSerializationVisibility attribute is applied the framework will know to "dive in" to that property and try and serialize what is within, but unfortunately that isnt quite enough. It still needs to know how to create instances of OutlookBarCategory and OutlookBarButton to populate the collection. Enter the TypeConverter class, which we derive from to make OutlookBarCategoryConverter and OutlookBarButtonCoverter.
This small class lets the framework analyse an instance of OutlookBarCategory, and your code will let it know to use the default constructor to recreate the instance. To put this in perspective, take the Treeview control. You can build a complete tree in it at design time, and the framework will serialize it in to code to generate that tree at runtime. Personally I find this very impressive, and it works by using TypeConverter as above. There are numerous constructors for TreeNode so the code for that is a little more complex, but ours is similar.
Once this little class is written we just need to "apply" it to the OutlookBarCategory class, so the framework knows where to look. We do this with the TypeConverter attribute:
The same needs to be done with the OutlookBarButton class, but the code is virtually the same so its not worth going in to in detail.
Finishing off
Thats about it. Hopefully the code will explain more than I can.
What next?
I got carried away and forgot I was just writing a control for an article, and I added some animation code for when the user changes the displayed category. Just in case you were wondering.
While the control may be pretty feature complete, it needs a few things doing before it should be used in an application. Firstly, there is no support for doing anything with buttons that do not fit in to the available category space. Some kind of scrolling interface should be implemented for this. Secondly, there is no way of controlling the control with the keyboard.
I hope this has been of help to someone, I could certainly have done with an example when I first learned to make user interface elements in VB6. If you take it upon yourself to improve this control, feel free to share your new source with the rest of the forum.
Good luck!
Introduction
Your user interface is the gateway between your program and the user. It can make the difference between the user being able to use your program, and the alternative. Different people attach different levels of importance to the interface and its components, but being able to make such a component is a good skill to have.
I wrote the basics of an Outlook Bar style control as the basis of this article. Hopefully examining and playing with the source will help you gain a good understanding of the concepts of authoring custom controls. I will attempt to explain roughly what I have done in the rest of the article.
All this said, I am far better at writing code than I am at writing articles. Im hoping the code will be fairly self-explanatory.
.NET
The framework ships with excellent managed wrappers for GDI+. Using these wrappers is pretty easy, and there is a lot of information out there on doing so.
This article is not meant to teach how to write classes or how to use the System.Drawing namespace, rather how to piece together the components which make a good Windows Forms Control.
What is a Windows Forms Control?
Everything put on a form or in a container in .NET derives from the Control class. It has methods for hiding and showing, setting location and size and pretty much every other useful thing an interface element should be able to do. It can be as simple as a coloured square on the form, or a label, or as complex as a listview or hierarchical display of information like the Treeview class.
Starting off - Creating your control
I suggest you create a new Windows Forms project to host your control while it is in development. Once the IDE has started, you will have a blank form which you can use for testing the control. Go to the Project menu, and choose "Add User Control". The the purposes of this article, I called it OutlookBar. You should then have a blank control open in the designer window. Go to the code for the class, and close that designer window.
.NET is very versatile in what you can put in User Controls. The designers can be quite useful when it comes to making a control that basically groups together other controls, but when you are making your own custom control from scratch they are useless since all drawing will be done by yourself.
The first thing you need to do with a brand new control that you will be drawing yourself is to set the appropriate drawing styles for it. To do this, you use the SetStyle protected method of the Control class. In the constructor for your control, you will set the following styles:
ControlStyles.AllPaintingInWmPaint - this lets the control know that you will only be painting it in response to the Paint event or when you override the OnPaint method. This allows .NET to optimize painting.
ControlStyles.DoubleBuffer - nobody wants to see your control flicker as it draws itself, so turning this on in combination with AllPaintingInWmPaint lets the framework use fast, native double-buffering techniques using DIB sections.
ControlStyles.ResizeRedraw - you wont always need this one, but in this case we will. This style makes the framework invoke a redraw when the user reduces the size of the control. If we were writing a textbox, this would be useless as the contents of it dont need to be repainted when it gets smaller, only when it gets bigger (a paint will always be invoked in this case). Since this control has centred headings and icons, clearly changing the size of it will always mean a redraw is required.
The OutlookBarCategory class
All the buttons on an Outlook Bar fall under seperate categories, or headings. This will be a simple class which exposes a couple of properties - Title, to set the heading text, and Buttons - to add and remove buttons to and from the category.
The OutlookBarCategoryCollection class
See this class for an example of writing a strongly-typed collection. .NET makes this remarkably easy for us by providing a CollectionBase class for us to derive from. Only a small collection of methods need to be implemented to get a strongly-typed collection going.
The extra code in these classes is to bubble an event back to the main control when the user changes a property and it needs to be redrawn. There is more than one way of doing this, but I chose to use events in this case.
The OutlookBarButton and OutlookBarButtonCollection classes
These are the same in design as the above two classes.
Testing your control on a form
When you add a User Control to your project and build the project, that usercontrol will appear in the toolbox when you have a Windows Forms Designer open. You can draw the control on the form like you would any other. If you make a change in your controls code, all you have to do is switch back to the form and hit rebuild, and the control will be refreshed.
Writing the drawing code
Once you have the object model of your control set up (which usually wont be as complex as this one!) you can write the drawing code. This is undoubtedly the best part of writing custom controls.
In the drawing code for this example, we loop through all categories before the selected one and draw their headings. We then draw the selected ones heading and all its buttons. Lastly, the other category headings are drawn aligned with the bottom of the control. This mimics the behaviour of the Outlook Bar quite nicely.
Getting the codedom serializers working
If your control has anything but the simplest of properties on it, you may find yourself needing to write additional code to get the codedom serializers to maintain its state properly. CodeDom is a technology in .NET that is meant to represent code without actually applying syntax to it. .NET uses this to generate a CodeDom Graph to generate your control once the user has configured its properties, then turns this CodeDom Graph in to syntax for the particular .NET language you are using. But I digress.
This article proved invaluable in learning how to use attributes and a couple of extra classes to show the codedom serializers, good as they are, just how to serialize and deserialize our control to and from a codedom graph.
DesignerSerializationVisibility(DesignerSerializationVisibility.Content) is the attribute we need to apply to both properties that expose strongly-typed collections. When we specify Content for this attribute, it forces those codedom serializers to recurse on themselves and delve in to the collection for further serialization. If we didnt do this, the control would forget the categories and buttons the user had set up at design time.
This brings up an interesting point. If you are writing a control which will be passed around and used by people other than yourself, you really need to do this. .NET provides great design time support and extensibility, and once youre familiar with it its easy to implement. However, if you are writing a user interface element specifically for a project, which will be compiled in and used only in that project, you can forget all that stuff. Instead of setting up the control at design time youll probably be setting it up in your code, and thus no serialization attributes and code are needed.
Once the DesignerSerializationVisibility attribute is applied the framework will know to "dive in" to that property and try and serialize what is within, but unfortunately that isnt quite enough. It still needs to know how to create instances of OutlookBarCategory and OutlookBarButton to populate the collection. Enter the TypeConverter class, which we derive from to make OutlookBarCategoryConverter and OutlookBarButtonCoverter.
This small class lets the framework analyse an instance of OutlookBarCategory, and your code will let it know to use the default constructor to recreate the instance. To put this in perspective, take the Treeview control. You can build a complete tree in it at design time, and the framework will serialize it in to code to generate that tree at runtime. Personally I find this very impressive, and it works by using TypeConverter as above. There are numerous constructors for TreeNode so the code for that is a little more complex, but ours is similar.
Once this little class is written we just need to "apply" it to the OutlookBarCategory class, so the framework knows where to look. We do this with the TypeConverter attribute:
Code:
<TypeConverter(GetType(OutlookBarCategoryConverter))> _
Public Class OutlookBarCategory
...
The same needs to be done with the OutlookBarButton class, but the code is virtually the same so its not worth going in to in detail.
Finishing off
Thats about it. Hopefully the code will explain more than I can.
What next?
I got carried away and forgot I was just writing a control for an article, and I added some animation code for when the user changes the displayed category. Just in case you were wondering.
While the control may be pretty feature complete, it needs a few things doing before it should be used in an application. Firstly, there is no support for doing anything with buttons that do not fit in to the available category space. Some kind of scrolling interface should be implemented for this. Secondly, there is no way of controlling the control with the keyboard.
I hope this has been of help to someone, I could certainly have done with an example when I first learned to make user interface elements in VB6. If you take it upon yourself to improve this control, feel free to share your new source with the rest of the forum.
Good luck!
Attachments
Last edited by a moderator: