canard29 Ответов: 3

Пользовательских элементов управления нагрузкой путем отражения


Привет,

Я бы хотел загрузить Usercontrol путем отражения с помощью функции "Show" в моем интерфейсе следующим образом :
void Show();


Код для интерфейса и загрузки в порядке.

Есть идеи ? Спасибо Вам за вашу большую поддержку


Дополнительный вопрос :
Я хотел бы запустить функцию в плагине. Например, в IView реализована функция "void LoadSettings ()".
В этом списке выбранный объект имеет тип "
IUIViewProviderBase
Как "дотянуться" до объекта IView ?

Что я уже пробовал:

На данный момент функция show возвращает объект UserControl для отображения, а userControl отображается в основном приложении.
UserControl Show();


Он работает хорошо, но клиент не хочет, чтобы основное приложение видело объект UserControl


Интерфейс выглядит примерно так :

namespace MyApp
{
    public interface IPlugins
    {
        /// <summary>
        /// Function called to receive the UserControl to display
        /// </summary>
        /// <returns>USerControl object to display</returns>
        UserControl Show();

    }
}


Код для загрузки плагина таков :
Assembly dll = Assembly.LoadFrom(dllPath); 

            //Check all classes implemented in the DLL
            foreach (Type type in dll.GetTypes())
            {
                Type[] interfaces = type.GetInterfaces();
                //Check in the class implement an interface IPlugins
                if (interfaces.Contains(typeof(IPlugins)))
                {
                    //Create an instance of the class
                    (IPlugins) iPlugin = (IPlugins)Activator.CreateInstance(type);
                    ...
                }
            }


В XAML я показываю UserControl с помощью этого :
<Grid Grid.Column="1" Name="UC_Grid" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">

        </Grid>


а в коде :
UC_Grid.Children.Add(myIPlugin.Control); // myIPlugin.Control is the UserControl in the plugin

Gerry Schmitz

Проверьте "визуальное дерево" в отладке.

Нужно подтвердить видимость, высоту и т.д.

(Предполагая, что вы действительно добавили UC).

3 Ответов

Рейтинг:
25

Graeme_Grant

Как рекомендовал Йоханнес Нестлер, мне нравится использовать Меф[^] для таких приложений, как эти.

Это будет длинный ответ, но держитесь, это того стоит...

Я сделал для вас пару примеров, чтобы у вас было представление о том, как вы могли бы сделать это с помощью MEF:
1. консольное решение - это демонстрирует, как может работать MEF;
2. В WPF приложение с модулями - это расширяет на примере консоли с автозагрузкой модулей

Во-первых, вот простой пример консольного приложения, который показывает, как работает MEF:

internal static class Program
{
    private static void Main()
    {
        Mef.Initialize();
        var process = new Process();
        process.Service?.Print();
        Console.ReadKey();
    }
}

class Process
{
    public Process()
    {
        Mef.ComposeParts(this);
    }

    [Import]
    public DummyService Service { get; set; }

}

[Export]
class DummyService
{
    public void Print()
    {
        Console.WriteLine("Running Service...");
    }
}

Меф делает всю тяжелую работу за вас. [Export] имеет соответствующий Import и разве это волшебство, когда ты просишь его об этом: Mef.ComposeParts(this);. Вот (сокращенный) класс, который я использую для обертывания MEF:
public static class Mef
{
    private static object lockObj = new object();
    private static CompositionContainer container;
    private static AggregateCatalog catalog;

    public static void Initialize()
    {
        catalog = new AggregateCatalog();
        container = new CompositionContainer(catalog);
    }

    public static void ComposeParts(params object[] attributedParts)
    {
        lock (lockObj)
            container.ComposeParts(attributedParts);
    }
}

То Initialize() метод сшивает все импортные и экспортные операции вместе.

Теперь, для плагинов WPF и погрузки/показывать элементы управления UserControl, немного больше работы требуется. Раствор состоит из 3 частей:
1. Общий плагин contact Dll
2. основное приложение WPF
3. модули, содержащие элементы управления пользователя

Общий плагин состоит из:
1. The MEF вспомогательный класс
2. А UIProvider - метаданные, описывающие плагин, используемый основным приложением
3. В IView интерфейс - используется для привязки UserControl к UIProvider
4. В ExportView атрибут - для именования уникальных экспортируемых IViews

Пересмотренная версия MEF класс, который находит модули плагинов и загружает их в реальном времени:
public static class Mef
{
    private static object lockObj = new object();

