[置顶] ASP.NET AJAX 控件使用概述

作者: qustdjx
发布时间:2015-07-17 14:40:55

一、ASP.NET AJAX 控件概述

借助 ASP.NET AJAX 控件,使用很少的客户端脚本或不使用客户端脚本就能创建丰富的客户端行为,如在异步回发过程中进行部分页更新(在回发时刷新网页的选定部分,而不是刷新整个网页)和显示更新进度。异步部分页更新可避免整页回发的开销。

ScriptManager 控件   为启用了 AJAX 的 ASP.NET 网页管理客户端脚本。

ScriptManagerProxy 控件   允许内容页和用户控件等嵌套组件在父元素中已定义了 ScriptManager 控件的情况下将脚本和服务引用添加到网页。

Timer 控件   在定义的时间间隔执行回发。如果将 Timer 控件和 UpdatePanel 控件结合在一起使用,可以按照定义的间隔启用部分页更新。您还可以使用 Timer 控件来发布整个网页。

UpdatePanel 控件   可用于生成功能丰富、以客户端为中心的 Web 应用程序。通过使用 UpdatePanel 控件,可以执行部分页更新。

UpdateProgress 控件   提供有关 UpdatePanel 控件中的部分页更新的状态信息。

所有 ASP.NET AJAX 控件都需要 web.config 文件中的特定设置才能正常运行。如果您试图使用这些控件之一,但您的网站不包含所需的 web.config 文件,则网页的“设计”视图中本应显示该控件之处会出现错误。在“设计”视图中,如果您单击处于该状态的控件,则 Microsoft Expression Web 会让您选择要新建一个 web.config 文件还是更新现有的 web.config 文件。

二、ScriptManager 控件

ScriptManager 控件为启用了 AJAX 的 ASP.NET 网页管理客户端脚本。默认情况下,ScriptManager 控件会向网页注册 Microsoft AJAX Library 的脚本。这样,客户端脚本就能使用类型系统扩展插件,还能支持部分页呈现和 Web 服务调用之类的功能。

 方案  若要启用 ASP.NET 的以下 AJAX 功能,必须在网页中使用一个 ScriptManager 控件:Microsoft AJAX Library 的客户端脚本功能和要发送到浏览器的任何自定义脚本。

部分页呈现允许在不回发的情况下单独刷新网页上的各个区域。ASP.NET UpdatePanel、UpdateProgress 和 Timer 控件需要有 ScriptManager 控件才能支持部分页呈现。

Web 服务的 JavaScript 代理类,借助它们,您能够使用客户端脚本访问 ASP.NET 网页中的 Web 服务和特殊标记的方法。它通过将 Web 服务和网页方法公开为强类型化对象来实现此操作。JavaScript 类,用于访问 ASP.NET 身份验证、配置文件和角色应用程序服务。

 背景  当某网页包含一个或多个 UpdatePanel 控件时,ScriptManager 控件会管理浏览器中的部分页呈现。该控件通过与网页生命周期进行交互来更新该网页在 UpdatePanel 控件内的组成部分。

ScriptManager 控件的 EnablePartialRendering 属性确定网页是否参与部分页更新。默认情况下,EnablePartialRendering 属性为真。因此,当您将 ScriptManager 控件添加到网页时,会默认启用部分页呈现功能。

ScriptManagerProxy 类

一个网页只能添加一个 ScriptManager 控件实例。该控件可直接包含在网页中,也可以间接包含在用户控件、母版页的内容页或嵌套的母版页等嵌套组件内。如果某网页已包含一个 ScriptManager 控件,但嵌套组件或父组件需要 ScriptManager 控件的其他功能,则该组件可以包括一个 ScriptManagerProxy 控件。例如,利用 ScriptManagerProxy 控件,可以添加专用于嵌套组件的脚本和服务。

三、Timer 控件

ASP.NET AJAX Timer 控件可按照定义的间隔执行回发。如果将 Timer 控件和 UpdatePanel 控件结合在一起使用,可以按照定义的间隔启用部分页更新。您还可以使用 Timer 控件来发布整个网页。

 方案

当您要执行以下操作时,可使用 Timer 控件:

定期更新一个或多个 UpdatePanel 控件的内容而不刷新整个网页。

每次 Timer 控件导致回发时在服务器上运行代码。

