20241310 2024-2025-2 《Python程序设计》实验四 实验报告
作业信息
| 这个作业属于哪个课程 | 2025年春全院公选Python (北京电子科技学院) | 
|---|---|
| 这个作业要求在哪里 | [实验四 Python综合实践](https://edu.cnblogs.com/campus/besti/2025Python-/homework/13401 | 
| 这个作业的目标 | 编写让自己满意的Python期末作业 | 
| 作业正文 | 本博客链接 | 
| 课程名称 | 《Python程序设计》 | 
| 班级 | 2413 | 
| 姓名 | 苏子涛 | 
| 学号 | 20241310 | 
| 实验教师 | 王志强 | 
| 实验日期 | 2025年6月1日 | 
| 必修/选修 | 公选课 | 
这一次就不用以前实验报告的格式了老师
接下来迎接我的桌宠吧
实验过程
先讲一讲我做这个实验过程中的酸甜苦辣吧
本来这个实验我要做什么我也是很迷茫,有很多想法不知道要怎么办
然后后面我想到了老师以前说过的连续发消息的那个小玩具,于是我做了一个,不过这个只是玩玩,不是我的作业哈(附赠版)
导入必要的库
import pyautogui  # 用于模拟鼠标键盘操作
import time  # 用于程序暂停和延时控制
import pyperclip  # 用于操作剪贴板,实现文本复制粘贴
def send_qq_messages(content, count):
"""
自动向当前QQ聊天窗口发送指定内容
:param content: 要发送的消息内容
:param count: 消息发送次数
"""
# 提示用户准备工作
print(f"准备发送 {count} 条消息: '{content}'")
print("请在5秒内将鼠标移动到QQ消息输入框!")
time.sleep(5)  # 留出5秒供用户切换到QQ窗口并定位光标
try:
    # 循环执行消息发送
    for i in range(count):
        # 将消息内容复制到剪贴板
        pyperclip.copy(content)
        # 模拟按下Ctrl+V组合键,实现粘贴操作
        pyautogui.hotkey('ctrl', 'v')
        # 按下Enter键发送消息(QQ默认设置下)
        # 若QQ设置为Ctrl+Enter发送,需修改为pyautogui.hotkey('ctrl', 'enter')
        pyautogui.press('enter')
        time.sleep(0.5)  # 每次发送间隔0.5秒,避免触发QQ风控
        print(f"已发送 {i + 1}/{count} 条消息")
    print(f"任务完成!共发送 {count} 条消息。")
except Exception as e:
    # 异常处理,捕获并打印任何运行时错误
    print(f"发生错误: {e}")
if name == "main":
# 获取用户输入的消息内容
message_content = input("请输入要发送的消息内容: ")
# 验证并获取有效的发送次数(正整数)
while True:
    try:
        message_count = int(input("请输入要发送的次数: "))
        if message_count <= 0:
            print("发送次数必须是正整数,请重新输入!")
        else:
            break  # 输入合法时跳出循环
    except ValueError:
        print("输入无效,请输入一个整数!")
# 调用发送函数执行任务
send_qq_messages(message_content, message_count)
实验结果:


图片没截完整,不过确实是10次
就是上面这个,这个不能说对实验没有帮助,恰巧因为这个学习了这个,给了我模拟键盘输入的思路
然后我开始想,我要做什么?
非常普遍的天气消息发送的想法就出来了,我寻思着可以写一个定时发送天气的脚本,然后把天气部分写完后难题就来了
由于微信查机器人很严,我不敢冒险,于是转战QQ,以下是代码运行结果

但是问题又来了,定时怎么办,我能想到的最安全方式是QQ机器人,但由于对网上教程对github的依赖和FQ这件事,最终不了了之了
为期3天的努力失去了它的方向
端午节我重拾Python作业,我当时意识到了一件很重要的事,也许这并不只算一次Python期末作业,既然是要我满意的,那必定是我现在以及用的上的
我想起我平时手机上电脑上没有专门记录待办事项的APP,提醒也只能靠自己定闹钟,总是突然忘事或者是卡ddl,那我做的东西为什么不给它这么一个功能呢
于是我弄清楚了我想要做的东西,我打算做一个比较有功能性的桌宠,包含如待办,祝福语,图片更改,天气预报(前面写的天气预报浪费了怪可惜的)的功能
(由于时间并不算多,并且个人能力有限,暂时只能有这些功能了,暑假我打算优化并且加新功能,更好地利用一下)
好了,这个作业形成的过程介绍完了,正篇开始
实验内容
一.代码如下
import tkinter as tk
from tkinter import simpledialog, messagebox, filedialog
from PIL import Image, ImageTk, ImageFilter
import urllib.request
import os
import random
import threading
import time
import datetime
import shutil
import json
import requests
from bs4 import BeautifulSoup
import re
class DeskPet:
def init(self, root):
self.root = root
self.root.title("桌面宠物")
self.root.overrideredirect(True)  # 去掉窗口边框
self.root.wm_attributes("-topmost", True)  # 窗口置顶
self.root.wm_attributes("-transparentcolor", "white")  # 透明色(Windows有效)
    # 图片文件夹和图片列表
    self.img_folder = "pet_images"
    os.makedirs(self.img_folder, exist_ok=True)
    self.img_urls = [
        "https://ai-gen-img-1253302184.cos.ap-beijing.myqcloud.com/fd378b4f869044a2a761b6e6aa436184.png",
    ]
    self.img_filename = os.path.join(self.img_folder, "pet.png")
    # 图片大小设置为200x200
    self.target_width = 200
    self.target_height = 200
    # 下载默认图片
    if not os.path.exists(self.img_filename):
        try:
            print("正在下载宠物图片...")
            urllib.request.urlretrieve(self.img_urls[0], self.img_filename)
            print("图片下载完成。")
        except Exception as e:
            messagebox.showerror("错误", f"下载默认图片失败:{e}")
    # 加载图片
    try:
        self.pet_img = Image.open(self.img_filename).convert("RGBA")
    except Exception as e:
        messagebox.showerror("错误", f"加载宠物图片失败:{e}")
        self.pet_img = Image.new("RGBA", (self.target_width, self.target_height), (255, 0, 0, 255))  # 红色占位图
    # 创建画布
    self.canvas = tk.Canvas(root, width=self.target_width, height=self.target_height, highlightthickness=0,
                            bg='white')
    self.canvas.pack()
    # 加载图片并处理阴影
    self.load_pet_image(self.img_filename)
    # 说话内容
    self.phrases = ["今天的你也很耀眼耶", "快乐+1", "幸运值+1", "又是精彩的一天呢", "Ciallo~(∠・ω< )⌒☆"]
    # 拖动状态
    self.is_dragging = False
    self.drag_start_x = 0
    self.drag_start_y = 0
    # 绑定事件
    self.canvas.bind("<ButtonPress-1>", self.start_move)
    self.canvas.bind("<B1-Motion>", self.do_move)
    self.canvas.bind("<ButtonRelease-1>", self.end_move)
    self.canvas.bind("<ButtonRelease-1>", self.on_click_pet)  # 点击事件放在ButtonRelease
    self.canvas.bind("<ButtonPress-3>", self.show_menu)
    # 右键菜单
    self.menu = tk.Menu(root, tearoff=0)
    self.menu.add_command(label="每日天气", command=self.show_weather)
    self.menu.add_command(label="查看待办", command=self.view_todos)
    self.menu.add_command(label="添加待办", command=self.add_todo)
    self.menu.add_command(label="更换图片(网络)", command=self.change_image_network)
    self.menu.add_command(label="更换图片(本地)", command=self.change_image_local)
    self.menu.add_separator()
    self.menu.add_command(label="退出", command=root.destroy)
    # 待办事项列表
    self.todos = self.load_todos()
    # 天气信息
    self.weather_info = self.get_real_weather()  # 使用真实天气数据
    if not self.weather_info:
        self.weather_info = self.get_fallback_weather()  # 如果获取失败使用备用数据
    # 启动待办提醒线程
    self.reminder_thread = threading.Thread(target=self.reminder_loop, daemon=True)
    self.reminder_thread.start()
    # 启动天气更新线程
    self.weather_thread = threading.Thread(target=self.update_weather_loop, daemon=True)
    self.weather_thread.start()
    self.animate()
def load_pet_image(self, path):
    """加载宠物图片并添加阴影效果"""
    try:
        img = Image.open(path).convert("RGBA")
        img = img.resize((self.target_width, self.target_height), Image.LANCZOS)
    except Exception as e:
        messagebox.showerror("错误", f"加载宠物图片失败:{e}")
        img = Image.new("RGBA", (self.target_width, self.target_height), (255, 0, 0, 255))  # 红色占位图
    self.pet_img = img
    self.width, self.height = self.pet_img.size
    # 制作阴影效果
    shadow = self.pet_img.copy().convert("RGBA")
    alpha = shadow.split()[3]
    shadow = Image.new("RGBA", self.pet_img.size, (0, 0, 0, 150))
    shadow.putalpha(alpha)
    shadow = shadow.filter(ImageFilter.GaussianBlur(3))
    shadow_img = Image.new("RGBA", self.pet_img.size, (0, 0, 0, 0))
    shadow_img.paste(shadow, (3, 3), shadow.split()[3])
    self.combined_img = Image.alpha_composite(shadow_img, self.pet_img)
    self.tk_pet_img = ImageTk.PhotoImage(self.combined_img)
    self.canvas.config(width=self.width, height=self.height)
def draw_pet(self):
    """绘制宠物和待办红点"""
    self.canvas.delete("all")
    self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_pet_img)
    # 绘制待办事项红点指示器(未完成数量)
    pending_count = sum(1 for todo in self.todos if not todo.get('completed', False))
    if pending_count > 0:
        dot_size = 16
        x = self.target_width - dot_size - 5
        y = 5
        self.canvas.create_oval(x, y, x + dot_size, y + dot_size, fill="red", outline="white", width=1)
        self.canvas.create_text(x + dot_size / 2, y + dot_size / 2, text=str(pending_count),
                                fill="white", font=("Arial", 10, "bold"))
