Prism событие активации View модуля?
От: Glas  
Дата: 10.06.11 20:52
Оценка:
Можно ли какими-то внутренними средствами Prism узнать активен ли View модуля? Я для навигации использую View injection.


moduleRegion.Activate(view);


Хотелось бы после этого сказать модулю, что он активен. IEventAggregator использовать не хочу, итак много событий между модулями отсылается.
Re: Prism событие активации View модуля?
От: Fortnum  
Дата: 11.06.11 21:56
Оценка:
Здравствуйте, Glas, Вы писали:

G>Можно ли какими-то внутренними средствами Prism узнать активен ли View модуля? Я для навигации использую View injection.

G>
G>moduleRegion.Activate(view);
G>

G>Хотелось бы после этого сказать модулю, что он активен. IEventAggregator использовать не хочу, итак много событий между модулями отсылается.

Внутренних средств у Prism для этого нет — надо городить самому.

Очевидно, что сам класс модуля должен реализовать, например, тот же IActiveAware. Теперь вопрос, как изменения активности вида должны "просачиваться" к модулям? Активностью видов управляют в Prism'е регионы (IRegion). Но ни регионы, ни даже виды, ничего не знают о модулях. Вообще, по умолчанию в Prism'е не существует реестра загруженных модулей. А следовательно такой реестр надо придумать и создать.

В Prism'е существует механизм, позволяющий в сам вид, и в его модель пробрасывать информацию об активности вида из региона. Для этого вид или его модель должны реализовать IActiveAware. Но чтобы не городить механизм проброса информации об активности вида в модуль, из которого этот вид был загружен, на наше счастье в Prism'е существует механизм поведений регионов (RegionBehavior), позволяющий нам цепляться к экземплярам регионов, наподобие того как Attached Properties позволяют нам цепляться к элементам WPF-дерева в XAML. Можно для региона написать такое поведение, которое бы при изменении активных видов в регионе, пробрасывало бы эту информацию в модули этих видов.

Остается вопрос, каким образом вид может сообщить поведению региона о том, какому модулю этот вид принадлежит? Это зависит от решения по реестру модулей. Поведение должно иметь доступ к реестру модулей, должно уметь вычитывать из вида информацию о том, какому модулю этот вид принадлежит, и выполнять над соответствующим модулем необходимые операции в части его активации/деактивации.

Если принять, что каждый модуль загружается в приложение только один раз, то для идентификации модуля отлично подойдет некий GUID. В качестве реестра для модулей можно использовать контейнер IUnityContainer, в котором можно сохранять экземпляры модулей под IModule и ключом — тем самым GUID'ом. Чтобы не городить код по сохранению экземпляров модулей в контейнере внутри самих модулей, можно переопределить механизм инициализации модулей, IModuleInitializer, чтобы он делал это за нас. А классы модулей, например, помечать атрибутом IdentifiableModuleAttribute. Вот пример такого инициализатора модулей:

public class IdentifiableModuleInitializer : ModuleInitializer, IModuleInitializer
{
    public IdentifiableModuleInitializer(IUnityContainer container)
        : base(container.Resolve<IServiceLocator>(), container.Resolve<ILoggerFacade>())
    {
        Container = container;
    }
        
    IUnityContainer Container { get; set; }
        
    public new void Initialize(ModuleInfo moduleInfo)
    {
        if (moduleInfo == null)
        {
            throw new ArgumentNullException("moduleInfo");
        }

        IModule moduleInstance = null;

        try
        {
            moduleInstance = this.CreateModule(moduleInfo);
            moduleInstance.Initialize();

            var identifiableModuleAttributes = (IdentifiableModuleAttribute[])
                moduleInstance.GetType().GetCustomAttributes(typeof(IdentifiableModuleAttribute), false);

            if (identifiableModuleAttributes.Length > 0)
            {
                Container.RegisterInstance<IModule>(identifiableModuleAttributes[0].ModuleId,
                    moduleInstance, new ContainerControlledLifetimeManager());
            }
        }
        catch (Exception ex)
        {
            this.HandleModuleInitializationError(moduleInfo, (moduleInstance != null) ? moduleInstance.GetType().Assembly.FullName : null, ex);
        }
    }
}


