Могу ли я реализовать свой собственный сервис разрешения представлений и использовать RequestNavigate? - PullRequest
5 голосов
/ 17 сентября 2011

Я довольно новичок в Prism, и в настоящее время я переписываю одно из наших существующих приложений, используя Prism в качестве концепции проекта.

Приложение использует MVVM с первым подходом ViewModel: наш ViewModelразрешается контейнером, и сервис IViewResolver выясняет, к какому представлению он должен быть подключен (помимо прочего, используя соглашения об именах).

Код (для добавления представления к элементу управления с вкладками) вмомент выглядит примерно так:

var vm = (get ViewModel from somewhere)
IRegion reg = _regionManager.Regions["MainRegion"];
var vw = _viewResolver.FromViewModel(vm); // Spins up a view and sets its DataContext
reg.Add(vw);
reg.Activate(vw);

Все это прекрасно работает, однако я бы очень хотел использовать навигационную среду Prism, чтобы сделать все это для меня, чтобы я мог сделать что-то вроде этого:

_regionManager.RequestNavigate(
    "MainRegion", 
    new Uri("NameOfMyViewModel", UriKind.Relative)
);

и Prism раскручивает ViewModel + View, настраивает DataContext и вставляет представление в область.

Я добился определенного успеха, создав DataTemplates со ссылками на типы ViewModel,например:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Module01">
<DataTemplate DataType="{x:Type local:TestViewModel}">
<local:TestView />
</DataTemplate>
</ResourceDictionary>

... и заставить модуль добавить соответствующий словарь ресурсов в ресурсы приложений при инициализации модуля, ноэто выглядит немного глупо.

Есть ли способ эффективно взять на себя создание представления из Prism, так что, когда вызывается RequestNavigate, я могу посмотреть на предоставленный Uri и раскрутить представление / модель представления на основена что?Есть перегрузка RegionManager.RegisterViewWithRegion, которая принимает делегата, который позволяет вам самим создавать представление, и я предполагаю, что мне нужно что-то подобное.

Я думаю, что мне может потребоваться предоставить свой собственный IRegionBehaviorFactory,но я не уверен, в чем дело (или даже если я на правильном пути!).

Любая помощь приветствуется!

- примечание: Первоначально опубликовано на сайте кодового комплекса призмы

Ответы [ 2 ]

8 голосов
/ 18 сентября 2011

Конечно, вы можете сделать это. Я обнаружил, что Prism v4 действительно расширяемый, если только вы знаете, куда его подключить.

В этом случае вам нужна собственная пользовательская реализация IRegionNavigationContentLoader.

Вот как все настроить в своем загрузчике (пример взят из подкласса UnityBootstrapper из одного из моих собственных проектов):

protected override void ConfigureContainer()
{
    // IMPORTANT: Due to the inner workings of UnityBootstrapper, accessing
    // ServiceLocator.Current here will throw an exception!
    // If you want access to IServiceLocator, resolve it from the container directly.
    base.ConfigureContainer();

    // Set up our own content loader, passing it a reference to the service locator
    // (it will need this to resolve ViewModels from the container automatically)
    this.Container.RegisterInstance<IRegionNavigationContentLoader>(
       new ViewModelContentLoader(this.Container.Resolve<IServiceLocator>()));
}

Сам ViewModelContentLoader является производным от RegionNavigationContentLoader для повторного использования кода и будет выглядеть примерно так:

public class ViewModelContentLoader : RegionNavigationContentLoader
{
    private readonly IServiceLocator serviceLocator;

    public ViewModelContentLoader(IServiceLocator serviceLocator)
        : base(serviceLocator)
    {
        this.serviceLocator = serviceLocator;
    }

    // THIS IS CALLED WHEN A NEW VIEW NEEDS TO BE CREATED
    // TO SATISFY A NAVIGATION REQUEST
    protected override object CreateNewRegionItem(string candidateTargetContract)
    {
        // candidateTargetContract is e.g. "NameOfMyViewModel"

        // Just a suggestion, plug in your own resolution code as you see fit
        var viewModelType = this.GetTypeFromName(candidateTargetContract);
        var viewModel = this.serviceLocator.GetInstance(viewModelType);

        // get ref to viewResolver somehow -- perhaps from the container?
        var view = _viewResolver.FromViewModel(vm);

        return view;
    }

    // THIS IS CALLED TO DETERMINE IF THERE IS ANY EXISTING VIEW
    // THAT CAN SATISFY A NAVIGATION REQUEST
    protected override IEnumerable<object> 
    GetCandidatesFromRegion(IRegion region, string candidateNavigationContract)
    {
        if (region == null) {
            throw new ArgumentNullException("region");
        }

        // Just a suggestion, plug in your own resolution code as you see fit
        var viewModelType = this.GetTypeFromName(candidateNavigationContract);

        return region.Views.Where(v =>
            ViewHasDataContract((FrameworkElement)v, viewModelType) ||
            string.Equals(v.GetType().Name, candidateNavigationContract, StringComparison.Ordinal) ||
            string.Equals(v.GetType().FullName, candidateNavigationContract, StringComparison.Ordinal));
    }

    // USED IN MY IMPLEMENTATION OF GetCandidatesFromRegion
    private static bool 
    ViewHasDataContract(FrameworkElement view, Type viewModelType)
    {
        var dataContextType = view.DataContext.GetType();

        return viewModelType.IsInterface
           ? dataContextType.Implements(viewModelType)
           : dataContextType == viewModelType 
                   || dataContextType.GetAncestors().Any(t => t == viewModelType);
    }

    // USED TO MAP STRINGS OF VIEWMODEL TYPE NAMES TO ACTUAL TYPES
    private Type GetTypeFromName(string typeName)
    {
        // here you need to map the string type to a Type object, e.g.
        // "NameOfMyViewModel" => typeof(NameOfMyViewModel)

        return typeof(NameOfMyViewModel); // hardcoded for simplicity
    }
}
2 голосов
/ 17 сентября 2011

Чтобы избежать путаницы с «первым подходом ViewModel»: Вы используете больше «подход к контроллеру», но не «подход к ViewModel первым». «Первый подход ViewModel» заключается в том, что когда вы внедряете View в ViewModel, но подключаете оба, ViewModel и View, через сторонний компонент (контроллер), что, кстати, является (я не хочу сказать, «лучший», но) наиболее слабосвязанный подход.

Но чтобы ответить на ваш вопрос: Возможное решение - написать расширение для Prism RegionManager, которое будет делать именно то, что вы описали выше:

    public static class RegionManagerExtensions
    {            
        public static void AddToRegion<TViewModel>(
               this IRegionManager regionManager, string region)
        {
            var viewModel = ServiceLocator.Current.GetInstance<TViewModel>();
            FrameworkElement view;

            // Get View depending on your conventions

            if (view == null) throw new NullReferenceException("View not found.");

            view.DataContext = viewModel;
            regionManager.AddToRegion(region, view);
            regionManager.Regions[region].Activate(view);

        }
    }

тогда вы можете вызвать этот метод так:

regionManager.AddToRegion<IMyViewModel>("MyRegion");
...