layui源码详细分析系列之流加载模块_玉案轩窗的博客-CSDN博客_layui 流加载

mikel阅读(623)

来源: layui源码详细分析系列之流加载模块_玉案轩窗的博客-CSDN博客_layui 流加载

前言
所谓的流加载实际上是第一种优化手段,通常用于图片丰富的网站,目的是为了提供更好的用户体验。

具体的体现是在页面初始化时,先加载一小部分内容,当用户下拉页面到一定的距离,开始加载另一小部分的内容,直至将所有资源加载呈现,体现的是化整为零的思想,具有较好地用户体现效果。

自实现流加载以及图片懒加载功能
流加载功能和图片懒加载功能是分开实现的,使用原生的JavaScript开发(所有涉及交互效果的案例等都是使用JavaScript来实现,基础才是王道)现在先说说流加载功能,具体的实现效果图如下:

上面的是自动带图标加载形式的,还有事按钮形式的加载形式,具体效果图如下:

下面说说具体的实现思路,实际上就是监听指定元素的scroll事件,当滚动条滚动时需要做的事情:

判断是否滚动到底部,判断条件是(假设elem就是外部容器节点), elem.clientHeight+ elem.scrollTop === elem.scrollHeight(容器可是区域的高度 + 滚动条距离顶部的距离 === 容器的滚动高度)
当到达条件时就创建加载区域的节点并追加到容器中,当加载成功后,首先删除加载节点,将元素节点都追加到容器中
判断资源是否完全加载,完全加载就显示没有多余的资源
在上面实现思路需要考虑的问题:

滚动条向上滚动带来的重复性问题
我的解决是维持一个状态数组,记录任何时刻前一次scroll触发时的scrollTop,与当前scrollTop比较,只有当前scrollTop大于保存的状态值,才进行相应的程序处理。

流加载中最主要的是判断滚动条是否滚动条底部,从而执行程序处理。

图片懒加载:
所谓的图片懒加载就是页面初始化时不加载所有图片,当用户滚动到可视区域(就是图片需要显示的区域时),加载当前可视区域内的图片,具体的实现效果如下:

由于上传图片有大小限制,可能拉滚动条有点快,不过效果还是可以看到的。
实现的关键点:

判断图片是否在当前可是区域内
实现思路:

