CAB UI Extension sites and Multiple Toolbars

Topics: CAB & Smart Client Software Factory
Jan 8, 2007 at 11:42 PM
Greetings, All -

I'd like to be able to have each module in my application be able to add it's own toolbar to the shell layout when it is loaded, particularly since the modules that are loaded depend on who is logging in, and what priveleges they have.

The problem is, I can't figure out how to add an entirely new toolbar, just add buttons to one that already exists. Is there a way to add an entire toolbar from the initialization code of a module as it is loading?

Mark Faulcon
Jan 9, 2007 at 3:02 AM
It's possible but not out of the box.

I wrote some code based on a blog post of mine that can get it done. First add these classes to a project:

public class ToolStripPanelUIAdapter : IUIElementAdapter
{
ToolStripPanel panel;

public ToolStripPanelUIAdapter(ToolStripPanel panel) {
this.panel = panel;
}

public object Add(object uiElement) {
panel.Controls.Add((ToolStrip)uiElement);
return uiElement;
}

public void Remove(object uiElement) {
panel.Controls.Remove((ToolStrip)uiElement);
}
}

public class ToolStripPanelUIAdapterFactory : IUIElementAdapterFactory
{
public IUIElementAdapter GetAdapter(object uiElement) {
if (uiElement is ToolStripPanel)
return new ToolStripPanelUIAdapter((ToolStripPanel)uiElement);

throw new ArgumentException("uiElement");
}

public bool Supports(object uiElement) {
return uiElement is ToolStripPanel;
}
}

Then add this to your ShellForm:

internal ToolStripPanel ToolStripPanel {
get { return this.toolStripContainer1.TopToolStripPanel; }
}

Then add this to your ShellApplication's AfterShellCreated method:

IUIElementAdapterFactoryCatalog catalog =
RootWorkItem.Services.Get<IUIElementAdapterFactoryCatalog>();
catalog.RegisterFactory(new ToolStripPanelUIAdapterFactory());
RootWorkItem.UIExtensionSites.RegisterSite("SomeName",
Shell.ToolStripPanel);

Then in your module do something like this:

UIExtensionSite site = WorkItem.UIExtensionSites"SomeName";
ToolStrip strip = new ToolStrip();
strip.Location = new System.Drawing.Point(0, 25);
strip.Text = "Module Options";
strip.Items.Add("Option 1");
strip.Items.Add("Option 2");
strip.Items.Add("Option 3");
site.Add<ToolStrip>(strip);

I think that's about it.

Justin Burtch
Co-Founder, Newbrook Solutions, Inc.
Jan 9, 2007 at 6:59 PM
Excellent! Thanks for the thorough response. In case anyone from the SC-SF group is watching, this should be standard out of the box...

Mark Faulcon
Mar 5, 2008 at 8:29 PM
Nice!

A potential issue I see (because I am working on similar functionality) is handling the case when the active View changes. Ideally, the toolbar will have to be removed for the time being and re-added when "its' view becomes visible/active again.

Ideally if the toolbar is somehow associated with the view (at SmartPart level) and have a "smart" WorkSpace.Show implementation that knows how to activate/deactivate SmartPart-specific toolbars...

Thoughts?
Mar 6, 2008 at 6:30 AM
gstoy

You may want to wrap the toolbar functionality in a ToolBarService which is injected through DI. This way you can hide the complexity of dealing with the UIExtension site and the toolstrip.

Regards
Glenn
Mar 6, 2008 at 6:33 AM
Mfaulcon.

We're watching, thanks for the feedback. Another option is for Justin or some one else in the community to add this to SCSFContrib
Mar 6, 2008 at 2:02 PM
I was asking for suggestions on the best way to hide the toolbar when the bar-owning view is not "active".

My quick and dirty solution is:
1. Add View's toolbars to a custom key in View's SmartPartInfo
wspi.KeysWindowWorkspaceSetting.FormSpecificToolbars = new List<ToolStrip>();
((List<ToolStrip>)wspi.KeysWindowWorkspaceSetting.FormSpecificToolbars).Add((ToolStrip)bindingNavigator1);
2. Extend DockSpace that displays the View to:
A) maintain a dictionary of all active Views' toolbars:
private Dictionary<Control, List<ToolStrip>> smartPartToolbars = new Dictionary<Control, List<ToolStrip>>();

B) store View's toolbars that Dictionary (done in in OnApplySmartPartInfo override)
...
if (spi.Keys.ContainsKey(WindowWorkspaceSetting.FormSpecificToolbars))
{
smartPartToolbars.Add(smartPart, (List<ToolStrip>)spi.KeysWindowWorkspaceSetting.FormSpecificToolbars);
}

C) iterrate this collection in OnActivate override to flag Visible=false of all toolbars except the current View's ones (make those visible)
foreach(List<ToolStrip> list in smartPartToolbars.Values)
{
foreach (ToolStrip toolbar in list)
toolbar.Visible = false;
}

