分享非常漂亮的WPF界面框架源码及其实现原理

简介: 原文 http://www.cnblogs.com/baihmpgy/archive/2013/05/09/3068370.html 在上文《分享一个非常漂亮的WPF界面框架http://www.cnblogs.com/baihmpgy/archive/2013/05/06/3062220.html》中我简单的介绍了一个界面框架,有朋友已经指出了,这个界面框架是基于ModernUI来实现的,在该文我将分享所有的源码,并详细描述如何基于ModernUI来构造一个非常通用的、插件化的WPF开发框架。

原文 http://www.cnblogs.com/baihmpgy/archive/2013/05/09/3068370.html

在上文《分享一个非常漂亮的WPF界面框架http://www.cnblogs.com/baihmpgy/archive/2013/05/06/3062220.html》中我简单的介绍了一个界面框架,有朋友已经指出了,这个界面框架是基于ModernUI来实现的,在该文我将分享所有的源码,并详细描述如何基于ModernUI来构造一个非常通用的、插件化的WPF开发框架。下载源码的同志,希望点击一下推荐。

 

本文将按照以下四点来介绍:

(1)ModernUI简介;

(2)构建通用界面框架的思路;

(3)基于ModernUI和OSGi.NET的插件化界面框架实现原理及源码分析;

(4)其它更有趣的东西~~。

 

1 ModernUI简介