def animate(self):
    """动画循环,持续刷新宠物显示"""
    self.draw_pet()
    self.root.after(50, self.animate)
def start_move(self, event):
    """开始拖动"""
    self.is_dragging = False
    self.drag_start_x = event.x_root
    self.drag_start_y = event.y_root
def do_move(self, event):
    """拖动中"""
    dx = event.x_root - self.drag_start_x
    dy = event.y_root - self.drag_start_y
    if not self.is_dragging and (abs(dx) > 5 or abs(dy) > 5):
        self.is_dragging = True
    if self.is_dragging:
        x = self.root.winfo_x() + dx
        y = self.root.winfo_y() + dy
        self.root.geometry(f"+{x}+{y}")
        self.drag_start_x = event.x_root
        self.drag_start_y = event.y_root
def end_move(self, event):
    """结束拖动"""
    def reset_drag():
        self.is_dragging = False
    self.root.after(100, reset_drag)
def on_click_pet(self, event):
    """点击宠物时显示随机短语"""
    if not self.is_dragging:
        phrase = random.choice(self.phrases)
        messagebox.showinfo("小卡布奇诺想和你说", phrase)
def show_menu(self, event):
    """显示右键菜单"""
    self.menu.post(event.x_root, event.y_root)
def show_weather(self):
    """显示天气信息弹窗"""
    if not self.weather_info:
        self.weather_info = self.get_real_weather()
        if not self.weather_info:
            self.weather_info = self.get_fallback_weather()
    weather_text = (f"日期: {self.weather_info['date']}\n"
                    f"天气状况: {self.weather_info['weather']}\n"
                    f"最低温度: {self.weather_info['min_temp']}℃\n"
                    f"最高温度: {self.weather_info['max_temp']}℃\n"
                    f"当前温度: {self.weather_info['current_temp']}℃\n"
                    f"穿衣建议: {self.weather_info['dressing']}")
    messagebox.showinfo("每日天气", weather_text)