Метод Initialize пришлось переопределять при помощи ключевого слова new из-за ошибки в базовом классе Prism'ы. Код нагло стырен из Prism'ы рефлектором с добавлением выделенного жирным фрагмента.

При этом для автоматической регистрации модуля в контейнере, к классу модуля необходимо добавить соответствующий атрибут:

[IdentifiableModule("A52773FA-1579-4D54-B4FC-EFEB7BE65EA8")]
public class Module : ActiveAwareModule
{


Класс ActiveAwareModule — это вспомогательный базовый класс для модулей, так как он реализует IActiveAware, но ничего существенного кроме этого не делает:

Чтобы поведение региона могло вычитать из вида информацию о том, какому модулю этот вид принадлежит, применим к классу вида такой же атрибут:

[IdentifiableModule("A52773FA-1579-4D54-B4FC-EFEB7BE65EA8")]
public partial class View1 : UserControl
{


И, наконец, дирижер всей этой песни — поведение региона ModuleActiveAwareBehavior:

public class ModuleActiveAwareBehavior : RegionBehavior
{
    public ModuleActiveAwareBehavior(IUnityContainer container)
    {
        Container = container;
    }

    public IUnityContainer Container { get; private set; }

    protected override void OnAttach()
    {
        Region.ActiveViews.CollectionChanged += new NotifyCollectionChangedEventHandler(ActiveViews_CollectionChanged);
    }

    void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        var activeModules = Region.ActiveViews
            .Where(view => view.GetType().GetCustomAttributes(typeof(IdentifiableModuleAttribute), false).Count() > 0)
            .Select(view => Container.Resolve<IModule>(((IdentifiableModuleAttribute[])
                view.GetType().GetCustomAttributes(typeof(IdentifiableModuleAttribute), false))[0].ModuleId))
                    .OfType<IActiveAware>();
            
        var inactiveModules = Container.ResolveAll<IModule>().OfType<IActiveAware>().Except(activeModules).ToArray();
            
        foreach (var activeModule in activeModules)
        {
            activeModule.IsActive = true;
        }

        foreach (var activeModule in inactiveModules)
        {
            activeModule.IsActive = false;
        }
    }
}


Данное поведение внедряется в архитектуру приложения на стадии Bootstrapper'а в соответствующем методе:

protected override IRegionBehaviorFactory ConfigureDefaultRegionBehaviors()
{
    var regionBehaviorFactory = base.ConfigureDefaultRegionBehaviors();

    regionBehaviorFactory.AddIfMissing("ModuleActiveAwareBehavior", typeof(ModuleActiveAwareBehavior));

    return regionBehaviorFactory;
}


Все. Демонстрационный проект можно скачать здесь.
Re[2]: Prism событие активации View модуля?
От: Glas  
Дата: 12.06.11 12:35
Оценка:
Здравствуйте, Fortnum, Вы писали:

F>Внутренних средств у Prism для этого нет — надо городить самому.


F>Очевидно, что сам класс модуля должен реализовать, например, тот же IActiveAware. Теперь вопрос, как изменения активности вида должны "просачиваться" к модулям? Активностью видов управляют в Prism'е регионы (IRegion). Но ни регионы, ни даже виды, ничего не знают о модулях. Вообще, по умолчанию в Prism'е не существует реестра загруженных модулей. А следовательно такой реестр надо придумать и создать.


Сделал, проще. Реализовал IActiveAware в классе View. Теперь гораздо проще сообщить ViewModel, что ее View активен.
Re[3]: Prism событие активации View модуля?
От: Fortnum  
Дата: 12.06.11 20:12
Оценка:
Здравствуйте, Glas, Вы писали:

G>Сделал, проще. Реализовал IActiveAware в классе View. Теперь гораздо проще сообщить ViewModel, что ее View активен.


Можно еще проще — реализовать IActiveView не во View, а сразу и прямо во ViewModel Только вопрос стоял иначе: "Хотелось бы после этого сказать модулю, что он активен".
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.