一、目的:在使用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数据库的示例如下