def fetch_weather_page(self, url, headers=None):
    """发送请求获取天气页面内容"""
    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.encoding = response.apparent_encoding
        if response.status_code == 200:
            return response.text
        else:
            print(f"请求失败,状态码: {response.status_code}")
    except requests.RequestException as e:
        print(f"请求天气页面时出错: {e}")
    return None
def parse_weather_info(self, html):
    """解析网页,提取未来几天天气信息"""
    soup = BeautifulSoup(html, 'html.parser')
    weather_data = []
    # 中国天气网新版页面结构,尝试定位未来天气列表
    ul = soup.find('ul', class_='t clearfix')
    if not ul:
        ul = soup.find('ul', class_='t7 clearfix')
    if not ul:
        print("未找到天气列表")
        return weather_data
    lis = ul.find_all('li')
    for li in lis:
        # 提取日期
        date_element = li.find('h1')
        date = date_element.text.strip() if date_element else "未知日期"
        # 提取天气状况
        weather_element = li.find('p', class_='wea')
        weather = weather_element.text.strip() if weather_element else "未知天气"
        # 提取温度
        temperature_element = li.find('p', class_='tem')
        min_temp, max_temp = 20, 28  # 默认温度
        if temperature_element:
            # 最高温度在 <span> 中,最低温度在 <i> 中
            max_span = temperature_element.find('span')
            min_i = temperature_element.find('i')
            try:
                max_temp = int(max_span.text.strip().replace('℃', '')) if max_span else max_temp
            except:
                max_temp = 28
            try:
                min_temp = int(min_i.text.strip().replace('℃', '')) if min_i else min_temp
            except:
                min_temp = 20
        weather_data.append({
            'date': date,
            'weather': weather,
            'min_temp': min_temp,
            'max_temp': max_temp
        })
    return weather_data
