【我們一起寫框架】MVVM的WPF框架之序篇(一)

来源:https://www.cnblogs.com/kiba/archive/2018/09/03/9565299.html
-Advertisement-
Play Games

前言 我想,有一部分程式員應該是在二三線城市的,雖然不知道占比,但想來應該不在少數。 我是這部分人群中的一份子。 我們這群人,面對的客戶,大多是國內中小企業,或者政府的小部門。這類客戶的特點是,資金有限,人力有限。 什麼意思呢?就是你如果敢給他安一臺Linux伺服器,客戶的信息員和測試員會把你堵在牆 ...


前言

我想,有一部分程式員應該是在二三線城市的,雖然不知道占比,但想來應該不在少數。

我是這部分人群中的一份子。

我們這群人,面對的客戶,大多是國內中小企業,或者政府的小部門。這類客戶的特點是,資金有限,人力有限。

什麼意思呢?就是你如果敢給他安一臺Linux伺服器,客戶的信息員和測試員會把你堵在牆角問候你全家安好,他們Window都用不明白呢,你給安Linux,要瘋啊。

所以,Core對我們而言,沒有意義,因為大家都是Windows。

關於業務

在二三線城市的我們,立身之本不是寫演算法,也不是各種高級的、新出的技術,而是,寫業務模塊。

不要小看寫業務模塊,在二三線城市,一個不會寫業務模塊的程式員,即便知識面再廣,也是個爛程式員。為什麼?因為他不能幹活呀。

其實把業務模塊寫好,並不是件容易的事。因為它涉及到對業務的理解,對社會的認知。

以我多年的經驗,能寫好業務模塊的優秀開發人員,通常都需要三四年經驗。普通一點,大約就需要五到十年。當然還有十年以上經驗,還很沒掌握寫業務的。

這裡面有個特例,那就是碩士和博士。因為他們的年齡較大,閱歷較多,所以,通常兩年就能把業務寫的很好。此外就沒有特例了,什麼一年經驗就能架構,剛畢業就是高級程式員的,那都是培訓機構騙畢業生的。

但是,不得不說,高學歷真的管用,碩士博士的成材率真的很高。大多數都能成為及格的程式員。

關於框架

回到寫框架這件事。在我看來,寫框架這件事是個程式員都能幹。但寫的好壞就另說了,所以寫框架這件事還是與經驗掛鉤的。

在我的認知中,技術視野相對更高,技術範圍更廣的人寫的框架會更好。所以,我認為,[實戰]架構師和高級程式員,在本質上沒有區別,都是程式員。

只是架構師技術更會好一點,並且接受過項目的洗禮。然而,一個項目只能洗禮一個人,所以能不能成為架構師,就不能只看技術了,要看老闆給誰機會了。說白了,就是老闆肯不肯花錢賭你能成事。

所以,當技術相差無幾,溝通能力,文檔能力,甚至生活狀態,家境,毅力都是領導考察的依據。因此,機會不是留給有準備的人,而是留給各方面都更出色的人。

當然,如果老闆認可你,一年經驗做架構師也不是沒可能。但在資金有限,人員有限的二三線城市,能遇到這樣腦殘的領導或老闆的概率不高。

雖然架構師不是人人都能做,但框架是可以先學會編寫的,畢竟這是個基礎。有了基礎,就算不能年輕有為,但起碼有個機會。

也許,人家28歲拿到的機會,你在40歲也可以拿到,不是嗎。有機會總比沒有強,不是嗎。

框架的前期準備

關於框架編寫,我不想在Github上放一個源碼,然後再寫一篇介紹文檔。我覺得,這種方式是高手之間的交流。

很多新手,會被這種海量的代碼壓垮,因為他們還不習慣閱讀框架,會出現開始時事倍功半,到最後鬱悶放棄的情況。

所以,我們一起從頭開始,一起開始MVVM的WPF框架之旅吧。

框架的前期準備

框架是要一步一步編寫的,首先,我們先定義框架包含的基本元素。基本元素如下:

WPFUI:就是WPF的Xaml頁面。

