在《一步一步学习使用LiveBindings(12)》课中,非常详细的介绍了如何在设计时手工的编辑DynamicAppearance类型的项,大大方便了构建自定义的列表项。但是很多情况下,仍然要面对编程创建列表项的情形,特别是当要实现自定义的列表项时,将不得不面对编程创建列表项的挑战。

注意:更想的自定义列表项的的方法是为 TListView 组件编写自定义样式;将组件放入一个包中,安装到 IDE 中,然后从对象检查器窗口使用它。这样就可以在多个项目中重复的使用。

这一课将主要介绍如下内容:

  • 使用TRestClient从远端服务器获取服务。
  • 解析JSON,根据JOSN的内容,创建TListViewItem。
  • 根据JSON的内容,生成不同的列表项呈现。

这一课将是创建自定义列表项的基础,掌握了这一课的内容,基本对TListView,也就没有大的问题了。

1. 关于天气预报网站OpenWeatherMap:

OpenWeatherMap 是一个提供全球天气数据的知名服务平台,主要面向开发者、企业和个人用户。以下是它的核心特点:

  1. 实时与预报天气数据

    提供实时天气、分钟级降水预报、短期(5天)及长期(16天)预报,甚至历史天气数据。

    覆盖全球任意坐标(包括偏远地区),数据来源自气象站、卫星和雷达。
  2. 多样化的数据接口

    通过 REST API 和 地图图层API 提供数据,支持JSON/XML格式。

    免费层有限制(如60次/分钟调用),付费层可获取更高精度(如小时级降水)和更多功能。
  3. 丰富的天气指标

    温度、湿度、风速、气压、降水、紫外线指数等。

    特殊数据如空气质量(PM2.5)、花粉浓度、自然灾害警报等需订阅高级服务。
  4. 应用场景广泛

    开发者常用其API集成到移动应用、网站或IoT设备(如智能家居)。

    企业用户可能用于物流、农业或旅游行业的天气分析。
  5. 数据可视化工具

    提供交互式地图,可叠加云层、降水等实时天气图层。
  6. 费用与权限

    免费方案 适合小规模测试(如1,000次/天API调用)。

    商业用途需选择付费计划(如企业级数据或高频率访问)。
  7. 数据覆盖与更新

    覆盖40多万个城市,部分数据更新频率可达每分钟。

    注意事项

    免费数据可能不如专业气象机构精确,付费服务(如History API或Agro API)更适合高要求场景。

    使用前需注册获取API Key,并遵守其条款(如注明数据来源)。

    如果需要替代方案,可考虑 WeatherAPI、AccuWeather 或 ClimaCell(现为Tomorrow.io)。具体选择取决于精度需求和预算。

在该网站上查询北京的天气如下:

网站列出了8天的天气,本课的小程序的效果如下:

本课示例代码来自《Delphi Cookbook》

你需要在该网站注册一个账号,然后申请一个免费的API Key,不过使用《Delphi Cookbook》作者的api key一样可以使用,但是这个示例调用的是旧版本的API,新版本的API已经进化到3.0,可以使用本课学到的知识打造一个真正有价值的世界天气预报应用程序。

2. 构建多设备应用程序。

1. 单击主菜单中的 File > New > Multi-Device Application - Delphi > Header/Footer ,创建一个新的多设备应用程序。

建议立即单击工具栏上的Save All按钮,将单元文件保存为MainFormU.pas,将项目保存为WeatherForecastEx.dproj。

项目结构应该像这样:

2.1 设置用户界面

用户界面大概按如下的步骤:

  1. Header/Footer会自动添加Header和Footer两个TToolBar,在Header上面添加了一个名为HeaderLabel的TLabel控件,指定其Text属性为“天气预报小程序”。

  2. 在Header上添加一个TAniIndictor控件,设置其Align属性为Right,这个控件将显示一个动态的指示器,提供用户运行中的反馈。

  3. 在Header下面添加一个Align为Top的TPanel控件,在上面放2个TEdit,分别命名为EditCity和EditCountry,在最右侧放一个TButton控件,指定name属性为btnGetForecasts,将EditCity的Align设置为Client,EditCountry和btnGetForecasts的Align指定为Right。

  4. 在Footer上放一个TLable控件,指定其name属性为lblInfo,用来显示一些系统消息。

  5. 接下来放一个Align为Client的TListView控件,指定其ItemAppearance.ItemAppearance属性为Custom,将用来编程控制Item。

  6. 最后,手一个TRestClient和TRestRequest控件到主窗体,指定TRestRequest.Client属性为TRestClient。

