让我们写一个 Win32 文本编辑器吧 - 2. 计划和显示

如果你已经阅读了简介,相信你已经对我们接下来要做的事情有所了解。

本文,将会把简介中基础程序修改为一个窗体应用程序。并对编辑器接下来的编辑计划进行说明。

1. 程序改造

阅读过曾经我认为C语言就是个弟弟这篇文章的读者应该知道,编辑器(包括所有Win32应用程序控件),本质上都是一个窗口(WNDCLASSA(已被WNDCLASSEX取代)结构体描述)。

在本节,我们将对上一篇文章所建立的项目进行改造,使其弹出一个主窗体,并附加一个编辑器窗体。

  1. 设置项目子系统

在之前,我们为了简便,没有修改 vicapp 项目的子系统,其默认值为控制台应用程序,所以我们可以用如下代码调用 vitality-controls 给出的函数 vic_prints

  1. #include "../../shared-include/vitality-controls.h"
  2. int main(int argc, char** argv) {
  3. vic_prints("hello vic.");
  4. return 0;
  5. }

但是,对于一个编辑器来说,应该是一个窗体应用程序。所以,我们要对 vicapp 进行子系统设置,打开 vicapp 项目属性(参考上一篇文章),最终设置如下:

  1. 修改主程序代码

修改之系统为窗口后,编译程序,会发现如下错误:

这是因为,链接程序会根据项目设置,去查找不同的主函数名称,而对于窗体应用程序,其主函数名应为WinMain,所以这里会报找不到符号 WinMain,因为我们没有定义它。

对于不同项目类型的启动函数定义,参考文件VS安装目录\VC\Tools\MSVC\14.31.31103\crt\src\vcruntime\exe_common.inl, 现在将相关代码列出如下:

  1. #if defined _SCRT_STARTUP_MAIN
  2. using main_policy = __scrt_main_policy;
  3. using file_policy = __scrt_file_policy;
  4. using argv_policy = __scrt_narrow_argv_policy;
  5. using environment_policy = __scrt_narrow_environment_policy;
  6. static int __cdecl invoke_main()
  7. {
  8. return main(__argc, __argv, _get_initial_narrow_environment());
  9. }
  10. #elif defined _SCRT_STARTUP_WMAIN
  11. using main_policy = __scrt_main_policy;
  12. using file_policy = __scrt_file_policy;
  13. using argv_policy = __scrt_wide_argv_policy;
  14. using environment_policy = __scrt_wide_environment_policy;
  15. static int __cdecl invoke_main()
  16. {
  17. return wmain(__argc, __wargv, _get_initial_wide_environment());
  18. }
  19. #elif defined _SCRT_STARTUP_WINMAIN
  20. using main_policy = __scrt_winmain_policy;
  21. using file_policy = __scrt_file_policy;
  22. using argv_policy = __scrt_narrow_argv_policy;
  23. using environment_policy = __scrt_narrow_environment_policy;
  24. static int __cdecl invoke_main()
  25. {
  26. return WinMain(
  27. reinterpret_cast<HINSTANCE>(&__ImageBase),
  28. nullptr,
  29. _get_narrow_winmain_command_line(),
  30. __scrt_get_show_window_mode());
  31. }
  32. #elif defined _SCRT_STARTUP_WWINMAIN
  33. using main_policy = __scrt_winmain_policy;
  34. using file_policy = __scrt_file_policy;
  35. using argv_policy = __scrt_wide_argv_policy;
  36. using environment_policy = __scrt_wide_environment_policy;
  37. static int __cdecl invoke_main()
  38. {
  39. return wWinMain(
  40. reinterpret_cast<HINSTANCE>(&__ImageBase),
  41. nullptr,
  42. _get_wide_winmain_command_line(),
  43. __scrt_get_show_window_mode());
  44. }
  45. #elif defined _SCRT_STARTUP_ENCLAVE || defined _SCRT_STARTUP_WENCLAVE
  46. using main_policy = __scrt_enclavemain_policy;
  47. using file_policy = __scrt_nofile_policy;
  48. using argv_policy = __scrt_no_argv_policy;
  49. using environment_policy = __scrt_no_environment_policy;
  50. #if defined _SCRT_STARTUP_ENCLAVE
  51. static int __cdecl invoke_main()
  52. {
  53. return main(0, nullptr, nullptr);
  54. }
  55. #else
  56. static int __cdecl invoke_main()
  57. {
  58. return wmain(0, nullptr, nullptr);
  59. }
  60. #endif
  61. #endif

可以看到,根据不同的宏定义,函数 invoke_main() 函数的定义也不相同,由于我们的编辑器应该支持Unicode字符,并且我们是一个窗体应用程序。所以,我们主函数应该参考 _SCRT_STARTUP_WWINMAIN 宏定义内的主函数定义。

除了在 exe_common.inl 中定义了主函数的调用函数,另外,窗体应用程序的主函数还在 WinBase.h(该文件可以通过 Windows.h 查找到 #include "WinBase.h" 一行,然后打开,或者可以直接引用) 文件中做了定义,如下:

  1. #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
  2. int
  3. #if !defined(_MAC)
  4. #if defined(_M_CEE_PURE)
  5. __clrcall
  6. #else
  7. WINAPI
  8. #endif
  9. #else
  10. CALLBACK
  11. #endif
  12. WinMain (
  13. _In_ HINSTANCE hInstance,
  14. _In_opt_ HINSTANCE hPrevInstance,
  15. _In_ LPSTR lpCmdLine,
  16. _In_ int nShowCmd
  17. );
  18. int
  19. #if defined(_M_CEE_PURE)
  20. __clrcall
  21. #else
  22. WINAPI
  23. #endif
  24. wWinMain(
  25. _In_ HINSTANCE hInstance,
  26. _In_opt_ HINSTANCE hPrevInstance,
  27. _In_ LPWSTR lpCmdLine,
  28. _In_ int nShowCmd
  29. );
  30. #endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */

根据之前的描述,我们把之前的 vitality-controls.h 修改为如下代码:

  1. #pragma once
  2. #ifdef VITALITY_CONTROLS_EXPORTS
  3. #define VIC_API __declspec(dllexport)
  4. #else
  5. #define VIC_API __declspec(dllimport)
  6. #endif // VITALITY_CONTROLS_EXPORTS
  7. #include <Windows.h>
  8. /**
  9. * 函数描述:
  10. * 初始化编辑器环境,需要在调用任何本程序集的函数之前,
  11. * 调用本函数。
  12. *
  13. * 返回值:
  14. * 如果初始化成功,返回 TRUE,否则返回 FALSE,并设置错误码,
  15. * 错误码可以通过 GetLastError() 获取。
  16. */
  17. VIC_API BOOL Vic_Init();
  18. /**
  19. * 函数描述:
  20. * 创建并初始化一个编辑器。
  21. *
  22. * 参数:
  23. * parent: 新创建的编辑器的父窗体。
  24. *
  25. * 返回值:
  26. * 如果创建控件成功,返回该控件的句柄,否则返回 -1 并设置错误码。
  27. * 错误码可以通过 GetLastError() 获取。
  28. */
  29. VIC_API HWND Vic_CreateEditor(
  30. HWND parent
  31. );

首先,我们将 stdio.h 的引用,换成了 Windows.h,这允许我们使用 Windows 关于桌面应用程序的 API

其次,我们去除了 vic_print 函数的定义。因为该函数主要是上一篇文章测试跨 DLL 调用函数的测试函数。现在,我们不再需要它。

同时,我们添加了两个函数:

  • Vic_Init

    用于初始化环境,主要是注册我们编辑器的窗体类。至于要特别添加一个初始化函数,主要是由于微软官方文档中明确指出,在 DllMain 中调用复杂的函数,可能会造成死锁。
  • Vic_CreateEditor

    用于创建一个编辑器,这里暂时不需要指定编辑器的信息,只是指定一个父窗体的句柄,以便将编辑器添加到窗体。参考曾经我认为C语言就是个弟弟中创建编辑器控件的代码。

接下来,我们还要实现这两个函数。

在项目 vitality-controlssrc\include\ 目录,建立一个 common.h 文件,输入如下内容:

  1. #pragma once
  2. #include "../../../shared-include/vitality-controls.h"
  3. #define EDITOR_CLASS_NAME L"VicEditor"
  4. LRESULT CALLBACK TextEditorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

其中,该文件引入了外部 API 文件定义,同时,声明了一个宏 EDITOR_CLASS_NAME,该宏定义了我们要创建的目标编辑器的类名。

在项目 vitality-controlssrc\controls\ 文件夹下,建立一个 init.c 文件,并编辑如下代码:

  1. #include "../include/common.h"
  2. /**
  3. * 函数描述:
  4. * 初始化编辑器环境,需要在调用任何本程序集的函数之前,
  5. * 调用本函数。
  6. */
  7. VIC_API BOOL Vic_Init() {
  8. WNDCLASSEX wnd = { 0 };
  9. wnd.cbSize = sizeof(wnd);
  10. wnd.hInstance = GetModuleHandle(NULL);
  11. wnd.lpszClassName = EDITOR_CLASS_NAME;
  12. wnd.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));
  13. wnd.hCursor = LoadCursor(NULL, IDC_IBEAM);
  14. wnd.style = CS_GLOBALCLASS | CS_PARENTDC | CS_DBLCLKS;
  15. wnd.lpfnWndProc = TextEditorWindowProc;
  16. return RegisterClassEx(&wnd) != 0;
  17. }