所有的img标签的src属性都是空或没有src属性,初始化时显示当前区域的图片,判断的条件(假设图片节点是image, 容器节点elem):image.offsetTop < elem.clientHeight
elem容器节点绑定scroll事件,同时也要处理向上滚动带来的问题
设置需要显示图片的可视区域,判断当前图片是否在可视区域从而是否需要显示,代码如下:
// record = [0], data是图片src的数据源
elem.addEventListener(‘scroll’, function() {
let scrollTop = this.scrollTop,
scrollHeight = this.scrollHeight,
clientHeight = elem.clientHeight;
// 处理向上滚动的重复性问题
if (scrollTop > record[0]) {
record.shift();
record.push(scrollTop);
}
// 只有向下滚动才执行相关程序
if (scrollTop >= record[0]) {
for (let index = 0; index < images.length; index++) {
let image = images[index],
offsetTop = image.offsetTop,
start = scrollTop,
end = start + clientHeight;
// 核心代码,图片显示的可视区域: scrollTop ~ scollTop + clientHeight
if (offsetTop >= start && offsetTop <= end) {
if (!image.src) {
setTimeout(function() {
image.src = data[index];
}, 300);
}
}
}
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
代码主要组织结构图如下:

下面说说layui中流加载模块的实现,该内置模块使用JQuery来实现,对于流加载以及图片懒加载实现思想,我上面的实现思路跟它的相似,核心代码还是有所区别,下面主要说说区别点:

layui内置流加载模块,考虑到容器元素是全局的情况处理, 作者考虑的很全面
内置模块对外提供api支持用于自实现具体的容器元素
layui该内置模块实现在操作的过程中更加流畅自然
该内置模块代码组织结构图如下:

该模块组织非常简洁明了,load是用于处理流加载,lazyimg是处理图片懒加载的。

layui框架内置模块flow.js的详细代码注释以及我自己实现的demo的源代码会上传到我的Github上,与君共进步。
————————————————
版权声明:本文为CSDN博主「玉案轩窗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/s1879046/article/details/76856015

layui源码详细分析系列之element模块以及自定义事件机制_玉案轩窗的博客-CSDN博客

mikel阅读(689)

来源: layui源码详细分析系列之element模块以及自定义事件机制_玉案轩窗的博客-CSDN博客

element内置模块时layui框架中页面元素的交互功能的实现,主要是选项卡、面板、导航等的交互的实现。

下面先分析element模块的组织结构,具体如下图所示:

从上图中可以看出,element模块使用该框架自己的模块加载机制来加载该模块。

该框架内置的模块都是采用面向对象的编程,该模块也是,定义了Element构造函数,对外的API接口都定义在Element的原型对象上,使用JQuery来绑定一些原生的事件。call对象中定义真正处理选项卡等交互以及初始状态的处理程序。

element模块中使用框架自己定义的事件处理机制,具体实际上使用layui主体文件中的onevent和event来进行事件的注册以及事件的执行。

因为该模块最后输出的是函数类型,所以在使用该模块时要求如下:

layui.use([‘element’], function() {
var element = layui.element();
});
1
2
3
上面实际会自动执行一些初始化的工作,具体就会选项卡、面板等的初始状态的设置。

下面讲解layui自定义的事件机制,该框架定义事件名的形式如下:

功能名(lay-filter属性名)
1
在该框架中有lay-filter属性,该属性就是用于事件标识的。

什么时候使用该框架内置的事件机制?当你想要执行其他的操作,例如获取相关数据等,就可以使用自定义的事件机制。

下面使用实例来讲解框架事件机制的具体的逻辑处理,假设选项卡有属性lay-filter=’demo’, 那么就可以使用该框架自定义的事件机制,具体如下:

使用该机制的代码:
layui.use([‘element’], function() {
var ele = layui.element();

// tab(demo)就是事件名
ele.on(‘tab(demo)’, function(data) {
cosnole.log(data);
});
});
1
2
3
4
5
6
7
8
9
具体的逻辑流程如下图:

具体如上图所示,实际上内部维持了config.event对象来保存事件,onevent实现事件注册以及监听,实际上就是存储在config.event对象中,具体存储形式如下:

config.event[modeName + ‘.’ + event] = [callback]
1
在本例中modeName为element,event为tab(demo), callback就是就是事件处理程序,本例中callback如下:

callback = functio(data) {
console.log(data);
};
1
2
3
详细的代码注释以及相关的逻辑图我会上传到我的Github上,通过今天对于现在这种代码学习,有些迷茫了。

总结下现在这种阅读代码的方式的缺点:

有点纸上谈兵的感觉,不充实很虚
有些为看而看的感觉,没有非常大的收获,反而有些疲惫,没有自己成长了的自豪感
所以以后的文章就不会是这种方式出现了,目前想到的很好提高自己同时最大程度的提高自己,以后的风格将会以事实为依据,来具体展开,具体的形式如下:

自己会动手编写一个简易版实现核心功能的demo,并给出自己的思路
查看框架作者实现的思路,比较自己的思路与作者思路的相同点以及不同点,进行分析总结,丰满自己的思路
今天就到此为止,走走停停,方能走得更远,明天会分析文件上传以及流加载模块。
————————————————
版权声明:本文为CSDN博主「玉案轩窗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/s1879046/article/details/76216485

layui框架详细分析系列之熟悉框架以及提供的页面元素_玉案轩窗的博客-CSDN博客

mikel阅读(569)

来源: layui框架详细分析系列之熟悉框架以及提供的页面元素_玉案轩窗的博客-CSDN博客

前不久,阅读了layer.js的源码,实际上它是layui框架的内置模块,不过可以独立出去使用,主要是用于弹出层的,layui框架中内置了很多模块,在未来的一段时间内,我都会对于这个框架的各个内置模块代码以及整个逻辑进行详细的描述和说明,以此来提高自己的各个方面的能力。

layui是一个UI框架,它的官网对其的说明是经典模块化前端框架,该框架的内部提供了一些UI框架常有的功能点,例如按钮、表单等。

先说说为什么选择这个框架作为学习源码过程中的第一步,原因大概有如下几点:

该框架不依赖与其他的Js框架,虽然它有的模块需要JQuery
它是基于原生Js的,对于我来说我不需要去了解其他Js的框架或库,减少分析该框架的成本
就正式进入正题吧,看框架源码是要有目的性的,这句话确实不错啊。如果只是为看而看,当你看到一定程度后,你就不想看了,代码是枯燥的,特别是看别人的源码,有了目的性,就有所不同,你知道自己想要什么。
在正式进入框架代码阅读分析之前,我认为需要熟悉和使用该框架,这样你在分析其源码的过程中,会更加清晰具体的功能。下面主要介绍该框架中命名规范以及提供的页面元素。
该框架中关于css类的命名规范分为两种,一种是具体模块的css的命令,它遵循layui-模块-状态或类型,另一种是公共类(可以说是不具体属于哪一个模块的),它遵循layui-状态或类型。
该框架中关于js的命名规则:
变量基本采用小写
方法名采用驼峰法命名
逻辑相关的都以is开头
获取相关的方法是以get开头
文件名小写
该框架提供的页面元素有:
内置几种背景颜色,主要的css类:.layui-bg-red、.layui-bd-orange等
图标,使用的是iconfont
按钮以及按钮组,分为大小、状态等,主要的css类:.layui-btn、.layui-btn-primary、.layui-btn-big等
表单,主要的css类有:.layui-form等
导航,分为水平导航、垂直导航、侧边栏导航,主要css类有:.layui-nav、.layui-nav-tree、.layui-nav-side等
选项卡,分为卡片选项卡、简洁风格、可删除的,主要css类有:.layui-tab等
表格,主要css类有.layui-table等
进度条,主要css类有.layui-progress、.layui-progress-bar等
面板,手风琴风格,主要的css类有.layui-collapse等
上面提供的都是css类,用于呈现该框架UI风格,一些功能需要交互才可以完成,所以需要引入模块来保证完整功能的使用。
本文主要是描述下该框架的css命名规范以及熟悉该框架,不出意外,明天会分析该框架中自己提供的模块加载机制,该收拾下班了。
————————————————
版权声明:本文为CSDN博主「玉案轩窗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/s1879046/article/details/76095467

layui框架详细分析系列之框架主体组织结构_玉案轩窗的博客-CSDN博客

mikel阅读(711)

来源: layui框架详细分析系列之框架主体组织结构_玉案轩窗的博客-CSDN博客

layui框架主体
今天正式的进入框架主体部分的学习与分析,该框架开源从GitHub上clone下来的源码主要的部分就是src部分,该部分主要的目录结构构成如下:

从上图可以看出css存储样式,font存储图标(iconfont), images存储图片,lay存储其他内置模块的js文件,layui.js文件就是主体部分。
layui.js还是采用IIFE的形式构成,如下面所示:

;!function(window, undefined) {
// 主要代码
}(window);
1
2
3
其中;号与!号的作用就不在啰嗦了,我之前的文章有对其进行解释,我分析了layui.js整体,绘制了思维导图,具体如下图所示:

从上图可以看出,主体文件的组织结构很清晰明了,主要分为:
1、需要用到的变量,比如内置的模块对象、获取layui.js文件路径的函数以及非常重要的config配置参数对象
2、Lay构造函数,面向对象编程,方法都定义在构造函数的原型对象上
3、通过Lay构造函数创建对象,并通过window对象将其暴露出去

主体结构清晰简洁,封装性感觉很好,没有不必要的属性暴露出去,框架提供的页面元素的相关都是通过定义的CSS类来实现,行为和样式分离开来,耦合性相对较小。

该框架实现了自己的模块加载机制以及自定义事件机制,实现方法也是定义在Lay的原型对象上,我是通过分析内置模块来分析相关联的主体函数的功能的,理解并给予详细的注释。

本篇文章主要分析主体文件的组织结构,相关注释的主体文件以及各个内置模块的注释源文件都会上传到我的Github上,下一篇将会分析该框架的模块加载机制。
————————————————
版权声明:本文为CSDN博主「玉案轩窗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/s1879046/article/details/76162161

layui源码详细分析系列之模块加载机制_玉案轩窗的博客-CSDN博客_layui源码

mikel阅读(579)

来源: layui源码详细分析系列之模块加载机制_玉案轩窗的博客-CSDN博客_layui源码

这个系列的文章主要是分析layui源码,所以文章是针对于主要的功能或者单独的内置模块来书写的,本章主要分析layui框架自己的模块加载机制的实现。

上一篇文章是通过MarkDown来书写,感觉排版比之前的好多了,之后就采用MarkDown来编写了。今天天气热的公司空调都挂了,直接接入正题,真是热……

该框架的模块加载机制主要是使用define和use这两个方法来实现的,define方法适用于定义模块,use方法是用于使用模块,它们都定义在Lay构造函数的原型对象上,而window.layui = new Lay();,所以可以通过layui.define、layui.use来直接调用。

define以及use方法的定义如下:

Lay.fn.define = function(deps, callback) {
// 相关处理代码
};
1
2
3
Lay.fn.use = function(apps, callback, exports) {
// 相关处理代码
};
1
2
3
在该官网上介绍了该框架的几种使用,常用有两种方式:
1、自定义模块,在自己定义的模块中使用相关的模块,如下面所示:

index.js文件代码如下:
layui.define([‘layer’], function(exports) {
var layer = layui.layer;
exports(‘index’, {});
})
1
2
3
4
5
在html文件中使用index模块:
layui.config({
base: ‘index所在目录路径’
}).use(‘index’);
1
2
3
4
2、直接使用use方法

layui.use([‘layer’], function() {
var layer = layui.layer;
});
1
2
3
本章就使用第一种方式来讲解layui框架的模块加载机制,例子就使用上面1中的例子,具体分析步骤如下:
1、先分析index.js中define方法的逻辑处理
2、再分析use方法的逻辑处理

我详细分析了define方法执行的每一步并绘制了逻辑图,具体如下:

具体的步骤如上图所示,实际上define还会调用use方法,虽然define方法是定义模块的实际上还调用了use方法。

use方法的逻辑处理的每一步分析,实际上在define中就具体分析了,在单独使用use的过程中,大部分地处理是相同的,但还是有所区别,具体如下图所示:

因为相关的逻辑分析细节比较多,通过图的方式会更加直观,文字描述会比较繁琐。
具体的代码分析注释以及逻辑分析图我会上传到我的GitHub上,该框架的模块加载机制主要就是通过define以及use来实现,内部实际上维护了config.status来记录模块的加载状态,维护config.modules来存储模块的URL,内部通过script标签来加载模块。
通过今天的学习,对于简单模块加载机制有了一定的认知,对于面向对象以及相关代码的组织也有了一定的理解,不出意外,明天会分析element.js模块。

————————————————
版权声明:本文为CSDN博主「玉案轩窗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/s1879046/article/details/76164371

「前端进阶」高性能渲染十万条数据(虚拟列表) - 云+社区 - 腾讯云

mikel阅读(666)

来源: 「前端进阶」高性能渲染十万条数据(虚拟列表) – 云+社区 – 腾讯云

前言

在工作中,有时会遇到需要一些不能使用分页方式来加载列表数据的业务情况,对于此,我们称这种列表叫做 长列表。比如,在一些外汇交易系统中,前端会实时的展示用户的持仓情况(收益、亏损、收入等),此时对于用户的持仓列表一般是不能分页的。

高性能渲染十万条数据(时间分片)一文中,提到了可以使用 时间分片的方式来对长列表进行渲染,但这种方式更适用于列表项的DOM结构十分简单的情况。本文会介绍使用 虚拟列表的方式,来同时加载大量数据。

为什么需要使用虚拟列表

假设我们的长列表需要展示10000条记录,我们同时将10000条记录渲染到页面中,先来看看需要花费多长时间:

<button id="button">button</button><br>
<ul id="container"></ul>
document.getElementById('button').addEventListener('click',function(){
    // 记录任务开始时间
    let now = Date.now();
    // 插入一万条数据
    const total = 10000;
    // 获取容器
    let ul = document.getElementById('container');
    // 将数据插入容器中
    for (let i = 0; i < total; i++) {
        let li = document.createElement('li');
        li.innerText = ~~(Math.random() * total)
        ul.appendChild(li);
    }
    console.log('JS运行时间:',Date.now() - now);
    setTimeout(()=>{
      console.log('总运行时间:',Date.now() - now);
    },0)

    // print JS运行时间:38
    // print 总运行时间:957
  })

当我们点击按钮,会同时向页面中加入一万条记录,通过控制台的输出,我们可以粗略的统计到,JS的运行时间为 38ms,但渲染完成后的总时间为 957ms

简单说明一下,为何两次 console.log的结果时间差异巨大,并且是如何简单来统计 JS运行时间总渲染时间

  • 在 JS 的 EventLoop中,当JS引擎所管理的执行栈中的事件以及所有微任务事件全部执行完后,才会触发渲染线程对页面进行渲染
  • 第一个 console.log的触发时间是在页面进行渲染之前,此时得到的间隔时间为JS运行所需要的时间
  • 第二个 console.log是放到 setTimeout 中的,它的触发时间是在渲染完成,在下一次 EventLoop中执行的

关于Event Loop的详细内容请参见这篇文章–>

然后,我们通过 ChromePerformance工具来详细的分析这段代码的性能瓶颈在哪里:

Performance可以看出,代码从执行到渲染结束,共消耗了 960.8ms,其中的主要时间消耗如下:

  • Event(click) : 40.84ms
  • Recalculate Style : 105.08ms
  • Layout : 731.56ms
  • Update Layer Tree : 58.87ms
  • Paint : 15.32ms

从这里我们可以看出,我们的代码的执行过程中,消耗时间最多的两个阶段是 RecalculateStyleLayout

  • RecalculateStyle:样式计算,浏览器根据css选择器计算哪些元素应该应用哪些规则,确定每个元素具体的样式。
  • Layout:布局,知道元素应用哪些规则之后,浏览器开始计算它要占据的空间大小及其在屏幕的位置。

在实际的工作中,列表项必然不会像例子中仅仅只由一个li标签组成,必然是由复杂DOM节点组成的。

那么可以想象的是,当列表项数过多并且列表项结构复杂的时候,同时渲染时,会在 RecalculateStyleLayout阶段消耗大量的时间。

虚拟列表就是解决这一问题的一种实现。

什么是虚拟列表

虚拟列表其实是按需显示的一种实现,即只对 可见区域进行渲染,对 非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。

假设有1万条记录需要同时渲染,我们屏幕的 可见区域的高度为 500px,而列表项的高度为 50px,则此时我们在屏幕中最多只能看到10个列表项,那么在首次渲染的时候,我们只需加载10条即可。

说完首次加载,再分析一下当滚动发生时,我们可以通过计算当前滚动值得知此时在屏幕 可见区域应该显示的列表项。

假设滚动发生,滚动条距顶部的位置为 150px,则我们可得知在 可见区域内的列表项为 第4项至`第13项。

实现

虚拟列表的实现,实际上就是在首屏加载的时候,只加载 可视区域内需要的列表项,当滚动发生时,动态通过计算获得 可视区域内的列表项,并将 非可视区域内存在的列表项删除。

  • 计算当前 可视区域起始数据索引( startIndex)
  • 计算当前 可视区域结束数据索引( endIndex)
  • 计算当前 可视区域的数据,并渲染到页面中
  • 计算 startIndex对应的数据在整个列表中的偏移位置 startOffset并设置到列表上

由于只是对 可视区域内的列表项进行渲染,所以为了保持列表容器的高度并可正常的触发滚动,将Html结构设计成如下结构:

<div class="infinite-list-container">
    <div class="infinite-list-phantom"></div>
    <div class="infinite-list">
      <!-- item-1 -->
      <!-- item-2 -->
      <!-- ...... -->
      <!-- item-n -->
    </div>
</div>
  • infinite-list-container可视区域的容器
  • infinite-list-phantom 为容器内的占位,高度为总列表高度,用于形成滚动条
  • infinite-list 为列表项的 渲染区域

接着,监听 infinite-list-containerscroll事件,获取滚动位置 scrollTop

  • 假定 可视区域高度固定,称之为 screenHeight
  • 假定 列表每项高度固定,称之为 itemSize
  • 假定 列表数据称之为 listData
  • 假定 当前滚动位置称之为 scrollTop

则可推算出:

  • 列表总高度 listHeight = listData.length * itemSize
  • 可显示的列表项数 visibleCount = Math.ceil(screenHeight / itemSize)
  • 数据的起始索引 startIndex = Math.floor(scrollTop / itemSize)
  • 数据的结束索引 endIndex = startIndex + visibleCount
  • 列表显示数据为 visibleData = listData.slice(startIndex,endIndex)

当滚动后,由于 渲染区域相对于 可视区域已经发生了偏移,此时我需要获取一个偏移量 startOffset,通过样式控制将 渲染区域偏移至 可视区域中。

  • 偏移量 startOffset = scrollTop – (scrollTop % itemSize);

最终的 简易代码如下:

<template>
  <div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
    <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
    <div class="infinite-list" :style="{ transform: getTransform }">
      <div ref="items"
        class="infinite-list-item"
        v-for="item in visibleData"
        :key="item.id"
        :style="{ height: itemSize + 'px',lineHeight: itemSize + 'px' }"
      >{{ item.value }}</div>
    </div>
  </div>
</template>
export default {
  name:'VirtualList',
  props: {
    //所有列表数据
    listData:{
      type:Array,
      default:()=>[]
    },
    //每项高度
    itemSize: {
      type: Number,
      default:200
    }
  },
  computed:{
    //列表总高度
    listHeight(){
      return this.listData.length * this.itemSize;
    },
    //可显示的列表项数
    visibleCount(){
      return Math.ceil(this.screenHeight / this.itemSize)
    },
    //偏移量对应的style
    getTransform(){
      return `translate3d(0,${this.startOffset}px,0)`;
    },
    //获取真实显示列表数据
    visibleData(){
      return this.listData.slice(this.start, Math.min(this.end,this.listData.length));
    }
  },
  mounted() {
    this.screenHeight = this.$el.clientHeight;
    this.start = 0;
    this.end = this.start + this.visibleCount;
  },
  data() {
    return {
      //可视区域高度
      screenHeight:0,
      //偏移量
      startOffset:0,
      //起始索引
      start:0,
      //结束索引
      end:null,
    };
  },
  methods: {
    scrollEvent() {
      //当前滚动位置
      let scrollTop = this.$refs.list.scrollTop;
      //此时的开始索引
      this.start = Math.floor(scrollTop / this.itemSize);
      //此时的结束索引
      this.end = this.start + this.visibleCount;
      //此时的偏移量
      this.startOffset = scrollTop - (scrollTop % this.itemSize);
    }
  }
};

在线Demo及完整代码:

最终效果如下:

列表项动态高度

在之前的实现中,列表项的高度是固定的,因为高度固定,所以可以很轻易的获取列表项的整体高度以及滚动时的显示数据与对应的偏移量。而实际应用的时候,当列表中包含文本之类的可变内容,会导致列表项的高度并不相同。

比如这种情况:

在虚拟列表中应用动态高度的解决方案一般有如下三种:

1.对组件属性 itemSize进行扩展,支持传递类型为 数字数组函数

  • 可以是一个固定值,如 100,此时列表项是固定高度的
  • 可以是一个包含所有列表项高度的数据,如 [50, 20, 100, 80, …]
  • 可以是一个根据列表项索引返回其高度的函数:(index: number): number

这种方式虽然有比较好的灵活度,但仅适用于可以预先知道或可以通过计算得知列表项高度的情况,依然无法解决列表项高度由内容撑开的情况。

2.将列表项 渲染到屏幕外,对其高度进行测量并缓存,然后再将其渲染至可视区域内。

由于预先渲染至屏幕外,再渲染至屏幕内,这导致渲染成本增加一倍,这对于数百万用户在低端移动设备上使用的产品来说是不切实际的。

3.以 预估高度先行渲染,然后获取真实高度并缓存。

这是我选择的实现方式,可以避免前两种方案的不足。

接下来,来看如何简易的实现:

定义组件属性 estimatedItemSize,用于接收 预估高度

props: {
  //预估高度
  estimatedItemSize:{
    type:Number
  }
}

定义 positions,用于列表项渲染后存储 每一项的高度以及位置信息,

this.positions = [
  // {
  //   top:0,
  //   bottom:100,
  //   height:100
  // }
];

并在初始时根据 estimatedItemSizepositions进行初始化。

initPositions(){
  this.positions = this.listData.map((item,index)=>{
    return {
      index,
      height:this.estimatedItemSize,
      top:index * this.estimatedItemSize,
      bottom:(index + 1) * this.estimatedItemSize
    }
  })
}

由于列表项高度不定,并且我们维护了 positions,用于记录每一项的位置,而 列表高度实际就等于列表中最后一项的底部距离列表顶部的位置。

//列表总高度
listHeight(){
  return this.positions[this.positions.length - 1].bottom;
}

由于需要在 渲染完成后,获取列表每项的位置信息并缓存,所以使用钩子函数 updated来实现:

updated(){
  let nodes = this.$refs.items;
  nodes.forEach((node)=>{
    let rect = node.getBoundingClientRect();
    let height = rect.height;
    let index = +node.id.slice(1)
    let oldHeight = this.positions[index].height;
    let dValue = oldHeight - height;
    //存在差值
    if(dValue){
      this.positions[index].bottom = this.positions[index].bottom - dValue;
      this.positions[index].height = height;
      for(let k = index + 1;k<this.positions.length; k++){
        this.positions[k].top = this.positions[k-1].bottom;
        this.positions[k].bottom = this.positions[k].bottom - dValue;
      }
    }
  })
}

滚动后获取列表 开始索引的方法修改为通过 缓存获取:

//获取列表起始索引
getStartIndex(scrollTop = 0){
  let item = this.positions.find(i => i && i.bottom > scrollTop);
  return item.index;
}

由于我们的缓存数据,本身就是有顺序的,所以获取 开始索引的方法可以考虑通过 二分查找的方式来降低检索次数:

//获取列表起始索引
getStartIndex(scrollTop = 0){
  //二分法查找
  return this.binarySearch(this.positions,scrollTop)
},
//二分法查找
binarySearch(list,value){
  let start = 0;
  let end = list.length - 1;
  let tempIndex = null;
  while(start <= end){
    let midIndex = parseInt((start + end)/2);
    let midValue = list[midIndex].bottom;
    if(midValue === value){
      return midIndex + 1;
    }else if(midValue < value){
      start = midIndex + 1;
    }else if(midValue > value){
      if(tempIndex === null || tempIndex > midIndex){
        tempIndex = midIndex;
      }
      end = end - 1;
    }
  }
  return tempIndex;
},

滚动后将 偏移量的获取方式变更:

scrollEvent() {
  //...省略
  if(this.start >= 1){
    this.startOffset = this.positions[this.start - 1].bottom
  }else{
    this.startOffset = 0;
  }
}

通过faker.js 来创建一些 随机数据

let data = [];
for (let id = 0; id < 10000; id++) {
  data.push({
    id,
    value: faker.lorem.sentences() // 长文本
  })
}

在线Demo及完整代码:

最终效果如下:

从演示效果上看,我们实现了基于 文字内容动态撑高列表项情况下的 虚拟列表,但是我们可能会发现,当滚动过快时,会出现短暂的 白屏现象

为了使页面平滑滚动,我们还需要在 可见区域的上方和下方渲染额外的项目,在滚动时给予一些 缓冲,所以将屏幕分为三个区域:

  • 可视区域上方: above
  • 可视区域: screen
  • 可视区域下方: below

定义组件属性 bufferScale,用于接收 缓冲区数据可视区数据比例

props: {
  //缓冲区比例
  bufferScale:{
    type:Number,
    default:1
  }
}

可视区上方渲染条数 aboveCount获取方式如下:

aboveCount(){
  return Math.min(this.start,this.bufferScale * this.visibleCount)
}

可视区下方渲染条数 belowCount获取方式如下:

belowCount(){
  return Math.min(this.listData.length - this.end,this.bufferScale * this.visibleCount);
}

真实渲染数据 visibleData获取方式如下:

visibleData(){
  let start = this.start - this.aboveCount;
  let end = this.end + this.belowCount;
  return this._listData.slice(start, end);
}

在线Demo及完整代码:

最终效果如下:

基于这个方案,个人开发了一个基于Vue2.x的虚拟列表组件:vue-virtual-listview Github地址:https://github.com/chenqf/vue-virtual-listview

面向未来

在前文中我们使用 监听scroll事件的方式来触发可视区域中数据的更新,当滚动发生后,scroll事件会频繁触发,很多时候会造成 重复计算的问题,从性能上来说无疑存在浪费的情况。

可以使用IntersectionObserver替换监听scroll事件, IntersectionObserver可以监听目标元素是否出现在可视区域内,在监听的回调事件中执行可视区域数据的更新,并且 IntersectionObserver的监听回调是异步触发,不随着目标元素的滚动而触发,性能消耗极低。

遗留问题

我们虽然实现了根据列表项动态高度下的虚拟列表,但如果列表项中包含图片,并且列表高度由图片撑开,由于图片会发送网络请求,此时无法保证我们在获取列表项真实高度时图片是否已经加载完成,从而造成计算不准确的情况。

这种情况下,如果我们能监听列表项的大小变化就能获取其真正的高度了。我们可以使用ResizeObserver来监听列表项内容区域的高度改变,从而实时获取每一列表项的高度。

不过遗憾的是,在撰写本文的时候,仅有少数浏览器支持 ResizeObserver

参考

  • https://github.com/dwqs/blog/issues/70
  • https://github.com/dwqs/blog/issues/72
  • https://itsze.ro/blog/2017/04/09/infinite-list-and-react.html
  • https://zhuanlan.zhihu.com/p/34585166

写在最后

  • 如果你觉得这篇文章稍有收获,点个 再看,让更多的人也能看到这篇文章~
  • 关注我的 Github查看更多精品原创文章~
  • https://github.com/chenqf/frontEndBlog

C# 创建INI文件,写入并可读取。 - xmy_007 - 博客园

mikel阅读(835)

来源: C# 创建INI文件,写入并可读取。 – xmy_007 – 博客园

基于C#winform设计。

首先创建一个类,我命名为IniFiles。并引入命名空间using System.Runtime.InteropServices;

接着,声明API函数

      [DllImport("kernel32")]
        private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
        [DllImport("kernel32")]
        private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);

写入INI函数方法

复制代码
   /// <summary> 
        /// 写入INI文件 
        /// </summary> 
        /// <param name="Section">项目名称(如 [TypeName] )</param> 
        /// <param name="Key">键</param> 
        /// <param name="Value">值</param> 
        public void IniWriteValue(string Section, string Key, string Value)
        {
            WritePrivateProfileString(Section, Key, Value, this.inipath);
        }
复制代码

读取INI文件方法

复制代码
 /// <summary> 
        /// 读出INI文件 
        /// </summary> 
        /// <param name="Section">项目名称(如 [TypeName] )</param> 
        /// <param name="Key">键</param> 
        public string IniReadValue(string Section, string Key)
        {
            StringBuilder temp = new StringBuilder(500);
            int i = GetPrivateProfileString(Section, Key, "", temp, 500, this.inipath);
            return temp.ToString();
        }
复制代码

验证文件是否存在

复制代码
         /// <summary> 
        /// 验证文件是否存在 
        /// </summary> 
        /// <returns>布尔值</returns> 
        public bool ExistINIFile()
        {
            return File.Exists(inipath);
        }
复制代码

在其他窗体页面如何条用?请看:

 

这时候是创建INI文件(位置一般处于资源文件下bin\Debug目录)

public partial class Frm_Login : Form
    {
        HotelSystemORM.Unitl.IniFiles ini = new HotelSystemORM.Unitl.IniFiles(Application.StartupPath + @"\MyConfig.INI");//Application.StartupPath只适用于winform窗体程序
}

 

生成了文件之后就可以写入和读取信息了。

ini.IniWriteValue("登入详细", "账号", "test");
ini.IniWriteValue("登入详细", "密码", "password");

 

读取登入信息(页面加载的时候)

if (ini.ExistINIFile())//验证是否存在文件,存在就读取
label1.Text = ini.IniReadValue("登入详细", "用户名");

 

完成!

 

 

 

IniFiles类全部完整代码

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
    public class IniFiles
    {
        public string inipath;

        //声明API函数

        [DllImport("kernel32")]
        private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
        [DllImport("kernel32")]
        private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);
        /// <summary> 
        /// 构造方法 
        /// </summary> 
        /// <param name="INIPath">文件路径</param> 
        public IniFiles(string INIPath)
        {
            inipath = INIPath;
        }

        public IniFiles() { }

        /// <summary> 
        /// 写入INI文件 
        /// </summary> 
        /// <param name="Section">项目名称(如 [TypeName] )</param> 
        /// <param name="Key">键</param> 
        /// <param name="Value">值</param> 
        public void IniWriteValue(string Section, string Key, string Value)
        {
            WritePrivateProfileString(Section, Key, Value, this.inipath);
        }
        /// <summary> 
        /// 读出INI文件 
        /// </summary> 
        /// <param name="Section">项目名称(如 [TypeName] )</param> 
        /// <param name="Key">键</param> 
        public string IniReadValue(string Section, string Key)
        {
            StringBuilder temp = new StringBuilder(500);
            int i = GetPrivateProfileString(Section, Key, "", temp, 500, this.inipath);
            return temp.ToString();
        }
        /// <summary> 
        /// 验证文件是否存在 
        /// </summary> 
        /// <returns>布尔值</returns> 
        public bool ExistINIFile()
        {
            return File.Exists(inipath);
        }
    }
}
复制代码

 

页面调用:

 

C#中使用设置(Settings.settings) Properties.Settings.Default ._ZXD9790902的专栏-CSDN博客

mikel阅读(663)

来源: C#中使用设置(Settings.settings) Properties.Settings.Default ._ZXD9790902的专栏-CSDN博客

在设计时创建新设置的步骤

在“Solution Explorer”(解决方案资源管理器)中,展开项目的“Properties”(属性)节点。

在“Solution Explorer”(解决方案资源管理器)中,双击要在其中添加新设置的 .settings 文件。此文件的默认名称是 Settings.settings。

 


为设置键入新值,然后保存该文件。

在运行时使用设置
运行时应用程序可以通过代码使用设置。具有应用程序作用域的设置值能够以只读方式进行访问,而用户作用域设置的值可以进行读写。在 C# 中可以通过 Properties 命名空间使用设置。

在运行时读取设置
可在运行时使用 Properties 命名空间读取应用程序作用域及用户作用域设置。Properties 命名空间通过Properties.Settings.Default 对象公开了项目的所有默认设置。编写使用设置的代码时,所有设置都会出现在 IntelliSense 中并且被强类型化。因此,举例来说,如果设置的类型为 System.Drawing.Color,则无需先对其进行强制类型转换即可使用该设置,如下例所示:

this.BackColor = Properties.Settings.Default.myColor;
在运行时保存用户设置
应用程序作用域设置是只读的,只能在设计时或通过在应用程序会话之间修改 <AssemblyName>.exe.config 文件来进行更改。然而,用户作用域设置却可以在运行时进行写入,就像更改任何属性值那样。新值会在应用程序会话持续期间一直保持下去。可以通过调用 Settings.Save 方法来保持在应用程序会话之间对用户设置所做的更改。这些设置保存在 User.config 文件中。

在运行时写入和保持用户设置的步骤


访问用户设置并为其分配新值,如下例所示:

Properties.Settings.Default.myColor = Color.AliceBlue;


如果要保持在应用程序会话之间对用户设置所做的更改,请调用 Save 方法,如以下代码所示:

Properties.Settings.Default.Save();

 

=========================================================================================================================================================================================================================================================================================================================================================================================================

 

 

1、定义

在Settings.settings文件中定义配置字段。把作用范围定义为:User则运行时可更改,Applicatiion则运行时不可更改。可以使用数据网格视图,很方便;

2、读取配置值

text1.text = Properties.Settings.Default.FieldName;
//FieldName是你定义的字段

3、修改和保存配置

Properties.Settings.Default.FieldName = “server”;

Properties.Settings.Default.Save();//使用Save方法保存更改

4、也可以自己创建

创建一个配置类FtpSetting。在WinForm应用程序里,一切配置类都得继承自 ApplicationSettingsBase 类。

sealed class FtpSettings : ApplicationSettingsBase

{
[UserScopedSetting]
[DefaultSettingValue(“127.0.0.1”)]
public string Server
{
get { return (string)this[“Server”]; }
set { this[“Server”] = value; }
}
[UserScopedSetting]
[DefaultSettingValue(“21”)]
public int Port
{
get { return (int)this[“Port”]; }
set { this[“Port”] = value; }
}
}

使用上述配置类,可以用:

private void button2_Click(object sender, EventArgs e)
{
FtpSettings ftp = new FtpSettings();
string msg = ftp.Server + “:” + ftp.Port.ToString();
MessageBox.Show(msg);
}

我们在使用上述FtpSetting 配置时,当然要先进行赋值保存,然后再使用,后面再修改,再保存,再使用。
private void button2_Click(object sender, EventArgs e)
{
FtpSettings ftp = new FtpSettings();
ftp.Server = “ftp.test.com”;
ftp.Port = 8021;
ftp.Save();
ftp.Reload();
string msg = ftp.Server + “:” + ftp.Port.ToString();
MessageBox.Show(msg);
}
嗯。已经Save了,你可能会在应用程序文件夹里找不到它到底保存到哪里去了。由于我们是用UserScope的,所以其实该配置信息是保存到了你的Windows的个人文件夹里去了。比如我的就是 C:\Documents and Settings\brooks\Local Settings\Application Data\TestWinForm目录了。

 

=========================================================================================================================================================================================================================================================================================================================================================================================================

例:
using System;

using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Configuration;

namespace 设置文件读写测试
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
Properties.Settings.Default.name = this.textBox1.Text;
Properties.Settings.Default.Save();
this.label1.Text = Properties.Settings.Default.name;
}

private void button2_Click(object sender, EventArgs e)
{
this.label1.Text = Properties.Settings.Default.name;
}

private void button3_Click(object sender, EventArgs e)
{
FtpSettings ftp = new FtpSettings();
string msg = ftp.Server + “:” + ftp.Port.ToString();
this.label2.Text = msg;
}

private void button4_Click(object sender, EventArgs e)
{
FtpSettings ftp = new FtpSettings();
ftp.Server = this.textBox2.Text ;
ftp.Port = Convert.ToInt32(this.textBox3.Text);
ftp.Save();
//ftp.Reload();
string msg = ftp.Server + “:” + ftp.Port.ToString();
this.label2.Text = msg;
}
}

sealed class FtpSettings : ApplicationSettingsBase
{
[UserScopedSetting]
[DefaultSettingValue(“127.0.0.1”)]
public string Server
{
get { return (string)this[“Server”]; }
set { this[“Server”] = value; }
}
[UserScopedSetting]
[DefaultSettingValue(“21”)]
public int Port
{
get { return (int)this[“Port”]; }
set { this[“Port”] = value; }
}
}
}
————————————————
版权声明:本文为CSDN博主「ZXD9790902」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zxd9790902/article/details/52119340

C# Setting.settings . - 路灯下的诗人 - 博客园

mikel阅读(717)

来源: C# Setting.settings . – 路灯下的诗人 – 博客园

1、定义

在Settings.settings文件中定义配置字段。把作用范围定义为:User则运行时可更改,Applicatiion则运行时不可更改。可以使用数据网格视图,很方便;

2、读取配置值

text1.text = Properties.Settings.Default.FieldName;
//FieldName是你定义的字段

3、修改和保存配置

Properties.Settings.Default.FieldName = “server”;

Properties.Settings.Default.Save();//使用Save方法保存更改

4、也可以自己创建

创建一个配置类FtpSetting。在WinForm应用程序里,一切配置类都得继承自 ApplicationSettingsBase 类。

sealed class FtpSettings : ApplicationSettingsBase

{
[UserScopedSetting]
[DefaultSettingValue(“127.0.0.1”)]
public string Server
{
get { return (string)this[“Server”]; }
set { this[“Server”] = value; }
}
[UserScopedSetting]
[DefaultSettingValue(“21”)]
public int Port
{
get { return (int)this[“Port”]; }
set { this[“Port”] = value; }
}
}

使用上述配置类,可以用:

private void button2_Click(object sender, EventArgs e)
{
FtpSettings ftp = new FtpSettings();
string msg = ftp.Server + “:” + ftp.Port.ToString();
MessageBox.Show(msg);
}

我们在使用上述FtpSetting 配置时,当然要先进行赋值保存,然后再使用,后面再修改,再保存,再使用。
private void button2_Click(object sender, EventArgs e)
{
FtpSettings ftp = new FtpSettings();
ftp.Server = “ftp.test.com”;
ftp.Port = 8021;
ftp.Save();
ftp.Reload();
string msg = ftp.Server + “:” + ftp.Port.ToString();
MessageBox.Show(msg);
}
嗯。已经Save了,你可能会在应用程序文件夹里找不到它到底保存到哪里去了。由于我们是用UserScope的,所以其实该配置信息是保存到了你的Windows的个人文件夹里去了。比如我的就是 C:\Documents and Settings\brooks\Local Settings\Application Data\TestWinForm目录了。

C# App.config文件的使用 - lvyafei - 博客园

mikel阅读(660)

来源: C# App.config文件的使用 – lvyafei – 博客园

App.config文件

1. 配置文件概述: 应用程序配置文件是标准的 XML 文件,XML 标记和属性是区分大小写的。它是可以按需要更改的,开发人员可以使用配置文件来更改设置,而不必重编译应用程序。配置文件的根节点是configuration。我们经常访问的是appSettings,它是由.Net预定义配置节。我们经常使用的配置文件的架构是象下面的形式。先大概有个印象,通过后面的实例会有一个比较清楚的认识。下面的“配置节”可以理解为进行配置一个XML的节点。

常见配置文件模式:

<configuration>
<configSections> //配置节声明区域,包含配置节和命名空间声明
<section> //配置节声明
<sectionGroup> //定义配置节组
<section> //配置节组中的配置节声明
<appSettings> //预定义配置节
<Custom element for configuration section> //配置节设置区域

2. 只有appSettings节的配置文件及访问方法

下面是一个最常见的应用程序配置文件的例子,只有appSettings节。
程序代码: [ 复制代码到剪贴板 ]
<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>
<appSettings>
<add key=”connectionstring” value=”User ID=sa;Data Source=.;Password=;Initial Catalog=test;Provider=SQLOLEDB.1;” />
<add key=”TemplatePATH” value=”Template” />
</appSettings>
</configuration>

 

下面来看看这样的配置文件如何方法。

程序代码: [ 复制代码到剪贴板 ]
string _connectionString=ConfigurationSettings.AppSettings[“connectionstring”];

使用ConfigurationSettings类的静态属性AppSettings就可以直接方法配置文件中的配置信息。这个属性的类型是NameValueCollection。

3. 自定义配置文件

3.1 自定义配置节

一个用户自定义的配置节,在配置文件中分为两部分:一是在<configSections></ configSections> 配置节中声明配置节(上面配置文件模式中的“<section>”),另外是在<configSections>< / configSections >之后设置配置节(上面配置文件模式中的“< Custom element for configuration section>”),有点类似一个变量先声明,后使用一样。声明一个配置文件的语句如下:

<section name=” ” type=” “/>
<section>:声明新配置节,即可创建新配置节。

name:自定义配置节的名称。

type:自定义配置节的类型,主要包括System.Configuration.SingleTagSectionHandler、 System.Configuration.DictionarySectionHandler、 System.Configuration.NameValueSectionHandler。

不同的type不但设置配置节的方式不一样,最后访问配置文件的操作上也有差异。下面我们就举一个配置文件的例子,让它包含这三个不同的type。

程序代码:
<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
<configSections>
<section name=”Test1″ type=”System.Configuration.SingleTagSectionHandler”/>
<section name=”Test2″ type=”System.Configuration.DictionarySectionHandler”/>
<section name=”Test3″ type=”System.Configuration.NameValueSectionHandler” />
</configSections>

<Test1 setting1=”Hello” setting2=”World”/>
<Test2>
<add key=”Hello” value=”World” />
</Test2>
<Test3>
<add key=”Hello” value=”World” />
</Test3>
</configuration>

我们对上面的自定义配置节进行说明。在声明部分使用
<section name=”Test1″ type= “System.Configuration.SingleTagSectionHandler”/>声明了一个配置节它的名字叫Test1,类型为SingleTagSectionHandler。在设置配置节部分使用 <Test1 setting1= “Hello” setting2=”World”/>设置了一个配置节,它的第一个设置的值是Hello,第二个值是World,当然还可以有更多。其它的两个配置节和这个类似。
下面我们看在程序中如何访问这些自定义的配置节。我们用过ConfigurationSettings类的静态方法GetConfig来获取自定义配置节的信息。

程序代码: public static object GetConfig(string sectionName);

下面是访问这三个配置节的代码:

程序代码:
//访问配置节Test1
IDictionary IDTest1 = (IDictionary)ConfigurationSettings.GetConfig(“Test1”);
string str = (string)IDTest1[“setting1″] +” “+(string)IDTest1[“setting2″];
MessageBox.Show(str); //输出Hello World

//访问配置节Test1的方法2
string[] values1=new string[IDTest1.Count];
IDTest1.Values.CopyTo(values1,0);
MessageBox.Show(values1[0]+” “+values1[1]); //输出Hello World

//访问配置节Test2
IDictionary IDTest2 = (IDictionary)ConfigurationSettings.GetConfig(“Test2″);
string[] keys=new string[IDTest2.Keys.Count];
string[] values=new string[IDTest2.Keys.Count];
IDTest2.Keys.CopyTo(keys,0);
IDTest2.Values.CopyTo(values,0);
MessageBox.Show(keys[0]+” “+values[0]);

//访问配置节Test3
NameValueCollection nc=(NameValueCollection)ConfigurationSettings.GetConfig(“Test3″);
MessageBox.Show(nc.AllKeys[0].ToString()+” “+nc[“Hello”]); //输出Hello World

通过上面的代码我们可以看出,不同的type通过GetConfig返回的类型不同,具体获得配置内容的方式也不一样。

[table]
配置节处理程序|返回类型[br]
[/table]
SingleTagSectionHandler Systems.Collections.IDictionary

DictionarySectionHandler Systems.Collections.IDictionary

NameValueSectionHandler Systems.Collections.Specialized.NameValueCollection

3.2 自定义配置节组
配置节组是使用<sectionGroup>元素,将类似的配置节分到同一个组中。配置节组声明部分将创建配置节的包含元素,在< configSections>元素中声明配置节组,并将属于该组的节置于<sectionGroup>元素中。下面是一个包含配置节组的配置文件的例子:

复制代码 代码如下:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
<configSections>
<sectionGroup name=”TestGroup”>
<section name=”Test” type=”System.Configuration.NameValueSectionHandler”/>
</sectionGroup>
</configSections>

<TestGroup>
<Test>
<add key=”Hello” value=”World”/>
</Test>
</TestGroup>
</configuration>

$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

/// <summary>
C App.config文件的使用 - lybwwp - LYBWWP/// Read confing
C App.config文件的使用 - lybwwp - LYBWWP/// </summary>
C App.config文件的使用 - lybwwp - LYBWWP/// <param name=”path”></param>
C App.config文件的使用 - lybwwp - LYBWWP/// <param name=”appKey”></param>
C App.config文件的使用 - lybwwp - LYBWWP/// <returns></returns>
C App.config文件的使用 - lybwwp - LYBWWPpublic string GetConfigValue(string path,string appKey)
C App.config文件的使用 - lybwwp - LYBWWP{
C App.config文件的使用 - lybwwp - LYBWWP  XmlDocument xDoc = new XmlDocument();
C App.config文件的使用 - lybwwp - LYBWWP  try
C App.config文件的使用 - lybwwp - LYBWWP  C App.config文件的使用 - lybwwp - LYBWWP{
C App.config文件的使用 - lybwwp - LYBWWP     xDoc.Load(path);
C App.config文件的使用 - lybwwp - LYBWWP    //xDoc.Load(System.Windows.Forms.Application.ExecutablePath+”.config”);
C App.config文件的使用 - lybwwp - LYBWWP    XmlNode xNode;
C App.config文件的使用 - lybwwp - LYBWWP    XmlElement xElem;
C App.config文件的使用 - lybwwp - LYBWWP    xNode = xDoc.SelectSingleNode(“//appSettings”);
C App.config文件的使用 - lybwwp - LYBWWP    xElem = (XmlElement)xNode.SelectSingleNode(“//add[@key='”+appKey+”‘]”);
C App.config文件的使用 - lybwwp - LYBWWP    if(xElem!=null)
C App.config文件的使用 - lybwwp - LYBWWP     return xElem.GetAttribute(“value”);
C App.config文件的使用 - lybwwp - LYBWWP    else
C App.config文件的使用 - lybwwp - LYBWWP     return   “”;
C App.config文件的使用 - lybwwp - LYBWWP   }
C App.config文件的使用 - lybwwp - LYBWWP catch(Exception)
C App.config文件的使用 - lybwwp - LYBWWP{
C App.config文件的使用 - lybwwp - LYBWWP     return “”;
C App.config文件的使用 - lybwwp - LYBWWP   }
C App.config文件的使用 - lybwwp - LYBWWP}