    private static CompositionContainer container;

    private static AggregateCatalog catalog;

    private static void Initialize()
    {
        catalog = new AggregateCatalog();
        catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.exe"));
        catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.dll"));
        container = new CompositionContainer(catalog);
    }

    public static void Initialize(bool? isRecomposable = true)
    {
        Initialize();
        if (isRecomposable == true)
            StartWatch();
    }

    public static void Initialize<T>(T attributedPart, bool isRecomposable)
    {
        Initialize(isRecomposable);
        if (isRecomposable)
            ComposeParts(attributedPart);
        else
            lock (lockObj)
                container.SatisfyImportsOnce(attributedPart);
    }

    public static void ComposeParts(params object[] attributedParts)
    {
        lock (lockObj)
            container.ComposeParts(attributedParts);
    }

    private static void StartWatch()
    {
        var watcher = new FileSystemWatcher() { Path = ".", NotifyFilter = NotifyFilters.LastWrite };
        watcher.Changed += (s, e) =>
        {
            string lName = e.Name.ToLower();
            if (lName.EndsWith(".dll") || lName.EndsWith(".exe"))
                Refresh();
        };
        watcher.EnableRaisingEvents = true;
    }

    public static void Refresh()
    {
        DispatcherHelper.CheckBeginInvokeOnUI(() =>
        {
            foreach (DirectoryCatalog dCatalog in catalog.Catalogs)
                dCatalog.Refresh();
        });
    }
}

// Helper class for dispatcher operations on the UI thread...
// (Abridged version)
// full version: https://github.com/lbugnion/mvvmlight/blob/master/GalaSoft.MvvmLight/GalaSoft.MvvmLight.Platform%20(NET45)/Threading/DispatcherHelper.cs
public static class DispatcherHelper
{
    public static Dispatcher UIDispatcher
    {
        get;
        private set;
    }

    public static void CheckBeginInvokeOnUI(Action action)
    {
        if (action == null)
            return;

        CheckDispatcher();

        if (UIDispatcher.CheckAccess())
            action();
        else
            UIDispatcher.BeginInvoke(action);
    }

    private static void CheckDispatcher()
    {
        if (UIDispatcher == null)
        {
            var error = new StringBuilder("The DispatcherHelper is not initialized.");
            error.AppendLine();
            error.Append("Call DispatcherHelper.Initialize() in the static App constructor.");
            throw new InvalidOperationException(error.ToString());
        }
    }

    public static void Initialize()
    {
        if (UIDispatcher != null
            && UIDispatcher.Thread.IsAlive)
            return;

        UIDispatcher = Dispatcher.CurrentDispatcher;
    }
}

То DispatcherHelper используется для того, чтобы избежать каких-либо проблем с перекрестным тредингом.

Теперь базовый UIProvider. Это позволяет использовать несколько типов, таких как UserControl и Page типы и описание каждого плагина для хостинга приложения:
public interface IUIProviderBase
{
    string Key { get; }
    string Title { get; }
}

public abstract class UIProviderBase : IUIProviderBase
{
    public string Key { get; set; }
    public string Title { get; set; }
}

И основание UIViewProviderBase реализация метода представления пользовательского элемента управления, интерфейс, маркер IView, и обычай Export атрибут для UserControl:
public interface IUIViewProviderBase : IUIProviderBase
{
    ExportFactory<IView> Entry { get; set; }
}

public abstract class UIViewProviderBase : UIProviderBase, IUIViewProviderBase
{
    public abstract ExportFactory<IView> Entry { get; set; }
}

public interface IView
{
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ExportViewAttribute : ExportAttribute
{
    public ExportViewAttribute(string contractName)
        : base(contractName, typeof(IView))
    {
    }
}

Теперь мы можем создавать автономные модули плагинов DLL/EXE. Модуль плагина должен ссылаться на Общий плагин contact Dll выше. Библиотека DLL должна быть либо типом проекта WPF UserControl, либо типом проекта Wpf Custom Control.

Вот один пример плагина. Сделайте столько, сколько вы хотите проверить...

Первый плагин UIProvider, чтобы описать плагин с фабричный метод для представления (пользовательского элемента управления):
[Export(typeof(IUIViewProviderBase))]
public class UIProvider : UIViewProviderBase
{
    public UIProvider()
    {
        Key = "PluginA";
        Title = "My Plugin A";
    }

