淺談 WPF 項目框架搭建


 

在WPF項目開發中最常用的開發模式無疑是MVVM模式,  MVVM模式開發的好處,在這里就不詳細討論, 還有 本文中所使用MVVMLight框架,為什么使用MVVM框架(1、框架較輕,2、學習成本低、3、適用大多數中小型項目,4、相對於微軟的prism框架更容易上手)    下面開始 一步一步 搭建框架

 

第一步: 利用反射創建VM構造器

 public class ViewModelFactory
    {
        private static Dictionary<string, object> vmMap = new Dictionary<string, object>();
public static T GetViewModel<T>() where T : ViewModelBase { Type vmType = typeof(T); if (vmMap.ContainsKey(vmType.FullName)) { return (T)vmMap[vmType.FullName]; } else { object vm = Activator.CreateInstance(vmType); vmMap.Add(vmType.FullName, vm); return (T)vm; } } public static T GetViewModel<T>(object[] data,string id) where T : ViewModelBase { Type vmType = typeof(T); if (vmMap.ContainsKey(id)) { return (T)vmMap[id]; } else { object vm = Activator.CreateInstance(vmType, data); vmMap.Add(id, vm); return (T)vm; } } }

為什么用一個Dictionary  將ViewModel  緩存起來,相信利用MVVM模式開發大多數的開發者碰到的問題無疑是各個VM之間的數據通信問題,利用Dictionary緩存起來有兩個好處:

1、可以解決VM之間相互通信的問題(當然你也可以用MvvmLight的 Message機制來通信,PS:個人認為完全沒必要用MvvmLight中的 Messgae,如果我們框架搭的合理完全可以規避去用MvvmLight中 Message,Message比較難於管理,如果在我們的代碼中出現大量的Message無疑是一件痛苦的事情,所以筆者不推薦用MvvmLight中的Message) 

2、如果我們的應用程序要頻繁的與服務器做交互,我們完全可以用緩存,以避免每次都去請求服務器(可以緩存一些在應用程序中一直使用的數據,規避二次請求)

 public static T GetViewModel<T>() where T : ViewModelBase  這個函數(將我們的VM完全限定名作為KEY緩存)適用於單例模式的VM,

 public static T GetViewModel<T>(object[] data,string id) where T : ViewModelBase 這個函數(主要構件帶參數的VM構造函數,id是唯一ID),為什么會用到它,舉個例子

例如我們的QQ聊天窗口,所有聊天窗口基本相同用到的VM類型也是相同,所以這時候就需要多個VM實例了,第一種方法就行不通了 所以會用到這種方法去構建VM,並將id作為KEY值緩存起來

 

第二步:構建我們的ViewModel 基類:

  public delegate void CloseEventHandle(object sender);
    public class CustomViewModel : ViewModelBase
    {

        public event CloseEventHandle CloseEvent;
      protected bool hasData;

        public CustomViewModel()
        {
           LoadCommand = new RelayCommand(() =>
            {
                if (!hasData)
                {

                    ThreadPool.QueueUserWorkItem((obj) =>
                    {
                        lock (this)
                        {
                            OnLoad();
                            hasData = true;
                        }
                    });
                }
            });
        }public RelayCommand LoadCommand { private set; get; }

        protected virtual void OnLoad()
        {

        }

        protected void OnClose(object sender)
        {
            if (sender != null && CloseEvent != null)
            {
                CloseEvent(sender);
            }
        }
    }

上面CustomViewModel 繼承的ViewModelBase 是MvvmLight中的ViewModelBase,至於MvvmLight用法不在本文中討論,

1、為什么要聲明LoadCommand,因為大多數的時候我們會在窗體或用戶控件Loaded的時候去加載數據,有可能是異步加載,也有可能是同步加載,所以我們在CustomViewModel中

聲明省去了各個VM子類中去聲明LoadCommand的麻煩,使用時我們直接在XAML利用MvvmLight提供的EventToCommand 去綁定LoadCommand,然后在對應的VM去重寫CustomViewModel基類中的OnLoad方法就可以了。

2、CloseEvent 故名思議是用來在VM中關閉窗體用的(詳細用法會在下文中討論)

3、我們也可以將一些公有的數據都提煉到VM中來。

 