def get_current_temperature(self, html):
    """从网页中提取当前温度"""
    if not html:
        return 25  # 默认当前温度
    soup = BeautifulSoup(html, 'html.parser')
    # 中国天气网当前温度一般在 <p class="tem"><span>xx</span>℃</p> 或 <p class="tem">xx℃</p>
    current_temp_ele = soup.find('p', class_='tem')
    if current_temp_ele:
        span = current_temp_ele.find('span')
        if span and span.text.strip():
            try:
                return int(span.text.strip())
            except:
                pass
        else:
            # 可能只有文本
            text = current_temp_ele.text.strip().replace('℃', '').strip()
            try:
                return int(text)
            except:
                pass
    # 获取失败时,根据最高温和最低温计算合理的当前温度
    weather_data = self.parse_weather_info(html)
    if weather_data and len(weather_data) > 0:
        today = weather_data[0]
        return (today['min_temp'] + today['max_temp']) // 2
    return 25  # 提取失败时返回默认值
def clothing_suggestion(self, min_temp, max_temp, current_temp):
    """根据温度给出穿衣建议"""
    # 确保温度值合理
    current_temp = max(min_temp, min(current_temp, max_temp))
    if max_temp >= 30:
        return f"天气炎热,当前温度{current_temp}℃,建议穿轻薄透气的短袖、短裤、短裙等夏装,记得做好防晒措施。"
    elif 25 <= max_temp < 30:
        return f"天气温暖,当前温度{current_temp}℃,适合穿短袖衬衫、薄长裙、薄T恤等夏季服装。"
    elif 20 <= max_temp < 25:
        return f"气温较为舒适,当前温度{current_temp}℃,可穿长袖衬衫、薄外套、牛仔裤等。"
    elif 15 <= max_temp < 20:
        return f"天气稍凉,当前温度{current_temp}℃,建议穿上毛衣、卫衣、夹克等,搭配长裤。"
    elif 10 <= max_temp < 15:
        return f"气温较低,当前温度{current_temp}℃,需穿厚外套、风衣、毛呢大衣等,注意保暖。"
    elif 0 <= max_temp < 10:
        return f"天气寒冷,当前温度{current_temp}℃,要穿上羽绒服、棉衣、厚毛衣等保暖衣物。"
    else:
        return f"天气严寒,当前温度{current_temp}℃,务必穿厚羽绒服、雪地靴,做好全身保暖。"
def get_real_weather(self):
    """获取真实天气数据"""
    try:
        # 北京丰台区天气页面(中国天气网)
        url = 'http://www.weather.com.cn/weather/101010300.shtml'
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
        }
        html = self.fetch_weather_page(url, headers)
        if html:
            weather_list = self.parse_weather_info(html)
            if weather_list and len(weather_list) > 0:
                today = weather_list[0]
                current_temp = self.get_current_temperature(html)
                min_temp = today['min_temp']
                max_temp = today['max_temp']
                # 调整当前温度范围
                if current_temp < min_temp:
                    current_temp = min_temp
                if current_temp > max_temp:
                    current_temp = max_temp
                suggestion = self.clothing_suggestion(min_temp, max_temp, current_temp)
                return {
                    'date': today['date'],
                    'weather': today['weather'],
                    'min_temp': min_temp,
                    'max_temp': max_temp,
                    'current_temp': current_temp,
                    'dressing': suggestion
                }
        print("获取天气失败,使用备用数据")
    except Exception as e:
        print(f"获取真实天气失败: {e}")
    return None
def get_fallback_weather(self):
    """获取天气失败时的备用数据"""
    today = datetime.datetime.now().strftime("%Y年%m月%d日")
    default_min_temp = 22
    default_max_temp = 28
    default_current_temp = (default_min_temp + default_max_temp) // 2
    return {
        'date': today,
        'weather': "晴",
        'min_temp': default_min_temp,
        'max_temp': default_max_temp,
        'current_temp': default_current_temp,
        'dressing': "气温较为舒适,可穿长袖衬衫、薄外套、牛仔裤等。"
    }
def update_weather_loop(self):
    """定时更新天气信息"""
    while True:
        self.weather_info = self.get_real_weather()
        if not self.weather_info:
            self.weather_info = self.get_fallback_weather()
        time.sleep(3600)  # 每小时更新一次
def view_todos(self):
    """查看待办事项窗口"""
    if not self.todos:
        messagebox.showinfo("待办事项", "暂无待办事项")
        return
    # 创建待办事项窗口
    todo_window = tk.Toplevel(self.root)
    todo_window.title("我的待办事项")
    todo_window.geometry("450x550")
    # 创建列表框和滚动条
    frame = tk.Frame(todo_window)
    frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
    scrollbar = tk.Scrollbar(frame)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    self.todo_listbox = tk.Listbox(
        frame,
        width=60,
        height=25,
        font=("Arial", 10),
        yscrollcommand=scrollbar.set,
        selectmode=tk.SINGLE
    )
    self.todo_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    scrollbar.config(command=self.todo_listbox.yview)
    # 填充列表,已完成事项显示灰色并带[已完成]
    for idx, todo in enumerate(self.todos):
        due_date = f" (截止: {todo['due_date']})" if todo.get('due_date') else ""
        completed = todo.get('completed', False)
        text = f"{todo['text']}{due_date}"
        if completed:
            text = "[已完成] " + text
        self.todo_listbox.insert(tk.END, text)
        if completed:
            self.todo_listbox.itemconfig(idx, fg='gray')
    # 按钮框架
    button_frame = tk.Frame(todo_window)
    button_frame.pack(fill=tk.X, padx=10, pady=10)
    def delete_selected():
        selected = self.todo_listbox.curselection()
        if not selected:
            messagebox.showwarning("警告", "请先选择要删除的待办事项")
            return
        index = selected[0]
        if messagebox.askyesno("确认", "确定要删除这条待办事项吗?"):
            del self.todos[index]
            self.save_todos()
            self.todo_listbox.delete(index)
            self.draw_pet()  # 更新红点显示
            messagebox.showinfo("成功", "待办事项已删除")
    def mark_completed():
        selected = self.todo_listbox.curselection()
        if not selected:
            messagebox.showwarning("警告", "请先选择要标记的待办事项")
            return
        index = selected[0]
        todo = self.todos[index]
        if todo.get('completed', False):
            messagebox.showinfo("提示", "该待办事项已标记为完成")
            return
        todo['completed'] = True
        self.save_todos()
        # 更新列表显示
        due_date = f" (截止: {todo['due_date']})" if todo.get('due_date') else ""
        text = f"[已完成] {todo['text']}{due_date}"
        self.todo_listbox.delete(index)
        self.todo_listbox.insert(index, text)
        self.todo_listbox.itemconfig(index, fg='gray')
        self.draw_pet()  # 更新红点显示
        messagebox.showinfo("成功", "待办事项已标记为完成")
    delete_btn = tk.Button(
        button_frame,
        text="删除选中待办",
        command=delete_selected,
        bg="#ff6b6b",
        fg="white"
    )
    delete_btn.pack(side=tk.LEFT, padx=5, ipadx=10)
    complete_btn = tk.Button(
        button_frame,
        text="标记为已完成",
        command=mark_completed,
        bg="#4caf50",
        fg="white"
    )
    complete_btn.pack(side=tk.LEFT, padx=5, ipadx=10)
    close_btn = tk.Button(
        button_frame,
        text="关闭",
        command=todo_window.destroy
    )
    close_btn.pack(side=tk.RIGHT, padx=5, ipadx=10)