ModernUI(http://mui.codeplex.com/)是一个开源的WPF界面库,利用该界面库,我们可以创建很酷的应用程序。下面是ModernUI官方示例,你可以从官方网站直接下载源码运行,如果是.NET 4.0的话,记得要声明“NET4”预编译变量,否则无法编译通过。

mui.sample

要编写这样的WPF界面,我们需要在一个Window上声明菜单和Tab页面,下图是定义菜单的声明。

image

此外,每一个Tab风格页面,你也需要手动的为菜单创建这样的界面元素。

 

直接用这样的方式来使用ModernUI,显然不太适合团队协作性的并行开发,因为在一个团队的协作中,不同的人需要完成不同的功能,实现不同页面,每个人都需要来更改主界面。

 

我非常希望模块化的开发方法,因为这可以尽可能的复用现有资产,使程序员可以聚焦在自己关注的业务逻辑上,不需要关心UI的使用。下面,我将来描述基于ModernUI实现的一个通用界面框架,这个界面框架允许程序员在自己的业务模块中配置需要显示的界面元素。

 

2 通用界面框架实现思路

我希望能够实现这样的通用界面框架:

(1)程序员可以直接实现需要展现业务逻辑的界面,不需要关注如何使用ModernUI;

(2)程序员可以通过简单的配置就可以将自己实现的业务逻辑页面显示在主界面中;

(3)这个界面框架可以完全复用。

 

当我看到ModernUI这个界面库时,我希望将应用程序做成模块化,每一个模块能够:

(1)通过以下配置能够直接显示二级菜单。

image

(2)通过以下配置能够直接显示三级菜单。

image

这样做的好处是,开发插件的时候可以不需要关心界面框架插件;团队在协作开发应用的时候,可以独立开发并不需要修改主界面;团队成员的插件可以随时集成到这个主界面;当主界面无法满足我们的布局时或者用户需求无法满足时,可以直接替换主界面框架而不需要修改任何插件代码。

 

最终的效果如下,以下界面的几个菜单及点击菜单显示的内容由DemoPlugin插件、DemoPlugin2插件来提供。当插件框架加载更多插件时,界面上会出现更多的菜单;反之,当插件被卸载或者被停止时,则相应的菜单将消失掉。

image       image

下面我来介绍如何实现。

 

3 基于ModernUI和OSGi.NET的插件化界面框架实现原理及源码分析

 

3.1 OSGi.NET插件框架原理简介

OSGi.NET框架是一个完全通用的.NET插件框架,它支持WPF、WinForm、ASP.NET、ASP.NET MVC 3.0/4.0、控制台等任意.NET应用程序,也就是说,你可以基于该插件框架来快速构架插件化的应用程序。OSGi.NET插件框架提供了插件化支 持、插件扩展和面向服务支持三大功能。

 

OSGi.NET插件框架启动时,从插件目录中搜索插件,安装并启动这些插件,将这些插件组装在插件框架中;一个插件可以暴露扩展点,允许其它插件在不更改其代码情况下,扩展该插件的功能;插件间可以通过服务来进行通讯。

 

在一个插件应用程序中,它首先要获取一个入口点,这个入口点由一个插件来提供,然后进入这个插件的入口并运行起来。一个提供入口的插件通常是一个主 界面插件,比如上面介绍的这个WPF界面框架。也就是说,插件应用程序启动起来后,会先运行这个界面框架的主界面。而主界面一般都提供了关于界面元素的扩 展,允许其它插件将菜单、导航和内容页面注册到主界面,因此,当主界面运行时,它会将其它插件注册的界面元素显示出来。当用户点击界面元素时,插件框架就 会加载这个插件的页面,某个插件的页面在呈现时,则有可能会从数据库中提取数据展示,这时候,该插件则可能会调用数据访问服务提供的通用数据访问接口。 OSGi.NET提供的三大功能,刚好能够非常的吻合这样的系统的启动形式。当然,OSGi.NET除了提供插件三大支撑功能之外,它还支持插件动态性与 隔离性。动态性,意味着我们可以在运行时来动态安装、启动、停止、卸载和更新插件,而隔离性则意味着每一个插件都拥有自己独立的目录,有自己独立的类型加 载器和类型空间。

 

基于OSGi.NET插件框架,我们很容易实现插件的动态安装、远程管理、自动化部署、自动升级和应用商店。下面,我来描述如何使用OSGi.NET来构建一个WPF插件应用。

 

3.2 基于OSGi.NET来实现WPF插件应用

利用OSGi.NET来创建一个WPF插件应用非常的简单。只需要实现:(1)创建一个插件主程序,定义插件目录;(2)在主程序中利用 BootStrapper实现OSGi.NET内核升级检测与自动升级;(3)启动插件框架;(4)利用PageFlowService获取主界面,然后 运行主界面。下面我们看一下插件主程序。(注:如果你安装了OSGi.NET框架,可以直接使用项目模板来创建WPF主程序项目。)

image

在这个主程序,我们在项目的属性将输出路径改为bin,并在bin目录下创建一个Plugins目录,然后将OSGi.NET四个标准插件拷贝到 Plugins目录,它们分别用于:(1)插件远程管理,即RemotingManagement和WebServiceWrapperService, 支持远程管理控制台调试用;(2)插件管理服务,即UIShell.BundleManagementService,支持对本地插件管理和插件仓库访问 与下载;(3)页面流服务,即UIShell.PageFlowService,用于获取主界面。

 

下面我们来看一下App.xaml.cs源码,在这里实现了插件加载、启动和进入主界面的功能。

复制代码
namespace UIShell.iOpenWorks.WPF
{
    /// <summary>
    /// WPF startup class.
    /// </summary>
    public partial class App : Application
    {
        // Use object type to avoid load UIShell.OSGi.dll before update.
        private object _bundleRuntime;

        public App()
        {
            UpdateCore();
            StartBundleRuntime();
        }

        void UpdateCore() // Update Core Files, including BundleRepositoryOpenAPI, PageFlowService and OSGi Core assemblies.
        {
            if (AutoUpdateCoreFiles)
            {
                new CoreFileUpdater().UpdateCoreFiles(CoreFileUpdateCheckType.Daily);
            }
        }

        void StartBundleRuntime() // Start OSGi Core.
        {
            var bundleRuntime = new BundleRuntime();
            bundleRuntime.AddService<Application>(this);
            bundleRuntime.Start();

            Startup += App_Startup;
            Exit += App_Exit;
            _bundleRuntime = bundleRuntime;
        }

        void App_Startup(object sender, StartupEventArgs e)
        {
            Application app = Application.Current;
            var bundleRuntime = _bundleRuntime as BundleRuntime;
            app.ShutdownMode = ShutdownMode.OnLastWindowClose;

            #region Get the main window
            var pageFlowService = bundleRuntime.GetFirstOrDefaultService<IPageFlowService>();
            if (pageFlowService == null)
            {
                throw new Exception("The page flow service is not installed.");
            }

            if (pageFlowService.FirstPageNode == null || string.IsNullOrEmpty(pageFlowService.FirstPageNode.Value))
            {
                throw new Exception("There is not first page node defined.");
            }

            var windowType = pageFlowService.FirstPageNodeOwner.LoadClass(pageFlowService.FirstPageNode.Value);
            if (windowType == null)
            {
                throw new Exception(string.Format("Can not load Window type '{0}' from Bundle '{1}'.", pageFlowService.FirstPageNode.Value, pageFlowService.FirstPageNodeOwner.SymbolicName));
            }

            app.MainWindow = System.Activator.CreateInstance(windowType) as Window;
            #endregion 

            app.MainWindow.Show();
        }

        void App_Exit(object sender, ExitEventArgs e)
        {
            if (_bundleRuntime != null)
            {
                var bundleRuntime = _bundleRuntime as BundleRuntime;
                bundleRuntime.Stop();
                _bundleRuntime = null;
            }
        }
        // Other codes
    }
}
复制代码

上述代码非常简单,我将介绍一下每一个函数的功能。

(1)构造函数:调用UpdateCore和StartBundleRuntime;

(2)UpdateCore:调用BootStrapper程序集的CoreFileUpdater来实现内核文件升级;

(3)StartBundleRuntime:创建一个BundleRuntime,即插件框架,BundleRuntime默认构造函数指定的插件目录为Plugins;启动BundleRuntime,即启动插件框架;挂载Startup和Exit事件;

(4)在App_Startup事件处理函数中,从插件框架获取PageFlowService服务,利用该服务获取主界面,然后创建该界面实例,并运行;

(5)在App_Exit事件处理函数中,终止插件框架,释放资源。

 

3.3 基于ModernUI实现通用界面插件框架

我在第2节描述了通用界面框架的思路。这个界面框架将基于OSGi.NET插件框架三大功能之一——插件扩展来实现。我将按照以下顺序来描述实现。

 

3.3.1 OSGi.NET插件扩展原理

下图是OSGi.NET插件扩展原理,在这里,需要暴露扩展点的插件暴露一个ExtensionPoint,提供扩展的插件则声明一个Extension(XML格式),如下所示。暴露扩展点的插件通过OSGi.NET框架获取所有Extension,然后对其进行处理。

插件框架原理

依据第2节描述,通用界面框架插件需要暴露扩展点和处理扩展。暴露扩展点意味着它需要定义界面扩展的格式。下面我来介绍扩展格式的XML定义。

 

3.3.2 界面扩展XML定义

根据界面框架要实现的功能,我们定义的扩展格式,如下所示。扩展点的名称为UIShell.WpfShellPlugin.LinkGroups。 通过LinkGroup来定义一级菜单,通过Link来定义叶子节点菜单,通过TabLink来定义三级菜单的Tab布局方式。

复制代码
<Extension Point="UIShell.WpfShellPlugin.LinkGroups">
  
  <LinkGroup DisplayName="一级菜单" DefaultContentSource="默认显示页面">
    <Link DisplayName="二级菜单" Source="二级菜单页面" />
    <TabLink DisplayName="三级菜单Tab布局" DefaultContentSource="默认页面" Layout="List/Tab">
      <Link DisplayName="三级菜单" Source="三级菜单页面" />
    </TabLink>
  </LinkGroup>
</Extension>
复制代码

界面框架插件需要做的就是获取这样的XML定义,并且自动在界面上将元素创建出来并自动加载插件提供的页面。下面我来介绍界面框架如何实现。

 

3.3.3 界面框架的实现

界面框架基于ModernUI来实现,它需要完成:(1)为Extension创建扩展模型;(2)获取所有扩展模型对象,并在主界面创建界面元素;(3)监听扩展变更事件,动态变更界面元素。

 

首先,我们来看看扩展模型的构建。在这里,定义了LinkGroupData、TabLinkData、LinkData分别对应于扩展的XML的元素。

image

这里的ShellExtensionPointHandler对象则用于同OSGi.NET框架扩展扩展信息,并将其转换成扩展对象模型,然后存储 在LinkGroups属性中。LinkGroups为ObservableCollection,当添加或者删除LinkGroup时会抛出 Add/Remov事件。下面来看一下这个类的代码。

复制代码
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Xml;
using UIShell.OSGi;

namespace UIShell.WpfShellPlugin.ExtensionModel
{
    public class ShellExtensionPointHandler
    {
        public const string ExtensionPointName = "UIShell.WpfShellPlugin.LinkGroups";

        public IBundle Bundle { get; private set; }
        public ObservableCollection<LinkGroupData> LinkGroups { get; private set; }

        public ShellExtensionPointHandler(IBundle bundle)
        {
            Bundle = bundle;
            InitExtensions();
            if (Bundle.Context != null)
            {
                Bundle.Context.ExtensionChanged += Context_ExtensionChanged;
            }
        }

        void InitExtensions() // Init
        {
            if (Bundle.Context == null)
            {
                return;
            }
            // Get all extensions.
            var extensions = Bundle.Context.GetExtensions(ExtensionPointName);
            LinkGroups = new ObservableCollection<LinkGroupData>();

            // Convert extensions to LinkGroupData collection.
            foreach (var extension in extensions)
            {
                AddExtension(extension);
            }
        }

        // Handle ExtensionChanged event.
        void Context_ExtensionChanged(object sender, ExtensionEventArgs e)
        {
            if (e.ExtensionPoint.Equals(ExtensionPointName))
            {
                // Create LinkGroupData objects for new Extension.
                if (e.Action == CollectionChangedAction.Add)
                {
                    AddExtension(e.Extension);
                }
                else // Remove LinkGroupData objects respond to the Extension.
                {
                    RemoveExtension(e.Extension);
                }
            }
        }

        // Convert Extension to LinkGroupData instances.
        void AddExtension(Extension extension)
        {
            LinkGroupData linkGroup;
            foreach (XmlNode node in extension.Data)
            {
                if (node is XmlComment)
                {
                    continue;
                }
                linkGroup = new LinkGroupData(extension);
                linkGroup.FromXml(node);
                LinkGroups.Add(linkGroup);
            }
        }
        // Remove LinkGroupData instances of the Extension.
        void RemoveExtension(Extension extension)
        {
            var toBeRemoved = new List<LinkGroupData>();
            foreach (var linkGroup in LinkGroups)
            {
                if (linkGroup.Extension.Equals(extension))
                {
                    toBeRemoved.Add(linkGroup);
                }
            }
            foreach (var linkGroup in toBeRemoved)
            {
                LinkGroups.Remove(linkGroup);
            }
        }
    }
}
复制代码

这个类有以下几个方法:

(1)InitExtensions:即从OSGi.NET框架获取已经注册的扩展信息,将其转换成LinkGroupData实例,并保存;

(2)Context_ExtensionChanged事件处理函数:即当Extension被添加或者删除时的处理函数,这在插件安装和卸载时 发生,我们需要将新建的Extension转换成LinkGroupData实例保存起来,需要已删除的Extension对应的 LinkGroupData实例移除掉。

 

那接下来我们来看一下主界面如何根据扩扎模型来创建或者删除界面元素。首先,你可以看到,这个主界面是空的没有预先定义任何的界面元素。

image

那你一定猜到了,这个界面肯定是通过代码来动态创建界面元素,我们来看看代码先。

复制代码
namespace UIShell.WpfShellPlugin
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : ModernWindow
    {
        public static ShellExtensionPointHandler ShellExtensionPointHandler { get; set; }
        private List<Tuple<LinkGroupData, LinkGroup>> LinkGroupTuples { get; set; }
        
        public MainWindow()
        {
            InitializeComponent();
            LinkGroupTuples = new List<Tuple<LinkGroupData, LinkGroup>>();
            ShellExtensionPointHandler = new ShellExtensionPointHandler(BundleActivator.Bundle);
            ShellExtensionPointHandler.LinkGroups.CollectionChanged += LinkGroups_CollectionChanged;
            InitializeLinkGroupsForExtensions();
        }

        void InitializeLinkGroupsForExtensions()
        {
            foreach (var linkGroupData in ShellExtensionPointHandler.LinkGroups)
            {
                CreateLinkGroupForData(linkGroupData);
            }

            // 设置第一个页面
            if (ShellExtensionPointHandler.LinkGroups.Count > 0)
            {
                var first = ShellExtensionPointHandler.LinkGroups[0];
                ContentSource = new Uri(first.FormatSource(first.DefaultContentSource), UriKind.RelativeOrAbsolute);
            }
        }

        void LinkGroups_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            Action action = () =>
            {
                if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
                {
                    // 新加了LinkGroupData
                    foreach (LinkGroupData item in e.NewItems)
                    {
                        CreateLinkGroupForData(item);
                    }
                }
                else if(e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
                {
                    // 删除了LinkGroupData
                    foreach (LinkGroupData item in e.OldItems)
                    {
                        RemoveLinkGroupForData(item);
                    }
                }
            };
            
            Dispatcher.Invoke(action);
        }

        void CreateLinkGroupForData(LinkGroupData linkGroupData)
        {
            var linkGroup = new LinkGroup { DisplayName = linkGroupData.DisplayName, GroupName = linkGroupData.GroupName };
            foreach (var linkData in linkGroupData.Links)
            {
                if (linkData is LinkData)
                {
                    
                    linkGroup.Links.Add(new Link { DisplayName = linkData.DisplayName, Source = new Uri(linkData.FormatSource((linkData as LinkData).Source), UriKind.RelativeOrAbsolute) });
                }
                else if (linkData is TabLinkData)
                {
                    linkGroup.Links.Add(new Link { DisplayName = linkData.DisplayName, Source = new Uri("UIShell.WpfShellPlugin@UIShell.WpfShellPlugin.Pages.ContentPlaceHolder?LinkId=" + linkData.LinkId.ToString(), UriKind.RelativeOrAbsolute) });
                }
            }
            if (linkGroupData.IsTitleLink)
            {
                TitleLinks.Add(new Link { DisplayName = linkGroupData.DisplayName, Source = new Uri(linkGroupData.FormatSource(linkGroupData.DefaultContentSource), UriKind.RelativeOrAbsolute) });
            }
            MenuLinkGroups.Add(linkGroup);
            LinkGroupTuples.Add(new Tuple<LinkGroupData, LinkGroup>(linkGroupData, linkGroup));
        }

        void RemoveLinkGroupForData(LinkGroupData linkGroupData)
        {
            var tuple = LinkGroupTuples.Find(t => t.Item1.Equals(linkGroupData));
            if (tuple != null)
            {
                MenuLinkGroups.Remove(tuple.Item2);
                LinkGroupTuples.Remove(tuple);
            }
        }
    }
}
复制代码