第三步  管理窗口:

  在開發程序的時候我們通常要去管理窗口的如果你沒用到MVVM模式 或者是傳統的Winform 你可以隨便的去new Window(),或者隨便的去改Window的構造函數,或者隨意的去構造單例窗體,但是如果用到了MVVM模式似乎以上所說的一切都變得復雜了,剛開始的時候我也是挺傷腦筋的,后來在不斷的重構代碼中找到了解決方法,(PS:本人也是一名菜鳥,只想把自己在開發中的問題及解決方法分享出來,未必就是好的解決方案,所以大神們勿噴)下面上代碼: 構建我們的ShowHelper類:

  public class ShowHelper
    {
        private static Dictionary<string, Window> windowManager = new Dictionary<string, Window>();
public static void ShowDiaglogUc<T>(string title, object[] constructors = null, bool isDialog = false) where T : UserControl { Type controlType = typeof(T); string key; if (constructors == null) //如果構造參數為null { key = controlType.FullName; //key = T 的完全限定名 } else { // 如果不為空 並且 第二個構造參數為string(第二個參數代表id -->有可能是GroupId 有可能是UserId); if (constructors.Length == 2 && constructors[1] is string)  //ps:這里本人寫死了可以根據需求自行修改 { key = controlType.FullName + constructors[1].ToString(); //key = 控件 完全限定名+id; } else //不滿足條件 { key = controlType.FullName; //key = 限定名 } } if (windowManager.ContainsKey(key)) //如果包含KEY { windowManager[key].Topmost = true; //設置TopMost return; } UserControl content; if (constructors == null) { content = Activator.CreateInstance(controlType) as UserControl; } else { content = Activator.CreateInstance(controlType, constructors) as UserControl; } BaseWindow window = new BaseWindow(); //PS這是自己封裝 的Window,(可以用直接用原始的Wpf Widnow) window.Title = title; windowManager.Add(key, window); window.Closed += (sen, cloE) => { windowManager.Remove(key); }; if (isDialog) { window.ShowDialog(); } else { window.Show(); } #region 注冊關閉事件 if (content.DataContext as CustomViewModel != null) { CustomViewModel vm = content.DataContext as CustomViewModel; vm.CloseEvent += (obj) => { if (content.DataContext.Equals(obj)) { window.Close(); } }; } #endregion } public static CustomDialogResult ShowOkCancleUC<T>(string title, MsgBoxBtn okCancle, out object data) where T : Control { Type vmType = typeof(T); Control content = Activator.CreateInstance(vmType) as Control; OkCanleWindow window = new OkCanleWindow(); window.ShowInTaskbar = false; return window.ShowDialog(title, okCancle, content, out data); } public static CustomDialogResult MessageBoxDialog(string title, string message, MsgBoxBtn okCancle) { OkCanleWindow window = new OkCanleWindow(); window.ShowInTaskbar = false; object none; return window.ShowDialog(title, okCancle, new MessageUC() { Message = message }, out none); }


1、(1)開始剖析 public static void ShowDiaglogUc<T>(string title, object[] constructors = null, bool isDialog = false) where T : UserControl
  ShowDialogUc  是用來在VM中用來創建UserControl並顯示在Window中的。你可能會問為啥用windowManager 將窗口緩存起來(PS這里主要還是為了解決單例窗口的麻煩),

  至於 下面這段代碼,我們可以回到創建的CustomerViewModel中,對這里需要注冊VM中CloseEvent事件,這樣我們在VM中就可以直接調用OnClose()方法就OK了

  #region 注冊關閉事件
            if (content.DataContext as CustomViewModel != null)
            {
                CustomViewModel vm = content.DataContext as CustomViewModel;
                vm.CloseEvent += (obj) =>
                {
                    if (content.DataContext.Equals(obj))
                    {
                        window.Close();
                    }
                };
            }
  #region 注冊關閉事件

  (2)開始剖析 public static void ShowDiaglogUc<T>(string title, object[] constructors = null, bool isDialog = false) where T : UserControl 函數中的 constructors 參數

  在開始剖析 constructors 之前先讓我們 聯想一下應用場景(可以先想下,QQ的聊天窗口,例如群聊天吧,所有的群聊天都是相同界面,也就是說他們所對應的VM應該是統一類型的      VM,如果我們雙擊群,則會彈出對應相應的聊天窗口,正常的思維是會給聊天窗口傳遞參數也就是組ID 這時候我們的VM就需要構造參數了,還有一個問題就是每個群組聊天窗口只能有一個,總不能每次雙擊就new一個聊天窗口了吧 所以這時候我們就需要做緩存了,) 綜上constructors參數在配合ViewModelFactory中的 public static T GetViewModel<T>(object[] data,string id) where T : ViewModelBase 方法  可以解決我們VM中需要傳遞參數的問題,windowManager 可以解決窗口緩存問題(如果你現在還看不明白請 仔細看上面代碼(雖然代碼有點渣),如果實在看不明白可以在留言板吐槽)。

 

2、 開始 剖析 public static CustomDialogResult ShowOkCancleUC<T>(string title, MsgBoxBtn okCancle, out object data) where T : Control

  (1)開始剖析該函數前讓我們 新建一個自己的帶返回值的 ShowDialog 窗口

     新建xaml窗口 

    

<controls:BaseWindow x:Class="Common.OkCanleWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:controls="clr-namespace:Controls;assembly=Controls"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MessageBoxWindow">
    <Grid x:Name="grid">
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Button Content="確   定" x:Name="okBtn" Click="okBtn_Click" Grid.Row="1" Height="30" Width="120" HorizontalAlignment="Right" Margin="0 0 10 0"/>
        <Button Content="取   消" x:Name="canleBtn" Click="canleBtn_Click" Grid.Row="1" Grid.Column="1" Height="30" Width="120" HorizontalAlignment="Left" Margin="10 0 0 0"/>

    </Grid>
</controls:BaseWindow>

         后台代碼:
    

 public partial class OkCanleWindow : BaseWindow
    {
        public OkCanleWindow()
        {
            InitializeComponent();
this.Closed += (s, e) =>
                {
                    if (result == CustomDialogResult.None)
                    {
                        result = CustomDialogResult.Cancel;
                    }

                };
        }
        private System.Windows.Controls.Control control;

        CustomDialogResult result;
        public CustomDialogResult ShowDialog(string title, MsgBoxBtn btnState, Control uc, out object dataContext)
        {
            #region  設置控件
            if (btnState == MsgBoxBtn.Ok) //如果為OK狀態
            {
                Grid.SetColumnSpan(okBtn, 2);  //設置OK按鈕跨兩列
                okBtn.HorizontalAlignment = System.Windows.HorizontalAlignment.Center; //設置OK按鈕居中對齊
                canleBtn.Visibility = System.Windows.Visibility.Collapsed; //設置Cancel 按鈕隱藏;
                if (uc != null)
                {
                    control = uc;     
                    Grid.SetRow(uc, 0);    //設置控件所在Grid 的行
                    Grid.SetColumnSpan(uc, 2); //設置控件所在Grid 的列
                    this.Width = uc.Width;    //設置窗體寬度
                    this.Height = uc.Height + grid.RowDefinitions[1].Height.Value + 35; //設置窗體寬度 高度
                    grid.Children.Add(uc);  //加入控件
                }
            }
            if (btnState == MsgBoxBtn.None)  //如果為None  既沒有OK 也沒有 Cancle
            {
                grid.RowDefinitions.RemoveAt(1);
                okBtn.Visibility = System.Windows.Visibility.Collapsed;
                canleBtn.Visibility = System.Windows.Visibility.Hidden;
                if(uc !=null)
                {
                    control = uc;
                    Grid.SetRow(uc, 0);    //設置控件所在Grid 的行
                    Grid.SetColumnSpan(uc, 2); //設置控件所在Grid 的列
                    this.Width = uc.Width;    //設置窗體寬度
                    this.Height = uc.Height + 35;
                    grid.Children.Add(uc);  //加入控件
                }
            }
            
            this.Title = title;
            dataContext = uc.DataContext;
            #endregion
            this.ShowDialog();return result;
        }

        private void okBtn_Click(object sender, RoutedEventArgs e)
        {
            result = CustomDialogResult.OK;
            this.Close();
        }

        private void canleBtn_Click(object sender, RoutedEventArgs e)
        {
            result = CustomDialogResult.Cancel;
            this.Close();
        }
    }

    public enum CustomDialogResult 
    {
        None,OK,Cancel
    }

    public enum MsgBoxBtn 
    {
        None,Ok,OkCancel
    }


 剖析 ShowDialog(string title, MsgBoxBtn btnState, Control uc, out object dataContext) 方法

 在Control uc 代表我們要ShowDialog的UC,dataContext 可以輸出一些數據,另外我們要自定義一些枚舉

 

 public static CustomDialogResult MessageBoxDialog(string title, string message, MsgBoxBtn okCancle)  主要用來顯示自定義MessageBoxUserControl;和上面得方法差不多,

本文目前先跟新到如果項目后期碰到什么問題會跟進本文;(轉載請注明出處)


免責聲明!

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



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