按照定义的间隔将整个网页同步发布到 Web 服务器。

 背景

Timer 控件是一个服务器控件,用于将 JavaScript 组件嵌入到网页中。当 Interval 属性中定义的间隔已过时,JavaScript 组件会从浏览器启动回发。您在服务器上运行的代码中为 Timer 控件设置属性,这些属性会传递给该 JavaScript 组件。

使用 Timer 控件时,网页中必须包括 ScriptManager 类的实例。

当 Timer 控件启动回发时,Timer 控件会在服务器上引发 Tick 事件。您可以为 Tick 事件创建一个事件处理程序,以便将网页发布到服务器时执行操作。

设置 Interval 属性来指定发生回发的频率,并设置 Enabled 属性来开启或关闭 Timer。Interval 属性以毫秒为单位,其默认值为 60,000 毫秒,即 60 秒。

 

说明:将 Timer 控件的 Interval 属性设置为一个较小值会大量增加 Web 服务器的通信量。使用 Timer 控件可专门根据需要的频率来刷新内容。

 

如果不同的 UpdatePanel 控件必须在不同的时间间隔更新,则可以在网页上加入多个 Timer 控件。此外,Timer 控件的单个实例可以是某网页中多个 UpdatePanel 控件的触发器。

在 UpdatePanel 控件内部使用 Timer 控件

当 Timer 控件包含在 UpdatePanel 控件内时,Timer 控件会自动充当该 UpdatePanel 控件的触发器。通过将 UpdatePanel 控件的 ChildrenAsTriggers 属性设置为 false,可阻止此行为。

如果 Timer 控件在 UpdatePanel 控件内,则仅当每个回发操作完成时才会重新创建 JavaScript 计时组件。因此,在完成回发重新显示网页之前,不会算做计时间隔。例如,如果 Interval 属性设置为 60,000 毫秒(60 秒),但回发需要 3 秒钟才能完成,则下次回发将在上次回发出现 63 秒后才会开始。

在 UpdatePanel 控件外部使用 Timer 控件

当 Timer 控件在 UpdatePanel 控件之外时,必须将 Timer 控件显式定义为要更新的 UpdatePanel 控件的触发器。

如果 Timer 控件在 UpdatePanel 控件之外,则在处理回发的同时 JavaScript 计时组件会继续运行。例如,如果 Interval 属性设置为 60,000 毫秒(60 秒),而回发需要 3 秒钟才能完成,则下次回发会在上次回发启动 60 秒后发生。用户仅能看到 UpdatePanel 控件中 57 秒内刷新的内容。

所设置的 Interval 属性的值必须允许一个异步回发能在下次回发启动前完成。如果上一个回发还在处理当中就启动了新的回发,则第一个回发会被取消。

四、UpdateProgress 控件

UpdateProgress 控件提供有关 UpdatePanel 控件中的部分页更新的状态信息。您可以自定义 UpdateProgress 控件的默认内容和布局。为防止在部分页更新非常快时出现闪烁,可以指定在 UpdateProgress 控件显示之前有一个延迟。

 方案

利用 UpdateProgress 控件,在网页包含一个或多个用于部分页呈现的 UpdatePanel 控件时,您可以设计一个更直观的用户界面。如果部分页更新速度较慢,您可以使用 UpdateProgress 控件来直观地反映更新状态。您可以在一个网页上放置多个 UpdateProgress 控件,每个控件都与不同的 UpdatePanel 控件关联。此外,也可以使用一个 UpdateProgress 控件,并将其与该网页上的所有 UpdatePanel 控件关联。

背景

UpdateProgress 控件将呈现一个 <div> 元素,该元素是显示还是隐藏取决于关联的 UpdatePanel 控件是否导致了异步回发。对于初始页呈现和同步回发,UpdateProgress 控件不显示。

将 UpdateProgress 控件与 UpdatePanel 控件关联

通过设置 UpdateProgress 控件的 AssociatedUpdatePanelID 属性可将 UpdateProgress 控件与 UpdatePanel 控件相关联。当某 UpdatePanel 控件发生回发事件时,会显示所有关联的 UpdateProgress 控件。如果不将 UpdateProgress 控件与特定的 UpdatePanel 控件相关联,则 UpdateProgress 控件会显示任何异步回发的进度。

