用DeWeb可以實現用Delphi開發echarts圖表
DeWeb : Delphi開發者的Web解決方案!
DeWeb是一個可以直接將Delphi程序快速轉換為網頁應用的工具! 使用DeWeb, 開發者不需要學習HTML、JavaScript、Java、PHP、ASP、C#等新知識,用Delphi搞定一切。 DeWeb開發的網頁支持所有客戶端,包括手機、平板等。
網址:https://gitee.com/xamh/dewebsdk
采用DeWeb可以非常方便開發echarts圖表應用。
還不會DeWeb開發的朋友請參考相關文檔
1 打開一個基本DeWeb應用
比如自帶的hello例程
2 創建echarts控件
拖放一個TMemo控件,設置其HelpKeyword為echarts
3 配置echarts控件
(1)在echarts官網打開擬開發的圖表例程,如
https://echarts.apache.org/examples/zh/editor.html?c=pie-nest
效果圖如下:
echarts圖表效果
(2)復制該網頁左側的JS代碼,注意:只復制option后{....}及其中的內容
(3)打開Notepad++, 新建一個文件,粘貼進去, 再全選,然后復制;
這一步驟主要是保持原代碼的換行符。直接復制到TMemo控件中會丟失,導致錯誤。
(4)打開Delphi中TMemo控件的Lines, 粘貼進去
即內容為
{
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} (j9np179%)'
},
legend: {
data: [
'Direct',
'Marketing',
'Search Engine',
'Email',
'Union Ads',
'Video Ads',
'Baidu',
'Google',
'Bing',
'Others'
]
},
series: [
{
name: 'Access From',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
position: 'inner',
fontSize: 14
},
labelLine: {
show: false
},
data: [
{ value: 1548, name: 'Search Engine' },
{ value: 775, name: 'Direct' },
{ value: 679, name: 'Marketing', selected: true }
]
},
{
name: 'Access From',
type: 'pie',
radius: ['45%', '60%'],
labelLine: {
length: 30
},
label: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}:}{c} {per|vhjd919%} ',
backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E',
borderWidth: 1,
borderRadius: 4,
rich: {
a: {
color: '#6E7079',
lineHeight: 22,
align: 'center'
},
hr: {
borderColor: '#8C8D8E',
width: '100%',
borderWidth: 1,
height: 0
},
b: {
color: '#4C5058',
fontSize: 14,
fontWeight: 'bold',
lineHeight: 33
},
per: {
color: '#fff',
backgroundColor: '#4C5058',
padding: [3, 4],
borderRadius: 4
}
}
},
data: [
{ value: 1048, name: 'Baidu' },
{ value: 335, name: 'Direct' },
{ value: 310, name: 'Email' },
{ value: 251, name: 'Google' },
{ value: 234, name: 'Union Ads' },
{ value: 147, name: 'Bing' },
{ value: 135, name: 'Video Ads' },
{ value: 102, name: 'Others' }
]
}
]
}
在窗體的OnMouseDown事件中寫入
dwEcharts(Memo1);
編譯運行,即可得到
(5)更新數據。
執行到上一步,效果出來了,但會發現無法更新數據。
原因是代碼中把數據(data部分)寫成固定的了
先需要把TMemo的內容改成以下。注意"data:XXX"的變化
{
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} (fjbv7pn%)'
},
legend: {
data: [
'Direct',
'Marketing',
'Search Engine',
'Email',
'Union Ads',
'Video Ads',
'Baidu',
'Google',
'Bing',
'Others'
]
},
series: [
{
name: 'Access From',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
position: 'inner',
fontSize: 14
},
labelLine: {
show: false
},
data: this.value0
},
{
name: 'Access From',
type: 'pie',
radius: ['45%', '60%'],
labelLine: {
length: 30
},
label: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}:}{c} {per|v7799v9%} ',
backgroundColor: '#F6F8FC',
borderColor: '#8C8D8E',
borderWidth: 1,
borderRadius: 4,
rich: {
a: {
color: '#6E7079',
lineHeight: 22,
align: 'center'
},
hr: {
borderColor: '#8C8D8E',
width: '100%',
borderWidth: 1,
height: 0
},
b: {
color: '#4C5058',
fontSize: 14,
fontWeight: 'bold',
lineHeight: 33
},
per: {
color: '#fff',
backgroundColor: '#4C5058',
padding: [3, 4],
borderRadius: 4
}
}
},
data: this.value1
}
]
}
對應
this.value0應為
[
{ value: 1548, name: 'Search Engine' },
{ value: 775, name: 'Direct' },
{ value: 679, name: 'Marketing', selected: true }
]
this.value1應為
[
{ value: 1048, name: 'Baidu' },
{ value: 335, name: 'Direct' },
{ value: 310, name: 'Email' },
{ value: 251, name: 'Google' },
{ value: 234, name: 'Union Ads' },
{ value: 147, name: 'Bing' },
{ value: 135, name: 'Video Ads' },
{ value: 102, name: 'Others' }
]
在Delphi中的事件中組類似的字符串就可以了
具體做法是增加一個Timer, 時間設置為3000,代碼如下:
procedure TForm1.Timer1Timer(Sender: TObject);
var
sV0,sV1 : String;
sJS : String;
begin
//value0
//[
// { value: 1548, name: 'Search Engine' },
// { value: 775, name: 'Direct' },
// { value: 679, name: 'Marketing', selected: true }
//]
//value1
//[
// { value: 1048, name: 'Baidu' },
// { value: 335, name: 'Direct' },
// { value: 310, name: 'Email' },
// { value: 251, name: 'Google' },
// { value: 234, name: 'Union Ads' },
// { value: 147, name: 'Bing' },
// { value: 135, name: 'Video Ads' },
// { value: 102, name: 'Others' }
//]
//
//
Randomize;
//Get value0 string
sV0 :='this.value0=['
+'{ value: %d, name: ''Search Engine'' },'
+'{ value: %d, name: ''Direct'' },'
+'{ value: %d, name: ''Marketing'', selected: true }'
+'];';
sV0 :=Format(sV0,[Random(1500),Random(1000),Random(1000)]);
//Get value1 string
sV1 :='this.value1=['
+'{ value: %d, name: ''Baidu'' },'
+'{ value: %d, name: ''Direct'' },'
+'{ value: %d, name: ''Email'' },'
+'{ value: %d, name: ''Google'' },'
+'{ value: %d, name: ''Union Ads'' },'
+'{ value: %d, name: ''Bing'' },'
+'{ value: %d, name: ''Video Ads'' },'
+'{ value: %d, name: ''Others'' }'
+'];';
sV1 :=Format(sV1,[Random(1500),Random(1000),Random(1000),Random(1000),Random(1000),Random(1000),Random(1000),Random(1000)]);
//
dwRunJS(sV0+sV1,self);
dwEcharts(Memo1);
end;
這樣echarts圖表就可以動態更新了。
關注我, 學習更多Delphi知識!
載大文件時,斷點續傳是很有必要的,特別是網速度慢且不穩定的情況下,很難保證不出意外,一旦意外中斷,又要從頭下載,會很讓人抓狂。斷點續傳就能很好解決意外中斷情況,再次下載時不需要從頭下載,從上次中斷處繼續下載即可,這樣下載幾G或十幾G大小的一個文件都沒問題。本文介紹利用miniframe開源Web框架分別在lazarus、delphi下實現文件HTTP下載斷點續傳的功能。
本文Demo還實現了批量下載文件,同步服務器上的文件到客戶端的功能。文件斷點續傳原理:分塊下載,下載后客戶端逐一合并,同時保存已下載的位置,當意外中斷再次下載時從保存的位置開始下載即可。這其中還要保證,中斷后再次下載時服務器上相應的文件如果更新了,還得重新下載,不然下載到的文件是錯了。說明:以下代碼lazarus或delphi環境下都能使用。全部源碼及Demo請到miniframe開源web框架下載: https://www.wyeditor.com/miniframe/或https://github.com/dajingshan/miniframe。
文件下載斷點續傳服務器端很簡單,只要提供客戶端要求下載的開始位置和指定大小的塊即可。
以下是服務器獲取文件信息和下載一個文件一塊的代碼:
<%@//Script頭、過程和函數定義
program codes;
%>
<%!//聲明變量
var
i,lp: integer;
FileName, RelativePath, FromPath, ErrStr: string;
json: TminiJson;
FS: TFileStream;
function GetOneDirFileInfo(Json: TminiJson; Path: string): string;
var
Status: Integer;
SearchRec: TSearchRec;
json_sub: TminiJson;
begin
Path :=PathWithSlash(Path);
SearchRec :=TSearchRec.Create;
Status :=FindFirst(Path + '*.*', faAnyFile, SearchRec);
try
while Status=0 do
begin
if SearchRec.Attr and faDirectory=faDirectory then
begin
if (SearchRec.name <> '.') and (SearchRec.name <> '..') then
GetOneDirFileInfo(Json, Path + SearchRec.Name + '\');
end else
begin
FileName :=Path + SearchRec.Name;
try
if FileExists(FileName) then
begin
json_sub :=Pub.GetJson;
json_sub.SO; //初始化 或 json.Init;
json_sub.S['filename'] :=SearchRec.name;
json_sub.S['RelativePath'] :=GetDeliBack(FileName, FromPath);
json_sub.S['FileTime'] :=FileGetFileTimeA(FileName);
json_sub.I['size'] :=SearchRec.Size;
json.A['list'] :=json_sub;
end;
except
//print(ExceptionParam)
end;//}
end;
Status :=FindNext(SearchRec);
end;
finally
FindClose(SearchRec);
SearchRec.Free;
end;//*)
end;
%>
<%
begin
FromPath :='D:\code\delphi\sign\發行文件'; //下載源目錄
json :=Pub.GetJson; //這樣創建json對象不需要自己釋放,系統自動管理
json.SO; //初始化 或 json.Init;
// 驗證是否登錄代碼
{if not Request.IsLogin('Logined') then
begin
json.S['retcode'] :='300';
json.S['retmsg'] :='你還沒有登錄(no logined)!';
print(json.AsJson(true));
exit;
end;//}
json.S['retcode'] :='200';
json.S['retmsg'] :='成功!';
if Request.V('opr')='1' then
begin //獲取服務上指定目錄的文件信息
GetOneDirFileInfo(Json, FromPath);
end else
if Request.V('opr')='2' then
begin //下載指定文件給定大小的塊
FromPath :=PathWithSlash(FromPath);
RelativePath :=Request.V('fn');
FileName :=FromPath + RelativePath;
Fs :=Pub.GetFS(FileName, fmShareDenyWrite, ErrStr);
if trim(ErrStr) <> '' then
begin
json.S['retcode'] :='300';
json.S['retmsg'] :=ErrStr;
print(json.AsJson(true));
exit;
end;
Fs.Position :=StrToInt(Request.V('pos'));
Response.ContentStream :=TMemoryStream.Create; //注意不能用 Pub.GetMs,這是因為Pub.GetMs創建的對象在動態腳本運行完就釋放了
Response.ContentStream.CopyFrom(Fs, StrToInt(Request.V('size')));
//返回流數據
Response.ContentType :='application/octet-stream';
end;
print(json.AsJson(true));
end;
%>
客戶端收到塊后,進行合并。全部塊下載完成后,還要把新下載的文件的文件修改為與服務器上的文件相同。以下是客戶端實現的主代碼:
procedure TMainForm.UpgradeBlock_Run(var ThreadRetInfo: TThreadRetInfo);
const
BlockSize=1024*1024; //1M
var
HTML, ToPath, RelativePath, FN, Tmp, TmpFileName, FailFiles, SuccFiles, Newfn, TmpToPath: string;
Json, TmpJson: TminiJson;
lp, I, Number, HadUpSize, AllSize, AllBlockCount, MySize, MyNumber: Int64;
Flag: boolean;
SL, SLDate, SLSize, SLTmp: TStringlist;
MS: TMemoryStream;
Fs: TFileStream;
procedure HintMsg(Msg: string);
begin
FMyMsg :=Msg; // '正在獲取文件列表。。。';
ThreadRetInfo.Self.Synchronize(ThreadRetInfo.Self, MyUpdateface); //為什么不直接用匿名,因為laz不支持
end;
begin
ToPath :='D:\superhtml'; //如果是當前程序更新 ExtractFilePath(ParamStr(0))
ThreadRetInfo.Ok :=false;
HintMsg('正在獲取文件列表。。。');
if not HttpPost('/接口/同步文件到客戶端.html?opr=1',
'', ThreadRetInfo.ErrStr, ThreadRetInfo.HTML) then exit;
if Pos('{', ThreadRetInfo.HTML) <> 1 then
begin
ThreadRetInfo.ErrStr :='請先檢查腳本源碼是否配置正確!';
exit;
end;
ToPath :=Pub.PathWithSlash(ToPath);
Json :=TminiJson.Create;
SL :=TStringlist.Create;
SLDate :=TStringlist.Create;
SLSize :=TStringlist.Create;
SLTmp :=TStringlist.Create;
try
Json.LoadFromString(ThreadRetInfo.HTML);
if json.S['retcode']='200' then
begin
TmpJson :=json.A['list'];
for lp :=0 to TmpJson.length - 1 do
begin
HintMsg(lp.ToString + '/' + TmpJson.length.ToString + '正在檢查文件:' + RelativePath);
RelativePath :=TmpJson[lp].S['RelativePath'];
if trim(RelativePath)='' then Continue;
Flag :=FileExists(ToPath + RelativePath);
if Flag then
begin
if (PubFile.FileGetFileTimeA(ToPath + RelativePath)=TmpJson[lp].S['FileTime']) and
(PubFile.FileGetFileSize(ToPath + RelativePath)=TmpJson[lp].I['Size']) then
else
Flag :=false;
end;
if not Flag then //此文件需要更新
begin
SL.Add(RelativePath);
SLDate.Add(TmpJson[lp].S['FileTime']);
SLSize.Add(TmpJson[lp].S['Size']);
end;
end;
//開始下載
FailFiles :='';
SuccFiles :='';
HintMsg('需要更新的文件共有' + IntToStr(SL.Count) + '個。。。');
for lp :=0 to SL.Count - 1 do
begin
RelativePath :=SL[lp];
if RelativePath[1]='\' then RelativePath :=Copy(RelativePath, 2, MaxInt);
FN :=ToPath + RelativePath;
//先計算要分幾個包,以處理進度
Number :=0;
HadUpSize :=0;
AllSize :=StrToInt64(SLSize[lp]);
AllBlockCount :=0;
while true do
begin
AllBlockCount :=AllBlockCount + 1;
if AllSize - HadUpSize >=BlockSize then
MySize :=BlockSize
else
MySize :=AllSize - HadUpSize;
HadUpSize :=HadUpSize + MySize;
if HadUpSize >=AllSize then
break;
end;
//開始分塊下載
Number :=0;
HadUpSize :=0;
//AllSize :=Fs.Size;
//TmpToPath :=PubFile.FileGetTemporaryPath;
Newfn :='@_' + PubPWD.GetMd5(SLDate[lp] + SLSize[lp]) + ExtractFileName(FN); //Pub.GetClientUniqueCode;
if FileExists(ToPath + Newfn) and (FileExists(FN)) then
begin
SLTmp.LoadFromFile(ToPath + Newfn);
MyNumber :=StrToInt64(trim(SLTmp.Text));
Fs :=TFileStream.Create(FN, fmOpenWrite);
end else
begin
MyNumber :=0;
Fs :=TFileStream.Create(FN, fmCreate);
end;
try
while true do
begin
HintMsg('正在下載文件[' + Pub.GetDeliBack(RelativePath, '@@') + ']第[' + IntToStr(Number + 1) + '/' + IntToStr(AllBlockCount) + ']個包。。。');
if AllSize - HadUpSize >=BlockSize then
MySize :=BlockSize
else
MySize :=AllSize - HadUpSize;
Number :=Number + 1;
if (MyNumber=0) or (Number >=MyNumber) or (HadUpSize + MySize >=AllSize) then
begin
for I :=1 to 2 do //意外出錯重試一次
begin
if not HttpPost('/接口/同步文件到客戶端.html?opr=2fn=' + UrlEncode(RelativePath) +
'pos=' + UrlEncode(IntToStr(HadUpSize)) + 'size=' + UrlEncode(IntToStr(MySize)),
'', ThreadRetInfo.ErrStr, ThreadRetInfo.HTML, MS) then
begin
if I=2 then
begin
ThreadRetInfo.ErrStr :=Json.S['retmsg'];
exit;
end else
Continue;
end;
if Pos('{', ThreadRetInfo.HTML) < 1 then
begin
if I=2 then
begin
ThreadRetInfo.ErrStr :=Json.S['retmsg'];
exit;
end else
Continue;
end;
Json.LoadFromString(ThreadRetInfo.HTML);
if json.S['retcode'] <> '200' then
begin
if I=2 then
begin
ThreadRetInfo.ErrStr :=Json.S['retmsg'];
exit;
end else
Continue;
end;
break;
end;
if MS=nil then
begin
ThreadRetInfo.ErrStr :='沒能下載到文件[' + RelativePath + ']!' + json.S['retmsg'];
exit;
end else
begin
Fs.Position :=HadUpSize;
MS.Position :=0;
Fs.CopyFrom(MS, MS.Size);
MS.Free;
MS :=nil;
SLTmp.Text :=Number.ToString;
try
SLTmp.SaveToFile(ToPath + Newfn);
except
end;
end;
end;
HadUpSize :=HadUpSize + MySize;
if HadUpSize >=AllSize then
begin //全部下載完成
Fs.Free;
Fs :=nil;
Sleep(10);
PubFile.FileChangeFileDate(Fn, SLDate[lp]);
DeleteFile(ToPath + Newfn);
SuccFiles :=SuccFiles + #13#10 + RelativePath;
break;
end;
end;
finally
if Fs <> nil then
Fs.Free;
end;
end;
ThreadRetInfo.HTML :='';
if trim(SuccFiles) <> '' then
ThreadRetInfo.HTML :='本次更新了以下文件:'#13#10 + SuccFiles;
//if trim(FailFiles) <> '' then
//ThreadRetInfo.HTML :=trim(ThreadRetInfo.HTML + #13#10'以下文件更新失敗:'#13#10 + FailFiles);
end;
finally
SLTmp.Free;
SLSize.Free;
SL.Free;
Json.Free;
SLDate.Free;
end;
ThreadRetInfo.Ok :=true;
end;
以下是Demo運行界面:
方描述
Unit that implements generic container classes to group data items in arrays, dictionaries, lists, stacks, queues, and more.
譯文:實現泛型容器類的單元,以在數組中對數據項進行分組,詞典,列表,堆棧,隊列等。
起初在接觸到Delphi的時候我以為沒有這一部分內容,所有的都要自己實現,此處請原諒我的無知。。。。
關于從那個版本開始支持泛型的,我在官方文檔上沒有找到對應的說明,也可能是我英語太菜,在官網迷路了。
根據百度得來的結果2007的版本沒有(真百度的)支持2009的版本(萬一老師的博客上引用的也是這個版本)開始出現,也就是最早支持泛型容器的版本應該是Delphi2009
聊泛型容器避不開的就是泛型的概念,之前看嗶哩嗶哩的網友留言從泛型一節就是開始懵逼了。其實大可不必,泛型可以理解為一個變量,它的值是一個具體的類型
嗯,其實也可以把它當作孫大圣,它可以變成任何東西
在這個單元內一共定義了13個容器類,我們在其中甚至可以看到線程隊列。那么問題來了,我們需要精通或者學習所有的類嗎?答案是否定的
容器類就我個人的理解其實一共有兩大表現形式,一種是列表形式的像數組,而另一種形式則以 Key,Value 成對的形式。也就是說我們從中選擇兩個比較有代表性的類進行學習即可。下面是我選擇的兩個類
注意:在單元內我們還可以看到相似的類例如 System.Generics.Collections.TObjectList<T> 和 System.Generics.Collections.TList<T>區別在于帶有Object的類會在刪除元素時釋放對象,而沒有帶Object的不會釋放
針對容器類學習的總原則是圍繞增、刪、改、查這幾個核心的API功能即可,其他的就只能現用現查了,不知道別人寫代碼是什么習慣,我寫代碼的時候幫助文檔基本上都是開著的。。。
下面是針對兩個容器類的代碼實現
實體類(TStudent)代碼
type
TStudent=class
private
FName: string;
public
property Name: string read FName write FName;
// 構造方法
constructor Create; overload;
// 有參構造方法
constructor Create(FName: string); overload;
end;
constructor TStudent.Create;
begin
end;
constructor TStudent.Create(FName: string);
begin
Self.FName :=FName;
end;
操作類(即增、刪、改、查),我沒有使用內聯,幾次想用都刪了
uses
System.Generics.Collections, System.SysUtils;
var
// 文檔上的定義是TList<T>,而我們的定義尖括號中的是TStudent,這就是泛型的用法
StudentList: TList<TStudent>;
// 循環中使用獲取TList成員
Stu: TStudent;
begin
// 初始化學生列表
StudentList :=TList<TStudent>.Create;
StudentList.add(TStudent.Create('小強'));
StudentList.add(TStudent.Create('蕭薔'));
StudentList.add(TStudent.Create('小黑'));
StudentList.add(TStudent.Create('小白'));
StudentList.add(TStudent.Create('小黃'));
// 開始之前輸出一次
for Stu in StudentList do begin
Writeln('學生信息是:' + Stu.Name);
end;
// 泛型容器自帶的刪除函數,偷個懶
StudentList.Delete(0);
// 修改,查詢到指定的學生,修改其值即可
StudentList.Items[0].Name :='小白';
// 查詢,其實和數組的操作沒有太大區別
for Stu in StudentList do begin
Writeln('學生信息是:' + Stu.Name);
end;
end.
此類容器的特點是 key 唯一,并且幾乎所有的操作都是根據 key 來的
改造下實體類(TStudent)增加一個屬性,代碼如下
type
TStudent=class
private
FName: string;
FId: string;
public
property Name: string read FName write FName;
property Id: string read FId write FId;
// 構造方法
constructor Create; overload;
// 有參構造方法
constructor Create(FName: string; FId: string); overload;
end;
constructor TStudent.Create;
begin
end;
constructor TStudent.Create(FName: string; FId: string);
begin
Self.FName :=FName;
end;
操作類(即增、刪、改、查)
{注意單元的引用}
uses
System.Generics.Collections, System.SysUtils;
var
// 文檔上的定義是TList<T>,而我們的定義尖括號中的是TStudent,這就是泛型的用法
StudentMap: TDictionary<string, TStudent>;
// 循環中使用獲取TList成員
Key: string;
Student,Stu: TStudent;
begin
// 初始化學生列表
StudentMap :=TDictionary<string, TStudent>.Create;
// 此處的添加其實并不是太好,根據文檔描述,當我們添加的元素的key已經存在會拋出異常
// 建議使用AddOrSetValue
StudentMap.add('1001', TStudent.Create('1002', '小強'));
StudentMap.add('1002', TStudent.Create('1001', '蕭薔'));
StudentMap.add('1003', TStudent.Create('1003', '小黑'));
StudentMap.add('1004', TStudent.Create('1004', '小白'));
StudentMap.add('1005', TStudent.Create('1004', '小黃'));
// 注意此處獲取是key,也就是1001 1002這些東西
for Key in StudentMap.Keys do begin
// 獲取到key之后,再根據key獲取對應value也就是學生對象
// 這里的獲取方式有點兒任性居然要的是一個指針,而不是直接返回
StudentMap.TryGetValue(Key, Student);
// 最好判斷一下,否則容易出現空指針
if (Student <> nil) then
Writeln('學生信息是:' + Student.Name)
end;
// 刪除元素,無論什么操作都是通過key去操作,因為key是不可以重復的
StudentMap.Remove('1005');
// 修改,沒有就添加,有就更新
StudentMap.AddOrSetValue('1004', TStudent.Create('1004', '小黃'));
// 直接獲取value
for Stu in StudentMap.values do begin
// 最好判斷一下,否則容易出現空指針
if (Stu <> nil) then
Writeln('學生信息是:' + Student.Name)
end;
end.
萬一的博客:https://www.cnblogs.com/del/category/113556.html
官方文檔:http://docwiki.embarcadero.com/Libraries/Sydney/en/System.Generics.Collections
*請認真填寫需求信息,我們會在24小時內與您取得聯系。