现在用户界面相关的控年已经准备好了。

2.2 测试天气预报服务

接下来,打开Delphi主菜单的 Tools >REST Debugger,d在弹出的窗口的第一页,Method指定为Get,在URL文本框上指定URL为:

http://api.openweathermap.org/data/2.5/forecast

Content-Type使用默认的Application/json

接下来切换到Parameterss页,添加如下的键值对:

单击右上角的“Send Request”按钮,如果URL构建成功,则Response会返回200-OK状态,Response的Header区可以看到请求的头信息,Body区则可以看到具体的JSON数据。

通过分析JSON,可以得知应用程序将要提供哪些数据给用户,从而构建真正的用户界面。



整个JSON包含了一个list数组,每个数组里面的元素内容如下:

  • dt:Unix时间戳值。
  • main.temp_min:指定时间戳范围内的最低温度。
  • main.temp_max: 指定时间戳范围内的最高温度。
  • weather.main: 天气情况的英语表达。
  • weather.description:天气情况的本地语言表达。

3. 获取天气JSON数据,并进行JSON解析,生成用户界面

1. 在主窗体加载时,将会初始化RestClient和RESTRequest两个控件,FormCreate事件如下:

// 主窗体创建时触发的事件处理过程
procedure TMainForm.FormCreate(Sender: TObject);
var
LocaleService: IFMXLocaleService; // 声明一个FMX本地化服务接口变量
begin
// 检查当前平台是否支持IFMXLocaleService接口
if TPlatformServices.Current.SupportsPlatformService(IFMXLocaleService) then
begin
// 获取FMX本地化服务接口实例
LocaleService := TPlatformServices.Current.GetPlatformService
(IFMXLocaleService) as IFMXLocaleService; // 获取当前系统的语言ID(例如:"en-US"、"zh-CN"等)
Lang := LocaleService.GetCurrentLangID;
end
else
// 如果不支持本地化服务,则默认使用美国英语('US')
Lang := 'US'; // 设置国家/地区文本框默认值为中国('CN')
EditCountry.Text := 'CN'; // 设置REST客户端的基础URL为OpenWeatherMap API地址
RESTClient1.BaseURL := 'http://api.openweathermap.org/data/2.5'; // 设置REST请求的资源路径,包含参数占位符:
// {country} - 国家/地区代码
// {lang} - 语言代码
// {APPID} - API密钥
RESTRequest1.Resource :=
'forecast?q={country}&mode=json&lang={lang}&units=metric&APPID={APPID}'; // 设置REST请求中的APPID参数值
RESTRequest1.Params.ParameterByName('APPID').Value := APPID; // 初始化时将活动指示器设置为不可见(不显示加载动画)
AniIndicator1.Visible := False;
end;

当访问一个RESTful服务器时,一般在TRESTClient控件中指定基本的URL,而查询字符串参数则在TRESTReuqest控件中指定。花括号内是查询字符串参数的占位和会,可以通过RESTRequest1.Params.ParameterByName这样的语法来进行设置。

2.为天气预报的刷新按钮添加事件处理代码,这里将使用标准的RESTRequest1.ExecuteAsync异步获取服务器端数据,获取完成后会执行一个匿名方法。

在这个事件处理代码中,调用了3个自定义的过程:

// 在列表视图中添加日期分组标题项
// 参数:
// AItems: 列表视图的项集合
// ADateStr: 要显示的日期字符串(格式为yyyy/mm/dd)
procedure TMainForm.AddHeader(AItems: TListViewItems; const ADateStr: string);
// 在列表视图中添加单个天气预报项
// 参数:
// AItems: 列表视图的项集合
// ADateTime: 预报日期时间
// ADescription: 天气描述(如"晴"、"多云"等)
// ATempMin: 最低温度
// ATempMax: 最高温度
procedure TMainForm.AddForecastItem(AItems: TListViewItems;
ADateTime: TDateTime; const ADescription: string;
ATempMin, ATempMax: Double);
// 在列表视图中添加日期分组的汇总信息(当天温度极值)
// 参数:
// AItems: 列表视图的项集合
// AMinTemp: 当天最低温度
// AMaxTemp: 当天最高温度
procedure TMainForm.AddFooter(AItems: TListViewItems;
AMinTemp, AMaxTemp: Double);

有了这些子程序的辅助,事件处理的代码就会相对简洁不少,完整的单击事件处理代码如下所示:

// 获取天气预报按钮点击事件处理过程
procedure TMainForm.btnGetForecastsClick(Sender: TObject);
begin
// 清空列表视图中的所有项
ListView1.Items.Clear; // 设置REST请求中的country参数值,将城市和国家用逗号连接
RESTRequest1.Params.ParameterByName('country').Value :=
String.Join(',', [EditCity.Text, EditCountry.Text]); // 设置REST请求中的lang参数值,使用之前获取的语言ID
RESTRequest1.Params.ParameterByName('lang').Value := Lang; // 显示并启用活动指示器(加载动画)
AniIndicator1.Visible := True;
AniIndicator1.Enabled := True; // 禁用按钮防止重复点击
btnGetForecasts.Enabled := False; // 异步执行REST请求
RESTRequest1.ExecuteAsync(
procedure
var
LForecastDateTime: TDateTime; // 预报日期时间
LJValue: TJSONValue; // JSON值对象
LJObj, LMainForecast, // JSON对象
LForecastItem, LJObjCity: TJSONObject;
LJArrWeather, LJArrForecasts: TJSONArray; // JSON数组
LTempMin, LTempMax: Double; // 最低和最高温度
LDay, LLastDay: string; // 当前日期和上一个日期
LWeatherDescription: string; // 天气描述
LAppRespCode: string; // 应用响应代码
LMinInTheDay, LMaxInTheDay: Double; // 当天最低和最高温度 begin
try
// 将响应内容转换为JSON对象
LJObj := RESTRequest1.Response.JSONValue as TJSONObject; // 检查错误响应
LAppRespCode := LJObj.GetValue('cod').Value;
if LAppRespCode.Equals('404') then
begin
// 城市未找到的错误处理
lblInfo.Text := '没有找到指定城市';
Exit;
end;
if not LAppRespCode.Equals('200') then
begin
// 其他错误处理
lblInfo.Text := '错误 ' + LAppRespCode;
Exit;
end; // 解析天气预报数据
LMinInTheDay := 1000; // 初始化当天最低温度为极大值
LMaxInTheDay := -LMinInTheDay; // 初始化当天最高温度为极小值 // 获取预报列表数组
LJArrForecasts := LJObj.GetValue('list') as TJSONArray; // 遍历所有预报项
for LJValue in LJArrForecasts do
begin
// 获取单个预报项对象
LForecastItem := LJValue as TJSONObject; // 将Unix时间戳转换为Delphi日期时间
LForecastDateTime := UnixToDateTime((LForecastItem.GetValue('dt')
as TJSONNumber).AsInt64); // 获取主预报信息对象
LMainForecast := LForecastItem.GetValue('main') as TJSONObject; // 获取最低和最高温度
LTempMin := (LMainForecast.GetValue('temp_min')
as TJSONNumber).AsDouble;
LTempMax := (LMainForecast.GetValue('temp_max')
as TJSONNumber).AsDouble; // 获取天气描述数组
LJArrWeather := LForecastItem.GetValue('weather') as TJSONArray; // 获取第一个天气描述
LWeatherDescription := TJSONObject(LJArrWeather.Items[0])
.GetValue('description').Value; // 格式化日期为yyyy/mm/dd
LDay := FormatDateTime('yyyy/mm/dd', DateOf(LForecastDateTime)); // 检查是否是新的日期
if LDay <> LLastDay then
begin
// 如果不是第一个日期,添加前一天的最高和最低温度的统计信息
if not LLastDay.IsEmpty then
begin
AddFooter(ListView1.Items, LMinInTheDay, LMaxInTheDay);
end; // 添加新日期的标题
AddHeader(ListView1.Items, LDay); // 重置当天温度极值
LMinInTheDay := 1000;
LMaxInTheDay := -LMinInTheDay;
end; // 保存当前日期供下次比较
LLastDay := LDay; // 更新当天最低和最高温度
LMinInTheDay := Min(LMinInTheDay, LTempMin);
LMaxInTheDay := Max(LMaxInTheDay, LTempMax); // 添加预报项到列表视图
AddForecastItem(ListView1.Items, LForecastDateTime,
LWeatherDescription, LTempMin, LTempMax);
end; // 结束预报项遍历 // 添加最后一天的汇总信息
if not LLastDay.IsEmpty then
AddFooter(ListView1.Items, LMinInTheDay, LMaxInTheDay); // 获取并显示城市和国家信息
LJObjCity := LJObj.GetValue('city') as TJSONObject;
lblInfo.Text := LJObjCity.GetValue('name').Value + ', ' +
LJObjCity.GetValue('country').Value; finally
// 无论成功或失败,都执行以下清理操作
AniIndicator1.Visible := False; // 隐藏活动指示器
AniIndicator1.Enabled := False; // 禁用活动指示器
btnGetForecasts.Enabled := True; // 重新启用按钮
end;
end);
end;

