date: 2019-05-06 13:18:45

updated: 2019-08-09 15:18:45

Qlik Sense学习笔记之插件开发

1.开发前的基础工作

1.1 新建插件

dev-hub -> Extension-Editor -> Create new project -> 自己起一个名字,在模板 template 下拉列表中选择 Angular Basic Visualization template,之后在 qmc 中下载新建的插件,并导入到 IDE 中编辑打开。

1.2 项目结构

新建的插件一共包含4个文件,以插件名称为 test 为例,四个文件分别是:test.js,test.qext, template.html, wbfolder.wbl。

  1. test.js:是主 js 文件,包括插件的一些自定义配置选项,以及获取数据的函数方法,处理数据一般会引用其他的 js 文件,调用其中的函数来处理,以避免文件过长显冗余。

  2. test.qext:是一个 json 格式的配置文件,name 是你在使用插件的时候所用到的名字,比如你的项目名称为 test,但是这里的 name 写的是 "name": "TEST",那么在打包上传到 qmc 之后,在 qmc 里显示的是 test,但是在具体使用的时候是插件的名称是 TEST。在 description 中可以填写具体插件的描述,如果需要添加自定义图片的话,类似于插件的封面图,可以添加 "preview":"test.png",照片可以放在项目的根目录下,直接引用即可,qlik sense 官网给出的图片大小建议是 140*140 png 格式。

  3. template.html:是插件内容呈现的页面,使用的语法是 AngularJS。

  4. wbfolder.wbl:凡是在这里面填写出来的文件都可以在 dev-hub 中被打开编辑,即如果你引用了类似 echarts.js 这种的文件,你只想使用但不需要编辑,那只需要在 test.js 最上面通过 require 的方式引用即可,无需再添加在 wbfoler.wbl 中。在 dev-hub 中点击某插件时,会最先访问该文件,如果不想让其他使用者在 dev-hub 中打开本插件,可以直接删掉,并不会影响正常使用。

1.3 代码结构

define( ["qlik", "text!./template.html"],
function ( qlik, template ) { return {
template: template,
support: {
snapshot: true,
export: true,
exportData: false
},
paint: function () {
return qlik.Promise.resolve();
},
controller: ['$scope', function ( $scope ) {
//add your rendering code here
$scope.html = "Hello World";
}]
}; } );

刚新建的插件只有这一部分最基础的代码,最上面是通过 require 的方式引入的 template.html,与编写 mashup 时引入文件是一样的。

support 里面需要配置的是 qlik sense 自带的功能,比如截图、导出数据,我们需要把 exportData 改为 true,这样就可以右键点击插件导出数据了。

插件第一次运行的时候会先执行 controller 里面的内容,之后会调用 paint 函数,同时每次修改(比如放大缩小插件所在的div)都会调用 paint 函数,以实现自适应或者数据的更新。

paint 函数与 controller 函数之间变量上的互通是通过 $scope 这一个参数来实现的。

2.具体开发

这里的话会按照 比如要引入文件、获取数据等具体的操作来进行分类说明

2.1 修改项目结构

按照 mashup 的编写习惯的话,我们会在项目根目录下新建文件夹,来保证项目结构不会因为文件太多而显得冗余。

原始项目结构
test
- test.js
- test.qext
- template.html
- wbfolder.wbl 修改后的项目结构
test
- lib
- js
- xxx.js
- css
- xxx.css
- img
- xxx.png
- test.js
- test.qext
- template.html
- wbfolder.wbl

在 lib 文件夹下存放了开发需要的各种 js、css、img 文件,只需要在 test.js 文件中引入即可。

2.2 引入 css 文件

假设 css 文件名称为 style.css,我们可以如下引用该文件:

define( ["qlik", "text!./template.html", "text!./lib/css/style.css"],
function ( qlik, template, style ) {
$("<style>").html(style).appendTo("head");
return {
template: template,
support: {
snapshot: true,
export: true,
exportData: false
},
paint: function () {
return qlik.Promise.resolve();
},
controller: ['$scope', function ( $scope ) {
//add your rendering code here
$scope.html = "Hello World";
}]
};
} );

主要是在第一行 define 中通过路径引入该 css 文件,同时在 function 函数中的第一行中将该 css 文件添加到 template.html 文件的头部,即可使用,需要注意的是在 define 中引入的文件,如果你在下面的代码中需要用到这个文件的话,就必须在 function 函数的形参中添加一个指代名称才行, qlik sense 会按照顺序去匹配,缺少形参指代会无法执行。这个形参可以用 '_' 下划线来代替。

"text!./lib/css/style.css" 中的 text! :只有当后面引用的文件是 js 文件的时候可以省略。

2.3 引用 jquery

类似于 mashup 引用,不需要添加什么 js 文件,直接引用即可。

define( ["jquery", "qlik", "text!./template.html", "text!./lib/css/style.css"],
function ( $, qlik, template, style ) {
$("<style>").html(style).appendTo("head");
return {
template: template,
support: {
snapshot: true,
export: true,
exportData: false
},
paint: function () {
return qlik.Promise.resolve();
},
controller: ['$scope', function ( $scope ) {
//add your rendering code here
$scope.html = "Hello World";
}]
};
} );

一般会把 jquery 放在第一个, 并习惯用 '$' 来指代。

2.4 引用 js 文件

与引用 css 文件不一样的地方是,require 会自动识别 js 文件,即在引用的时候是不需要注明文件拓展名的。以引用 render_radar.js 和 echarts-all.js 文件为例,前者是我们用来处理数据的一个子 js 文件,后者是 echarts 生成图表的官方 js 文件。

define( ["jquery", "qlik", "text!./template.html", "text!./lib/css/style.css",
"./lib/js/render_radar", "./lib/js/echarts-all"],
function ( $, qlik, template, style, render_radar ) {
$("<style>").html(style).appendTo("head");
return {
template: template,
support: {
snapshot: true,
export: true,
exportData: false
},
paint: function () {
return qlik.Promise.resolve();
},
controller: ['$scope', function ( $scope ) {
//add your rendering code here
$scope.html = "Hello World";
}]
};
} );

你会发现我们只对 render_radar.js 起了一个形参指代的别名,在 function 函数中我们可以通过 render_radar.xxx(p1, p2……) 的方式来调用 render_radar.js 中的函数,而 echarts-all.js 并没有别名指代,这是因为在生成图表的时候我们必须用到 echarts-all.js 这个文件,但是我们并不会对这个文件进行任何操作,那我们就不需要起一个别名指代(可以起但没必要)。

2.5 调用其他 js 文件中的方法

在上面我们已经调用了 render_radar.js 文件,在该 js 文件中我们定义以下函数方法:init_radar_chart,这样就可以在 test.js (主 js 文件)中通过 render_radar.init_radar_chart(param1, param2, param3, ……) 方法来调用该函数。

define([], function(){
return ({
init_radar_chart:function(param1, param2, param3, ……){
. . .
}
});
});

这里需要注意的是,return 中的函数是外部文件引用本文件时可以调用的函数,如果需要调用本文件的函数可以通过以下方式:

define( [],
function () {
function func2() {
console.log("2");
};
return {
func1:function() {
console.log("1");
func2();
}
};
});

同理,我们也可以在 render_radar.js 文件中引用其他的 js 文件(在 define 中引用 js 文件,在 function 中添加形参指代)以便调用其他文件中的函数方法。

2.6 调用图片文件

如果是在 qext 文件中引用一个图片来作为封面的话好说,可以把图片放到根目录下,直接调用。但是如果是引用一个图片来作为背景图,或者是在设置div的背景图的话,引用的会稍微不同,具体引用方式如下:

background: url("../extensions/test/background.png") no-repeat;

在插件使用图片的时候,qlik sense 会自动封装出来一个路径,如果是按照下面的方式写(前提是 css 和 img 这两个文件夹同级)

background: url("../img/background.png") no-repeat;

会报找不到图片的错误,通过查看错误原因,我们可以这样把访问图片的路径固定写成这样,test 是插件名称,这样就可以访问到图片。

2.7 添加维度、度量等相关自定义配置

与 template、support、paint、controller 平级,添加一下代码:

definition: {
type: "items", // 表明这一级是由好几个item组成,即有子结构体
component: "accordion",
items: {
dimensions: { // 维度
uses: "dimensions",
items:{
title: {
type: "string",
label: "标题",
ref: "qAttributeExpressions.0.qExpression",
component: "expression",
defaultValue: ""
},
},
},
measures: { // 度量
uses: "measures",
items:{
maxMeasure: {
type: "string",
label: "度量最大值",
ref: "qAttributeExpressions.0.qExpression",
component: "expression",
defaultValue: ""
},
},
},
sorting: {
uses: "sorting"
},
addons: {
uses: "addons",
items: {
dataHandling: {
uses: "dataHandling"
}
}
},
appearance:{
uses: "settings"
}
}
},

Qlik Sense官方对自定义属性的介绍

下方链接:

https://help.qlik.com/en-US/sense-developer/June2019/Subsystems/Extensions/Content/Sense_Extensions/Howtos/custom-integer-properties.htm

Field Description
type Used for all custom property type definitions. Can be either string, integer, number, array or boolean.This field is mandatory and should always be "integer" for an integer property type definition.
component Used for defining how the property is visualized in the property panel. Used to override the default component that comes with the type setting.
label Used for defining the label that is displayed in the property panel.
ref Name or ID used to reference a property.
defaultValue Used for defining the default value of your custom property.
min Used for defining the minimum value of the property.
max Used for defining the maximum value of the property.

这段代码中,use 后面写出来的值都是 qlik sense 自己的相关默认配置,固定写法。

ref 中的值需要着重注意,需要分两种情况来看。

  1. 如果这是在维度和度量里的,ref 的填写方式就是上面这样,这是将“标题”中的数据存到了 qMatrix 中,即我们通过 backendApi.getData() 获取数据的时候会一并带回。同时,如果还想添加数据,ref: "qAttributeExpressions.0.qExpression", 只需要把 0 改成 1 即可,可以一直写。

  2. 如果是用户填写的一些自定义选项,比如字体大小、字体颜色等选项,我们可以选择下面的写法。

fontcolor : {
ref: "fontcolor",
label:"字体颜色",
type: "string",
expression: "optional",
defaultValue: '#1E90FF'
},
fontsize: {
type: "integer",
label: "字体大小",
ref: "fontsize",
defaultValue: 24
}

其中 ref 里的值会直接保存在 $scope.layout 中,可以在 paint 中获取到,并实时进行更新。

2.8 通过 backendApi 来获取数据

paint: function (p1,p2) {
let _this = this;
var self = this, requestPage = [{
qTop : 0,
qLeft : 0,
qWidth : 10,
qHeight : 100
}];
// backendApi 只能在 paint 函数中使用
this.backendApi.getData(requestPage).then(function(dataPages) {
//_this.$scope.data = dataPages[0].qMatrix;
var vData = dataPages[0].qMatrix;
console.log(vData); // 动态获取当前object的高度和宽度,并重新赋值echarts图表,自适应
var echart_id = "#" + _this.$scope.getId(); var height = p1[0].offsetHeight;
var width = p1[0].offsetWidth; $(echart_id).height(height);
$(echart_id).width(width);
});
return qlik.Promise.resolve();
}

requestPage 中定义了我们要如何获取数据,因为 qlik sense 的数据都是存放在数组里的,所以我们可以定义从第几行第几列开始获取,以及获取几列几行数据。一次性获取到的总数量是2W,即 行 * 列 <= 2W,如果超过了这个数据量,可以循环去取。

你会发现我们在 paint 的函数里添加了两个参数:p1 和 p2。

  1. p1 指代的是整个 object 的信息,比如用户拖拉整个 object 的实时高度和宽度,通过获取这两个值我们可以做到自适应。在官方文档里,这个形参被命名为 $element。

  2. p2 里面存放了所有自定义选项的值,比如之前设置的字体颜色、字体大小,都可以在 p2 中获取。在官方文档里,这个形参被命名为 layout。

2.9 创建不同 id

考虑到可能在同一个工作表中可能会重复使用到同一种插件,对于点击事件来说,大多数情况下需要针对的是 id,而 id 是不可以重复的,所以我们可以通过下面的方式来保证 id 的唯一性。

controller: ['$scope', function ( $scope ) {
//add your rendering code here
$scope.html = "Hello World"; let generateId = new Date().getTime();
$scope.getId = function () {
return generateId;
}
}]

2.10 获取关于object更多的相关数据

这一点在 2.8 已经提到了,但是因为很重要,所以单独再提出来说一遍。

在 paint: function() 中其实是有参数的,只不过 qlik sense 把参数隐藏了,我们可以添加形参然后打印出来,查看每一个参数的数据格式。

除了可以获取 object 的相关数据,用户在自定义配置中填写所有的值都可以获取到。我们可以通过数据来编写自适应,可以及时处理数据,同样的,在实现点击跳转功能时,url 或者 sheetID 就是从这里获取的,只不过编写的代码是写在了 controller 中。

2.11 添加跳转事件