如果 UpdatePanel 控件的 ChildrenAsTriggers 属性设置为假,并且该 UpdatePanel 控件内部发生了一个异步回发,则会显示任何关联的 UpdateProgress 控件。

创建 UpdateProgress 控件的内容

若要指定 UpdateProgress 控件显示的消息,请在“设计”视图中将所需的内容放置到面板中。例如,您可以将其他 ASP.NET 和 HTML 控件拖入面板,然后将光标置于面板内并在面板中直接键入内容。在“设计”视图中向 UpdateProgress 控件添加消息时,系统会自动在内容两边添加所需的 <ProgressTemplate> 标记。如果您是在“代码”视图而不是“设计”视图中向 UpdateProgress 控件添加内容,则必须手动添加 <ProgressTemplate></ProgressTemplate> 标记(如果它们尚不存在),否则不会呈现该消息。

指定内容布局

当 DynamicLayout 属性为真时,UpdateProgress 控件最初不会占据网页显示中的任何空间,而是在需要时网页动态地更改为显示 UpdateProgress 控件内容。为支持动态显示,该控件呈现为一个其显示样式属性最初设置为无的 <div> 元素。

当 DynamicLayout 属性为假时,UpdateProgress 控件会占用网页显示空间,即使该控件不可见也会占用。在这种情况下,该控件的 <div> 元素将其显示样式属性设置为块,将其可见性最初设置为隐藏。

将 UpdateProgress 控件置于网页上

您可以将 UpdateProgress 控件置于 UpdatePanel 控件内部或外部。只要其关联的 UpdatePanel 控件因异步回发而更新,UpdateProgress 控件就会显示。即使 UpdateProgress 控件在另一个 UpdatePanel 控件内部也是如此。

如果 UpdatePanel 控件在另一个更新面板内部,则子面板内部发生的回发会导致与子面板关联的所有 UpdateProgress 控件都显示出来。它也会显示所有与父面板关联的 UpdateProgress 控件。如果回发发生在父面板的直接子控件中,则仅显示与该父面板关联的 UpdateProgress 控件。这种行为遵循了回发触发方式的逻辑。

五、ScriptManagerProxy 控件

使用 ScriptManagerProxy 控件,内容页和用户控件等嵌套组件可以在父元素中已定义了 ScriptManager 控件的情况下将脚本和服务引用添加到网页。

一个网页只能包含一个 ScriptManager 控件,该控件可直接位于网页本身,也可以间接放置在嵌套的组件或父组件内。利用 ScriptManagerProxy 控件,可以将脚本和服务添加到其母版页或主机页已包含 ScriptManager 控件的内容页和用户控件。

当您使用 ScriptManagerProxy 控件时,可以添加 ScriptManager 控件定义的脚本和服务集合。如果不希望在包括特定 ScriptManager 控件的每个网页上包括特定的脚本和服务,请将它们从 ScriptManager 控件中删除,而改用 ScriptManagerProxy 控件将它们添加到单独的网页。

六、UpdatePanel 控件

ASP.NET UpdatePanel 控件可用于生成功能丰富、以客户端为中心的 Web 应用程序。通过使用 UpdatePanel 控件,可以在回发期间刷新网页的选定部分而不是刷新整个网页。这称为执行部分页更新。包含一个 ScriptManager 控件和一个或多个 UpdatePanel 控件的 ASP.NET 网页,不需要使用自定义客户端脚本即可自动参与部分页更新。

 方案

UpdatePanel 控件是一个服务器控件,借助它,可以开发出具有复杂客户端行为的网页,使网页能够更好地与最终用户进行交互。编写在服务器和客户端进行协调以便只更新网页指定部分的代码通常需要深入了解 ECMAScript (JavaScript)。但是,使用 UpdatePanel 控件,可以使网页参与部分页更新,而无需编写任何客户端脚本。如果您需要,可以添加自定义客户端脚本来改善客户端用户的使用体验。使用 UpdatePanel 控件时,网页行为与浏览器无关并可以潜在减少客户端和服务器之间传输的数据量。

 背景

UpdatePanel 控件的工作方式是指定可更新的网页区域,而不刷新整个网页。此过程由 ScriptManager 服务器控件和客户端 PageRequestManager 类进行协调。当部分页更新被启用时,控件可异步发布到服务器。异步回发的行为类似于常规回发,因为生成的服务器网页执行整个网页和控件生命周期。但是,使用异步回发,网页更新被限制为 UpdatePanel 控件中包含的网页区域以及被标记为要更新的区域。服务器仅向浏览器发送受影响元素的 HTML 标记。

