准備.Net轉前端開發-WPF界面框架那些事,搭建基礎框架


題外話

    最近都沒怎么寫博客,主要是最近在看WPF方面的書《wpf-4-unleashed.pdf》,挑了比較重要的幾個章節學習了下WPF基礎技術。另外,也把這本書推薦給目前正在從事WPF開發的程序猿。 現在書看完了也該實踐實踐,寫了個WPF項目,主要以界面框架為主。  最近的幾篇博客也主要圍繞這個WPF項目,介紹下WPF搭建界面框架以及怎樣寫自定義的Windows界面和控件。

    這也許是寫最后幾篇關於.Net技術的博客。做.Net開發也快五年了,感覺自己搞得不溫不火,另外工作中正好有一個機會轉做前段開發。穩穩當當的做年幾年.Net開發,也想給自己一個挑戰的機會。所以這幾篇博客完成后,更多的可能是涉及前段開發。

  雷軍通知常常愛說:不忘初心,不忘初心、,不忘初心。重要的事說三遍。走在程序猿的路上,別忘了自己的初心,是做一個牛逼的程序猿呢還是做一個非常牛逼的程序猿呢?come on-打着雞血一般的奮斗吧!

設計思路

    前段時間看了下ASP.NET請求過程的管道運行,其中包含有IHttpModule接口,通過接口的擴展,可以讓各個模塊獨立化,例如登錄認證、權限認證以及處理IHttpHandler。同理,我們可以讓WPF系統啟動過程中獨立加載各個模塊,包括系統資源模塊、主題資源模塊、登錄模塊、用戶認證模塊、權限管理模塊、以及自定義模塊等。先來看下系統僅有的兩個配置文件,分別是Application.xml和Startup.xml。Application.xml結構如下圖所示:

<?xml version="1.0" encoding="utf-8" ?>
<Application  xmlns="http://tempuri.org/ApplicationSchema.xsd"
    xmlns:mstns="http://tempuri.org/ApplicationSchema.xsd">
  <Attributes>
    <Attr mstns:Name="stsdb" mstns:Value="Data/db.stsdb4"/>
  </Attributes>
</Application>

 

    上圖中,我們很容易的看出Application.xml主要存放字典資源,例如數據庫的配置。sttsdb是數據庫的字典名,而"Data/db.stsdb4"是數據庫的路徑。節點Attr表示一個字典項。

    除了Application.xml文件,Startup.xml配置文件是整個系統的核心部分,因為我們系統啟動的整個流程都體現在配置內容中。先看看節點結構:

<?xml version="1.0" encoding="utf-8" ?>
<Startup xmlns="http://tempuri.org/StartupSchema.xsd"
xmlns:xsi="http://tempuri.org/StartupSchema.xsd">
  <Startup.Modules>
    <ResourceModules>
       <Add xsi:Type="HeaviSoft.Documentor.Theme.Implements.ThemeModule, HeaviSoft.Documentor.Theme"/>
    </ResourceModules>
    <LoginModules>
       <Add xsi:Name="LoginModule" xsi:Type="HeaviSoft.Documentor.Presentation.Login.Implements.LoginModule,HeaviSoft.Documentor.Presentation.Login"/>
    </LoginModules>
    <AuthenticationModules>
      <Add xsi:Name="AuthenticationModule" xsi:Type="HeaviSoft.Documentor.Application.Security.AuthenticationModule, HeaviSoft.Documentor.Application.Security"  />
    </AuthenticationModules>
    <AuthorizationModules>
    </AuthorizationModules>
    <ExecutionModules>
      <Add xsi:Type="HeaviSoft.Documentor.Presentation.Docking.Implements.ExecutionModule, HeaviSoft.Documentor.Presentation.Docking"/>
    </ExecutionModules>
  </Startup.Modules>
</Startup>

    系統的所有資源存放在節點Startup.Modules節點下。節點說明:

節點 說明
ResourceModules 存放所有的資源模塊,例如Theme資源以及語言資源,可添加多個。
LoginModules 存放登錄模塊資源,一般我們只用添加一個Login模塊。但也支持添加多個。
AuthenticationModules 驗證系統用戶的身份是否合法,可添加多個。
AuthorizationModules 系統的授權管理,可添加多個。
ExecutionModules 通用執行模塊,前面是個模塊執行完后,可添加自己的功能模塊。例如我們可以把主界面的顯示流程添加到這個模塊。

談談框架

    比較常見的系統框架一般都包含兩個部分:基礎框架和業務框架。基礎框架一般不會太涉及業務,主要為業務框架提供平台,提供上下文環境。本次設計的基礎框架主要包括了上下文環境、安全策略、日志以及自定義組件等等。而業務框架我們主要就是考慮業務模塊的功能實現。在實現業務功能的同時,我們得考慮分層設計,保證業務的高擴展性以及系統的可持續性。

基礎框架

    基礎框架包含了七個模塊,框架結構如下圖所示:

    image

    接下來我們就分別介紹上圖中比較重要的幾個模塊:Core、utility、Resource。

基礎框架-Core

    Core包含了Core模塊和Configuration模塊,兩個模塊都有一個共同的功能,都為系統的啟動過程服務。Configuration模塊定義了系統配置文件的模板。Configuration的整個工程結構如下所示:

   

image

    圖中的Schema文件夾下定義了兩個配置規則文件,分別對應Application.xml和Startup.xml。這兩個文件的具體內容在前面的“設計思路”部分已經給出。

    配置已經有了,我們分析Startup.xml中的節點內容,例如:<Add xsi:Type="HeaviSoft.FrameworkBase.Client.Implements.ThemeModule, HeaviSoft.FrameworkBase.Client"/>。這一句代碼的實際意義是什么?屬性Type的值代表一個類的名稱以及所在命名空間。在系統啟動的過程中,可通過反射機制動態創建節點類型的實體對象。這些通過動態創建的類都有一個共同的特點,都需要實現某個接口來擴展一個模塊的功能。而Core工程下為我們定義了這些模塊接口,如下圖所示:

    image

    主題和語言接口包括:IResourceModule、IThemeResourceModule、ILanguageResourceModule。IResourceModule是父接口,而IThemeResourceModule和ILanguageResourceModule是IResourceModule的擴展接口。IResourceModule定義如下:

/// <summary>
    /// 資源加載模塊
    /// </summary>
    public interface IResourceModule
    {
        /// <summary>
        /// 資源加載中
        /// </summary>
        /// <param name="app">應用對象</param>
        /// <param name="name">資源名稱</param>
        /// <returns></returns>
        bool Loading(ExtendedApplicationBase app, string name);
        /// <summary>
        /// 資源加載完成
        /// </summary>
        /// <param name="app">應用對象</param>
        /// <param name="name">資源名稱</param>
        /// <returns></returns>
        bool Loaded(ExtendedApplicationBase app, string name);
        /// <summary>
        /// 資源卸載中
        /// </summary>
        /// <param name="app">應用對象</param>
        /// <param name="name">資源名稱</param>
        /// <returns></returns>
        bool UnLoading(ExtendedApplicationBase app, string name);
        /// <summary>
        /// 資源卸載完成
        /// </summary>
        /// <param name="app">應用對象</param>
        /// <param name="name">資源名稱</param>
        /// <returns></returns>
        bool UnLoaded(ExtendedApplicationBase app, string name);
    }

    我們可以在Loading中執行加載資源,並且接收兩個參數:app和name,app的類型為ExtendedApplicationBase,ExtendedApplicationBase就是我們的應用基類,它幾乎存儲了系統的所有上下文信息,稍后再詳解。在IResourceModule接口的方法中,我們可以獲取系統的任何上下文信息。

    IThemeResourceModule是IResourceModule的一個子接口,用於加載主題資源;和IThemeResourceModule相似,ILanguageResourceModule用戶加載管理語言資源。如果我們要切換主題或資源,可先掉調用IResourceModule接口的UnLoading方法先卸載主題和語言資源。然后再調用新資源的Loading方法。

    登錄接口:ILoginModule。系統的登錄操作可通過實現ILoginModule接口來執行,代碼如下:

/// <summary>
    /// 登錄模塊
    /// </summary>
    public interface ILoginModule
    {
        /// <summary>
        /// 登錄接口
        /// </summary>
        /// <param name="app">應用對象</param>
        /// <returns>返回登陸操作狀態</returns>
        bool Login(ExtendedApplicationBase app);
        /// <summary>
        /// 登錄成功
        /// </summary>
        /// <param name="app">應用對象</param>
        /// <param name="message">成功消息</param>
        void LoginSuccessed(ExtendedApplicationBase app, object message);
        /// <summary>
        /// 登錄失敗
        /// </summary>
        /// <param name="app">應用對象</param>
        /// <param name="message">失敗消息</param>
        void LoginFailed(ExtendedApplicationBase app, object message);
    }

    登錄過程在Login方法中實現,登錄驗證后,如果登錄成功則會調用LoginSuccessed方法,如果登錄失敗,則調用LoginFailed。

    身份認證:IAuthenticationModule接口。ILoginModule接口執行后,上下文信息中緩存了加密后的登錄信息,IAuthenticationModule主要對上下文中的登錄信息做驗證。驗證有沒有通過我們可以通過app中的Context.User.Identity.IsAuthenticated來判斷。

    授權接口:IAuthorizationModule。IAuthenticationModule執行后,我們能夠知道用戶身份是否合法,如果身份有效,IAuthorizationModule可到服務器上獲取用戶的權限信息,並保存在上下文中。如果用戶要使用某個業務模塊,可從上下文中調用接口IPrincipal的IsInRole方法驗證授權。IPrincipal的定義如下:

public interface IPrincipal
    {
        IIdentity Identity { get; }
        bool IsInRole(string role);
    }

      模塊的接口就介紹到這里,在啟動系統時,怎樣把這些接口串聯的執行。這就不得不提到系統基礎ExtendedApplicationBase。

基礎框架-ExtendedApplicationBase

    首先,我們需要考慮系統啟動后,Startup.xml中模塊集合存放在哪里。ExtendedApplicationBase就是比較合適的一個容器,它定義了像ThemeResourceModules、LanguageResourceMudules等模塊集合。容器有了,那么執行的步驟怎樣串聯起來?ExtendedApplicationBase實現了一個抽象方法BuildSteps(),用於構建系統的啟動步驟。而整個系統的執行需要實現抽象方法ExecuteSteps()。還是先看看代碼再分析:

/// <summary>
    /// 應用對象類
    /// </summary>
    public abstract class ExtendedApplicationBase : Application
    {
        public readonly string PROPERTY_ACCOUNT = "ExtendedApplicationBase_Account";
        public readonly string PROPERTY_PASSWORD = "ExtendedApplicationBase_Password";

        /// <summary>
        /// 當前應用實例
        /// </summary>
        public static ExtendedApplicationBase Current { get; set; }

        public ExtendedApplicationBase() : base()
        {
            Data = new Dictionary<object, object>();
            ThemeResourceModules = new List<IThemeResourceModule>();
            LanguageResourceMudules = new List<ILanguageResourceModule>();
            LoginModules = new List<ILoginModule>();
            AuthenticationModules = new List<IAuthenticationModule>();
            AuthorizationModules = new List<IAuthorizationModule>();
            ExecutionModules = new List<IExecutionModule>();

            Context = new AppContext();
        }

        /// <summary>
        /// 應用上下文
        /// </summary>
        public AppContext Context { get; private set; }

        public Dictionary<object, object> Data { get; private set; }

        protected List<IThemeResourceModule> ThemeResourceModules { get; private set; }

        protected List<ILanguageResourceModule> LanguageResourceMudules { get; private set; }

        protected List<ILoginModule> LoginModules { get; private set; }

        protected List<IAuthenticationModule> AuthenticationModules { get; private set; }

        protected List<IAuthorizationModule> AuthorizationModules { get; private set; }

        protected List<IExecutionModule> ExecutionModules { get; private set; }

        /// <summary>
        /// 構建步驟
        /// </summary>
        public abstract void BuildSteps();

        /// <summary>
        /// 執行步驟
        /// </summary>
        /// <returns>執行狀態</returns>
        public virtual bool ExecuteSteps()
        {
            if (!ExecuteThemeResourceModulesCore())
                return false;
            if (!ExecuteLanguageResourceModulesCore())
                return false;
            if (!ExecuteLoginModulesCore())
                return false;
            if (!ExecuteAuthorizationModulesCore())
                return false;
            if (!ExecuteExecutionModulesCore())
                return false;

            return true;
        }

        /// <summary>
        /// 關閉系統
        /// </summary>
        public virtual void ExitEx()
        {
            Environment.Exit(0);
        }

        /// <summary>
        /// 執行主題資源加載
        /// </summary>
        /// <returns></returns>
        protected abstract bool ExecuteThemeResourceModulesCore();

        /// <summary>
        /// 執行語言資源加載
        /// </summary>
        /// <returns></returns>
        protected abstract bool ExecuteLanguageResourceModulesCore();

        /// <summary>
        /// 執行登錄流程
        /// </summary>
        /// <returns></returns>
        protected abstract bool ExecuteLoginModulesCore();
        /// <summary>
        /// 執行身份驗證
        /// </summary>
        /// <returns></returns>
        public abstract bool ExecuteAutheticationModulesCore();
        /// <summary>
        /// 執行身份授權
        /// </summary>
        /// <returns></returns>
        protected abstract bool ExecuteAuthorizationModulesCore();
        /// <summary>
        /// 執行常規流程
        /// </summary>
        /// <returns></returns>
        protected abstract bool ExecuteExecutionModulesCore();

        /// <summary>
        /// 激活當前應用
        /// </summary>
        public void Activate()
        {
            this.MainWindow.Show();
            this.MainWindow.Activate();
        }

    代碼中,ExecuteSteps方法分別調用了ExecuteThemeResourceModulesCore、ExecuteLanguageResourceModulesCore、ExecuteLoginModulesCore、ExecuteAuthorizationModulesCore、ExecuteExecutionModulesCore。這幾個方法都是抽象方法。具體的實現都是在子類中。我們還看到類中包括一個Data屬性,類型為Dictionary。之前我們提到了Application.xml,這里邊配置的所有Attr字典配置數據都會存儲在Data中,取值直接通過ExtendedApplicationBase.Current.Data[Key]讀取。

基礎框架-ExtendedApplicationBase實現類

    在系統中添加一個ExtendedApplicationBase的具體實現類ExtendedApplication。代碼如下:

/// <summary>
    /// 系統應用
    /// </summary>
    internal class ExtendedApplication : ExtendedApplicationBase
    {
        public ExtendedApplication() : base()
        {
            ShutdownMode = ShutdownMode.OnExplicitShutdown;
        }

        #region 系統事件
        /// <summary>
        /// 流程啟動
        /// </summary>
        /// <param name="e"></param>
        protected override void OnStartup(StartupEventArgs e)
        {
            //注冊事件
            RegistEvent();
            //開始構建步驟
            try
            {
                this.BuildSteps();
                //執行步驟
                if (!this.ExecuteSteps())
                {
                    this.ExitEx();
                }
            }
            catch (Exception ex)
            {
                //寫日志
                Logger.Error("Error occured during appication start-up.", ex);
                this.ExitEx();
            }
        }

        private void RegistEvent()
        {
            //捕獲UI線程
            this.DispatcherUnhandledException += ExtendedApplication_DispatcherUnhandledException;
            //捕獲其他線程異常
            TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
        }


        private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            //寫日志
            Logger.Error("Unknown error.", e.Exception);
            //處理異常
        }

        /// <summary>
        /// 未捕獲的異常
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExtendedApplication_DispatcherUnhandledException(object sender,
            System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
        {
            HandleUnKnownExcpetion(e.Exception);
        }

        private void HandleUnKnownExcpetion(Exception e)
        {
            //寫日志
            Logger.Error("Unknown error.", e);
            //啟動過程中發生了異常。
            if (e is StartupException)
            {
                //啟動異常提示
            }
            else if (e is FatalException)
            {
                //中斷異常提示
            }
            else
            {
                //其他異常
            }
        }

        #endregion

        #region 父類抽象方法實現
        public override void BuildSteps()
        {
            #region Application Attributes
            var applicationRoot = ConfigurationHelper.GetApplicationConfigRoot();
            if (!applicationRoot.IsNull())
            {
                var eleLayouts = new string[] { ConfigurationHelper.Config_Node_Attriutes, ConfigurationHelper.Config_Node_Attr };
                var attributes = applicationRoot.GetElements(eleLayouts);
                foreach (var element in attributes)
                {
                    var name = element.GetAttributeValue("Name");
                    var value = element.GetAttributeValue("Value");

                    if (!this.Data.ContainsKey(name))
                    {
                        this.Data.Add(name, value);
                    }
                }
            }
            #endregion

            #region Startup 
            var startupRoot = ConfigurationHelper.GetStartupConfigRoot();
            if (!startupRoot.IsNull())
            {
                //加載ResourceModules
                var startups = startupRoot.GetElements(new string[] { ConfigurationHelper.Config_Node_Modules, ConfigurationHelper.Config_Node_ResourceModules, ConfigurationHelper.Config_Node_Operation });
                var resourcesTypes = startups.ToList().Select(el => el.GetAttributeValue("Type"));
                var resourceModules = new List<IResourceModule>();
                foreach (var type in resourcesTypes)
                {
                    resourceModules.Add(CreateInstanceByType<IResourceModule>(type));
                }
                this.ThemeResourceModules.AddRange(resourceModules.Where(res => res is IThemeResourceModule).Cast<IThemeResourceModule>());
                this.LanguageResourceMudules.AddRange(resourceModules.Where(res => res is ILanguageResourceModule).Cast<ILanguageResourceModule>());
                //加載LoginModules
                var loginTypes = startupRoot.GetElements(new string[] { ConfigurationHelper.Config_Node_Modules, ConfigurationHelper.Config_Node_LoginModules, ConfigurationHelper.Config_Node_Operation }).ToList().Select(el => el.GetAttributeValue("Type"));
                foreach (var type in loginTypes)
                {
                    this.LoginModules.Add(CreateInstanceByType<ILoginModule>(type));
                }
                //加載AuthenticationModules
                var authenticationTypes = startupRoot.GetElements(new string[] { ConfigurationHelper.Config_Node_Modules, ConfigurationHelper.Config_Node_AuthenticationModules, ConfigurationHelper.Config_Node_Operation }).ToList().Select(el => el.GetAttributeValue("Type"));
                foreach (var type in authenticationTypes)
                {
                    this.AuthenticationModules.Add(CreateInstanceByType<IAuthenticationModule>(type));
                }
                //加載AutorizationModules
                var authorizationTypes = startupRoot.GetElements(new string[] { ConfigurationHelper.Config_Node_Modules, ConfigurationHelper.Config_Node_AuthorizationModules, ConfigurationHelper.Config_Node_Operation }).ToList().Select(el => el.GetAttributeValue("Type"));
                foreach (var type in authorizationTypes)
                {
                    this.AuthorizationModules.Add(CreateInstanceByType<IAuthorizationModule>(type));
                }
                //加載執行流程
                var executionTypes = startupRoot.GetElements(new string[] { ConfigurationHelper.Config_Node_Modules, ConfigurationHelper.Config_Node_ExecutionModules, ConfigurationHelper.Config_Node_Operation }).ToList().Select(el => el.GetAttributeValue("Type"));
                foreach (var type in executionTypes)
                {
                    this.ExecutionModules.Add(CreateInstanceByType<IExecutionModule>(type));
                }
            }

            #endregion
        }

        protected override bool ExecuteThemeResourceModulesCore()
        {
            //加載主題資源
            foreach (var module in ThemeResourceModules)
            {
                if (!module.Loading(this, Context.CurrentTheme))
                {
                    throw new StartupException("Error occured when loading theme resource.");
                }
            }
            //主題資源加載完成
            foreach (var module in ThemeResourceModules)
            {
                if (!module.Loaded(this, Context.CurrentTheme))
                {
                    throw new StartupException("Error occured when loaded theme resource.");
                }
            }
            return true;
        }

        protected override bool ExecuteLanguageResourceModulesCore()
        {
            //加載語言資源
            foreach (var module in LanguageResourceMudules)
            {
                if (!module.Loading(this, Context.CurrentLanguage))
                {
                    throw new StartupException("Error occured when loading language resource.");
                }
            }
            //語言資源加載完成
            foreach (var module in LanguageResourceMudules)
            {
                if (!module.Loaded(this, Context.CurrentLanguage))
                {
                    throw new StartupException("Error occured when loaded language resource.");
                }
            }
            return true;
        }

        protected override bool ExecuteLoginModulesCore()
        {
            foreach (var login in LoginModules)
            {
                try
                {
                    if (!login.Login(this))
                    {
                        return false;
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

            return true;
        }

        public override bool ExecuteAutheticationModulesCore()
        {
            var result = true;
            var message = "Authenticating user is successed.";
            //執行認證
            foreach (var auth in AuthenticationModules)
            {
                if (!auth.Authenticate(this))
                {
                    result = false;
                    message = "Invalid user, please check inputed user info!";
                    break;
                }
            }
            //用戶認證失敗,判斷是否需要重新啟動登錄
            if (!result)
            {
                //是否需要重新登陸
                foreach (var login in LoginModules)
                {
                    login.LoginFailed(this, message);
                }
            }
            //用戶認證成功
            else
            {
                foreach (var login in LoginModules)
                {
                    login.LoginSuccessed(this, message);
                }
            }

            return result;
        }

        protected override bool ExecuteAuthorizationModulesCore()
        {
            //授權之前檢查用戶是否驗證通過
            if (!Context.User.Identity.IsAuthenticated)
            {
                Logger.Info("User must be autenticated before executing authorizationModule.");
                return false;
            }
            //用戶授權操作
            foreach (var autor in AuthorizationModules)
            {
                if (!autor.Authorize(this))
                {
                    Logger.Info("User authorizate failed.");
                    return false;
                }
            }

            return true;
        }

        protected override bool ExecuteExecutionModulesCore()
        {
            //加載數據s
            foreach (var excute in ExecutionModules)
            {
                excute.Execute(this);
            }

            return true;
        }

        #endregion

        #region 私有方法
        private T CreateInstanceByType<T>(string typeInfo)
        {
            try
            {
                var array = typeInfo.Split(',');
                object instance = null;
                try
                {
                    instance = Assembly.LoadFrom(string.Format("{0}.dll", array[1].Trim())).CreateInstance(array[0].Trim());
                }
                catch (FileNotFoundException)
                {
                    instance = Assembly.LoadFrom(string.Format("{0}.exe", array[1].Trim())).CreateInstance(array[0].Trim());
                }

                return (T)instance;
            }
            catch (Exception ex)
            {
                throw new StartupException(string.Format("Error occured when executing CreateInstanceByType method, parameter:{0}", typeInfo), ex);
            }
        }

        #endregion

    }

    首先說說重寫的BuildSteps方法,先從Application.xml中讀取配置字典,然后從Startup.xml讀取模塊配置。按順序先后讀取ResourceModules、LoginModules、AuthenticationModules、AutorizationModules、ExecutionModules。並且在每讀取一個模塊,調用CreateInstanceByType<IXXXModule>(moduleTypeName)實例化具體類型的對象。每創建一個實體對象,都會存放在模塊集合中,例如創建的ThemeResourceModule對象就會存放在ExtendedApplicationBase的ThemeResourceModules集合中。在ExtendedApplicationBase類中我們也看到幾個模塊集合對象,如下圖所示:

public Dictionary<object, object> Data { get; private set; }

        protected List<IThemeResourceModule> ThemeResourceModules { get; private set; }

        protected List<ILanguageResourceModule> LanguageResourceMudules { get; private set; }

        protected List<ILoginModule> LoginModules { get; private set; }

        protected List<IAuthenticationModule> AuthenticationModules { get; private set; }

        protected List<IAuthorizationModule> AuthorizationModules { get; private set; }

        protected List<IExecutionModule> ExecutionModules { get; private set; }

    所有的模塊對象都准備就緒,現在要考慮整個系統的執行步驟怎樣實現。我們知道系統的啟動都是以Application的OnStartup方法作為入口,那看看OnStartup代碼:

/// <summary>
        /// 流程啟動
        /// </summary>
        /// <param name="e"></param>
        protected override void OnStartup(StartupEventArgs e)
        {
            //注冊事件
            RegistEvent();
            //開始構建步驟
            try
            {
                this.BuildSteps();
                //執行步驟
                if (!this.ExecuteSteps())
                {
                    this.ExitEx();
                }
            }
            catch (Exception ex)
            {
                //寫日志
                Logger.Error("Error occured during appication start-up.", ex);
                this.ExitEx();
            }
        }

    代碼先執行RegistEvent方法注冊異常捕獲事件,一般都是注冊事件到DispatcherUnhandledException捕獲UI線程的未知異常,但別忘了捕獲工作線程,工作線程的異常使用TaskScheduler.UnobservedTaskException捕獲。事件注冊之后執行BuildSteps方法,前面剛剛說過了。步驟構建完了,就該調用ExecuteSteps方法執行具體模塊。 ExecuteSteps的實現是在ExtendedApplicationBase中,代碼如下:

/// <summary>
        /// 執行步驟
        /// </summary>
        /// <returns>執行狀態</returns>
        public virtual bool ExecuteSteps()
        {
            if (!ExecuteThemeResourceModulesCore())
                return false;
            if (!ExecuteLanguageResourceModulesCore())
                return false;
            if (!ExecuteLoginModulesCore())
                return false;
            if (!ExecuteAuthorizationModulesCore())
                return false;
            if (!ExecuteExecutionModulesCore())
                return false;

            return true;
        }

    這里列舉一個ExecuteThemeResourceModulesCore的實現做簡要說明,其他的模塊執行流程相似。ExecuteThemeResourceModulesCore實現如下:

protected override bool ExecuteThemeResourceModulesCore()
        {
            //加載主題資源
            foreach (var module in ThemeResourceModules)
            {
                if (!module.Loading(this, Context.CurrentTheme))
                {
                    throw new StartupException("Error occured when loading theme resource.");
                }
            }
            //主題資源加載完成
            foreach (var module in ThemeResourceModules)
            {
                if (!module.Loaded(this, Context.CurrentTheme))
                {
                    throw new StartupException("Error occured when loaded theme resource.");
                }
            }
            return true;
        }

    前面已經給出了IResouceModule接口的定義。ExecuteThemeResourceModulesCore先遍歷ThemeResouceModules的對象,調用Loading方法。緊接着再次遍歷ThemeResourceModules集合,調用對象的Loaded方法。其他的幾個模塊的執行原理相似。

本篇總結

    本篇介紹的內容比較簡單,首先介紹了系統的兩個配置文件Application.xml和Startup.xml,根據這兩個配置文件提出系統的設計思路。其次,給出了Core中的7個接口代碼,每個接口代表了一個獨立的模塊,像資源模塊、認證模塊、授權模塊等。然后,分析自定義的Application類:ExtendedApplicationBase,它包括了系統流程的構建方法BuildSteps、系統流程的執行方法ExecuteSteps。最后給出ExtendedApplicationBase的實現類ExtendedApplication,並且給出代碼,根據代碼分析了步驟如何構建,步驟如何執行,並且列舉了資源模塊的執行過程ExecuteThemeResourceModulesCore方法。

    搭建一個WPF界面框架,肯定少不了Custom Window和Custom Control。因為開發一個給客戶使用的系統,肯定會根據客戶設計不同的UI。那么,如何開發這些具有獨特風格的UI以及自定義控件,並且包含可替換的主題。下一篇再做詳解。

    如果本篇內容對大家有幫助,請點擊頁面右下角的關注。如果覺得不好,也歡迎拍磚。你們的評價就是博主的動力!下篇內容,敬請期待!

源代碼

   完整的代碼存放在GitHub上,代碼路徑:https://github.com/heavis/Documentor_V01R01/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM