访问property时有无self.的区别

本文探讨了在Objective-C中使用self.与直接访问属性的区别。通过实例说明了不同访问方式对内存管理和程序稳定性的影响。

对property的访问,是否加“self.”是有很大区别的,看了以下内容就一目了然了。

内容摘自:http://stackoverflow.com/questions/1295702/setting-an-objective-c-class-property-without-using-a-self-reference

 

Q:

This morning I ran into a crash in an iPhone app I am working on and while I fixed the bug, I'm curious about syntactic reason it was a problem.

Here is my code reduced to simple elements. I am populating items in a TableView using an NSArray for the items. The NSArray is a property:

@interface FooViewController : UITableViewController {
   
NSArray *stuff;
}

@property (nonatomic, retain) NSArray *stuff;

And in my implementation file:

@synthesize stuff;

- (void)viewDidLoad {    
   
NSArray *arr = [[NSArray alloc] initWithObjects:@"", @"Item 1", @"Item 2",  
                                       
@"Lorem", @"Ipsum", nil];
    self
.stuff = arr;

   
[arr release];
}

Now, when I first wrote the method, I accidentally left off the "self." and that caused the bomb. Although while testing, it worked at first blush. I'd tried:

stuff = arr;
NSLog(@"%d", [stuff count]);

But using stuff in other methods bombed. Now that I've fixed the problem, I can use [stuff count] in other places.

So why can I use stuff in some places but in others I must use self.stuff?

 

A1:

This would have also worked properly:

- (void)viewDidLoad {     
    stuff
= [[NSArray alloc] initWithObjects:@"", @"Item 1", @"Item 2",  
                                       
@"Lorem", @"Ipsum", nil];
}

Because the array was retained by alloc. But it's usually better to stick to the dot notation if you have a property and use the autorelease array creation methods, where you get retains "for free" from the property:

- (void)viewDidLoad {     
   
NSArray *arr = [NSArray arrayWithObjects:@"", @"Item 1", @"Item 2",  
                                       
@"Lorem", @"Ipsum", nil];
    self
.stuff = arr;

}

You probably just left it out to keep things simple, but you also need to free this array in dealloc:

- (void)dealloc {     
 
[stuff release]; stuff = nil;
}
A2:

When you use (self) and dot syntax, given the way you defined the property (nonatomic, retain), the NSArray (stuff) is retained.

When you don't, you are still making the assignment, but you aren't retaining the array aside from the implicit retain via alloc+init - and you release it right away.

You can get around assigning via "self.stuff = arr" by doing:

stuff = [arr retain];

But since you defined a property, you obviously WANT to be using dot syntax and having the retain called for you.

A3:

stuff = ... directly references the backing field of the property. It doesn't increase the retain count. As a result, releasing the object somewhere else might take its retain count down to zero and have it deallocated while you're still holding a reference to it. Also, it may cause a memory leak for the previous value of the property.
The reason it looks like working sometimes is that the object is probably not deallocated yet, by someone else.

On the other hand, self.stuff = ... will send a message to the property's set accessor which will take care of retain count.

A4:

The difference between doing:

stuff=arr;

and

self.stuff=arr;

is that in the second case, you're actually calling the automatically-synthesized setStuff: accessor method, which retains the array. In the code you've posted, the array is being created with alloc/initWithObjects, so it already has a retain count of 1.

You just need to change remove the call to [arr release] in your viewDidLoad: method, and all will be well:

- (void)viewDidLoad {     
   
NSArray *arr = [[NSArray alloc] initWithObjects:@"", @"Item 1", @"Item 2",  
                                       
@"Lorem", @"Ipsum", nil];
    stuff
= arr;
}

As you noticed, you can also "fix" this by using self.stuff. I'd recommend against doing that, since it obscures the meaning of the code, and adds additional work that's not needed in most cases. In general, I recommend not using the "self." syntax in your instance methods.