启用部分页更新

UpdatePanel 控件要求网页中有一个 ScriptManager 控件。默认情况下,当 ScriptManager 控件的 EnablePartialRendering 属性的默认值为 true 时会启用部分页更新。

指定 UpdatePanel 控件的内容

通过在“设计”视图中将内容放在面板中,可以向 UpdatePanel 控件添加内容。例如,可以将其他 ASP.NET 和 HTML 控件拖入该面板,并将光标置于面板内并直接在其中键入内容。在“设计”视图中向 UpdatePanel 控件添加内容时,系统会自动在内容两边添加必要的 <ContentTemplate></ContentTemplate> 标记。如果您是在“代码”视图而不是“设计”视图中向 UpdatePanel 控件添加内容,则必须手动添加 <ContentTemplate></ContentTemplate> 标记(如果它们尚不存在),否则不会呈现 UpdatePanel 中的内容。

当包含一个或多个 UpdatePanel 控件的网页第一次呈现时,UpdatePanel 控件的所有内容都将呈现并发送到浏览器。以后发生异步回发时,可能会分别更新各个 UpdatePanel 控件的内容。更新取决于面板设置、哪些元素引发了回发,以及每个面板特有的代码。

指定 UpdatePanel 触发器

默认情况下,UpdatePanel 控件内的任何回发控件都会导致异步回发并刷新面板的内容。但是,您也可以将网页上的其他控件配置为刷新 UpdatePanel 控件。为此,需要为 UpdatePanel 控件定义触发器。触发器是指定哪个回发控件和事件导致面板更新的绑定。当发生触发器控件的指定事件(例如,按钮的 Click 事件)时,就会刷新更新面板。

您可以使用“UpdatePanelTrigger 集合编辑器”对话框为 UpdatePanel 控件创建触发器,该对话框可从“标记属性”任务窗格的“触发器”属性调出。

触发器的控件事件是可选的。如果您不指定事件,触发器事件是控件的默认事件。例如,对于 Button 控件,默认事件是 Click 事件。

如何刷新 UpdatePanel 控件

以下列表介绍的 UpdatePanel 控件的属性设置决定在部分页呈现过程中面板的内容何时更新:

如果 UpdateMode 属性设置为“Always”,则网页的任何地方发生的每个回发都会导致 UpdatePanel 控件内容进行更新。其中包括其他 UpdatePanel 控件内的控件引发的异步回发,以及不在 UpdatePanel 控件内的控件引发的回发。

如果 UpdateMode 属性设置为“Conditional”,则当以下其中一项为真时 UpdatePanel 控件的内容会得到更新:

当回发是由该 UpdatePanel 控件的触发器所引起时。

当您显式调用 UpdatePanel 控件的 Update 方法时。

当 UpdatePanel 控件嵌套在另一个 UpdatePanel 控件内且父面板发生更新时。

当 ChildrenAsTriggers 属性设置为真且该 UpdatePanel 控件的任何子控件导致回发时。嵌套的 UpdatePanel 控件的子控件不会导致外部 UpdatePanel 控件发生更新,除非它们被显式定义为父面板的触发器。

如果 ChildrenAsTriggers 属性设置为 false 且 UpdateMode 属性设置为“Always”,则会引发异常。仅当 UpdateMode 属性设置为“Conditional”时,才能使用 ChildrenAsTriggers 属性。

使用嵌套的 UpdatePanel 控件

UpdatePanel 控件可以嵌套。如果父面板被刷新,则所有嵌套的面板都会刷新。如果子面板被刷新,则仅该子面板会更新。

与 UpdatePanel 控件不兼容的控件

下面的 ASP.NET 控件与部分页更新不兼容,因此,不能用在 UpdatePanel 控件内:

在以下几种情况下的 Treeview 控件:一种是当回调不是作为异步回发的一部分启用时;一种是您直接将样式设置为控件属性,而不是使用对 CSS 样式的引用隐式为控件设置样式时;另一种是 EnableClientScript 属性为 false(默认值为 true)时;还有一种是您在两次异步回发之间更改 EnableClientScript 属性的值时。