代码的详细解说如下:

  1. RESTRequest1.Params 是 TRESTRequest 组件中用于管理所有请求参数的集合属性,它允许你在发送 HTTP 请求前配置各种类型的参数,参数country配置为“城市,国家”,这是API的定义需求,直接写城市有时也能找到。

  2. RESTRequest1.Response包含很多属性用来获取来自服务器的响应,如果服务器端返回JOSN数据,则使用JSONValue属性可以获取到JSON实例。

  3. 服务器返回的JSON中包含了状态码,可以根据状态码判断查询成功还是失败。

  4. 接下来对JSON中的list数组进行了解析,LMinInTheDay和LMinInTheDay 变量是统计1天中最高与最低温度的变量,在每天的日期发生变化后清零,在每天会进行Min与Max的统计运算。

  5. 在每天的日期发生变化后,会调用AddHeader添加页眉,在添加页眉之前,总是会先对上一个日期结束位置调用AddFooter添加页脚。

  6. 在最后一天也会添加一个页脚,避免遗漏。

  7. 在每一天的日期记录中,调用AddForecastItem添加项。

  8. 在代码最后为主窗体的footer区的TLabel控件也设置了显示文本。

接下来看看3个过程的具体实现,它们并未涉及到具体的显示位置等逻辑,这一切要在TListView的OnUpdateObject事件中完成。