上面的代码也很简单,逻辑很清晰,我来说明一下各个方法的用处:

(1)InitializeLinkGroupsForExtensions:获取扩展模型对象,并将对象转换成界面元素LinkGroup,然后监听扩展模型变更事件;

(2)LinkGroups_CollectionChanged:扩展模型变更事件,当有扩展对象添加时,需要添加新的界面元素;反之,则需要移除界面元素;

(3)CreateLinkGroupForData:为扩展模型创建界面元素LinkGroup;

(4)RemoveLinkGroupForData:当扩展模型被删除时,需要将对应的界面元素删除掉。

为了支持插件化,还需要为ModernUI做一个变更,下面我将来介绍。

 

3.4 ModernUI插件化支撑所做的变更

为了支持插件化,我需要对ModernUI的ContentLoader进行扩展,使其支持直接从插件加载内容页面。详细查看以下代码。

复制代码
/// <summary>
/// Loads the content from specified uri.
/// </summary>
/// <param name="uri">The content uri</param>
/// <returns>The loaded content.</returns>
protected virtual object LoadContent(Uri uri)
{
    // don't do anything in design mode
    if (ModernUIHelper.IsInDesignMode)
    {
        return null;
    }
    string uriString = string.Empty;
    string paraString = string.Empty;
    Dictionary<string, string> parameters = new Dictionary<string, string>();
    if (uri.OriginalString.Contains('?'))
    {
        var uriPara = uri.OriginalString.Split('?');
        uriString = uriPara[0];
        paraString = uriPara[1];
        var parameterStrs = paraString.Split('&');
        string[] parameterStrSplitted;
        foreach (var parameterStr in parameterStrs)
        {
            parameterStrSplitted = parameterStr.Split('=');
            parameters.Add(parameterStrSplitted[0], parameterStrSplitted[1]);
        }
    }
    else
    {
        uriString = uri.OriginalString;
    }

    object result = null;
    // 1st Format: [BundleSymbolicName]@[Class Full Name]
    if (uriString.Contains('@'))
    {
        var bundleSymbolicNameAndClass = uriString.Split('@');
        if (bundleSymbolicNameAndClass.Length != 2 || string.IsNullOrEmpty(bundleSymbolicNameAndClass[0]) || string.IsNullOrEmpty(bundleSymbolicNameAndClass[1]))
        {
            throw new Exception("The uri must be in format of '[BundleSymbolicName]@[Class Full Name]'");
        }
        var bundle = BundleRuntime.Instance.Framework.Bundles.GetBundleBySymbolicName(bundleSymbolicNameAndClass[0]);
        if (bundle == null)
        {
            throw new Exception(string.Format("The uri is not correct since the bunde '{0}' does not exist.", bundleSymbolicNameAndClass[0]));
        }
        var type = bundle.LoadClass(bundleSymbolicNameAndClass[1]);
        if (type == null)
        {
            throw new Exception(string.Format("The class '{0}' is not found in bunle '{1}'.", bundleSymbolicNameAndClass[1], bundleSymbolicNameAndClass[0]));
        }
        result = Activator.CreateInstance(type);
    }
    // 2nd Format: /[AssemblyName],Version=[Version];component/[XAML relative path]
    else if (string.IsNullOrEmpty(paraString))
    {
        result = Application.LoadComponent(uri);
    }
    else
    {
        result = Application.LoadComponent(new Uri(uriString, UriKind.RelativeOrAbsolute));
    }

    ApplyProperties(result, parameters);

    return result;
}
复制代码