跳转事件一般有两种,一个是跳转本 App 下的其他任一 sheet 页面,另一个是跳转外部网址。

要跳转 sheet 页面,就需要获取到当前 app 下所有的 sheet 页面有那些,需要用到 app.getAppObjectList() 这个 api 方法。在 mashup 里是通过 appID 来获取到的 app,在 extension 里需要通过 qlik.currApp() 的方法来获取当前 app。同时需要引入一个 core.utils/deferred 文件。

define( ['jquery', "qlik", "core.utils/deferred", "text!./template.html"],
function ($, qlik, Deferred, template ) {
var app = qlik.currApp(); // 直接获取app
return {
template: template,
initialProperties:{
eventArray: []
// 这里定义了一个 property,之后可以从 layout.eventArray中获取到事件相关的属性值
},
support:{
... // 省略
}, ...]
};
} );

之后在 definition 下,与 appearance 平级的地方添加一下代码。

events: {
label: "事件",
type: "array",
ref: "eventArray", // 指向 initialProperties 定义的变量
itemTitleRef: "name",
allowAdd: true,
allowRemove: true,
addTranslation: '添加事件',
items: {
name: {
ref: "name",
label: "事件名称",
type: "string",
expression: "optional"
}, type: {
ref: "type",
label: "事件类型",
component: "dropdown",
type: "string",
options: [{
value: "gotosheet",
label: "跳转工作表"
},{
value: "gotowebSite",
label: "跳转网页"
}]
},
sheetID: {
ref: "sheetID",
label: "跳转页面",
component: "dropdown",
type: "string",
options: function (data) {
var df = Deferred(); app.getAppObjectList("sheet", function (reply) {
var sheetList = reply.qAppObjectList.qItems.map(function (sheet) {
return {
value: sheet.qInfo.qId,
label: sheet.qMeta.title
}
});
//console.log("####");
//console.log(sheetList); df.resolve(sheetList);
}); return df.promise;
},
show: function (p1, p2, p3) {
return p1.type == "gotosheet"; // console.log(p1);
// console.log(p2);
// console.log(p3);
}
},
webSite: {
ref: "webSite",
label: "网址",
type: "string",
defaultValue: '',
show: function(p1){
return p1.type == "gotowebSite";
}
},
sameWindow: {
ref: "sameWindow",
label: "在本页内打开",
type: "boolean",
defaultValue: true,
show: function (p1) {
return p1.type == "gotowebSite";
}
},
}
}

在 template.html 文件里,在想要添加点击事件的 div 中添加 ng-click 方法。

<div ng-click="gotoAnywhere()" id="{{getId()}}">
{{ html }}
</div>

之后在 controller 方法中添加对 gotoAnywhere() 函数的编写。

$scope.gotoAnywhere = function () {
// find() 返回的是object类型,返回第一个符合条件的数据
var res = $scope.layout.eventArray.find(function(item){
if(item.type == 'gotosheet'){
qlik.navigation.gotoSheet(item.sheetID);
}else if(item.type == 'gotowebSite'){
// console.log(item);
var url = item.webSite.startsWith("http://") || item.webSite.startsWith("https://")
? item.webSite : "http://" + item.webSite;
if(!item.sameWindow)
window.open(url, "_blank");
else
window.open(url, "_self");
}
});
};

跳转 sheet 页面调用是 Navigation API, 通过 sheetID 就可以直接跳转。但是对于网页来说,需要进行一步转换,因为用户填写的网址没有添加 http:// 或者 https://, qlik 会在自己的域名后直接拼接上 url,会导致打不开页面,所以需要转换一下 url 链接。

2.12 获取当前服务器下所有的 App 列表 以及获取对应 App 下的 Sheet 列表

otherAppID: {
ref: "otherAppID",
label: "其他App",
component: "dropdown",
type: "string",
options: function (data) {
//var Promise = qlik.Promise;
var df = Deferred();
qlik.getGlobal().getAppList(function (items) {
var appList = items.map(function (item) {
if(item.qDocId != qlik.currApp().id){
return {
value: item.qDocId,
label: item.qTitle
}
}
});
// console.log(appList);
df.resolve(appList);
});
return df.promise;
},
show: function (p1, p2, p3) {
// console.log(p1);
// console.log(p2);
// console.log(p3);
return p1.type == "gotoothersheet";
}
},
otherAppSheetID:{
ref: "otherAppSheetID",
label: "其他App下的工作表",
component: "dropdown",
type: "string",
options: function(data, p1,p2){
// console.log("###");
// console.log(p1.layout.eventArray[0].otherAppID);
// console.log(p2);
// console.log(data);
// 这里不使用 data.otherAppID,因为选择工作表后刷新paint,导致data数据发生改变
// 这里能成功的前提是假设只能有一个跳转事件,有多个的话,下标就会错乱
var otherApp = qlik.openApp(p1.layout.eventArray[0].otherAppID);
// console.log(otherApp);
var defer = Deferred(); otherApp.getAppObjectList("sheet", function (reply) {
var otherSheetList = reply.qAppObjectList.qItems.map(function (sheet) {
return {
value: sheet.qInfo.qId,
label: sheet.qMeta.title
}
});
// console.log("####");
// console.log(otherSheetList); defer.resolve(otherSheetList);
}); return defer.promise;
},
show: function (p1) {
// console.log(p1);
return p1.type == "gotoothersheet" && p1.otherAppID != "";
}
}

2.13 跳转传参

APP 之间跳转分两种方式,直接跳转对应 app 下的某一个 sheet 页的方式或者通过 single object 的方式,分别需要用到以下 API:

  1. App Integration API

官方关于在 url 添加 selections 的例子:
http[s]://<machinename | servername>/{virtual proxy}/sense/app/{appid}/
sheet/{sheetid}/state/analysis/select/{field}/{value1;value2}/select/{field2}/{value1;value2} eg:
http://域名/sense/app/eac10684-59bc-4a4f-9b44-b599d961eac0/sheet/a403971e-0f5c-4c51-8342-a72f462bef63/select/企业名称/蚌埠燃气;东莞燃气/select/年月/2019-05 如果需要先清空之前所有的选择,即每次都以本次打开的选择为优先(因为 selection 是一个全局变量,不同用户对同一个工作表的数据选择都会相互影响),可以在 url 中添加 options 变量,例子如下: http://域名/sense/app/eac10684-59bc-4a4f-9b44-b599d961eac0/sheet/a403971e-0f5c-4c51-8342-a72f462bef63/options/clearselections/select/企业名称/蚌埠燃气;东莞燃气/select/年月/2019-05
  1. Single Integration API

官方对于 url 中 "选项" 这一参数的解释如下:

opt Description
ctxmenu enables the context menu.
currsel displays the Selection bar.
debug starts a JavaScript debugger.The debug option can only be defined in the URL.
noanimate turns off animations.
noselections turns off selections.
nointeraction turns off interaction.

ctxmenu 即用户右键点击是的上下文菜单

nointeraction 即禁止一切交互,包括用户拖拉表格都会被禁止,所以如果要禁止用户进行一些选择上的操作,参数选择 noselections 即可

eg:支持上下文菜单(导出数据等)& 在头部显示选择项 & 禁止用户选择

http://域名/single/?appid=516f7af6-fec7-475b-9750-75a1a590cd86&sheet=567ffa46-a1b8-4d35-8b69-b8885f682b94&opt=ctxmenu,currsel,noselections&select=clearall&select=fieldName,fieldValue&select=fieldName,fieldValue1,fieldValue2

http://域名/single/?appid=516f7af6-fec7-475b-9750-75a1a590cd86&sheet=567ffa46-a1b8-4d35-8b69-b8885f682b94&opt=ctxmenu,currsel,noselections&select=clearall&select=公司代码,13&select=小区,容城县南张镇,爱佳公寓

