源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

c#学习之30分钟学会XAML

  • 时间:2022-02-17 17:05 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:c#学习之30分钟学会XAML
[b]1.狂妄的WPF[/b] 相对传统的Windows图形编程,需要做很多复杂的工作,引用许多不同的API。例如:WinForm(带控件表单)、GDI+(2D图形)、DirectX API(3D图形)以及流媒体和流文档等,都需要不同的API来构建应用程序。 WPF就是看着上面的操作复杂和不爽,自己决定做老大,想用DirectX技术涵盖一切,于是想要将上述的东西全部融合到自身,减少复杂度,让编程变得爽起来的技术。 而不可否认的是,WPF虽然很狂妄,但是这种技术里面还是有不少的可圈可点的东西。而支持WPF狂妄的资本,则就是和它后台代码可以前后分离的XAML技术。下面用30分钟时间说一下XAML。 [b]2.什么是XAML[/b] 一个界面程序的核心,无疑就是界面和后台代码,而xaml就是微软为构建应用程序界面而创建的一种描述性语言,也就是说,这东西是搞界面的。 先上一段xaml代码:
 <Window x:Class='MyXaml.Window1'
  xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
  xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
  Title='MyXaml' Height='150' Width='300' >
  <Grid>
   <Grid.RowDefinitions>
   <RowDefinition Height='30'/>
   <RowDefinition Height='30'/>
   <RowDefinition Height='30'/>
   </Grid.RowDefinitions>
   <Grid.ColumnDefinitions>
   <ColumnDefinition Width='Auto'/>
   <ColumnDefinition Width='*'/>
   </Grid.ColumnDefinitions>
   <TextBlock Grid.Column='0' Grid.Row='0' FontWeight='Bold' Text='姓名:' Width='30'/>
   <TextBlock Grid.Column='0' Grid.Row='1' FontWeight='Bold' Width='30'>性别:</TextBlock>
   <TextBlock Grid.Column='0' Grid.Row='2' FontWeight='Bold' Width='30' Text='年龄'></TextBlock>
   <TextBox Grid.Column='1' Grid.Row='0' FontWeight='Bold' Width='100' />
   <TextBox Grid.Column='1' Grid.Row='1' FontWeight='Bold' Width='100'/>
   <TextBox Grid.Column='1' Grid.Row='2' FontWeight='Bold' Width='100'/>
  </Grid>
 </Window>
上述xaml是我设计了一个三行两列的界面,运行之后显示如下: [img]http://files.jb51.net/file_images/article/201611/201611272241581.png[/img] 在此,我没有写一行c#代码,但是它竟然可以运行,所以也可以说它也是一种编程语言。只不过它更关注界面上面的东西而已。 那么它的运行是如何产生的?下面看几个东西: x:Class='MyXaml.Window1' ——利用class特性指定c#类名(后台c#代码) xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml——这表示利用x代替XAML的命名空间。用于包含特定的关键字和System.Windows.Markup中类型的子集。 xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation——另一个命名空间。映射诸多wpf.net命名空间(system.windows.xxx,是个一对多的映射,主要封装了三个程序集中,WindowsBase.dll、Presentation.dll和PresentationFramework.dll) [b]2.1 启动[/b] 程序启动的地方,其实是在程序的App.xaml文件里面:
 <Application x:Class='MyXaml.App'
  xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
  xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
  StartupUri='Window1.xaml'>
  <Application.Resources>
   
  </Application.Resources>
 </Application>
看下面这句: StartupUri='Window1.xaml' 这个就是程序的入口点,运行程序之后,我们就将window1显示在了显示屏上。 [b]3.XAML语法概述[/b] 上述xaml中,显示的一个核心布局就是以下这些代码:
 <Grid>
   <Grid.RowDefinitions>
   <RowDefinition Height='30'/>
   <RowDefinition Height='30'/>
   <RowDefinition Height='30'/>
   </Grid.RowDefinitions>
   <Grid.ColumnDefinitions>
   <ColumnDefinition Width='Auto'/>
   <ColumnDefinition Width='*'/>
   </Grid.ColumnDefinitions>
   <TextBlock Grid.Column='0' Grid.Row='0' FontWeight='Bold' Text='姓名:' Width='30'/>
   <TextBlock Grid.Column='0' Grid.Row='1' FontWeight='Bold' Width='30'>性别:</TextBlock>
   <TextBlock Grid.Column='0' Grid.Row='2' FontWeight='Bold' Width='30' Text='年龄'></TextBlock>
   <TextBox Grid.Column='1' Grid.Row='0' FontWeight='Bold' Width='100' />
   <TextBox Grid.Column='1' Grid.Row='1' FontWeight='Bold' Width='100'/>
   <TextBox Grid.Column='1' Grid.Row='2' FontWeight='Bold' Width='100'/>
  </Grid>

1、看Grid控件,这个在wpf中是一个布局控件,就跟将窗体设置单元格差不过,可以依据它的row和column属性来设置行和列。上述设置了一个三行两列的布局。 2、看TextBlock和TextBox控件,这些都是一些显示控件,一个显示条和一个文本框,wpf中除此之外,还有许多的控件。 首先需要再确认的一点,那就是在C#中一切皆对象。如此一来,也就好理解了。 在XAML中的这些形形色色的控件其实就是一个个的类,我们应用了他们就相当于是应用了一个个的对象,而他们之中定义的一些width、height等属性,就是这些类中封装的一些属性字段。 当然,像上面TextBox和TextBlock中的Grid.Column='1' Grid.Row='2'等属性,其实并不属于这两个类中的属性和字段。 而使得他们具备这样属性的,无疑就是外面的Grid布局控件赋予的,而在WPF中这样的功能实现叫做附加属性,是依赖属性的一个特殊的用法。关于依赖属性,在以后会详细的讨论。 [b]第一个总结 [/b]WPF的XAML语法其实可以理解成另外一种形式的编程语言,其语法表现形式和XML类似,但是更严谨和更要求准确性。 XAML主要包括布局和控件,以此来构建各种形态的应用程序,除此之外,其中还有许多新的强大的东西,使得它更灵活和方便,例如依赖属性。 [b]3.1 布局[/b] WPF中的布局常用的主要包括五种:Canvas、Grid、StackPanel、DockPanel和WrapPanel。下面分别说一下这五种布局控件的使用。 1、 Canvas 要说Canvas,先看以下的xaml代码:
<Canvas>
  <Button Name='btn1' Height='100' Width='100' Content='btn1' Margin='10'/>
  <Button Name='btn2' Height='100' Width='100' Content='btn2' Margin='10'/>
</Canvas>
然后,查看在画布Canvas上面生成的画面,情况如下: [img]http://files.jb51.net/file_images/article/201611/201611272241582.png[/img] 为什么不显示btn1?因为两个button重叠了起来,只显示最上面的控件(越接近结束标签 </Canvas>的控件,如果两个button的位置颠倒一下,显现出来的就是btn1). Canvas的布局基本就和之前的Winform一致了,都是以左上角为中心,按照上下距离左上角的坐标为标准的。如果想要改变button的位置,就要给button控件设置Canvas.Left、 Canvas.Top、Canvas.Bottom 和Canvas.Right这四个属性。 所以Canvas得重点在绝对布局,对要求不太高的界面和比较固定的界面可以用这样的方式拖拽控件布局。 [b]2、 Grid[/b] WPF窗体程序的默认布局就是一个Grid,先看如下代码:
<Grid>
  <Button Name='btn1' Height='100' Width='100' Content='btn1' Margin='10'/>
  <Button Name='btn2' Height='100' Width='100' Content='btn2' Margin='10'/>
</Grid>
看到这里,你也许会说,这不是和上面的Canvas一样吗?但是真的一样吗?他们的窗体如下所示: [img]http://files.jb51.net/file_images/article/201611/201611272241583.png[/img] 相信你一定看出来了,不错,和Canvas不同,Grid窗体默认的显示是以中心为基准的,不像Canvas布局是以左上角为标准。 那么Grid布局有什么样的好处呢?看下面这些代码:
<Grid>
  <Grid.RowDefinitions>
   <RowDefinition Height='*'/>
   <RowDefinition Height='*'/>
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
   <ColumnDefinition Width='*'/>
   <ColumnDefinition Width='*'/>
  </Grid.ColumnDefinitions>
  <Button Name='btn1' Height='40' Width='40' Content='btn1' />
  <Button Grid.Row='1' Grid.Column='1' Name='btn2' Height='40' Width='40' Content='btn2' />
</Grid>
然后在看看生成的窗口: [img]http://files.jb51.net/file_images/article/201611/201611272241594.png[/img] 相信你一定可以看明白,知道Grid是把窗体分成了一块块的网格,而分割这个功能是通过Grid.RowDefinitions和Grid.ColumnDefinitions两组属性定义的,分别在其中定义了行和列,将窗体分为了几行几列,行的高度和列的宽度可以根据Width属性设置。 如上图,利用xaml定义了一个两行两列的Grid,并且在第0行0列放置btn1(如不显示定义,则拖入的控件默认放在此网格。),第1行1列放置了btn2. [b]关于网格属性做以下几个说明。[/b] RowDefinition 表示网格的行,其只有一个Height属性而没有Width属性,*表示该行占据窗体剩下的所有的高度,如都设为*则表示平分窗体。如上所示,你还可以让窗体3:1,只要将两个行定义分别设置为3*和*就可以了。 ColumnDefinition 表示网格的列,其中只有一个Width属性而没有Height属性,*号的用法同RowDefinition的用法,不过相对RowDefinition分割窗体的高度而言,在这里*表示分割窗体的宽度。 可以说,Grid实现的是一种网格布局,这种布局是相当强大的,可以并且用起来也非常灵活。 [b]3、 StackPanel[/b] StackPanel布局遵循的默认原则是从窗口中间部位,从顶部开始,从上到下排列控件。先看下面代码:
<StackPanel>
    <Button Name='btn1' Height='40' Width='40' Content='btn1' />
    <Button Name='btn2' Height='40' Width='40' Content='btn2' />
</StackPanel>
表现出来的窗体如下: [img]http://files.jb51.net/file_images/article/201611/201611272241595.png[/img] 如上图所示,控件的布局默认是竖向布局,那么如何设置控件横向布局呢?我们只需要设置StackPanel的Orientation属性就可以了: Orientation='Horizontal' 横向布局,从窗体中部,左侧开始从左向右排列控件。 Orientation='Vertical'纵向布局,默认属性。 横向布局的实例如下:
<StackPanel Orientation='Horizontal'>
  <Button Name='btn1' Height='40' Width='40' Content='btn1' />
  <Button Name='btn2' Height='40' Width='40' Content='btn2' />
</StackPanel>
窗口显示: [img]http://files.jb51.net/file_images/article/201611/201611272241596.png[/img] [b]4、 DockPanel[/b] Dockpanel默认布局原则,从左中位置开始,控件依次排列,最后一个控件将剩余区域从中心填充。看下面代码:
<DockPanel>
  <Button Name='btn1' Height='40' Width='40' Content='btn1' />
  <Button Height='40' Width='40' Content='btn2' />
  <Button Height='40' Width='40' Content='btn2' />
  <Button Height='40' Width='40' Content='btn2' />
  <Button Height='40' Width='40' Content='btn2' />
  <Button Height='40' Width='40' Content='btn2' />
</DockPanel>
该布局的窗体显示如下: [img]http://files.jb51.net/file_images/article/201611/201611272241597.png[/img] 同StackPanel不同,Dockpanel布局依靠的是它的四个附加在其余控件上的附加属性,看下表:

DockPanel.Dock属性

说明
Top 顶部,如果设置,从中间顶部开始依照上述原则布局
Left 左部,默认布局
Right 右部,如果设置,从中间右侧开始依照上述原则布局
Button 底部,如果设置,从中间底部开始依照上述原则布局

看下面实例,xaml代码:
<DockPanel>
 <Button DockPanel.Dock="Bottom" Name="btn1" Height="40" Width="40" Content="btn1" />
 <Button DockPanel.Dock="Bottom" Height="40" Width="40" Content="btn2" />
 <Button Height="40" Width="40" Content="btn2" />
 <Button Height="40" Width="40" Content="btn2" />
 <Button Height="40" Width="40" Content="btn2" />
 <Button Height="40" Width="40" Content="btn2" />
 </DockPanel>
界面表现如下: [img]http://files.jb51.net/file_images/article/201611/201611272241598.png[/img] 有上述描述代码可知,此处StackPanel从底部开始布局两个btn,在剩余的空间中(窗体上部),然后从默认位置左部开始依次放置btn,最后一个btn将最后剩余的空间填充。 [b]5、 WrapPanel[/b] WrapPanel布局遵循的原则是从窗体左上角开始,多控件的自动换行。同StackPanel一样,它也有表示纵向和横向的Orientation属性。看下面xaml代码:
<WrapPanel>
     <Button Name="btn1" Height="40" Width="40" Content="btn1" />
     <Button Height="40" Width="40" Content="btn2" />
     <Button Height="40" Width="40" Content="btn2" />
     <Button Height="40" Width="40" Content="btn2" />
     <Button Height="40" Width="40" Content="btn2" />
     <Button Height="40" Width="40" Content="btn2" />
     <Button Height="40" Width="40" Content="btn2" />
     <Button Height="40" Width="40" Content="btn2" />
     <Button Height="40" Width="40" Content="btn2" />
     <Button Height="40" Width="40" Content="btn2" />
</WrapPanel>
生成的界面如下: [img]http://files.jb51.net/file_images/article/201611/201611272241599.png[/img] 由此可知,WrapPanel默认的布局是横向,等到右边没有空余,其余的控件将从第二行开始重新排列。 如果将其Orientation属性设置Vertical那么排列将会从左上角开始,从上到下排列,下面没有空余将会从第二行开始从上而下重新排列。如下xaml:
<WrapPanel Orientation="Vertical">
     <Button Name="btn1" Height="40" Width="40" Content="btn1" />
     <Button Height="30" Width="40" Content="btn2" />
     <Button Height="30" Width="40" Content="btn2" />
     <Button Height="30" Width="40" Content="btn2" />
     <Button Height="30" Width="40" Content="btn2" />
   </WrapPanel>
窗体布局如下: [img]http://files.jb51.net/file_images/article/201611/2016112722415910.png[/img] 由上可知,WrapPanel主要提供的功能是从窗体左上角开始,多控件的自动换行。 [b]第二个总结:[/b] WPF中的布局除此之外还有如下的几种:abPanel, ToolBarOverflowPanel, ToolBarPanel, UniformGrid, VirtualizingPanel, VirtualizingStackPanel,它们都派生于Panel抽象类,内容属性Children可以添加多个控件。它们藉此来控制控件的布局。 [b]3.2 控件[/b] 其实布局也是控件的一种,但是单独讲布局列出来。因为布局在WPF中占据着异常重要的地位。接下来,主要说一下其他的控件,大体上,除了布局之外的控件可以分为以下的三类。 第一类:核心用户输入控件,用户创建用户界面的核心。其中比较常用的有Button、RadioButton、ComboBox、CheckBox、DataGrid、ListBox、ListView、TreeView、TextBlock、TextBox、Label。 第二类:窗口修饰控件,这些元素用于装饰Window对象的框架。其中常用的有Menu、ToolBar、StatusBar、ToolTip、ProgressBar。 第三类:媒体控件,支持音频/视频的重放和图像的显示。其中比较常用的控件有Image、MediaElement、SoundPlayerAction。 [b]下面分别说说每类控件的基本功能。[/b] [b]一、 核心用户输入控件[/b] 1、 Button、RadioButton,Button控件的基础用法,主要是处理单击Button的时候对单击事件处理的方式。Button的单击事件是通过Click特性声明,其模式主要有三种: Release:Button被按下然后松开时发生单击事件 Hover: 鼠标悬停在按钮上方引发单击事件 Press:当单击按钮时引发单击事件 我们可以利用Button单击事件的模式设置我们想要的点击控件的效果。 Button是内容控件,我们可以给它的内容设置其他的东西,比如下面的代码,就是给一个Button按钮设置图片:
<Button x:Name="ImageButton" Margin="3" Grid.Row="1" HorizontalAlignment="Left"> 
    <StackPanel Margin="1" Orientation="Horizontal" Width="620"> 
      <Image Source="back.bmp" Stretch="UniformToFill" Width="160"/> 
      <TextBlock Width="130" /> 
      <TextBlock Text="图片按钮" Margin="1,15,1,1"/> 
    </StackPanel> 
  </Button>
除此之外,我们还可以给Button设置不同的样式,使得它变得好看。涉及到了样式、模板和触发器,在此不过多陈述。 RadioButton和Button都是继承自ButtonBase类,所具有的基本属性是相同的,用法也大同小异。 2、 ComboBox控件,表示带有下拉列表的选择控件,通过单击控件上的箭头可显示或隐藏下拉列表。用法如下:
<ComboBox>

 aaa

</ComboBox>
其中Item表示下拉项,可以自己设定也可以通过Binding获得。界面编辑器在ComboBox的属性里面有一个Items集合,用来设定ComboBox的选项。Binding所用到的属性是ItemsSource属性。效果如下图: [img]http://files.jb51.net/file_images/article/201611/2016112722415911.png[/img] ComboBox控件选中事件可以从其选中项的SelectedItem的属性来binding事件处理逻辑。 3、 CheckBox控件,表示用户可以选择并清除的控件。其用法形式如下:
<CheckBox>
 Content
</CheckBox>
效果如下图所示: [img]http://files.jb51.net/file_images/article/201611/2016112722415912.png[/img] CheckBox控件主要用来处理三个事件,选中事件Checked、Unchecked,以及影响外观的Indeterminate事件,可以分别在后台显示三者的处理逻辑。
<CheckBox x:Name="cb1" Grid.Row="1" Margin="5,0,0,0" 
      Content="Three-state CheckBox" IsThreeState="True"
      Checked="HandleCheck" Unchecked="HandleUnchecked" 
      Indeterminate="HandleThirdState" />
4、 DataGrid、ListBox、ListView、TreeView,都是用来以行列形式显示的控件,前三个都是几行几列的形式,而最后一个TreeView则是显示出来一种树形结构。 以上几种控件可以用来进行数据绑定xml数据或者是数据库,每一种都有不同的形式。基本的显示图如下所示: TreeView: [img]http://files.jb51.net/file_images/article/201611/2016112722415913.png[/img] [img]http://files.jb51.net/file_images/article/201611/2016112722420014.png[/img] 而TextBox也可以用来做数据绑定,可以关联其他的控件对象,来实现想要显现的效果。 核心用户输入控件,除了上面列出来的这些之外,还有许多,例如:Calendar、Slider、TabControl等等,它们一起构成了WPF的完整的输入控件族,是用户创建界面的核心。 [b]二、 窗口修饰控件[/b] 1、 Menu控件,菜单控件,相信大家都不陌生,先看下面的xaml:
<Grid>
 <Menu>
 <MenuItem Header="First" >
  <MenuItem Header="second"/>
  <MenuItem Header="secend">
   <MenuItem Header="third" Click="Handler"/>
  </MenuItem>
  </MenuItem>
 </Menu>
</Grid>
显示的界面: [img]http://files.jb51.net/file_images/article/201611/2016112722420015.png[/img] 由此可以看出,菜单控件是利用MenuItem子项形成层级结构,并且可以为每一个菜单项设置Click事件。上面实例没有设置菜单的高度,故而菜单铺满全局。 菜单项有几个属性需要注意一下上图中可以看出,一个子菜单主要有三个部分,其中一个是显示内容,内容前面有个空白(这是一个图标Icon的占位空白),还有子项后面的那个黑色的三角箭头。 Icon:可以设置菜单的图标,其内容可以是一个image空间,用法如下:
<MenuItem.Icon>
    <Image Source="Delete.png"/>
 </MenuItem.Icon>
其中source指向你想要显示为Icon的图片。 Header:菜单的内容,设置如上的xaml所示。 2、  ToolBar控件,工具条菜单。ToolBar 是一个 [url=http://msdn.microsoft.com/zh-cn/library/system.windows.controls.headereditemscontrol(v=vs.90).aspx]HeaderedItemsControl[/url] 。其内容属性为  [url=http://msdn.microsoft.com/zh-cn/library/system.windows.controls.itemscontrol.items(v=vs.90).aspx]Items[/url]  和  [url=http://msdn.microsoft.com/zh-cn/library/system.windows.controls.itemscontrol.itemssource(v=vs.90).aspx]ItemsSource[/url] ,其标头属性为  [url=http://msdn.microsoft.com/zh-cn/library/system.windows.controls.headereditemscontrol.header(v=vs.90).aspx]Header[/url] 。基本用法看下面xaml:
<ToolBarTray Background="White">
 <ToolBar Band="10" BandIndex="10">
  <Button>
   <Image Source="Ore.jpg" />
  </Button>
  <Separator/>
  <Button>
   <Image Source="Ore.jpg" />
  </Button>
 </ToolBar>
</ToolBarTray>
其中Separator表示分隔条,可以为工具条中的每个按钮设计单击事件打开某一程序。 工具条默认显示为横向,如果想要纵向显示需要设置工具条的Orientation属性。 工具条大概显示如下: [img]http://files.jb51.net/file_images/article/201611/2016112722420016.png[/img] [img]http://files.jb51.net/file_images/article/201611/2016112722420017.png[/img] 4、 ToolTip控件, Tooltip控件是一个简单,但非常有用的控件。它能够为我们的软件提供非常漂亮的提示信息,提高软件的可用性,给用户比较好的体验。上xaml:
<Button Height="25" Content="提示工具演示" HorizontalAlignment="Center">
   <Button.ToolTip>
    <ToolTip Background="#60AA4030" Foreground="White" HasDropShadow="False" 
       Placement="Mouse">
     <StackPanel>
      <TextBlock Margin="3">提示语:这是什么?</TextBlock>
      <!--<Image Source="black.jpg" Stretch="Fill"/>-->
      <TextBlock Margin="3">传说中的3亿网站。</TextBlock>
     </StackPanel>
    </ToolTip>
   </Button.ToolTip>
  </Button>
[img]http://files.jb51.net/file_images/article/201611/2016112722420018.png[/img] 5、 ProgressBar控件,进度条。进度条有两个属性Minimum和Maximum,可以用这两个属性绑定事件来实现进度条的进展操作。一般情况下,进度条用于启动时间过长需要等待而给用户带来的一个视觉加载效果。   [img]http://files.jb51.net/file_images/article/201611/2016112722420019.png[/img]     其中有个IsIndeterminate属性设置进度条的进度动画,设为TRUE的时候,会显示进度信息。默认情况是FALSE。 [b]三、 媒体控件[/b] [img]http://files.jb51.net/file_images/article/201611/2016112722420020.png[/img] 2、  MediaElement控件,在WPF 中可以使用MediaElement 为应用程序添加媒体播放控件,以完成播放音频、视频功能。由于MediaElement 属于UIElement,所以它同时也支持鼠标及键盘的操作。 [url=http://www.cnblogs.com/gnielee/archive/2010/05/06/wpf4-media-player-mediaelement.html]参考实例。[/url] 3、  SoundPlayerAction控件,     WPF定义了一个SoundPlayerAction类(继承自TriggerAction),它用一种友好的方式封装了SoundPlayer类。这样做的好处是,可以在控件的EventTrigger中添加SoundPlayerAciton动作,进而可以播放音频文件。 例如:
<Button Content="xirihanlin">
    <Button.Triggers>
      <EventTrigger RoutedEvent="Button.Click">
        <SoundPlayerAction Source="BLOW.WAV"/>
      </EventTrigger>
    </Button.Triggers>
 </Button>
有SoundPlayerAction类的一个好处是你不用为播放音频文件而在后台书写代码。但是,这样的好处也会给你带来限制,因为你根本无法控制SoundPlayerAction与SoundPlayer之间的交互。 当点击Button时,会创建SoundPlayerAction对象,而SoundPlayerAction内部构建了一个SoundPlayer实例,并把SoundPlayerAction的Source属性值传给了SoundPlayer实例,并调用了SoundPlayer的Play,而事实上,由于音频文件没有提前加载,你将不能在点击的同时就能听见声音。因此,使用SoundPlayerAction类的限制还包括无法提前加载文件和设置循环播放等。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部