【我們一起寫框架】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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...