Qlik Sense学习笔记之插件开发的更多相关文章

  1. Qlik Sense学习笔记之Mashup开发(二)

    date: 2019-01-26 11:28:07 updated: 2019-01-26 11:28:07 Qlik Sense学习笔记之Mashup开发(二) 1.Mobile SPA UI Fr ...

  2. Qlik Sense学习笔记之Mashup开发(一)

    date: 2018-12-21 12:33:29 updated: 2018-12-21 12:33:29 Qlik Sense学习笔记之Mashup开发(一) 1.基于Qlik Sense API ...

  3. jQuery学习笔记之插件开发(4)

    jQuery学习笔记之插件开发(4) github源码地址 插件:了让原有功能的增强. 1.插件的种类(3种):局部.全局.选择器插件 1.1封装对象方法的插件 这种类型的插件是把一些常用或者重复使用 ...

  4. jquery学习笔记---jquery插件开发

    http://www.cnblogs.com/Wayou/p/jquery_plugin_tutorial.html jquery插件开发:http://www.cnblogs.com/damonla ...

  5. Eclipse插件开发 学习笔记 PDF 第一篇到第四篇 免分下载 开发基础 核心技术 高级进阶 综合实例

    <<Eclipse插件开发 学习笔记>>,本书由浅入深.有重点.有针对性地介绍了Eclipse插件开发技术,全书分为4篇共24章.第一篇介绍Eclipse平台界面开发的基础知识 ...

  6. Java-Eclipse插件开发学习笔记

    Eclipse插件 学习笔记 作者   Rick- Bao 开始日期  2014年8月26日 结束日期  2014年8月27日 一 . CVS(current version system) 版本控制 ...

  7. Linux 学习笔记

    Linux学习笔记 请切换web视图查看,表格比较大,方法:视图>>web板式视图 博客园不能粘贴图片吗 http://wenku.baidu.com/view/bda1c3067fd53 ...

  8. A.Kaw矩阵代数初步学习笔记 10. Eigenvalues and Eigenvectors

    “矩阵代数初步”(Introduction to MATRIX ALGEBRA)课程由Prof. A.K.Kaw(University of South Florida)设计并讲授. PDF格式学习笔 ...

  9. <老友记>学习笔记

    这是六个人的故事,从不服输而又有强烈控制欲的monica,未经世事的千金大小姐rachel,正直又专情的ross,幽默风趣的chandle,古怪迷人的phoebe,花心天真的joey——六个好友之间的 ...

随机推荐

  1. php bypass disable function

    前言 最近开学,事太多了,好久没更新了,然后稍微闲一点一直在弄这个php bypass disable function,一开始自己的电脑win10安装蚁剑的插件,一直报错.怀疑是必须linux环境. ...

  2. Next轻量级框架与主流工具的整合

    前言 老大说以后会用 next 来做一下 SSR 的项目,让我们有空先学学.又从 0 开始学习新的东西了,想着还是记录一下学习历程,有输入就要有输出吧,免得以后给忘记学了些什么~ Next框架与主流工 ...

  3. Egg.js学习

    egg.js是什么 是一个node.js的后台web框架,类似的还有express,koa 优势:规范.插件机制Egg.js约定了一套代码目录结构(配置config.路由router.扩展extend ...

  4. Restful 风格是什么?

    1.1 什么是RESTful 1. REST与技术无关,代表的是一种软件架构风格(REST是Representational State Transfer的简称,中文翻译为"表征状态转移&q ...

  5. Spring 注解形式AOP

    AOP 面向切面编程,通过预编译的方式,在运行期通过动态代理实现一种技术,AOP可实现业务与切面的逻辑分离,降低耦合度 一.注解形式的AOP Aspect:切面 Joinpoint:连接点,要拦截的方 ...

  6. 【SCOI2016】背单词

    P3294[SCOI2016]背单词 [提示] 这道题大概是告诉我们,让我们用一堆n个单词安排顺序,如果当前位置为x,当前单词的后缀没在这堆单词出现过,代价就为x,这里的后缀是原意,但不算自己(不算本 ...

  7. 038 01 Android 零基础入门 01 Java基础语法 04 Java流程控制之选择结构 05 案例演示switch结构-星期的表示案例以及总结

    038 01 Android 零基础入门 01 Java基础语法 04 Java流程控制之选择结构 05 案例演示switch结构-星期的表示案例以及总结 本文知识点:案例演示switch结构并对sw ...

  8. Java知识系统回顾整理01基础03变量04类型转换

    一.不同类型之间的数据可以互相转换,但是要满足一定的规则 二.数据类型转换规则 转换规则如图所示  精度高的数据类型就像容量大的杯子,可以放更大的数据 精度低的数据类型就像容量小的杯子,只能放更小的数 ...

  9. 查杀进程小工具——WPF和MVVM初体验

    最近因为工作需要,研究了一下桌面应用程序.在winform.WPF.Electron等几种技术里,最终选择了WPF作为最后的选型.WPF最吸引我的地方,就是MVVM模式了.MVVM模式完全把界面和业务 ...

  10. 多测师讲解jmeter _安装和配置环境(00)_高级讲师肖sir

    1.下载jmeter包,我们已经下载了有现成的: 2.安装jjdk默认安装或自定义安装 默认安装的路径: 如下图 3.第三步:安装完成后配置JDK的环境变量  位置:计算机→属性→高级系统设置→高级→ ...