这集成了默认的加载行为,同时支持:(1)以“[BundleSymbolicName]@[PageClassName]”方式支持内容加载;(2)支持WPF传统资源加载方式;(3)支持参数化。

 

另外,为了实现三级菜单,我定义了一个ContentPlaceHolder,它用于获取第三级的菜单,并创建内容,代码如下。

复制代码
namespace UIShell.WpfShellPlugin.Pages
{
    /// <summary>
    /// ContentPlaceHolder.xaml 的交互逻辑
    /// </summary>
    public partial class ContentPlaceHolder : UserControl
    {
        private string _linkId = string.Empty;
        private FirstFloor.ModernUI.Windows.Controls.ModernTab _tab;
        public string LinkId
        {
            get
            {
                return _linkId;
            }
            set
            {
                _linkId = value;
                TabLinkData tabLinkData = null;
                foreach (var linkGroupData in MainWindow.ShellExtensionPointHandler.LinkGroups)
                {
                    foreach (var link in linkGroupData.Links)
                    {
                        if (link.LinkId.ToString().Equals(_linkId, StringComparison.OrdinalIgnoreCase))
                        {
                            tabLinkData = link as TabLinkData;
                            break;
                        }
                    }
                }

                if (tabLinkData != null)
                {
                    _tab.SelectedSource = new Uri(tabLinkData.FormatSource(tabLinkData.DefaultContentSource), UriKind.RelativeOrAbsolute);
                    _tab.Layout = (TabLayout)Enum.Parse(typeof(TabLayout), tabLinkData.Layout);
                    foreach(var linkData in tabLinkData.Links)
                    {
                        _tab.Links.Add(new Link { DisplayName = linkData.DisplayName, Source = new Uri(linkData.FormatSource(linkData.Source), UriKind.RelativeOrAbsolute) });
                    }
                }
            }
        }
        public ContentPlaceHolder()
        {
            InitializeComponent();
            _tab = FindName("ModernTab") as FirstFloor.ModernUI.Windows.Controls.ModernTab;
        }
    }
}
复制代码

