XAML和C#

实现相同的窗体的两种语言的源码

<Window Title="XAML 窗口" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width = "300" Height="200" ><DockPanel><Button DockPanel.Dock="Left"Background="AliceBlue" Margin="0 5 0 10"  Content="Hello XAML"/><Button ><!--������DockPanel��������Button--><DockPanel.Dock>Right</DockPanel.Dock><Button.Background><LinearGradientBrush StartPoint="0,0" EndPoint="1,1"><GradientStop Color="Yellow" Offset="0.0" /><GradientStop Color="Red" Offset="0.25" /><GradientStop Color="Blue" Offset="0.75" /><GradientStop Color="LimeGreen" Offset="1.0" /></LinearGradientBrush></Button.Background>Hello XAML</Button></DockPanel>
</Window>
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace mumu_windowcs
{public class MyWindow : Window{[STAThread]public static void Main(){MyWindow win = new MyWindow();win.Show();Application app = new Application();app.Run();}public MyWindow(){InitializeComponent();}private void InitializeComponent(){this.Title = "XAML窗口";this.Width = 300;this.Height = 200;DockPanel panel = new DockPanel();Button btn = new Button();btn.Content = "Hello XAML";btn.Background = new SolidColorBrush(Colors.AliceBlue);btn.Margin = new Thickness(0, 5, 0, 10);DockPanel.SetDock(btn, Dock.Left);panel.Children.Add(btn);Button btn2 = new Button();btn2.Content = "Hello XAML";LinearGradientBrush brush = new LinearGradientBrush();brush.StartPoint = new Point(0, 0);brush.EndPoint = new Point(1, 1);brush.GradientStops.Add(new GradientStop(Colors.Yellow, 0));brush.GradientStops.Add(new GradientStop(Colors.Red, 0.25));brush.GradientStops.Add(new GradientStop(Colors.Blue, 0.75));brush.GradientStops.Add(new GradientStop(Colors.LimeGreen, 1));btn2.Background = brush;DockPanel.SetDock(btn2, Dock.Right);panel.Children.Add(btn2);this.Content = panel;}}
}

XAML文件有两个重要组成部分:一是有完整的开始和结束标签的要素,如Window,DockPanel和Button等,称为“元素”(Element);二是依附于元素的要素,如Width,Height和Background等,称为“属性”(Attribute).
在XAML需要命名空间:xmlns=“http://schemas.microsoft.com/winfx/2006/xaml/presentation”

命名空间以及映射

在WPF中只有4种元素可以作为根元素

  • Window:一个窗体
  • Page:页面
  • Application:一个应用程序
  • ResourceDictionary:代表一个逻辑资源的集合

其他的命名空间

  1. 系统命名空间:xmlns:s =“clr-namespace:System;assembly=mscorlib.dll”
  2. 自定义类
    2-1. 本地的自定义类:xmlns:local =“clr-namespace:mumu_customnamespace”
    2-2. 外部程序的自定义类:xmlns:customlib =“clr-namespace:mumu_custolib;assembly = mumu_customlib”

简单属性

  • 直接赋值的,比如像“Title”和“Width”.
  • 相当于c#中枚举型,直接字符赋值就行,比如
    C#
solidbrush.Color= Colors.AliceBlue;

XAML

<Button Color="AliceBlue" />

还有c#的枚举可以通过运算符“|”,而XAML直接用“,”。

附加属性

  • 附加属性是可以用于多个控件,但是在另外一个类中定义的属性。
  • 附加属性常用于布局,命名方式是“定义类型.属性”比如“DockPanel.Dock=“Left” ”。
  • 附加属性的设置可以使用“Attribute”和“Property-Element”语法
    比如
<Button><!-- 这里是DockPanel,而不是Button--><DockPanel.Dock>Right</DockPanel.Dock>Hello CAML
</Button>

Content属性

<Button Width = "300" Height= "200"><Button.Content>Hello XAML</Button.Content>
</Button>
<Button Width = "300" Height= "200" Content="Hello XAML">
</Button>
<Button Width = "300" Height= "200">
Hello XAML
</Button>

C# 自定义Content属性
在book类之前添加一个Text属性,并将其制定为Content属性。然后在ToString函数中添加Text的内容如下代码

[ContentProperty("Text")] //声明Content属性public class Book{public Book(){}//Name属性public string Name{get;set;}//Price属性的数据类型是一个MoneyType类,该类声明了类型转换器,可以将带有美元符号的价格转换为人民币public double Price{get;set;}//Text属性public string Text { get; set; }public override string ToString(){string str = Name + "售价为:" + Price + "元\n"+Text;return str;}}

类型转换器

字符串 -> 类型转换器 -> CLR对象
自定义类型转换器:
例如:
为Price定义一个新的类型MoneyType,它只是简单的封装了一个double类型的变量。同时提供了一个静态的Parse方法将一个字符串正确转换为MoneyType类型的对象

//声明类型转换器[TypeConverter(typeof(MoneyConverter))]public class MoneyType{private double _value;public MoneyType() { _value = 0; }public MoneyType(double value){_value = value;}public override string ToString(){return _value.ToString();}//价格转换方法,这里只考虑美元和人民币,不考虑其他币种public static MoneyType Parse(string value){string str = (value as string).Trim();if (str[0] == '$'){//将美元转换为人民币string newprice = str.Remove(0, 1);double price = double.Parse(newprice);return new MoneyType(price * 8);}else{//不带特殊符号的字符串默认识别为人民币double price = double.Parse(str);return new MoneyType(price);}}}public class MoneyConverter : TypeConverter{public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType){if (sourceType == typeof(string))return true;return base.CanConvertFrom(context, sourceType);}//转换为字符串类型其实不需要重写此方法public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType){if (destinationType == typeof(string)){return true;}return base.CanConvertTo(context, destinationType);}//将string转换为MoneyTypepublic override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value){if (value is string)return MoneyType.Parse((string)value);return base.ConvertFrom(context, culture, value);}//将MoneyType转换为stringpublic override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType){if (destinationType == typeof(string)){return ((MoneyType)value).ToString();}return base.ConvertTo(context, culture, value, destinationType);}}

标记扩展

现在大多数的属性XAML都可以工作得很好,但依旧会有几种情况XAML难以胜任

  • 将一个属性赋值为null.
  • 将一个属性赋值给一个静态变量
    遇到以上情况我们就需要使用标记扩展了。标记扩展是通过XAML的显式的,一致的语法调用实现。标记扩展可以通过自定义来实现XAML的语义扩展。在XAML中只要属性值被一对花括号{}括起来,XAML解析器就会认为这是一个标记扩展。
    比如
<Button Name="btn" Content ="MyButton" Click="btn_Click" Background="{x:Null}">

C#和XAML合璧

  • Markup +Code-Behind
  • 工作原理
    APP类仍从Main函数开始,XAML的名称和XAML文件中Name属性一致。在Connect方法中获得该窗口的对象的实例,并实现事件处理函数的注册。Connect方法是IComponentConnect必须实现的一个接口方法,在MainWindow的InitializeComponent函数中会根据初始的XAML文件将其转换为当前的应用对象,该函数调用LoadComponent后将一个bool类型的私有变量设置为true。以保证在程序的生命周期中请求的资源只加载一次。加载的资源是一个BAML文件,它和生成的g.cs文件在同一个目录下。BAML文件是一个二进制形式的XAML文件,它会作为一个二进制资源嵌入到程序集中,可以通过Reflector工具查到,这样做的目的是提高程序运行时的性能。

依赖属性

依赖属性是WPF引入的一个新的属性类型。是一种类型为DependcyProperty的属性,其依赖属性标识则是依赖属性的实例。
相关术语:

  • DependencyObject: WPF中的一种类型。继承该类后可以注册和拥有依赖属性。
  • WPF属性系统:WPF通过提供一系列的服务扩展了普通的.NET属性,这些服务总称为“WPF属性系统”
  • .NET属性包装器:指属性的get和set实现
    依赖属性像一个计算过程,依据输入值经过计算得到输出值,整个计算过程依赖其他属性与内在和外在的多种因素

为什么要引入依赖属性

WPF主要设计思想是侧重属性胜于方法和事件。即如果属性可以解决问题,则坚决不使用方法和事件。

依赖属性的用例

该用例实现了资源引用支持,样式支持,动画支持,数据绑定的支持,属性值继承的支持,元数据重载的支持,对WPF设计器的集成支持。
APP的XAML如下

<Application x:Class="mumu_Button01.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:mumu_Button01"StartupUri="MainWindow.xaml"><Application.Resources><!-- 引入一个.NET资源对象--><local:BindingData x:Key="myDataSource"/><!-- 定义一个KEY值为MyBrush的画刷资源--><SolidColorBrush x:Key="MyBrush" Color="Gold"/><!-- 添加一个新的样式 "GreenButtonStyle"--><Style x:Key="GreenButtonStyle"><Setter Property="Control.Background" Value="Green"/></Style></Application.Resources>
</Application>

BindingData.cs如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;namespace mumu_Button01
{class BindingData{public BindingData(){ ColorName = "Red"; }private string name = "Red";public string ColorName{get { return name; }set{name = value;}}}}

MainWindow的XAML如下

<Window x:Class="mumu_Button01.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="mumu_Button01" Height="269" Width="572"><Grid Name="Grid1">      <Grid.RowDefinitions><RowDefinition /><RowDefinition /><RowDefinition /></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition /><ColumnDefinition /><ColumnDefinition /></Grid.ColumnDefinitions><!--资源支持--><Label HorizontalAlignment="Center" VerticalAlignment="Center">资源支持</Label><Button Grid.Row="0" Grid.Column="1"  Name="resouceBtn"  Margin="5" Background="{DynamicResource MyBrush}">金色按钮</Button><!--样式支持--><Label Grid.Row="0" Grid.Column ="2" HorizontalAlignment="Center" VerticalAlignment="Center">样式支持</Label><Button Grid.Row="0" Grid.Column="3" Name="styleBtn"  Padding="0" Margin="5" Style="{StaticResource GreenButtonStyle}">绿色按钮</Button><!--动画支持--><Label Grid.Row="1" Grid.Column ="0" HorizontalAlignment="Center" VerticalAlignment="Center">动画支持</Label><Button Grid.Row="1" Grid.Column="1" Name="animationBtn" Margin="5"><Button.Background><SolidColorBrush x:Name="AnimBrush"/></Button.Background><Button.Triggers><EventTrigger RoutedEvent="Button.Loaded"><BeginStoryboard><Storyboard><ColorAnimationStoryboard.TargetName="AnimBrush" Storyboard.TargetProperty="(SolidColorBrush.Color)"From="Red" To="Green" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" /></Storyboard></BeginStoryboard></EventTrigger></Button.Triggers>动画按钮</Button><!--数据绑定支持--><Label Grid.Row="1" Grid.Column ="2" HorizontalAlignment="Center" VerticalAlignment="Center">数据绑定支持</Label><Button Grid.Row="1" Grid.Column="3" Name="BindingBtn" Background="{Binding Source={StaticResource myDataSource},Path=ColorName}">我被绑定成红色!</Button><!--属性值继承--><Label Grid.Row="2" Grid.Column ="0" HorizontalAlignment="Center" VerticalAlignment="Center">属性继承支持</Label><Button Grid.Row="2" Grid.Column="1" Name="FontSizeWinBtn" Click="FontSizeWinBtn_Click">设置窗口字体:16</Button><Button Grid.Row="2" Grid.Column="2" Name="FontSizeBtn" Click="FontSizeBtn_Click">设置按钮字体:8</Button><Button Grid.Row="2" Grid.Column="3" Name="ResetFontSizeBtn" Click="ResetFontSizeBtn_Click">重置字体:12</Button></Grid>
</Window>

mainWindow.xaml.cs如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;namespace mumu_Button01
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();_oldFontSize = FontSize;}private double _oldFontSize = 0;private void FontSizeWinBtn_Click(object sender, RoutedEventArgs e){FontSize = 16;}private void FontSizeBtn_Click(object sender, RoutedEventArgs e){this.FontSizeBtn.FontSize = 8;}private void ResetFontSizeBtn_Click(object sender, RoutedEventArgs e){FontSize = _oldFontSize;//this.FontSizeBtn.FontSize = _oldFontSize;}}
}

自定义依赖属性

何时需要自定义依赖属性:需要资源引用支持,样式支持,动画支持,数据绑定的支持,属性值继承的支持,元数据重载的支持,对WPF设计器的集成支持的时候。
前置条件:

  • 该类必须继承自DependencyObject类。
  • 该类中必须定义一个public static readonly 成员变量,类型为DependencyProperty.
  • 该依赖属性名必须以“属性名+Property”命名。
  • 必须调用DependencyProperty的注册方法在WPF属性系统中注册该依赖属性或者使用依赖属性的AddOwner方法。
  • 为依赖属性实现一个.NET属性包装器

自定义Button按钮

using System;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;namespace mumu_Button02
{public class SpaceButton : Button{// 传统.Net做法 私有字段搭配一个公开属性string txt;public string Text{set{txt = value;Content = SpaceOutText(txt);}get{return txt;}}// 依赖属性public static readonly DependencyProperty SpaceProperty;//.Net属性包装器public int Space{set{SetValue(SpaceProperty, value);}get{return (int)GetValue(SpaceProperty);}}// 静态的构造函数static SpaceButton(){// 定义元数据FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();metadata.DefaultValue = 0;metadata.Inherits = true;metadata.PropertyChangedCallback += OnSpacePropertyChanged;// 注册依赖属性SpaceProperty = DependencyProperty.Register("Space", typeof(int), typeof(SpaceButton), metadata, ValidateSpaceValue);}// 值验证的回调函数static bool ValidateSpaceValue(object obj){int i = (int)obj;return i >= 0;}// 属性值改变的回调函数static void OnSpacePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args){SpaceButton btn = obj as SpaceButton;string txt = btn.Content as string;if (txt == null) return;btn.Content = btn.SpaceOutText(txt);}// 该方法给字符串间距加上空格string SpaceOutText(string str){if (str == null)return null;StringBuilder build = new StringBuilder();// 往里面加上Space个空格foreach (char ch in str)build.Append(ch + new string(' ', Space));return build.ToString();}}
}

外部调用自定义按钮

<Window x:Class="mumu_Button02.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local ="clr-namespace:mumu_Button02"Title="mumu_Button02" Width="300" Height="300"><Grid><Grid.RowDefinitions><RowDefinition/><RowDefinition/></Grid.RowDefinitions><local:SpaceButton x:Name="btnSpace" Grid.Column="0" Grid.Row="0" Margin="5" Click="btnSpace_Click" Text="设置按钮字符空格:2"></local:SpaceButton><local:SpaceButton x:Name= "winSpace" Grid.Column="0" Grid.Row="1" Margin="5" Click="winSpace_Click" Text="设置窗口字符空格:2"></local:SpaceButton></Grid></Window>

在MainWindow.xaml.cs中添加事件

private void btnSpace_Click(object sender, RoutedEventArgs e){this.btnSpace.Space = 2;}

在窗口中继承依赖属性

static MainWindow(){FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();metadata.Inherits = true;SpaceProperty = SpaceButton.SpaceProperty.AddOwner(typeof(MainWindow));SpaceProperty.OverrideMetadata(typeof(Window),metadata);}public static readonly DependencyProperty SpaceProperty;public int Space{set{SetValue(SpaceProperty, value);}get{return (int)GetValue(SpaceProperty);}}

注意窗口的依赖特性Space不是通过注册而来,而是从SpaceButton的Space属性的AddOwner方法得来的,即依赖属性可以选择把自身添加给其他属性。需要特别注意原始元数据不能在使用,必须新建一个,为了实现属性值继承,将Inherit标识设置为true.

依赖属性的优先级

附加属性

在WPF里,最典型的附加属性就是各种布局里面的属性,比如Grid的Row,Column。
附加属性本质是依赖属性,但是与普通的依赖属性有所不同:

  • 注册不在是通过Register方法注册,而是通过RegisterAttached方法注册。
  • 没有普通的.NET属性包装器,而是通过Get和Set属性名来实现包装的
  • 没有普通的.NET属性

路由事件

路由事件的定义:“Functional definition : A routed event is a type of event that can invoke handlers on multiple in an element tree ,rather than just on the object that object that raised the event.
Implementation definition: A routed event is a CLR event that is backed by an instance of the RounedEvent class and is processed by the Windows Presentation Foundation(WPF)event system”
函数定义:路由事件是一种事件类型,它可以在元素树中的多个元素上调用处理程序,而不仅仅是在引发事件的对象上调用处理程序。
实现定义:路由事件是由RounedEvent类的实例支持并由Windows Presentation Foundation(WPF)事件系统处理的CLR事件

路由事件的作用

以按钮为例,在WinForm中,按钮就是按钮。在WPF中按钮可以任何部件。
传统的事件

路由事件

识别路由事件

路由信息主要包括唯一标识ClickEvent,事件委托形式RoutedEventHandler,以及路由策略,Click事件的路由策略是Bubbling.

路由事件的旅行

路由事件由事件源作为起点,每个事件监听者都要路过。
路由事件的策略有3种

  • Bubbling: 事件从事件源触发一路上追溯到根节点。
  • Direct:事件从事件源出发,围绕事件源转一圈结束。
  • Tunneling:事件源触发事件后,事件从根节点下沉直到事件源。
    改变旅行策略因素之一:事件处理函数

    改变旅行策略因素之二:类和实例事件处理函数
    事件处理函数有两种类型:一是前面所说的普通事件处理函数,称为“实例事件处理函数”(Instance Handlers);二是通过EventManager.RegisterClassHandle方法将一个事件处理函数和一个类关联起来,称为“类事件处理函数”(Class Handlers),其优先级高于前者。

WPF中Command

使用Command的主要原因:

  • 使代码更为简洁,容易支持菜单,工具栏和快捷键等多种途径触发。
  • 无须担心各个UI的状态(启用或者禁用)

WPF的Command模型

模型包括四个部分:

  • Command:应用程序需要执行的任务,比如关闭等任务。
  • CommandBinding: 连接Command和特定的应用程序逻辑。
  • CommandSource:触发Command的对象。
  • Command target :Command 应用在上面的对象。
    Command

ICommand接口

Execute方法:当Command被触发时调用该方法。执行与命令相对应的操作。
CanExecute方法:用来判断该命令是否可应用到当前Command target上,如果该方法返回为true,则可以;否则不行。
CanExceuteChanged事件:Command有执行或者不执行两种状态,状态改变时触发该事件,一般监听该事件的是
CommandSource,他监听到该事件会调用绑定的Command的CanExecute方法检查当前Command的状态,然后决定CommandSource 是否启用.

类RoutedCommand

WPF内置的Command库

WPF自学手册-读书笔记(二)心法相关推荐

  1. WPF自学手册-读书笔记(一)

    本读书笔记是根据李响的<WPF自学手册>编写的学习笔记.争取每一卷出一个读书笔记. 上路吧 WPF 微软四重门 DirectX 使上层应用程序跳过中间层直接全速访问硬件设备.与OpenGL ...

  2. WPF自学手册-读书笔记(三)小有所成

    应用程序窗口 WPF应用程序的重要两个类型是Application 和 Window,前者在一个应用程序中是全局唯一的,代表一个应用程序,它可以提供很多基础的应用程序的服务,应用程序也有生命周期. 应 ...

  3. Star Schema完全参考手册读书笔记二

    本博客继续就书中的一些术语和概念进行总结. 数据仓库体系结构 数据仓库描述任何包含分析型数据库的解决方案,包括独立型数据集市.该术语并不表示一个中心或集成仓库. 企业数据仓库指的是企业信息化工厂的中央 ...

  4. 广告贴——希望大家有空能够参加11月27日的《葵花宝典——WPF自学手册》签名售书活动...

    活动主办方:电子工业出版社博文视点公司 活动时间及地点:2010年11月27日  下午13:30~15:00  中关村图书大厦五层多功能厅 这是一个广告贴.但是我会用心去写,否则发在首页,就愧对大家了 ...

  5. 3D游戏设计读书笔记二

    3D游戏设计读书笔记二 一.简答题 • 解释 游戏对象(GameObjects) 和 资源(Assets)的区别与联系.   GameObjects是一个具体的实例,Assets是包括诸多游戏素材的资 ...

  6. 葵花宝典:WPF自学手册(奋斗的小鸟)_PDF 电子书

    下载地址:http://pan.baidu.com/share/link?shareid=2504268006&uk=721744522 内容简介 这本书最大的作用是让从未接触过Microso ...

  7. oracle直查和call哪个更快,让oracle跑的更快1读书笔记二

    当前位置:我的异常网» 数据库 » <>读书笔记二 <>读书笔记二 www.myexceptions.net  网友分享于:2013-08-23  浏览:9次 <> ...

  8. 《How Tomcat Works》读书笔记(二)

    <How Tomcat Works>读书笔记(二) 这是<How Tomcat Works>第一二章的读书笔记.第一张主要写了一个静态资源处理的web服务器,第二章加了对ser ...

  9. 《Docker 技术入门与实践》-读书笔记二

    <Docker 技术入门与实践>-读书笔记一 <Docker 技术入门与实践>-读书笔记二 一.数据管理 用户在使用 Docker 的过程中,往往需要能查看容器内应用产生的数据 ...

最新文章

  1. Linux那些事儿之我是Sysfs(3)设备模型上层容器
  2. Chrome浏览器查看SSL证书信息
  3. 初识Frida--Android逆向之Java层hook (一)
  4. react重新渲染菜单_React实现递归组件
  5. 前端学习(726):如何交换变量值
  6. Win10 安装 MongoDB 3.6.5 失败的问题及解决方法
  7. package 和 install的区别
  8. 用事件驱动编程解救臃肿的代码
  9. Android技能树 — 网络小结(6)之 OkHttp超超超超超超超详细解析
  10. PYTHON运维开发面试题整理
  11. iphone手机屏幕投射电脑 简单几步教你完成
  12. 【Proteus】单片机H桥驱动24V直流有刷电机
  13. 苹果审核4.3如何解决?混淆?还是重新上架?用这招居然成功上架AppStore了!
  14. iOS切换根控制器动画!
  15. 值得分享的炒白银技巧有哪些?
  16. 弹簧(压簧)力度计算与设计
  17. JSP中page指令
  18. 解决JS双击事件dblclick触发时,同时会执行click事件中的语句
  19. 音视频技术开发周刊 | 254
  20. 为什么国家将加快人工智能研究生培养?又为什么很多研究生评论人工智能是个大坑呢?...

热门文章

  1. 11种流行的木马清除方法
  2. Linux diff命令详解
  3. 关于c3p0报错:An attempt by a client to checkout a Connection has timed out
  4. 图书馆管理系统 13-架构设计
  5. 百度网盘是如何实现妙传的
  6. 项目经理是怎样炼成的?
  7. 主板检测卡(POST卡)故障代码及排除方法速查表
  8. DELL液晶显示器如何进入工程模式
  9. B站 MySQL学习随手记 全是满满的干货!
  10. javascript弹出窗口代码大全(转)