List<ToolStrip> toolbars = null;
if (smartPartToolbars.ContainsKey(smartPart))
toolbars = smartPartToolbarssmartPart;
if (toolbars != null)
{
foreach (ToolStrip toolbar in toolbars)
toolbar.Visible = true;
}

D) remove toolbar reference in OnClose override.
if (smartPartToolbars.ContainsKey(smartPart))
smartPartToolbars.Remove(smartPart);
Mar 9, 2008 at 11:00 PM
What if I have multiple view(documents) that share the same toolbar ? each time a new view(document) is opened, a new similar tool bar will be added, and this is not good.
who should track the currently opened documents and decide if a new tool bar should be added or not ? the view presenter, the workitem controller or the view itself ?

and I am not sure about that the workspace should be responsible for that.

any suggestion ?
Mar 11, 2008 at 3:38 PM
Edited Mar 11, 2008 at 3:40 PM
As I said - quick and dirty :), but if we think it through...

What if I have multiple view(documents) that share the same toolbar ? each time a new view(document) is opened, a new similar tool bar will be added, and this is not good.


I think what I am proposing above is a method to HIDE/SHOW the toolbar that a View "reported" as ITS toolbar via SmartPartsInfo. If a toolbar is shared between views and each View registered the toolbar as "ITS toolbar", then toolbar will eventually remain VISIBLE as long as the active view claims to own that toolbar (even if shared).

(I am not proposing how your View creates or obtains a reference to the toolbar... that's outside the scope of this problem/
solution)


who should track the currently opened documents and decide if a new tool bar should be added or not ? the view presenter, the workitem controller or the view itself ?

and I am not sure about that the workspace should be responsible for that.



I look at the workspace as an entity that provides the service of visualizing a View. My understanding is that its precisely the workspace that is tracking the active/visible Views. Correct me if I'm wrong, please. As far as responsibility to add or not a toolbar - that's up to the View/Presenter and its outside of the scope of this discussion.


To summarize what Justin is proposing:
1. Place a ToolStripPanel where it suits your layout design;
2. Implement IUIElementAdapter and IUIElementAdapterFactory
3. Register with shell's IUIElementAdapterFactoryCatalog and UIExtensionSites facilities
Later, in your View/Presenter (or for that matter wherever else desired/feasible) add your toolbar to that UIExtensionSite
What I am proposing:
1. Register the toolbar with View's SmartPartInfo
2. Extend your workspace to HIDE/SHOW toolbars registered with SmartPartInfo

thank you for sharing your thougths
Mar 12, 2008 at 7:41 PM
Edited Mar 12, 2008 at 7:52 PM
Hi gstoy,
Let's say that my apllication mimic the Visual Studio IDE !
My shell is an MDI container that host a DockPanelWorkSpace (a workspace that I has created although there is someone that has created one...).
Into my shell, I put a ToolStripPanel at the top, so view that want to add it's toolbar can use the Join method to do this. So I don't know how a Workspace

can talk to this ToolStripPanel ? For that I decide that the Shell itself will do this.
So this is my solution:
1-A view must implement this interface if he want to be an MdiChild
Public Interface IMdiChild
Function GetToolBar()
Sub OnViewActivated()
End Interface
2-We overrides OnMdiChildActivate in the Shell form this way: (where lastDisplayedToolBar is a preivate memeber of type ToolStrip).
Protected Overrides Sub OnMdiChildActivate(ByVal e As System.EventArgs)
MyBase.OnMdiChildActivate(e)
If Me.ActiveMdiChild IsNot Nothing Then
Dim mdiChild As IMdiChild = TryCast(Me.ActiveMdiChild.Controls(0), IMdiChild) ' Because each view is encapsulated into a specific form (the
' workspace do this), so this special form have only one control (the view)
Dim mdiChildHaveToolBar As Boolean = IIf(mdiChild IsNot Nothing AndAlso mdiChild.GetToolBar IsNot Nothing, True, False)
If _lastDisplayedToolBar IsNot Nothing AndAlso mdiChildHaveToolBar AndAlso _lastDisplayedToolBar IsNot mdiChild.GetToolBar Then
Me.ToolStripPanel1.SuspendLayout()
Me.ToolStripPanel1.Controls.Remove(_lastDisplayedToolBar)
Me.ToolStripPanel1.Join(mdiChild.GetToolBar, ToolStripPanel1.Rows.Count)
_lastDisplayedToolBar = mdiChild.GetToolBar
Me.ToolStripPanel1.ResumeLayout(True)
ElseIf _lastDisplayedToolBar Is Nothing AndAlso mdiChildHaveToolBar Then
Me.ToolStripPanel1.Join(mdiChild.GetToolBar, ToolStripPanel1.Rows.Count)
_lastDisplayedToolBar = mdiChild.GetToolBar
ElseIf _lastDisplayedToolBar IsNot Nothing AndAlso Not mdiChildHaveToolBar Then
Me.ToolStripPanel1.Controls.Remove(_lastDisplayedToolBar)
_lastDisplayedToolBar = Nothing
End If
If mdiChild IsNot Nothing Then
mdiChild.OnViewActivated()
End If
ElseIf _lastDisplayedToolBar IsNot Nothing Then
Me.ToolStripPanel1.Controls.Remove(_lastDisplayedToolBar)
_lastDisplayedToolBar = Nothing
End If
End Sub
3-The presenter will hold a shared variable that point to the toolbar (in my sutuation, this toolbar is hosted in a normal user control), so when the view
implement the GetToolBar function, it simply make a call to a public property in the presenter that give him this reference. I do this because I don't want
that every view create a new toolbar instance...
The presenter also have a public Sub OnViewActivated that the view will call when it is activated (the shell will call this procedure through the interface)
This will give the presenter the chance to initialize any state of the toolbar items that belong to it's view...

So now my shell mimic Visual Studio IDE: if an MdiView have a toolbar it will be shown if it is active, and any added view of the same type will not alter
the toolbar(nothing will be get hiden and visible again), and if a view that does not have a toolbar is displayed, the toolbar will goes, and if he have a
toolbar different from the preview one, it will replace it...

I am happy now with this solution and it work great and I think that everything is in the correct place (I hope !).
Mar 12, 2008 at 7:44 PM
Hi gstoy,
Let's say that my apllication mimic the Visual Studio IDE !
My shell is an MDI container that host a DockPanelWorkSpace (a workspace that I has created although there is someone that has created one...).
Into my shell, I put a ToolStripPanel at the top, so view that want to add it's toolbar can use the Join method to do this. So I don't know how a Workspace

can talk to this ToolStripPanel ? For that I decide that the Shell itself will do this.
So this is my solution:
1-A view must implement this interface if he want to be an MdiChild
Public Interface IMdiChild
Function GetToolBar()
Sub OnViewActivated()
End Interface
2-We overrides OnMdiChildActivate in the Shell form this way: (where lastDisplayedToolBar is a preivate memeber of type ToolStrip).
Protected Overrides Sub OnMdiChildActivate(ByVal e As System.EventArgs)
MyBase.OnMdiChildActivate(e)
If Me.ActiveMdiChild IsNot Nothing Then
Dim mdiChild As IMdiChild = TryCast(Me.ActiveMdiChild.Controls(0), IMdiChild) ' Because each view is encapsulated into a specific form (the
' workspace do this), so this special form have only one control (the vieW)
Dim mdiChildHaveToolBar As Boolean = IIf(mdiChild IsNot Nothing AndAlso mdiChild.GetToolBar IsNot Nothing, True, False)
If _lastDisplayedToolBar IsNot Nothing AndAlso mdiChildHaveToolBar AndAlso _lastDisplayedToolBar IsNot mdiChild.GetToolBar Then
Me.ToolStripPanel1.SuspendLayout()
Me.ToolStripPanel1.Controls.Remove(_lastDisplayedToolBar)
Me.ToolStripPanel1.Join(mdiChild.GetToolBar, ToolStripPanel1.Rows.Count)
_lastDisplayedToolBar = mdiChild.GetToolBar
Me.ToolStripPanel1.ResumeLayout(True)
ElseIf _lastDisplayedToolBar Is Nothing AndAlso mdiChildHaveToolBar Then
Me.ToolStripPanel1.Join(mdiChild.GetToolBar, ToolStripPanel1.Rows.Count)
_lastDisplayedToolBar = mdiChild.GetToolBar
ElseIf _lastDisplayedToolBar IsNot Nothing AndAlso Not mdiChildHaveToolBar Then
Me.ToolStripPanel1.Controls.Remove(_lastDisplayedToolBar)
_lastDisplayedToolBar = Nothing
End If
If mdiChild IsNot Nothing Then
mdiChild.OnViewActivated()
End If
ElseIf _lastDisplayedToolBar IsNot Nothing Then
Me.ToolStripPanel1.Controls.Remove(_lastDisplayedToolBar)
_lastDisplayedToolBar = Nothing
End If
End Sub
3-The presenter will hold a shared variable that point to the toolbar (in my sutuation, this toolbar is hosted in a normal user control), so when the view
implement the GetToolBar function, it simply make a call to a public property in the presenter that give him this reference. I do this because I don't want
that every view create a new toolbar instance...
The presenter also have a public Sub OnViewActivated that the view will call when it is activated (the shell will call this procedure through the interface)
This will give the presenter the chanec to initialize any state of the toolbar items that belong to it's view...

So now my shell mimic Visual Studio IDE: if an MdiView have a toolbar it will be shown if it is active, and any added view of the same type will not alter
the toolbar(nothing will be get hiden and visible again), and if a view that does not have a toolbar is displayed, the toolbar will goes, and if he have a
toolbar different from the preview one, it will replace it...

I am happy now with this solution and it work great and I think that every think is in the correct place (I hope !).