在项目 vitality-controlssrc\controls\ 文件夹下,建立一个 common.c 文件,并输入如下代码:

  1. #include "../include/common.h"
  2. /**
  3. * 函数描述:
  4. * 创建并初始化一个编辑器。
  5. *
  6. * 参数:
  7. * parent: 新创建的编辑器的父窗体。
  8. *
  9. * 返回值:
  10. * 如果创建控件成功,返回该控件的句柄,否则返回 NULL 并设置错误码。
  11. * 错误码可以通过 GetLastError() 获取。
  12. */
  13. VIC_API HWND Vic_CreateEditor(
  14. HWND parent
  15. ) {
  16. RECT rect = { 0 };
  17. if (!GetClientRect(parent, &rect)) {
  18. return NULL;
  19. }
  20. return CreateWindowEx(
  21. 0,
  22. EDITOR_CLASS_NAME,
  23. L"",
  24. WS_CHILD | WS_VISIBLE | ES_MULTILINE |
  25. WS_VSCROLL | WS_HSCROLL |
  26. ES_AUTOHSCROLL | ES_AUTOVSCROLL,
  27. 0, 0,
  28. rect.right,
  29. rect.bottom,
  30. parent,
  31. NULL,
  32. GetModuleHandle(NULL),
  33. NULL
  34. );
  35. }
  36. LRESULT CALLBACK TextEditorWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  37. switch (uMsg) {
  38. case WM_PAINT: {
  39. PAINTSTRUCT ps;
  40. HDC hdc = BeginPaint(hwnd, &ps);
  41. TextOut(hdc, 0, 0, L"HELLO", 5);
  42. EndPaint(hwnd, &ps);
  43. return 0;
  44. }
  45. default:
  46. break;
  47. }
  48. return DefWindowProc(hwnd, uMsg, wParam, lParam);
  49. }

其中,新增了一个 TextEditorWindowProc 函数,该函数是我们编辑器的回调函数,参考 init.c 文件中,对 wnd.lpfnWndProc 字段的赋值。关于回调函数,参考文档

