WPF入门到精通:3.MVVM简单应用及全局异常处理

MVVM简介

在WPF应用程序开发中,MVVM(Model-View-ViewModel)是一种非常流行的架构模式。它为应用程序的设计提供了良好的分层结构和可扩展性。

结构分为下列三部分

  1. Model:定义了应用程序的数据模型 就是系统中的对象,可包含属性和行为(是一个class实体,是对现实中事物的抽象,开发过程中涉及到的事物都可以抽象为Model,例如用户的账号、密码、电话等),负责从数据源中获取数据并将其提供给ViewModel。

  2. ViewModel:封装了应用程序的业务逻辑,通过View类的DataContext属性绑定到View,负责将数据从Model传递到View,并将用户交互事件传递回Model。显示数据对应ViewMode中的Property,执行命令对应ViewModel中的Command。

  3. View:用xaml实现的界面,接收用户输入,把数据展现给用户,并与ViewModel交互以便进行数据绑定和命令绑定。

在MVVM模式中,ViewModel的主要职责是将数据从Model传递到View,并响应View的用户交互事件。ViewModel通过命令绑定和数据绑定与View进行交互。

MVVM优点

  1. 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
  2. 灵活扩展:可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑;
  3. 易测试:可以针对ViewModel来写测试用例
  4. 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于xaml页面设计。

MVVM示例

以下是一个简单的WPF MVVM登录示例:

底层通用实体

ViewModelBase

添加一个viewmodelbase方便后续数据双向绑定更新

cs 复制代码
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace YourProjectName.Comm
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void PC(string propertyName)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public void PCEH([CallerMemberName] string propertyName = null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

CommandBase

添加一个继承ICommand实体,方便后续绑定command

cs 复制代码
using System;
using System.Windows.Input;

namespace YourProjectName.Comm
{
    public class CommandBase : ICommand
    {
        private readonly Action<object> _execute;

        private readonly Func<object, bool> _canExecute;

        public event EventHandler CanExecuteChanged
        {
            add
            {
                CommandManager.RequerySuggested += value;
            }
            remove
            {
                CommandManager.RequerySuggested -= value;
            }
        }

        public CommandBase(Action<object> execute)
            : this(execute, null)
        {
        }

        public CommandBase(Action<object> execute, Func<object, bool> canExecute)
        {
            _execute = execute ?? throw new ArgumentNullException("execute");
            _canExecute = canExecute ?? ((Func<object, bool>)((object x) => true));
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        public void Refresh()
        {
            CommandManager.InvalidateRequerySuggested();
        }
    }
}

ViewModel及Model

在ViewModel文件夹下,创建一个名为LoginViewModel的类。 因为只有两个字段测试所以未新建Model,实际开发中注意新建

cs 复制代码
using System.Windows.Input;
using YourProjectName.Comm;


namespace YourProjectName.ViewModel 
{
    public class LoginViewModel : ViewModelBase 
    {
        private string _username;
        private string _password;
        private bool _rememberMe;

        public string Username 
        {
            get { return _username; }
            set { _username= value; PCEH();}
        }

        public string Password 
        {
            get { return _password; }
            set { _password= value; PCEH();}
        }

        public bool RememberMe 
        {
            get { return _rememberMe; }
                  set { _rememberMe= value; PCEH();}
        }

        public ICommand LoginCommand;

        private void Login() 
        {
   LoginCommand=  new CommandBase(async l =>
           {
            // TODO: Add login logic.
});
        }
    }
}

View

在View文件夹下,创建一个名为LoginView的XAML和CS文件。

cs 复制代码
<Window x:Class="YourProjectName.View.LoginView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:YourProjectName.View"
        xmlns:vm="clr-namespace:YourProjectName.ViewModel"
        mc:Ignorable="d"
        Title="LoginView" Height="300" Width="400">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Label Grid.Row="0" Grid.Column="0" Content="Username:" />
        <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Username}" />
        <Label Grid.Row="1" Grid.Column="0" Content="Password:" />
        <PasswordBox Grid.Row="1" Grid.Column="1" Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}" />
        <CheckBox Grid.Row="2" Grid.Column="1" Content="Remember me" IsChecked="{Binding RememberMe}" />
        <Button Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Content="Login" Command="{Binding LoginCommand}" />
        <TextBlock Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Text="Don't have an account? Sign up." />
    </Grid>
</Window>

初始化DataContext ,也可在页面中初始化

cs 复制代码
using System.Windows;

namespace YourProjectName.View 
{
    public partial class LoginView : Window 
    {
   private readonly LoginViewModel model;
        public LoginView() 
        {
            InitializeComponent();
      DataContext = model = new();
        }
    }
}

启动应用程序并测试登录页面。在"TODO"注释的位置添加实际的登录代码。

全局异常处理

在WPF应用程序中,全局异常处理非常重要。全局异常处理可以帮助我们捕获应用程序中的所有未处理异常,防止程序异常崩溃并提供更好的用户体验。在WPF中,可以通过在应用程序的App.xaml.cs文件中添加以下代码来实现全局异常处理:

异常处理示例

cs 复制代码
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
        DispatcherUnhandledException += App_DispatcherUnhandledException;
       TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
    }

    private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
    {
        //处理UI线程上的未处理异常
        e.Handled = true;
        MessageBox.Show("发生错误:" + e.Exception.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
    }

    private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        //处理非UI线程上的未处理异常
        Exception ex = (Exception)e.ExceptionObject;
        MessageBox.Show("发生错误:" + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
    }

  /// <summary>
        /// Task线程内未捕获异常处理事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs ex)
        {
 MessageBox.Show("Task线程异常:" + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
            //设置该异常已察觉(这样处理后就不会引起程序崩溃)
            e.SetObserved();
        }
}

以上代码会在应用程序启动时为当前域的未处理异常事件和UI线程上的未处理异常事件及task线程异常注册处理程序。在发生异常时,会弹出一个消息框来通知用户并不会造成程序崩溃。

总结:

MVVM模式是WPF应用程序开发中非常重要的主题。通过理解和实战练习,我们可以更好地开发出强大和稳定的应用程序。

全局异常处理是程序中必不可少的一步,能保障程序在异常发生过程中正常运行。

相关推荐
月落.4 小时前
WPF的<ContentControl>控件
wpf
就是有点傻4 小时前
WPF中的依赖属性
开发语言·wpf
wangnaisheng4 小时前
【WPF】把一个Window放在左上角/右上角顶格显示
wpf
WineMonk4 小时前
.NET WPF CommunityToolkit.Mvvm框架
.net·wpf·mvvm
月落.4 小时前
WPF中的INotifyPropertyChanged接口
wpf
界面开发小八哥4 小时前
界面控件DevExpress WPF中文教程:Data Grid——卡片视图设置
.net·wpf·界面控件·devexpress·ui开发
平凡シンプル4 小时前
WPF 打包
wpf
VickyJames4 小时前
基于XAML框架和跨平台项目架构设计的深入技术分析
wpf·开源分享·unoplatform·winui3·项目架构
冷眼Σ(-᷅_-᷄๑)8 小时前
WPF缩放动画和平移动画叠加后会发生什么?
wpf·动画
△曉風殘月〆10 小时前
WPF MVVM入门系列教程(二、依赖属性)
c#·wpf·mvvm