开发者

Prism for Silverlight: How to maintain views in a specific order inside a region

I am creating sort of a "Navigation panel" (which is actually an ItemControl) for SL and using Regions to allow each module to add his link to the panel.

Problem is that modules loading is inconsistent and thus order of links in the panel can change according to modules loading o开发者_C百科rder.

Restricting the modules order is out of the question.

Other feasible option is the order the region's Views Collection that is binded to the ItemControl, the problem is that ViewCollection is very limited, so ordering it is pretty hard.

Did I miss an option, do you have an idea?

Thanks Ariel


In Prism4 you just apply the ViewSortHintAttribute to your views:

[ViewSortHint("100")]
class FirstView : UserControl { }

[ViewSortHint("200")]
class SecondView : UserControl { }

The default sort comparer on the regions will pick up this attribute and sort the views accordingly. You can put any string into the attribute but I tend to use medium sized numbers that allow me to easily put a new view in between existing ones.


Refering to Sam's answer you first have to build your comparer. The following one is also capable of views that do not have a dedicated wish to be positioned at. To attach this comparer to the region that has to be sorted you can use a way intruduced by the prism manual:

public partial class MainView : UserControl
{
    public MainView( ) 
    {
        InitializeComponent( );

        ObservableObject<IRegion> observableRegion = RegionManager.GetObservableRegion( ContentHost );

        observableRegion.PropertyChanged += ( sender, args ) =>
        {
            IRegion region = ( (ObservableObject<IRegion>)sender ).Value;
            region.SortComparison = CompareViews;
        };
    }

    private static int CompareViews( object x, object y )
    {
        IPositionView positionX = x as IPositionView;
        IPositionView positionY = y as IPositionView;
        if ( positionX != null && positionY != null )
        {
            //Position is a freely choosable integer
            return Comparer<int>.Default.Compare( positionX.Position, positionY.Position );
        }
        else if ( positionX != null )
        {
            //x is a PositionView, so we favour it here
            return -1;
        }
        else if ( positionY != null )
        {
            //y is a PositionView, so we favour it here
            return 1;
        }
        else
        {
            //both are no PositionViews, so we use string comparison here
            return String.Compare( x.ToString( ), y.ToString( ) );
        }
    }
}


At least in prism V4 there you can tell the region manager how to sort the views in a specific region. You just need to provide a compare function to the region.

This example sorts by a very stupid value, the function name:

private static int CompareViews(object x, object y)
{
  return String.Compare(x.ToString(), y.ToString());
}

this._regionManager.Regions["MyRegion"].SortComparison = CompareViews;

Of course the region needs to be known to the region manager before you can set the SortComparison. So far the only workaround I found to achieve this was to defer to set the comparison function using the Dispatcher:

private readonly IRegionManager _regionManager;

[ImportingConstructor]
public ShellViewModel(IRegionManager regionManager)
{
  this._regionManager = regionManager;
  Dispatcher dp = Dispatcher.CurrentDispatcher;
  dp.BeginInvoke(DispatcherPriority.ApplicationIdle, new ThreadStart(delegate
  {
    if (this._regionManager.Regions.ContainsRegionWithName("MyRegion"))
      this._regionManager.Regions["MyRegion"].SortComparison = CompareViews;
  }));
}

Of course you should use some more useful information than the class name for the sorting order, but this should be easy to solve.


This is not built into Prism regions, however it's easily implementable.

Damian Schenkelman has posted an extension method he created for adding a region to an index that seems to work pretty well. http://blogs.southworks.net/dschenkelman/2009/03/14/how-to-add-a-view-to-a-region-in-a-particular-index-with-prism-v2/

Hope this helps.


I found that Sam's solution worked, but discovered that it executes the sort when all views have been added to the region, thus sorting the views twice.

Although it is still a valid solution, reading this post in Prism discussion made me think about a way of implementing this just when the region has been loaded, but before any views have been added yet.

1 - Subscribe to the CollectionChanged of Regions collection

I placed this in the Shell ViewModel code which is the one associated to the View that contains the region I want to sort. Whenever the IRegionManager import has been resolved I subscribe to the CollectionChanged event of its Regions collection:

this._regionManager.Regions.CollectionChanged +=
        new NotifyCollectionChangedEventHandler(Regions_CollectionChanged);

2 - Change the SortComparison of the region in the event delegate

Then the delegate Regions_CollectionChanged will execute whenever the Regions collection is updated and will change the SortComparison of my desired region:

void Regions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Add)
    {
        foreach (var o in e.NewItems)
        {
            IRegion region = o as IRegion;
            if (region != null && region.Name == RegionNames.NavigationRegion)
            {
                region.SortComparison = CompareNavigatorViews;
            }
        }
    }
}

3 - Define the CompareNavigatorViews delegate

In my case, I just sort the views by the title of the assembly where they are contained, you can implement your own compare method here. Remember that the objects you'll receive here are the Views and not the ViewModels.

private static int CompareNavigatorViews(object x, object y)
{
    if (x == null)
        if (y == null)
            return 0;
        else
            return -1;
    else
        if (y == null)
            return 1;
        else
        {
            AssemblyInfo xAssemblyInfo = new AssemblyInfo(Assembly.GetAssembly(x.GetType()));
            AssemblyInfo yAssemblyInfo = new AssemblyInfo(Assembly.GetAssembly(y.GetType()));

            return String.Compare(xAssemblyInfo.Title, yAssemblyInfo.Title);
        }
}

Just in case somebody asks, the AssemblyInfo class is an utility class I made. To get the title of an assembly you could use this function:

string GetAssemblyTitle(Assembly assembly)
{
    object[] attributes = assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
    if (attributes.Length == 1)
    {
        return (attributes[0] as AssemblyTitleAttribute).Title;
    }
    else
    {
        // Return the assembly name if there is no title
        return this.GetType().Assembly.GetName().Name;
    }
}

Hope this helps someone!


Well as the lack of answers counting. I have not found a solution with Prism.

Instead I've used MEF to solve this.

I will write a blog post on it and update this place holder.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