Импорт MEF
37C# и .NET --- Основы .NET --- Импорт MEF
Теперь давайте приступим к использованию пользовательских элементов управления WPF и приложения-хоста WPF. Представление визуального конструктора приложения-хоста показано на рисунке:
<Window x:Class="Wrox.ProCSharp.MEF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wrox.ProCSharp.MEF"
Title="Calculator" Height="240" Width="500">
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close" Executed="OnClose" />
<CommandBinding Command="local:CalculatorCommands.RefreshExtensions" Executed="OnRefreshExtensions" />
<CommandBinding Command="local:CalculatorCommands.GetExports" Executed="OnGetExports" />
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="4*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Menu Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
<MenuItem Header="_File">
<MenuItem Header="E_xit" Command="ApplicationCommands.Close" />
</MenuItem>
<MenuItem x:Name="menuAddins" Header="_AddIns">
</MenuItem>
<MenuItem Header="Manage">
<MenuItem Header="_Refresh Extensions" Command="local:CalculatorCommands.RefreshExtensions" />
<MenuItem Header="Get _Exports" Command="local:CalculatorCommands.GetExports" />
</MenuItem>
</Menu>
<Grid Grid.Row="1" Grid.Column="0" Margin="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="3" Button.Click="OnNumberClick">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="7" Margin="1" />
<Button Grid.Row="0" Grid.Column="1" Content="8" Margin="1" />
<Button Grid.Row="0" Grid.Column="2" Content="9" Margin="1" />
<Button Grid.Row="1" Grid.Column="0" Content="4" Margin="1" />
<Button Grid.Row="1" Grid.Column="1" Content="5" Margin="1" />
<Button Grid.Row="1" Grid.Column="2" Content="6" Margin="1" />
<Button Grid.Row="2" Grid.Column="0" Content="1" Margin="1" />
<Button Grid.Row="2" Grid.Column="1" Content="2" Margin="1" />
<Button Grid.Row="2" Grid.Column="2" Content="3" Margin="1" />
<Button Grid.Row="3" Grid.Column="0" Content="0" Grid.ColumnSpan="2" Margin="1" />
<Button Grid.Row="3" Grid.Column="2" Content="." Margin="1" />
</Grid>
<ListBox Grid.Row="0" Grid.Column="1" x:Name="listOperators">
</ListBox>
<TextBlock x:Name="textInput" Grid.Row="1" Grid.Column="1" />
<TextBlock x:Name="textResult" Grid.Row="2" Grid.Column="1" />
<Button Click="InvokeOperation" Content="Calculate" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="3" Margin="5" />
</Grid>
<GridSplitter Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" VerticalAlignment="Stretch" Width="5" ShowsPreview="True" />
<TabControl x:Name="tabExtensions" Grid.Row="1" Grid.Column="1" Margin="2">
</TabControl>
<ScrollViewer Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2">
<TextBlock ScrollViewer.VerticalScrollBarVisibility="Auto" x:Name="textStatus" />
</ScrollViewer>
</Grid>
</Window>
WPFCalculator — это приложение WPF, которое загружает функциональное дополнение-калькулятор, реализующее интерфейсы ICalculator и IOperation, а также дополнения с пользовательским интерфейсом, которые реализуют интерфейс ICalculatorExtension. Чтобы подключиться к экспортам частей, нужны импорты.
Импорт подключается к экспорту. При использовании экспортированных частей необходим импорт для установки соединения. С помощью атрибута Import можно подключиться к одному экспорту. Если должно загружаться более одного дополнения, необходим атрибут ImportMany, и он должен быть определен как массив типа IEnumerable<T>. Поскольку приложение-хост калькулятора допускает загрузку многих расширений калькуляторов, реализующих интерфейс ICalculatorExtension, в классе CalculatorExtensionImport определено свойство CalculatorExtensions типа IEnumerable<ICalculatorExtension> для доступа ко всем частям — расширениям калькуляторов:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Windows.Controls;
namespace MEF
{
public class CalculatorExtensionImport : IPartImportsSatisfiedNotification
{
public event EventHandler<ImportEventArgs> ImportsSatisfied;
[ImportMany(AllowRecomposition=true)]
public IEnumerable<ICalculatorExtension> CalculatorExtensions { get; set; }
public void OnImportsSatisfied()
{
if (ImportsSatisfied != null)
ImportsSatisfied(this, new ImportEventArgs { StatusMessage = "ICalculatorExtension imports successful" });
}
}
}
Атрибуты Import и ImportMany позволяют использовать ContractName и ContractType для отображения импорта на экспорт. Другие свойства, которые могут быть установлены с этими атрибутами — это AllowRecomposition и RequiredCreationPolicy.
Свойство AllowRecomposition допускает динамическое отображение на новые экспорты во время работы приложения, а также динамическую выгрузку экспортов. С помощью RequiredCreationPolicy можно выбирать, должна часть разделяться между запрашивающими сторонами (CreationPolicy.Shared) или нет(CreationPolicy.Nonshared), либо же политика в этом отношении определяется контейнером (CreationPolicy.Any).
Чтобы удостовериться, что все импорты успешны, можно реализовать интерфейс IPartImportsSatisfiedNotification.
Интерфейс определяет единственный метод — OnImportsSatifsifed(), который вызывается, когда все импорты класса успешны. В классе CalculatorImport метод инициирует событие ImportsSatisfied.
Событие CalculatorImport подключается к обработчику события при создании Calculatorlmport для записи сообщения в элемент TextBlock, который отображает информацию о состоянии:
private void InitializeContainer()
{
catalog = new DirectoryCatalog(Properties.Settings.Default.AddInDirectory);
catalog.Changed += (sender, e) =>
{
this.textStatus.Text += sb.ToString();
};
container = new CompositionContainer(catalog);
InitializeOperations();
}
По умолчанию части загружаются из контейнера, например, путем вызова расширяющего метода ComposeParts() класса CompositionContainer. С помощью класса Lazy<T> части могут быть загружены при первом к ним обращении. Тип Lazy<T> позволяет выполнять позднее создание экземпляров любого типа T и определяет свойства IsValueCreated и Value. Свойство IsValueCreated имеет булевский тип и возвращает информацию о том, создан ли уже внутренний экземпляр типа Т. Свойство Value инициализирует вложенный тип T при первом обращении и возвращает его экземпляр.
Импорт дополнений может быть объявлен, как относящийся к типу Lazy<T>, что показано на примере Lazy<ICalculator>:
[Import(typeof(ICalculator))]
public Lazy<ICalculator> Calculator { get; set; }
Вызов импортированных средств также требует некоторых изменений в доступе к свойству Value типа Lazy<T>. Переменная calcImport имеет тип CalculatorImport. Свойство Calculator возвращает Lazy<ICalculator>. Свойство Value отложенным образом создает экземпляр импортированного типа и возвращает интерфейс ICalculator, на котором может быть вызван метод GetOperations() для получения всех поддерживаемых дополнением калькулятора операций:
private void InitializeOperations()
{
Contract.Requires(calcImport != null);
Contract.Requires(calcImport.Calculator != null);
var operators = calcImport.Calculator.Value.GetOperations();
foreach (var op in operators)
{
var b = new Button();
b.Width = 40;
b.Height = 30;
b.Content = op.Name;
b.Margin = new Thickness(2);
b.Padding = new Thickness(4);
b.Tag = op;
b.Click += new RoutedEventHandler(DefineOperation);
listOperators.Items.Add(b);
}
}