It’s been a time since I was wondering about posting about the SmartPartPlaceholder. It’s a useful control, yet it’s another concept that we have little information about. So, I decided to take my time and see what could be done with this control, and how it was done.
Basically, the way in which we can use the placeholder is quite simple:
We add the placeholder in a view or form and set its SmartPartName property with some ID. Then, we make sure that we have added a view with that ID in a WorkItem before adding the view of the placeholder, so that the placeholder gets it and displays it.
As simple as it gets, right?
But, how does that happen? Is that everything we can do with the placeholder? Why is the view with the placeholder scoped into a WorkItem in the SmartPart Quickstart?
All these questions have an answer, and that’s what this article will deal with.
First of all, I’ll start describing the different classes that work together to perform “the magic behind the scenes”.
The ISmartPartPlaceholder interface
The ISmartPartPlaceholder interface contains only two properties which are implemented in the SmartPartPlaceholder control class. These properties are:
- SmartPartName. This property gets or sets the default name for the SmartPart contained in the current WorkItem.
- SmartPart. This property gets or sets the SmartPart contained by the placeholder.
The SmartPartPlaceholder control class
Here’s the main character! The class that has the main role in this article’s play!
Luckily, it’s not a complex class at all. It consists of one constructor, a method that draws its border at design-time, the two properties implemented from the ISmartPartPlaceholder interface, and a method that fires an event when a SmartPart is shown.
So, let’s see them in detail:
- The SmartPartPlaceholder constructor sets the style of the placeholder to support transparent color as its background color. Then, it sets the background color of the placeholder to transparent.
- The protected OnPaint(PaintEventArgs e) method draws the dashed rectangle that forms the border of the SmartPartPlaceholder at design-time.
- The SmartPartName property is used by the ControlSmartPartStrategy builder strategy to get the SmartPart (whose ID matches the SmartPartName property) from the current WorkItem in order to display it in the placeholder. NOTE: The value of this property cannot be null, otherwise an exception will be thrown.
- The SmartPart property displays the SmartPart it gets as value in the placeholder. It checks that the set value is not null and that it is type of Control (otherwise, an exception is thrown). Once this is done, it casts the value to Control, sets its Dock property to DockStyle.Fill and calls its Show() method. Then, the Controls collection of the placeholder is cleared and the casted SmartPart is added to this collection. Finally, the OnSmartPartShown method is called, sending the SmartPart as argument.
- The OnSmartPartShown private method receives an object as argument (in fact, the SmartPart that has just been displayed in the placeholder). It checks whether the SmartPartShown event has any handler attached, and, if so, fires the event with the placeholder as sender and with a new instance of the SmartPartPlaceHolderEventArgs class as event arguments (this instance contains the SmartPart in its SmartPart read-only property which was sent as argument in its constructor).
The SmartPartPlaceHolderEventArgs class
The SmartPartPlaceHolderEventArgs class has a constructor that receives an object as argument (the SmartPart recently shown in the placeholder). This constructor checks that the argument it’s receiving is not null (otherwise, it throws an exception) and sets the value of a private member (named "smartPart") to this argument. This class also has a read-only property named SmartPart (of type object) that can be used to retrieve the SmartPart that has just been displayed in the placeholder.
The ControlSmartPartStrategy class
The ControlSmartPartStrategy class is a builder strategy that is in charge of detecting the SmartPartPlaceholder whenever a control is run through the pipeline of ObjectBuilder (NOTE: the strategy also looks for Workspaces and SmartParts). Once the placeholder is detected, the strategy checks its SmartPartName property, so it can retrieve the SmartPart whose ID matches SmartPartName property from the Items collection of the current WorkItem. Having retrieved the SmartPart, it sets the value of the SmartPart property of the placeholder to the retrieved value. If the retrieved value is null, the SmartPart property of the placeholder is not set at all; this is why no exception is thrown when no matching SmartPart is found. The strategy also adds the placeholder to the Items collection of the current WorkItem, with the name of the placeholder as its ID (if the placeholder has no name, a GUID is set instead).
Here’s the code of the ReplaceIfPlaceHolder(WorkItem workItem, Control control) method that performs what was described previously:
private void ReplaceIfPlaceHolder(WorkItem workItem, Control control)
ISmartPartPlaceholder placeholder = control as ISmartPartPlaceholder;
if (placeholder != null)
Control replacement = workItem.Items.Get<Control>(placeholder.SmartPartName);
if (replacement != null)
placeholder.SmartPart = replacement;
The following sequence diagram depicts the interactions described in the "The Actors" section. For the sake of simplicity, some interactions are commented as notes.
WorkItem-scope for placeholders?
As it was shown in the "The Actors" section, the ControlSmartPartStrategy class added the placeholder to the current WorkItem with its name as ID. So, if you add two instances of the view that contains that placeholder to the same WorkItem, you will get an exception stating that there’s already an object with that ID. This is one of the reasons why placeholders are scoped in different child WorkItems.
The other reason deals with the view that will be displayed in the placeholder. As I said before, the SmartPartName property of the placeholder cannot be null (or you will get an exception). To get the placeholder work with this property, you must add a view with some ID to the WorkItem before you add the view with the placeholder. But, if you add two views with the same ID, you will get an exception because there’s already an object with the same ID. This is why, in the SmartPart Quickstart, the view displayed in the placeholder and the view with the placeholder are scoped into a child WorkItem.
Nevertheless, this kind of scope is necessary if any of the reasons described above may happen.
One useful way to use the SmartPartPlaceholder is like a "reduced DeckWorkspace" or a "lightweight DeckWorkspace". Before I start describing this, let me state that a placeholder can be put in forms too, not only in views! Now, let’s move on. As I said, the placeholder is added to the WorkItem with some ID, so you may put it in the ShellForm with some ID and access it in your modules. For example:
SmartPartPlaceholder placeholder = WorkItem.RootWorkItem.Items.Get<SmartPartPlaceholder>("Placeholder");
MyView view = WorkItem.SmartParts.AddNew<MyView>();
placeholder.SmartPart = view;
In that way, you may display a view in the placeholder, and when you add another, the first view will automatically be removed from the placeholder.
For this purpose, you should keep in mind that:
- Calling the Presenter.OnCloseView() does nothing as it searches for a container workspace and calls its IWorkspace.Close(object view) method and, in this case, the view is contained by a SmartPartPlaceholder.
- If you want to remove the view from the placeholder, you should explicitly remove the view from the Controls collection.
- The placeholder does not contain a SmartPartClosing event.
- The placeholder does not contain a collection of SmartParts.
I hope this article gives you a clearer concept of the SmartPartPlaceholder class.
As always: any feedback is welcome!
Ver este post en español.