// 添加日期分组标题项到列表视图
procedure TMainForm.AddHeader(AItems: TAppearanceListViewItems;
const ADay: String);
var
LItem: TListViewItem; // 声明列表项变量
begin
// 在列表项集合中添加新项
LItem := AItems.Add; // 设置该项为分组标题类型
LItem.Purpose := TListItemPurpose.Header; // 在名为'HeaderLabel'的绘制对象中设置日期文本
LItem.Objects.FindDrawable('HeaderLabel').Data := ADay;
end; // 添加单个天气预报项到列表视图
procedure TMainForm.AddForecastItem(AItems: TAppearanceListViewItems;
const AForecastDateTime: TDateTime; // 预报日期时间
const AWeatherDescription: String; // 天气描述文本
const ATempMin, ATempMax: Double); // 最低/最高温度
var
LItem: TListViewItem; // 声明列表项变量
begin
// 在列表项集合中添加新项
LItem := AItems.Add; // 设置'WeatherDescription'绘制对象的数据:
// 格式为"小时时+天气描述"(如"14时 多云")
LItem.Objects.FindDrawable('WeatherDescription').Data :=
FormatDateTime('HH', AForecastDateTime) + '时 ' + AWeatherDescription; // 设置'MinTemp'绘制对象的数据:
// 格式为"最低温度°"(如"12.50°")
LItem.Objects.FindDrawable('MinTemp').Data :=
FormatFloat('#0.00', ATempMin) + '°'; // 设置'MaxTemp'绘制对象的数据:
// 格式为"最高温度°"(如"24.80°")
LItem.Objects.FindDrawable('MaxTemp').Data :=
FormatFloat('#0.00', ATempMax) + '°';
end; // 添加日期分组汇总信息到列表视图
procedure TMainForm.AddFooter(AItems: TAppearanceListViewItems;
const LMinInTheDay, LMaxInTheDay: Double); // 当日最低/最高温度
var
LItem: TListViewItem; // 声明列表项变量
begin
// 在列表项集合中添加新项
LItem := AItems.Add; // 设置该项为分组脚注类型
LItem.Purpose := TListItemPurpose.Footer; // 设置脚注文本:
// 格式为"最低 XX.XX° 最高 XX.XX°"(如"最低 12.50° 最高 24.80°")
LItem.Text := Format('最低 %2.2f° 最高 %2.2f°', [LMinInTheDay, LMaxInTheDay]);
end;

这里的过程仅负则添加和设置文本或Data数据,每一次AItems.Add过程调用之后,需要指定Purpose为具体的项类型,以便于OnUpdateObjects进行处理。

每次在Item.Txt或者是FindDrawable语句触发时,会自动调用OnUpdateObject来实现显示对象的真正呈现工作,事件处理代码如下:

// 列表视图项更新事件处理过程
procedure TMainForm.ListView1UpdateObjects(const Sender: TObject;
const AItem: TListViewItem);
var
AQuarter: Double; // 用于存储四分之一宽度的变量
lb: TListItemText; // 列表项文本对象引用
lListView: TListView; // 列表视图引用
begin
// 将Sender转换为TListView类型
lListView := Sender as TListView; // 根据列表项的目的类型进行不同处理
case AItem.Purpose of
// 处理普通列表项
TListItemPurpose.None:
begin
// 计算每列宽度(总宽度减去左右边距后分成4份)
AQuarter := (lListView.Width - lListView.ItemSpaces.Left -
lListView.ItemSpaces.Right) / 4; // 设置普通项的高度为24像素
AItem.Height := 24; // 处理天气描述文本控件
lb := TListItemText(AItem.Objects.FindDrawable('WeatherDescription'));
if not Assigned(lb) then // 如果对象不存在则创建
begin
lb := TListItemText.Create(AItem); // 创建新文本对象
lb.PlaceOffset.X := 0; // 设置X偏移为0
lb.TextAlign := TTextAlign.Leading; // 文本左对齐
lb.Name := 'WeatherDescription'; // 设置对象名称
end;
lb.PlaceOffset.X := 0; // 确保X偏移为0 // 处理最低温度文本控件
lb := TListItemText(AItem.Objects.FindDrawable('MinTemp'));
if not Assigned(lb) then // 如果对象不存在则创建
begin
lb := TListItemText.Create(AItem); // 创建新文本对象
lb.TextAlign := TTextAlign.Trailing; // 文本右对齐
lb.TextColor := TAlphaColorRec.Blue; // 设置文本颜色为蓝色
lb.Name := 'MinTemp'; // 设置对象名称
end;
lb.PlaceOffset.X := AQuarter * 2; // 设置X位置为第二列
lb.Width := AQuarter; // 设置宽度为四分之一宽度 // 处理最高温度文本控件
lb := TListItemText(AItem.Objects.FindDrawable('MaxTemp'));
if not Assigned(lb) then // 如果对象不存在则创建
begin
lb := TListItemText.Create(AItem); // 创建新文本对象
lb.TextAlign := TTextAlign.Trailing; // 文本右对齐
lb.TextColor := TAlphaColorRec.Red; // 设置文本颜色为红色
lb.Name := 'MaxTemp'; // 设置对象名称
end;
lb.PlaceOffset.X := AQuarter * 3; // 设置X位置为第三列
lb.Width := AQuarter; // 设置宽度为四分之一宽度
end; // 处理列表头项
TListItemPurpose.Header:
begin
// 设置头部高度为48像素
AItem.Height := 48; // 处理头部标签文本控件
lb := TListItemText(AItem.Objects.FindDrawable('HeaderLabel'));
if not Assigned(lb) then // 如果对象不存在则创建
begin
lb := TListItemText.Create(AItem); // 创建新文本对象
lb.TextAlign := TTextAlign.Center; // 文本居中对齐
lb.Align := TListItemAlign.Center; // 控件居中对齐
lb.TextColor := TAlphaColorRec.Red; // 设置文本颜色为红色
lb.Name := 'HeaderLabel'; // 设置对象名称
end;
lb.PlaceOffset.Y := AItem.Height / 4; // 设置Y偏移为高度的四分之一
end; // 处理列表脚注项
TListItemPurpose.Footer:
begin
// 设置脚注文本右对齐
AItem.Objects.TextObject.TextAlign := TTextAlign.Trailing;
end;
end;
end;