它利用传递的参数可以获取对应的三级菜单的扩展模型,然后创建对应的界面元素。

 

到此,我们已经成功实现了整个插件化的界面框架了,文章有点长,能坚持看到这的基本属于勇士了~~,接下来还想用一点点篇幅演示一下界面框架动态性。

 

4 动态性演示

OSGi.NET动态性支持允许我们在程序运行中来安装、启动、停止、卸载和更新插件,请看下图。当你运行下载的程序时,最开始会展示以下菜单,其 中“演示11、演示12”菜单由DemoPlugin插件注册,“演示3”由DemoPlugin2插件提供,此时,你运行一下远程管理控制台,输入 list指令后,可以发现这两个插件都是Active状态。

image

下面我们输入“stop 2”指令,将DemoPlugin插件停止,如下图所示,此时你可以发现DemoPlugin注册的菜单已经动态的从主界面中被移除掉了。

image

同样,你还可以继续尝试Start、Stop、Install、Uninstall等指令来动态更改插件状态,从而影响应用程序的行为。

当然,你也可以通过“插件管理”来实现对内核安装的插件的状态变更,如下所示。

image

再进一步,你可以直接访问插件仓库来安装更多的插件。你可以在源码中查看到如何实现插件管理和插件仓库访问及下载安装插件的代码。