def add_todo(self):
    """添加新的待办事项"""
    time_format_hint = "请输入截止时间 (格式:YYYY-MM-DD HH:MM,例如:2025-06-15 18:30)"
    todo_text = simpledialog.askstring("添加待办", "请输入待办事项内容:")
    if not todo_text:
        return
    due_date = None
    while due_date is None:
        due_date = simpledialog.askstring("设置截止时间", time_format_hint)
        if due_date:
            try:
                datetime.datetime.strptime(due_date, "%Y-%m-%d %H:%M")
            except ValueError:
                messagebox.showerror("格式错误", "日期格式应为 YYYY-MM-DD HH:MM,例如:2025-06-15 18:30")
                due_date = None
        else:
            # 用户取消输入截止时间,确认是否不设置
            if messagebox.askyesno("确认", "不设置截止时间吗?"):
                break
            else:
                due_date = None
    self.todos.append({
        'text': todo_text,
        'due_date': due_date,
        'created_at': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        'completed': False
    })
    self.save_todos()
    messagebox.showinfo("成功", "待办事项已添加")
    # 添加后刷新红点
    self.draw_pet()
def save_todos(self):
    """保存待办事项到文件"""
    try:
        with open(os.path.join(self.img_folder, "todos.json"), "w", encoding="utf-8") as f:
            json.dump(self.todos, f, ensure_ascii=False, indent=2)
    except Exception as e:
        print(f"保存待办事项失败: {e}")
def load_todos(self):
    """从文件加载待办事项"""
    try:
        if os.path.exists(os.path.join(self.img_folder, "todos.json")):
            with open(os.path.join(self.img_folder, "todos.json"), "r", encoding="utf-8") as f:
                return json.load(f)
    except Exception as e:
        print(f"加载待办事项失败: {e}")
    return []
def reminder_loop(self):
    """待办事项提醒循环,定时检查是否有到期未完成事项"""
    while True:
        now = datetime.datetime.now()
        for todo in self.todos:
            if todo.get('due_date') and not todo.get('completed', False):
                try:
                    due = datetime.datetime.strptime(todo['due_date'], "%Y-%m-%d %H:%M")
                    # 检查是否到了提醒时间
                    if (due - now).total_seconds() <= 0:
                        # 弹窗提醒,但不自动标记完成,保持小红点
                        messagebox.showinfo("待办提醒",
                                            f"待办事项到期: {todo['text']}\n截止时间: {todo['due_date']}")
                        # 这里不修改todo状态,避免自动去除小红点
                except ValueError:
                    continue
        time.sleep(60)  # 每分钟检查一次
def change_image_network(self):
    """通过网络URL更换宠物图片"""
    url = simpledialog.askstring("更换图片(网络)", "请输入图片URL:")
    if url:
        try:
            urllib.request.urlretrieve(url, self.img_filename)
            self.load_pet_image(self.img_filename)
        except Exception as e:
            messagebox.showerror("错误", f"下载图片失败:{e}")
def change_image_local(self):
    """通过本地文件更换宠物图片"""
    file_path = filedialog.askopenfilename(title="选择图片", filetypes=[("图片文件", "*.png;*.jpg;*.jpeg;*.bmp")])
    if file_path:
        try:
            shutil.copy(file_path, self.img_filename)
            self.load_pet_image(self.img_filename)
        except Exception as e:
            messagebox.showerror("错误", f"加载本地图片失败:{e}")