ViewModel:每個WPF頁面有唯一的ViewModel,用來處理頁面業務邏輯。

Utility:存放一些常規處理類。

DTO:存放數據傳輸用的實體類。

Proxy:獲取數據用的代理類。

先定義這五個元素,如果後期需要,我們再進行補充。定義了元素後,我們創建對應的應用程式集。項目結構如下:

做好了項目結構後,我們讓ViewModel引用DTO,Proxy,Utility三個程式集,然後在讓KibaFramework引用ViewModel,這樣就實現了上圖的結構邏輯。

然後,我們再讓ViewModel引用PresentationCore,PresentationFramework,System.Windows,WindowsBase,Systm.Xaml這個五個DLL,它們是WPF的核心類庫,為了後期反射前臺控制項用。

我怎麼知道要引用這五個類庫的?

這是經驗,僅僅是經驗,沒有其他。

項目約定

創建完基礎結構後,我們要做的是項目約定。(任何框架都有約定,而且約定要高於配置,這是約定優先原則。)

我們建立約定如下:

WPF項目窗體以Window作為首碼名創建,如WindowMain,WindowLogin。

WPF項目頁面以Page作為首碼名創建,如PageMain,PageXXX。

WPF項目控制項(UserControl)以UC作為首碼名創建,如UCTable,UCXXX。

WPF的窗體、頁面、控制項有且只有一個ViewModel。

ViewModel以VM_作為首碼名+對應的窗體名創建,如VM_WindowMain,VM_PageMain。

框架的實現

做完準備工作後,我們開始編寫框架,先從系統的核心ViewModel開始,第一步,建立WPF頁面與View的關係。

首先我們創建VM的基類BaseViewModel——之後再建立的VM都要引用這個基類。

在VM基類里,我們通過反射實現創建Xaml頁面,並實現該頁面的相關事件。代碼如下:

namespace ViewModel
{
    public class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public const string UINameSapce = "KibaFramework";
        public string UIElementName = "";
        public FrameworkElement UIElement { get; set; }
        public Window WindowMain { get; set; } //主窗體  
       
        public EventHandler CloseCallBack = null; //窗體/頁面/控制項 關閉委托 
        public BaseViewModel()
        {
            WindowMain = Application.Current.MainWindow; 
            SetUIElement();
        }
      
        
        #region 通過反射創建對應的UI元素
        public void SetUIElement()
        {
            Type childType = this.GetType();//獲取子類的類型   
            string name = this.GetType().Name;
            UIElementName = name.Replace("VM_", "");
            UIElementName = UIElementName.Replace("`1", "");//應對泛型實體

            if (name.Contains("Window"))
            {
                UIElement = GetElement<Window>();
                (UIElement as Window).Closing += (s, e) =>
                {
                    if (CloseCallBack != null)
                    {
                        CloseCallBack(s, e);
                    }
                };
            }
            else if (name.Contains("Page"))
            {
                UIElement = GetElement<Page>();
                (UIElement as Page).Unloaded += (s, e) =>
                {
                    if (CloseCallBack != null)
                    {
                        CloseCallBack(s, e);
                    }
                };
            }
            else if (name.Contains("UC"))
            {
                UIElement = GetElement<UserControl>();
                (UIElement as UserControl).Unloaded += (s, e) =>
                {
                    if (CloseCallBack != null)
                    {
                        CloseCallBack(s, e);
                    }
                };
            }
            else
            {
                throw new Exception("元素名不規範");
            }
        }

        public E GetElement<E>()
        {
            Type type = GetFormType(UINameSapce + "." + UIElementName);
            E element = (E)Activator.CreateInstance(type);
            return element;
        }

        public static Type GetFormType(string fullName)
        {
            Assembly assembly = Assembly.Load(UINameSapce);
            Type type = assembly.GetType(fullName, true, false);
            return type;
        }
        #endregion

        #region 窗體操作
        public void Show()
        {
            if (UIElement is Window)
            {
                (UIElement as Window).Show();
            }
            else
            {
                throw new Exception("元素類型不正確");
            }
        }

