Prism: Stacking controls in a region?
My Pri开发者_如何学编程sm app needs to insert buttons from several modules into a Shell region. The buttons will be stacked vertically, like the navigation buttons (Mail, Calendar, and so on) in Outlook 2010. I am using custom controls as buttons, so I don't need to worry about templating--my problem is the same as if I was inserting plain radio buttons.
How do set up the region so that the buttons will appear stacked vertically? Thanks for your help.
StackPanel immediately jumps to mind when thinking of stacking items vertically. Unfortunately Prism doesn't support the StackPanel to be a region out of the box. Luckily you can create a RegionAdapter to fix this problem.
Public Class StackPanelRegionAdapter
Inherits RegionAdapterBase(Of StackPanel)
Public Sub New(ByVal behaviorFactory As IRegionBehaviorFactory)
MyBase.New(behaviorFactory)
End Sub
Protected Overrides Sub Adapt(ByVal region As IRegion, ByVal regionTarget As StackPanel)
AddHandler region.Views.CollectionChanged, Sub(sender As Object, e As NotifyCollectionChangedEventArgs)
If e.Action = NotifyCollectionChangedAction.Add Then
For Each element As FrameworkElement In e.NewItems
regionTarget.Children.Add(element)
Next
Else
If e.Action = NotifyCollectionChangedAction.Remove Then
For Each element In e.OldItems
If regionTarget.Children.Contains(element) Then
regionTarget.Children.Remove(element)
End If
Next
End If
End Sub
End Sub
Protected Overrides Function CreateRegion() As Microsoft.Practices.Prism.Regions.IRegion
Return New AllActiveRegion
End Function
End Class
From there you just need to add the mapping in the ConfigureRegionAdapterMappings Override in your bootstrapper.
Protected Overrides Function ConfigureRegionAdapterMappings() As Microsoft.Practices.Prism.Regions.RegionAdapterMappings
Dim mappings = MyBase.ConfigureRegionAdapterMappings()
mappings.RegisterMapping(GetType(Grid), Container.Resolve(Of PrismExtensions.GridRegionAdapter))
mappings.RegisterMapping(GetType(StackPanel), Container.Resolve(Of PrismExtensions.StackPanelRegionAdapter))
Return mappings
End Function
Edit: Found the John Papa link I originally got the code from. (In C# if that's what you're using) Fill My Prism Region, Please
Why not just use an ItemsControl as a region? It stacks items vertically and Prism has builtin region adapters for it. I don't understand why you'd want to use a StackPanel for anything but layout.
An ItemsControl by default uses an ItemsPanel that contains a StackPanel, so it's equivalent to the answers already provided, but without the unnecessary code.
From NVenhola's comment in the Fill My Prism Region, Please article, there is an easy, adaptable solution:
<ItemsControl cal:RegionManager.RegionName="XXRegionNameXX">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Matt's solution is explained in the 'Developers Guide to Microsoft Prism' (V4) at pp. 189-191.
For C# developers researching this issue, here is a translation of Matt's adapter to C#:
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Practices.Prism.Regions;
namespace FsNoteMaster3.Shell.Views.Utility
{
/// <summary>
/// Enables use of a StackPanel in a Prism region.
/// </summary>
/// <remarks> See stackoverflow.com/questions/4950464/prism-stacking-controls-in-a-region</remarks>
public class StackPanelRegionAdapter : RegionAdapterBase<StackPanel>
{
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="behaviorFactory">Allows the registration of the default set of RegionBehaviors.</param>
public StackPanelRegionAdapter(IRegionBehaviorFactory behaviorFactory) : base(behaviorFactory)
{
}
/// <summary>
/// Adapts a ContentControl to an IRegion.
/// </summary>
/// <param name="region">The new region being used.</param>
/// <param name="regionTarget">The object to adapt.</param>
protected override void Adapt(IRegion region, StackPanel regionTarget)
{
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (FrameworkElement element in e.NewItems)
{
regionTarget.Children.Add(element);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (UIElement elementLoopVariable in e.OldItems)
{
var element = elementLoopVariable;
if (regionTarget.Children.Contains(element))
{
regionTarget.Children.Remove(element);
}
}
break;
}
};
}
/// <summary>
/// Template method to create a new instance of IRegion that will be used to adapt the object.
/// </summary>
/// <returns>A new instance of IRegion.</returns>
protected override Microsoft.Practices.Prism.Regions.IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
}
And for the Bootstrapper, here is the ConfigureRegionAdapterMappings() override in C#, updated for Prism 4:
/// <summary>
/// Configures the default region adapter mappings to use in the application.
/// </summary>
/// <returns>The RegionAdapterMappings instance containing all the mappings.</returns>
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
var mappings = base.ConfigureRegionAdapterMappings();
mappings.RegisterMapping(typeof(StackPanel), ServiceLocator.Current.GetInstance<StackPanelRegionAdapter>());
return mappings;
}
How about just using itemscontrol that is just doing doing what you want. By default, ItemsControl will use stackpanel to render the module views.
also be very careful about RegionAdapterMapping instance in Bootstrapper.cs: write it like this:
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
//this is the correct way
RegionAdapterMappings regionAdapterMappings = base.ConfigureRegionAdapterMappings();
regionAdapterMappings.RegisterMapping(typeof(StackPanel), Container.Resolve<StackPanelRegionAdapter>());
return regionAdapterMappings;
}
It took me hours to figure out you cant write:
//RegionAdapterMappings regionAdapterMappings = new RegionAdapterMappings();
精彩评论