Контейнеры и поставщики экспорта MEF

31

Импорт частей осуществляется с помощью контейнеров. Типы хостинг-частей определены в пространстве имен System.ComponentModel.Composition.Hosting. Класс CompositionContainer — это контейнер частей. В конструкторе этого класса может быть присвоено несколько объектов ExportProvider, как и ComposablePartCatalog.

Каталоги — это источники частей, которые обсуждаются в следующей статье. Поставщики экспорта позволяют обращаться ко всем экспортам программно, с помощью перегруженных методов GetExport<T>(). Поставщик экспорта используется для доступа к каталогу, а сам CompositionContainer является поставщиком экспорта. Это позволяет вкладывать контейнеры друг в друга.

Части загружаются при вызове метода Compose() (если только не выбрана отложенная загрузка). До сих пор всегда применялся метод ComposePart(), как показано в следующем методеInitializeContainer():

container.Compose(calcImport);

Расширяющий метод ComposePart() определен классом AttributedModelServices. Этот класс предоставляет методы, которые используют атрибуты и рефлексию .NET для доступа к информации части и добавления частей к контейнеру. Вместо метода расширения может применяться метод Compose() класса CompositionContainer. Метод Compose() работает с классом CompositionBatch. Класс CompostionBatch может быть использован для определения того, какие части должны быть удалены из контейнера.

Методы AddPart() и RemovePart() имеют перегрузки, позволяющие добавлять снабженную атрибутами часть (calcImport — экземпляр класса CalculatorImport, имеющего атрибуты Import) либо часть, унаследованную от базового класса ComposablePart:

var batch = new CompositionBatch();
batch.AddPart(calcImport);
container.Compose(batch);

Приложение-хост WPFCalculator загружает два разных вида частей. Первая часть реализует интерфейс ICalculator и загружается в методе InitialiseContainer, который показан ранее. Части, реализующие интерфейс ICalculatorExtension, загружаются в методе RefreshExtensions. Метод Container.ComposeParts() получает импорт из класса CalculatorExtensionImport и подключает его ко всем доступным импортам из каталога.

Поскольку здесь динамически загружается несколько дополнений (пользователь может выбрать нужное), элемент управления Menu динамически расширяется. Приложение выполняет итерацию по всем расширениям калькулятора и для каждого создается экземпляр MenuItem. Заголовок элемента управления MenuItem содержит элемент Label со свойством Title расширения и CheckBox. Элемент CheckBox позднее будет применяться для динамического удаления экспорта.

Свойство ToolTip класса MenuItem устанавливается равным свойству Description расширяющего элемента управления. Свойство Tag устанавливается в сам элемент-расширитель, что облегчает получение элемента-расширителя при выборе пункта меню. Событие Click элемента MenuItem устанавливается в метод ShowAddIn(). Этот метод вызывает метод GetUI() расширяющего элемента управления для отображения элемента внутри нового TabItem контейнера TabControl:

private void RefreshExensions() 
        {
            catalog.Refresh();
            calcExtensionImport = new CalculatorExtensionImport();
            calcExtensionImport.ImportsSatisfied += (sender, e) =>
                {
                    this.textStatus.Text += String.Format("{0}\n", e.StatusMessage);
                };


            container.ComposeParts(calcExtensionImport);
            menuAddins.Items.Clear();
            foreach (var extension in calcExtensionImport.CalculatorExtensions)
            {
                var menuItemHeader = new StackPanel { Orientation = Orientation.Horizontal };
                menuItemHeader.Children.Add(new Label { Content = extension.Title });
                var menuCheck = new CheckBox { IsChecked = true };
                menuCheck.Unchecked += (sender1, e1) =>
                    {
                        MenuItem mi = (sender1 as CheckBox).Tag as MenuItem;
                        ICalculatorExtension ext = mi.Tag as ICalculatorExtension;
                        ComposablePart part = AttributedModelServices.CreatePart(ext);
                        var batch = new CompositionBatch();
                        batch.RemovePart(part);
                        container.Compose(batch);
                        MenuItem parentMenu = mi.Parent as MenuItem;    
                        parentMenu.Items.Remove(mi);
                    };
                menuItemHeader.Children.Add(menuCheck);
                
                var menuItem = new MenuItem { Header = menuItemHeader, ToolTip = extension.Description, Tag = extension };
                menuCheck.Tag = menuItem;
                menuItem.Click += ShowAddIn;
                menuAddins.Items.Add(menuItem);
            }        
        }

        private void ShowAddIn(object sender, RoutedEventArgs e)
        {
            var mi = e.Source as MenuItem;
            var ext = mi.Tag as ICalculatorExtension;
            FrameworkElement uiControl = ext.GetUI();
            var headerPanel = new StackPanel { Orientation = Orientation.Horizontal };
            headerPanel.Children.Add(new Label { Content = ext.Title });
            var closeButton = new Button { Content = "X" };
            var ti = new TabItem { Header = headerPanel, Content = uiControl };
            closeButton.Click += delegate
                {
                    tabExtensions.Items.Remove(ti);
                };
            headerPanel.Children.Add(closeButton);

            tabExtensions.SelectedIndex = tabExtensions.Items.Add(ti);
        }

Для динамического удаления экспорта устанавливается событие Unchecked в элементе управления CheckBox, расположенного внутри заголовка MenuItem. Экземпляр ICalculatorExtension доступен через свойство Tag элемента управления MenuItem, потому что он был установлен в предыдущем фрагменте кода. Чтобы удалить часть из контейнера, должен быть создан объект CompositeBatch, содержащий части для удаления. Метод RemovePart() класса CompositeBatch принимает объект ComposablePart; не предусмотрено перегрузки для объекта с атрибутами. С помощью класса AttributedModelServices из объекта с атрибутами методом CreatePart() может быть создан экземпляр ComposablePart. Объект CompositeBatch затем передается в метод Compose() экземпляра CompositionContainer для удаления из него части.

Пройди тесты
Лучший чат для C# программистов