Menu 控件:当您直接将样式设置为控件属性,而不是使用对 CSS 样式的引用隐式为该控件设置样式时。

FileUpload 和 HtmlInputFile 控件:当它们用来作为异步回发一部分的上载文件时。

其 EnableSortingAndPagingCallbacks 属性被设置为 true 时的 GridView 和 DetailsView 控件。默认值为 false。

其内容尚未被转换为可编辑模板的 Login、PasswordRecovery、ChangePassword 和 CreateUserWizard 控件。

Substitution 控件。

若要将 FileUpload 或 HtmlInputFile 控件用在 UpdatePanel 控件内,请将提交该文件的回发控件设置为该面板的 PostBackTrigger 控件。FileUpload 和 HtmlInputFile 控件可以仅用在回发情况下。

所有其他控件均可用在 UpdatePanel 控件内。

在 UpdatePanel 控件内使用 Web 部件控件

ASP.NET Web 部件是一组集成控件,用于创建网站使最终用户可以直接从浏览器修改网页的内容、外观和行为。只要遵守以下限制,可以在 UpdatePanel 控件内使用 Web 部件控件:

每个 WebPartZone 控件都必须在同一个 UpdatePanel 控件内。例如,网页上不能有两个 UpdatePanel 控件,而每个控件又都有其自己的 WebPartZone 控件。

WebPartManager 控件管理 Web 部件控件的所有客户端状态信息。它必须在网页上最外面的 UpdatePanel 控件内。

不能使用异步回发来导入或导出 Web 部件控件。(执行此任务需要一个 FileUpload 控件,它不能用于异步回发。)默认情况下,导入 Web 部件控件会执行完整回发。

在异步回发过程中,不能添加或修改 Web 部件控件的样式。

 

 

教程:如何创建包含两个独立更新的区域的网页

在本教程中,您将在一个网页中使用多个 UpdatePanel 控件。通过在一个网页上使用多个 UpdatePanel 控件,您可以按照递增的方式单独更新或一起更新该网页的各个区域。

创建包含两个独立更新的区域的网页

1、在“文件”菜单上,指向“新建”,然后单击“ASPX”。

2、将光标置于 ASPX 网页的“设计”视图中。

3、在“工具箱”任务窗格中,在“ASP.NET 控件”下的“AJAX”下,双击要添加到该网页的“ScriptManager”控件。

4、在“工具箱”任务窗格中,在“ASP.NET 控件”下的“AJAX”下,双击“UpdatePanel”控件两次,将两个 UpdatePanel 控件添加到该网页。

5、选中网页中的一个 UpdatePanel 控件,在“标记属性”任务窗格中,将“UpdateMode”属性设置为“Conditonal”。对另一个 UpdatePanel 控件重复此步骤。

6、在“工具箱”任务窗格中,在“ASP.NET 控件”和“标准”类别下,将“Label”控件拖到“设计”视图中的一个 UpdatePanel 控件中。

7、选中网页中的“Label”控件,在“标记属性”任务窗格中,将“Text”属性设置为“Panel Created”。

8、在“工具箱”任务窗格中,在“ASP.NET 控件”和“标准”类别下,将“Button”控件拖到包含“Label”控件的同一个 UpdatePanel 控件中。选中网页中的该按钮,在“标记属性”任务窗格中,将“Text”属性设置为“Refresh Panel”。

9、在“工具箱”任务窗格中,在“ASP.NET 控件”和“标准”类别下,将“Calendar”控件拖到“设计”视图中的另一个 UpdatePanel 控件中。

    protected void Button1_Click(object sender, EventArgs e)

    {

        Label1.Text = "Panel refreshed at " + DateTime.Now.ToString();

    }

10、按 F12 在您的 Web 浏览器中预览该网页。在 Web 浏览器中的该网页中,单击该按钮。面板中的文本将更改,以显示该面板的内容上次刷新的时间。在日历中,移到其他月份。另一个面板中的时间不会更改。这两个面板的内容会单独更新。

回顾

本教程介绍了在一个网页中使用多个 UpdatePanel 控件的概念。当 UpdatePanel 控件不嵌套时,您可以通过将 UpdateMode 属性设置为“Conditional”来独立更新每个面板。(UpdateMode 属性的默认值为“Always”。这将导致面板在任何异步回发后进行刷新。)