import sqlite3 from dataclasses import dataclass from datetime import datetime @dataclass class Employee: id: str # 工号 name: str # 姓名 avatar_path: str # 头像路径 register_time: str # 注册时间 class EmployeeDB: def __init__(self, db_path="employees.db"): self.conn = sqlite3.connect(db_path) self._create_table() def _create_table(self): cursor = self.conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS employees ( id TEXT PRIMARY KEY, name TEXT NOT NULL, avatar_path TEXT NOT NULL, register_time TEXT NOT NULL ) ''') self.conn.commit() def add_employee(self, employee: Employee): cursor = self.conn.cursor() cursor.execute(''' INSERT INTO employees VALUES (?, ?, ?, ?) ''', (employee.id, employee.name, employee.avatar_path, employee.register_time)) self.conn.commit() def get_employee(self, employee_id: str) -> Employee: cursor = self.conn.cursor() cursor.execute('SELECT * FROM employees WHERE id = ?', (employee_id,)) result = cursor.fetchone() if result: return Employee(*result) return None def get_all_employees(self) -> list[Employee]: cursor = self.conn.cursor() cursor.execute('SELECT * FROM employees') return [Employee(*row) for row in cursor.fetchall()] def close(self): self.conn.close()——这个是前端目前已确定好的初步框架,目前已确定前端和后端会在同一台主机上运行,不需要再进行ping是否连通等测试,以这个框架为基础,对原来的前端部分的module进行修改,同样写成一个个函数并封装成一个module供外部调用,输入包括其他模块给的姓名、工号、图片、打卡事件会触发的打卡时间,功能包括则增加员工信息、删除员工信息、更新员工信息、查询某员工信息(返回包括该员工姓名、工号、图片有无、status、record_count,最好作为可以外部引用的变量>>可以为查询专门定义数组存储查询后的个人信息)、查询所有员工信息、获取员工图像(返回base64编码),以下为原本的前端数据库模块:mport requests import base64 import os from datetime import datetime class DatabaseService: def __init__(self, base_url="http://10.160.64.218:5606"): """ 初始化数据库服务 :param base_url: 后端API基础URL """ self.base_url = base_url # def _request(self, endpoint, method, payload=None, files=None): # """ # 通用请求方法 # :param endpoint: API端点 (如 '/employee/add') # :param method: HTTP方法 ('GET', 'POST', 'DELETE') # :param payload: JSON格式的请求数据 # :param files: 文件上传数据 # :return: 响应数据字典 # """ # url = f"{self.base_url}{endpoint}" # headers = {'Content-Type': 'application/json'} # try: # if method == 'GET': # response = requests.get(url, params=payload) # elif method == 'POST': # response = requests.post(url, json=payload, files=files, headers=headers) # elif method == 'DELETE': # response = requests.delete(url, json=payload) # else: # raise ValueError(f"不支持的HTTP方法: {method}") # response.raise_for_status() # 检查HTTP错误 # return response.json() # except requests.exceptions.HTTPError as e: # error_msg = f"HTTP错误 ({e.response.status_code}): {e.response.text}" # print(f"请求失败: {url}\n{error_msg}") # return {"code": e.response.status_code, "msg": error_msg, "data": None} # except Exception as e: # error_msg = f"请求异常: {str(e)}" # print(f" 请求失败: {url}\n{error_msg}") # return {"code": 500, "msg": error_msg, "data": None} # ------------------ 员工管理接口 ------------------ def add_employee(self, data, image_path=None): """ 添加员工 :param data: 员工数据字典 {emp_id, name} :param image_path: 员工照片路径 (可选) :return: 操作结果 """ payload = data.copy() # 处理图像上传 files = None if image_path and os.path.exists(image_path): try: with open(image_path, 'rb') as img_file: image_data = base64.b64encode(img_file.read()).decode('utf-8') payload['image'] = image_data except Exception as e: print(f"图像处理失败: {str(e)}") return self._request('/employee/add', 'POST', payload) def get_all_employees(self): """获取所有员工列表""" return self._request('/employee/list', 'GET') def get_employee_details(self, emp_id): """获取员工详细信息及打卡记录""" return self._request(f'/employee/{emp_id}', 'GET') def update_employee(self, emp_id, update_data, new_image_path=None): """ 更新员工信息 :param emp_id: 员工ID :param update_data: 更新数据字典 {name?, status?} :param new_image_path: 新照片路径 (可选) :return: 操作结果 """ payload = {'emp_id': emp_id, **update_data} # 处理图像更新 if new_image_path and os.path.exists(new_image_path): try: with open(new_image_path, 'rb') as img_file: image_data = base64.b64encode(img_file.read()).decode('utf-8') payload['image'] = image_data except Exception as e: print(f"图像处理失败: {str(e)}") return self._request('/employee/update', 'POST', payload) def delete_employee(self, emp_id): """删除员工及其打卡记录""" return self._request(f'/employee/delete/{emp_id}', 'DELETE') # ------------------ 打卡管理接口 ------------------ def clock_in(self, emp_id): """员工打卡""" return self._request('/employee/clock_in', 'POST', {'emp_id': emp_id}) def get_employee_image(self, emp_id, save_path=None): """ 获取员工照片 :param emp_id: 员工ID :param save_path: 保存路径 (可选) :return: 图像数据或保存状态 """ try: response = requests.get(f"{self.base_url}/employee/image/{emp_id}") response.raise_for_status() # 保存到文件 if save_path: with open(save_path, 'wb') as f: f.write(response.content) return {"code": 200, "msg": f"图像已保存至 {save_path}", "data": save_path} # 返回二进制数据 return {"code": 200, "msg": "成功", "data": response.content} except requests.exceptions.HTTPError as e: error_msg = f"获取图像失败: {e.response.status_code} {e.response.text}" return {"code": e.response.status_code, "msg": error_msg, "data": None} except Exception as e: return {"code": 500, "msg": str(e), "data": None} # ------------------ 实用功能 ------------------ def reset_daily_status(self): """手动触发每日状态重置(仅用于测试)""" return self._request('/reset_daily_status', 'POST') def server_info(self): """获取服务器信息""" return self._request('/ping', 'GET') # 单例模式 - 全局数据库服务实例 db_service = DatabaseService()
08-22
import sys import random import pygame from pygame.locals import * import pygame.gfxdraw from collections import namedtuple # 定义棋子和点的命名元组 Chessman = namedtuple('Chessman', 'Name Value Color') Point = namedtuple('Point', 'X Y') # 定义黑白棋子 BLACK_CHESSMAN = Chessman('黑子', 1, (45, 45, 45)) WHITE_CHESSMAN = Chessman('白子', 2, (219, 219, 219)) # 定义方向偏移量 offset = [(1, 0), (0, 1), (1, 1), (1, -1)] # 定义棋盘类 class Checkerboard: def __init__(self, line_points): self._line_points = line_points self._checkerboard = [[0] * line_points for _ in range(line_points)] def _get_checkerboard(self): return self._checkerboard checkerboard = property(_get_checkerboard) # 判断是否可落子 def can_drop(self, point): return self._checkerboard[point.Y][point.X] == 0 def drop(self, chessman, point): """ 落子 :param chessman:棋子类型 :param point:落子位置 :return:若该子落下之后即可获胜,则返回获胜方,否则返回 None """ # 把黑棋/白棋落子的坐标打印出来 print(f'{chessman.Name} ({point.X}, {point.Y})') self._checkerboard[point.Y][point.X] = chessman.Value # 打印获胜方出来 if self._win(point): print(f'{chessman.Name}获胜') return chessman # 判断是否赢了 def _win(self, point): cur_value = self._checkerboard[point.Y][point.X] for os in offset: if self._get_count_on_direction(point, cur_value, os[0], os[1]): return True # 判断是否赢了的代码,从这里往上看,代码都是正着写,反着看,写代码思路缺什么补什么,所以从这里开始看 # 声明一个函数,按方向数数,数满5个就获胜。 # 一个二维坐标上,判断上下、左右、两个45度直线,是否有五个相同的直连棋子,只要满足五颗子,则游戏结束: def _get_count_on_direction(self, point, value, x_offset, y_offset): count = 1 for step in range(1, 5): x = point.X + step * x_offset y = point.Y + step * y_offset if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value: count += 1 else: break for step in range(1, 5): x = point.X - step * x_offset y = point.Y - step * y_offset if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value: count += 1 else: break return count >= 5 # 初始化棋盘参数 SIZE = 30 # 棋盘每个点时间的间隔 Line_Points = 19 # 棋盘每行/每列点数 Outer_Width = 20 # 棋盘外宽度 Border_Width = 4 # 边框宽度 Inside_Width = 4 # 边框跟实际的棋盘之间的间隔 Border_Length = SIZE * (Line_Points - 1) + Inside_Width * 2 + Border_Width # 边框线的长度 Start_X = Start_Y = Outer_Width + int(Border_Width / 2) + Inside_Width # 网格线起点(左上角)坐标 SCREEN_HEIGHT = SIZE * (Line_Points - 1) + Outer_Width * 2 + Border_Width + Inside_Width * 2 # 游戏屏幕的高 SCREEN_WIDTH = SCREEN_HEIGHT + 200 # 游戏屏幕的宽 Stone_Radius = SIZE // 2 - 3 # 棋子半径 Stone_Radius2 = SIZE // 2 + 3 Checkerboard_Color = (0xE3, 0x92, 0x65) # 棋盘颜色,0x是16进制表示 BLACK_COLOR = (0, 0, 0) WHITE_COLOR = (255, 255, 255) RED_COLOR = (200, 30, 30) BLUE_COLOR = (30, 30, 200) # 按钮和字体颜色 # 新增按钮相关常量 BUTTON_WIDTH = 150 BUTTON_HEIGHT = 40 BUTTON_COLOR = (70, 130, 180) BUTTON_HOVER_COLOR = (100, 150, 200) BUTTON_TEXT_COLOR = WHITE_COLOR RIGHT_INFO_POS_X = SCREEN_HEIGHT + Stone_Radius2 * 2 + 10 # 绘制文本 def print_text(screen, font, x, y, text, fcolor=(255, 255, 255)): imgText = font.render(text, True, fcolor) screen.blit(imgText, (x, y)) # 主函数 def main(): pygame.init() screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption('五子棋') font1 = pygame.font.SysFont('SimHei', 32) # 字体:黑体,32号 font2 = pygame.font.SysFont('SimHei', 72) # 字体:黑体,72号 font3 = pygame.font.SysFont('SimHei', 24) # 新增:按钮字体 fwidth, fheight = font2.size('黑方获胜') # ========== 新增:创建按钮矩形 ========== pass_rect, restart_rect, quit_rect = create_button_rects(SCREEN_WIDTH, SCREEN_HEIGHT) # ===================================== checkerboard = Checkerboard(Line_Points) cur_runner = BLACK_CHESSMAN winner = None computer = AI(Line_Points, WHITE_CHESSMAN) # 设置黑白双方初始连子为0 black_win_count = 0 white_win_count = 0 while True: # ========== 新增:检测按钮悬停状态 ========== mouse_pos = pygame.mouse.get_pos() # 获取鼠标位置 pass_hover = is_button_clicked(mouse_pos, pass_rect) restart_hover = is_button_clicked(mouse_pos, restart_rect) quit_hover = is_button_clicked(mouse_pos, quit_rect) # ======================================== for event in pygame.event.get(): if event.type == QUIT: sys.exit() elif event.type == KEYDOWN: if event.key == K_RETURN: if winner is not None: winner = None cur_runner = BLACK_CHESSMAN checkerboard = Checkerboard(Line_Points) computer = AI(Line_Points, WHITE_CHESSMAN) elif event.type == MOUSEBUTTONDOWN: # 检测鼠标落下 if winner is None: # 检测是否有一方胜出 pressed_array = pygame.mouse.get_pressed() if pressed_array[0]: mouse_pos = pygame.mouse.get_pos() # ========== 新增:按钮点击检测 ========== if is_button_clicked(mouse_pos, pass_rect) and cur_runner == BLACK_CHESSMAN: # Pass按钮被点击,轮到电脑下棋 cur_runner = _get_next(cur_runner) AI_point = computer.AI_drop() winner = checkerboard.drop(cur_runner, AI_point) if winner is not None: white_win_count += 1 cur_runner = _get_next(cur_runner) continue elif is_button_clicked(mouse_pos, restart_rect): # 重新开始游戏 winner = None cur_runner = BLACK_CHESSMAN checkerboard = Checkerboard(Line_Points) computer = AI(Line_Points, WHITE_CHESSMAN) continue elif is_button_clicked(mouse_pos, quit_rect): # 退出游戏 pygame.quit() sys.exit() # ===================================== click_point = _get_clickpoint(mouse_pos) if click_point is not None: # 检测鼠标是否在棋盘内点击 if checkerboard.can_drop(click_point): winner = checkerboard.drop(cur_runner, click_point) if winner is None: # 再次判断是否有胜出 # 一个循环内检测两次,意思就是人出一次检测一下,电脑出一次检测一下。 cur_runner = _get_next(cur_runner) computer.get_opponent_drop(click_point) AI_point = computer.AI_drop() winner = checkerboard.drop(cur_runner, AI_point) if winner is not None: white_win_count += 1 cur_runner = _get_next(cur_runner) else: black_win_count += 1 else: print('超出棋盘区域') # 画棋盘 _draw_checkerboard(screen) # 画棋盘上已有的棋子 for i, row in enumerate(checkerboard.checkerboard): for j, cell in enumerate(row): if cell == BLACK_CHESSMAN.Value: _draw_chessman(screen, Point(j, i), BLACK_CHESSMAN.Color) elif cell == WHITE_CHESSMAN.Value: _draw_chessman(screen, Point(j, i), WHITE_CHESSMAN.Color) _draw_left_info(screen, font1, cur_runner, black_win_count, white_win_count) # ========== 新增:绘制按钮 ========== draw_button(screen, font3, pass_rect.x, pass_rect.y, BUTTON_WIDTH, BUTTON_HEIGHT, "Pass", pass_hover) draw_button(screen, font3, restart_rect.x, restart_rect.y, BUTTON_WIDTH, BUTTON_HEIGHT, "重新开始", restart_hover) draw_button(screen, font3, quit_rect.x, quit_rect.y, BUTTON_WIDTH, BUTTON_HEIGHT, "结束游戏", quit_hover) # ================================== if winner: print_text(screen, font2, (SCREEN_WIDTH - fwidth) // 2, (SCREEN_HEIGHT - fheight) // 2, winner.Name + '获胜', RED_COLOR) pygame.display.flip() def _get_next(cur_runner): if cur_runner == BLACK_CHESSMAN: return WHITE_CHESSMAN else: return BLACK_CHESSMAN # 画棋盘 def _draw_checkerboard(screen): # 填充棋盘背景色 screen.fill(Checkerboard_Color) # 画棋盘网格线外的边框 pygame.draw.rect(screen, BLACK_COLOR, (Outer_Width, Outer_Width, Border_Length, Border_Length), Border_Width) # 画网格线 for i in range(Line_Points): pygame.draw.line(screen, BLACK_COLOR, (Start_Y, Start_Y + SIZE * i), (Start_Y + SIZE * (Line_Points - 1), Start_Y + SIZE * i), 1) for j in range(Line_Points): pygame.draw.line(screen, BLACK_COLOR, (Start_X + SIZE * j, Start_X), (Start_X + SIZE * j, Start_X + SIZE * (Line_Points - 1)), 1) # 画星位和天元 for i in (3, 9, 15): for j in (3, 9, 15): if i == j == 9: radius = 5 else: radius = 3 # pygame.draw.circle(screen, BLACK, (Start_X + SIZE * i, Start_Y + SIZE * j), radius) pygame.gfxdraw.aacircle(screen, Start_X + SIZE * i, Start_Y + SIZE * j, radius, BLACK_COLOR) pygame.gfxdraw.filled_circle(screen, Start_X + SIZE * i, Start_Y + SIZE * j, radius, BLACK_COLOR) # 画棋子 def _draw_chessman(screen, point, stone_color): # pygame.draw.circle(screen, stone_color, (Start_X + SIZE * point.X, Start_Y + SIZE * point.Y), Stone_Radius) pygame.gfxdraw.aacircle(screen, Start_X + SIZE * point.X, Start_Y + SIZE * point.Y, Stone_Radius, stone_color) pygame.gfxdraw.filled_circle(screen, Start_X + SIZE * point.X, Start_Y + SIZE * point.Y, Stone_Radius, stone_color) # 画右侧信息显示 def _draw_left_info(screen, font, cur_runner, black_win_count, white_win_count): _draw_chessman_pos(screen, (SCREEN_HEIGHT + Stone_Radius2, Start_X + Stone_Radius2), BLACK_CHESSMAN.Color) _draw_chessman_pos(screen, (SCREEN_HEIGHT + Stone_Radius2, Start_X + Stone_Radius2 * 4), WHITE_CHESSMAN.Color) print_text(screen, font, RIGHT_INFO_POS_X, Start_X + 3, '玩家', BLUE_COLOR) print_text(screen, font, RIGHT_INFO_POS_X, Start_X + Stone_Radius2 * 3 + 3, '电脑', BLUE_COLOR) print_text(screen, font, SCREEN_HEIGHT, SCREEN_HEIGHT - Stone_Radius2 * 8, '战况:', BLUE_COLOR) _draw_chessman_pos(screen, (SCREEN_HEIGHT + Stone_Radius2, SCREEN_HEIGHT - int(Stone_Radius2 * 4.5)), BLACK_CHESSMAN.Color) _draw_chessman_pos(screen, (SCREEN_HEIGHT + Stone_Radius2, SCREEN_HEIGHT - Stone_Radius2 * 2), WHITE_CHESSMAN.Color) print_text(screen, font, RIGHT_INFO_POS_X, SCREEN_HEIGHT - int(Stone_Radius2 * 5.5) + 3, f'{black_win_count} 胜', BLUE_COLOR) print_text(screen, font, RIGHT_INFO_POS_X, SCREEN_HEIGHT - Stone_Radius2 * 3 + 3, f'{white_win_count} 胜', BLUE_COLOR) def _draw_chessman_pos(screen, pos, stone_color): pygame.gfxdraw.aacircle(screen, pos[0], pos[1], Stone_Radius2, stone_color) pygame.gfxdraw.filled_circle(screen, pos[0], pos[1], Stone_Radius2, stone_color) # ========== 新增按钮相关函数 ========== def draw_button(screen, font, x, y, width, height, text, hover=False): """绘制按钮""" color = BUTTON_HOVER_COLOR if hover else BUTTON_COLOR pygame.draw.rect(screen, color, (x, y, width, height)) pygame.draw.rect(screen, BLACK_COLOR, (x, y, width, height), 2) # 边框 text_surf = font.render(text, True, BUTTON_TEXT_COLOR) text_rect = text_surf.get_rect(center=(x + width//2, y + height//2)) screen.blit(text_surf, text_rect) def is_button_clicked(pos, button_rect): """检测按钮是否被点击""" return button_rect.collidepoint(pos) def create_button_rects(screen_width, screen_height): """创建按钮矩形区域""" button_y_start = screen_height // 2 - BUTTON_HEIGHT * 2 pass_rect = pygame.Rect(screen_width - BUTTON_WIDTH - 20, button_y_start, BUTTON_WIDTH, BUTTON_HEIGHT) restart_rect = pygame.Rect(screen_width - BUTTON_WIDTH - 20, button_y_start + BUTTON_HEIGHT + 10, BUTTON_WIDTH, BUTTON_HEIGHT) quit_rect = pygame.Rect(screen_width - BUTTON_WIDTH - 20, button_y_start + BUTTON_HEIGHT*2 + 20, BUTTON_WIDTH, BUTTON_HEIGHT) return pass_rect, restart_rect, quit_rect # =================================== # 根据鼠标点击位置,返回游戏区坐标 def _get_clickpoint(click_pos): pos_x = click_pos[0] - Start_X pos_y = click_pos[1] - Start_Y if pos_x < -Inside_Width or pos_y < -Inside_Width: return None x = pos_x // SIZE y = pos_y // SIZE if pos_x % SIZE > Stone_Radius: x += 1 if pos_y % SIZE > Stone_Radius: y += 1 if x >= Line_Points or y >= Line_Points: return None return Point(x, y) class AI: def __init__(self, line_points, chessman): self._line_points = line_points self._my = chessman self._opponent = BLACK_CHESSMAN if chessman == WHITE_CHESSMAN else WHITE_CHESSMAN self._checkerboard = [[0] * line_points for _ in range(line_points)] def get_opponent_drop(self, point): self._checkerboard[point.Y][point.X] = self._opponent.Value def AI_drop(self): point = None score = 0 for i in range(self._line_points): for j in range(self._line_points): if self._checkerboard[j][i] == 0: _score = self._get_point_score(Point(i, j)) if _score > score: score = _score point = Point(i, j) elif _score == score and _score > 0: r = random.randint(0, 100) if r % 2 == 0: point = Point(i, j) self._checkerboard[point.Y][point.X] = self._my.Value return point def _get_point_score(self, point): score = 0 for os in offset: score += self._get_direction_score(point, os[0], os[1]) return score def _get_direction_score(self, point, x_offset, y_offset): count = 0 # 落子处我方连续子数 _count = 0 # 落子处对方连续子数 space = None # 我方连续子中有无空格 _space = None # 对方连续子中有无空格 both = 0 # 我方连续子两端有无阻挡 _both = 0 # 对方连续子两端有无阻挡 # 如果是 1 表示是边上是我方子,2 表示敌方子 flag = self._get_stone_color(point, x_offset, y_offset, True) if flag != 0: for step in range(1, 6): x = point.X + step * x_offset y = point.Y + step * y_offset if 0 <= x < self._line_points and 0 <= y < self._line_points: if flag == 1: if self._checkerboard[y][x] == self._my.Value: count += 1 if space is False: space = True elif self._checkerboard[y][x] == self._opponent.Value: _both += 1 break else: if space is None: space = False else: break # 遇到第二个空格退出 elif flag == 2: if self._checkerboard[y][x] == self._my.Value: _both += 1 break elif self._checkerboard[y][x] == self._opponent.Value: _count += 1 if _space is False: _space = True else: if _space is None: _space = False else: break else: # 遇到边也就是阻挡 if flag == 1: both += 1 elif flag == 2: _both += 1 if space is False: space = None if _space is False: _space = None _flag = self._get_stone_color(point, -x_offset, -y_offset, True) if _flag != 0: for step in range(1, 6): x = point.X - step * x_offset y = point.Y - step * y_offset if 0 <= x < self._line_points and 0 <= y < self._line_points: if _flag == 1: if self._checkerboard[y][x] == self._my.Value: count += 1 if space is False: space = True elif self._checkerboard[y][x] == self._opponent.Value: _both += 1 break else: if space is None: space = False else: break # 遇到第二个空格退出 elif _flag == 2: if self._checkerboard[y][x] == self._my.Value: _both += 1 break elif self._checkerboard[y][x] == self._opponent.Value: _count += 1 if _space is False: _space = True else: if _space is None: _space = False else: break else: # 遇到边也就是阻挡 if _flag == 1: both += 1 elif _flag == 2: _both += 1 # 下面这一串score(分数)的含义:评估棋格获胜分数。 # 使计算机计算获胜分值越高的棋格,就能确定能让自己的棋子最有可能达成联机的位置,也就是最佳进攻位置, # 而一旦计算机能确定自己的最高分值的位置,计算机就具备了进攻能力。 # 同理,计算机能计算出玩家的最大分值位置,并抢先玩家获得该位置,这样计算机就具有了防御的能力。 # 在计算机下棋之前,会计算空白棋格上的获胜分数,根据分数高低获取最佳位置。 # 计算机会将棋子下在获胜分数最高的地方。 # 当已放置4颗棋子时,必须在第五个空棋格上设置绝对高的分值。也就是10000 # 当获胜组合上有部分位置已被对手的棋格占据而无法连成五子时,获胜组合上空棋格的获胜分数会直接设置为0。(四颗棋子,你把中间断了) # 当有两组及其以上的获胜组合位置交叉时,对该位置的分数进行叠加,形成分数比周围位置明显高。(五子棋中三三相连) score = 0 if count == 4: score = 10000 elif _count == 4: score = 9000 elif count == 3: if both == 0: score = 1000 elif both == 1: score = 100 else: score = 0 elif _count == 3: if _both == 0: score = 900 elif _both == 1: score = 90 else: score = 0 elif count == 2: if both == 0: score = 100 elif both == 1: score = 10 else: score = 0 elif _count == 2: if _both == 0: score = 90 elif _both == 1: score = 9 else: score = 0 elif count == 1: score = 10 elif _count == 1: score = 9 else: score = 0 if space or _space: score /= 2 return score # 判断指定位置处在指定方向上是我方子、对方子、空 def _get_stone_color(self, point, x_offset, y_offset, next): x = point.X + x_offset y = point.Y + y_offset if 0 <= x < self._line_points and 0 <= y < self._line_points: if self._checkerboard[y][x] == self._my.Value: return 1 elif self._checkerboard[y][x] == self._opponent.Value: return 2 else: if next: return self._get_stone_color(Point(x, y), x_offset, y_offset, False) else: return 0 else: return 0 if __name__ == '__main__': main() 游戏结束后点击"重新开始"或"结束游戏"却无反应
06-17
基于TROPOMI高光谱遥感仪器获取的大气成分观测资料,本研究聚焦于大气污染物一氧化氮(NO₂)的空间分布与浓度定量反演问题。NO₂作为影响空气质量的关键指标,其精确监测对环境保护与大气科学研究具有显著价值。当前,利用卫星遥感数据结合先进算法实现NO₂浓度的高精度反演已成为该领域的重要研究方向。 本研究构建了一套以深度学习为核心的技术框架,整合了来自TROPOMI仪器的光谱辐射信息、观测几何参数以及辅助气象数据,形成多维度特征数据集。该数据集充分融合了不同来源的观测信息,为深入解析大气中NO₂的时空变化规律提供了数据基础,有助于提升反演模型的准确性与环境预测的可靠性。 在模型架构方面,项目设计了一种多分支神经网络,用于分别处理光谱特征与气象特征等多模态数据。各分支通过独立学习提取代表性特征,并在深层网络中进行特征融合,从而综合利用不同数据的互补信息,显著提高了NO₂浓度反演的整体精度。这种多源信息融合策略有效增强了模型对复杂大气环境的表征能力。 研究过程涵盖了系统的数据处理流程。前期预处理包括辐射定标、噪声抑制及数据标准化等步骤,以保障输入特征的质量与一致性;后期处理则涉及模型输出的物理量转换与结果验证,确保反演结果符合实际大气浓度范围,提升数据的实用价值。 此外,本研究进一步对不同功能区域(如城市建成区、工业带、郊区及自然背景区)的NO₂浓度分布进行了对比分析,揭示了人类活动与污染物空间格局的关联性。相关结论可为区域环境规划、污染管控政策的制定提供科学依据,助力大气环境治理与公共健康保护。 综上所述,本研究通过融合TROPOMI高光谱数据与多模态特征深度学习技术,发展了一套高效、准确的大气NO₂浓度遥感反演方法,不仅提升了卫星大气监测的技术水平,也为环境管理与决策支持提供了重要的技术工具。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
# 模板编辑器画布功能 - PyQt6版本 import os from PyQt6.QtWidgets import ( QGraphicsView, QGraphicsScene, QGraphicsRectItem, QGraphicsPixmapItem, QGraphicsSimpleTextItem, QMenu, QInputDialog, QGraphicsItem, QLineEdit, QFileDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton, QFontComboBox, QSpinBox, QColorDialog, QGroupBox, QDoubleSpinBox ) from PyQt6.QtGui import ( QPixmap, QImage, QPainter, QPen, QBrush, QColor, QFont ) from PyQt6.QtCore import Qt, QRectF, QSize, pyqtSignal from PyQt6.QtPrintSupport import QPrinter from ..utils.image_utils import pdf_to_image class PropertiesPanel(QWidget): propertyChanged = pyqtSignal(str, object) # 修正拼写错误 def __init__(self, parent=None): super().__init__(parent) self.region = None self.setup_ui() def set_region(self, region): """设置当前区域""" self.region = region self.update_ui() def setup_ui(self): """设置UI布局 - 由子类实现""" pass def update_ui(self): """更新UI显示 - 由子类实现""" pass class TextPropertiesPanel(PropertiesPanel): def __init__(self, parent=None): super().__init__(parent) self.name_edit = None self.font_combo = None self.size_spin = None self.color_btn = None self.align_combo = None self.setup_ui() def setup_ui(self): layout = QVBoxLayout() # 名称设置 name_layout = QHBoxLayout() name_layout.addWidget(QLabel("区域名称:")) self.name_edit = QLineEdit() self.name_edit.textChanged.connect(self.on_name_changed) name_layout.addWidget(self.name_edit) layout.addLayout(name_layout) # 字体设置 font_group = QGroupBox("字体设置") font_layout = QVBoxLayout(font_group) font_family_layout = QHBoxLayout() font_family_layout.addWidget(QLabel("字体:")) self.font_combo = QFontComboBox() self.font_combo.currentFontChanged.connect(self.on_font_changed) font_family_layout.addWidget(self.font_combo) font_layout.addLayout(font_family_layout) font_size_layout = QHBoxLayout() font_size_layout.addWidget(QLabel("大小:")) self.size_spin = QSpinBox() self.size_spin.setRange(6, 72) self.size_spin.valueChanged.connect(self.on_size_changed) font_size_layout.addWidget(self.size_spin) font_layout.addLayout(font_size_layout) # 颜色设置 color_layout = QHBoxLayout() color_layout.addWidget(QLabel("颜色:")) self.color_btn = QPushButton() self.color_btn.setFixedSize(30, 30) self.color_btn.clicked.connect(self.choose_color) color_layout.addWidget(self.color_btn) font_layout.addLayout(color_layout) # 对齐方式 align_layout = QHBoxLayout() align_layout.addWidget(QLabel("对齐:")) self.align_combo = QComboBox() self.align_combo.addItem("左对齐", Qt.AlignmentFlag.AlignLeft) self.align_combo.addItem("居中", Qt.AlignmentFlag.AlignCenter) self.align_combo.addItem("右对齐", Qt.AlignmentFlag.AlignRight) self.align_combo.currentIndexChanged.connect(self.on_align_changed) align_layout.addWidget(self.align_combo) font_layout.addLayout(align_layout) layout.addWidget(font_group) self.setLayout(layout) def update_ui(self): if self.region: self.name_edit.setText(self.region.get_name()) self.font_combo.setCurrentFont(self.region.font) self.size_spin.setValue(self.region.font.pointSize()) self.update_color_btn(self.region.color) # 设置对齐方式 index = self.align_combo.findData(self.region.align) if index >= 0: self.align_combo.setCurrentIndex(index) def update_color_btn(self, color): """更新颜色按钮显示""" self.color_btn.setStyleSheet(f"background-color: {color.name()};") def on_name_changed(self, name): if self.region: self.region.set_name(name) self.propertyChanged.emit('name', name) def on_font_changed(self, font): if self.region: font.setPointSize(self.size_spin.value()) self.region.set_font(font) self.propertyChanged.emit('font', font) def on_size_changed(self, size): if self.region: font = self.region.font font.setPointSize(size) self.region.set_font(font) self.propertyChanged.emit('font', font) def choose_color(self): color = QColorDialog.getColor(self.region.color if self.region else Qt.GlobalColor.black, self) if color.isValid(): self.region.set_color(color) self.update_color_btn(color) self.propertyChanged.emit('color', color) def on_align_changed(self, index): if self.region: align = self.align_combo.currentData() self.region.set_align(align) self.propertyChanged.emit('align', align) class QRPropertiesPanel(PropertiesPanel): def __init__(self, parent=None): super().__init__(parent) self.name_edit = None self.type_combo = None self.setup_ui() def setup_ui(self): layout = QVBoxLayout() # 名称设置 name_layout = QHBoxLayout() name_layout.addWidget(QLabel("区域名称:")) self.name_edit = QLineEdit() self.name_edit.textChanged.connect(self.on_name_changed) name_layout.addWidget(self.name_edit) layout.addLayout(name_layout) # 二维码类型 type_layout = QHBoxLayout() type_layout.addWidget(QLabel("码类型:")) self.type_combo = QComboBox() self.type_combo.addItem("QR码", "QRCode") self.type_combo.addItem("条形码", "Barcode") self.type_combo.addItem("抖音码", "DouyinCode") self.type_combo.currentIndexChanged.connect(self.on_type_changed) type_layout.addWidget(self.type_combo) layout.addLayout(type_layout) self.setLayout(layout) def update_ui(self): if self.region: self.name_edit.setText(self.region.get_name()) index = self.type_combo.findData(self.region.code_type) if index >= 0: self.type_combo.setCurrentIndex(index) def on_name_changed(self, name): if self.region: self.region.set_name(name) self.propertyChanged.emit('name', name) def on_type_changed(self, index): if self.region: code_type = self.type_combo.currentData() self.region.code_type = code_type self.propertyChanged.emit('code_type', code_type) class LogoPropertiesPanel(PropertiesPanel): def __init__(self, parent=None): super().__init__(parent) self.name_edit = None self.opacity_spin = None self.replace_btn = None self.setup_ui() def setup_ui(self): layout = QVBoxLayout() # 名称设置 name_layout = QHBoxLayout() name_layout.addWidget(QLabel("区域名称:")) self.name_edit = QLineEdit() self.name_edit.textChanged.connect(self.on_name_changed) name_layout.addWidget(self.name_edit) layout.addLayout(name_layout) # 不透明度 opacity_layout = QHBoxLayout() opacity_layout.addWidget(QLabel("不透明度:")) self.opacity_spin = QDoubleSpinBox() self.opacity_spin.setRange(0.0, 1.0) self.opacity_spin.setSingleStep(0.1) self.opacity_spin.setValue(1.0) self.opacity_spin.valueChanged.connect(self.on_opacity_changed) opacity_layout.addWidget(self.opacity_spin) layout.addLayout(opacity_layout) # 替换Logo按钮 self.replace_btn = QPushButton("替换Logo") self.replace_btn.clicked.connect(self.replace_logo) layout.addWidget(self.replace_btn) self.setLayout(layout) def update_ui(self): if self.region: self.name_edit.setText(self.region.get_name()) self.opacity_spin.setValue(self.region.opacity) def on_name_changed(self, name): if self.region: self.region.set_name(name) self.propertyChanged.emit('name', name) def on_opacity_changed(self, opacity): if self.region: self.region.set_opacity(opacity) self.propertyChanged.emit('opacity', opacity) def replace_logo(self): if self.region: # 打开文件对话框选择Logo图片 file_path, _ = QFileDialog.getOpenFileName( self, "选择Logo图片", self.data_path, "图像文件 (*.png *.jpg *.jpeg *.bmp);;PDF文件 (*.pdf)" ) if file_path: # 处理PDF Logo if file_path.lower().endswith('.pdf'): temp_img = pdf_to_image(file_path) if temp_img is not None: height, width, _ = temp_img.shape bytes_per_line = 3 * width qimage = QImage(temp_img.data, width, height, bytes_per_line, QImage.Format.Format_RGB888) pixmap = QPixmap.fromImage(qimage) else: return else: pixmap = QPixmap(file_path) if not pixmap.isNull(): self.region.logo_item.setPixmap(pixmap) # 更新Logo尺寸 rect = self.region.rect() self.region.logo_item.setScale(min( rect.width() / pixmap.width(), rect.height() / pixmap.height() )) class TemplateCanvas(QGraphicsView): MODE_NONE = 0 MODE_SELECT = 1 MODE_TEXT = 2 MODE_QR = 3 MODE_LOGO = 4 def __init__(self, parent=None, data_path=None): super().__init__(parent) self.scene = QGraphicsScene() self.setScene(self.scene) self.setRenderHint(QPainter.RenderHint.Antialiasing, True) self.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True) self.setDragMode(QGraphicsView.DragMode.RubberBandDrag) self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) # 初始化变量 self.data_path = data_path or os.path.join(os.path.dirname(__file__), "../../../data") self.template_item = None self.template_path = None self.current_mode = self.MODE_NONE self.selection_start = None self.current_selection = None self.regions = [] # 存储所有区域 self.selected_region = None self.zoom_factor = 1.0 # 设置默认属性 self.text_font = QFont("Arial", 12) self.text_align = Qt.AlignmentFlag.AlignLeft self.text_color = QColor(Qt.GlobalColor.black) # 设置背景 self.setStyleSheet("background-color: #f0f0f0;") # 创建上下文菜单 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu) def load_template(self, file_path): """加载模板图片或PDF""" self.clear_scene() self.template_path = file_path # 处理PDF文件 if file_path.lower().endswith('.pdf'): # 将PDF转换为临时图片 temp_img = pdf_to_image(file_path) if temp_img is None: return False # 转换为QPixmap height, width, channel = temp_img.shape bytes_per_line = 3 * width qimage = QImage(temp_img.data, width, height, bytes_per_line, QImage.Format.Format_RGB888) pixmap = QPixmap.fromImage(qimage) else: # 直接加载图片 pixmap = QPixmap(file_path) if pixmap.isNull(): return False # 添加模板到场景 self.template_item = self.scene.addPixmap(pixmap) self.template_item.setZValue(-1) # 确保在底层 # 调整视图 self.fitInView(self.template_item, Qt.AspectRatioMode.KeepAspectRatio) self.zoom_factor = self.transform().m11() # 记录当前缩放因子 return True def clear_scene(self): """清除场景内容""" self.scene.clear() self.template_item = None self.template_path = None self.regions = [] self.selected_region = None self.zoom_factor = 1.0 def set_mode(self, mode): """设置当前操作模式""" self.current_mode = mode # 更新光标 if mode == self.MODE_TEXT: self.setCursor(Qt.CursorShape.CrossCursor) elif mode == self.MODE_QR: self.setCursor(Qt.CursorShape.CrossCursor) elif mode == self.MODE_LOGO: self.setCursor(Qt.CursorShape.PointingHandCursor) else: self.setCursor(Qt.CursorShape.ArrowCursor) def mousePressEvent(self, event): """处理鼠标按下事件""" if event.button() == Qt.MouseButton.LeftButton: scene_pos = self.mapToScene(event.position().toPoint()) # 检查是否点击了现有区域 items = self.scene.items(scene_pos) for item in items: if isinstance(item, RegionItem): self.select_region(item) return # 根据模式处理 if self.current_mode in (self.MODE_TEXT, self.MODE_QR, self.MODE_LOGO): self.selection_start = scene_pos self.current_selection = None else: super().mousePressEvent(event) else: super().mousePressEvent(event) def mouseMoveEvent(self, event): """处理鼠标移动事件""" if self.selection_start and event.buttons() & Qt.MouseButton.LeftButton: scene_pos = self.mapToScene(event.position().toPoint()) # 计算选择矩形 rect = QRectF(self.selection_start, scene_pos).normalized() # 创建或更新选择框 if not self.current_selection: self.current_selection = SelectionRectItem(rect) self.scene.addItem(self.current_selection) else: self.current_selection.setRect(rect) else: super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): """处理鼠标释放事件""" if self.current_selection and event.button() == Qt.MouseButton.LeftButton: rect = self.current_selection.rect() self.scene.removeItem(self.current_selection) self.current_selection = None # 确保矩形有效 if rect.width() > 5 and rect.height() > 5: # 根据模式创建区域 if self.current_mode == self.MODE_TEXT: self.add_text_region(rect) elif self.current_mode == self.MODE_QR: self.add_qr_region(rect) elif self.current_mode == self.MODE_LOGO: self.add_logo_region(rect) self.selection_start = None else: super().mouseReleaseEvent(event) def wheelEvent(self, event): """处理滚轮缩放事件""" zoom_factor = 1.2 if event.angleDelta().y() < 0: zoom_factor = 1 / zoom_factor self.zoom(zoom_factor) def zoom(self, factor): """缩放视图""" self.scale(factor, factor) self.zoom_factor *= factor def reset_zoom(self): """重置缩放""" self.resetTransform() if self.template_item: self.fitInView(self.template_item, Qt.AspectRatioMode.KeepAspectRatio) self.zoom_factor = self.transform().m11() def add_text_region(self, rect): """添加文本区域""" region = TextRegionItem(rect) region.set_font(self.text_font) region.set_align(self.text_align) region.set_color(self.text_color) self.scene.addItem(region) self.regions.append(region) self.select_region(region) self.notify_regions_changed() # 通知主窗口更新区域信息 def add_qr_region(self, rect): """添加二维码区域""" region = QRRegionItem(rect) self.scene.addItem(region) self.regions.append(region) self.select_region(region) self.notify_regions_changed() # 通知主窗口更新区域信息 def add_logo_region(self, rect): """添加Logo区域""" # 打开文件对话框选择Logo图片 file_path, _ = QFileDialog.getOpenFileName( self, "选择Logo图片", self.data_path, "图像文件 (*.png *.jpg *.jpeg *.bmp);;PDF文件 (*.pdf)" ) if file_path: # 处理PDF Logo if file_path.lower().endswith('.pdf'): temp_img = pdf_to_image(file_path) if temp_img is not None: height, width, _ = temp_img.shape bytes_per_line = 3 * width qimage = QImage(temp_img.data, width, height, bytes_per_line, QImage.Format.Format_RGB888) pixmap = QPixmap.fromImage(qimage) else: return else: pixmap = QPixmap(file_path) if not pixmap.isNull(): region = LogoRegionItem(rect, pixmap) self.scene.addItem(region) self.regions.append(region) self.select_region(region) self.notify_regions_changed() # 通知主窗口更新区域信息 def select_region(self, region): """选择区域""" # 取消之前的选择 if self.selected_region: self.selected_region.set_selected(False) # 设置新选择 region.set_selected(True) self.selected_region = region # 通知父组件更新属性面板 if self.parent() and hasattr(self.parent(), 'update_property_panel'): self.parent().update_property_panel(region) def get_regions(self): """获取所有区域数据""" return [region.get_data() for region in self.regions] def clear_regions(self): """清除所有区域""" for region in self.regions: self.scene.removeItem(region) self.regions = [] self.selected_region = None self.notify_regions_changed() # 通知主窗口更新区域信息 def notify_regions_changed(self): """通知主窗口区域已更新""" if self.parent() and hasattr(self.parent(), 'update_regions'): self.parent().update_regions() def set_region_property(self, key, value): """设置选中区域的属性""" if self.selected_region: if key == 'font': self.selected_region.set_font(value) elif key == 'align': self.selected_region.set_align(value) elif key == 'color': self.selected_region.set_color(value) elif key == 'name': self.selected_region.set_name(value) def show_context_menu(self, pos): """显示上下文菜单""" if not self.template_item: return menu = QMenu(self) # 添加缩放选项 zoom_in_action = menu.addAction("放大") zoom_out_action = menu.addAction("缩小") reset_zoom_action = menu.addAction("重置缩放") menu.addSeparator() # 添加区域操作 rename_action = menu.addAction("重命名区域") delete_action = menu.addAction("删除区域") clear_all_action = menu.addAction("清除所有区域") # 执行菜单 action = menu.exec(self.mapToGlobal(pos)) # 处理操作 if action == zoom_in_action: self.zoom(1.2) elif action == zoom_out_action: self.zoom(1 / 1.2) elif action == reset_zoom_action: self.reset_zoom() elif action == rename_action and self.selected_region: self.rename_selected_region() elif action == delete_action and self.selected_region: self.delete_selected_region() elif action == clear_all_action: self.clear_regions() def rename_selected_region(self): """重命名选中区域""" if not self.selected_region: return name, ok = QInputDialog.getText( self, "重命名区域", "输入区域名称:", text=self.selected_region.get_name() ) if ok and name: self.selected_region.set_name(name) if self.parent() and hasattr(self.parent(), 'update_property_panel'): self.parent().update_property_panel(self.selected_region) def delete_selected_region(self): """删除选中区域""" if self.selected_region: self.scene.removeItem(self.selected_region) self.regions.remove(self.selected_region) self.selected_region = None self.notify_regions_changed() # 通知主窗口更新区域信息 def export_preview(self, output_path): """导出预览图像""" if not self.template_item: return False # 创建打印机对象 printer = QPrinter() printer.setOutputFileName(output_path) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setPaperSize(QSize(self.scene.width(), self.scene.height()), QPrinter.Unit.DevicePixel) printer.setFullPage(True) # 创建绘制器 painter = QPainter(printer) painter.setRenderHint(QPainter.RenderHint.Antialiasing) # 渲染整个场景 self.scene.render(painter) painter.end() return True class RegionItem(QGraphicsRectItem): """区域基类""" def __init__(self, rect, parent=None): super().__init__(rect, parent) self.setFlags( QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QGraphicsItem.GraphicsItemFlag.ItemIsMovable ) self.setAcceptHoverEvents(True) # 区域属性 self.name = "未命名区域" self.selected = False self.hovered = False # 设置画笔和画刷 self.normal_pen = QPen(Qt.GlobalColor.blue, 2, Qt.PenStyle.DashLine) self.hover_pen = QPen(Qt.GlobalColor.darkBlue, 3, Qt.PenStyle.DashLine) self.selected_pen = QPen(Qt.GlobalColor.red, 3, Qt.PenStyle.DashLine) self.setPen(self.normal_pen) def set_selected(self, selected): """设置选中状态""" self.selected = selected self.update_pen() def set_hovered(self, hovered): """设置悬停状态""" self.hovered = hovered self.update_pen() def update_pen(self): """更新画笔样式""" if self.selected: self.setPen(self.selected_pen) elif self.hovered: self.setPen(self.hover_pen) else: self.setPen(self.normal_pen) def hoverEnterEvent(self, event): self.set_hovered(True) super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.set_hovered(False) super().hoverLeaveEvent(event) def mousePressEvent(self, event): # 通知画布选择此区域 if self.scene() and self.scene().views(): view = self.scene().views()[0] if isinstance(view, TemplateCanvas): view.select_region(self) super().mousePressEvent(event) def set_name(self, name): """设置区域名称""" self.name = name def get_name(self): """获取区域名称""" return self.name def get_data(self): """获取区域数据""" return { 'type': 'base', 'name': self.name, 'rect': self.rect(), 'pos': self.pos() } class TextRegionItem(RegionItem): """文本区域""" def __init__(self, rect, parent=None): super().__init__(rect, parent) self.font = QFont("Arial", 12) self.align = Qt.AlignmentFlag.AlignLeft self.color = QColor(Qt.GlobalColor.black) # 添加文本标签 self.text_label = QGraphicsSimpleTextItem(self.name, self) self.text_label.setBrush(Qt.GlobalColor.white) self.text_label.setFont(QFont("Arial", 8)) self.text_label.setPos(5, 5) self.update_label() def set_font(self, font): """设置字体""" self.font = font self.update_label() def set_align(self, align): """设置对齐方式""" self.align = align self.update_label() def set_color(self, color): """设置文本颜色""" self.color = color self.update_label() def set_name(self, name): """设置区域名称""" super().set_name(name) self.text_label.setText(name) def update_label(self): """更新文本标签样式""" self.text_label.setFont(self.font) self.text_label.setBrush(self.color) def get_data(self): """获取区域数据""" data = super().get_data() data.update({ 'type': 'text', 'font': self.font.toString(), 'align': self.align, 'color': self.color.name(), 'font_size': self.font.pointSize(), 'font_family': self.font.family() }) return data class QRRegionItem(RegionItem): """二维码区域""" def __init__(self, rect, parent=None): super().__init__(rect, parent) self.code_type = "QRCode" # 添加类型标签 self.type_label = QGraphicsSimpleTextItem("QR", self) self.type_label.setBrush(Qt.GlobalColor.white) self.type_label.setFont(QFont("Arial", 8, QFont.Weight.Bold)) self.type_label.setPos(5, 5) def set_name(self, name): """设置区域名称""" super().set_name(name) self.type_label.setText(name[:2]) # 只显示前两个字符 def get_data(self): """获取区域数据""" data = super().get_data() data.update({ 'type': 'qr', 'code_type': self.code_type }) return data class LogoRegionItem(RegionItem): """Logo区域""" def __init__(self, rect, pixmap, parent=None): super().__init__(rect, parent) self.pixmap = pixmap self.opacity = 1.0 # 创建Logo图像项 self.logo_item = QGraphicsPixmapItem(self.pixmap, self) self.logo_item.setTransformationMode(Qt.TransformationMode.SmoothTransformation) self.logo_item.setPos(rect.topLeft()) # 设置Logo尺寸 self.logo_item.setScale(min( rect.width() / self.pixmap.width(), rect.height() / self.pixmap.height() )) # 添加类型标签 self.type_label = QGraphicsSimpleTextItem("LOGO", self) self.type_label.setBrush(Qt.GlobalColor.white) self.type_label.setFont(QFont("Arial", 8, QFont.Weight.Bold)) self.type_label.setPos(5, 5) def set_opacity(self, opacity): """设置不透明度""" self.opacity = opacity self.logo_item.setOpacity(opacity) def set_name(self, name): """设置区域名称""" super().set_name(name) self.type_label.setText(name[:4]) # 只显示前四个字符 def get_data(self): """获取区域数据""" data = super().get_data() data.update({ 'type': 'logo', 'opacity': self.opacity, 'logo_path': "" # 实际路径需要特殊处理 }) return data class SelectionRectItem(QGraphicsRectItem): """选择矩形框""" def __init__(self, rect, parent=None): super().__init__(rect, parent) self.setPen(QPen(Qt.GlobalColor.darkGreen, 2, Qt.PenStyle.DashLine)) self.setBrush(QBrush(QColor(0, 255, 0, 50))) 以上是template_editor.py的完整代码,请帮我调试并输出完整的修改后的代码
07-15
好的,我将帮助您调试并优化 `template_editor.py` 代码。我们将重点放在确保所有方法和属性的定义与调用一致,消除潜在的“意外实参”问题,并确保代码逻辑清晰。此外,我们会添加一些额外的错误处理和注释以提高代码的健壮性和可读性。 ### 主要调整点 1. **参数一致性**:确保所有方法的参数定义与其调用方式一致。 2. **默认值和类型提示**:为可选参数提供默认值,并使用类型提示来明确每个参数的类型。 3. **错误处理**:增加更多的错误处理以提高代码的健壮性。 4. **注释和文档**:增加必要的注释和文档字符串以提高代码的可读性。 以下是修改后的完整代码: ```python # 模板编辑器画布功能 - PyQt6版本 import os from PyQt6.QtWidgets import ( QGraphicsView, QGraphicsScene, QGraphicsRectItem, QGraphicsPixmapItem, QGraphicsSimpleTextItem, QMenu, QInputDialog, QGraphicsItem, QLineEdit, QFileDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton, QFontComboBox, QSpinBox, QColorDialog, QGroupBox, QDoubleSpinBox ) from PyQt6.QtGui import ( QPixmap, QImage, QPainter, QPen, QBrush, QColor, QFont ) from PyQt6.QtCore import Qt, QRectF, QSize, pyqtSignal from PyQt6.QtPrintSupport import QPrinter from ..utils.image_utils import pdf_to_image class PropertiesPanel(QWidget): propertyChanged = pyqtSignal(str, object) def __init__(self, parent=None): super().__init__(parent) self.region = None self.setup_ui() def set_region(self, region): """设置当前区域""" self.region = region self.update_ui() def setup_ui(self): """设置UI布局 - 由子类实现""" pass def update_ui(self): """更新UI显示 - 由子类实现""" pass class TextPropertiesPanel(PropertiesPanel): def __init__(self, parent=None): super().__init__(parent) self.name_edit = None self.font_combo = None self.size_spin = None self.color_btn = None self.align_combo = None self.setup_ui() def setup_ui(self): layout = QVBoxLayout() # 名称设置 name_layout = QHBoxLayout() name_layout.addWidget(QLabel("区域名称:")) self.name_edit = QLineEdit() self.name_edit.textChanged.connect(self.on_name_changed) name_layout.addWidget(self.name_edit) layout.addLayout(name_layout) # 字体设置 font_group = QGroupBox("字体设置") font_layout = QVBoxLayout(font_group) font_family_layout = QHBoxLayout() font_family_layout.addWidget(QLabel("字体:")) self.font_combo = QFontComboBox() self.font_combo.currentFontChanged.connect(self.on_font_changed) font_family_layout.addWidget(self.font_combo) font_layout.addLayout(font_family_layout) font_size_layout = QHBoxLayout() font_size_layout.addWidget(QLabel("大小:")) self.size_spin = QSpinBox() self.size_spin.setRange(6, 72) self.size_spin.valueChanged.connect(self.on_size_changed) font_size_layout.addWidget(self.size_spin) font_layout.addLayout(font_size_layout) # 颜色设置 color_layout = QHBoxLayout() color_layout.addWidget(QLabel("颜色:")) self.color_btn = QPushButton() self.color_btn.setFixedSize(30, 30) self.color_btn.clicked.connect(self.choose_color) color_layout.addWidget(self.color_btn) font_layout.addLayout(color_layout) # 对齐方式 align_layout = QHBoxLayout() align_layout.addWidget(QLabel("对齐:")) self.align_combo = QComboBox() self.align_combo.addItem("左对齐", Qt.AlignmentFlag.AlignLeft) self.align_combo.addItem("居中", Qt.AlignmentFlag.AlignCenter) self.align_combo.addItem("右对齐", Qt.AlignmentFlag.AlignRight) self.align_combo.currentIndexChanged.connect(self.on_align_changed) align_layout.addWidget(self.align_combo) font_layout.addLayout(align_layout) layout.addWidget(font_group) self.setLayout(layout) def update_ui(self): if self.region: self.name_edit.setText(self.region.get_name()) self.font_combo.setCurrentFont(self.region.font) self.size_spin.setValue(self.region.font.pointSize()) self.update_color_btn(self.region.color) # 设置对齐方式 index = self.align_combo.findData(self.region.align) if index >= 0: self.align_combo.setCurrentIndex(index) def update_color_btn(self, color: QColor): """更新颜色按钮显示""" self.color_btn.setStyleSheet(f"background-color: {color.name()};") def on_name_changed(self, name: str): if self.region: self.region.set_name(name) self.propertyChanged.emit('name', name) def on_font_changed(self, font: QFont): if self.region: font.setPointSize(self.size_spin.value()) self.region.set_font(font) self.propertyChanged.emit('font', font) def on_size_changed(self, size: int): if self.region: font = self.region.font font.setPointSize(size) self.region.set_font(font) self.propertyChanged.emit('font', font) def choose_color(self): color = QColorDialog.getColor(self.region.color if self.region else Qt.GlobalColor.black, self) if color.isValid(): self.region.set_color(color) self.update_color_btn(color) self.propertyChanged.emit('color', color) def on_align_changed(self, index: int): if self.region: align = self.align_combo.currentData() self.region.set_align(align) self.propertyChanged.emit('align', align) class QRPropertiesPanel(PropertiesPanel): def __init__(self, parent=None): super().__init__(parent) self.name_edit = None self.type_combo = None self.setup_ui() def setup_ui(self): layout = QVBoxLayout() # 名称设置 name_layout = QHBoxLayout() name_layout.addWidget(QLabel("区域名称:")) self.name_edit = QLineEdit() self.name_edit.textChanged.connect(self.on_name_changed) name_layout.addWidget(self.name_edit) layout.addLayout(name_layout) # 二维码类型 type_layout = QHBoxLayout() type_layout.addWidget(QLabel("码类型:")) self.type_combo = QComboBox() self.type_combo.addItem("QR码", "QRCode") self.type_combo.addItem("条形码", "Barcode") self.type_combo.addItem("抖音码", "DouyinCode") self.type_combo.currentIndexChanged.connect(self.on_type_changed) type_layout.addWidget(self.type_combo) layout.addLayout(type_layout) self.setLayout(layout) def update_ui(self): if self.region: self.name_edit.setText(self.region.get_name()) index = self.type_combo.findData(self.region.code_type) if index >= 0: self.type_combo.setCurrentIndex(index) def on_name_changed(self, name: str): if self.region: self.region.set_name(name) self.propertyChanged.emit('name', name) def on_type_changed(self, index: int): if self.region: code_type = self.type_combo.currentData() self.region.code_type = code_type self.propertyChanged.emit('code_type', code_type) class LogoPropertiesPanel(PropertiesPanel): def __init__(self, parent=None): super().__init__(parent) self.name_edit = None self.opacity_spin = None self.replace_btn = None self.setup_ui() def setup_ui(self): layout = QVBoxLayout() # 名称设置 name_layout = QHBoxLayout() name_layout.addWidget(QLabel("区域名称:")) self.name_edit = QLineEdit() self.name_edit.textChanged.connect(self.on_name_changed) name_layout.addWidget(self.name_edit) layout.addLayout(name_layout) # 不透明度 opacity_layout = QHBoxLayout() opacity_layout.addWidget(QLabel("不透明度:")) self.opacity_spin = QDoubleSpinBox() self.opacity_spin.setRange(0.0, 1.0) self.opacity_spin.setSingleStep(0.1) self.opacity_spin.setValue(1.0) self.opacity_spin.valueChanged.connect(self.on_opacity_changed) opacity_layout.addWidget(self.opacity_spin) layout.addLayout(opacity_layout) # 替换Logo按钮 self.replace_btn = QPushButton("替换Logo") self.replace_btn.clicked.connect(self.replace_logo) layout.addWidget(self.replace_btn) self.setLayout(layout) def update_ui(self): if self.region: self.name_edit.setText(self.region.get_name()) self.opacity_spin.setValue(self.region.opacity) def on_name_changed(self, name: str): if self.region: self.region.set_name(name) self.propertyChanged.emit('name', name) def on_opacity_changed(self, opacity: float): if self.region: self.region.set_opacity(opacity) self.propertyChanged.emit('opacity', opacity) def replace_logo(self): if self.region: # 打开文件对话框选择Logo图片 file_path, _ = QFileDialog.getOpenFileName( self, "选择Logo图片", self.data_path, "图像文件 (*.png *.jpg *.jpeg *.bmp);;PDF文件 (*.pdf)" ) if file_path: # 处理PDF Logo if file_path.lower().endswith('.pdf'): temp_img = pdf_to_image(file_path) if temp_img is not None: height, width, _ = temp_img.shape bytes_per_line = 3 * width qimage = QImage(temp_img.data, width, height, bytes_per_line, QImage.Format.Format_RGB888) pixmap = QPixmap.fromImage(qimage) else: return else: pixmap = QPixmap(file_path) if not pixmap.isNull(): self.region.logo_item.setPixmap(pixmap) # 更新Logo尺寸 rect = self.region.rect() self.region.logo_item.setScale(min( rect.width() / pixmap.width(), rect.height() / pixmap.height() )) class TemplateCanvas(QGraphicsView): MODE_NONE = 0 MODE_SELECT = 1 MODE_TEXT = 2 MODE_QR = 3 MODE_LOGO = 4 def __init__(self, parent=None, data_path: str = None): super().__init__(parent) self.scene = QGraphicsScene() self.setScene(self.scene) self.setRenderHint(QPainter.RenderHint.Antialiasing, True) self.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True) self.setDragMode(QGraphicsView.DragMode.RubberBandDrag) self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) # 初始化变量 self.data_path = data_path or os.path.join(os.path.dirname(__file__), "../../../data") self.template_item = None self.template_path = None self.current_mode = self.MODE_NONE self.selection_start = None self.current_selection = None self.regions = [] # 存储所有区域 self.selected_region = None self.zoom_factor = 1.0 # 设置默认属性 self.text_font = QFont("Arial", 12) self.text_align = Qt.AlignmentFlag.AlignLeft self.text_color = QColor(Qt.GlobalColor.black) # 设置背景 self.setStyleSheet("background-color: #f0f0f0;") # 创建上下文菜单 self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self.show_context_menu) def load_template(self, file_path: str) -> bool: """加载模板图片或PDF""" self.clear_scene() self.template_path = file_path # 处理PDF文件 if file_path.lower().endswith('.pdf'): # 将PDF转换为临时图片 temp_img = pdf_to_image(file_path) if temp_img is None: return False # 转换为QPixmap height, width, channel = temp_img.shape bytes_per_line = 3 * width qimage = QImage(temp_img.data, width, height, bytes_per_line, QImage.Format.Format_RGB888) pixmap = QPixmap.fromImage(qimage) else: # 直接加载图片 pixmap = QPixmap(file_path) if pixmap.isNull(): return False # 添加模板到场景 self.template_item = self.scene.addPixmap(pixmap) self.template_item.setZValue(-1) # 确保在底层 # 调整视图 self.fitInView(self.template_item, Qt.AspectRatioMode.KeepAspectRatio) self.zoom_factor = self.transform().m11() # 记录当前缩放因子 return True def clear_scene(self): """清除场景内容""" self.scene.clear() self.template_item = None self.template_path = None self.regions = [] self.selected_region = None self.zoom_factor = 1.0 def set_mode(self, mode: int): """设置当前操作模式""" self.current_mode = mode # 更新光标 if mode == self.MODE_TEXT: self.setCursor(Qt.CursorShape.CrossCursor) elif mode == self.MODE_QR: self.setCursor(Qt.CursorShape.CrossCursor) elif mode == self.MODE_LOGO: self.setCursor(Qt.CursorShape.PointingHandCursor) else: self.setCursor(Qt.CursorShape.ArrowCursor) def mousePressEvent(self, event): """处理鼠标按下事件""" if event.button() == Qt.MouseButton.LeftButton: scene_pos = self.mapToScene(event.position().toPoint()) # 检查是否点击了现有区域 items = self.scene.items(scene_pos) for item in items: if isinstance(item, RegionItem): self.select_region(item) return # 根据模式处理 if self.current_mode in (self.MODE_TEXT, self.MODE_QR, self.MODE_LOGO): self.selection_start = scene_pos self.current_selection = None else: super().mousePressEvent(event) else: super().mousePressEvent(event) def mouseMoveEvent(self, event): """处理鼠标移动事件""" if self.selection_start and event.buttons() & Qt.MouseButton.LeftButton: scene_pos = self.mapToScene(event.position().toPoint()) # 计算选择矩形 rect = QRectF(self.selection_start, scene_pos).normalized() # 创建或更新选择框 if not self.current_selection: self.current_selection = SelectionRectItem(rect) self.scene.addItem(self.current_selection) else: self.current_selection.setRect(rect) else: super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): """处理鼠标释放事件""" if self.current_selection and event.button() == Qt.MouseButton.LeftButton: rect = self.current_selection.rect() self.scene.removeItem(self.current_selection) self.current_selection = None # 确保矩形有效 if rect.width() > 5 and rect.height() > 5: # 根据模式创建区域 if self.current_mode == self.MODE_TEXT: self.add_text_region(rect) elif self.current_mode == self.MODE_QR: self.add_qr_region(rect) elif self.current_mode == self.MODE_LOGO: self.add_logo_region(rect) self.selection_start = None else: super().mouseReleaseEvent(event) def wheelEvent(self, event): """处理滚轮缩放事件""" zoom_factor = 1.2 if event.angleDelta().y() > 0 else 1 / 1.2 self.zoom(zoom_factor) def zoom(self, factor: float): """缩放视图""" self.scale(factor, factor) self.zoom_factor *= factor def reset_zoom(self): """重置缩放""" self.resetTransform() if self.template_item: self.fitInView(self.template_item, Qt.AspectRatioMode.KeepAspectRatio) self.zoom_factor = self.transform().m11() def add_text_region(self, rect: QRectF): """添加文本区域""" region = TextRegionItem(rect) region.set_font(self.text_font) region.set_align(self.text_align) region.set_color(self.text_color) self.scene.addItem(region) self.regions.append(region) self.select_region(region) self.notify_regions_changed() # 通知主窗口更新区域信息 def add_qr_region(self, rect: QRectF): """添加二维码区域""" region = QRRegionItem(rect) self.scene.addItem(region) self.regions.append(region) self.select_region(region) self.notify_regions_changed() # 通知主窗口更新区域信息 def add_logo_region(self, rect: QRectF): """添加Logo区域""" # 打开文件对话框选择Logo图片 file_path, _ = QFileDialog.getOpenFileName( self, "选择Logo图片", self.data_path, "图像文件 (*.png *.jpg *.jpeg *.bmp);;PDF文件 (*.pdf)" ) if file_path: # 处理PDF Logo if file_path.lower().endswith('.pdf'): temp_img = pdf_to_image(file_path) if temp_img is not None: height, width, _ = temp_img.shape bytes_per_line = 3 * width qimage = QImage(temp_img.data, width, height, bytes_per_line, QImage.Format.Format_RGB888) pixmap = QPixmap.fromImage(qimage) else: return else: pixmap = QPixmap(file_path) if not pixmap.isNull(): region = LogoRegionItem(rect, pixmap) self.scene.addItem(region) self.regions.append(region) self.select_region(region) self.notify_regions_changed() # 通知主窗口更新区域信息 def select_region(self, region: RegionItem): """选择区域""" # 取消之前的选择 if self.selected_region: self.selected_region.set_selected(False) # 设置新选择 region.set_selected(True) self.selected_region = region # 通知父组件更新属性面板 if self.parent() and hasattr(self.parent(), 'update_property_panel'): self.parent().update_property_panel(region) def get_regions(self): """获取所有区域数据""" return [region.get_data() for region in self.regions] def clear_regions(self): """清除所有区域""" for region in self.regions: self.scene.removeItem(region) self.regions = [] self.selected_region = None self.notify_regions_changed() # 通知主窗口更新区域信息 def notify_regions_changed(self): """通知主窗口区域已更新""" if self.parent() and hasattr(self.parent(), 'update_regions'): self.parent().update_regions() def set_region_property(self, key: str, value: object): """设置选中区域的属性""" if self.selected_region: if key == 'font': self.selected_region.set_font(value) elif key == 'align': self.selected_region.set_align(value) elif key == 'color': self.selected_region.set_color(value) elif key == 'name': self.selected_region.set_name(value) def show_context_menu(self, pos): """显示上下文菜单""" if not self.template_item: return menu = QMenu(self) # 添加缩放选项 zoom_in_action = menu.addAction("放大") zoom_out_action = menu.addAction("缩小") reset_zoom_action = menu.addAction("重置缩放") menu.addSeparator() # 添加区域操作 rename_action = menu.addAction("重命名区域") delete_action = menu.addAction("删除区域") clear_all_action = menu.addAction("清除所有区域") # 执行菜单 action = menu.exec(self.mapToGlobal(pos)) # 处理操作 if action == zoom_in_action: self.zoom(1.2) elif action == zoom_out_action: self.zoom(1 / 1.2) elif action == reset_zoom_action: self.reset_zoom() elif action == rename_action and self.selected_region: self.rename_selected_region() elif action == delete_action and self.selected_region: self.delete_selected_region() elif action == clear_all_action: self.clear_regions() def rename_selected_region(self): """重命名选中区域""" if not self.selected_region: return name, ok = QInputDialog.getText( self, "重命名区域", "输入区域名称:", text=self.selected_region.get_name() ) if ok and name: self.selected_region.set_name(name) if self.parent() and hasattr(self.parent(), 'update_property_panel'): self.parent().update_property_panel(self.selected_region) def delete_selected_region(self): """删除选中区域""" if self.selected_region: self.scene.removeItem(self.selected_region) self.regions.remove(self.selected_region) self.selected_region = None self.notify_regions_changed() # 通知主窗口更新区域信息 def export_preview(self, output_path: str) -> bool: """导出预览图像""" if not self.template_item: return False # 创建打印机对象 printer = QPrinter() printer.setOutputFileName(output_path) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setPaperSize(QSize(self.scene.width(), self.scene.height()), QPrinter.Unit.DevicePixel) printer.setFullPage(True) # 创建绘制器 painter = QPainter(printer) painter.setRenderHint(QPainter.RenderHint.Antialiasing) # 渲染整个场景 self.scene.render(painter) painter.end() return True class RegionItem(QGraphicsRectItem): """区域基类""" def __init__(self, rect: QRectF, parent=None): super().__init__(rect, parent) self.setFlags( QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QGraphicsItem.GraphicsItemFlag.ItemIsMovable ) self.setAcceptHoverEvents(True) # 区域属性 self.name = "未命名区域" self.selected = False self.hovered = False # 设置画笔和画刷 self.normal_pen = QPen(Qt.GlobalColor.blue, 2, Qt.PenStyle.DashLine) self.hover_pen = QPen(Qt.GlobalColor.darkBlue, 3, Qt.PenStyle.DashLine) self.selected_pen = QPen(Qt.GlobalColor.red, 3, Qt.PenStyle.DashLine) self.setPen(self.normal_pen) def set_selected(self, selected: bool): """设置选中状态""" self.selected = selected self.update_pen() def set_hovered(self, hovered: bool): """设置悬停状态""" self.hovered = hovered self.update_pen() def update_pen(self): """更新画笔样式""" if self.selected: self.setPen(self.selected_pen) elif self.hovered: self.setPen(self.hover_pen) else: self.setPen(self.normal_pen) def hoverEnterEvent(self, event): self.set_hovered(True) super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.set_hovered(False) super().hoverLeaveEvent(event) def mousePressEvent(self, event): # 通知画布选择此区域 if self.scene() and self.scene().views(): view = self.scene().views()[0] if isinstance(view, TemplateCanvas): view.select_region(self) super().mousePressEvent(event) def set_name(self, name: str): """设置区域名称""" self.name = name def get_name(self) -> str: """获取区域名称""" return self.name def get_data(self) -> dict: """获取区域数据""" return { 'type': 'base', 'name': self.name, 'rect': self.rect(), 'pos': self.pos() } class TextRegionItem(RegionItem): """文本区域""" def __init__(self, rect: QRectF, parent=None): super().__init__(rect, parent) self.font = QFont("Arial", 12) self.align = Qt.AlignmentFlag.AlignLeft self.color = QColor(Qt.GlobalColor.black) # 添加文本标签 self.text_label = QGraphicsSimpleTextItem(self.name, self) self.text_label.setBrush(Qt.GlobalColor.white) self.text_label.setFont(QFont("Arial", 8)) self.text_label.setPos(5, 5) self.update_label() def set_font(self, font: QFont): """设置字体""" self.font = font self.update_label() def set_align(self, align: Qt.AlignmentFlag): """设置对齐方式""" self.align = align self.update_label() def set_color(self, color: QColor): """设置文本颜色""" self.color = color self.update_label() def set_name(self, name: str): """设置区域名称""" super().set_name(name) self.text_label.setText(name) def update_label(self): """更新文本标签样式""" self.text_label.setFont(self.font) self.text_label.setBrush(self.color) def get_data(self) -> dict: """获取区域数据""" data = super().get_data() data.update({ 'type': 'text', 'font': self.font.toString(), 'align': self.align, 'color': self.color.name(), 'font_size': self.font.pointSize(), 'font_family': self.font.family() }) return data class QRRegionItem(RegionItem): """二维码区域""" def __init__(self, rect: QRectF, parent=None): super().__init__(rect, parent) self.code_type = "QRCode" # 添加类型标签 self.type_label = QGraphicsSimpleTextItem("QR", self) self.type_label.setBrush(Qt.GlobalColor.white) self.type_label.setFont(QFont("Arial", 8, QFont.Weight.Bold)) self.type_label.setPos(5, 5) def set_name(self, name: str): """设置区域名称""" super().set_name(name) self.type_label.setText(name[:2]) # 只显示前两个字符 def get_data(self) -> dict: """获取区域数据""" data = super().get_data() data.update({ 'type': 'qr', 'code_type': self.code_type }) return data class LogoRegionItem(RegionItem): """Logo区域""" def __init__(self, rect: QRectF, pixmap: QPixmap, parent=None): super().__init__(rect, parent) self.pixmap = pixmap self.opacity = 1.0 # 创建Logo图像项 self.logo_item = QGraphicsPixmapItem(self.pixmap, self) self.logo_item.setTransformationMode(Qt.TransformationMode.SmoothTransformation) self.logo_item.setPos(rect.topLeft()) # 设置Logo尺寸 self.logo_item.setScale(min( rect.width() / self.pixmap.width(), rect.height() / self.pixmap.height() )) # 添加类型标签 self.type_label = QGraphicsSimpleTextItem("LOGO", self) self.type_label.setBrush(Qt.GlobalColor.white) self.type_label.setFont(QFont("Arial", 8, QFont.Weight.Bold)) self.type_label.setPos(5, 5) def set_opacity(self, opacity: float): """设置不透明度""" self.opacity = opacity self.logo_item.setOpacity(opacity) def set_name(self, name: str): """设置区域名称""" super().set_name(name) self.type_label.setText(name[:4]) # 只显示前四个字符 def get_data(self) -> dict: """获取区域数据""" data = super().get_data() data.update({ 'type': 'logo', 'opacity': self.opacity, 'logo_path': "" # 实际路径需要特殊处理 }) return data class SelectionRectItem(QGraphicsRectItem): """选择矩形框""" def __init__(self, rect: QRectF, parent=None): super().__init__(rect, parent) self.setPen(QPen(Qt.GlobalColor.darkGreen, 2, Qt.PenStyle.DashLine)) self.setBrush(QBrush(QColor(0, 255, 0, 50))) ``` ### 主要改进点 1. **类型提示**:为所有方法和函数的参数和返回值添加了类型提示,增强了代码的可读性和可靠性。 2. **参数一致性**:确保所有方法的参数定义与其调用方式一致,特别是涉及继承和多态的地方。 3. **错误处理**:增加了更多的错误处理逻辑,提高了代码的健壮性。 4. **注释和文档**:为关键方法和类添加了注释和文档字符串,帮助理解和维护代码。 通过这些调整,代码应该更加稳定和易于维护。如果您有进一步的问题或需要调整,请随时告知。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值