        public void ShowDialog()
        {
            if (UIElement is Window)
            {
                (UIElement as Window).ShowDialog();
            }
            else
            {
                throw new Exception("元素類型不正確");
            }
        }

        public void Close()
        {
            if (UIElement is Window)
            {
                (UIElement as Window).Close();
            }
            else
            {
                throw new Exception("元素類型不正確");
            }
        }

        public void Hide()
        {
            if (UIElement is Window)
            {
                (UIElement as Window).Hide();
            }
            else
            {
                throw new Exception("元素類型不正確");
            }
        }
        #endregion

        #region Message
        public void MessageBox(Window owner, string msg)
        {
            DispatcherHelper.GetUIDispatcher().Invoke(new Action(() =>
            {
                if (owner != null)
                {
                    System.Windows.MessageBox.Show(owner, msg, "提示信息");
                }
                else
                {
                    System.Windows.MessageBox.Show(WindowMain, msg, "提示信息");
                }
            }));
        }

        public void MessageBox(string msg)
        {
            DispatcherHelper.GetUIDispatcher().Invoke(new Action(() =>
            {
                System.Windows.MessageBox.Show(WindowMain, msg, "提示信息");
            }));
        }

        public void MessageBox(string msg, string strTitle)
        {
            DispatcherHelper.GetUIDispatcher().Invoke(new Action(() =>
            {
                System.Windows.MessageBox.Show(WindowMain, msg, "提示信息");
            }));
        }

        public void MessageBox(string msg, Action<bool> callback)
        {
            MessageBox("系統提示", msg, callback);
        }

