Library tutorials & articles
Collection Controls with Rich Design Time Support
- Introduction
- Starting Off
- Drawing and Layout Logic
- Controlling Serialization
- Adding the Designer
- Adding & Selecting Buttons
- Wrapping Up
Adding & Selecting Buttons
Adding Buttons
The first thing we want the user to be able to do is to add buttons. We will make use of a designer verb to do this. For an explanation of designer verbs, see my article Introduction to Designers. We only want one verb, and we'll simply title it "Add Button".
In the code that executes when the user activates this verb, we have to create a button and add it to the collection. This may sound trivial, but this is one of those times when we have to play nice with the other designers. If we simply created a button and added it to the collection, how would the IDE know anything had changed? How would it know WHAT had changed, so the user can undo/redo?
Enter the DesignerTransaction class. When you perform a significant action (or group of actions)
to something on the design surface, you should wrap it in a transaction. Every transaction has a
friendly name, which appears on the dropdown by the Undo/Redo buttons in the host environment.
Also, every distinct change to make to an object (in this case, the Buttons collection) needs to be
wrapped with a call to OnComponentChanging and OnComponentChanged, on the IComponentChangeService.
Lastly, you should not attempt to instantiate a ColourButton directly - let the designer host
(another service we'll use) do the creating for you. This ensures that the object is on the design
surface, and it keeps everyone happy. If the ColourButton class had a designer itself, that would
get created too. I know this all sounds like a lot of work, and it is, but you get used to it and
most of it is boilerplate that can be copy/pasted easily.
VB.NET
Public Overrides ReadOnly Property Verbs() As _
System.ComponentModel.Design.DesignerVerbCollection
Get
Dim v As New DesignerVerbCollection()
'Commands to insert and add buttons
v.Add(New DesignerVerb("&Add Button", AddressOf OnAddButton))
Return v
End Get
End Property
Private Sub OnAddButton(ByVal sender As Object, ByVal e As EventArgs)
Dim button As ColourButton
Dim h As IDesignerHost = DirectCast(GetService(GetType(IDesignerHost)), IDesignerHost)
Dim dt As DesignerTransaction
Dim c As IComponentChangeService = DirectCast(getservice(GetType _
(IComponentChangeService)), IComponentChangeService)
'Add a new button to the collection
dt = h.CreateTransaction("Add Button")
button = DirectCast(h.CreateComponent(GetType(ColourButton)), ColourButton)
c.OnComponentChanging(MyControl, Nothing)
MyControl.Buttons.Add(button)
c.OnComponentChanged(MyControl, Nothing, Nothing, Nothing)
dt.Commit()
End Sub
C#
public override System.ComponentModel.Design.DesignerVerbCollection Verbs
{
get
{
DesignerVerbCollection v = new DesignerVerbCollection();
// Verb to add buttons
v.Add(new DesignerVerb("&Add Button", new EventHandler(OnAddButton)));
return v;
}
}
private void OnAddButton(object sender, System.EventArgs e)
{
ColourButton button;
IDesignerHost h = (IDesignerHost) GetService(typeof(IDesignerHost));
DesignerTransaction dt;
IComponentChangeService c = (IComponentChangeService)
GetService(typeof(IComponentChangeService));
// Add a new button to the collection
dt = h.CreateTransaction("Add Button");
button = (ColourButton) h.CreateComponent(typeof(ColourButton));
c.OnComponentChanging(MyControl, null);
MyControl.Buttons.Add(button);
c.OnComponentChanged(MyControl, null, null, null);
dt.Commit();
}
Note that even after writing all that, our implementation isn't quite complete yet - you can add buttons, and the undo and redo buttons will remove the button from the design surface ok but they won't remove the button from the Buttons collection - we'll come back to that later.
Selecting Buttons
Designers offer a useful method to override, called GetHitTest. This is passed some coordinates, and
it's up to your logic to let the designer know whether or not to pass the event (usually a click) on
to the control underneath. We will override this method, and see if the mouse cursor is within the
bounds of any of the buttons on the control. If it is, we'll return true.
VB.NET
Protected Overrides Function GetHitTest(ByVal point As System.Drawing.Point) As Boolean
Dim button As ColourButton
Dim wrct As Rectangle
point = MyControl.PointToClient(point)
For Each button In MyControl.Buttons
wrct = button.Bounds
If wrct.Contains(point) Then Return True
Next
Return False
End Function
C#
protected override bool GetHitTest(System.Drawing.Point point)
{
Rectangle wrct;
point = MyControl.PointToClient(point);
foreach (ColourButton button in MyControl.Buttons)
{
wrct = button.Bounds;
if (wrct.Contains(point))
return true;
}
return false;
}
This way, our MouseDown event in the control will be fired if the user clicks on a button.
In this event, we check if we're in design mode (with the DesignMode property) and if we are,
find which button the cursor is on. Then we get a reference to ISelectionService and set the
selection to that button.
VB.NET
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
Dim button As ColourButton
Dim wrct As Rectangle
Dim s As ISelectionService
Dim a As ArrayList
If DesignMode Then
For Each button In Buttons
wrct = button.Bounds
If wrct.Contains(e.X, e.Y) Then
s = DirectCast(GetService(GetType(ISelectionService)), ISelectionService)
a = New ArrayList()
a.Add(button)
s.SetSelectedComponents(a)
Exit For
End If
Next
End If
MyBase.OnMouseDown(e)
End Sub
C#
protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
{
Rectangle wrct;
ISelectionService s;
ArrayList a;
if (DesignMode)
{
foreach (ColourButton button in Buttons)
{
wrct = button.Bounds;
if (wrct.Contains(e.X, e.Y))
{
s = (ISelectionService) GetService(
typeof(ISelectionService));
a = new ArrayList();
a.Add(button);
s.SetSelectedComponents(a);
break;
}
}
}
base.OnMouseDown(e);
}
At this point, clicking on an individual button in the control at design time will select it, and you can even modify its properties in the propertygrid. We've one last piece of code to write before the selection stuff is complete though, and that's filling in the function we created earlier that is called when the selection changes. It's in here that we'll set the highlightedButton variable we created so the selection is indicated visually too.
VB.NET
Friend Sub OnSelectionChanged()
Dim button As ColourButton
Dim newHighlightedButton As ColourButton = Nothing
Dim s As ISelectionService = DirectCast(GetService(GetType _
(ISelectionService)), ISelectionService)
'See if the primary selection is one of our buttons
For Each button In Buttons
If s.PrimarySelection Is button Then
newHighlightedButton = button
Exit For
End If
Next
'Apply if necessary
If Not newHighlightedButton Is highlightedButton Then
highlightedButton = newHighlightedButton
Invalidate()
End If
End Sub
C#
internal void OnSelectionChanged()
{
ColourButton newHighlightedButton = null;
ISelectionService s = (ISelectionService) GetService(typeof(ISelectionService));
// See if the primary selection is one of our buttons
foreach (ColourButton button in Buttons)
{
if (s.PrimarySelection == button)
{
newHighlightedButton = button;
break;
}
}
// Apply if necessary
if (newHighlightedButton != highlightedButton)
{
highlightedButton = newHighlightedButton;
Invalidate();
}
}
We're almost there. We can now add the control to a form, use the designer verb to add buttons, and select those buttons visually, changing their properties in the propertygrid.
Related articles
Related discussion
-
Problem after strong naming an assembly
by rinkurathor1 (0 replies)
-
Very slow inserts using SqlCommand.ExecuteNonQuery()
by porchelvi (1 replies)
-
VB.net class to connect to sql database
by senol01 (2 replies)
-
how to select item to datagrid from textbox
by chandradev1 (49 replies)
-
Adobe Flex reaches out to .NET developers
by ranganathanmca (1 replies)
Related podcasts
-
CodeCast Episode 4: State of .NET, IE8, ASP.NET MVC, and O'Reilly Media
CodeCast Episode 4: State of .NET, IE8, ASP.NET MVC, and O'Reilly MediaHosts Ken Levy and Markus Egger discuss the new State of .NET events, IE8, ASP.NET MVC, followed by an interview from PDC with two editors from O'Reilly Media. More on ASP.NET MVC can be found at http://asp.net/mvc. Interview...
Related jobs
-
Microsoft .Net Architect
in AMSTERDAM (€50K-€90K per annum) -
Applicatie ontwikkelaar binnen Defensie
in Amsterdam (£50K-£90K per annum) -
Business Analist (Openbaar) Vervoer
in Amsterdam (€50K-€90K per annum) -
Application Engineer (VB, .Net, Java) - Standplaats: Utrecht
in Amsterdam (€50K-€90K per annum)
Events coming up
-
Dec
6
Developing AJAX Web Applications with Castle Monorail
London, United Kingdom
Monorail is the model-view-controller engine of the Castle Project, bringing many of the best ideas of Ruby on Rails to the .NET world. In this talk, David De Florinier and Gojko Adzic show how Monorail makes it easy to develop .NET based AJAX applications, and how to use the Castle Project to build Web 2.0 applications effectively. Come to this session if you are a .NET web developer. Everyone is welcome!
GREAT ARTICLE¡¡¡ thank you very, very much, now i can finish my own control.
I could not save my own custom class object, NOW I CAN.
I've tried to compile the code I've downloaded but at run-time (placed in a form on another project) I don't see the buttons I've created at deign-time..
Is it safe to assume value (of complex type) of a property has instance descriptor converter for every component/control you dropped on design surface.
Regards
Phani
This thread is for discussions of Collection Controls with Rich Design Time Support.