可以看到,在OnUpdateObject事件中,动态的创建了可绘制项元素,动态计算并设置其宽度和高度,所有与呈现相关的工作都在这个事件中得以完成。

总结

这篇文章主要分享了如下几个知识点:

  1. 使用TRESTClient和TRESTResponse访问远程服务器,获取JSON数据源。
  2. 使用System.JSON命名空间中的类解析JSON。
  3. 根据JSON数据源动态创建列表项。
  4. 处理OnUpdateObject事件创建列表项的呈现对象。

相信通过对这篇文章的学习,可以对TListView有较为深入的理解。

一步一步学习使用LiveBindings(14)TListView进阶使用(2),打造天气预报程序的更多相关文章

  1. 12.Linux软件安装 (一步一步学习大数据系列之 Linux)

    1.如何上传安装包到服务器 有三种方式: 1.1使用图形化工具,如: filezilla 如何使用FileZilla上传和下载文件 1.2使用 sftp 工具: 在 windows下使用CRT 软件 ...

  2. (转) 一步一步学习ASP.NET 5 (四)- ASP.NET MVC 6四大特性

    转发:微软MVP 卢建晖 的文章,希望对大家有帮助.原文:http://blog.csdn.net/kinfey/article/details/44459625 编者语 : 昨晚写好的文章居然csd ...

  3. (转) 一步一步学习ASP.NET 5 (二)- 通过命令行和sublime创建项目

    转发:微软MVP 卢建晖 的文章,希望对大家有帮助. 注:昨天转发之后很多朋友指出了vNext的命名问题,原文作者已经做出了修改,后面的标题都适用 asp.net 5这个名称. 编者语 : 昨天发了第 ...

  4. 一步一步学习SignalR进行实时通信_1_简单介绍

    一步一步学习SignalR进行实时通信\_1_简单介绍 SignalR 一步一步学习SignalR进行实时通信_1_简单介绍 前言 SignalR介绍 支持的平台 相关说明 OWIN 结束语 参考文献 ...

  5. 一步一步学习SignalR进行实时通信_8_案例2

    原文:一步一步学习SignalR进行实时通信_8_案例2 一步一步学习SignalR进行实时通信\_8_案例2 SignalR 一步一步学习SignalR进行实时通信_8_案例2 前言 配置Hub 建 ...

  6. 一步一步学习SignalR进行实时通信_9_托管在非Web应用程序

    原文:一步一步学习SignalR进行实时通信_9_托管在非Web应用程序 一步一步学习SignalR进行实时通信\_9_托管在非Web应用程序 一步一步学习SignalR进行实时通信_9_托管在非We ...

  7. 一步一步学习SignalR进行实时通信_7_非代理

    原文:一步一步学习SignalR进行实时通信_7_非代理 一步一步学习SignalR进行实时通信\_7_非代理 SignalR 一步一步学习SignalR进行实时通信_7_非代理 前言 代理与非代理 ...

  8. 一步一步学习SignalR进行实时通信_5_Hub

    原文:一步一步学习SignalR进行实时通信_5_Hub 一步一步学习SignalR进行实时通信\_5_Hub SignalR 一步一步学习SignalR进行实时通信_5_Hub 前言 Hub命名规则 ...

  9. 一步一步学习SignalR进行实时通信_6_案例

    原文:一步一步学习SignalR进行实时通信_6_案例 一步一步学习SignalR进行实时通信\_6_案例1 一步一步学习SignalR进行实时通信_6_案例1 前言 类的定义 各块功能 后台 上线 ...

  10. 一步一步学习SignalR进行实时通信_4_Hub

    原文:一步一步学习SignalR进行实时通信_4_Hub 一步一步学习SignalR进行实时通信\_4_Hub SignalR 一步一步学习SignalR进行实时通信_4_Hub 前言 创建Hub 配 ...