    [Import("MyViewA")]
    public override ExportFactory<IView> Entry { get; set; }
}

Теперь вид плагина (пользовательский элемент управления) сама:
[ExportView("MyViewA")]
public partial class View : UserControl, IView
{
    public View()
    {
        InitializeComponent();
    }
}

А вот макет XAML для целей тестирования:
<UserControl x:Class="PluginA.View"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 

             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 

             xmlns:local="clr-namespace:PluginA"

             mc:Ignorable="d" 

             d:DesignHeight="450" d:DesignWidth="800">
    <Viewbox>
        <TextBlock Text="ViewA"/>
    </Viewbox>
</UserControl>

Наконец, хост-приложение WPF. Мне нравится использовать шаблон проектирования MVVM, но код позади него может работать так же хорошо. Убедитесь, что вы также добавили ссылку на Общий плагин contact Dll выше.

Сначала ViewModel для размещения плагинов для основного вида/окна:
public class MainViewModel : IPartImportsSatisfiedNotification
{
    public MainViewModel()
    {
        Mef.Initialize(this, isRecomposable: true);
    }

    [ImportMany(typeof(IUIViewProviderBase), AllowRecomposition = true)]
    public ObservableCollection<IUIViewProviderBase> Plugins { get; set; }

    public void OnImportsSatisfied()
    {
        // uncomment if you need to do something when plugin(s) is/are loaded
        //Debugger.Break();
    }
}

Нам нужен ... UIExportViewConverter класс для загрузки представления плагина (UserControl)
class UIExportViewConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return ((ExportFactory<IView>) value).CreateExport().Value;
    }

    public object ConvertBack(object value, Type targetType, obj


canard29

Спасибо Вам за ваш ответ !! Я сейчас над этим работаю

canard29

Прежде всего я хочу поблагодарить @Graeme_Grant за его великолепную поддержку в этой статье

Я скопировал код, добавил ссылку, и все работает хорошо !
У меня есть еще один вопрос. Я хочу иметь доступ к выбранному элементу списка (IView) для вызова функции внутри него.

Что я уже пробовал:

В MainView.язык XAML

<ListBox Grid.Row="1" Margin="10" Height="100"
Name="PluginList"
ItemsSource="{Привязка Плагинов}"
ItemTemplate="{StaticResource DetailsTemplate}"
SelectionChanged="ListBox_SelectionChanged"/>


в MainWindow.язык XAML.в CS

частная ListBox_SelectionChanged недействительным(объект отправителя, SelectionChangedEventArgs е)
{
IUIViewProviderBase selectedPlugin = PluginList.SelectedItem as IUIViewProviderBase;
ExportFactory<iview> Iviews = selectedPlugin.Вход;
IView item = Iviews.CreateExport().Значение как руки;

}


Проблема заключается в последней строке, CreateExport создает новый экземпляр Iview...Я не достигаю существующего экземпляра.

Graeme_Grant

Это другой вопрос по сравнению с исходной проблемой и требует, чтобы был задан новый вопрос.

То, что вы пытаетесь сделать, нарушает все правила, когда речь идет о плагинах. Хост не должен знать внутреннюю работу плагина, так как они должны быть слабо связаны.

Но быстро, у вас есть два варианта для обмена данными:
1. Используйте службу обмена сообщениями, подобную той, что находится в MVVMLight;
2. Продолжать использовать MEF позволяет обмениваться данными как у нас выше.

Рейтинг:
2

canard29

Образец работает хорошо, и он интегрирован в мое приложение. Я многому научился с вашим очень хорошим образцом. Я пытаюсь решить 2 вопроса, не очень связанных с Меф :
- UserControl отображается в верхнем левом углу. Я бы хотел отобразить его в центре ContentControl
- Если usercontrol больше размера приложения, полосы прокрутки не отображаются.

Я попытался исправить эти проблемы с помощью XAML, но пока не нашел решения, но если у вас есть несколько советов...


Nelek

Пожалуйста, прекратите публиковать "решения", чтобы общаться с людьми. Если вы хотите им что-то сказать, используйте кнопку "Задать вопрос или комментарий" под каждым решением или кнопку "ответить" под каждым уже существующим комментарием. Таким образом, они получат уведомление о том, что вы им ответили, и вероятность того, что их прочтут, намного больше.

Nelek

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

Рейтинг:
1

johannesnestler

Взгляните на MEF - это может быть именно то, что вам нужно...
Управляемых (MEF) | Майкрософт документы[^]


Graeme_Grant

Опереди меня ... ему бы помог пример ... Я добавил один ниже.