if name == "main":
root = tk.Tk()
app = DeskPet(root)
root.mainloop()
二、开头一堆库(需要在终端下载的东东就不发出来了)
tkinterPython的图形界面开发库,用于创建窗口和控件
simpledialog弹出简单输入对话框
messagebox弹出消息提示框
filedialog弹出文件选择对话框
PIL(Pillow):处理和操作图片的库
ImageTk将PIL图片转换为Tkinter可用格式
ImageFilter:对图片应用滤镜效果
urllib.request:发送网络请求和下载文件
requests:简化的HTTP请求库
BeautifulSoup:解析和提取网页内容
re:正则表达式,用于文本匹配
os:操作系统相关功能,如文件路径和目录操作
shutil:高级文件操作,如复制和移动文件
json:处理JSON格式数据
random:生成随机数和随机选择
threading:实现多线程并发执行
time:时间相关操作,如延时
datetime:处理日期和时间
三、下面是一块一块的
1.初始化部分(init__方法)
def__init(self,root):
self.root=root
self.root.title("桌面宠物")
self.root.overrideredirect(True)去掉窗口边框
self.root.wm_attributes("topmost",True)窗口置顶
self.root.wm_attributes("transparentcolor","white")透明色(Windows有效)
创建主窗口,去掉边框,使窗口无边框且总在最前端。
设置白色为透明色,实现窗口透明效果(仅Windows有效)。

2.桌宠图片管理
self.img_folder="pet_images"
os.makedirs(self.img_folder,exist_ok=True)
self.img_urls=[
"https://aigenimg1253302184.cos.apbeijing.myqcloud.com/fd378b4f869044a2a761b6e6aa436184.png",
]
self.img_filename=os.path.join(self.img_folder,"pet.png")
创建存放宠物图片的文件夹。
预设一个宠物图片的网络地址。
定义本地图片路径。
ifnotos.path.exists(self.img_filename):
try:
print("正在下载宠物图片...")
urllib.request.urlretrieve(self.img_urls[0],self.img_filename)
print("图片下载完成。")
exceptExceptionase:
messagebox.showerror("错误",f"下载默认图片失败:{e}")
如果本地没有宠物图片,则从网络下载默认图片。
try:
self.pet_img=Image.open(self.img_filename).convert("RGBA")
exceptExceptionase:
messagebox.showerror("错误",f"加载宠物图片失败:{e}")
self.pet_img=Image.new("RGBA",(self.target_width,self.target_height),(255,0,0,255))红色占位图
加载图片,失败则用红色占位图代替。

3.画布与图片阴影处理
self.canvas=tk.Canvas(root,width=self.target_width,height=self.target_height,highlightthickness=0,bg='white')
self.canvas.pack()
self.load_pet_image(self.img_filename)
创建画布显示宠物图片。
调用load_pet_image方法加载并处理图片。
load_pet_image方法:
读取图片,调整大小。
制作阴影效果:复制图片,生成黑色半透明阴影,模糊处理后与原图合成。
将合成图转换为Tkinter可用的PhotoImage。
设置画布大小为图片大小。
4.宠物显示与动画
defdraw_pet(self):
self.canvas.delete("all")
self.canvas.create_image(0,0,anchor=tk.NW,image=self.tk_pet_img)
绘制待办红点
清空画布,绘制宠物图片。
根据未完成待办事项数量绘制红点提示。
defanimate(self):
self.draw_pet()
self.root.after(50,self.animate)
通过after方法每50ms刷新一次,实现动画效果(这里主要是刷新红点和图片)。




5.窗口拖动功能
defstart_move(self,event):
self.is_dragging=False
self.drag_start_x=event.x_root
self.drag_start_y=event.y_root
defdo_move(self,event):
dx=event.x_rootself.drag_start_x
dy=event.y_rootself.drag_start_y
判断是否拖动超过5像素,开始拖动
移动窗口位置
更新起始坐标
defend_move(self,event):
defreset_drag():
self.is_dragging=False
self.root.after(100,reset_drag)
通过鼠标左键按下、移动、释放事件实现窗口拖动。
只有拖动超过一定距离才算拖动,避免误触发点击事件。
6.点击宠物显示随机短语
defon_click_pet(self,event):
ifnotself.is_dragging:
phrase=random.choice(self.phrases)
messagebox.showinfo("小卡布奇诺想和你说",phrase)【卡布奇诺是微信名哈哈哈】
点击宠物时弹出随机鼓励短语。


