[后端人员耍前端系列]KnockoutJs篇:使用WebApi+Bootstrap+KnockoutJs打造单页面程序
一、前言
在前一个专题快速介绍了KnockoutJs相关知识点,也写了一些简单例子,希望通过这些例子大家可以快速入门KnockoutJs。为了让大家可以清楚地看到KnockoutJs在实际项目中的应用,本专题将介绍如何使用WebApi+Bootstrap+KnockoutJs+Asp.net MVC来打造一个单页面Web程序。这种模式也是现在大多数公司实际项目中用到的。
二、SPA(单页面)好处
在介绍具体的实现之前,我觉得有必要详细介绍了SPA。SPA,即Single Page Web Application的缩写,是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。
单页面程序的好处在于:
- 更好的用户体验,让用户在Web app感受native app的速度和流畅。
- 分离前后端关注点,前端负责界面显示,后端负责数据存储和计算,各司其职,不会把前后端的逻辑混杂在一起。
- 减轻服务器压力,服务器只用生成数据就可以,不用管展示逻辑和页面逻辑,增加服务器吞吐量。MVC中Razor语法写的前端是需要服务器完成页面的合成再输出的。
- 同一套后端程序,可以不用修改直接用于Web界面、手机、平板等多种客户端。
当然单页面程序除了上面列出的优点外,也有其不足:
- 不利于SEO。这点如果是做管理系统的话是没影响的
- 初次加载时间相对增加。因为所有的JS、CSS资源会在第一次加载完成,从而使得后面的页面流畅。对于这点可以使用Asp.net MVC中Bundle来进行文件绑定。关于Bundle的详细使用参考文章:http://www.cnblogs.com/xwgli/p/3296809.html和http://www.cnblogs.com/wangiqngpei557/p/3309812.html。
- 导航不可用。如果一定要导航需自行实现前进、后退。对于这点,可以自行实现前进、后退功能来弥补。其实现在手机端网页就是这么干的,现在还要上面导航的。对于一些企业后台管理系统,也可以这么做。
- 对开发人员技能水平、开发成本高。对于这点,也不是事,程序员嘛就需要不断学习来充电,好在一些前端框架都非常好上手。
三、使用Asp.net MVC+WebAPI+Bootstrap+KnockoutJS实现SPA
前面详细介绍了SPA的优缺点,接下来,就让我们使用Asp.net MVC+WebAPI+BS+KO来实现一个单页面程序,从而体验下SPA流畅和对原始Asp.net MVC +Razor做出来的页面进行效果对比。
- 使用VS2013创建Asp.net Web应用程序工程,勾选MVC和WebAPI类库。具体见下图:
2. 创建对应的仓储和模型。这里演示的是一个简单任务管理系统。具体的模型和仓储代码如下:
任务实体类实现:
- public enum TaskState
- {
- Active = ,
- Completed =
- }
- /// <summary>
- /// 任务实体
- /// </summary>
- public class Task
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Description { get; set; }
- public DateTime CreationTime { get; set; }
- public DateTime FinishTime { get; set; }
- public string Owner { get; set; }
- public TaskState State { get; set; }
- public Task()
- {
- CreationTime = DateTime.Parse(DateTime.Now.ToLongDateString());
- State = TaskState.Active;
- }
- }
任务仓储类实现:
- /// <summary>
- /// 这里仓储直接使用示例数据作为演示,真实项目中需要从数据库中动态加载
- /// </summary>
- public class TaskRepository
- {
- #region Static Filed
- private static Lazy<TaskRepository> _taskRepository = new Lazy<TaskRepository>(() => new TaskRepository());
- public static TaskRepository Current
- {
- get { return _taskRepository.Value; }
- }
- #endregion
- #region Fields
- private readonly List<Task> _tasks = new List<Task>()
- {
- new Task
- {
- Id =,
- Name = "创建一个SPA程序",
- Description = "SPA(single page web application),SPA的优势就是少量带宽,平滑体验",
- Owner = "Learning hard",
- FinishTime = DateTime.Parse(DateTime.Now.AddDays().ToString(CultureInfo.InvariantCulture))
- },
- new Task
- {
- Id =,
- Name = "学习KnockoutJs",
- Description = "KnockoutJs是一个MVVM类库,支持双向绑定",
- Owner = "Tommy Li",
- FinishTime = DateTime.Parse(DateTime.Now.AddDays().ToString(CultureInfo.InvariantCulture))
- },
- new Task
- {
- Id =,
- Name = "学习AngularJS",
- Description = "AngularJs是MVVM框架,集MVVM和MVC与一体。",
- Owner = "李志",
- FinishTime = DateTime.Parse(DateTime.Now.AddDays().ToString(CultureInfo.InvariantCulture))
- },
- new Task
- {
- Id =,
- Name = "学习ASP.NET MVC网站",
- Description = "Glimpse是一款.NET下的性能测试工具,支持asp.net 、asp.net mvc, EF等等,优势在于,不需要修改原项目任何代码,且能输出代码执行各个环节的执行时间",
- Owner = "Tonny Li",
- FinishTime = DateTime.Parse(DateTime.Now.AddDays().ToString(CultureInfo.InvariantCulture))
- },
- };
- #endregion
- #region Public Methods
- public IEnumerable<Task> GetAll()
- {
- return _tasks;
- }
- public Task Get(int id)
- {
- return _tasks.Find(p => p.Id == id);
- }
- public Task Add(Task item)
- {
- if (item == null)
- {
- throw new ArgumentNullException("item");
- }
- item.Id = _tasks.Count + ;
- _tasks.Add(item);
- return item;
- }
- public void Remove(int id)
- {
- _tasks.RemoveAll(p => p.Id == id);
- }
- public bool Update(Task item)
- {
- if (item == null)
- {
- throw new ArgumentNullException("item");
- }
- var taskItem = Get(item.Id);
- if (taskItem == null)
- {
- return false;
- }
- _tasks.Remove(taskItem);
- _tasks.Add(item);
- return true;
- }
- #endregion
- }
3. 通过Nuget添加Bootstrap和KnockoutJs库。
4. 实现后端数据服务。这里后端服务使用Asp.net WebAPI实现的。具体的实现代码如下:
- /// <summary>
- /// Task WebAPI,提供数据服务
- /// </summary>
- public class TasksController : ApiController
- {
- private readonly TaskRepository _taskRepository = TaskRepository.Current;
- public IEnumerable<Task> GetAll()
- {
- return _taskRepository.GetAll().OrderBy(a => a.Id);
- }
- public Task Get(int id)
- {
- var item = _taskRepository.Get(id);
- if (item == null)
- {
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
- return item;
- }
- [Route("api/tasks/GetByState")]
- public IEnumerable<Task> GetByState(string state)
- {
- IEnumerable<Task> results = new List<Task>();
- switch (state.ToLower())
- {
- case "":
- case "all":
- results = _taskRepository.GetAll();
- break;
- case "active":
- results = _taskRepository.GetAll().Where(t => t.State == TaskState.Active);
- break;
- case "completed":
- results = _taskRepository.GetAll().Where(t => t.State == TaskState.Completed);
- break;
- }
- results = results.OrderBy(t => t.Id);
- return results;
- }
- [HttpPost]
- public Task Create(Task item)
- {
- return _taskRepository.Add(item);
- }
- [HttpPut]
- public void Put(Task item)
- {
- if (!_taskRepository.Update(item))
- {
- throw new HttpResponseException(HttpStatusCode.NotFound);
- }
- }
- public void Delete(int id)
- {
- _taskRepository.Remove(id);
- }
- }
5. 使用Asp.net MVC Bundle对资源进行打包。对应的BundleConfig实现代码如下:
- /// <summary>
- /// 只需要补充一些缺少的CSS和JS文件。因为创建模板的时候已经添加了一些CSS和JS文件
- /// </summary>
- public class BundleConfig
- {
- // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862
- public static void RegisterBundles(BundleCollection bundles)
- {
- bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
- "~/Scripts/jquery-{version}.js"));
- bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
- "~/Scripts/jquery.validate*"));
- // Use the development version of Modernizr to develop with and learn from. Then, when you're
- // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
- bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
- "~/Scripts/modernizr-*"));
- bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
- "~/Scripts/bootstrap.js",
- "~/Scripts/bootstrap-datepicker.min.js"));
- bundles.Add(new StyleBundle("~/Content/css").Include(
- "~/Content/bootstrap.css",
- "~/Content/bootstrap-datepicker3.min.css",
- "~/Content/site.css"));
- bundles.Add(new ScriptBundle("~/bundles/knockout").Include(
- "~/Scripts/knockout-{version}.js",
- "~/Scripts/knockout.validation.min.js",
- "~/Scripts/knockout.mapping-latest.js"));
- bundles.Add(new ScriptBundle("~/bundles/app").Include(
- "~/Scripts/app/app.js"));
- }
- }
6. 因为我们需要在页面上使得枚举类型显示为字符串。默认序列化时会将枚举转换成数值类型。所以要对WebApiConfig类做如下改动:
- public static class WebApiConfig
- {
- public static void Register(HttpConfiguration config)
- {
- // Web API 配置和服务
- // Web API 路由
- config.MapHttpAttributeRoutes();
- config.Routes.MapHttpRoute(
- name: "DefaultApi",
- routeTemplate: "api/{controller}/{id}",
- defaults: new { id = RouteParameter.Optional }
- );
- // 使得序列化使用驼峰式大小写风格序列化属性
- config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
- // 将枚举类型在序列化时序列化字符串
- config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new StringEnumConverter());
- }
- }
注:如果上面没有使用驼峰小写风格序列化的话,在页面绑定数据的时候也要进行调整。如绑定的Name属性的时候直接使用Name大写,如果使用name方式会提示这个属性没有定义错误。由于JS是使用驼峰小写风格对变量命名的。所以建议大家加上使用驼峰小写风格进行序列化,此时绑定的时候只能使用"name"这样的形式进行绑定。这样也更符合JS代码的规范。
7. 修改对应的Layout文件和Index文件内容。
Layout文件具体代码如下:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title> Learninghard SPA Application</title>
- @Styles.Render("~/Content/css")
- @Scripts.Render("~/bundles/modernizr")
- </head>
- <body>
- <div class="navbar navbar-inverse navbar-fixed-top">
- <div class="container">
- <div class="navbar-header">
- <p class="navbar-brand">简单任务管理系统</p>
- </div>
- <div class="navbar-collapse collapse">
- <ul class="nav navbar-nav">
- <li class="active"><a href="/">主页</a></li>
- </ul>
- </div>
- </div>
- </div>
- <div class="container body-content" id="main">
- @RenderBody()
- <hr />
- <footer>
- <p>© @DateTime.Now.Year - Learninghard SPA Application</p>
- </footer>
- </div>
- @Scripts.Render("~/bundles/jquery")
- @Scripts.Render("~/bundles/bootstrap")
- @Scripts.Render("~/bundles/knockout")
- @Scripts.Render("~/bundles/app")
- </body>
- </html>
Index页面代码如下:
- @{
- ViewBag.Title = "Index";
- Layout = "~/Views/Shared/_Layout.cshtml";
- }
- <div id="list" data-bind="if:canCreate">
- <h2>Tasks</h2>
- <div class="table-responsive">
- <table class="table table-striped">
- <thead>
- <tr>
- <th>编号</th>
- <th>名称</th>
- <th>描述</th>
- <th>负责人</th>
- <th>创建时间</th>
- <th>完成时间</th>
- <th>状态</th>
- <th></th>
- </tr>
- </thead>
- <tbody data-bind="foreach:tasks">
- <tr>
- <td data-bind="text: id"></td>
- <td><a data-bind="text: name, click: handleCreateOrUpdate"></a></td>
- <td data-bind="text: description"></td>
- <td data-bind="text: owner"></td>
- <td data-bind="text: creationTime"></td>
- <td data-bind="text: finishTime"></td>
- <td data-bind="text: state"></td>
- <td><a class="btn btn-xs btn-primary" data-bind="click:remove" href="javascript:void(0)">Remove</a></td>
- </tr>
- </tbody>
- </table>
- </div>
- <div class="col-sm-4">
- <a href="javascript:void(0)" data-bind="click: function(data, event){ setTaskList('all') }">All </a> |
- <a href="javascript:void(0)" data-bind="click: function(data, event){ setTaskList('active') }"> Active</a> |
- <a href="javascript:void(0)" data-bind="click: function(data, event){ setTaskList('completed') }"> Completed</a>
- </div>
- <div class="col-sm-2 col-sm-offset-6">
- <a href="javascript:void(0)" data-bind="click: handleCreateOrUpdate">添加任务</a>
- </div>
- </div>
- <div id="create" style="visibility: hidden">
- <h2>添加任务</h2>
- <br/>
- <div class="form-horizontal">
- <div class="form-group">
- <label for="taskName" class="col-sm-2 control-label">名称 *</label>
- <div class="col-sm-10">
- <input type="text" data-bind="value: name" class="form-control" id="taskName" name="taskName" placeholder="名称">
- </div>
- </div>
- <div class="form-group">
- <label for="taskDesc" class="col-sm-2 control-label">描述</label>
- <div class="col-sm-10">
- <textarea class="form-control" data-bind="value: description" rows="3" id="taskDesc" name="taskDesc" placeholder="描述"></textarea>
- </div>
- </div>
- <div class="form-group">
- <label for="taskOwner" class="col-sm-2 control-label">负责人 *</label>
- <div class="col-sm-10">
- <input class="form-control" id="taskOwner" name="taskOwner" data-bind="value: owner" placeholder="负责人">
- </div>
- </div>
- <div class="form-group">
- <label for="taskFinish" class="col-sm-2 control-label">预计完成时间 *</label>
- <div class="col-sm-10">
- <input class="form-control datepicker" id="taskFinish" data-bind="value: finishTime" name="taskFinish">
- </div>
- </div>
- <div class="form-group">
- <label for="taskOwner" class="col-sm-2 control-label">状态 *</label>
- <div class="col-sm-10">
- <select id="taskState" class="form-control" data-bind="value: state">
- <option>Active</option>
- <option>Completed</option>
- </select>
- </div>
- </div>
- <div class="form-group">
- <div class="col-sm-offset-2 col-sm-10">
- <button class="btn btn-primary" data-bind="click:handleSaveClick">Save</button>
- <button data-bind="click: handleBackClick" class="btn btn-primary">Back</button>
- </div>
- </div>
- </div>
- </div>
8. 创建对应的前端脚本逻辑。用JS代码来请求数据,并创建对应ViewModel对象来进行前端绑定。具体JS实现代码如下:
- var taskListViewModel = {
- tasks: ko.observableArray(),
- canCreate:ko.observable(true)
- };
- var taskModel = function () {
- this.id = 0;
- this.name = ko.observable();
- this.description = ko.observable();
- this.finishTime = ko.observable();
- this.owner = ko.observable();
- this.state = ko.observable();
- this.fromJS = function(data) {
- this.id = data.id;
- this.name(data.name);
- this.description(data.description);
- this.finishTime(data.finishTime);
- this.owner(data.owner);
- this.state(data.state);
- };
- };
- function getAllTasks() {
- sendAjaxRequest("GET", function (data) {
- taskListViewModel.tasks.removeAll();
- for (var i = 0; i < data.length; i++) {
- taskListViewModel.tasks.push(data[i]);
- }
- }, 'GetByState', { 'state': 'all' });
- }
- function setTaskList(state) {
- sendAjaxRequest("GET", function(data) {
- taskListViewModel.tasks.removeAll();
- for (var i = 0; i < data.length; i++) {
- taskListViewModel.tasks.push(data[i]);
- }},'GetByState',{ 'state': state });
- }
- function remove(item) {
- sendAjaxRequest("DELETE", function () {
- getAllTasks();
- }, item.id);
- }
- var task = new taskModel();
- function handleCreateOrUpdate(item) {
- task.fromJS(item);
- initDatePicker();
- taskListViewModel.canCreate(false);
- $('#create').css('visibility', 'visible');
- }
- function handleBackClick() {
- taskListViewModel.canCreate(true);
- $('#create').css('visibility', 'hidden');
- }
- function handleSaveClick(item) {
- if (item.id == undefined) {
- sendAjaxRequest("POST", function (newItem) { //newitem是返回的对象。
- taskListViewModel.tasks.push(newItem);
- }, null, {
- name: item.name,
- description: item.description,
- finishTime: item.finishTime,
- owner: item.owner,
- state: item.state
- });
- } else {
- sendAjaxRequest("PUT", function () {
- getAllTasks();
- }, null, {
- id:item.id,
- name: item.name,
- description: item.description,
- finishTime: item.finishTime,
- owner: item.owner,
- state: item.state
- });
- }
- taskListViewModel.canCreate(true);
- $('#create').css('visibility', 'hidden');
- }
- function sendAjaxRequest(httpMethod, callback, url, reqData) {
- $.ajax("/api/tasks" + (url ? "/" + url : ""), {
- type: httpMethod,
- success: callback,
- data: reqData
- });
- }
- var initDatePicker = function() {
- $('#create .datepicker').datepicker({
- autoclose: true
- });
- };
- $('.nav').on('click', 'li', function() {
- $('.nav li.active').removeClass('active');
- $(this).addClass('active');
- });
- $(document).ready(function () {
- getAllTasks();
- // 使用KnockoutJs进行绑定
- ko.applyBindings(taskListViewModel, $('#list').get(0));
- ko.applyBindings(task, $('#create').get(0));
- });
到此,我们的单页面程序就开发完毕了,接下来我们来运行看看其效果。
从上面运行结果演示图可以看出,一旦页面加载完之后,所有的操作都好像在一个页面操作,完全感觉浏览器页面转圈的情况。对比于之前使用Asp.net MVC +Razor开发的页面,你是否感觉了SPA的流畅呢?之前使用Asp.net MVC +Razor开发的页面,你只要请求一个页面,你就可以感受整个页面刷新的情况,这样用户体验非常不好。
四、与Razor开发模式进行对比
相信大家从效果上已经看出SPA优势了,接下来我觉得还是有必要与传统实现Web页面方式进行一个对比。与Razor开发方式主要有以下2点不同:
- 页面被渲染的时候,数据在浏览器端得到处理。而不是在服务器上。将渲染压力分配到各个用户的浏览器端,从而减少网站服务器的压力。换做是Razor语法,前端页面绑定语句应该就是如下:
- @Model IEnumerable<KnockoutJSSPA.Models.Task>
- @foreach (var item in Model)
- {
- <tr>
- <td>@item.Name</td>
- <td>@item.Description</td>
- </tr>
- }
这些都是在服务器端由Razor引擎渲染的。这也是使用Razor开发的页面会看到页面转圈的情况的原因。因为你每切换一个页面的时候,都需要请求服务端进行渲染,服务器渲染完成之后再将html返回给客户端进行显示。
2. 绑定的数据是动态的。意味着数据模型的改变会马上反应到页面上。这效果归功于KnockoutJs实现的双向绑定机制。
采用这种方式,对于程序开发也简单了,Web API只负责提供数据,而前端页面也减少了很多DOM操作。由于DOM操作比较繁琐和容易出错。这样也意味着减少了程序隐性的bug。并且,一个后端服务,可以供手机、Web浏览器和平台多个平台使用,避免重复开发。
五、总结
到此,本文的介绍就介绍了。本篇主要介绍了使用KnockoutJs来完成一个SPA程序。其实在实际工作中,打造单页面程序的模式更多的采用AngularJS。然后使用KnockoutJs也有很多,但是KnockoutJs只是一个MVVM框架,其路由机制需要借助其他一些类库,如我们这里使用Asp.net MVC中的路由机制,你还可以使用director.js前端路由框架。相对于KnockoutJs而言,AngularJs是一个MVVM+MVC框架。所以在下一个专题将介绍使用如何使用AngularJs打造一个单页面程序(SPA)。
本文所有源码下载:SPAWithKnockoutJs
另外,如果觉得本文对你有帮助,请帮忙点下推荐或者扫描二维码对我进行打赏,你们的支持也是我继续为大家分享好文章的动力,希望大家可以对我支持。谢谢
[后端人员耍前端系列]KnockoutJs篇:使用WebApi+Bootstrap+KnockoutJs打造单页面程序的更多相关文章
- [后端人员耍前端系列]AngularJs篇:30分钟快速掌握AngularJs
一.前言 对于前端系列,自然少不了AngularJs的介绍了.在前面文章中,我们介绍了如何使用KnockoutJs来打造一个单页面程序,后面一篇文章将介绍如何使用AngularJs的开发一个单页面应用 ...
- [后端人员耍前端系列]AngularJs篇:使用AngularJs打造一个简易权限系统
一.引言 上一篇博文已经向大家介绍了AngularJS核心的一些知识点,在这篇博文将介绍如何把AngularJs应用到实际项目中.本篇博文将使用AngularJS来打造一个简易的权限管理系统.下面不多 ...
- [后端人员耍前端系列]KnockoutJs篇:使用KnockoutJs+Bootstrap实现分页
一.引言 由于最近公司的系统需要改版,改版的新系统我打算使用KnockoutJs来制作Web前端.在做的过程中,遇到一个问题——如何使用KnockoutJs来完成分页的功能.在前一篇文章中并没有介绍使 ...
- [后端人员耍前端系列]Bootstrap篇:30分钟快速掌握Bootstrap
一.引言 很久没有写过博客了,但是最近这段时间都没有闲着,接触了很多方面.比如一些前端框架和组件.还有移动开发React-Native.以及对.NET框架设计的一些重新认识.这些内容在接下来的时间都会 ...
- [后端人员耍前端系列]KnockoutJs篇:快速掌握KnockoutJs
一.引言 之前这个系列文章已经介绍Bootstrap.由于最近项目中,前端是Asp.net MVC + KnockoutJs + Bootstrap来做的.所以我又重新开始写这个系列.今天就让我们来看 ...
- 每天记录一点:NetCore获得配置文件 appsettings.json vue-router页面传值及接收值 详解webpack + vue + node 打造单页面(入门篇) 30分钟手把手教你学webpack实战 vue.js+webpack模块管理及组件开发
每天记录一点:NetCore获得配置文件 appsettings.json 用NetCore做项目如果用EF ORM在网上有很多的配置连接字符串,读取以及使用方法 由于很多朋友用的其他ORM如S ...
- WebApi学习总结系列第二篇(webapi的调试)
目前使用webapi的调试主要有 1.用接口宿主调试.(宿主形式多样:web.winform.还有就是直接用app进行接口调试) 2.用Fiddler抓Http信息,进行调试. 1.用接口宿主调试. ...
- bootstrap学习起步篇:初识bootstrap之表单验证(二)
学习bootstrap是个过程,它提供给我们的文档上有很详细的说明.包括常用的栅栏布局.页面元素等,这里就不啰嗦了,今天,我就来说下结合jquery的表单验证. 最开始不借助插件,我们需要自己去编写验 ...
- webpack + vue + node 打造单页面(入门篇)
1.node下载地址:http://nodejs.cn/download/,安装完成检查node和npm版本 2.淘宝镜像 : npm install cnpm -g --registry=https ...
随机推荐
- 借助无线路由器+2台笔记本+Windows桥接功能,成功绕过了微信聊天记录迁移的BUG
最近入了台iphone se,在迁移微信聊天记录的时候,遇到个BUG.它的迁移流程是这样的:需要将两台手机连接到同一个WIFI上面,然后新手机扫旧手机上面的二维码,来完成导入.中途遇到的问题是: 此时 ...
- python课程第二周重点记录
python课程第二周重点记录 1.元组的元素不可被修改,元组的元素的元素可以被修改(字典在元组中,字典的值可以被修改) 2.个人感觉方便做加密解密 3.一些方法的使用 sb = "name ...
- 用Backbone.js创建一个联系人管理系统(四)
原文: Build a Contacts Manager Using Backbone.js: Part 4 这一系列教程的第四部分,教我们如何完成对已经存在的Contacts进行编辑和保存. 本教程 ...
- 字符串分割函数(New)
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Form ...
- hibernate配置文件中的schema="dbo"在MySQL数据库不可用
把项目的数据库由SQL Server更改为MySQL之后,发现hibernate报错. 问题在于schema="dbo",使用SQL Sever数据库时正常,使用MySQL数据库需 ...
- MyBatis-NET
http://www.codeproject.com/Articles/894127/MyBatis-NET https://mybatis.github.io/mybatis-3/
- hdoj 1016 Prime Ring Problem
Problem Description A ring is compose of n circles as shown in diagram. Put natural number 1, 2, ... ...
- Spring In action chapter1_wiringBeans
Automatically wiring beans Spring attacks automatic wiring from two angles: Component scanning-Sprin ...
- 将excel数据导入到mysql的方法
文本框被键盘遮挡到了,不会再获取焦点的时候被顶到键盘顶部.解决方案:设置A的Position为绝对定位absolute即可,其他几种定位方式未测试,但是不能是fixed ,正是因为这种定位方式,导致它 ...
- 如何定制Activity的标题栏
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); setContentView(R.layout.main); //自定义标题栏 mW ...