image

怎样,很强大吧!!如果我们构建了这样的通用框架,以后开发起来那简单多了。当然,如果你还有兴趣的话,你可以再尝试了解基于插件的一键部署和自动化升级,我在《分享让你震惊的自动化升级和部署方案,让我们一起来PK一下!http://www.cnblogs.com/baihmpgy/archive/2013/01/25/2876405.html》这篇文章介绍了。

 

好了~,非常感谢这么耐心看完这篇文章。该附上源码了~。

源码下载 点击下载。

 

Creative Commons License 有任何技术问题,可以加入iOpenWorks插件仓库平台QQ交流群: 121369588。

本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 道法自然(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
目录
相关文章
|
2月前
|
设计模式 前端开发 C#
使用 Prism 框架实现导航.NET 6.0 + WPF
使用 Prism 框架实现导航.NET 6.0 + WPF
114 10
|
3月前
|
编解码 C# 数据库
C# + WPF 音频播放器 界面优雅,体验良好
【9月更文挑战第18天】这是一个用 C# 和 WPF 实现的音频播放器示例,界面简洁美观,功能丰富。设计包括播放/暂停按钮、进度条、音量控制滑块、歌曲列表和专辑封面显示。功能实现涵盖音频播放、进度条控制、音量调节及歌曲列表管理。通过响应式设计、动画效果、快捷键支持和错误处理,提升用户体验。可根据需求扩展更多功能。
137 3
|
4月前
|
开发框架 前端开发 JavaScript
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(12) -- 使用代码生成工具Database2Sharp生成WPF界面代码
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(12) -- 使用代码生成工具Database2Sharp生成WPF界面代码
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(12) -- 使用代码生成工具Database2Sharp生成WPF界面代码
|
4月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
104 0
|
4月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
221 0
|
4月前
|
容器 C# 开发者
XAML语言大揭秘:WPF标记的魅力所在,让你轻松实现界面与逻辑分离,告别复杂代码!
【8月更文挑战第31天】XAML提供了一种直观且易于维护的界面设计方式,使得开发者可以专注于逻辑和业务代码的编写,而无需关心界面细节。通过数据绑定、布局管理和动画效果等特性,XAML可以实现丰富的界面交互和视觉效果。在实际开发过程中,开发者应根据具体需求选择合适的技术方案,以确保应用程序能够满足用户的需求。希望本文的内容能够帮助您在WPF应用程序开发中更好地利用XAML语言。
49 0
|
4月前
|
数据处理 开发者 C#
WPF数据绑定实战:从零开始,带你玩转数据与界面同步,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,数据绑定是核心技能之一,它能实现界面元素与数据源的同步更新。本文详细介绍了WPF数据绑定的概念与实现方法,包括属性绑定、元素绑定及路径绑定等技术,并通过示例代码展示了如何创建数据绑定。通过数据绑定,开发者不仅能简化代码、提高可维护性,还能提升用户体验。无论初学者还是有经验的开发者,都能从中受益,更好地掌握WPF数据绑定技巧。
99 0
|
4月前
|
C# Windows 开发者
超越选择焦虑:深入解析WinForms、WPF与UWP——谁才是打造顶级.NET桌面应用的终极利器?从开发效率到视觉享受,全面解读三大框架优劣,助你精准匹配项目需求,构建完美桌面应用生态系统
【8月更文挑战第31天】.NET框架为开发者提供了多种桌面应用开发选项,包括WinForms、WPF和UWP。WinForms简单易用,适合快速开发基本应用;WPF提供强大的UI设计工具和丰富的视觉体验,支持XAML,易于实现复杂布局;UWP专为Windows 10设计,支持多设备,充分利用现代硬件特性。本文通过示例代码详细介绍这三种框架的特点,帮助读者根据项目需求做出明智选择。以下是各框架的简单示例代码,便于理解其基本用法。
222 0
|
4月前
|
存储 前端开发 C#
WPF/C#:更改界面的样式
WPF/C#:更改界面的样式
46 0
|
4月前
|
开发框架 JSON 前端开发
WPF应用框架中工作流模块的介绍
WPF应用框架中工作流模块的介绍