WPF自定义MessageBox:基于消息中介模式实现UI与业务逻辑解耦 📅 发布时间:2026/9/4 23:03:33 👁 浏览次数: 简介本资源是一个面向WPF开发者的自定义消息框解决方案适用于需要解耦UI与业务逻辑的中高级C#开发者解决传统MessageBox界面僵化、难以定制及跨项目复用困难的问题。资源包共21个文件包含9个C#逻辑类如MessageBoxHelper.cs、各XAML窗口对应的代码后置文件、5个XAML界面模板支持Toast、Simple等多种样式、1个配置文件、1个解决方案及项目配置文件等整体仅14KB轻量易集成。已有1133人学习下载体现了其在实际项目中的实用价值。读者可直接复用封装完备的MessageBoxHelper泛型调用机制通过替换XAML即可快速适配不同UI风格同时获得完整可运行的WPF工程结构涵盖资源字典、设置管理、本地化支持.resx及标准项目配置为构建统一交互组件体系提供即开即用的参考范式。1. 项目概述为什么我们需要一个“操作分离”的自定义MessageBox在WPF桌面应用开发中MessageBox是开发者与用户进行简单信息交互的“标配”。无论是提示一个操作成功还是询问用户“是否确认删除”我们都会习惯性地调用System.Windows.MessageBox.Show()。然而用久了你会发现这个系统自带的“黑盒子”存在几个让人头疼的痛点界面风格与你的应用主题格格不入像个闯入者按钮的文本和数量固定想加个“稍后提醒”或“查看详情”按钮没门更重要的是它的调用是阻塞式的模态对话框一旦弹出整个UI线程就被“冻住”直到用户点击。如果你想在后台执行一个耗时操作比如上传文件并在操作的不同阶段开始、进度、完成、失败弹出不同样式的提示用原生的MessageBox会非常别扭要么阻塞主线程导致界面卡死要么需要自己另起线程和Dispatcher来操作代码变得支离破碎。这就是“操作分离”的自定义MessageBox要解决的问题。它不是一个简单的皮肤替换而是一种架构上的改进。核心思想是将消息的展示逻辑与触发消息的业务逻辑彻底解耦。业务代码只需要说“我需要弹出一个提示内容是XXX类型是成功3秒后自动关闭”而完全不用关心这个提示框长什么样、在哪里显示、如何动画弹出。展示层则负责接收这些“指令”并以一种统一、可定制、非阻塞的方式渲染到界面上。这带来的好处是巨大的UI可以灵活定制以保持应用风格统一交互可以更丰富支持自定义按钮、输入框、进度条等最重要的是它不会阻塞UI线程让应用在后台处理任务时依然保持流畅响应。结合热搜词来看很多开发者正在寻找wpf界面设计、wpf mvvm、c#多线程以及c# 上位机wpf例程的解决方案。一个操作分离的MessageBox正是实现优雅界面、响应式交互和复杂业务逻辑如c#对西门子plc数据采集的基石。它让那些需要处理c#串口助手通信、c#实现ble蓝牙通信状态反馈或是在wpf开发mes系统中需要频繁提示操作结果的场景代码变得清晰且易于维护。2. 核心架构设计从“调用-阻塞”到“发布-订阅”要实现操作分离关键在于改变通信模式。传统的MessageBox.Show()是直接的、同步的调用。而我们需要的是一个间接的、异步的通知机制。在WPF的生态里有几种成熟的模式可以实现这一点。2.1 方案选型为什么是Messenger/EventAggregator首先想到的可能是直接在ViewModel中暴露一个属性比如CurrentMessage然后View通过绑定来显示。这在简单场景下可行但当你的应用有多个独立的模块、窗口甚至需要从非ViewModel层如一个后台服务类触发消息时这种紧耦合的方式就会导致复杂的依赖传递。更优雅的方案是使用一种轻量级的消息中介模式。MVVM Light Toolkit中的Messenger或者Prism框架中的EventAggregator都是为此而生。它们的作用就像一个全局的“广播站”或“邮局”。任何地方发布者都可以发送一条消息而任何对此消息感兴趣的地方订阅者都可以接收到并处理双方不需要知道彼此的存在。对于我们的自定义MessageBox我强烈推荐使用这种模式。理由如下彻底解耦业务逻辑代码只需发送消息完全不知道也不关心消息由谁、以何种方式呈现。这完美符合“操作分离”的定义。跨组件通信无论消息是从主窗口ViewModel、一个子控件还是一个纯粹的后台线程中发出的消息都能被统一的消息展示器接收。易于测试业务逻辑的单元测试可以轻松模拟消息发送而无需创建真实的UI。灵活性高我们可以定义多种消息类型纯文本提示、带确认的询问、带进度的通知等订阅者可以根据类型决定不同的呈现方式。虽然热搜词里有c#委托和c#事件它们也能实现一定程度的解耦但在跨线程、跨模块的复杂场景下管理和维护事件订阅关系会变得非常繁琐。消息中介模式提供了一个更干净、更标准的解决方案。2.2 消息数据模型设计承载意图的载体消息本身需要携带足够的信息告诉展示层“如何做”。我们需要设计一个消息数据模型Message Model。这个模型是一个简单的POCO普通C#对象类。public class MessageBoxMessage { // 消息内容 public string Content { get; set; } // 消息标题 public string Caption { get; set; } // 消息类型决定图标、颜色等 public MessageType Type { get; set; } MessageType.Info; // 预设的按钮组合 public MessageBoxButton Buttons { get; set; } MessageBoxButton.OK; // 用户自定义的按钮列表优先级高于预设Buttons public IEnumerableMessageBoxButtonConfig CustomButtons { get; set; } // 是否模态阻塞后续交互 public bool IsModal { get; set; } false; // 自动关闭延时毫秒为0则不自动关闭 public int AutoCloseDelay { get; set; } 0; // 回调Action当用户做出选择后执行 public ActionMessageBoxResult Callback { get; set; } } // 消息类型枚举 public enum MessageType { Info, // 信息 Success, // 成功 Warning, // 警告 Error, // 错误 Question // 询问 } // 自定义按钮配置 public class MessageBoxButtonConfig { public string Content { get; set; } // 按钮显示文本 public MessageBoxResult Result { get; set; } // 点击后返回的结果 public bool IsDefault { get; set; } false; // 是否为默认按钮 public bool IsCancel { get; set; } false; // 是否为取消按钮 }设计要点解析CustomButtons属性提供了极高的灵活性你可以定义任意文本和行为的按钮比如“重试”、“忽略”、“详情”等这直接回应了原生MessageBox按钮固定的痛点。IsModal属性保留了模拟原生阻塞行为的能力虽然我们鼓励非阻塞但某些关键操作如“确认格式化硬盘”仍需强制用户响应。AutoCloseDelay是实现“Toast”式轻量提示的关键对于成功提示设置2-3秒后自动消失可以极大提升体验。Callback是连接用户操作与业务逻辑的桥梁。当用户点击按钮后通过这个回调通知发送方用户的选择实现了异步的“等待-响应”。2.3 展示层设计可插拔的呈现器消息的展示应该由一个独立的、通常位于UI层顶部的组件负责我们称之为MessageBoxService或MessagePresenter。它通常被放置在MainWindow的顶层容器中例如一个Grid或Canvas的顶层通过较高的ZIndex确保显示在最前面。这个展示器的主要职责是订阅消息在构造函数或初始化时订阅特定的消息类型例如MessageBoxMessage。管理消息队列考虑到可能短时间内连续收到多条消息需要一个队列来管理它们的显示顺序避免重叠。渲染UI根据消息模型的数据动态创建或控制一个自定义的用户控件MessageBoxWindow或MessageBoxControl的显示。处理交互监听用户对自定义控件中按钮的点击关闭提示并调用消息中携带的Callback。这种设计使得更换整个提示框的UI样式变得极其简单你只需要替换这个MessagePresenter所控制的用户控件模板而所有业务代码无需任何改动。3. 分步实现从零搭建消息中枢与展示器接下来我们抛开任何第三方MVVM框架用最纯粹的C#和WPF来实现一个核心可用的自定义MessageBox系统。我们将使用一个简单的静态消息中介类来模拟Messenger的功能。3.1 第一步实现简易消息中介器我们先创建一个全局的消息中介类。在实际大型项目中你可能希望将其设计为单例或通过依赖注入容器管理但这里为了简洁我们使用静态类。// SimpleMessenger.cs public static class SimpleMessenger { // 使用字典来存储类型与对应处理器列表的映射 private static readonly DictionaryType, Listobject _handlers new DictionaryType, Listobject(); // 订阅消息 public static void RegisterTMessage(ActionTMessage handler) { var messageType typeof(TMessage); if (!_handlers.ContainsKey(messageType)) { _handlers[messageType] new Listobject(); } _handlers[messageType].Add(handler); } // 取消订阅在实际应用中很重要防止内存泄漏 public static void UnregisterTMessage(ActionTMessage handler) { var messageType typeof(TMessage); if (_handlers.ContainsKey(messageType)) { _handlers[messageType].Remove(handler); } } // 发送消息 public static void SendTMessage(TMessage message) { var messageType typeof(TMessage); if (_handlers.ContainsKey(messageType)) { // 为了安全遍历副本。因为处理器在执行时可能会修改订阅列表。 var handlers _handlers[messageType].ToList(); foreach (var handler in handlers) { ((ActionTMessage)handler)?.Invoke(message); } } } }注意这个简易实现未考虑线程安全。在WPF中如果消息可能从非UI线程发送你需要在Send方法内部将最终对UI控件的操作派发Dispatcher.Invoke到UI线程。更健壮的做法是在订阅时允许指定同步上下文SynchronizationContext或者像Prism的EventAggregator那样在发布时选择线程选项。3.2 第二步创建自定义消息框用户控件这是UI定制的核心。我们将创建一个UserControl其XAML定义了提示框的外观后台代码处理交互逻辑。XAML部分 (CustomMessageBox.xaml): 这里创建一个带有背景遮罩、圆角边框、图标、内容和按钮面板的控件。UserControl x:ClassYourNamespace.CustomMessageBox ... UserControl.Resources !-- 定义不同消息类型的颜色和图标 -- SolidColorBrush x:KeyInfoColor Color#17a2b8/ SolidColorBrush x:KeySuccessColor Color#28a745/ !-- ... 其他颜色 -- PathGeometry x:KeyInfoIcon.../PathGeometry !-- ... 其他图标几何图形 -- /UserControl.Resources Grid x:NameRootGrid Background#80000000 VisibilityCollapsed !-- 半透明遮罩层 -- Border CornerRadius8 BackgroundWhite Width400 VerticalAlignmentCenter HorizontalAlignmentCenter Grid Margin20 Grid.RowDefinitions RowDefinition HeightAuto/ RowDefinition Height*/ RowDefinition HeightAuto/ /Grid.RowDefinitions !-- 标题栏 -- StackPanel Grid.Row0 OrientationHorizontal Margin0,0,0,10 Path Data{Binding IconGeometry} Fill{Binding TypeColor} Width20 Height20 Margin0,0,10,0/ TextBlock Text{Binding Caption} FontWeightBold FontSize16 VerticalAlignmentCenter/ /StackPanel !-- 内容区 -- ScrollViewer Grid.Row1 MaxHeight200 VerticalScrollBarVisibilityAuto TextBlock Text{Binding Content} TextWrappingWrap/ /ScrollViewer !-- 按钮区 -- ItemsControl Grid.Row2 ItemsSource{Binding ButtonConfigs} Margin0,20,0,0 ItemsControl.ItemsPanel ItemsPanelTemplate StackPanel OrientationHorizontal HorizontalAlignmentRight/ /ItemsPanelTemplate /ItemsControl.ItemsPanel ItemsControl.ItemTemplate DataTemplate Button Content{Binding Content} Margin5,0,0,0 Padding15,5 Command{Binding DataContext.ButtonClickCommand, RelativeSource{RelativeSource AncestorTypeUserControl}} CommandParameter{Binding Result}/ /DataTemplate /ItemsControl.ItemTemplate /ItemsControl /Grid /Border /Grid /UserControl后台代码 (CustomMessageBox.xaml.cs):public partial class CustomMessageBox : UserControl, INotifyPropertyChanged { public CustomMessageBox() { InitializeComponent(); this.DataContext this; ButtonClickCommand new RelayCommandMessageBoxResult(OnButtonClicked); } // 依赖属性或通知属性用于绑定 private string _caption; public string Caption { get _caption; set { _caption value; OnPropertyChanged(); } } // ... 为Content, TypeColor, IconGeometry, ButtonConfigs等定义类似的属性 public ICommand ButtonClickCommand { get; } // 显示消息框的方法 public void Show(MessageBoxMessage message) { // 1. 将message中的数据映射到当前控件的绑定属性 this.Caption message.Caption; this.Content message.Content; this.TypeColor GetColorByType(message.Type); this.IconGeometry GetIconByType(message.Type); // 2. 生成按钮配置列表 var buttonConfigs new ListMessageBoxButtonConfig(); if (message.CustomButtons ! null message.CustomButtons.Any()) { buttonConfigs.AddRange(message.CustomButtons); } else { // 根据预设的Buttons枚举生成默认按钮 buttonConfigs.AddRange(GenerateDefaultButtons(message.Buttons)); } this.ButtonConfigs buttonConfigs; // 3. 存储回调 _callback message.Callback; // 4. 显示控件可以加入动画 this.Visibility Visibility.Visible; RootGrid.Visibility Visibility.Visible; // 可以在这里添加Storyboard实现淡入、缩放等动画效果 // 5. 处理自动关闭 if (message.AutoCloseDelay 0) { var closeTimer new DispatcherTimer { Interval TimeSpan.FromMilliseconds(message.AutoCloseDelay) }; closeTimer.Tick (s, e) { closeTimer.Stop(); Close(MessageBoxResult.None); }; closeTimer.Start(); } } private void OnButtonClicked(MessageBoxResult result) { Close(result); } private void Close(MessageBoxResult result) { // 执行关闭动画然后隐藏 // ... this.Visibility Visibility.Collapsed; _callback?.Invoke(result); _callback null; // 清理回调 } private ActionMessageBoxResult _callback; // ... INotifyPropertyChanged实现和辅助方法 }3.3 第三步创建全局消息展示服务这个服务是连接消息中介和UI控件的桥梁。它通常是一个单例在应用启动时初始化。// MessageBoxService.cs public class MessageBoxService { private readonly CustomMessageBox _messageBoxControl; private readonly QueueMessageBoxMessage _messageQueue new QueueMessageBoxMessage(); private bool _isShowingMessage false; public MessageBoxService(Window mainWindow) { // 1. 找到主窗口的容器并将我们的自定义控件添加进去 var container mainWindow.FindName(MessageBoxContainer) as Panel; if (container null) { // 如果主窗口没有预留容器可以动态创建一个覆盖层 var grid mainWindow.Content as Grid; if (grid ! null) { _messageBoxControl new CustomMessageBox(); Grid.SetRowSpan(_messageBoxControl, grid.RowDefinitions.Count); Grid.SetColumnSpan(_messageBoxControl, grid.ColumnDefinitions.Count); grid.Children.Add(_messageBoxControl); } } else { _messageBoxControl new CustomMessageBox(); container.Children.Add(_messageBoxControl); } // 2. 订阅消息 SimpleMessenger.RegisterMessageBoxMessage(EnqueueMessage); } private void EnqueueMessage(MessageBoxMessage message) { // 将消息加入队列 _messageQueue.Enqueue(message); // 如果当前没有正在显示的消息则立即显示下一个 if (!_isShowingMessage) { ShowNextMessage(); } } private void ShowNextMessage() { if (_messageQueue.Count 0) { _isShowingMessage false; return; } _isShowingMessage true; var nextMessage _messageQueue.Dequeue(); // 确保在UI线程上执行显示操作 Application.Current.Dispatcher.Invoke(() { _messageBoxControl.Show(nextMessage); // 假设CustomMessageBox在关闭时会触发一个事件 // 这里我们简化处理使用一个延时来模拟消息关闭后显示下一个 // 实际应在CustomMessageBox的Close方法中触发一个“MessageClosed”事件 _messageBoxControl.MessageClosed OnCurrentMessageClosed; }); } private void OnCurrentMessageClosed(object sender, EventArgs e) { _messageBoxControl.MessageClosed - OnCurrentMessageClosed; // 显示队列中的下一条消息 ShowNextMessage(); } }在App.xaml.cs或主窗口初始化中启动服务public partial class MainWindow : Window { private MessageBoxService _messageService; public MainWindow() { InitializeComponent(); _messageService new MessageBoxService(this); } }3.4 第四步在业务代码中使用现在在任何地方你都可以像下面这样触发一个自定义提示而无需引用任何UI组件// 在某个ViewModel或后台服务中 public void UploadFile() { // 发送一个“开始上传”的提示非模态自动关闭 SimpleMessenger.Send(new MessageBoxMessage { Caption 系统提示, Content 文件上传开始..., Type MessageType.Info, AutoCloseDelay 1500 }); try { // 模拟上传操作 Task.Run(() { // 上传逻辑... Thread.Sleep(2000); // 上传成功发送成功提示 Application.Current.Dispatcher.Invoke(() { SimpleMessenger.Send(new MessageBoxMessage { Caption 上传成功, Content 文件已成功上传至服务器。, Type MessageType.Success, AutoCloseDelay 3000 }); }); }); } catch (Exception ex) { // 上传失败发送错误提示带重试按钮 SimpleMessenger.Send(new MessageBoxMessage { Caption 上传失败, Content $上传过程中发生错误{ex.Message}, Type MessageType.Error, CustomButtons new ListMessageBoxButtonConfig { new MessageBoxButtonConfig { Content 重试, Result MessageBoxResult.Retry, IsDefault true }, new MessageBoxButtonConfig { Content 取消, Result MessageBoxResult.Cancel, IsCancel true } }, Callback result { if (result MessageBoxResult.Retry) { UploadFile(); // 重新执行上传 } } }); } }4. 高级功能扩展与实战技巧基础框架搭建完成后我们可以根据实际需求为其添加更多强大的功能使其成为一个真正专业级的组件。4.1 支持模态与非模态的混合管理我们的设计默认是非模态的。但某些危险操作需要强制用户响应。我们可以在MessageBoxMessage模型中增加一个IsModal属性。在MessageBoxService中当收到一个模态消息时需要暂停消息队列的处理直到当前模态消息被关闭。这可以通过在EnqueueMessage和ShowNextMessage方法中加入对IsModal的判断逻辑来实现模态消息显示时_isShowingMessage保持为true且不处理队列中的下一条。4.2 丰富的内容模板现在的消息内容只是简单的文本。我们可以扩展MessageBoxMessage模型加入一个ContentTemplate属性类型可以是DataTemplate或一个标识模板的键。在CustomMessageBox的XAML中将固定的TextBlock替换为一个ContentControl并将其ContentTemplate绑定到这个属性。这样你就可以发送包含ProgressBar、CheckBox甚至复杂表单的消息了。这对于需要用户输入验证码或确认选项的场景非常有用。4.3 动画与视觉效果一个优秀的UI组件离不开流畅的动画。在CustomMessageBox.Show()和Close()方法中不要直接设置Visibility而是启动一个Storyboard。显示动画可以从透明度0到1的淡入结合从0.9到1.0的缩放营造出“弹出”感。关闭动画淡出或者向上滑出屏幕。 使用WPF的RenderTransform和Opacity属性结合DoubleAnimation可以轻松实现。确保动画完成后再真正隐藏控件或触发关闭事件避免画面撕裂。4.4 与MVVM框架深度集成如果你在使用Prism或MVVM Light等框架我们的实现可以做得更优雅。替换SimpleMessenger直接使用框架提供的IEventAggregator或IMessenger。在MessageBoxService中注入IEventAggregator并订阅特定事件在业务层则通过它来发布事件。依赖注入将IMessageBoxService接口化并通过DI容器如Prism的IContainerExtension将其注册为单例。这样任何需要弹出消息的类只需要在构造函数中请求IMessageBoxService即可完全解耦了具体的消息发送方式。区域支持在Prism中你甚至可以将CustomMessageBox控件放在一个特定的区域Region中由区域管理器来控制其显示实现更灵活的布局。4.5 性能优化与内存管理控件复用频繁创建和销毁CustomMessageBox控件会有开销。可以考虑使用对象池Object Pool模式。在MessageBoxService中维护一个控件的实例池显示时从池中取出并设置数据关闭后重置状态并放回池中而不是每次都new一个。事件解绑在CustomMessageBox的Close方法中务必解绑所有临时事件处理器如自动关闭的Timer.Tick事件防止内存泄漏。线程安全强化完善SimpleMessenger在_handlers的增删改查操作上加锁lock语句确保在多线程环境下安全。或者在Send方法中使用SynchronizationContext来确保处理器在正确的线程上执行对于UI更新至关重要。5. 常见问题、调试技巧与避坑指南在实际开发和使用过程中你肯定会遇到一些“坑”。以下是我从多个项目中总结出来的经验。5.1 消息不显示检查订阅和线程问题调用了SimpleMessenger.Send但界面上没有任何反应。排查步骤订阅时机确保MessageBoxService即消息订阅者的初始化发生在你第一次发送消息之前。通常在主窗口构造函数或Loaded事件中初始化是安全的。线程派发这是最常见的问题。如果你在非UI线程如Task.Run或后台Worker中发送消息而你的CustomMessageBox.Show()方法内部直接操作了UI控件属性就会抛出InvalidOperationException“调用线程无法访问此对象…”。解决方案在SimpleMessenger.Send内部或者在你的业务代码发送消息前使用Application.Current.Dispatcher.Invoke或BeginInvoke将发送操作封送到UI线程。更推荐在MessageBoxService的EnqueueMessage方法开头进行派发检查。消息类型匹配确保Send和Register使用的泛型类型TMessage完全一致。MessageBoxMessage和其派生类是不同的类型。5.2 模态对话框导致主窗口无法操作正确处理父子窗口关系问题当你将IsModal设为true并尝试以Window的形式弹出自定义MessageBox时发现主窗口虽然变灰但依然可以点击。解决方案如果你要实现真正的模态需要创建一个新的Window并将其Owner属性设置为当前主窗口然后调用window.ShowDialog()。在我们的架构中更推荐使用“伪模态”即在主窗口内通过一个全屏的、能拦截所有鼠标事件的遮罩层IsHitTestVisible”True”来实现视觉和交互上的阻塞同时内部消息队列暂停。这样更轻量且风格统一。5.3 自动关闭定时器不准确或内存泄漏问题设置了AutoCloseDelay但提示框有时不关闭或者关闭后感觉应用变慢了。排查定时器未停止在CustomMessageBox.Show()中创建的DispatcherTimer必须在关闭时无论是自动关闭还是手动点击关闭调用timer.Stop()并且最好将timer.Tick事件解绑。否则定时器会持续触发虽然控件隐藏了但回调逻辑可能还在执行。使用正确的定时器在WPF UI线程中务必使用DispatcherTimer而不是System.Timers.Timer或System.Threading.Timer因为前者在UI线程触发Tick事件可以直接操作控件。清理回调在Close方法中执行完_callback?.Invoke(result);后立即将_callback设为null。这可以防止因为持有对某个对象的引用而意外阻止其被垃圾回收。5.4 自定义按钮样式与布局问题问题CustomButtons绑定的按钮样式难看或者水平排列不符合预期。技巧样式分离不要在CustomMessageBox的XAML里硬编码按钮样式。将按钮样式定义在UserControl.Resources或更外层的App.xaml资源字典中然后通过ItemContainerStyle或ItemTemplate中的Style属性来应用。这样便于全局统一修改。动态布局如果按钮数量多水平排列会溢出。可以在ItemsControl的ItemsPanelTemplate中使用WrapPanel替代StackPanel并设置一个最大宽度让按钮自动换行。或者根据按钮数量动态计算并调整整个提示框的宽度。5.5 在复杂页面结构中的定位问题你的应用可能使用Frame导航、TabControl或者复杂的嵌套布局直接将CustomMessageBox添加到MainWindow的顶层Grid可能无法在所有页面上正确显示比如被某些容器裁剪。解决方案使用AdornerLayer。Adorner是WPF中一个位于所有可视化元素之上的装饰层。你可以创建一个MessageBoxAdorner来承载你的自定义控件。这样无论页面结构多复杂提示框都能稳定地显示在最顶层。实现起来稍复杂但是最健壮的方式。MessageBoxService需要获取当前活动窗口的AdornerLayer来添加或移除你的Adorner。最后这个自定义MessageBox系统是一个起点。你可以根据热搜词中提到的具体场景去增强它例如为wpf 仪表盘 控件监控到异常值时弹出特定颜色的警告在c# 上位机wpf例程中连接PLC成功或失败时发送对应的消息。它的价值在于提供了一套清晰、解耦的通信范式让界面反馈这件事变得简单而统一从而让你能更专注于核心业务逻辑无论是c#串口助手的数据处理还是wpf开发mes系统的流程控制的实现。本文还有配套的精品资源点击获取