封装:简要介绍自定义开发基于WPF的MVC框架

原文:封装:简要介绍自定义开发基于WPF的MVC框架

一、目的:在使用Asp.net Core时,深感MVC框架作为页面跳转数据处理的方便,但WPF中似乎没有现成的MVC框架,由此自定义开发一套MVC的框架,在使用过程中也体会到框架的优势,下面简要介绍一下这套基于MVVM的MVC框架

二、项目结构:

封装:简要介绍自定义开发基于WPF的MVC框架

主要有三部分组成:Controller、View、ViewModel

其中View和ViewModel就是传统WPF中的MVVM模式

不同地方在于页面的跳转应用到了Controller做控制,如下示例Controller的定义

二、Controller的结构和定义

1、定义LoyoutController


  1. [Route("Loyout")]
  2. class LoyoutController : Controller
  3. {
  4. public LoyoutController(ShareViewModel shareViewModel) : base(shareViewModel)
  5. {
  6. }
  7. public async Task<IActionResult> Center()
  8. {
  9. return View();
  10. }
  11. [Route("OverView/Button")]
  12. public async Task<IActionResult> Mdi()
  13. {
  14. return View();
  15. }
  16. public async Task<IActionResult> Left()
  17. {
  18. return View();
  19. }
  20. public async Task<IActionResult> Right()
  21. {
  22. return View();
  23. }
  24. public async Task<IActionResult> Top()
  25. {
  26. return View();
  27. }
  28. public async Task<IActionResult> Bottom()
  29. {
  30. return View();
  31. }
  32. [Route("OverView/Toggle")]
  33. public async Task<IActionResult> Toggle()
  34. {
  35. return View();
  36. }
  37. [Route("OverView/Carouse")]
  38. public async Task<IActionResult> Carouse()
  39. {
  40. return View();
  41. }
  42. [Route("OverView/Evaluate")]
  43. public async Task<IActionResult> Evaluate()
  44. {
  45. return View();
  46. }
  47. [Route("OverView/Expander")]
  48. public async Task<IActionResult> Expander()
  49. {
  50. return View();
  51. }
  52. [Route("OverView/Gif")]
  53. public async Task<IActionResult> Gif()
  54. {
  55. return View();
  56. }
  57. [Route("OverView/Message")]
  58. public async Task<IActionResult> Message()
  59. {
  60. return View();
  61. }
  62. [Route("OverView/Upgrade")]
  63. public async Task<IActionResult> Upgrade()
  64. {
  65. return View();
  66. }
  67. [Route("OverView/Property")]
  68. public async Task<IActionResult> Property()
  69. {
  70. return View();
  71. }
  72. [Route("OverView/ProgressBar")]
  73. public async Task<IActionResult> ProgressBar()
  74. {
  75. return View();
  76. }
  77. [Route("OverView/Slider")]
  78. public async Task<IActionResult> Slider()
  79. {
  80. return View();
  81. }
  82. [Route("OverView/Tab")]
  83. public async Task<IActionResult> Tab()
  84. {
  85. return View();
  86. }
  87. [Route("OverView/Tree")]
  88. public async Task<IActionResult> Tree()
  89. {
  90. return View();
  91. }
  92. [Route("OverView/Observable")]
  93. public async Task<IActionResult> Observable()
  94. {
  95. return View();
  96. }
  97. [Route("OverView/Brush")]
  98. public async Task<IActionResult> Brush()
  99. {
  100. return View();
  101. }
  102. [Route("OverView/Shadow")]
  103. public async Task<IActionResult> Shadow()
  104. {
  105. return View();
  106. }
  107. [Route("OverView/Button")]
  108. public async Task<IActionResult> Button()
  109. {
  110. await MessageService.ShowWaittingMessge(() => Thread.Sleep(500));
  111. this.ViewModel.ButtonContentText = DateTime.Now.ToString();
  112. return View();
  113. }
  114. [Route("OverView/Grid")]
  115. public async Task<IActionResult> Grid()
  116. {
  117. return View();
  118. }
  119. [Route("OverView/Combobox")]
  120. public async Task<IActionResult> Combobox()
  121. {
  122. return View();
  123. }
  124. [Route("OverView")]
  125. public async Task<IActionResult> OverView()
  126. {
  127. await MessageService.ShowWaittingMessge(() => Thread.Sleep(500));
  128. MessageService.ShowSnackMessageWithNotice("OverView");
  129. return View();
  130. }
  131. [Route("OverView/TextBox")]
  132. public async Task<IActionResult> TextBox()
  133. {
  134. return View();
  135. }
  136. [Route("OverView/Book")]
  137. public async Task<IActionResult> Book()
  138. {
  139. return View();
  140. }
  141. [Route("OverView/Xaml")]
  142. public async Task<IActionResult> Xaml()
  143. {
  144. return View();
  145. }
  146. [Route("OverView/Dimension")]
  147. public async Task<IActionResult> Dimension()
  148. {
  149. return View();
  150. }
  151. [Route("OverView/Geometry")]
  152. public async Task<IActionResult> Geometry()
  153. {
  154. return View();
  155. }
  156. [Route("OverView/Panel")]
  157. public async Task<IActionResult> Panel()
  158. {
  159. return View();
  160. }
  161. [Route("OverView/Transform3D")]
  162. public async Task<IActionResult> Transform3D()
  163. {
  164. return View();
  165. }
  166. [Route("OverView/Drawer")]
  167. public async Task<IActionResult> Drawer()
  168. {
  169. return View();
  170. }
  171. }

2、前端的页面

如下

其中红色部分对应Controller里面的要跳转的Route

封装:简要介绍自定义开发基于WPF的MVC框架

如:选择了红色部分的Button,首先会调用Button()方法,跳转到当前Controller对应的View文件加下的ButtonControl.xaml页面

[Route("OverView/Button")]

        public async Task<IActionResult> Button()

        {

            await MessageService.ShowWaittingMessge(() => Thread.Sleep(500)

;

this.ViewModel.ButtonContentText = DateTime.Now.ToString();

return View();

}

可以在Button()方法中,写一些业务逻辑,如对当前ViewModel的增删改查等常规操作,其中当前Controller成员ViewModel是内部封装好的ViewModel,对应ViewModel文件下面的当前Controller的ViewModel

3、示例:

封装:简要介绍自定义开发基于WPF的MVC框架

4、左侧的Xaml列表可以定义成如下形式:


  1. <Grid>
  2. <wpfcontrollib:LinkGroupExpander ScrollViewer.HorizontalScrollBarVisibility="Disabled" x:Name="selectloyout"
  3. SelectedLink="{Binding SelectLink,Mode=TwoWay}"
  4. Command="{x:Static wpfcontrollib:DrawerHost.CloseDrawerCommand}"
  5. CommandParameter="{x:Static Dock.Left}">
  6. <wpfcontrollib:LinkActionGroup DisplayName="基础控件" Logo="">
  7. <wpfcontrollib:LinkActionGroup.Links>
  8. <wpfcontrollib:LinkAction DisplayName="Button" Logo="" Controller="Loyout" Action="Button" />
  9. <wpfcontrollib:LinkAction DisplayName="TextBox" Logo="" Controller="Loyout" Action="TextBox"/>
  10. <wpfcontrollib:LinkAction DisplayName="Combobox" Logo="" Controller="Loyout" Action="Combobox" />
  11. <wpfcontrollib:LinkAction DisplayName="Toggle" Logo="" Controller="Loyout" Action="Toggle"/>
  12. <wpfcontrollib:LinkAction DisplayName="Evaluate" Logo="" Controller="Loyout" Action="Evaluate"/>
  13. <wpfcontrollib:LinkAction DisplayName="Expander" Logo="" Controller="Loyout" Action="Expander"/>
  14. <wpfcontrollib:LinkAction DisplayName="Gif" Logo="" Controller="Loyout" Action="Gif"/>
  15. <wpfcontrollib:LinkAction DisplayName="ProgressBar" Logo="" Controller="Loyout" Action="ProgressBar"/>
  16. <wpfcontrollib:LinkAction DisplayName="Slider" Logo="" Controller="Loyout" Action="Slider"/>
  17. </wpfcontrollib:LinkActionGroup.Links>
  18. </wpfcontrollib:LinkActionGroup>
  19. <wpfcontrollib:LinkActionGroup DisplayName="布局控件" Logo="">
  20. <wpfcontrollib:LinkActionGroup.Links>
  21. <wpfcontrollib:LinkAction DisplayName="MdiControl" Logo="" Controller="Loyout" Action="Mdi"/>
  22. <wpfcontrollib:LinkAction DisplayName="Carouse" Logo="" Controller="Loyout" Action="Carouse"/>
  23. <wpfcontrollib:LinkAction DisplayName="Tab" Logo="" Controller="Loyout" Action="Tab"/>
  24. <wpfcontrollib:LinkAction DisplayName="Tree" Logo="" Controller="Loyout" Action="Tree"/>
  25. <wpfcontrollib:LinkAction DisplayName="ObservableSource" Logo="" Controller="Loyout" Action="Observable"/>
  26. <wpfcontrollib:LinkAction DisplayName="Property" Logo="" Controller="Loyout" Action="Property"/>
  27. <wpfcontrollib:LinkAction DisplayName="Panel" Logo="" Controller="Loyout" Action="Panel"/>
  28. </wpfcontrollib:LinkActionGroup.Links>
  29. </wpfcontrollib:LinkActionGroup>
  30. <wpfcontrollib:LinkActionGroup DisplayName="全局控件" Logo="">
  31. <wpfcontrollib:LinkActionGroup.Links>
  32. <wpfcontrollib:LinkAction DisplayName="Message" Logo="" Controller="Loyout" Action="Message"/>
  33. <wpfcontrollib:LinkAction DisplayName="Upgrade" Logo="" Controller="Loyout" Action="Upgrade"/>
  34. <wpfcontrollib:LinkAction DisplayName="Drawer" Logo="" Controller="Loyout" Action="Drawer"/>
  35. </wpfcontrollib:LinkActionGroup.Links>
  36. </wpfcontrollib:LinkActionGroup>
  37. <wpfcontrollib:LinkActionGroup DisplayName="全局样式" Logo="">
  38. <wpfcontrollib:LinkActionGroup.Links>
  39. <wpfcontrollib:LinkAction DisplayName="Brush" Logo="" Controller="Loyout" Action="Brush"/>
  40. <wpfcontrollib:LinkAction DisplayName="Shadow" Logo="" Controller="Loyout" Action="Shadow"/>
  41. </wpfcontrollib:LinkActionGroup.Links>
  42. </wpfcontrollib:LinkActionGroup>
  43. </wpfcontrollib:LinkGroupExpander>
  44. </Grid>

通过LinkGroupExpander控件,封装LinkAction去实现页面的跳转,其中只需要定义LinkAction的几个属性即可达到跳转到指定页面的效果,如:

Controller属性:用来指示要跳转到哪个Controller

Action属性:用来指示跳转到哪个方法

DisplayName属性:在UI中显示的名称

Logo属性:在UI中显示的图标

如下,Controller中的Button()方法对应的跳转配置如下

[Route("OverView/Button")]

public async Task<IActionResult> Button()

<wpfcontrollib:LinkAction DisplayName="Button" Logo=""  Controller="Loyout" Action="Button" />

4、Controller基类的定义ControllerBase

主要方法是IActionResult View([CallerMemberName] string name = ""),这个方法是MVC实现的核心功能,主要通过反射去动态加载程序集,加载项目结构中的View、ViewModel去生成IActionResult返回给主页面进行页面跳转,代码如下:


  1. public abstract class ControllerBase : IController
  2. {
  3. protected virtual IActionResult View([CallerMemberName] string name = "")
  4. {
  5. var route = this.GetType().GetCustomAttributes(typeof(RouteAttribute), true).Cast<RouteAttribute>();
  6. string controlName = null;
  7. if (route.FirstOrDefault() == null)
  8. {
  9. controlName = this.GetType().Name;
  10. }
  11. else
  12. {
  13. controlName = route.FirstOrDefault().Name;
  14. }
  15. var ass = Assembly.GetEntryAssembly().GetName();
  16. string path = $"/{ass.Name};component/View/{controlName}/{name}Control.xaml";
  17. Uri uri = new Uri(path, UriKind.RelativeOrAbsolute);
  18. var content = Application.Current.Dispatcher.Invoke(() =>
  19. {
  20. return Application.LoadComponent(uri);
  21. });
  22. ActionResult result = new ActionResult();
  23. result.Uri = uri;
  24. result.View = content as ContentControl;
  25. Type type = Assembly.GetEntryAssembly().GetTypeOfMatch<NotifyPropertyChanged>(l => l.Name == controlName + "ViewModel");
  26. result.ViewModel = ServiceRegistry.Instance.GetInstance(type);
  27. Application.Current.Dispatcher.Invoke(() =>
  28. {
  29. (result.View as FrameworkElement).DataContext = result.ViewModel;
  30. });
  31. return result;
  32. }
  33. protected virtual IActionResult LinkAction([CallerMemberName] string name = "")
  34. {
  35. var route = this.GetType().GetCustomAttributes(typeof(RouteAttribute), true).Cast<RouteAttribute>();
  36. string controlName = null;
  37. if (route.FirstOrDefault() == null)
  38. {
  39. controlName = this.GetType().Name;
  40. }
  41. else
  42. {
  43. controlName = route.FirstOrDefault().Name;
  44. }
  45. var ass = Assembly.GetEntryAssembly().GetName();
  46. string path = $"/{ass.Name};component/View/{controlName}/{name}Control.xaml";
  47. Uri uri = new Uri(path, UriKind.RelativeOrAbsolute);
  48. var content = Application.Current.Dispatcher.Invoke(() =>
  49. {
  50. return Application.LoadComponent(uri);
  51. });
  52. ActionResult result = new ActionResult();
  53. result.Uri = uri;
  54. result.View = content;
  55. Type type = Assembly.GetEntryAssembly().GetTypeOfMatch<NotifyPropertyChanged>(l => l.Name == controlName + "ViewModel");
  56. result.ViewModel = ServiceRegistry.Instance.GetInstance(type);
  57. Application.Current.Dispatcher.Invoke(() =>
  58. {
  59. (result.View as FrameworkElement).DataContext = result.ViewModel;
  60. });
  61. return result;
  62. }
  63. }

说明:

1、通过Application.LoadComponent(uri);来加载生成Control

2、通过反射ViewModel基类NotifyPropertyChanged去找到对应ViewModel,绑定到View中

3、将View和ViewModel封装到IActionResult中返回给主页面进行加载

其中Controller中的方法返回类型是async Task<IActionResult>,也就是整个页面跳转都是在异步中进行的,可以有效的避免页面切换中的卡死效果

三、View中的结构和定义

其中View在项目中的定义就是根据Controller中的方法对应,在MVC中要严格按照结构定义[View/Loyout],好处是可以减少代码量,同时使格式统一代码整齐,结构如下:

封装:简要介绍自定义开发基于WPF的MVC框架

其中红色ButtonControl.xaml即是Controller中Button()方法要跳转的页面,其他页面同理

四、ViewModel的结构和定义

其中LoyoutViewModel即是LoyoutController和整个View/Loyout下所有页面对应的ViewModel

封装:简要介绍自定义开发基于WPF的MVC框架

五、整体MVC结构实现的效果如下

封装:简要介绍自定义开发基于WPF的MVC框架

以上就是MVC应用在WPF中的简要示例,具体内容和示例可从如下链接中下载代码查看

代码地址:https://github.com/HeBianGu/WPF-ControlBase.git

另一个应用Sqlite数据库的示例如下

代码地址:https://github.com/HeBianGu/WPF-ExplorerManager.git

上一篇:(转)基于 WPF + Modern UI 的 公司OA小助手 开发总结


下一篇:【CS Round #44 (Div. 2 only) C】Check DFS