最后,让我们修改我们应用程序的主函数,修改项目 vicapp 的主程序文件 vicapp-main.c 如下所示:


  1. #include <Windows.h>
  2. #include "../../shared-include/vitality-controls.h"
  3. LRESULT CALLBACK MainWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  4. PCWSTR MAIN_CLASS_NAME = L"VIC-APP-MAIN";
  5. HWND editorHwnd = NULL;
  6. LRESULT CALLBACK MainWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  7. {
  8. switch (uMsg)
  9. {
  10. case WM_DESTROY:
  11. PostQuitMessage(0);
  12. return 0;
  13. case WM_CREATE: {
  14. editorHwnd = Vic_CreateEditor(hwnd);
  15. if (editorHwnd == 0) {
  16. int lastError = GetLastError();
  17. ShowWindow(hwnd, 0);
  18. }
  19. return 0;
  20. }
  21. case WM_SIZE: {
  22. RECT rect = { 0 };
  23. if (!GetWindowRect(hwnd, &rect)) {
  24. break;
  25. }
  26. MoveWindow(
  27. editorHwnd,
  28. 0,
  29. 0,
  30. rect.right,
  31. rect.bottom,
  32. TRUE
  33. );
  34. return 0;
  35. }
  36. default:
  37. break;
  38. }
  39. return DefWindowProc(hwnd, uMsg, wParam, lParam);
  40. }
  41. BOOL InitApplication(HINSTANCE hinstance)
  42. {
  43. WNDCLASSEX wcx = { 0 };
  44. wcx.cbSize = sizeof(wcx);
  45. wcx.style = CS_HREDRAW | CS_VREDRAW;
  46. wcx.lpfnWndProc = MainWindowProc;
  47. wcx.cbClsExtra = 0;
  48. wcx.cbWndExtra = 0;
  49. wcx.hInstance = hinstance;
  50. wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  51. wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
  52. wcx.hbrBackground = GetStockObject(WHITE_BRUSH);
  53. wcx.lpszClassName = MAIN_CLASS_NAME;
  54. wcx.hIconSm = LoadImage(
  55. hinstance,
  56. MAKEINTRESOURCE(5),
  57. IMAGE_ICON,
  58. GetSystemMetrics(SM_CXSMICON),
  59. GetSystemMetrics(SM_CYSMICON),
  60. LR_DEFAULTCOLOR
  61. );
  62. return RegisterClassEx(&wcx);
  63. }
  64. BOOL InitInstance(HINSTANCE hinstance, int nCmdShow)
  65. {
  66. HWND hwnd = CreateWindowEx(
  67. 0,
  68. MAIN_CLASS_NAME,
  69. L"VicApp",
  70. WS_OVERLAPPEDWINDOW,
  71. CW_USEDEFAULT,
  72. CW_USEDEFAULT,
  73. CW_USEDEFAULT,
  74. CW_USEDEFAULT,
  75. (HWND)NULL,
  76. (HMENU)NULL,
  77. hinstance,
  78. (LPVOID)NULL
  79. );
  80. if (!hwnd) {
  81. return FALSE;
  82. }
  83. ShowWindow(hwnd, nCmdShow);
  84. UpdateWindow(hwnd);
  85. return TRUE;
  86. }
  87. int WINAPI wWinMain(
  88. _In_ HINSTANCE hInstance,
  89. _In_opt_ HINSTANCE hPrevInstance,
  90. _In_ LPWSTR lpCmdLine,
  91. _In_ int nShowCmd
  92. ) {
  93. MSG msg = { 0 };
  94. if (!Vic_Init()) {
  95. int err = GetLastError();
  96. return FALSE;
  97. }
  98. if (!InitApplication(hInstance))
  99. return FALSE;
  100. if (!InitInstance(hInstance, nShowCmd))
  101. return FALSE;
  102. BOOL fGotMessage;
  103. while ((fGotMessage = GetMessage(&msg, (HWND)NULL, 0, 0)) != 0 && fGotMessage != -1)
  104. {
  105. TranslateMessage(&msg);
  106. DispatchMessage(&msg);
  107. }
  108. return msg.wParam;
  109. }

其中,在出程序的第一句,我们调用了控件初始化函数 Vic_Init,并在创建主窗体的事件处理过程中,调用了 Vic_CreateEditor 函数,创建了一个子窗体,该子窗体就是我们的编辑器。

为了突出显示我们的编辑器,我们在 Vic_Init 函数中,设置背景颜色为红色,代码如下:

  1. wnd.hbrBackground = CreateSolidBrush(RGB(255, 0, 0));

编译,并运行我们的程序,可以看到如下窗体:

由于我们在处理函数 TextEditorWindowProc 中,在窗体上绘制了字符串 "HELLO"。所以,可以看到界面上出现了 "HELLO" 的字样,并且背景色为红色。

2. 之后的计划

由于代码编辑的过程中,想法可能发生改变,所以未来的计划并不是固定死的,有可能发生变更。

通常情况下,变更的可能有:

  • 发现了某个功能的更好的实现方式。
  • 某个功能过于复杂,导致一篇文章写不完。

虽然计划可能会变更,但是大致的思路如下:

  1. 背景设置

    在这里,你将看到,如何设置背景色,或者将我们的编辑器背景设置为一张图片。

    这个过程可能要耗费一节。

  2. 文本绘制

    主要目的是将当前使用 GDI 的文本绘制转换为 DirectWrite 绘制。

    这个过程可能要耗费一节。

  3. 光标

    在此小节,我们将会看到如何将光标显示在编辑器的指定位置。

    这个过程可能要耗费一节。

  4. 鼠标选择和高亮

    在此主题下,我们将会为我们的编辑器添加鼠标选择,以及选择区域高亮显示的支持。

    这个过程可能要耗费 2~3 个小结。

  5. 文本内存结构

    这将是一个比较大的主题,因为文件内容在内存中的保存,根据不同的考虑,将会采用不同的内存结构。

    这个过程可能要耗费 2~3 个小结。

  6. 滚动条实现

    由于我们计划让我们的编辑器可编辑的文件尽可能的大,并且 Windows 自带的滚动条的取值范围有限,所以我们打算实现一个滚动条,其最大取值为 UINT64 的最大取值,这样我们可以处理总行数就大大增加。

    这个过程可能要耗费一节。

  7. Unicode 支持

    这个主题下,我们会对 Unicode 编码格式做一个简单的介绍,并实现对 Unicode 字符的显示。

    这个过程可能要耗费 2~3 个小结。

  8. 文本透明度设置

    由于我们的编辑器允许我们设置背景颜色,甚至背景图片,考虑到文本颜色可能和背景色相近,导致不容易区分,那么文本的透明渲染就很有必要了。如果我们的文本是透明的,那就可以和背景色相结合,生成更丰富的颜色搭配,起到更好的阅读体验的目的。

    这个过程可能要耗费 1~2 个小结。

  9. 添加注解

    到此为止,我们的编辑器已经可以显示内容,选择内容,上下左右滚动,是时候添加注解功能了。

    这个过程可能要耗费 1~2 个小结。

  10. 添加样式支持

    这里所谓的样式,是根据配置,识别出文件的不同组成部分,然后将给定识别部分显示为固定颜色。如下方代码:

    1. int main(int argc, char** argv) {
    2. return 0;
    3. }

    根据配置,将会分别以不同的颜色/字体显示不同的元素,如类型 int 将会被显示为蓝色等等。

    这意味着,过了本节,你将至少可以实现一种编程语言的高亮功能。

    当前,我们考虑实现 C语言 的高亮显示。

好了,到此为止,我们已经能够将我们的控件显示出来了,计划也已经说明。如果你有什么建议,或者发现程序中有 BUG,欢迎到本文档所在项目lets-write-a-edit-control 下留言,或者到源代码项目 vitality-controls 下提交 issue

如果像针对本文留言,关注微信公众号编程之路漫漫,码途求知己,天涯觅一心。

让我们写一个 Win32 文本编辑器吧 - 2. 计划和显示的更多相关文章

  1. 写一个带文本菜单的程序,菜单项如下 (1) 取五个数的和 (2) 取五个数的平均值 (X) 退出。

    问题: 写一个带文本菜单的程序,菜单项如下(1)    取五个数的和 (2)     取五个数的平均值(X)    退出. 由用户做一个选择, 然后执行相应的功能.当用户选择退出时程序结束. 实现: ...

  2. [前端随笔][JavaScript] 制作一个富文本编辑器

    写在前面 现在网上有很多现成的富文本编辑器,比如百度家的UEditor,kindeditor,niceditor等等,功能特别的多,API也很多,要去熟悉他的规则也很麻烦,所以想自己了解一下原理,做一 ...

  3. iView + vue-quill-editor 实现一个富文本编辑器(包含图片,视频上传)

    1. 引入插件(注意IE10以下不支持) npm install vue-quill-editor --savenpm install quill --save (Vue-Quill-Editor需要 ...

  4. AUTOGUI生成的一个简易文本编辑器

    ; Generated by AutoGUI #SingleInstance Force #NoEnv SetWorkingDir %A_ScriptDir% SetBatchLines - #Inc ...

  5. 把DEDE的在线文本编辑器换成Kindeditor不显示问题

    在织梦论坛下载了[Kindeditor编辑器For DedeCMS],按照操作说明安装后,后台文章编辑的区域却显示空白,有人说不兼容V57版本,有人说不兼容gbk版本,我也纠结了很久,在网上找了很多版 ...

  6. [译] 通过 contentEditable 属性创建一个所见即所得的编辑器(富文本编辑器)

    译者注 这只是一篇入门教程,介绍了一些基础知识,仅供参考,切不可因此觉得富文本编辑器很简单. 创建富文本编辑器是一个非常复杂的工程,需要考虑到方方面面,也有很多坑(请参考原文第一条评论). 为免误导大 ...

  7. easyUI整合富文本编辑器KindEditor详细教程(附源码)

    原因 在今年4月份的时候写过一篇关于easyui整合UEditor的文章Spring+SpringMVC+MyBatis+easyUI整合优化篇(六)easyUI与富文本编辑器UEditor整合,从那 ...

  8. vue集成百度UEditor富文本编辑器

    在前端开发的项目中.难免会遇到需要在页面上集成一个富文本编辑器.那么.如果你有这个需求.希望可以帮助到你 vue是前端开发者所追捧的框架,简单易上手,但是基于vue的富文本编辑器大多数太过于精简.于是 ...

  9. 【JavaScript】富文本编辑器

    这是js写的富文本编辑器,还存在一些bug,但基本功能已经实现,通过这个练习,巩固了js富文本编辑方面的知识,里面包含颜色选择器.全屏.表情.上传图片等功能,每个功能实际对应的就是一个小插件啦 部分程 ...

随机推荐

  1. ArcGIS修路问题(最优路径问题)

    1 前言 修路问题,辅助减少花费.用栅格进行路径分析. 2 问题阐述 根据题目要求,找出一条从学校通往某目的地的道路,实现以下操作: (1)坡度在30度以上的地形不适合修建道路,适合修路的坡度相等间隔 ...

  2. MATLAB2018a安装

    1:同时选中进行解压 2:解压完后打开"setup.exe"进入安装步骤 3:选择"使用文件安装密钥" 4:接受条款,下一步 5:复制密钥 09806-0744 ...

  3. JDBC快速入门(附Java通过jar包连接MySQL数据库)

    •通过jar包连接mysql数据库 •下载jar包 Java 连接 MySQL 需要驱动包,官网下载地址为MySQL驱动包官网下载,选择适合的jar包版本进行安装 (记得安装的地址,下面导入包时会用到 ...

  4. java中自己常用到的工具类-压缩解压zip文件

    package com.ricoh.rapp.ezcx.admintoolweb.util; import java.io.File; import java.io.FileInputStream; ...

  5. Python通过snap7库与西门子S7-1200建立S7通信,读写存储器数据,顺便写个流水灯

    1.snap7 简介 snap7 是一个基于以太网与S7系列的西门子PLC通讯的开源库. 支持包括S7系列的S7-200.S7-200 Smart.S7-300.S7-400.S7-1200以及S7- ...

  6. GET、POST请求

    GET和POST的区别主要有以下几个方面: 1.URL可见性: GET:参数URL可见: POST:URL参数不可见: 2.数据传输 GET:通过拼接URL进行传递参数: POST:通过body体传输 ...

  7. 什么是 Spring Batch?

    Spring Boot Batch 提供可重用的函数,这些函数在处理大量记录时非常重要,包括日志/跟踪,事务管理,作业处理统计信息,作业重新启动,跳过和资源管理.它还提供了更先进的技术服务和功能,通过 ...

  8. myisamchk 是用来做什么的?

    它用来压缩 MyISAM 表,这减少了磁盘或内存使用. MyISAM Static 和 MyISAM Dynamic 有什么区别? 在 MyISAM Static 上的所有字段有固定宽度.动态 MyI ...

  9. 什么是 JavaConfig?

    Spring JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的纯 Java 方法.因此它有助于避免使用 XML 配置.使用 JavaConfig 的优点在 ...

  10. jQuery--属性和CSS

    1.属性和CSS介绍 属性(重点掌握) attr(name) 获取指定属性名的值 attr(key,val) 给一个指定属性名设置值 attr(prop) 给多个属性名设置值.参数:prop json ...