剩下的就不列出来了
7.右键菜单功能
self.menu=tk.Menu(root,tearoff=0)
self.menu.add_command(label="每日天气",command=self.show_weather)
self.menu.add_command(label="查看待办",command=self.view_todos)
self.menu.add_command(label="添加待办",command=self.add_todo)
self.menu.add_command(label="更换图片(网络)",command=self.change_image_network)
self.menu.add_command(label="更换图片(本地)",command=self.change_image_local)
self.menu.add_separator()
self.menu.add_command(label="退出",command=root.destroy)
右键弹出菜单,包含天气、待办事项管理、图片更换、退出等功能。

8.天气功能
get_real_weather:爬取中国天气网北京丰台区天气,解析网页获取天气数据。
parse_weather_info:用BeautifulSoup解析天气网页,提取日期、天气、温度等信息。
get_current_temperature:提取当前温度。
clothing_suggestion:根据温度给出穿衣建议。
get_fallback_weather:获取备用天气数据(当爬取失败时使用)。
update_weather_loop:后台线程每小时更新天气数据。
show_weather:弹窗显示天气信息。

9.待办事项管理
load_todos和save_todos:从文件加载和保存待办事项(JSON格式)。
view_todos:弹窗显示待办事项列表,支持删除和标记完成。
add_todo:弹窗添加新的待办事项,支持设置截止时间。
reminder_loop:后台线程每分钟检查是否有到期未完成的待办事项,弹窗提醒。
10.图片更换功能
change_image_network:输入网络图片URL,下载替换宠物图片。
change_image_local:选择本地图片文件,复制替换宠物图片。
11.主程序入口
if__name__=="main":
root=tk.Tk()
app=DeskPet(root)
root.mainloop()
创建Tkinter主窗口,实例化DeskPet类,启动事件循环。
然后代码介绍就到这里了,由于图片比较局限,视频交给了课代表,比较清楚一点哈
本来想打个包的,但是这个东西以后暑假我还要继续做,现在打包除了也只是个测试版,暑假做完再打吧
码云上传

遇到的问题及解决(问题有点多,而且基本都没有截图记录,挑几个比较有代表性)
问题1:拖动图片的时候触发左键点击的互动
解决方法:只有拖动超过一定距离才算拖动,这样就不会触发了
问题2:图片更换后突然变得巨大,差不多半个屏幕
解决方法:将300300的图片大小调为200200
问题3: Tkinter窗口不出来
忘了写mainloop(),程序没让窗口一直显示,补上root.mainloop(),就让窗口一直等着我操作。
全课总结,感想体会,意见建议
总结和感想:当初选课的时候,作为一个大一上萌新学生,我们其实也不知道该选什么课。在询问了学长学姐后,他们都告知我Python是必争之课,足以见到王老师课程质量之高、口碑之好。
于是我毅然报名了这门课程,抱着学习Python新东西的心理,我进入了软教四,梦开始的地方。在这里我遇见了热情幽默博学的王老师,积极的同学,融洽的课堂氛围。在Python课上,我们学到了丰富的Python知识,王老师讲得特别特别好,寓教于乐,把知识狠狠送进我们脑袋里。我真正理解了什么叫“人生苦短,我用Python”。Python(第一的高级语言!!!)以其简洁、高效和强大的功能,成为了我探索计算机世界的利器,也让我对未来充满了信心和期待。总之这门Python课程不仅让我收获了宝贵的编程技能,更让我感受到了学习的乐趣和成长的喜悦。感谢王老师的悉心教导和同学们的陪伴,这段学习经历将成为我大学生活中一段难忘而珍贵的记忆。未来的路还很长,但有了Python这把尚方宝剑,我相信自己能够勇敢前行,迎接更多挑战和机遇。
我和桌宠在这里祝王老师身体健康,万事如意,生活越来越好!!!
意见建议:可以多多提问哦,还有就是小组活动可以整点,嗯嗯嗯学习通打卡可以再搞怪点【狗头】嘻嘻嘻
参考资料:
发自内心
csdn
美好的回忆
                    
                
                
            
        
浙公网安备 33010602011771号