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

本文介绍了一种在WPF中实现MVC模式的方法,通过自定义Controller、View和ViewModel组件,实现了页面跳转和数据处理的高效管理。文章详细解释了Controller如何控制页面跳转,以及如何利用反射技术动态加载View和ViewModel。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、目的:在使用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="&#xe69f;">
                <wpfcontrollib:LinkActionGroup.Links>
                    <wpfcontrollib:LinkAction  DisplayName="Button" Logo="&#xe69f;"  Controller="Loyout" Action="Button" />
                    <wpfcontrollib:LinkAction  DisplayName="TextBox"  Logo="&#xe6a3;" Controller="Loyout"  Action="TextBox"/>
                    <wpfcontrollib:LinkAction  DisplayName="Combobox"  Logo="&#xe6a3;" Controller="Loyout" Action="Combobox"  />
                    <wpfcontrollib:LinkAction  DisplayName="Toggle"  Logo="&#xe6a3;" Controller="Loyout" Action="Toggle"/>
                    <wpfcontrollib:LinkAction  DisplayName="Evaluate" Logo="&#xe69f;" Controller="Loyout" Action="Evaluate"/>
                    <wpfcontrollib:LinkAction  DisplayName="Expander" Logo="&#xe69f;" Controller="Loyout" Action="Expander"/>
                    <wpfcontrollib:LinkAction  DisplayName="Gif" Logo="&#xe69f;" Controller="Loyout" Action="Gif"/>
                    <wpfcontrollib:LinkAction  DisplayName="ProgressBar" Logo="&#xe69f;" Controller="Loyout" Action="ProgressBar"/>
                    <wpfcontrollib:LinkAction  DisplayName="Slider" Logo="&#xe69f;" Controller="Loyout" Action="Slider"/>
                </wpfcontrollib:LinkActionGroup.Links>
            </wpfcontrollib:LinkActionGroup>

            <wpfcontrollib:LinkActionGroup DisplayName="布局控件" Logo="&#xe69f;">
                <wpfcontrollib:LinkActionGroup.Links>
                    <wpfcontrollib:LinkAction  DisplayName="MdiControl" Logo="&#xe69f;" Controller="Loyout" Action="Mdi"/>
                    <wpfcontrollib:LinkAction  DisplayName="Carouse" Logo="&#xe69e;" Controller="Loyout" Action="Carouse"/>
                    <wpfcontrollib:LinkAction  DisplayName="Tab" Logo="&#xe69f;" Controller="Loyout" Action="Tab"/>
                    <wpfcontrollib:LinkAction  DisplayName="Tree" Logo="&#xe69f;" Controller="Loyout" Action="Tree"/>
                    <wpfcontrollib:LinkAction  DisplayName="ObservableSource" Logo="&#xe69f;" Controller="Loyout" Action="Observable"/>
                    <wpfcontrollib:LinkAction  DisplayName="Property" Logo="&#xe69f;" Controller="Loyout" Action="Property"/>
                    <wpfcontrollib:LinkAction  DisplayName="Panel" Logo="&#xe69f;" Controller="Loyout" Action="Panel"/> 
                </wpfcontrollib:LinkActionGroup.Links>
            
            </wpfcontrollib:LinkActionGroup>

            <wpfcontrollib:LinkActionGroup DisplayName="全局控件" Logo="&#xe69f;">
                <wpfcontrollib:LinkActionGroup.Links>
                    <wpfcontrollib:LinkAction  DisplayName="Message" Logo="&#xe69f;" Controller="Loyout" Action="Message"/>
                    <wpfcontrollib:LinkAction  DisplayName="Upgrade" Logo="&#xe69e;" Controller="Loyout" Action="Upgrade"/>
                    <wpfcontrollib:LinkAction  DisplayName="Drawer" Logo="&#xe69f;" Controller="Loyout" Action="Drawer"/> 
                </wpfcontrollib:LinkActionGroup.Links>
            </wpfcontrollib:LinkActionGroup>

            <wpfcontrollib:LinkActionGroup DisplayName="全局样式" Logo="&#xe69f;">
                <wpfcontrollib:LinkActionGroup.Links>
                    <wpfcontrollib:LinkAction  DisplayName="Brush" Logo="&#xe69f;" Controller="Loyout" Action="Brush"/>
                    <wpfcontrollib:LinkAction  DisplayName="Shadow" Logo="&#xe69f;" 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="&#xe69f;"  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

 

  

需要了解的知识点

System.Windows.Controls 命名空间 | Microsoft Learn

了解更多

GitHub - HeBianGu/WPF-ControlDemo: 示例

GitHub - HeBianGu/WPF-ControlBase: Wpf封装的自定义控件资源库

GitHub - HeBianGu/WPF-Control: WPF轻量控件和皮肤库

System.Windows.Controls 命名空间 | Microsoft Learn

HeBianGu (HeBianGu) · GitHub

HeBianGu的个人空间-HeBianGu个人主页-哔哩哔哩视频

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值