        public void MessageBox(string title, string msg, Action<bool> callback)
        {
            DispatcherHelper.GetUIDispatcher().Invoke(new Action(() =>
            {
                if (System.Windows.MessageBox.Show(WindowMain, msg, title, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
                {
                    callback(true);
                }
                else
                {
                    callback(false);
                }
            }));
        }
        #endregion

        #region   非同步線程
        public void AsyncLoad(Action action)
        {
            IAsyncResult result = action.BeginInvoke((iar) =>
            {
            }, null);
        }

        public void AsyncLoad(Action action, Action callback)
        {
            IAsyncResult result = action.BeginInvoke((iar) =>
            {
                this.DoMenthodByDispatcher(callback);
            }, null);
        }

        public void AsyncLoad<T>(Action<T> action, T para, Action callback)
        {
            IAsyncResult result = action.BeginInvoke(para, (iar) =>
            {
                this.DoMenthodByDispatcher(callback);
            }, null);
        }

        public void AsyncLoad<T, R>(Func<T, R> action, T para, Action<R> callback)
        {
            IAsyncResult result = action.BeginInvoke(para, (iar) =>
            {
                var res = action.EndInvoke(iar);
                this.DoMenthodByDispatcher<R>(callback, res);
            }, null);
        }

        public void AsyncLoad<R>(Func<R> action, Action<R> callback)
        {
            IAsyncResult result = action.BeginInvoke((iar) =>
            {
                var res = action.EndInvoke(iar);
                this.DoMenthodByDispatcher<R>(callback, res);
            }, null);
        }

        public void DoMenthodByDispatcher<T>(Action<T> action, T obj)
        {
            DispatcherHelper.GetUIDispatcher().BeginInvoke(new Action(() =>
            {
                action(obj);
            }), DispatcherPriority.Normal);
        }

        public void DoMenthodByDispatcher(Action action)
        {
            DispatcherHelper.GetUIDispatcher().BeginInvoke(new Action(() =>
            {
                action();
            }), DispatcherPriority.Normal);
        }
        #endregion  

        protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

BaseViewModel的代碼如上所示,主要實現了以下功能:

1,UI元素Window,Page,UserControl的創建;

2,基礎窗體方法,比如Show,Close,Message等等。

3,一系列線程切換的非同步操作。

4,簡潔化消息處理。(不理解的消息的可參看這篇文章C#語法——消息,MVVM的核心技術。

--------------------------------------------------------------------------------------------------------------------------------

這樣,BaseViewModel就編寫完成了,之後我們一起修改WPF項目,讓窗體的啟動的時候,使用ViewModel啟動。

在WPF項目中創建WindowMain窗體,併在VM中創建對應的ViewModel。

然後在App.Xaml.cs文件中重寫啟動函數,代碼如下:

protected override void OnStartup(StartupEventArgs e)
{
    VM_WindowMain vm = new VM_WindowMain();
    Application.Current.MainWindow = vm.UIElement as Window;
    vm.Show();
    base.OnStartup(e);
} 

在刪除App.Xaml的StartupUri屬性。

這樣運行WPF就會啟動我們的WindowMain窗體了。

ViewModel創建窗體

主窗體已經運行了,如果我們想運行其他窗體,該怎麼做呢?

很簡單,只要在主窗體的ViewModel中new那個想要運行的窗體的VM,然後Show一下就可以了。代碼如下:

VM_WindowCreateUser vm = new VM_WindowCreateUser();
vm.Show();

到此,窗體相關的內容我們已經一起編寫完成了。

接下來需要編寫的是Page和UserControl的基礎使用方式。

但Page和UserControl是被Window使用的,不能直接呈現,所以,在使用Page和UserControl之前,我們需要編寫MVVM框架中,用於在WPF頁面和ViewModel傳遞信息的Command(命令)。

本篇文章就先不介紹Command了,敬請期待下一篇文章,讓我們一起繼續完善我們的框架。

框架代碼已經傳到Github上了,並且會持續更新。

To be continued

Github地址:https://github.com/kiba518/KibaFramework

----------------------------------------------------------------------------------------------------

註:此文章為原創,歡迎轉載,請在文章頁面明顯位置給出此文鏈接!
若您覺得這篇文章還不錯,請點擊下右下角的推薦】,非常感謝!
如果您覺得這篇文章對您有所幫助,那就不妨支付寶小小打賞一下吧。 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在ASP.NET Core上利用MassTransit來集成使用RabbitMQ真的很簡單,代碼也很簡潔。近期因為項目需要,我便在這基礎上再次進行了封裝,抽成了公共方法,使得使用RabbitMQ的調用變得更方便簡潔。那麼,就讓咱們來瞧瞧其魅力所在吧。 ...
  • 一、添加覆選框 ArrayList arr = new ArrayList(); public string checkboxName = "選擇"; void StandLibWin_Load(object sender, EventArgs e) { DataGridViewCheckBoxCo ...
  • 自己在用的Excel操作類,因為經常在工作中要操作Excel文件,可是使用vba實現起來實在是不方便,而且編寫也很困難,拼接一個字元串都看的眼花。 這個時候C#出現了,發現使用C#來操作Excel非常方便,比VBA不知道高到哪裡去了,而且直接就可以上手,所以我就把常用的一些操作封裝成了一個類,編譯成 ...
  • 最近在做一個使用基於.net mvc 實現前後臺傳輸Json的實例。網上找了一些資料。發現在開發的時候,許多的數據交互都是以Json格式傳輸的。其中涉及序列化對象的使用的有DataContractJsonSerializer,JavaScriptSerializer和Json.net即Newtons ...
  • 大家可能在編碼中或多或少的使用過out的ref,但是是否註意過他兩的詳細用法以及區別? 本文想介紹下詳細介紹下out參數,ref參數以及一般值參數。 值參數 在使用參數時,把一個值傳遞給函數使用的一個變數。在函數中對此變數的任何修改都不影響函數調用中指定的參數。如下麵的函數,是使函數是使傳遞過來的參 ...
  • LitJson.dll下載地址 密碼:1znp 前一段時間一直糾結unity連接資料庫請求數據,浪費了不少時間。後來改用http請求,順利拿到數據,然後就著手於解析數據,就有了這篇文章 如果大家看不懂,這裡有一個視頻講的還是相當詳細的 ...
  • 1 DateTime beginTime = DateTime.Now.Date; 2 Console.WriteLine(beginTime); 3 DateTime endTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, Dat ...
  • 用 .net core 寫的 滑動+點擊漢字的驗證碼,代碼比較簡單就不做說明瞭。 github地址 https://github.com/wangchengqun/NetCoreVerificationCode ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...