当面板嵌套时,行为稍有不同。如果将外部控件和嵌套控件的 UpdateMode 属性均设置为“Conditional”,则可在不刷新外部面板的情况下刷新内部面板。但是,如果刷新外部更新面板,则内部更新面板也会刷新。

 

教程:如何在规定的时间间隔刷新 UpdatePanel 控件

在本演练中,您将使用以下三个 ASP.NET AJAX 服务器控件来在规定的时间间隔更新网页的组成部分:即 ScriptManager 控件、UpdatePanel 控件和 Timer 控件。向网页中添加这些控件后,就不必在每次回发时刷新整个网页。只有 UpdatePanel 控件的内容会更新。

在规定的时间间隔刷新 UpdatePanel 控件

1、在“文件”菜单上,指向“新建”,然后单击“ASPX”。

2、将光标置于 ASPX 网页的“设计”视图中。

3、在“工具箱”任务窗格中,在“ASP.NET 控件”下的“AJAX”下,双击要添加到该网页的“ScriptManager”控件。

4、在“工具箱”任务窗格中,在“ASP.NET 控件”下的“AJAX”下,双击要添加到该网页的 UpdatePanel 控件。

5、将光标放在“设计”视图中的 UpdatePanel 控件内。

6、在“工具箱”任务窗格中,在“ASP.NET 控件”和“AJAX”类别下,双击“Timer”控件,将其插入网页中的 UpdatePanel 控件中。

 

说明:Timer 控件可以充当 UpdatePanel 控件内部或外部的触发器。本示例演示如何在 UpdatePanel 控件内使用 Timer 控件。有关将 Timer 控件用作 UpdatePanel 控件外部的触发器的示例,请参阅 MSDN Library 中的演练:对多个 UpdatePanel 控件使用 ASP.NET Timer 控件(此链接可能指向英文页面)。尽管该主题是针对 Microsoft Visual Web Developer 编写的,但是您仍可在 Microsoft Expression Web 中按照其执行操作,只有少数一些情况例外。

 

7、在网页中选中 Timer 控件,在“标记属性”任务窗格中,将“间隔”属性设置为 10000。“间隔”属性以毫秒为单位,因此,将“间隔”属性设置为 10,000 毫秒会每隔 10 秒钟刷新一次该 UpdatePanel 控件。

 

说明:在本示例中,计时器间隔设置为 10 秒。这样,当您运行该示例时,不必等很长时间就能看到结果。但是,每个计时器间隔都会导致回发到服务器并增加网络通信量。因此,在生产应用程序中,应该将此间隔设为仍然适用于您的应用程序的最长时间。

 

8、将光标放在“设计”视图中的 UpdatePanel 控件内。

9、在“工具箱”任务窗格中,在“ASP.NET 控件”和“标准”类别下,双击“Label”控件,将其插入 UpdatePanel 控件中。

10、选中您网页中的“Label”控件,在“标记属性”任务窗格中,在“文本”框中,键入“面板尚未刷新”。将光标放在 UpdatePanel 控件之外。

11、在“工具箱”任务窗格中,在“ASP.NET 控件”和“标准”类别下,双击“Label”控件,向该网页插入第二个标签。

 

说明:请确保将第二个 Label 控件添加在 UpdatePanel 控件之外。

 

  protected void Timer1_Tick(object sender, EventArgs e)

    {

        Label1.Text = "Panel refreshed at: " +DateTime.Now.ToLongTimeString();

    }

12、按 F12 在您的 Web 浏览器中预览该网页。等待至少 10 秒,以便 UpdatePanel 面板得到刷新。面板内的文本将更改,以显示上次刷新该面板的内容的时间。但是,面板外部的文本不会刷新。

本演练介绍了如何使用 Timer 控件和 UpdatePanel 控件来启用部分页更新的基本概念。您必须将 ScriptManager 控件添加到任一包含 UpdatePanel 控件或 Timer 控件的网页中。默认情况下,面板内的 Timer 控件将导致在异步回发过程中仅刷新该面板。如果将面板外的 Timer 控件配置为该面板的触发器,则会导致刷新 UpdatePanel。


转自:http://www.360doc.com/content/11/0815/13/19147_140528929.shtml

标签: .NET Ajax ASP.NET
来源:http://blog.csdn.net/qustdjx/article/details/17488745

推荐: