一、目的:在使用Asp.net Core时,深感MVC框架作为页面跳转数据处理的方便,但WPF中似乎没有现成的MVC框架,由此自定义开发一套MVC的框架,在使用过程中也体会到框架的优势,下面简要介绍一下这套基于MVVM的MVC框架
二、项目结构:
主要有三部分组成:Controller、View、ViewModel
其中View和ViewModel就是传统WPF中的MVVM模式
不同地方在于页面的跳转应用到了Controller做控制,如下示例Controller的定义
二、Controller的结构和定义
1、定义LoyoutController
- [Route("Loyout")]
- class LoyoutController : Controller
- {
-
- public LoyoutController(ShareViewModel shareViewModel) : base(shareViewModel)
- {
-
- }
-
- public async Task<IActionResult> Center()
- {
- return View();
- }
-
- [Route("OverView/Button")]
- public async Task<IActionResult> Mdi()
- {
- return View();
- }
-
- public async Task<IActionResult> Left()
- {
- return View();
- }
-
- public async Task<IActionResult> Right()
- {
- return View();
- }
-
- public async Task<IActionResult> Top()
- {
- return View();
- }
-
- public async Task<IActionResult> Bottom()
- {
- return View();
- }
-
- [Route("OverView/Toggle")]
- public async Task<IActionResult> Toggle()
- {
- return View();
- }
-
- [Route("OverView/Carouse")]
- public async Task<IActionResult> Carouse()
- {
- return View();
- }
-
- [Route("OverView/Evaluate")]
- public async Task<IActionResult> Evaluate()
- {
- return View();
- }
-
- [Route("OverView/Expander")]
- public async Task<IActionResult> Expander()
- {
- return View();
- }
-
- [Route("OverView/Gif")]
- public async Task<IActionResult> Gif()
- {
- return View();
- }
-
- [Route("OverView/Message")]
- public async Task<IActionResult> Message()
- {
- return View();
- }
-
- [Route("OverView/Upgrade")]
- public async Task<IActionResult> Upgrade()
- {
- return View();
- }
-
- [Route("OverView/Property")]
- public async Task<IActionResult> Property()
- {
- return View();
- }
-
- [Route("OverView/ProgressBar")]
- public async Task<IActionResult> ProgressBar()
- {
- return View();
- }
-
- [Route("OverView/Slider")]
- public async Task<IActionResult> Slider()
- {
- return View();
- }
-
- [Route("OverView/Tab")]
- public async Task<IActionResult> Tab()
- {
- return View();
- }
-
- [Route("OverView/Tree")]
- public async Task<IActionResult> Tree()
- {
- return View();
- }
-
- [Route("OverView/Observable")]
- public async Task<IActionResult> Observable()
- {
- return View();
- }
-
- [Route("OverView/Brush")]
- public async Task<IActionResult> Brush()
- {
- return View();
- }
-
- [Route("OverView/Shadow")]
- public async Task<IActionResult> Shadow()
- {
- return View();
- }
-
- [Route("OverView/Button")]
- public async Task<IActionResult> Button()
- {
- await MessageService.ShowWaittingMessge(() => Thread.Sleep(500));
-
- this.ViewModel.ButtonContentText = DateTime.Now.ToString();
-
- return View();
-
- }
-
-
-
- [Route("OverView/Grid")]
- public async Task<IActionResult> Grid()
- {
- return View();
- }
-
- [Route("OverView/Combobox")]
- public async Task<IActionResult> Combobox()
- {
- return View();
- }
-
- [Route("OverView")]
- public async Task<IActionResult> OverView()
- {
- await MessageService.ShowWaittingMessge(() => Thread.Sleep(500));
-
- MessageService.ShowSnackMessageWithNotice("OverView");
-
- return View();
- }
-
- [Route("OverView/TextBox")]
- public async Task<IActionResult> TextBox()
- {
- return View();
- }
-
- [Route("OverView/Book")]
- public async Task<IActionResult> Book()
- {
- return View();
- }
-
- [Route("OverView/Xaml")]
- public async Task<IActionResult> Xaml()
- {
- return View();
- }
-
- [Route("OverView/Dimension")]
- public async Task<IActionResult> Dimension()
- {
- return View();
- }
-
- [Route("OverView/Geometry")]
- public async Task<IActionResult> Geometry()
- {
- return View();
- }
-
- [Route("OverView/Panel")]
- public async Task<IActionResult> Panel()
- {
- return View();
- }
- [Route("OverView/Transform3D")]
- public async Task<IActionResult> Transform3D()
- {
- return View();
- }
-
- [Route("OverView/Drawer")]
- public async Task<IActionResult> Drawer()
- {
- return View();
- }
- }
2、前端的页面
如下
其中红色部分对应Controller里面的要跳转的Route
如:选择了红色部分的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、示例:
4、左侧的Xaml列表可以定义成如下形式:
- <Grid>
- <wpfcontrollib:LinkGroupExpander ScrollViewer.HorizontalScrollBarVisibility="Disabled" x:Name="selectloyout"
- SelectedLink="{Binding SelectLink,Mode=TwoWay}"
- Command="{x:Static wpfcontrollib:DrawerHost.CloseDrawerCommand}"
- CommandParameter="{x:Static Dock.Left}">
- <wpfcontrollib:LinkActionGroup DisplayName="基础控件" Logo="">
- <wpfcontrollib:LinkActionGroup.Links>
- <wpfcontrollib:LinkAction DisplayName="Button" Logo="" Controller="Loyout" Action="Button" />
- <wpfcontrollib:LinkAction DisplayName="TextBox" Logo="" Controller="Loyout" Action="TextBox"/>
- <wpfcontrollib:LinkAction DisplayName="Combobox" Logo="" Controller="Loyout" Action="Combobox" />
- <wpfcontrollib:LinkAction DisplayName="Toggle" Logo="" Controller="Loyout" Action="Toggle"/>
- <wpfcontrollib:LinkAction DisplayName="Evaluate" Logo="" Controller="Loyout" Action="Evaluate"/>
- <wpfcontrollib:LinkAction DisplayName="Expander" Logo="" Controller="Loyout" Action="Expander"/>
- <wpfcontrollib:LinkAction DisplayName="Gif" Logo="" Controller="Loyout" Action="Gif"/>
- <wpfcontrollib:LinkAction DisplayName="ProgressBar" Logo="" Controller="Loyout" Action="ProgressBar"/>
- <wpfcontrollib:LinkAction DisplayName="Slider" Logo="" Controller="Loyout" Action="Slider"/>
- </wpfcontrollib:LinkActionGroup.Links>
- </wpfcontrollib:LinkActionGroup>
-
- <wpfcontrollib:LinkActionGroup DisplayName="布局控件" Logo="">
- <wpfcontrollib:LinkActionGroup.Links>
- <wpfcontrollib:LinkAction DisplayName="MdiControl" Logo="" Controller="Loyout" Action="Mdi"/>
- <wpfcontrollib:LinkAction DisplayName="Carouse" Logo="" Controller="Loyout" Action="Carouse"/>
- <wpfcontrollib:LinkAction DisplayName="Tab" Logo="" Controller="Loyout" Action="Tab"/>
- <wpfcontrollib:LinkAction DisplayName="Tree" Logo="" Controller="Loyout" Action="Tree"/>
- <wpfcontrollib:LinkAction DisplayName="ObservableSource" Logo="" Controller="Loyout" Action="Observable"/>
- <wpfcontrollib:LinkAction DisplayName="Property" Logo="" Controller="Loyout" Action="Property"/>
- <wpfcontrollib:LinkAction DisplayName="Panel" Logo="" Controller="Loyout" Action="Panel"/>
- </wpfcontrollib:LinkActionGroup.Links>
-
- </wpfcontrollib:LinkActionGroup>
-
- <wpfcontrollib:LinkActionGroup DisplayName="全局控件" Logo="">
- <wpfcontrollib:LinkActionGroup.Links>
- <wpfcontrollib:LinkAction DisplayName="Message" Logo="" Controller="Loyout" Action="Message"/>
- <wpfcontrollib:LinkAction DisplayName="Upgrade" Logo="" Controller="Loyout" Action="Upgrade"/>
- <wpfcontrollib:LinkAction DisplayName="Drawer" Logo="" Controller="Loyout" Action="Drawer"/>
- </wpfcontrollib:LinkActionGroup.Links>
- </wpfcontrollib:LinkActionGroup>
-
- <wpfcontrollib:LinkActionGroup DisplayName="全局样式" Logo="">
- <wpfcontrollib:LinkActionGroup.Links>
- <wpfcontrollib:LinkAction DisplayName="Brush" Logo="" Controller="Loyout" Action="Brush"/>
- <wpfcontrollib:LinkAction DisplayName="Shadow" Logo="" Controller="Loyout" Action="Shadow"/>
-
- </wpfcontrollib:LinkActionGroup.Links>
- </wpfcontrollib:LinkActionGroup>
-
- </wpfcontrollib:LinkGroupExpander>
- </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返回给主页面进行页面跳转,代码如下:
- public abstract class ControllerBase : IController
- {
- protected virtual IActionResult View([CallerMemberName] string name = "")
- {
- var route = this.GetType().GetCustomAttributes(typeof(RouteAttribute), true).Cast<RouteAttribute>();
-
- string controlName = null;
-
- if (route.FirstOrDefault() == null)
- {
- controlName = this.GetType().Name;
- }
- else
- {
- controlName = route.FirstOrDefault().Name;
- }
-
- var ass = Assembly.GetEntryAssembly().GetName();
-
- string path = $"/{ass.Name};component/View/{controlName}/{name}Control.xaml";
-
- Uri uri = new Uri(path, UriKind.RelativeOrAbsolute);
-
- var content = Application.Current.Dispatcher.Invoke(() =>
- {
- return Application.LoadComponent(uri);
- });
-
- ActionResult result = new ActionResult();
-
- result.Uri = uri;
- result.View = content as ContentControl;
-
- Type type = Assembly.GetEntryAssembly().GetTypeOfMatch<NotifyPropertyChanged>(l => l.Name == controlName + "ViewModel");
-
- result.ViewModel = ServiceRegistry.Instance.GetInstance(type);
-
- Application.Current.Dispatcher.Invoke(() =>
- {
- (result.View as FrameworkElement).DataContext = result.ViewModel;
-
- });
-
- return result;
- }
-
-
- protected virtual IActionResult LinkAction([CallerMemberName] string name = "")
- {
- var route = this.GetType().GetCustomAttributes(typeof(RouteAttribute), true).Cast<RouteAttribute>();
-
- string controlName = null;
-
- if (route.FirstOrDefault() == null)
- {
- controlName = this.GetType().Name;
- }
- else
- {
- controlName = route.FirstOrDefault().Name;
- }
-
- var ass = Assembly.GetEntryAssembly().GetName();
-
- string path = $"/{ass.Name};component/View/{controlName}/{name}Control.xaml";
-
- Uri uri = new Uri(path, UriKind.RelativeOrAbsolute);
-
- var content = Application.Current.Dispatcher.Invoke(() =>
- {
- return Application.LoadComponent(uri);
- });
-
- ActionResult result = new ActionResult();
-
- result.Uri = uri;
- result.View = content;
-
- Type type = Assembly.GetEntryAssembly().GetTypeOfMatch<NotifyPropertyChanged>(l => l.Name == controlName + "ViewModel");
-
- result.ViewModel = ServiceRegistry.Instance.GetInstance(type);
-
- Application.Current.Dispatcher.Invoke(() =>
- {
- (result.View as FrameworkElement).DataContext = result.ViewModel;
- });
-
- return result;
- }
-
- }
说明:
1、通过Application.LoadComponent(uri);来加载生成Control
2、通过反射ViewModel基类NotifyPropertyChanged去找到对应ViewModel,绑定到View中
3、将View和ViewModel封装到IActionResult中返回给主页面进行加载
其中Controller中的方法返回类型是async Task<IActionResult>,也就是整个页面跳转都是在异步中进行的,可以有效的避免页面切换中的卡死效果
三、View中的结构和定义
其中View在项目中的定义就是根据Controller中的方法对应,在MVC中要严格按照结构定义[View/Loyout],好处是可以减少代码量,同时使格式统一代码整齐,结构如下:
其中红色ButtonControl.xaml即是Controller中Button()方法要跳转的页面,其他页面同理
四、ViewModel的结构和定义
其中LoyoutViewModel即是LoyoutController和整个View/Loyout下所有页面对应的ViewModel
五、整体MVC结构实现的效果如下
以上就是MVC应用在WPF中的简要示例,具体内容和示例可从如下链接中下载代码查看
代码地址:https://github.com/HeBianGu/WPF-ControlBase.git
另一个应用Sqlite数据库的示例如下
代码地址:https://github.com/HeBianGu/WPF-ExplorerManager.git