首页 > 编程 > C# > 正文

如何搭建新的WPF项目框架

2019-10-29 21:39:56
字体:大 中 小
来源:转载
供稿:网友

这篇文章主要介绍了如何搭建新的WPF项目框架,在项目开发中比较常见的开发模式就是MVVM模式,使用MVVM框架开发好处:1、框架较轻,2、学习成本低、3、适用大多数中小型项目,4、相对于微软的prism框架更容易上手,需要的朋友可以参考下

下面就WPF项目框架搭建步骤一步一步的分享给大家。

在WPF项目开发中最常用的开发模式无疑是MVVM模式, MVVM模式开发的好处,在这里就不详细讨论, 还有 本文中所使用MVVMLight框架,为什么使用MVVM框架(1、框架较轻,2、学习成本低、3、适用大多数中小型项目,4、相对于微软的prism框架更容易上手)

下面开始 一步一步 搭建框架

第一步: 利用反射创建VM构造器

 

 
  1. public class ViewModelFactory 
  2. { 
  3. private static Dictionary<string, object> vmMap = new Dictionary<string, object>();<br> 
  4. public static T GetViewModel<T>() where T : ViewModelBase 
  5. { 
  6. Type vmType = typeof(T); 
  7. if (vmMap.ContainsKey(vmType.FullName)) 
  8. { 
  9. return (T)vmMap[vmType.FullName]; 
  10. } 
  11. else 
  12. { 
  13. object vm = Activator.CreateInstance(vmType); 
  14. vmMap.Add(vmType.FullName, vm); 
  15. return (T)vm; 
  16. } 
  17. } 
  18.  
  19. public static T GetViewModel<T>(object[] data,string id) where T : ViewModelBase 
  20. { 
  21. Type vmType = typeof(T); 
  22. if (vmMap.ContainsKey(id)) 
  23. { 
  24. return (T)vmMap[id]; 
  25. } 
  26. else 
  27. { 
  28. object vm = Activator.CreateInstance(vmType, data); 
  29. vmMap.Add(id, vm); 
  30. return (T)vm; 
  31. } 
  32. } 
  33. } 

为什么用一个Dictionary 将ViewModel 缓存起来,相信利用MVVM模式开发大多数的开发者碰到的问题无疑是各个VM之间的数据通信问题,利用Dictionary缓存起来有两个好处:

1、可以解决VM之间相互通信的问题(当然你也可以用MvvmLight的 Message机制来通信,PS:个人认为完全没必要用MvvmLight中的 Messgae,如果我们框架搭的合理完全可以规避去用MvvmLight中 Message,Message比较难于管理,如果在我们的代码中出现大量的Message无疑是一件痛苦的事情,所以笔者不推荐用MvvmLight中的Message)

2、如果我们的应用程序要频繁的与服务器做交互,我们完全可以用缓存,以避免每次都去请求服务器(可以缓存一些在应用程序中一直使用的数据,规避二次请求)

public static T GetViewModel() where T : ViewModelBase 这个函数(将我们的VM完全限定名作为KEY缓存)适用于单例模式的VM,

public static T GetViewModel(object[] data,string id) where T : ViewModelBase 这个函数(主要构件带参数的VM构造函数,id是唯一ID),为什么会用到它,举个例子

例如我们的QQ聊天窗口,所有聊天窗口基本相同用到的VM类型也是相同,所以这时候就需要多个VM实例了,第一种方法就行不通了 所以会用到这种方法去构建VM,并将id作为KEY值缓存起来

第二步:构建我们的ViewModel 基类:

 

 
  1. public delegate void CloseEventHandle(object sender); 
  2. public class CustomViewModel : ViewModelBase 
  3. { 
  4.  
  5. public event CloseEventHandle CloseEvent; 
  6. protected bool hasData; 
  7.  
  8. public CustomViewModel() 
  9. { 
  10. LoadCommand = new RelayCommand(() => 
  11. { 
  12. if (!hasData) 
  13. { 
  14.  
  15. ThreadPool.QueueUserWorkItem((obj) => 
  16. { 
  17. lock (this) 
  18. { 
  19. OnLoad(); 
  20. hasData = true; 
  21. } 
  22. }); 
  23. } 
  24. }); 
  25. }public RelayCommand LoadCommand { private set; get; } 
  26.  
  27. protected virtual void OnLoad() 
  28. { 
  29.  
  30. } 
  31.  
  32. protected void OnClose(object sender) 
  33. { 
  34. if (sender != null && CloseEvent != null) 
  35. { 
  36. CloseEvent(sender); 
  37. } 
  38. } 
  39. } 

上面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类:

 

 
  1. public class ShowHelper 
  2. { 
  3. private static Dictionary<string, Window> windowManager = new Dictionary<string, Window>(); 
  4.  
  5. public static void ShowDiaglogUc<T>(string title, object[] constructors = null, bool isDialog = false) where T : UserControl 
  6. { 
  7. Type controlType = typeof(T); 
  8. string key; 
  9.  
  10. if (constructors == null) //如果构造参数为null 
  11. { 
  12. key = controlType.FullName; //key = T 的完全限定名 
  13. } 
  14. else 
  15. { 
  16. // 如果不为空 并且 第二个构造参数为string(第二个参数代表id -->有可能是GroupId 有可能是UserId); 
  17. if (constructors.Length == 2 && constructors[1] is string) //ps:这里本人写死了可以根据需求自行修改 
  18. { 
  19. key = controlType.FullName + constructors[1].ToString(); //key = 控件 完全限定名+id; 
  20. } 
  21. else //不满足条件 
  22. { 
  23. key = controlType.FullName; //key = 限定名 
  24. } 
  25.  
  26. } 
  27.  
  28. if (windowManager.ContainsKey(key)) //如果包含KEY 
  29. { 
  30. windowManager[key].Topmost = true; //设置TopMost 
  31. return; 
  32. } 
  33.  
  34. UserControl content; 
  35. if (constructors == null) 
  36. { 
  37. content = Activator.CreateInstance(controlType) as UserControl; 
  38. } 
  39. else 
  40. { 
  41. content = Activator.CreateInstance(controlType, constructors) as UserControl; 
  42. } 
  43.  
  44. BaseWindow window = new BaseWindow(); //PS这是自己封装 的Window,(可以用直接用原始的Wpf Widnow) 
  45. window.Title = title; 
  46. windowManager.Add(key, window); 
  47. window.Closed += (sen, cloE) => 
  48. { 
  49. windowManager.Remove(key); 
  50. }; 
  51. if (isDialog) 
  52. { 
  53. window.ShowDialog(); 
  54. } 
  55. else 
  56. { 
  57. window.Show(); 
  58. } 
  59. #region 注册关闭事件 
  60. if (content.DataContext as CustomViewModel != null) 
  61. { 
  62. CustomViewModel vm = content.DataContext as CustomViewModel; 
  63. vm.CloseEvent += (obj) => 
  64. { 
  65. if (content.DataContext.Equals(obj)) 
  66. { 
  67. window.Close(); 
  68. } 
  69. }; 
  70. } 
  71. #endregion 
  72. } 
  73.  
  74.  
  75. public static CustomDialogResult ShowOkCancleUC<T>(string title, MsgBoxBtn okCancle, out object data) where T : Control 
  76. { 
  77. Type vmType = typeof(T); 
  78. Control content = Activator.CreateInstance(vmType) as Control; 
  79. OkCanleWindow window = new OkCanleWindow(); 
  80. window.ShowInTaskbar = false; 
  81. return window.ShowDialog(title, okCancle, content, out data); 
  82. } 
  83.  
  84. public static CustomDialogResult MessageBoxDialog(string title, string message, MsgBoxBtn okCancle) 
  85. { 
  86. OkCanleWindow window = new OkCanleWindow(); 
  87. window.ShowInTaskbar = false; 
  88. object none; 
  89. return window.ShowDialog(title, okCancle, new MessageUC() { Message = message }, out none); 
  90. } 
(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了 
 
  1.   #region 注册关闭事件  
  2. if (content.DataContext as CustomViewModel != null)  
  3. {  
  4. CustomViewModel vm = content.DataContext as CustomViewModel;  
  5. vm.CloseEvent += (obj) =>  
  6. {  
  7. if (content.DataContext.Equals(obj))  
  8. {  
  9. window.Close();  
  10. }  
  11. };  
  12. }  
  13.   #region 注册关闭事件  

(2)开始剖析 public static void ShowDiaglogUc(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(object[] data,string id) where T : ViewModelBase 方法 可以解决我们VM中需要传递参数的问题,windowManager 可以解决窗口缓存问题(如果你现在还看不明白请 仔细看上面代码(虽然代码有点渣),如果实在看不明白可以在留言板吐槽)。

1、 开始 剖析 public static CustomDialogResult ShowOkCancleUC(string title, MsgBoxBtn okCancle, out object data) where T : Control

(1)开始剖析该函数前让我们 新建一个自己的带返回值的 ShowDialog 窗口

新建xaml窗口

 

 
  1. <controls:BaseWindow x:Class="Common.OkCanleWindow" 
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  3. xmlns:controls="clr-namespace:Controls;assembly=Controls" 
  4. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
  5. Title="MessageBoxWindow"> 
  6. <Grid x:Name="grid"> 
  7. <Grid.RowDefinitions> 
  8. <RowDefinition/> 
  9. <RowDefinition Height="50"/> 
  10. </Grid.RowDefinitions> 
  11. <Grid.ColumnDefinitions> 
  12. <ColumnDefinition/> 
  13. <ColumnDefinition/> 
  14. </Grid.ColumnDefinitions> 
  15. <Button Content="确 定" x:Name="okBtn" Click="okBtn_Click" Grid.Row="1" Height="30" Width="120" HorizontalAlignment="Right" Margin="0 0 10 0"/> 
  16. <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"/> 
  17.  
  18. </Grid> 
  19. </controls:BaseWindow> 
  20.  
  21. 后台代码: 
  22.  
  23. public partial class OkCanleWindow : BaseWindow 
  24. { 
  25. public OkCanleWindow() 
  26. { 
  27. InitializeComponent(); 
  28. this.Closed += (s, e) => 
  29. { 
  30. if (result == CustomDialogResult.None) 
  31. { 
  32. result = CustomDialogResult.Cancel; 
  33. } 
  34.  
  35. }; 
  36. } 
  37. private System.Windows.Controls.Control control; 
  38.  
  39. CustomDialogResult result; 
  40. public CustomDialogResult ShowDialog(string title, MsgBoxBtn btnState, Control uc, out object dataContext) 
  41. { 
  42. #region 设置控件 
  43. if (btnState == MsgBoxBtn.Ok) //如果为OK状态 
  44. { 
  45. Grid.SetColumnSpan(okBtn, 2); //设置OK按钮跨两列 
  46. okBtn.HorizontalAlignment = System.Windows.HorizontalAlignment.Center; //设置OK按钮居中对齐 
  47. canleBtn.Visibility = System.Windows.Visibility.Collapsed; //设置Cancel 按钮隐藏; 
  48. if (uc != null) 
  49. { 
  50. control = uc;  
  51. Grid.SetRow(uc, 0); //设置控件所在Grid 的行 
  52. Grid.SetColumnSpan(uc, 2); //设置控件所在Grid 的列 
  53. this.Width = uc.Width; //设置窗体宽度 
  54. this.Height = uc.Height + grid.RowDefinitions[1].Height.Value + 35; //设置窗体宽度 高度 
  55. grid.Children.Add(uc); //加入控件 
  56. } 
  57. } 
  58. if (btnState == MsgBoxBtn.None) //如果为None 既没有OK 也没有 Cancle 
  59. { 
  60. grid.RowDefinitions.RemoveAt(1); 
  61. okBtn.Visibility = System.Windows.Visibility.Collapsed; 
  62. canleBtn.Visibility = System.Windows.Visibility.Hidden; 
  63. if(uc !=null) 
  64. { 
  65. control = uc; 
  66. Grid.SetRow(uc, 0); //设置控件所在Grid 的行 
  67. Grid.SetColumnSpan(uc, 2); //设置控件所在Grid 的列 
  68. this.Width = uc.Width; //设置窗体宽度 
  69. this.Height = uc.Height + 35; 
  70. grid.Children.Add(uc); //加入控件 
  71. } 
  72. } 
  73.  
  74. this.Title = title; 
  75. dataContext = uc.DataContext; 
  76. #endregion 
  77. this.ShowDialog();return result; 
  78. } 
  79.  
  80. private void okBtn_Click(object sender, RoutedEventArgs e) 
  81. { 
  82. result = CustomDialogResult.OK; 
  83. this.Close(); 
  84. } 
  85.  
  86. private void canleBtn_Click(object sender, RoutedEventArgs e) 
  87. { 
  88. result = CustomDialogResult.Cancel; 
  89. this.Close(); 
  90. } 
  91. } 
  92.  
  93. public enum CustomDialogResult  
  94. { 
  95. None,OK,Cancel 
  96. } 
  97.  
  98. public enum MsgBoxBtn  
  99. { 
  100. None,Ok,OkCancel 
  101. } 

剖析 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;和上面得方法差不多,

以上分为三大步骤对WPF 项目框架搭建的介绍,并结合代码做剖析,希望对大家有所帮助。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表