-
1. 问题说明内置指示器indicator无法满足设计需求,只能设置为圆角样式,无法设置成UI设计中的其他例如方块样式、进度条样式等个性化设计需求,且位置、交互效果等定制化程度有限。2. 原因分析内置指示器样式固定,仅支持基础圆点样式,无法自定义形状、颜色过渡和交互反馈位置调整受限,默认边距无法完全消除,难以实现贴边显示效果交互能力有限,不支持点击切换页面等高级交互功能动画效果单一,无法实现进度条式等动态展示效果3. 解决思路通过关闭默认指示器 + Stack布局叠加自定义视图的方式实现完全个性化的指示器效果,具体包括:使用Stack容器实现指示器与Swiper组件的视觉叠加通过onChange事件同步当前轮播索引状态利用ForEach动态生成指示器项,适配不同数据量结合animation属性实现平滑过渡动画封装独立组件提高复用性和性能4. 解决方案4.1 基础自定义方案(替代内置指示器)通过Stack布局叠加Row实现基础自定义指示器,支持选中状态变化动画:Stack({ alignContent: Alignment.Bottom }) { // 主轮播内容 Swiper(this.swiperController) { ForEach(this.imageList, (item) => { Image(item) .width('100%') .height(240) }) } .indicator(false) // 关闭默认指示器 .onChange((index) => { this.currentIndex = index // 同步当前索引 }) // 自定义指示器 Row({ space: 6 }) { ForEach(this.imageList, (_, index) => { // 动态改变选中项样式 Column() .width(this.currentIndex === index ? 24 : 8) .height(8) .borderRadius(4) .backgroundColor(this.currentIndex === index ? Color('#007DFF') : Color('#CCCCCC')) .animation({ duration: 200, curve: Curve.EaseOut }) // 平滑过渡动画 }) } .margin({ bottom: 16 }) // 底部间距}关键技术点:使用Stack布局实现指示器与Swiper的视觉叠加通过onChange事件同步当前轮播索引利用animation属性实现选中状态过渡效果推荐使用ForEach动态生成指示器项,避免硬编码4.2 进度条式指示器(高级自定义)实现随轮播进度动态增长的进度条指示器,结合属性动画与Swiper事件:@Componentstruct ProgressIndicator { @Prop currentIndex: number @Prop totalCount: number @Prop duration: number // 与Swiper轮播间隔一致 build() { Row({ space: 4 }) { ForEach(Array.from({ length: this.totalCount }), (_, index) => { Stack({ alignContent: Alignment.Start }) { // 底层灰色轨道 Row() .width('100%') .height(2) .backgroundColor('#666666') // 上层白色进度条 Row() .width(this.currentIndex >= index ? '100%' : 0) .height(2) .backgroundColor('#FFFFFF') .animation({ duration: this.currentIndex === index ? this.duration : 0, curve: Curve.Linear }) } .layoutWeight(1) }) } .width('90%') .margin({ bottom: 20 }) }}// 使用方式Stack() { Swiper() { // 轮播内容 } .indicator(false) .autoPlay(true) .interval(3000) .onChange((index) => { this.currentIndex = index }) ProgressIndicator({ currentIndex: this.currentIndex, totalCount: this.imageList.length, duration: 3000 // 与interval保持一致 })}实现原理:每个进度条由上下两层Row组件叠加而成(轨道+进度条)当前页面对应的进度条通过animation实现3秒线性增长已轮播页面进度条保持100%宽度未轮播页面进度条宽度为04.3 带交互功能的自定义指示器实现点击指示器切换页面功能,结合SwiperController与手势识别:struct InteractiveIndicator { @Prop currentIndex: number @Prop totalCount: number controller: SwiperController // 接收Swiper控制器 build() { Row({ space: 8 }) { ForEach(Array.from({ length: this.totalCount }), (_, index) => { GestureDetector() { Column() .width(this.currentIndex === index ? 16 : 8) .height(8) .borderRadius(4) .backgroundColor(this.currentIndex === index ? Color.Red : Color.Gray) } .onClick(() => { // 点击切换到对应页面 this.controller.showIndex(index) }) }) } }}// 使用方式private controller: SwiperController = new SwiperController()build() { Stack() { Swiper(this.controller) { // 轮播内容 } .indicator(false) .onChange((index) => { this.currentIndex = index }) InteractiveIndicator({ currentIndex: this.currentIndex, totalCount: 5, controller: this.controller // 传递控制器 }) }}交互优化点:增大点击热区(建议最小8×8vp)添加点击反馈动画(如缩放、颜色变化)禁用快速连续点击(可通过防抖处理)确保指示器与Swiper滑动区域无重叠5. 常见问题解决方案5.1 内置指示器位置无法贴边显示问题现象:设置bottom: 0后仍有默认边距解决方案:通过负边距抵消内边距.indicator(Indicator.dot() .bottom(-8) // 负边距调整 .left(0))原理:Swiper组件内部有默认内边距,需通过负外边距补偿5.2 自定义指示器与Swiper滑动冲突问题现象:点击指示器时触发Swiper滑动解决方案:提高指示器手势优先级GestureDetector() { // 指示器内容}.priority(10) // 高于Swiper默认手势优先级.onClick(() => { // 切换逻辑})原理:鸿蒙手势系统通过priority属性解决冲突,值越高优先级越高5.3 循环模式(loop=true)下索引异常问题现象:启用循环后指示器计数错误解决方案:对索引进行取模处理.onChange((index: number) => { // 解决循环模式下索引溢出问题 this.currentIndex = index % this.totalCount})原理:loop模式下Swiper实际索引范围为[1, totalCount+1],需转换为[0, totalCount-1]6. 性能优化最佳实践6.1 减少重绘区域优化方案:将指示器独立为自定义组件,限制重绘范围@Componentstruct LightweightIndicator { @Prop currentIndex: number @Prop count: number build() { // 仅包含必要UI元素,避免复杂逻辑 Row({ space: 4 }) { // 指示器项 } }}性能收益:局部状态更新时仅重绘指示器区域,不影响Swiper主体6.2 避免过度动画优化建议:动画时长控制在200-300ms内优先使用系统内置曲线(如Curve.EaseOut)非关键状态变化可关闭动画.animation({ duration: 200, curve: Curve.EaseOut, iterations: 1 // 禁止无限循环})6.3 组件复用策略对频繁创建销毁的指示器项,使用**@Reusable**装饰器:@Reusable@Componentstruct ReusableIndicatorItem { @Prop isSelected: boolean build() { Column() .width(this.isSelected ? 20 : 8) .height(8) .backgroundColor(this.isSelected ? Color.Blue : Color.Gray) }}适用场景:动态数据源的Swiper(如网络图片轮播)频繁更新的指示器(如实时数据展示)长列表轮播(如商品列表)
-
前言不久前在仓颉的交流群中看到了关于仓颉案例共创的活动消息,于是想到可以用仓颉结合自身所长开发一个AI桌面宠物供大家娱乐学习。正好笔者封装的仓颉桌面渲染库 RGF_CJ 和控件库 RGF_UI 能满足这方面的需求,那么说干就干!成品效果开发前的准备在正式进行应用的开发之前,需要先准备好相关的依赖。领取华为云DeepSeek免费资源华为云为开发者提供了单模型200万Tokens的免费额度,包含DeepSeek-R1&V3满血版,登录华为云ModelArts Studio(MaaS)控制台即可领取,此处可以选择DeepSeek-R1满血版来搭建AI聊天桌宠。提示:点击此链接领取:https://www.huaweicloud.com/product/modelarts/studio.html进入网站签署服务声明领取免费额度并查看调用说明通过说明中的链接去创建API点击按钮创建API填写API相关信息获取API并保存提示:请保存好此处的API链接,后续代码开发中会用到。获取RGF依赖文件在后续应用的开发中会依赖到RGF_UI库,此库依赖到一些文件,可以从RGF_UI的库中获取。这些依赖文件后续会链接入应用中去。提示:点击此链接下载:https://gitcode.com/raozj/RGF_UI/tree/master/libs进入RGF_UI库的Libs目录,下载依赖文件保存依赖文件,留待后续使用安装仓颉STDX及相关依赖因为是基于仓颉开发通信相关的应用,因此需要依赖到STDX中的stdx.net.http、stdx.encoding.json、stdx.net.tls等包,而这些包自Cangjie v0.60.5版本开始从std标准库中移出,因此需要自行安装这些包以及这些包所依赖的OpenSSL。提示:点击此链接下载:https://gitcode.com/Cangjie/Cangjie-STDX注’ 由于笔者发文时Cangjie v0.60.5版本尚且处于内测阶段,因此若开发者无内测资格则无法下载到此版本的相关文件。关于 STDX 和 OpenSSL 的安装请参考上述链接中官方的文档,此处不做赘述。序列帧合图资源制作作为一个桌面宠物,自然需要拥有对应的动画,对此可以使用DragonBones、Spine或者Adobe Effect等软件制作。制作动画笔者在此使用的是DragonBones,关于DragonBones的安装和使用不在本文的探讨范畴之内,读者可自行选择喜欢的动画制作软件,设计并制作完成动画后输出序列帧以用于应用的开发。设计动画制作出序列帧制作合图资源如果每帧的图片都单独存储,对于应用的初始化加载和渲染都是一个较大的负担,因此建议将一类的序列帧动画按照相等宽高、间距合并为一张图片。由于手动合图比较麻烦,因此笔者写了一个Python脚本,快速的将目录中的序列帧图片按照指定的宽度分布数量合并为一张图片。注’ 由于此部分不属于文章核心部分,因此直接贴出代码以供参考,读者可通过其它工具或代码实现自己个性化的合图方案。import os import cv2 import numpy as np def load_images_from_folder(folder): """ 加载指定文件夹下的所有 .png 图像 """ image_files = [f for f in os.listdir(folder) if f.lower().endswith('.png')] image_files.sort() # 按文件名排序 images = [] for filename in image_files: file_path = os.path.join(folder, filename) # 读取图像(支持中文路径) with open(file_path, 'rb') as f: img_data = np.frombuffer(f.read(), dtype=np.uint8) img = cv2.imdecode(img_data, cv2.IMREAD_UNCHANGED) images.append(img) print(f"已加载: {filename}") return images def stitch_images(images, cols, bg_color=(0, 0, 0, 0)): """ 将图像按指定列数拼接成一张大图 :param images: 图像列表 :param cols: 每行显示的图像数量 :param bg_color: 背景颜色(支持 RGBA) :return: 拼接后的图像 """ if not images: raise ValueError("没有图像可供拼接") h, w = images[0].shape[:2] rows = (len(images) + cols - 1) // cols # 创建空白背景图 channels = images[0].shape[2] if len(images[0].shape) == 3 else 1 if channels == 1: bg_color = (bg_color[0], ) # 灰度图 elif channels == 3: bg_color = bg_color[:3] # RGB elif channels == 4: bg_color = bg_color[:4] # RGBA canvas = np.full((h * rows, w * cols, channels), bg_color, dtype=np.uint8) for idx, img in enumerate(images): row = idx // cols col = idx % cols x = col * w y = row * h canvas[y:y+h, x:x+w] = img return canvas def main(): folder = input("请输入包含PNG图像的文件夹路径:").strip() if not os.path.isdir(folder): print("输入的不是有效文件夹路径!") return try: cols = int(input("请输入每行显示的图像数量:")) except ValueError: print("请输入有效的数字!") return output_path = input("请输入输出图像的完整路径(如 output.png):").strip() images = load_images_from_folder(folder) if not images: print("未找到任何PNG图像!") return # 检查所有图像是否尺寸一致 first_h, first_w = images[0].shape[:2] for img in images[1:]: h, w = img.shape[:2] if h != first_h or w != first_w: print("警告:图像尺寸不一致,可能影响拼接效果。") # 拼接图像 print("开始拼接图像...") stitched_image = stitch_images(images, cols) # 保存结果 cv2.imwrite(output_path, stitched_image) print(f"拼接完成,已保存为: {output_path}") if __name__ == '__main__': main() 开发AI桌宠基本规划首先对项目目录和文件进行基本的规划,后续的开发就基于规划进行。目录规划Yez/ -------------------------------------------# 项目主目录 ├─libs/ ---------------------------------------# 依赖库 ├─res/ ----------------------------------------# 资源 │ └─SequenceFrame/ ---------------------------# 序列帧资源 │ └─skip_white/ ---------------------------# 椰椰的白色皮肤资源 ├─src/ ----------------------------------------# 源代码目录 └─tool/ ---------------------------------------# 工具目录文件规划Yez/ ├┈cjpm.lock ├┈cjpm.toml ├─libs/ │ ├┈libimm32.a -------------------------------# Windows 输入法库 │ ├┈resources.rc.o ---------------------------# 资源配置清单 内部声明应用依赖的最低系统版本 │ └┈libRgf.dll -------------------------------# RGF渲染库依赖的动态库 ├─res/ │ ├┈config.json ------------------------------# 椰椰的配置文件 │ └─SequenceFrame/ │ └─skip_white/ │ ├┈blink.json -------------------------# 眨眼合图描述文件 │ ├┈blink.png --------------------------# 眨眼动画合图 │ ├┈jump.json --------------------------# 跳跃合图描述文件 │ ├┈jump.png ---------------------------# 跳跃动画合图 │ ├┈speak.json -------------------------# 说话合图描述文件 │ ├┈speak.png --------------------------# 说话动画合图 │ ├┈wait.json --------------------------# 待机合图描述文件 │ └┈wait.png ---------------------------# 待机动画合图 ├─src/ │ ├┈chat.cj ----------------------------------# 大模型网络通信代码 │ ├┈config.cj --------------------------------# 配置管理代码 │ ├┈main.cj ----------------------------------# 程序主入口代码 │ ├┈res.cj -----------------------------------# 资源管理代码 │ ├┈win_anm.cj -------------------------------# 动画绘制窗口代码 │ ├┈win_input.cj -----------------------------# 输入框窗口代码 │ └┈win_text.cj ------------------------------# 鼠标文本渲染代码 └─tool/ └┈picture_puzzle.py ------------------------# python 合图脚本代码链接文件与库配置在开发之前,需要现为当前项目链接依赖文件和添加依赖库。链接文件在之前的小节中,下载了RGF_UI的依赖文件压缩包,在下载的压缩包内应该包含三个文件,即libRgf.dll、resources.rc.o和libimm32.a。这三个文件中,libRgf.dll是RGF_CJ库的依赖文件,必须随应用打包发布;resources.rc.o文件是Windows资源描述文件,读者也可以自行编写resources.rc文件并生成此文件(需设置最低支持Windows 8,即内核NT 6.2);libimm32.a文件是自绘编辑框控件依赖的文件,主要提供输入法相关的操作能力。在仓颉包管理配置toml文件中,需要加入链接上述文件的代码:[target] [target.x86_64-w64-mingw32] link-option = "-L ./libs -l:libRgf.dll -l user32 -l imm32" 库配置完成链接后,还需要为应用声明依赖RGF_CJ和RGF_UI包,并且为RGF_CJ包配置宏。提示:点击此链接查看依赖库RGF_CJ:https://gitcode.com/Cangjie-SIG/RGF_CJ提示:点击此链接查看依赖库RGF_UI:https://gitcode.com/raozj/RGF_UI[dependencies] [dependencies.rgf] git = "https://gitcode.com/Cangjie-SIG/RGF_CJ.git" [dependencies.rgfui] git = "https://gitcode.com/raozj/RGF_UI.git" [profile] [profile.customized-option] rgfCfg = "--cfg \"RGF_LANG=zh-cn\"" 上述代码中配置的“RGF_LANG=zh-cn”表示:当RGF_CJ出现异常时,捕获输出的异常提示内容为中文。配置加载模块笔者希望桌宠能够具备记忆配置的能力,因此需要为桌宠写一个配置的加载、保存模块,这个模块会记住桌宠所使用的皮肤、最后所在的桌面位置等信息,以确保桌宠重启后依然符合之前的情况。编写一个配置管理相关的恶汉单例类ConfigMgr,单例具备两个方法,一个用于从文件中加载配置,一个用于写出配置到文件中去。(其实也可以直接编写静态方法或直接编写函数而不使用类,看大家自己的选择吧。)代码如下:// Yez/src/config.cj // 配置管理类 class ConfigMgr { private static let _instance:ConfigMgr = ConfigMgr() public static prop i:ConfigMgr{ get(){ _instance } } private init() {} ... /** * 加载配置文件 * @return 是否成功 */ public func readConfig():Bool{ ... } /** 写出配置文件 */ public func writeConfig():Unit{ ... } ... } 编写一个ConfigInfo类,这个类用于存放实际的配置项,并且提供转换为Json的能力,代码如下:// Yez/src/config.cj class ConfigInfo <: Serializable<ConfigInfo> { // 椰椰右下角窗口坐标系的X坐标 var _x:Int32 = Int32.Min // 椰椰右下角窗口坐标系的Y坐标 var _y:Int32 = Int32.Min // 椰椰使用的皮肤 var _skip = "skip_white" // 椰椰画面缩放比例 var _zoom:Float32 = 0.125 // 椰椰的字体大小 var _fontSize:Float32 = 16.1 public func serialize(): DataModel { return DataModelStruct().add(field<Int32>("x", _x)) .add(field<Int32>("y", _y)) .add(field<String>("skip", _skip)) .add(field<Float32>("zoom", _zoom)) .add(field<Float32>("fontSize", _fontSize)) } public static func deserialize(dm: DataModel): ConfigInfo { var dms = match (dm) { case data: DataModelStruct => data case _ => throw Exception("this data is not DataModelStruct") } var result = ConfigInfo() result._x = Int32.deserialize(dms.get("x")) result._y = Int32.deserialize(dms.get("y")) result._skip = String.deserialize(dms.get("skip")) result._zoom = Float32.deserialize(dms.get("zoom")) result._fontSize = Float32.deserialize(dms.get("fontSize")) return result } } 将ConfigInfo以成员变量的形式加入到ConfigMgr中,并且由ConfigMgr对外提供访问属性,代码如下:// Yez/src/config.cj // 配置管理类 class ConfigMgr { // 配置信息 private var configImpl:ConfigInfo = ConfigInfo() ... public mut prop x:Int32{ get(){ return configImpl._x } set(val){ configImpl._x = val } } public mut prop y:Int32{ get(){ return configImpl._y } set(val){ configImpl._y = val } } public mut prop skip:String{ get(){ return configImpl._skip } set(val){ configImpl._skip = val } } public prop zoom:Float32{ get(){ return configImpl._zoom } } public prop fontSize:Float32{ get(){ return configImpl._fontSize } } } 接下来只要完善readConfig和writeConfig的读取和写出逻辑即可,代码如下:// Yez/src/config.cj // 配置文件路径 const CONFIG_FILE_PATH = "./res/config.json" ... public func readConfig():Bool{ // 判断目录是否存在 if(exists(CONFIG_FILE_PATH)){ let seqFramePath = canonicalize(CONFIG_FILE_PATH) let bytes = File.readFrom(seqFramePath) var jv = JsonValue.fromStr(StringReader(ByteBuffer(bytes)).readToEnd()) var dm = DataModel.fromJson(jv) configImpl = ConfigInfo.deserialize(dm) } // 目前始终返回 true // 当配置不存在时,则使用默认配置 return true } public func writeConfig():Unit{ if(!exists(CONFIG_FILE_PATH)){ File.create(CONFIG_FILE_PATH) } let seqFramePath = canonicalize(CONFIG_FILE_PATH) var Anm = configImpl.serialize() var dm = Anm.toJson() File.writeTo(seqFramePath,dm.toString().toArray()) } 最后,在入口函数中启用配置加载与写出即可。// Yez/src/main.cj main(): Int64 { // 加载配置文件数据 if(!ConfigMgr.i.readConfig()){ return 0 } ... // 保存配置到文件中去 ConfigMgr.i.writeConfig() return 0 } 资源加载模块除了配置之外,应用资源也是一个重要的部分,这部分的完整性会影响到应用是否正常渲染。首先采用和Config部分类似的结构准备恶汉单例类ResMgr和动画信息类AnmInfo,代码如下:// Yez/src/res.cj // 资源管理类 class ResMgr { private static let _instance:ResMgr = ResMgr() public static prop i:ResMgr{ get(){ _instance } } private init() {} // 动画 private static const ANM_BLINK ="blink" private static const ANM_JUMP ="jump" private static const ANM_SPEAK ="speak" private static const ANM_WAIT ="wait" private let _skipImg:HashMap<String, Array<(AnmInfo,MemBitmap)>> = HashMap<String, Array<(AnmInfo,MemBitmap)>>() public func loadRes():Bool{ ... } public func unloadRes(){ ... } public prop skip:HashMap<String, Array<(AnmInfo,MemBitmap)>>{ get(){ return _skipImg } } } class AnmInfo <: Serializable<AnmInfo> { // 单帧图片宽度 var width:Int32 = 0 // 单帧图片高度 var height:Int32 = 0 // 合图图片每行图片数量 var hCount:Int32 = 0 // 总帧数 var frames:Int32 = 0 ... } 然后就是实现在loadRes方法中加载合图及合图相关配置文件,代码逻辑还就是:从"./res/SequenceFrame"目录开始确保目录存在,然后遍历SequenceFrame目录下的所有皮肤目录,并且将皮肤目录内的合图资源及对应的配置文件载入到成员中去。如果目录下的资源文件不完整,则不会加载对应的内容到成员中。// Yez/src/res.cj /** * 代码较为复杂且不是核心代码,读者了解功能即可。 * 若需要查看相关代码,可下载项目查看。 */ public func loadRes():Bool{ ... } public func unloadRes(){ //释放所有图像资源(设备无关资源) for(skipPath in _skipImg){ skipPath[1][0][1].release() skipPath[1][1][1].release() skipPath[1][2][1].release() skipPath[1][3][1].release() } } 最后,在入口函数中启用资源加载与释放即可。// Yez/src/main.cj main(): Int64 { ... // 加载依赖资源 if(ResMgr.i.loadRes()){ ... } // 释放一切可能被加载的资源 ResMgr.i.unloadRes() ... return 0 } 大模型模块接下来便是与AI通信的核心部分了,此部分需要完成与大模型的互通。我们需要准备一个LLM类,这个类负责基于API、URL等信息与大模型建立链接,并且解析信息。// Yez/src/chat.cj class LLM { let client: Client let history = StringBuilder() public LLM( // 大模型URL链接 let url!: String, // 大模型 API Key let key!: String, // 大模型类型标识 let model!: String, // 上下文 let context!: Bool = false ) { var config = TlsClientConfig() config.verifyMode = TrustAll client = ClientBuilder() .tlsConfig(config) // AI 服务响应有时候比较慢,这里设置为无限等待 .readTimeout(Duration.Max) .build() } /** * 编码函数,将角色与内容编码为字符串 * return 字符串 */ func encode(role: Role, content: String) { ... } /** * 发送函数,将对话数据发送到服务器 * return HttpResponse */ func send(input: String, stream!: Bool = false) { ... } /** * 分析函数,解析服务器返回的数据并且提取其中有效的文本内容 * return 有效内容 */ func parse(text: String, stream!: Bool = false) { ... } /** * 流式对话 * @param input 输入的文本 * @param task 流式返回内容的回调函数 * @param taskEnd 返回内容结束的回调函数 */ public func chat(input: String, task!: (String) -> Unit = {o => print(o)},taskEnd!:()->Unit = {=>}) { ... } /** * 非流式对话 */ public func chat(input!: String) { ... } /** * 历史记忆预置 */ public func preset(context: Array<(Role, String)>) { ... } /** * 历史记忆清除 */ public func reset() { ... } } 根据华为云提供的调用参考,完善 send 方法代码如下:// Yez/src/chat.cj func send(input: String, stream!: Bool = false) { let message = encode(I, input) let content = '{"model":"${model}","messages":[${history}${message}],"stream":${stream}}' if (context) { history.append(message) } let request = HttpRequestBuilder() .url(url) .header('Authorization', 'Bearer ${key}') .header('Content-Type', 'application/json') .header('Accept', if (stream) { 'text/event-stream' } else { 'application/json' }) .body(content) .post() .build() client.send(request) } 根据华为云返回的数据格式,完善 parse 方法代码如下:// Yez/src/chat.cj func parse(text: String, stream!: Bool = false) { let json = JsonValue.fromStr(text).asObject() let choices = json.getFields()['choices'].asArray() if(choices.size() == 0){ return "" } // 流式和非流式情况下,这个字段名称不同 let key = if (stream) { 'delta' } else { 'message' } let message = choices[0].asObject().getFields()[key].asObject() if(message.containsKey("content")){ let content = message.getFields()['content'].asString().getValue() // 移除开头的两个 \n if(content == "\n\n"){ return "" } return content }else{ return "" } } 最后实现核心的流式对话部分,代码如下:// Yez/src/chat.cj public func chat(input: String, task!: (String) -> Unit = {o => print(o)},taskEnd!:()->Unit = {=>}) { // 根据传入的字符串构建上发服务器的 HttpResponse let response = send(input, stream: true) // 准备一个字符串缓冲区 let output = StringBuilder() // 准备一个字节缓冲区 let buffer = Array<Byte>(1024 * 8, {i=>0}) // 获取返回的内容 var length = response.body.read(buffer) while (length != 0) { // 将字节缓冲区内的内容按照UTF-8编码解析为字符串 let text = String.fromUtf8(buffer[..length]) const INDEX = 6 // 按行分割 for (line in text.split('\n', removeEmpty: true)) { // 确保获取的是完整的:data: {...} if (line.size > INDEX && line[INDEX] == b'{' && line[line.size - 1] == b'}') { let json = line[INDEX..line.size] // 分析出有效信息 let slice = parse(json, stream: true) if (context) { output.append(slice) } // 若有效信息不为空,则通过回调函数返回 if(slice != ""){ task(slice) } } } length = response.body.read(buffer) } if (context) { history.append(',${encode(AI, output.toString())},') } // 所有处理结束后,执行结束回调 taskEnd() } LLM类是一个较为通用的类,但是使用较为繁琐,因此对于外部调用而言,只需要一个简化的实例即可。因此,可以再编写一个独立的懒汉单例LLMImpl类,并且仅提供一个对外实现流式对话的chat方法,代码如下:// Yez/src/chat.cj class LLMImpl { private static var _i: Option<LLMImpl> = None public static prop i:LLMImpl { get(){ if(_i.isNone()){ _i = LLMImpl() } return _i.getOrThrow() } } private let robot:LLM // 初始化一个对话模型 private init(){ robot = LLM( // 华为云大模型调用URL url: 'https://maas-cn-southwest-2.modelarts-maas.com/v1/infers/8a062fd4-7367-4ab4-a936-5eeb8fb821c4/v1/chat/completions', // 如果示例自带的密钥失效,请自行注册,https://www.huaweicloud.com/product/modelarts/studio.html key: '/* !!! 这里填写读者自己的 API Key !!! */', // 要使用的 大模型 model: 'DeepSeek-R1', context: true ); // 设定基本信息 robot.preset([(System, #"我会自称“椰椰”并亲昵的在三句话之内回复所有问题。对于限定长度内无法阐述清晰的问题,我会表现出呆傻可爱的效果并说出“宕机”。如果对我说“拍照”或“截图”,我就会说“咔嚓~”。如果问我是谁开发的,我会说出“尧佥&椰子”。"#)]) } /** * 流式对话 * @param content 输入的文本 * @param task 流式返回内容的回调函数 * @param end 返回内容结束的回调函数 */ public func chat(content:String, task!: (String) -> Unit = {o => print(o)},end!:()->Unit = {=>}){ robot.chat(content,task:task,taskEnd:end) } } 注’ 完整的代码请参考提供的代码文件桌宠渲染接下来,需要实现可视化的窗口渲染,赋予“椰椰”一副“身体”。动画播放窗口为了实现AI桌宠的渲染,首先需要有一个窗口。对此,笔者选择基于RGF_CJ中的WinBase类,实现一个自己的窗口类WinMain。// Yez/src/win_anm.cj // 使用 RGF_CJ 的宏实现代码简化 @LifeCycle // 自动管理 设备相关资源 的释放 @HookProc // 自动挂载窗口类的消息事件方法 class WinMain <: WinBase{ ... // 窗口类初始化时,传入 ResMgr 载入的资源(设备无关资源) init(anm:Array<(AnmInfo, MemBitmap)>){ _anmRes = anm } /** * 加载设备无关资源 -> 设备相关资源 * @note 本部分将资源ResMgr类中的图片资源,载入到窗口类中 */ private func loadAnm(anm:Array<(AnmInfo, MemBitmap)>){ surface .createBitmapFromMemory(_bmps[0],anm[0][1]) .createBitmapFromMemory(_bmps[1],anm[1][1]) .createBitmapFromMemory(_bmps[2],anm[2][1]) .createBitmapFromMemory(_bmps[3],anm[3][1]) } /** * 创建设备资源事件,窗口渲染表面创建完成后,开始载入资源 */ public func createDeviceResources():Bool{ // 这里调用 loadAnm 载入图片资源 loadAnm(_anmRes) return true; } /** * 窗口创建完成事件,当窗口创建完成时,开始渲染动画 */ public func created(hwnd:RgfHwnd,x:Int32, y:Int32, nWidth:Int32, nHeight:Int32):Unit{ ... } /** * 绘制窗口事件,此处根据当前的动画状态渲染窗口 */ public override func onPaint(wRect:Rect):Unit{ surface.clear(0.0,0.0,0.0,0.0) match(_nowState){ case Wait => {=> // 等待状态的动画渲染比较特殊,因为需要实现眼睛跟随鼠标移动 if(_bmps[3].isValid() && _bmps[0].isValid()){ // 这一部分计算当前渲染帧的静态部分,即:身体, // 并且将这部分内容渲染到窗口 .... // 然后下面计算出鼠标对眼睛造成的偏移量 // 并且将眼睛渲染到身体上 if(_anmStep < _anmRes[0][0].frames){ .... }else{ .... } } }() case Jump => {=> // 这里渲染椰椰的跳跃动画 if(_bmps[1].isValid()){ .... } }() case Speak => {=> // 这里渲染椰椰正在说话的动画 if(_bmps[2].isValid()){ .... } }() } } } 在入口函数中,启用UIMain并且创建窗口,代码如下:// Yez/src/win_anm.cj main(): Int64 { ... if(ResMgr.i.loadRes()){ // 开始创建UI UIMain{=> // 确保皮肤配置有效 if(!ResMgr.i.skip.contains(ConfigMgr.i.skip)){ ConfigMgr.i.skip = ResMgr.i.skip.keys().toArray()[0] } // 创建窗口类 let anmWin:WinMain = WinMain(ResMgr.i.skip.get(ConfigMgr.i.skip).getOrThrow()) // 先随便在桌面 (0,0) 的位置创建一个 10像素×10像素 的窗口 // 窗口类内部会在显示前根据配置中的信息自动布局到正确位置和尺寸 anmWin.createWin(uiGetContext(), "Yez", 0, 0, 10, 10, 0, WinStyle.e2n(WinStyle.Popup), // 使用的是 TopMost 分层窗口(支持透明背景) dwExStyle:WinStyleEx.Default | WinStyleEx.TopMost, // 渲染使用的是 Direct2D 计数 layered:true,substrate:Substrate.Direct2D ) } } ... } 完成上述代码后,窗口已经可以正确的渲染动画了注’ 完整的代码请参考提供的代码文件编辑框窗口对于编辑框的开发是比较繁琐的,因此选择直接继承RGF_UI的UIInput类,然后将此控件修改为窗口,首先需要创建一个编辑框窗口类InputWin,代码如下:// Yez/src/win_input.cj public open class InputWin <: UIInput{ // 为编辑框设置独立的UI样式,而不是继承全局的样式 let css:UITheme = UITheme() init(){ super(theme:css) // 从配置中获取字体大小 css.fontSizeM = ConfigMgr.i.fontSize // 更新样式配置 css.update() } /** * 获取输入框窗口预期出现的位置 * @return 编辑框窗口坐标 * @note 本部分计算编辑框在当前椰椰所在桌面中心时的位置 */ private func getTargetLocation():Point{ // 方案二、桌面中心 let monitor = rsGetMonitorFromPoint(rsGetCursorPos()) let pos = Point( ((monitor.right - monitor.left) - INPUT_WIN_WIDTH) / 2, ((monitor.bottom - monitor.top) - INPUT_WIN_HEIGHT) / 2 ) return pos } /** * 创建窗口 */ public func createWin(hwndParent:RgfHwnd):RgfHwnd{ // 获取出现位置坐标 let pos = getTargetLocation() // 创建一个置顶层的无边框编辑框窗口 let retv = super.createWin( uiGetContext(),"",pos.x,pos.y,INPUT_WIN_WIDTH,INPUT_WIN_HEIGHT,hwndParent, WinStyle.PopupWindow | WinStyle.ClipChildren, dwExStyle:WinStyleEx.Default | WinStyleEx.TopMost, substrate:Substrate.Direct2D,layered:false,bgMod:BackGroundMod.CoverNotDraw,drawInterval:0 ) // 设置默认的内容为:“想对椰椰说什么:” content = INPUT_PROMPT // 创建好就显示窗口 show(true) return retv } } 完成编辑框窗口的创建后,还需要能响应相关的输入事件。对此,笔者希望:a) 当编辑框被按下或输入内容时,如果内容为默认内容,则清空提示内容;b) 当编辑框失去焦点时,窗口主动销毁自身;c) 当编辑框按下回车键时,将编辑框内容上发给大模型,并开始处理返回消息。实现自动清空内容很简单,代码如下:// Yez/src/win_input.cj /** 鼠标左键按下时,判断内容并清空 */ public override open func onLButtonDown(e:EvMouse):Unit{ if(content == INPUT_PROMPT){ content = "" } super.onLButtonDown(e) } /** 键盘按下某键时,判断内容并清空 */ public override open func onKeyDown(e:EvKey):Unit{ if(content == INPUT_PROMPT){ content = "" } super.onKeyDown(e) } 得益于RGF_CJ库的安全性,窗口销毁会自动释放资源,避免了繁琐的资源释放,和释放时机的管理。实现窗口失去焦点就是否窗口的代码如下:// Yez/src/win_input.cj /** 窗口失去焦点时,销毁窗口并释放窗口资源 */ public override open func onKillFocus(e:EvKillFocus):Unit{ destroyWin() } 希望实现按下回车键触发对话,则需要在对应的事件中调用LLMImpl类的流式对话方法,代码如下:// Yez/src/win_input.cj /** 字符输入事件 */ public override open func onChar(e:EvChar):Unit{ // 判断输入的是否为回车键 if(e.char == 0x0D/*VK_RETURN*/){ // 是回车键就销毁本窗口 destroyWin() // 并且调用 开始回答函数 start() // 然后调用 LLMImpl 类的流式对话方法 // 并且传入 流的回调函数 以及 回答结束的函数 LLMImpl.i.chat(content,task:task,end:end) }else{ super.onChar(e) } } // 回复流 public var task:(String) -> Unit = {str=>} // 开始回答 public var start:() -> Unit = {=>} // 开始回答 public var end:() -> Unit = {=>} 编辑框控件的效果如下图所示:注’ 完整的代码请参考提供的代码文件反馈窗口完成上述内容后,输入与处理已经基本完备,接下来需要实现文本的显示,这一部分涉及到内容绘制,同样还是基于WinBase类进行窗口开发。创建一个反馈文本的窗口类 WinText,并且准备基本的资源代码如下:// Yez/src/win_text.cj @Abbr // 文本色彩值转Color对象宏 @LifeCycle @HookProc open class WinText <: WinBase{ // 内容纯色画刷 private let _brhCnt:SolidColorBrush // 背景纯色画刷 private let _brhbg:SolidColorBrush // 文本渲染核心 private let _textCore:TextCore // 文本格式 private let _textformat:TextFormat // 文本布局 private let _textLayout:TextLayout // 文本内容 private var _text:String = "" /** 创建窗口 */ public func createWin( x:Int32, y:Int32, nWidth:Int32, nHeight:Int32, hWndParent:RgfHwnd, substrate!:Substrate = Substrate.Direct2D ):RgfHwnd{ ... } /** 文本对象管理方法,根据配置和窗口尺寸生成文本渲染依赖的对象 */ private func createFontObj(){ let rect:Rect = surface.getRect() surface.createTextCore(_textCore) .createTextFormat(_textformat,ConfigMgr.i.fontSize) .createTextLayout(_textLayout,_text,_textformat,Float32(rect.right),Float32(rect.bottom)) } /** 创建设备/渲染相关资源事件 */ public override open func createDeviceResources():Bool{ // 创建 纯黑内容画刷 和 白色半透明背景画刷 surface.createSolidColorBrush(_brhCnt,"#000",1.0) .createSolidColorBrush(_brhbg,"#FFFA",1.0) // 创建文本相关对象 createFontObj() return true; } } 完成渲染事件,确保内容正确渲染// Yez/src/win_text.cj // 绘制窗口 public override open func onPaint(wRect:Rect):Unit{ surface.clear(0.0,0.0,0.0,0.0) // 透明模式 // 渲染背景圆角矩形 surface.fillRoundedRectangle(surface.getRect(),4.0,4.0,_brhbg) // 渲染大模型返回的文本内容 surface.drawTexts(_textCore,_brhCnt,_textLayout,originX:0.0,originY:0.0) } 完成显示文本方法。当设置文本时,若窗口类已经存在,就更新内部文本;若窗口不存在,就根据当前内容创建窗口并显示。// Yez/src/win_text.cj public func showText(str:String,p:RgfHwnd):Unit{ // 获取光标位置 let pos = rsGetCursorPos() // 获取光标所在屏幕矩形范围 let monitor = rsGetMonitorFromPoint(pos) // 如果窗口未创建,就创建窗口 if(!isCreated()){ createWin(pos.x,pos.y,100,100,p) } if(_textLayout.isValid()){ if(str != _text){ // 更新文本内容 _text = str; // 创建文本布局信息 surface.createTextLayout(_textLayout,_text,_textformat,Float32(monitor.right - monitor.left) / 2.0,Float32(monitor.bottom - monitor.top) / 2.0) } // 获取文本自动布局后的测量数据 let m = _textLayout.getMetrics() let w = Int32(m.getOrThrow().width) let h = Int32(m.getOrThrow().height) 按照测量数据移动窗口 if(pos.x > (monitor.right - monitor.left) / 2){ move(pos.x - w,pos.y - h,w,h) }else{ move(pos.x,pos.y - h,w,h) } } // 窗口显示 show(true) } 文本控件的效果如下图所示:注’ 完整的代码请参考提供的代码文件联结各个窗口逻辑完成上述各个控件后,需要实现各个组件之间的联合。实现流式信息与动画的联动// Yez/src/win_anm.cj /** 文本变更事件 */ func onTextChange(str:String):Unit{ if(!_firstText){ _firstText = true // 当文本输出开始,切换“跳跃”动画状态到“说话”动画状态 play(AnmState.Speak) } _text += str _textBox.showText(_text,hWnd) } /** 大模型开始输出文本 */ func onTextStart():Unit{ killTimer(0) _text = "" // 给大模型上发请求后,播放“跳跃”动画 play(AnmState.Jump) _firstText = false } /** 大模型结束输出文本 */ func onTextEnd():Unit{ // 大模型输出结束后,延迟5s,以让用户完整阅读完内容 const cj_bug_val:Option<CFunc<(HWND, UInt32, UInt64, UInt32) -> Unit>> = None setTimer(0,5000,cj_bug_val) } /** 时钟事件 */ func onTimer(e:EvTimer):Unit{ killTimer(0) // 用户阅读完内容后,销毁文本框窗口 _textBox.destroyWin() // 播放“等待”动画 play(AnmState.Wait) } 双击“椰椰”时打开输入框窗口// Yez/src/win_anm.cj // 客户区双击鼠标左键 func onLButtonDblClk(e:EvMouse):Unit{ // 在“等待”状态下双击“椰椰” match(_nowState){ case AnmState.Wait => {=> // 销毁文本框(虽然已无可能还显示,不过保险起见) _textBox.destroyWin() if(!_inputBox.isCreated()){ // 创建文本输入框窗口 _inputBox.createWin(hWnd) }else{ // 如果窗口存在就移动输入框窗口 // 这部分适用于希望输入框跟随鼠标位置的情况 // 对于方案二的桌面居中而言,没有必要 _inputBox.move() } () }() case _ => () } } 结语至此,仓颉AI桌宠“椰椰”就基本开发完成了,其实还有很多规划的内容可以增加,比如当和椰椰说“咔嚓”或者“拍照”时激活截图功能。这些内容就留给读者继续探索啦!本文相关链接本文项目代码库:https://gitcode.com/raozj/YezRGF_CJ库:https://gitcode.com/Cangjie-SIG/RGF_CJRGF_UI库:https://gitcode.com/raozj/RGF_UIRGF案例库:https://gitcode.com/raozj/RGF_CJ_Example华为云ModelArts:https://www.huaweicloud.com/product/modelarts/studio.html
-
用的是ArkTs,要求这三个模式不能同时打开,要怎么做
-
目前乾坤终端安全检测到一个病毒就会弹窗一次,如果同时检测到多个病毒,用户需要多次点击关闭按钮才能将全部弹窗关闭,费时费力。能否考虑将这些弹窗合并在一起?参考ESET/Avast那样,可以一次性全部关闭。
-
上传组件上传rar文件,报文件IO异常是什么问题?测试了下,压缩rar4格式没问题,rar格式报文件IO异常。
-
51CTO.com快译】什么是PostgreSQL GUI?它如何帮助企业管理PostgreSQL数据库?人们需要了解2021年一些优秀的PostgreSQL GUI软件。PostgreSQL是一种先进的开源对象关系数据库管理系统,可以支持SQL和JSON查询。根据Stack Overflow公司的一项调查,PostgreSQL是目前仅次于MySQL的第二大常用数据库。在对7万多名受访者的调查中,超过40%的人表示更喜欢采用PostgreSQL,而不是SQLite、MongoDB、Redis等其他数据库。作为Postgres用户,有两种方法来管理数据库:通过命命令行界面(CLI)编写查询(并非所有人都喜欢)。使用Postgres GUI,该界面由PostgreSQL管理工具之一构建。Postgres GUI比命令行界面(CLI)方便得多。此外,它还可以提高企业的工作效率。以下了解一下Postgres GUI和最常用的PostgreSQL GUI工具。什么是PostgreSQL GUI?PostgreSQL GUI是PostgreSQL数据库的管理工具。它允许企业或数据库用户查询、可视化、操作、分析其Postgres数据。还可以通过Postgres GUI访问数据库服务器。很多用户更喜欢Postgres GUI而不是CLI的主要原因是:漫长的学习曲线和复杂的使用流程。CLI界面不便于使用。控制台提供的信息不足。难以通过控制台浏览和监控数据库。反过来,使用Postgres GUI可为企业提供以下优势:快捷方式可用于更快、更简单的工作。丰富的数据可视化机会。可以访问远程数据库服务器。轻松地访问操作系统。优秀的PostgreSQL GUI软件对于某些用户来说,以Postgres为中心的pgAdmin并不是目前唯一可用的Postgres GUI工具,这可能出乎人们的意料。以下了解一下如今流行的一些PostgreSQL GUI管理工具。也许其中之一将会显著简化Postgres数据库管理。1.pgAdminpgAdmin是一个开源的跨平台PostgreSQL GUI工具。优点:与Linux、Windows、macOS兼容。允许同时使用多个服务器。CSV文件导出。查询计划功能。能够通过仪表板监控会话、数据库锁定。SQL编辑器中的快捷方式,使工作更方便。内部程序语言调试器旨在帮助代码调试。完整的文档和充满活力的社区。缺点:与一些付费的工具相比,其用户界面运行缓慢且不直观。笨重。不易上手。企业需要高级技能才能同时使用多个数据库。2.DBeaver这是一个支持多数据库的开源PostgreSQL管理工具。优点:跨平台。支持80多个数据库。作为可视化查询生成器,允许企业在没有SQL技能的情况下添加SQL查询。具有多个数据视图。CSV、HTML、XML、JSON、XLS、XLSX中的数据导入/导出。高级数据安全性。全文数据搜索和将搜索结果显示为表格/视图的能力。提供免费计划。缺点:与竞争对手相比运行速度较慢。更新过于频繁,令人烦恼。在闲置一段时间后,DBeaver会断开与企业的数据库的连接。企业需要重新启动应用程序。3.Navicat这是一个非常直观的Postgres数据库管理图形工具。Navicat并不是开源的工具。优点:非常容易和快速安装。获得Windows、Linux、macOS、iOS支持。方便快捷的可视化SQL构建器。具有代码自动完成功能。数据建模工具:操作企业的数据库对象、设计模式。作业调度程序:运行作业,在作业完成时获得通知。内置团队协作。数据源同步。以Excel、Access、CSV和其他格式导入/导出数据。通过SSH隧道和SSL确保数据保护。与亚马逊、谷歌和其他公司的云计算服务商合作。缺点:GUI工具性能不高。与竞争对手相比价格偏高。一个许可证只限于一个平台(用户需要PostgreSQL和MySQL两个单独的许可证)。许多高级功能需要时间来学习如何使用。使用不方便:添加行时需要更新应用程序。4.DataGrip由JetBrains构建的支持多个数据库的高级IDE。优点:跨平台(Windows、macOS、Linux支持)。简单的架构导航。带有查询控制台的可自定义用户界面(UI),可确保企业的工作进度安全。提示错误检测。内置版本控制系统。MySQL、SQLite、MariaDB、Cassandra和其他数据库支持。清晰的报告,能够将它们与图表和图形集成。强大的自动完成功能,建议相关代码完成。缺点:相当昂贵。消耗内存。复杂的错误调试过程。DataGrip和JetBrains具有长期的学习曲线。难以用作基于云计算的管理Web应用程序。不适合同时管理多个数据库。5.HeidiSQL这是一个开源Postgres(不仅仅是)GUI工具。现在仅支持Windows。优点:易于安装,与竞争对手相比非常轻巧。PostgreSQL、MySQL、Microsoft SQL Server、MariaDB支持。能够在一个窗口中连接和管理多个数据库服务器。从一个数据库或服务器到另一个数据库或服务器的直接SQL导出。通过简单易用的网格进行批量表格浏览和编辑。代码完成和语法突出显示功能。活跃的支持社区定期增强这个GUI工具。网格和数据导出为Excel、HTML、JSON、PHP文件。100%加密数据连接。缺点:不能跨平台使用(仅支持Windows)。问题频繁出现。没有程序语言调试器来简单地进行代码调试。6.TablePlus用于管理SQL和NoSQL数据库的原生GUI软件。TablePlus并不是开源的工具。优点:根据用户的反馈提供高性能和速度。高度可定制的用户界面:根本无需求助于Mojave。支持语法突出显示。快捷方式可以节省时间并提高效率。由于客户端-服务器连接的端到端加密,确保了更高级别的数据安全。缺点:当企业使用PostgreSQL以外的其他数据库时,经常出现用户体验不佳的问题。价格昂贵。而免费试用的功能进行严格限制。客户支持还有很多需要改进的地方。7.OmniDB这是一个简单的PostgreSQL开源GUI管理工具。优点:跨平台(获得Windows、Linux、macOS支持)。获得PostgreSQL、Oracle、MySQL、MariaDB支持。与某些替代品相比,响应速度快且更加轻巧。SQL自动完成功能。具有语法高亮显示功能。能够创建可定制的图表以显示相关的数据库指标。内置调试功能。缺点:如果同时使用多个数据库,则不是很适合。缺乏支持和学习文件。结语当企业选择GUI软件时,应该基于以下几个方面做出最终决定:团队规模。操作系统。数据库类型。计划使用的多个数据库。DBeaver、DataGrp和HeidiSQL更适合个人使用的数据库。由于具有GUI工具协作功能,Navicat是团队的最佳选择。除了支持Windows的HeidiSQL之外,几乎所有提到的工具都是跨平台的。pgAdmin以PostgreSQL为中心,作为PostgreSQL GUI工具的功能相当强大。但是采用可视化的内部工具构建器有UI Bakery。如果需要将多个不同的数据源集成在一起,那么这个低代码开发平台非常有用——无论是数据库、第三方工具还是API。而企业不必只局限在一个生态系统中。UI Bakery不是Postgres原生的。但是,它的数据可视化功能允许企业根据从PostgreSQL、MySQL、MS SQL Server、MongoDB、Redis、Salesforce和一系列其他数据库和应用程序中提取的数据,构建真正美观、易懂的图表、表格和图形。企业还可以使用预构建的用户界面(UI)组件和模板,避免从头开始构建,并节省更多的时间。如果企业不确定内部工具构建器适合自己的特定需求,可以继续进行尝试。整个GUI工具开发过程可能需要数小时的时间,有时甚至低至数分钟,具体取决于企业的开发经验。pgAdmin和其他经典的GUI软件似乎正在失去吸引力。Postgres和其他数据库管理的低代码方法使企业可以在更短的时间内获得更好的结果。原文标题:Top 8 PostgreSQL GUI Software in 2021,作者:Ilon Adams【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】
-
DLL load failed while importing win32gui: The specified modulewindows server 2008 运行Studio2.17失败,错误提示:[2022-01-21 10:31:56] robot finished with exception, exception details: DLL load failed while importing win32gui: The specified module could not be found.Traceback (most recent call last):File "./antrobot.py", line 121, in <module>File "./com/huawei/antrobot/framework/linux_adaptation/adapter.py", line 133, in runFile "./com/huawei/antrobot/framework/linux_adaptation/adapter.py", line 92, in releaseImportError: DLL load failed while importing win32api: The specified module could not be found.解决方法studio安装路径下\python\Lib\site-packages\pywin32_system32pythoncom39.dllpywintypes39.dll把这两个文件copy到如下路径即可C:\Windows\System32——————如果如下方法都无法解决,可以留言另外可以参考这个解决方法步骤 1 使用浏览器访问如下URL地址,下载并安装“Visual C++ Redistributable for Visual Studio 2015”。URL地址:https://www.microsoft.com/en-us/download/details.aspx?id=48145或者运行在安装目录里/Robot/cfg/cv/vc_redist.x64.exe步骤 2 根据PC机的操作系统版本,使用浏览器如下URL地址,下载并安装“KB3063858”操作系统补丁。 32-bit:https://www.microsoft.com/en-us/download/details.aspx?id=47409 64-bit:https://www.microsoft.com/en-us/download/details.aspx?id=47442步骤 3 重启PC,使得安装的软件或操作系统补丁生效。步骤 4 重新运行脚本,如上述步骤仍无法解决问题,请联系华为工程师进行定位分析和处理。还可以参考这个帖子https://bbs.huaweicloud.com/blogs/282058
-
【功能模块】编译GUI demo,使用了GUI组件【操作步骤&问题现象】1、在组件中配置了GUI组件、GUI demo2、芯片选择了STM32F769I3、编译的时候提示没有lvgl.h头文件,进去到组件中真的没有这个库,组件里面没有响应的库和代码【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
【功能模块】编译GUI demo,使用了GUI组件【操作步骤&问题现象】1、在组件中配置了GUI组件、GUI demo2、芯片选择了STM32F769I3、编译的时候提示没有lvgl.h头文件,进去到组件中真的没有这个库,组件里面没有响应的库和代码【截图信息】【日志信息】(可选,上传日志内容或者附件)
-
盘点本周AI科技热点热点一:参加这场大佬云集的开发者大会,还能抽RTX3060,请叫我「良心之心」2021 上半年,人工智能领域依然发展迅猛,业界先后出现了很多吸引广大开发者关注的大事件。大规模中文预训练语言模型迎来井喷式发展,数百亿、上千亿乃至上万亿参数的中文 NLP 模型纷纷出现,预示着基于大模型的 AI 时代的到来;鸿蒙手机系统正式发布,这一支持多设备、多硬件的操作系统向全球第三大操作系统发起挑战;MIPS 技术公司宣布放弃继续设计 MIPS 指令集,转向开源架构 RISC-V,由此彰显了该架构在全球芯片开发商中的崛起态势,并证明了开源的重要性。此外,类脑芯片和量子领域也出现了一些引人瞩目的研究成果和大事件,如复旦大学研制国内首款无线脑机接口芯片和清华大学成立量子信息班……所有这些都向开发者们展示着人工智能领域的欣欣向荣,但 AI 技术的研发与应用落地永不停歇,是一个持之以恒的课题。以大规模预训练语言模型为例,如何实现其商业化落地并创造价值更是值得探讨的话题。其他领域亦是如此。原文链接:https://www.jiqizhixin.com/articles/2021-06-25-9 热点二:年轻的LeCun、吴恩达长啥样?升级版StyleGAN告诉你来自以色列特拉维夫大学的研究者在生成图像方面又有了新的升级,所用方法在保留源图像身份的同时,在细节编辑上实现了更精细的效果。英伟达提出的风格迁移模型 StyleGAN 系列,一直是人们用来进行各类脑洞画图实验的流行工具。从生成二次元「老婆」,照片修图,到人物的卡通化,最近几年基于这种技术的应用不一而足。然而 StyleGAN 也一直有着自己的不足,近年来的各种改进也未能让其在反向生成、细节编辑上实现精细的效果。什么时候我们才能让 AI 随心所欲地修改真人照片呢?今年 6 月,这样的研究终于出现了。在以色列特拉维夫大学的论文《Pivotal Tuning for Latent-based Editing of Real Images》中,研究人员通过添加一个简短的训练过程,让 StyleGAN 有了焕然一新的升级。原文链接:https://www.jiqizhixin.com/articles/2021-06-25-7 热点三:让机器学习设计手机GUI,这合理么?图形用户界面(Graphical UserInterface,简称 GUI,又称图形用户接口),为用户和计算机桌面程序,手机类移动端软件,和在线网站提供了可视化的交互方式。设计优秀的GUI颜值在线且简洁易用,吸引大量忠实用户。但即便对经验丰富的GUI设计者,新App与GUI的创作过程也是非常困难且耗费时间的,例如交互流畅、通用、简洁、美观、风格连贯等与设计相关的规则和标准是设计者们需要遵循的。而且为了紧跟时代潮流,不断从其他的资源(如Dribbble)寻找最新最热的设计来获取灵感也为设计者们带来了额外巨大的工作量。然而,GUI设计人员在公司中的人数有限,如此复杂繁重的GUI设计工作经常需要程序开发人员“顶上”。在一项对超过5700名软件开发者的调查中,51%的人反映他们从事App的GUI设计任务要比其他开发工作多,且每过几天就要参与。但是软件程序开发者往往缺少足够的专业设计经验与设计审美。所以经常在刚刚起步或小规模的公司中,缺少GUI设计经验的程序开发者们要通过参考网上现有的GUI来规划实现他们自己的GUI设计。原文链接:https://www.jiqizhixin.com/articles/2021-06-23-9 热点四:RNN 用于生物医学全息成像,速度加快50倍数字全息成像是生物医学成像中常用的显微镜技术。用于揭示样本的丰富光学信息。常见的图像传感器只对入射光的强度做出响应。因此,重建涉及光学相位检索的传感器以数字方式记录的全息图的完整 3D 信息,一直是数字全息术中一项耗时且计算量大的挑战性任务。近日,加州大学洛杉矶分校的研究团队开发出一种新的全息相位检索技术,可以快速重建样品的微观图像,与现有方法相比,可加速50倍。研究结果首次证明了使用循环神经网络(RNN)进行全息成像和相位恢复,所提出的框架将广泛适用于各种相干成像模式。该研究于5月26 日以「使用循环神经网络进行相位恢复和自动对焦的全息图像重建」(Holographic Image Reconstruction with Phase Recovery and Autofocusing Using Recurrent Neural Networks )为题发表在《ACS光子学》(ACS Photonics)杂志上。原文链接:https://www.jiqizhixin.com/articles/2021-06-23-2 热点五:CVPR 2021奖项出炉:最佳论文花落马普所,何恺明获提名,首届黄煦涛纪念奖颁布昨晚,CVPR 2021 公布了最佳论文、最佳学生论文等奖项。德国马普所和蒂宾根大学的研究者获得了最佳论文奖,加州理工和西北大学的研究者获得最佳学生论文奖。此外,FAIR 包括何恺明在内的两位华人学者获得最佳论文提名,而另一位华人学者、华盛顿大学计算机系硕士研究生林山川(Shanchuan Lin)获得了最佳学生论文提名。6 月 19 日,CVPR 2021 在线上拉开帷幕。今年,大会一共接收了 7039 篇有效投稿,其中进入 Decision Making 阶段的共有约 5900 篇,最终有 1366 篇被接收为 poster,295 篇被接收为 oral,其中录用率大致为 23.6%,略高于去年的 22.1%。原文链接:https://www.jiqizhixin.com/articles/2021-06-22-8 热点六:DNA逻辑电路可能是医学的未来,这个程序降低了DNA逻辑电路的设计门槛传统电子元件制造的雕刻技术将很快达到物理极限。计算机应对不同医学检测环境时,需要环境组件与计算组件进行连接,包括化学组件、生物组件。所以科学家亟须研发新的计算技术。于是,科学家提出了一个巧妙的想法,以合成的脱氧核糖核酸(DNA)分子作为底物来存储信息,并利用DNA的分子扩增机制完成计算。然而,DNA计算项目的实际运算过程相当复杂;我们需要新工具在高层次结构上描述它们,并利用当前的计算资源设计替代方案。南马托格罗索联邦大学(UFMS)计算学院的Renan Marks团队研发了一种名为DNAr-Logic 的算法系统。DNAr-Logic能够模拟概括,具有数百个反应的复杂数字逻辑电路。用户设计数字逻辑电路时,无需手动编写所有化学反应网络(Chemical reaction network ,CRN),更无需深入了解化学反应网络和 DNA 链置换技术(DNA Strand Displacement ,DSD)。原文链接:https://www.jiqizhixin.com/articles/2021-06-22-2 热点七:AI专用领域之一:声音、相机陷阱用于野生动物研究和保护近年来,生物多样性危机,即世界范围内的物种损失和生态系统的破坏问题,正在全球范围内持续加速,生物多样性正在迅速减少。例如,许多物种如老虎和犀牛,由于非法采伐(即偷猎)而面临灭绝的危险。研究动物的分布、运动和行为对解决环境挑战至关重要,如疾病的传播、入侵物种、气候和土地使用的变化等等。因此,迫切需要部署可扩展和具有成本效益的监测技术,以更好地模拟和了解野生动物及其居住的环境。随着人工智能的快速发展,人工智能技术也被引入到野生动物研究和保护中。哈佛大学、谷歌、英特尔、DeepMind,以及国内的快手、阿里等等众多研究机构、企业,甚至包括一些政府机构,都已经投入到了这项工作中,且研发和部署了相应的产品。我们在这篇文章中以两篇文章为基础,分别讨论了声音、相机陷阱(camera traps)是如何实现野生动物监测的。此外,最后一篇文章还讨论了如何利用人工智能技术辅助解决野生动物偷猎(wildlife poaching)的问题,即对偷猎者轨迹的预测问题。原文链接:https://www.jiqizhixin.com/articles/2021-06-20-3 总结:每周七个小热点,帮助各位了解最新科技资讯。欢迎大家阅览评论哦,也可以留言发表您的关注点,我们根据大家的关注点,推送更多您喜欢的资讯。
-
一:什么是PWM脉宽调制技术,全称为脉冲宽度调制,英文名称为Pulse Width Modulation,简写为PWM,是一种利用微处理器来完成对模拟电路控制的一种技术,其具有操作简单、灵活性好、反应速度快等诸多特点,现已在通信、测量、功率变换、功率控制等多个方面都得到了广泛的应用原理:脉宽调制技术是通过对逆变电路开关的通断控制来实现对模拟电路的控制的。脉宽调制技术的输出波形是一系列大小相等的脉冲,用于替代所需要的波形,以正弦波为例,也就是使这一系列脉冲的等值电压为正弦波,并且输出脉冲尽量平滑且具有较少的低次谐波。根据不同的需求,可以对各脉冲的宽度进行相应的调整,以改变输出电压或输出频率等值,进而达到对模拟电路的控制二:频率和占空比频率:物质在1秒内完成周期性变化的次数叫做频率单位:HZ单位换算:1kHz=1000Hz,1MHz=1000000Hz 1GHz=1000MHz周期:频率的倒数占空比:是指在一个脉冲循环内,通电时间相对于总时间所占的比例如图:一个周期内高电平所占的比就是占空比三:单片机怎么产生PWM调制对于51单片机而言,用定时器来产生pwm思路如下:我们知道单片机的时钟脉冲,那怎么利用PWM呢,其实就是通过定时器来改变一个周期内高低电平的占空比来实现PWM的调制.例如我们把电平置为高电平持续1ms,然后然低电平持续3毫秒,这样就能产生一个周期为4毫秒的占空比为25%的PWM波了.单片机的晶振是11.092MHZ方便计算取12MHZ.那么他的时钟周期就是1/12us,机器周期就是1us. 那么如何用定时器怎么来产生.对于定时器0来说定时器是16位的寄存器,来一个脉冲加一,加到65535时就会溢出.所以定时器0从0开始计数到65535,一个微妙就记一次数.那么要溢出则需要65535us.举个例子:我们要产生一个100HZ的PWM且占空比25%,用定时器怎么完成.首先100HZ的周期是10ms.也就是持续一个周期的时间是10ms,那么占空比25%就让高电平持续2.5个ms,低电平持续7.5个ms我们让定时器0产生10ms需要计数10000,因为定时器每计数一次是1us,那么10000次就是10ms,我们把这10000分成100分,就相当与把一个脉冲周期分成100分而占空比25%即高电平占了25份,低电平就占了75份.那么定时器就要每100us就要中断一下那么中断要装的初值就是TH0=(65535-100)/256,TL0=(65535-100)%256.然后在中断里面设置一个变量在这个变量在0到25期间是高电平否则是低电平就可以实现题目要求.4:pwm的应用通过按键改变小灯的亮度呼吸灯电机调速这里先介绍以下第一个:#include<reg52.h>#define uchar unsigned char#define uint unsigned intsbit L1=P0^0;sbit S7=P3^0;uchar aa=0;pwm=0;cont=0;void delay(uint tt){ while(tt--);}void HC_138(){ P2=(P2&0X1F)|0X80;}void scankey(){ if(S7==0) { delay(100); if(S7==0) { switch(cont) { case 0: L1=0; TR0=1;pwm=10; cont=1;break; case 1: pwm=50; cont=2;break; case 2: pwm=90; cont=3;break; case 3: L1=1;TR0=0;cont=0;break; } while(S7==0); } }}void INIT_t0(){ TMOD=0X01; TH0=(65535-100)/256; TL0=(65535-100)%256; ET0=1; EA=1; }void servicet0() interrupt 1{ TH0=(65535-100)/256; TL0=(65535-100)%256; aa++; if(aa==pwm) { L1=1; } else if(aa==100) { L1=0; aa=0; }}void init(){ P2=(P2&0X1F)|0Xa0; P0=0x00;}void main(){ init(); HC_138(); P0=0XFF; INIT_t0(); while(1) { scankey(); }}
-
第09课 GUI-对话框================================================简介: JDialog类创建的对话框必须要依赖于某个窗口。 对话框分为无模式和有模式两种。 有模式的对话框:当这个对话框处于激活状态时,只让程序响应对话框内部的事件, 而且将堵塞其它线程的执行,用户不能再激活对话框所在程序中的其它窗口,直到该对话框消失不可见。 无模式对话框:当这个对话框处于激活状态时,能再激活其它窗口,也不堵塞其它线程的执行。 ------------------------------------------------------------------------------消息对话框----------------------------------------------javax.swing包中的JOptionPane类的静态方法: public static void showMessageDialog( Component parentComponent, String message, String title, int messageType)------------------------------------------------------------输入对话框-------------------------------------------可以用javax.swing包中的JOptionPane类的静态方法: public static String showInputDialog(Component parentComponent, Object message, String title, int messageType)-----------------------------------------------------------------------------------------确认对话框---------------------------------------------------------------------可以用javax.swing包中的JOptionPane类的静态方法:public static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType)------------------------------------------------------------颜色对话框-----------------------------------------------------------------------------可以用javax.swing包中的JColorChooser类的静态方法: public static Color showDialog(Component component, String title, Color initialColor)----------------------------------------------------------------------------------------------------自定义对话框--------------------------------------------------------------------------创建对话框与创建窗口类似,通过建立JDialog的子类来建立一个对话框类,然后这个类的一个实例,即这个子类创建的一个对象,就是一个对话框。对话框是一个容器,它的默认布局是BorderLayout,对话框可以添加组件,实现与用户的交互操作。 ---------------------------------------------------------------------------构造对话框的2个常用构造方法JDialog() 构造一个无标题的初始不可见的对话框,对话框依赖一个默认的不可见的窗口,该窗口由Java运行环境提供。JDialog(JFrame owner) 构造一个无标题的初始不可见的无模式的对话框,owner是对话框所依赖的窗口,如果owner取null,对话框依赖一个默认的不可见的窗口,该窗口由Java运行环境提供。
-
主要内容: ◆ Java事件处理机制 ◆ 常见事件及处理接口 ◆ Java绘图机制▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Java事件处理机制===============================================简介: Java事件处理机制是一种基于授权的事件处理机制。-----------------------------------------------------------------------------------------------------------------------事件处理中的主要概念: 事件源,能够产生事件的对象都可以成为事件源 . 监视器,负责监视事件源的对象,当事件源产生事件时,监视器将调用方法对事件进行处理。 事件源通过调用相应的方法将某个对象注册为自己的监视器。 对于文本框,这个方法是:addActionListener(监视器); java语言中监视器都是使用接口来实现的。 事件源注册监视器之后,相应的操作就会导致相应的事件的发生,并通知监视器,监视器就会作出相应的处理。 事件处理接口,接口中定义了处理事件的方法。 监视器负责处理事件源发生的事件。 监视器是一个对象,为了处理事件源发生的事件,监视器这个对象会自动调用接口中一个方法来处理事件。 Java规定:为了让监视器这个对象能对事件源发生的事件进行处理, 创建该监视器对象的类必须声明实现相应的接口,那么当事件源发生事件时,监视器就自动调用被类重写的某个接口方法。----------------------------------------------------------------------------------------------------------------------------事件处理步骤:1.写一个监视器类 class Pliceman implements XXXListener { 接口方法(XXXEvent e) { //....... } }2.创建事件源、监视器对象3.事件源注册监视器 事件源.addXXXListener(监视器);---------------------------------------------常见事件及处理接口=========================================================ActionEvent事件 ***------------------------------------------------------------------------------------- 1 ActionEvent事件源 : 文本框、按纽、菜单项、密码框和单选按纽都可以触发ActionEvent事件,即都可以成为ActionEvent事件的事件源。 2.注册监视器: ActionListener接口作为监视器 事件源.addActionListener(ActionListener listen) 将实现ActionListener接口的类的实例注册为事件源的监视器。 3.ActionListener接口:重写接口中的方法作为事件处理。 ActionListener接口在java.awt.event包中,该接口中只有一个方法: public void actionPerformed(ActinEvent e) ; 事件源触发ActionEvent事件后,监视器将发现触发的ActionEvent事件,然后调用接口中的这个方法对发生的事件作出处理。 ActionEvent类事先创建的事件对象就会传递给该方法的参数e。 4.ActionEvent类中的方法:下面的方法能够获取发生动作的事件源。 public Object getSource() --------- 调用该方法可以获取发生ActionEvent事件的事件源对象的引用。 public String getActionCommand() --------- 调用该方法可以获取发生ActionEvent事件时,和该事件相关的一个命令字符串。------------------------------------------------------------------------------------------------------------------------------带事件处理的GUI程序总结:通常有3各类:监视器类public class Pliceman implements ActionListener { //取数组的组件 //显示数据的组件 //设置取数据组件的方法 //设置显示数据组件的方法 //接口中的方法 public void actionPerformed(ActionEvent e) { //取得数据 //处理数据 //显示数据 }}窗口类pulic MainFrame extends JFrame { //组件 //监视器 public MainFrame() { init(); initEvent(); //设置窗口的属性 } void init() { //设置布局 //创建组件 //添加组件 } void initEvent() { //创建监视器 //设置监视器的属性 //事件源注册监视器 }}主类--------------------------------------------------------ItemEvent事件-----------------------------------------------------------------------------------------------------------1.ItemEvent事件源 : 选择框、下拉列表都可以触发ItemEvent事件。2.注册监视器:ItemListener接口作为监视器 事件源.addItemListener(ItemListener listen) 将实现ItemListener接口的类的实例注册为事件源的监视器。 3.ItemListener接口:重写方法作为事件处理 ItemListener接口在java.awt.event包中,该接口中只有一个方法 public void itemStateChanged(ItemEvent e) 事件源触发ItemEvent事件后,监视器将发现触发的ItemEvent事件, 然后调用接口中的itemStateChanged(ItemEvent e)方法对发生的事件作出处理。 ItemEvent类事先创建的事件对象就会传递给该方法的参数e。 4.ItemEvent类中的方法 : getSource()方法返回发生Itemevent事件的事件源外 getItemSelectable()方法返回发生Itemevent事件的事件源。 ---------------------------------------------------------------------------------------------------DocumentEvent事件---------------------------------------------------------------------------------------------------------------------1. DocumentEvent事件源 : 文本区所维护的文档能触发DocumentEvent事件 2. 注册监视器:DocumentListener作为监视器 事件源.addDucumentListener(DocumentListener listen) 将实现DocumentListener接口的类的实例注册为事件源的监视器。 3. DocumentListener接口:重写接口中的方法作为事件处理 DocumentListener接口在javax.swing.event包中,该接口中有三个方法: public void changedUpdate(DocumentEvent e) public void removeUpdate(DocumentEvent e) public void insertUpdate(DocumentEvent e)----------------------------------------------------------------------------------------------------------------------MouseEvent事件_1-----------------------------------------------------------------------------------------------------------------------1. 事件源: 任何组件上都可以发生鼠标事件,如:鼠标进入组件、退出组件、在组件上方单击鼠标、拖动鼠标等都触发鼠标事件, 即导致MouseEvent类自动创建一个事件对象。 2.使用MouseListener接口可以处理以下5种操作触发的鼠标事件 事件源.addMouseListener(MouseListener listener)。 在事件源上按下鼠标键、在事件源上释放鼠标键、在事件源上击鼠标键、鼠标进入事件源、鼠标退出事件源。 注册监视器:MouseListener接口作为监视器3. MouseListener接口中有如下方法:重写方法作为事件源的处理 mousePressed(MouseEvent) 负责处理在组件上按下鼠标键触发的鼠标事件 mouseReleased(MouseEvent) 负责处理在组件上释放鼠标键触发的鼠标事件 mouseEntered(MouseEvent) 负责处理鼠标进入组件触发的鼠标事件 mouseExited(MouseEvent) 负责处理鼠标离开组件触发的鼠标事件 mouseClicked(MouseEvent) 负责处理在组件上单击鼠标键触发的鼠标事件4. MouseEvent 中有下列几个重要的方法: getX() 获取鼠标指针在事件源坐标系中的x-坐标。 getY() 获取鼠标指针在事件源坐标系中的y-坐标。 getModifiers() 获取鼠标的左键或右键。 getClickCount() 获取鼠标被单击的次数。 getSource() 获取发生鼠标事件的事件源。5. MouseMotionListener接口 可以处理以下两种操作触发的鼠标事件, 在事件源上拖动鼠标、在事件源上移动鼠标。 事件源注册监视器的方法是: 事件源.addMouseMotionListener(MouseMotionListener listener)。 MouseMotionListener接口中有如下方法: mouseDragged(MouseEvent) 负责处理拖动鼠标触发的鼠标事件。 mouseMoved(MouseEvent) 负责处理移动鼠标触发的鼠标事件。------------------------------------------------------------------------------------------------------------------------------焦点事件-----------------------------------------------------------------------------------------------1.焦点事件源 :组件可以触发焦点事件。 2.注册监视器:组件可以使用 addFocusListener(FocusListener listener) 注册焦点事件监视器。 3.FocusListener接口 : 创建监视器的类必须要实现FocusListener接口,该接口有两个方法: public void focusGained(FocusEvent e) public void focusLost(FocusEvent e) 当发生FocusEvent事件时,监视器调用类实现的接口中的相应方法。 4.组件也可调用 public boolean requestFocusInWindow() 方法可以获得输入焦点。 --------------------------------------------------------------------------------------------------------------键盘事件--------------------------------------------------------------------------------------------------1. 事件源: 当一个组件处于激活状态时,敲击键盘上一个键就导致这个组件触发键盘事件。2.某个组件使用addKeyListener方法注册监视器。3.接口KeyListener中有如下方法: public void keyPressed(KeyEvent e), public void keyTyped(KeyEvent e), public void KeyReleased(KeyEvent e) 4.(KeyEvent 相关方法: public int getKeyCode()判断哪个键被按下、敲击或释放,返回一个键码值 。 getKeyChar()判断哪个键被按下、敲击或释放,返回键上的字符。-------------------------------------------------------------------------------------------------------窗口事件-------------------------------------------------------------------------------------WindowListener接口. (1) public void windowActivated(WindowEvent e) 当窗口从非激活状态到激活时,窗口的监视器调用该方法。(2) public void windowDeactivated(WindowEvent e) 当窗口激活状态到非激活状态时,窗口的监视器调用该方法。(3) public void windowClosing(WindowEvent e) 当窗口正在被关闭时,窗口的监视器调用该方法。(4) public void windowClosed(WindowEvent e) 当窗口关闭后,窗口的监视器调用该方法。(5) public void windowIconified(WindowEvent e) 当窗口图标化时,窗口的监视器调用该方法。(6) public void windowDeiconified(WindowEvent e) 当窗口撤消图标化时,窗口的监视器调用该方法。(7) public void windowOpened(WindowEvent e) 当窗口打开时,窗口的监视器调用该方法。--------------------------------------------------------------------------------------------------------适配器------------------------------------------------------------------------------------------------------------------------每个具有不止一个方法的AWT监听器接口都有一个实现了它的所有方法,但却不做任何工作的适配器类。适配器可以代替接口来处理事件,当Java提供处理事件的接口中多于一个方法时,Java相应地就提供一个适配器类,比如WindowAdapter类。适配器已经实现了相应的接口,例如WindowAdapter类实现了WindowListener接口。因此,可以使用WindowAdapte的子类创建的对象做监视器,在子类中重写所需要的接口方法即可。 WindowAdapter适配器 .ComponentAdapterContainerAdapterFocusAdapterKeyAdapterMouseAdapterMouseMotio**pterWindowAdapter===========================================匿名类实例或窗口做监视器匿名类的方便之处是匿名类的外嵌类的成员变量在匿名类中仍然有效,当发生事件时,监视器就比较容易操作事件源所在的外嵌类中的成员.当事件的处理比较简单、系统也不复杂时,使用匿名类做监视器是一个不错的选择。让事件源所在的类的实例作为监视器,能让事件的处理比较方便,这是因为,监视器可以方便的操作事件源所在的类中的其他成员。当事件的处理比较简单,系统也不复杂时,让事件源所在的类的实例作为监视器是一个不错的选择。===================================事件总结-------------------------------------------------------------------------------------------------------1.授权模式 Java的事件处理是基于授权模式,即事件源调用调用方法将某个对象注册为自己的监视器。2.接口回调 addXXXListener(XXXListener listener)方法中的参数是一个接口,listener可以引用任何实现了该接口的类所创建的对象, 当事件源发生事件时,接口listener立刻回调被类实现的接口中的某个方法。3.方法绑定 当事件源触发事件发生后,监视器准确知道去调用哪个方法。4.保持松耦合 当事件源触发事件发生后,系统知道某个方法会被执行,但无须关心到底是哪个对象去调用了这个方法, 因为任何实现接口的类的实例(做为监视器)都可以调用这个方法来处理事件。 ====================================================================================================Java绘图机制---------------------------------------------------------------Component类有一个方法public void paint(Graphics g),程序可以在其子类中重写这个方法。当程序运行时,java运行环境会用Graphicd2D(Graphics的一个子类)将参数g实例化,对象g就可以在重写paint方法的组件上内绘制图形、图像等 .Graphics2D的“画笔”分别使用draw和fill方法来绘制和填充一个图形。组件都是矩形形状,组件本身有一个默认的坐标系,组件的左上角的坐标值是(0,0)。java.awt.geom直线 Line2D.Double(double x1, double y1, double x2, double y2)矩形Rectangle2D.Double(double x, double y, double w, double h)圆角矩形RoundRectangle2D.Double(double x, double y, double w, double h);椭圆Ellipse2D.Double(double x, double y double w, double h);圆弧Arc2D.Double(double x, double y, double w, double h, double start, double extent, int type) x, y, w, h-----------椭圆位置大小 start, -------------圆弧其实位置,单位:度 extent-----------划过的度的弧。单位:度。正值:逆时针;负值:顺时针。 type ---------------Arc2D.OPEN Arc2D.CHORD Arc2D.PIE文本 g.drawString(s, x, y)二次曲线三次曲线多边形-----------------------------------------------------有时需要平移、缩放或旋转一个图形。可以使用AffineTransform类来实现对图形的这些操作。例如: AffineTransform trans=new AffineTransform(); 将图形沿顺时针或逆时针以(x,y)为轴点旋转number个弧度 :trans.rotate(60.0*3.1415927/180,100,100); translate(double a, double b) -----------------------移动 scale(double a, double b) ------------放缩rotate(double number, double x, double y)-------------------------旋转 x,y --------旋转圆心 number--------旋转弧度g_2d.setTransform(trans);-------------------------------------------------两个图形进行布尔运算运算之前,必须分别用这两个图形创建两个Area区域对象,例如: Area a1 = new Area(T1); Area a2 = new Area(T2);a1就是图形T1所围成的区域;a2就是T2所围成的区域。那么,a1调用add方法: a1.add(a2); ------------- 或之后,a1就变成a1和a2经过布尔“或”运算后的图形区域。可以用Graphics2D对象g来绘制或填充一个Area对象(区域): g.draw(a1); g.fill(a1); a1.intersect(a2); ---------与 a1.exclusiveOr(a2) ----------------异或 a1.subtract(a2) ------------差。---------------------------------------------------1.加载图像组件调用getToolkit()方法可以返回这个对象的引用。Tollkit类的对象调用方法 Image getImage(String fileNme)或 Image getImage(File file)。可以返回一个Image对象,该对象封装着参数file(或参数fileName)指定的图像文件。2.绘制图像 public boolean drawImage(Image img,int x,int y,ImageObserver observer); 参数img是被绘制的Image对象,x、y是要绘制指定图像的矩形的左上角所处的位置,observer是加载图像时的图像观察器 。-----------------------------------------------------------
-
Graphical User Interface,简称 GUI,又称图形化用户接口,所谓的GUI编程,指的是用户不需要输入代码指令,只通过图形界面的交互就可以操作软件功能。Tkinter一个轻量级的跨平台图形用户界面(GUI)开发工具,是Python的自带的官方标准库,安装Python 之后直接导入就可以使用, 我们常见的python IDLE就是使用TKinter实现。它最大的特点就是上手简单, 做个简单的小工具基本够用了,比如登录,计算器,简单的输入输出工具等,缺点是不够美观,功能太单一。PyqtQT是一个C ++编写的跨平台的框架。这是一个非常全面的库。1991年奇趣科技公司开发的C++ GUI应用程序,2014年发布了Qt5.3正式版。PyQt是Qt库的Python版本,它有超过300类,将近6000个函数和方法。它是一个多平台的工具包,可以运行在所有主要操作系统上,包括UNIX,Windows和Mac。PyQt采用双许可证,开发人员可以选择GPL和商业许可。在此之前,GPL的版本只能用在Unix上,从PyQt的版本4开始,GPL许可证可用于所有支持的平台。QT带来的最大好处就是它有一个QT Desiginer,这个设计器可以方便我们进行页面的布局,可以说在Tkinter里面需要一坨坨的代码完成的页面布局,在QT里面只要拖一拖控件就搞定了。wxPythonwxPython 是一款开源软件,是 Python 语言的一套优秀的 GUI 图形库,允许 Python 程序员很方便的创建完整的、功能健全的 GUI 用户界面。wxPython开源免费,支持LINUX和WINDOWS,界面本地化好, 功能完善,它也提供类似QT Designer的设计器wxFormbuilder,可以说是个压缩版的QT。PySide使用“C++”语言编写的应用程序/用户界面(UI)框架。“PySide”是“Qt”的封装。与PySide的不同之处在于PyQt可以商用。PyGUI减少Python应用与平台底层GUI之间的代码量,面向Unix,Macintosh和Windows平台。Kivy基于OpenGL ES 2,能够让使用相同源代码创建的程序能跨平台运行。这个框架是事件驱动的,基于主循环,非常适合开发游戏。Kivy拥有能够处理动画、缓存、手势、绘图等功能。它还内置许多用户界面控件如:按纽、摄影机、表格、Slider和树形控件等。PyGTK -GTK在Linux中常用的“GTK+”是“PyGTK”的“GTK +”封装。与Kivy和PyQt相比,它能不加修改的稳定运行在各种操作系统之上,如Linux,Windows,MacOS等。Flexx许多Python GUI库基于以其他语言编写的库,例如用C++编写的库有wxWidgets和libavg。但Flexx是用Python创建的,使用Web技术,也正因为如此 Flexx 是跨平台的。只需要有 Python 和浏览器就可以运行CEF Python基于Google Chromium,面向Windows,MAC OS和Linux,其主要用于在第三方应用程序中嵌入式浏览器的使用上Dabo一个跨平台的应用程序开发框架,基于wxpython的再封装库。它提供数据库访问,商业逻辑以及用户界面Pyforms一个用于开发GUI应用程序的Python 2.7 / 3.x跨环境运行框架。PyGObject可以为GNOME项目编写Python应用程序,你也可以使用GTK +编写Python应用程序。关于框架的选择,没有最好的,只有最适合的。其他框架的具体使用和介绍,可以查看官方文档学习:https://wiki.python.org/moin/GuiProgramming
-
需要用到的库:tkinter:构建gui界面pyperclip:复制功能random:生成随机数string:处理字符串代码:12345678910111213141516171819202122232425262728293031323334353637383940414243from tkinter import *import random, stringimport pyperclip root =Tk()root.geometry("400x400")root.resizable(0,0)root.title("密码生成器") heading = Label(root, text = '密码' , font ='arial 15 bold').pack() pass_label = Label(root, text = '密码长度', font = 'arial 10 bold').pack()pass_len = IntVar()length = Spinbox(root, from_ = 8, to_ = 32 , textvariable = pass_len , width = 15).pack() pass_str = StringVar() def Generator(): password = '' for x in range (0,4): password = random.choice(string.ascii_uppercase)+random.choice(string.ascii_lowercase)+random.choice(string.digits)+random.choice(string.punctuation) for y in range(pass_len.get()- 4): password = password+random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits + string.punctuation) pass_str.set(password) Button(root, text = "获取密码" , command = Generator ).pack(pady= 5) Entry(root , textvariable = pass_str).pack() def Copy_password(): pyperclip.copy(pass_str.get()) Button(root, text = '复制密码', command = Copy_password).pack(pady=5) root.mainloop()运行效果:
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签