随机推荐

  1. 用脚手架创建odoo15项目

    Odoo 提供了一种机制来帮助建立一个新模块,odoo-bin有一个子命令脚手架来创建一个空模块 命令: $ odoo-bin scaffold <module name> <whe ...

  2. frp实现内网穿透访问内网多台Linux服务器

    本文主要记录笔者在使用frp实现内网穿透访问内网多台Linux服务器的全过程,包括公网服务器的配置.frp服务端.客户端的下载与配置,以及配置systmctl来实现系统级启停frp,并记录我遇到的一些 ...

  3. 浅谈commons-collections4链

    浅谈commons-collections4链 commons-collections4的背景: 由于commons-collections (3.x) 在架构设计和 API 上暴露出一些问题(例如接 ...

  4. (原创)[C#]一步步解决DotNetZip因超长路径(MAX_PATH)报错的问题。

    一.前言 超长路径(MAX_PATH)的问题,在很多地方都可能遇到,常见的解决办法无非三种:添加前缀\\?\.app.config添加配置.修改注册表等. 而对于其它第三方的DLL,我们如何去从外部解 ...

  5. 记一次 .NET 某发证机系统 崩溃分析

    一:背景 1. 讲故事 前些天有位朋友在微信上找到我,说他的系统有偶发崩溃,自己也没找到原因,让我帮忙看下怎么回事,我分析dump一直都是免费的,毕竟对这些东西挺感兴趣,有问题可以直接call我,好了 ...

  6. 八、make编译输出重定向

    4.编译输出重定向 ​ 将 make 命令的标准输出(stdout)和标准错误输出(stderr)重定向到文件,以便于查看编译日志,快速分析定位问题. 1.重定向到同一个文件 语法: make > ...

  7. 【译】Agent 模式现已全面推出并支持 MCP

    Copilot Agent 模式是 AI 辅助开发的又一次革新,如今已在 Visual Studio 六月更新版中全面推出. Agent 模式将 GitHub Copilot 转变为一个能够自主完成多 ...

  8. SpringBoot--简单入门

    简介 本质上说Spring是一个组件容器,它负责创建并管理容器中的组件(也被称为Bean),并管理组件之间的依赖关系. 为什么要用SpringBoot? Spring缺点是配置过多,SpringBoo ...

  9. 深度学习模型在C++平台的部署

    一.概述   深度学习模型能够在各种生产场景中发挥重要的作用,而深度学习模型往往在Python环境下完成训练,因而训练好的模型如何在生产环境下实现稳定可靠的部署,便是一个重要内容.C++开发平台广泛存 ...

  10. 小米盒子4刷三方ROM

    前提 小米盒子 以及 小米电视的系统都太垃圾了,到处都是广告.也不能怪小米,现在大环境下国内的手机.app.网页等广告无处不在! 所以出一个教程,刷三方纯净的ROM来避免这个问题. 前提准备 软件 下 ...