关于Event.REMOVED&Event.ENTER_FRAME的思考

本文通过实例探讨了Flash中ENTER_FRAME事件及MovieClip对象管理的常见误区,包括事件执行不受对象移除影响、移除后的影片剪辑仍会内部播放等问题,并提供了相应的解决建议。

最近在做项目的时候,碰见一个奇怪的现象,自己研究了下,发现对于Event.REMOVED&Event.ENTER_FRAME 自己的认识还不足,对于细节还不是很清楚,相信和我有同一毛病的小菜不再少数,所以发帖给大家提个醒.....
从前一直认为Event.ENTER_FRAME事件是 Sprite对象进入帧事件,当对象被移除(removeChild)的时候 就应该自动停止这个事件,直到对应的对象重新被加入场景中......
这是本人的第一个误区,经测试,对象被移除 事件依旧在执行
,参看例子中的test0.fla

代码:

//场景中的一个mc 默认已添加进舞台
mc.addEventListener("fuck",traceKao);
function traceKao(evt:Event){
    trace("Kao");
}
mc.addEventListener(Event.ENTER_FRAME,removeMc);
function removeMc(evt:Event){
    trace(mc.currentFrame)
    if(mc.currentFrame == 5){
        this.removeChild(mc);
    }
        
}

误区二:当对象是MovieClip类被移除后,将会停止本身影片剪辑的播放.....
实践证明,虽然不现实了,但内部逻辑依旧在播放中........
参看例子中的test1.fla

 

代码:

mc.addEventListener("fuck",traceKao);
function traceKao(evt:Event){
    trace("Kao");
}
mc.addEventListener(Event.ENTER_FRAME,removeMc);
function removeMc(evt:Event){
    trace(mc.currentFrame)
    if(mc.currentFrame == 5){
        this.removeChild(mc);
    }
        
}
mc.addEventListener(Event.REMOVED,removeHandler);
function removeHandler(evt:Event){
    trace("元件被移除");
    mc.removeEventListener(Event.ENTER_FRAME,removeMc);
    mc.removeEventListener("fuck",traceKao)
}
mc.addEventListener(Event.ADDED,addHandler);
function addHandler(evt:Event){
    trace("元件被添加");
}

 

2
3
4
5
元件被移除
元件被移除
元件被添加
mc播放完毕
元件被移除
元件被添加
元件被移除
元件被添加
mc播放完毕
元件被移除
元件被添加
......
影片剪辑不停的在播放,并且在第一帧的时候触发一次添加时间,在第五帧(只触发一次,因为被移除ENTER_FRAME事件了)的时候触发一次移除事件,在最后一帧又触发一次移除事件......
尝试在mc最后一帧加入stop();并删除移除函数 则输出为
2
3
4
............
元件被移除
[object Shape],[object testMC_1]
元件被添加
mc播放完毕
///////////////////end////////////////////////////
参看例子test2.fla
得出结论,原来的MovieClip被替换成Shape对象了,大概是flash播放器节省资源的策略吧........
大家可以尝试把stop()指令前移一帧,则不会发生元件移除事件,MovieClip类对象继续以MovieClip对象存在......
还有一些感觉有些头绪,有有些乱的思维,等整理后在发表意见吧,
根据两点误区 总结:
     ENTER_FRAME 要慎用,因为占用资源,然后要合理的删除,并不是已移除对象当做删除侦听器的方式
      removeChild掉MoviClip对象后,要注意先停止掉MoviClip对象的播放,防止发生逻辑问题.....
欢迎高手指点,和指明其中我没想明白 或者没想透的地方 谢谢

 

确认一下:
根据楼上的意思,是不是要销毁一个对象,只要removeChild(a);a=null;就可以了,a的enterFrame侦听也不用remove了?????

 

回复

如果a的enterFrame侦听器函数就是他本身的一个函数或者方法,就不需要removeEventListener,否则就必须removeEventListener
import tkinter as tk from tkinter import ttk, messagebox, simpledialog import json import os import re class Contact: """联系人类""" def __init__(self, name, phone, email="", address="", region="", company="", title=""): self.name = name self.phone = phone self.email = email self.address = address self.region = region self.company = company self.title = title def to_dict(self): """将联系人对象转换为字典""" return { 'name': self.name, 'phone': self.phone, 'email': self.email, 'address': self.address, 'region': self.region, 'company': self.company, 'title': self.title } @classmethod def from_dict(cls, data): """从字典创建联系人对象""" return cls( data['name'], data['phone'], data.get('email', ''), data.get('address', ''), data.get('region', ''), data.get('company', ''), data.get('title', '') ) def __str__(self): return (f"姓名: {self.name}, 电话: {self.phone}, 邮箱: {self.email}, " f"地址: {self.address}, 地区: {self.region}, 单位: {self.company}, 职务: {self.title}") class AddressBook: """通讯录类""" def __init__(self, filename="address_book.json"): self.filename = filename self.contacts = [] self.load_contacts() def validate_phone(self, phone): """验证电话号码格式""" # 简单的电话号码验证:只包含数字、空格、括号和连字符 pattern = r'^[\d\s\(\)\-]+$' return re.match(pattern, phone) is not None def validate_email(self, email): """验证邮箱格式""" if not email: # 邮箱可选,空值通过验证 return True pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return re.match(pattern, email) is not None def add_contact(self, name, phone, email="", address="", region="", company="", title=""): """添加联系人""" # 验证电话号码格式 if not self.validate_phone(phone): messagebox.showerror("错误", "电话号码格式不正确!") return False # 验证邮箱格式 if not self.validate_email(email): messagebox.showerror("错误", "邮箱格式不正确!") return False # 检查电话是否已存在 for contact in self.contacts: if contact.phone == phone: messagebox.showerror("错误", f"电话 {phone} 已存在!") return False new_contact = Contact(name, phone, email, address, region, company, title) self.contacts.append(new_contact) self.save_contacts() messagebox.showinfo("成功", f"联系人 {name} 添加成功!") return True def delete_contact(self, identifier): """删除联系人,可通过姓名或电话标识""" for i, contact in enumerate(self.contacts): if contact.name == identifier or contact.phone == identifier: removed = self.contacts.pop(i) self.save_contacts() messagebox.showinfo("成功", f"联系人 {removed.name} 删除成功!") return True messagebox.showerror("错误", f"未找到联系人: {identifier}") return False def search_contact(self, identifier): """查找联系人,可通过姓名、电话、地区、单位等标识""" results = [] for contact in self.contacts: if (identifier.lower() in contact.name.lower() or identifier in contact.phone or identifier.lower() in contact.region.lower() or identifier.lower() in contact.company.lower() or identifier.lower() in contact.title.lower()): results.append(contact) return results def update_contact(self, identifier, name=None, phone=None, email=None, address=None, region=None, company=None, title=None): """更新联系人信息""" for contact in self.contacts: if contact.name == identifier or contact.phone == identifier: # 验证新电话号码格式 if phone and not self.validate_phone(phone): messagebox.showerror("错误", "电话号码格式不正确!") return False # 验证新邮箱格式 if email and not self.validate_email(email): messagebox.showerror("错误", "邮箱格式不正确!") return False if name: contact.name = name if phone: # 检查新电话是否已存在(排除当前联系人) if phone != contact.phone and any(c.phone == phone for c in self.contacts): messagebox.showerror("错误", f"电话 {phone} 已存在!") return False contact.phone = phone if email: contact.email = email if address: contact.address = address if region: contact.region = region if company: contact.company = company if title: contact.title = title self.save_contacts() messagebox.showinfo("成功", "联系人信息更新成功!") return True messagebox.showerror("错误", f"未找到联系人: {identifier}") return False def display_all_contacts(self): """显示所有联系人""" return self.contacts def display_contact_details(self, identifier): """显示联系人的详细信息""" results = self.search_contact(identifier) if not results: messagebox.showinfo("提示", f"未找到联系人: {identifier}") return [] return results def get_contacts_by_region(self, region): """按地区筛选联系人""" return [contact for contact in self.contacts if region.lower() in contact.region.lower()] def get_contacts_by_company(self, company): """按单位筛选联系人""" return [contact for contact in self.contacts if company.lower() in contact.company.lower()] def get_contacts_by_title(self, title): """按职务筛选联系人""" return [contact for contact in self.contacts if title.lower() in contact.title.lower()] def save_contacts(self): """保存通讯录到文件""" data = [contact.to_dict() for contact in self.contacts] with open(self.filename, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) def load_contacts(self): """从文件加载通讯录""" if os.path.exists(self.filename): try: with open(self.filename, 'r', encoding='utf-8') as f: data = json.load(f) self.contacts = [Contact.from_dict(item) for item in data] except (json.JSONDecodeError, KeyError): messagebox.showwarning("警告", "通讯录文件损坏,将创建新的通讯录") self.contacts = [] class ContactFormDialog(simpledialog.Dialog): """添加/编辑联系人的对话框""" def __init__(self, parent, title, contact=None): self.contact = contact super().__init__(parent, title) def body(self, master): """创建对话框主体""" # 设置样式 style = ttk.Style() style.configure("Form.TLabel", font=("微软雅黑", 10), foreground="#333333") style.configure("Form.TEntry", font=("微软雅黑", 10)) ttk.Label(master, text="姓名:", style="Form.TLabel").grid(row=0, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="电话:", style="Form.TLabel").grid(row=1, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="邮箱:", style="Form.TLabel").grid(row=2, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="地址:", style="Form.TLabel").grid(row=3, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="地区:", style="Form.TLabel").grid(row=4, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="单位:", style="Form.TLabel").grid(row=5, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="职务:", style="Form.TLabel").grid(row=6, column=0, sticky="w", padx=15, pady=10) self.name_var = tk.StringVar() self.phone_var = tk.StringVar() self.email_var = tk.StringVar() self.address_var = tk.StringVar() self.region_var = tk.StringVar() self.company_var = tk.StringVar() self.title_var = tk.StringVar() # 如果正在编辑现有联系人,填充字段 if self.contact: self.name_var.set(self.contact.name) self.phone_var.set(self.contact.phone) self.email_var.set(self.contact.email) self.address_var.set(self.contact.address) self.region_var.set(self.contact.region) self.company_var.set(self.contact.company) self.title_var.set(self.contact.title) self.name_entry = ttk.Entry(master, textvariable=self.name_var, width=30, style="Form.TEntry") self.phone_entry = ttk.Entry(master, textvariable=self.phone_var, width=30, style="Form.TEntry") self.email_entry = ttk.Entry(master, textvariable=self.email_var, width=30, style="Form.TEntry") self.address_entry = ttk.Entry(master, textvariable=self.address_var, width=30, style="Form.TEntry") self.region_entry = ttk.Entry(master, textvariable=self.region_var, width=30, style="Form.TEntry") self.company_entry = ttk.Entry(master, textvariable=self.company_var, width=30, style="Form.TEntry") self.title_entry = ttk.Entry(master, textvariable=self.title_var, width=30, style="Form.TEntry") self.name_entry.grid(row=0, column=1, padx=15, pady=10) self.phone_entry.grid(row=1, column=1, padx=15, pady=10) self.email_entry.grid(row=2, column=1, padx=15, pady=10) self.address_entry.grid(row=3, column=1, padx=15, pady=10) self.region_entry.grid(row=4, column=1, padx=15, pady=10) self.company_entry.grid(row=5, column=1, padx=15, pady=10) self.title_entry.grid(row=6, column=1, padx=15, pady=10) return self.name_entry # 初始焦点 def apply(self): """应用更改""" self.result = ( self.name_var.get().strip(), self.phone_var.get().strip(), self.email_var.get().strip(), self.address_var.get().strip(), self.region_var.get().strip(), self.company_var.get().strip(), self.title_var.get().strip() ) class ScrollableFrame(ttk.Frame): """可滚动的框架""" def __init__(self, container, *args, **kwargs): super().__init__(container, *args, **kwargs) # 创建Canvas和滚动条 self.canvas = tk.Canvas(self, highlightthickness=0, bg='#f8f9fa') scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview) self.scrollable_frame = ttk.Frame(self.canvas) self.scrollable_frame.bind( "<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")) ) self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") self.canvas.configure(yscrollcommand=scrollbar.set) self.canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 绑定鼠标滚轮事件 self.canvas.bind("<Enter>", self._bind_mousewheel) self.canvas.bind("<Leave>", self._unbind_mousewheel) def _bind_mousewheel(self, event): self.canvas.bind_all("<MouseWheel>", self._on_mousewheel) def _unbind_mousewheel(self, event): self.canvas.unbind_all("<MouseWheel>") def _on_mousewheel(self, event): self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") class ContactDetailWindow: """联系人详情窗口""" def __init__(self, parent, contact): self.contact = contact self.window = tk.Toplevel(parent) self.window.title(f"联系人详情 - {contact.name}") self.window.geometry("550x450") self.window.resizable(True, True) self.window.transient(parent) self.window.configure(bg='#f8f9fa') # 设置窗口位置(居中显示) self.window.update_idletasks() x = parent.winfo_x() + (parent.winfo_width() - self.window.winfo_reqwidth()) // 2 y = parent.winfo_y() + (parent.winfo_height() - self.window.winfo_reqheight()) // 2 self.window.geometry(f"+{x}+{y}") # 延迟设置grab_set,确保窗口已经完全初始化 self.window.after(100, self._set_grab) self.create_widgets() def _set_grab(self): """延迟设置窗口焦点捕获""" try: self.window.grab_set() except tk.TclError: # 如果设置失败,忽略错误 pass def create_widgets(self): """创建详情窗口组件""" # 主框架 main_frame = ttk.Frame(self.window, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 标题 title_frame = ttk.Frame(main_frame) title_frame.pack(fill=tk.X, pady=(0, 10)) # 使用图标和标题 title_icon = ttk.Label(title_frame, text="👤", font=("微软雅黑", 16)) title_icon.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(title_frame, text=self.contact.name, font=("微软雅黑", 18, "bold"), foreground="#2c3e50") title_label.pack(side=tk.LEFT) # 创建可滚动的详情框架 scrollable_frame = ScrollableFrame(main_frame) scrollable_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 详情内容框架 detail_frame = ttk.Frame(scrollable_frame.scrollable_frame, padding=20) detail_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建详情字段 fields = [ ("📞 电话", self.contact.phone, "#3498db"), ("📧 邮箱", self.contact.email if self.contact.email else "未填写", "#e74c3c"), ("🏠 地址", self.contact.address if self.contact.address else "未填写", "#9b59b6"), ("🌍 地区", self.contact.region if self.contact.region else "未填写", "#2ecc71"), ("🏢 单位", self.contact.company if self.contact.company else "未填写", "#f39c12"), ("💼 职务", self.contact.title if self.contact.title else "未填写", "#1abc9c") ] # 使用卡片式布局 for i, (label, value, color) in enumerate(fields): # 创建卡片框架 card_frame = ttk.Frame(detail_frame, relief="solid", borderwidth=1) card_frame.pack(fill=tk.X, pady=8, padx=5) card_frame.configure(style="Card.TFrame") # 标签 lbl = ttk.Label(card_frame, text=label, font=("微软雅黑", 11, "bold"), foreground=color) lbl.pack(anchor="w", padx=15, pady=(10, 5)) # 值 val_lbl = ttk.Label(card_frame, text=value, font=("微软雅黑", 10), foreground="#2c3e50", wraplength=450, justify="left") val_lbl.pack(anchor="w", padx=15, pady=(0, 10)) # 按钮框架 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=(10, 0)) # 关闭按钮 close_btn = ttk.Button(button_frame, text="关闭", command=self.window.destroy, style="Accent.TButton") close_btn.pack(side=tk.RIGHT, padx=5) class AddressBookGUI: """通讯录GUI界面""" def __init__(self, root): self.root = root self.root.title("电子通讯录管理系统") self.root.geometry("1000x700") self.root.configure(bg='#f8f9fa') # 设置应用图标(如果有的话) try: self.root.iconbitmap("address_book.ico") except: pass # 创建通讯录实例 self.address_book = AddressBook() # 设置样式 self.setup_styles() # 创建界面 self.create_widgets() # 刷新联系人列表 self.refresh_contact_list() def setup_styles(self): """设置界面样式""" style = ttk.Style() # 尝试设置现代化主题 try: style.theme_use('clam') except: pass # 配置自定义样式 style.configure("Title.TLabel", font=("微软雅黑", 20, "bold"), background='#f8f9fa', foreground='#2c3e50') style.configure("Subtitle.TLabel", font=("微软雅黑", 11), background='#f8f9fa', foreground='#7f8c8d') style.configure("Custom.TButton", font=("微软雅黑", 10), padding=(12, 8), background='#ecf0f1', foreground='#2c3e50', borderwidth=0, focuscolor='none') style.configure("Accent.TButton", font=("微软雅黑", 10, "bold"), background='#3498db', foreground='white', padding=(12, 8), borderwidth=0, focuscolor='none') style.configure("Search.TButton", font=("微软雅黑", 9), padding=(10, 6), borderwidth=0) style.configure("Treeview", font=("微软雅黑", 10), rowheight=30, background='white', fieldbackground='white', borderwidth=0) style.configure("Treeview.Heading", font=("微软雅黑", 11, "bold"), background='#34495e', foreground='white', borderwidth=0, relief='flat') style.map("Treeview", background=[('selected', '#3498db')], foreground=[('selected', 'white')]) style.map("Accent.TButton", background=[('active', '#2980b9')]) style.map("Custom.TButton", background=[('active', '#d5dbdb')]) style.configure("Card.TFrame", background='white', relief='flat', borderwidth=1) style.configure("Search.TEntry", font=("微软雅黑", 10), padding=(10, 8)) def create_widgets(self): """创建界面组件""" # 标题区域 header_frame = ttk.Frame(self.root, style="Title.TFrame") header_frame.pack(fill=tk.X, padx=0, pady=0) header_frame.configure(style="Header.TFrame") # 渐变背景(模拟) title_container = ttk.Frame(header_frame, style="Title.TFrame") title_container.pack(fill=tk.X, padx=0, pady=0, ipady=20) title_label = ttk.Label(title_container, text="📒 电子通讯录管理系统", style="Title.TLabel") title_label.pack(pady=5) subtitle_label = ttk.Label(title_container, text="高效管理您的联系人信息", style="Subtitle.TLabel") subtitle_label.pack(pady=(0, 10)) # 搜索和操作区域 action_frame = ttk.Frame(self.root, style="Card.TFrame") action_frame.pack(fill=tk.X, padx=20, pady=15) # 搜索框 search_container = ttk.Frame(action_frame) search_container.pack(fill=tk.X, padx=15, pady=15) ttk.Label(search_container, text="搜索联系人:", font=("微软雅黑", 10, "bold"), foreground="#2c3e50").pack(side=tk.LEFT, padx=(0, 10)) self.search_var = tk.StringVar() self.search_entry = ttk.Entry(search_container, textvariable=self.search_var, width=30, font=("微软雅黑", 10), style="Search.TEntry") self.search_entry.pack(side=tk.LEFT, padx=(0, 10)) self.search_entry.bind("<Return>", self.on_search) ttk.Button(search_container, text="搜索", command=self.on_search, style="Search.TButton").pack(side=tk.LEFT, padx=(0, 10)) ttk.Button(search_container, text="显示全部", command=self.refresh_contact_list, style="Search.TButton").pack( side=tk.LEFT) # 主要操作按钮 button_container = ttk.Frame(action_frame) button_container.pack(fill=tk.X, padx=15, pady=(0, 15)) ttk.Button(button_container, text="➕ 添加联系人", command=self.add_contact, style="Accent.TButton").pack( side=tk.LEFT, padx=(0, 10)) ttk.Button(button_container, text="✏️ 编辑联系人", command=self.edit_contact, style="Custom.TButton").pack( side=tk.LEFT, padx=(0, 10)) ttk.Button(button_container, text="🗑️ 删除联系人", command=self.delete_contact, style="Custom.TButton").pack( side=tk.LEFT, padx=(0, 10)) ttk.Button(button_container, text="👁️ 查看详情", command=self.view_details, style="Custom.TButton").pack( side=tk.LEFT, padx=(0, 10)) # 筛选按钮区域 filter_frame = ttk.LabelFrame(button_container, text="筛选", padding=8) filter_frame.pack(side=tk.RIGHT) ttk.Button(filter_frame, text="🌍 按地区", command=self.filter_by_region, style="Search.TButton").pack( side=tk.LEFT, padx=(0, 5)) ttk.Button(filter_frame, text="🏢 按单位", command=self.filter_by_company, style="Search.TButton").pack( side=tk.LEFT, padx=(0, 5)) ttk.Button(filter_frame, text="💼 按职务", command=self.filter_by_title, style="Search.TButton").pack( side=tk.LEFT) # 联系人列表框架 list_frame = ttk.LabelFrame(self.root, text="联系人列表", padding=15) list_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 15)) columns = ("姓名", "电话", "邮箱", "地区", "单位", "职务") self.contact_tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=15) # 设置列标题和宽度 column_widths = {"姓名": 120, "电话": 140, "邮箱": 180, "地区": 120, "单位": 150, "职务": 120} for col in columns: self.contact_tree.heading(col, text=col) self.contact_tree.column(col, width=column_widths[col]) # 添加滚动条 scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.contact_tree.yview) self.contact_tree.configure(yscrollcommand=scrollbar.set) self.contact_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 绑定双击事件 self.contact_tree.bind("<Double-1>", self.on_double_click) # 状态栏 self.status_frame = ttk.Frame(self.root, relief=tk.SUNKEN) self.status_frame.pack(fill=tk.X, side=tk.BOTTOM) self.status_label = ttk.Label(self.status_frame, text=f"共有 {len(self.address_book.contacts)} 个联系人", font=("微软雅黑", 9), foreground="#7f8c8d") self.status_label.pack(side=tk.LEFT, padx=10, pady=5) def refresh_contact_list(self, contacts=None): """刷新联系人列表""" # 清空现有列表 for item in self.contact_tree.get_children(): self.contact_tree.delete(item) # 获取联系人列表 if contacts is None: contacts = self.address_book.display_all_contacts() # 填充列表 for contact in contacts: self.contact_tree.insert("", tk.END, values=( contact.name, contact.phone, contact.email, contact.region, contact.company, contact.title )) # 更新状态栏 self.status_label.config( text=f"共有 {len(self.address_book.contacts)} 个联系人 | 当前显示 {len(contacts)} 个联系人") def on_search(self, event=None): """搜索联系人""" keyword = self.search_var.get().strip() if not keyword: messagebox.showwarning("警告", "请输入搜索关键词") return results = self.address_book.search_contact(keyword) if results: self.refresh_contact_list(results) messagebox.showinfo("搜索结果", f"找到 {len(results)} 个匹配的联系人") else: messagebox.showinfo("搜索结果", "未找到匹配的联系人") def add_contact(self): """添加联系人""" dialog = ContactFormDialog(self.root, "添加联系人") if dialog.result: name, phone, email, address, region, company, title = dialog.result if not name or not phone: messagebox.showwarning("警告", "姓名和电话为必填项") return if self.address_book.add_contact(name, phone, email, address, region, company, title): self.refresh_contact_list() def edit_contact(self): """编辑联系人""" selection = self.contact_tree.selection() if not selection: messagebox.showwarning("警告", "请先选择一个联系人") return # 获取选中的联系人信息 item = selection[0] values = self.contact_tree.item(item, "values") name = values[0] # 查找联系人对象 contact = None for c in self.address_book.contacts: if c.name == name: contact = c break if not contact: messagebox.showerror("错误", "未找到选中的联系人") return # 打开编辑对话框 dialog = ContactFormDialog(self.root, "编辑联系人", contact) if dialog.result: new_name, new_phone, new_email, new_address, new_region, new_company, new_title = dialog.result if not new_name or not new_phone: messagebox.showwarning("警告", "姓名和电话为必填项") return if self.address_book.update_contact( name, new_name, new_phone, new_email, new_address, new_region, new_company, new_title ): self.refresh_contact_list() def delete_contact(self): """删除联系人""" selection = self.contact_tree.selection() if not selection: messagebox.showwarning("警告", "请先选择一个联系人") return # 获取选中的联系人信息 item = selection[0] values = self.contact_tree.item(item, "values") name = values[0] # 确认删除 if messagebox.askyesno("确认删除", f"确定要删除联系人 {name} 吗?"): if self.address_book.delete_contact(name): self.refresh_contact_list() def view_details(self): """查看联系人详情""" selection = self.contact_tree.selection() if not selection: messagebox.showwarning("警告", "请先选择一个联系人") return # 获取选中的联系人信息 item = selection[0] values = self.contact_tree.item(item, "values") name = values[0] # 查找联系人对象 contact = None for c in self.address_book.contacts: if c.name == name: contact = c break if not contact: messagebox.showerror("错误", "未找到选中的联系人") return # 使用新的详情窗口类 ContactDetailWindow(self.root, contact) def on_double_click(self, event): """双击联系人查看详情""" self.view_details() def filter_by_region(self): """按地区筛选联系人""" region = simpledialog.askstring("按地区筛选", "请输入地区:") if region: results = self.address_book.get_contacts_by_region(region) if results: self.refresh_contact_list(results) messagebox.showinfo("筛选结果", f"在 {region} 地区找到 {len(results)} 个联系人") else: messagebox.showinfo("筛选结果", f"在 {region} 地区没有找到联系人") def filter_by_company(self): """按单位筛选联系人""" company = simpledialog.askstring("按单位筛选", "请输入单位:") if company: results = self.address_book.get_contacts_by_company(company) if results: self.refresh_contact_list(results) messagebox.showinfo("筛选结果", f"在 {company} 单位找到 {len(results)} 个联系人") else: messagebox.showinfo("筛选结果", f"在 {company} 单位没有找到联系人") def filter_by_title(self): """按职务筛选联系人""" title = simpledialog.askstring("按职务筛选", "请输入职务:") if title: results = self.address_book.get_contacts_by_title(title) if results: self.refresh_contact_list(results) messagebox.showinfo("筛选结果", f"职务为 {title} 的联系人有 {len(results)} 个") else: messagebox.showinfo("筛选结果", f"没有找到职务为 {title} 的联系人") def main(): """主函数""" root = tk.Tk() app = AddressBookGUI(root) root.mainloop() if __name__ == "__main__": main()
最新发布
11-19
判断下面代码是否安全:/*! iFrame Resizer (iframeSizer.min.js ) - v4.3.5 - 2023-03-08 * Desc: Force cross domain iframes to size to content. * Requires: iframeResizer.contentWindow.min.js to be loaded into the target frame. * Copyright: (c) 2023 David J. Bradshaw - dave@bradshaw.net * License: MIT */ !function(d){var c,u,a,v,x,I,M,r,f,k,i,l,z;function m(){return window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver}function F(e,n,i){e.addEventListener(n,i,!1)}function B(e,n,i){e.removeEventListener(n,i,!1)}function p(e){return x+"["+(n="Host page: "+(e=e),n=window.top!==window.self?window.parentIFrame&&window.parentIFrame.getId?window.parentIFrame.getId()+": "+e:"Nested host page: "+e:n)+"]";var n}function t(e){return k[e]?k[e].log:u}function O(e,n){o("log",e,n,t(e))}function E(e,n){o("info",e,n,t(e))}function R(e,n){o("warn",e,n,!0)}function o(e,n,i,t){!0===t&&"object"==typeof window.console&&console[e](p(n),i)}function w(e){function i(){t("Height"),t("Width"),P(function(){H(w),C(b),l("onResized",w)},w,"init")}function n(){var e=p.slice(I).split(":"),n=e[1]?parseInt(e[1],10):0,i=k[e[0]]&&k[e[0]].iframe,t=getComputedStyle(i);return{iframe:i,id:e[0],height:n+function(e){if("border-box"!==e.boxSizing)return 0;var n=e.paddingTop?parseInt(e.paddingTop,10):0,e=e.paddingBottom?parseInt(e.paddingBottom,10):0;return n+e}(t)+function(e){if("border-box"!==e.boxSizing)return 0;var n=e.borderTopWidth?parseInt(e.borderTopWidth,10):0,e=e.borderBottomWidth?parseInt(e.borderBottomWidth,10):0;return n+e}(t),width:e[2],type:e[3]}}function t(e){var n=Number(k[b]["max"+e]),i=Number(k[b]["min"+e]),e=e.toLowerCase(),t=Number(w[e]);O(b,"Checking "+e+" is in range "+i+"-"+n),t<i&&(t=i,O(b,"Set "+e+" to min value")),n<t&&(t=n,O(b,"Set "+e+" to max value")),w[e]=""+t}function o(){var t=e.origin,o=k[b]&&k[b].checkOrigin;if(o&&""+t!="null"&&!function(){if(o.constructor!==Array)return e=k[b]&&k[b].remoteHost,O(b,"Checking connection is from: "+e),t===e;var e,n=0,i=!1;for(O(b,"Checking connection is from allowed list of origins: "+o);n<o.length;n++)if(o[n]===t){i=!0;break}return i}())throw new Error("Unexpected message received from: "+t+" for "+w.iframe.id+". Message was: "+e.data+". This error can be disabled by setting the checkOrigin: false option or by providing of array of trusted domains.");return 1}function a(e){return p.slice(p.indexOf(":")+v+e)}function s(i,t){var e,n,o;e=function(){var e,n;A("Send Page Info","pageInfo:"+(e=document.body.getBoundingClientRect(),n=w.iframe.getBoundingClientRect(),JSON.stringify({iframeHeight:n.height,iframeWidth:n.width,clientHeight:Math.max(document.documentElement.clientHeight,window.innerHeight||0),clientWidth:Math.max(document.documentElement.clientWidth,window.innerWidth||0),offsetTop:parseInt(n.top-e.top,10),offsetLeft:parseInt(n.left-e.left,10),scrollTop:window.pageYOffset,scrollLeft:window.pageXOffset,documentHeight:document.documentElement.clientHeight,documentWidth:document.documentElement.clientWidth,windowHeight:window.innerHeight,windowWidth:window.innerWidth})),i,t)},n=32,z[o=t]||(z[o]=setTimeout(function(){z[o]=null,e()},n))}function r(e){e=e.getBoundingClientRect();return W(b),{x:Math.floor(Number(e.left)+Number(M.x)),y:Math.floor(Number(e.top)+Number(M.y))}}function d(e){var n=e?r(w.iframe):{x:0,y:0},i={x:Number(w.width)+n.x,y:Number(w.height)+n.y};O(b,"Reposition requested from iFrame (offset x:"+n.x+" y:"+n.y+")"),window.top===window.self?(M=i,c(),O(b,"--")):window.parentIFrame?window.parentIFrame["scrollTo"+(e?"Offset":"")](i.x,i.y):R(b,"Unable to scroll to requested position, window.parentIFrame not found")}function c(){!1===l("onScroll",M)?S():C(b)}function u(e){var e=e.split("#")[1]||"",n=decodeURIComponent(e),n=document.getElementById(n)||document.getElementsByName(n)[0];n?(n=r(n),O(b,"Moving to in page link (#"+e+") at x: "+n.x+" y: "+n.y),M={x:n.x,y:n.y},c(),O(b,"--")):window.top===window.self?O(b,"In page link #"+e+" not found"):window.parentIFrame?window.parentIFrame.moveToAnchor(e):O(b,"In page link #"+e+" not found and window.parentIFrame not found")}function f(e){var n,i={};i=0===Number(w.width)&&0===Number(w.height)?{x:(n=a(9).split(":"))[1],y:n[0]}:{x:w.width,y:w.height},l(e,{iframe:w.iframe,screenX:Number(i.x),screenY:Number(i.y),type:w.type})}function l(e,n){return T(b,e,n)}function m(){switch(k[b]&&k[b].firstRun&&k[b]&&(k[b].firstRun=!1),w.type){case"close":N(w.iframe);break;case"message":n=a(6),O(b,"onMessage passed: {iframe: "+w.iframe.id+", message: "+n+"}"),l("onMessage",{iframe:w.iframe,message:JSON.parse(n)}),O(b,"--");break;case"mouseenter":f("onMouseEnter");break;case"mouseleave":f("onMouseLeave");break;case"autoResize":k[b].autoResize=JSON.parse(a(9));break;case"scrollTo":d(!1);break;case"scrollToOffset":d(!0);break;case"pageInfo":s(k[b]&&k[b].iframe,b),r=b,e("Add ",F),k[r]&&(k[r].stopPageInfo=o);break;case"pageInfoStop":k[b]&&k[b].stopPageInfo&&(k[b].stopPageInfo(),delete k[b].stopPageInfo);break;case"inPageLink":u(a(9));break;case"reset":j(w);break;case"init":i(),l("onInit",w.iframe);break;default:0===Number(w.width)&&0===Number(w.height)?R("Unsupported message received ("+w.type+"), this is likely due to the iframe containing a later version of iframe-resizer than the parent page"):i()}function e(n,i){function t(){k[r]?s(k[r].iframe,r):o()}["scroll","resize"].forEach(function(e){O(r,n+e+" listener for sendPageInfo"),i(window,e,t)})}function o(){e("Remove ",B)}var r,n}var g,h,p=e.data,w={},b=null;if("[iFrameResizerChild]Ready"===p)for(var y in k)A("iFrame requested init",L(y),k[y].iframe,y);else x===(""+p).slice(0,I)&&p.slice(I).split(":")[0]in k?(w=n(),b=w.id,k[b]&&(k[b].loaded=!0),(h=w.type in{true:1,false:1,undefined:1})&&O(b,"Ignoring init message from meta parent page"),!h&&(h=!0,k[g=b]||(h=!1,R(w.type+" No settings for "+g+". Message was: "+p)),h)&&(O(b,"Received: "+p),g=!0,null===w.iframe&&(R(b,"IFrame ("+w.id+") not found"),g=!1),g&&o()&&m())):E(b,"Ignored: "+p)}function T(e,n,i){var t=null,o=null;if(k[e]){if("function"!=typeof(t=k[e][n]))throw new TypeError(n+" on iFrame["+e+"] is not a function");o=t(i)}return o}function g(e){e=e.id;delete k[e]}function N(e){var n=e.id;if(!1===T(n,"onClose",n))O(n,"Close iframe cancelled by onClose event");else{O(n,"Removing iFrame: "+n);try{e.parentNode&&e.parentNode.removeChild(e)}catch(e){R(e)}T(n,"onClosed",n),O(n,"--"),g(e)}}function W(e){null===M&&O(e,"Get page position: "+(M={x:window.pageXOffset===d?document.documentElement.scrollLeft:window.pageXOffset,y:window.pageYOffset===d?document.documentElement.scrollTop:window.pageYOffset}).x+","+M.y)}function C(e){null!==M&&(window.scrollTo(M.x,M.y),O(e,"Set page position: "+M.x+","+M.y),S())}function S(){M=null}function j(e){O(e.id,"Size reset requested by "+("init"===e.type?"host page":"iFrame")),W(e.id),P(function(){H(e),A("reset","reset",e.iframe,e.id)},e,"reset")}function H(o){function i(e){var n;function i(){Object.keys(k).forEach(function(e){function n(e){return"0px"===(k[i]&&k[i].iframe.style[e])}var i;k[i=e]&&null!==k[i].iframe.offsetParent&&(n("height")||n("width"))&&A("Visibility change","resize",k[i].iframe,i)})}function t(e){O("window","Mutation observed: "+e[0].target+" "+e[0].type),h(i,16)}!a&&"0"===o[e]&&(a=!0,O(r,"Hidden iFrame detected, creating visibility listener"),e=m())&&(n=document.querySelector("body"),new e(t).observe(n,{attributes:!0,attributeOldValue:!1,characterData:!0,characterDataOldValue:!1,childList:!0,subtree:!0}))}function e(e){var n;n=e,o.id?(o.iframe.style[n]=o[n]+"px",O(o.id,"IFrame ("+r+") "+n+" set to "+o[n]+"px")):O("undefined","messageData id not set"),i(e)}var r=o.iframe.id;k[r]&&(k[r].sizeHeight&&e("height"),k[r].sizeWidth)&&e("width")}function P(e,n,i){i!==n.type&&r&&!window.jasmine?(O(n.id,"Requesting animation frame"),r(e)):e()}function A(n,i,t,o,e){function r(){var e;t&&"contentWindow"in t&&null!==t.contentWindow?(e=k[o]&&k[o].targetOrigin,O(o,"["+n+"] Sending msg to iframe["+o+"] ("+i+") targetOrigin: "+e),t.contentWindow.postMessage(x+i,e)):R(o,"["+n+"] IFrame("+o+") not found")}function a(){e&&k[o]&&k[o].warningTimeout&&(k[o].msgTimeout=setTimeout(function(){!k[o]||k[o].loaded||s||(s=!0,R(o,"IFrame has not responded within "+k[o].warningTimeout/1e3+" seconds. Check iFrameResizer.contentWindow.js has been loaded in iFrame. This message can be ignored if everything is working, or you can set the warningTimeout option to a higher value or zero to suppress this warning."))},k[o].warningTimeout))}var s=!1;o=o||t.id,k[o]&&(r(),a())}function L(e){return e+":"+k[e].bodyMarginV1+":"+k[e].sizeWidth+":"+k[e].log+":"+k[e].interval+":"+k[e].enablePublicMethods+":"+k[e].autoResize+":"+k[e].bodyMargin+":"+k[e].heightCalculationMethod+":"+k[e].bodyBackground+":"+k[e].bodyPadding+":"+k[e].tolerance+":"+k[e].inPageLinks+":"+k[e].resizeFrom+":"+k[e].widthCalculationMethod+":"+k[e].mouseEvents}function s(t,i){function e(i){var e=m();e&&(e=e,t.parentNode)&&new e(function(e){e.forEach(function(e){Array.prototype.slice.call(e.removedNodes).forEach(function(e){e===t&&N(t)})})}).observe(t.parentNode,{childList:!0}),F(t,"load",function(){var e,n;A("iFrame.onload",i,t,d,!0),e=k[r]&&k[r].firstRun,n=k[r]&&k[r].heightCalculationMethod in f,!e&&n&&j({iframe:t,height:0,width:0,type:"init"})}),A("init",i,t,d,!0)}function o(e){var n=e.split("Callback");2===n.length&&(this[n="on"+n[0].charAt(0).toUpperCase()+n[0].slice(1)]=this[e],delete this[e],R(r,"Deprecated: '"+e+"' has been renamed '"+n+"'. The old method will be removed in the next major version."))}function n(e){if(e=e||{},k[r]=Object.create(null),k[r].iframe=t,k[r].firstRun=!0,k[r].remoteHost=t.src&&t.src.split("/").slice(0,3).join("/"),"object"!=typeof e)throw new TypeError("Options is not an object");Object.keys(e).forEach(o,e);var n,i=e;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(k[r][n]=(Object.prototype.hasOwnProperty.call(i,n)?i:l)[n]);k[r]&&(k[r].targetOrigin=!0!==k[r].checkOrigin||""===(e=k[r].remoteHost)||null!==e.match(/^(about:blank|javascript:|file:\/\/)/)?"*":e)}var r=function(e){if("string"!=typeof e)throw new TypeError("Invaild id for iFrame. Expected String");var n;return""===e&&(t.id=(n=i&&i.id||l.id+c++,null!==document.getElementById(n)&&(n+=c++),e=n),u=(i||{}).log,O(e,"Added missing iframe ID: "+e+" ("+t.src+")")),e}(t.id);if(r in k&&"iFrameResizer"in t)R(r,"Ignored iFrame, already setup.");else{switch(n(i),O(r,"IFrame scrolling "+(k[r]&&k[r].scrolling?"enabled":"disabled")+" for "+r),t.style.overflow=!1===(k[r]&&k[r].scrolling)?"hidden":"auto",k[r]&&k[r].scrolling){case"omit":break;case!0:t.scrolling="yes";break;case!1:t.scrolling="no";break;default:t.scrolling=k[r]?k[r].scrolling:"no"}s("Height"),s("Width"),a("maxHeight"),a("minHeight"),a("maxWidth"),a("minWidth"),"number"!=typeof(k[r]&&k[r].bodyMargin)&&"0"!==(k[r]&&k[r].bodyMargin)||(k[r].bodyMarginV1=k[r].bodyMargin,k[r].bodyMargin=k[r].bodyMargin+"px"),e(L(r)),k[r]&&(k[r].iframe.iFrameResizer={close:N.bind(null,k[r].iframe),removeListeners:g.bind(null,k[r].iframe),resize:A.bind(null,"Window resize","resize",k[r].iframe),moveToAnchor:function(e){A("Move to anchor","moveToAnchor:"+e,k[r].iframe,r)},sendMessage:function(e){A("Send Message","message:"+(e=JSON.stringify(e)),k[r].iframe,r)}})}function a(e){var n=k[r][e];1/0!==n&&0!==n&&(t.style[e]="number"==typeof n?n+"px":n,O(r,"Set "+e+" = "+t.style[e]))}function s(e){if(k[r]["min"+e]>k[r]["max"+e])throw new Error("Value for min"+e+" can not be greater than max"+e)}}function h(e,n){null===i&&(i=setTimeout(function(){i=null,e()},n))}function e(){"hidden"!==document.visibilityState&&(O("document","Trigger event: Visibility change"),h(function(){b("Tab Visible","resize")},16))}function b(i,t){Object.keys(k).forEach(function(e){var n;k[n=e]&&"parent"===k[n].resizeFrom&&k[n].autoResize&&!k[n].firstRun&&A(i,t,k[e].iframe,e)})}function y(){F(window,"message",w),F(window,"resize",function(){var e;O("window","Trigger event: "+(e="resize")),h(function(){b("Window "+e,"resize")},16)}),F(document,"visibilitychange",e),F(document,"-webkit-visibilitychange",e)}function n(){function t(e,n){if(n){if(!n.tagName)throw new TypeError("Object is not a valid DOM element");if("IFRAME"!==n.tagName.toUpperCase())throw new TypeError("Expected <IFRAME> tag, found <"+n.tagName+">");s(n,e),o.push(n)}}for(var o,e=["moz","webkit","o","ms"],n=0;n<e.length&&!r;n+=1)r=window[e[n]+"RequestAnimationFrame"];return r?r=r.bind(window):O("setup","RequestAnimationFrame not supported"),y(),function(e,n){var i;switch(o=[],(i=e)&&i.enablePublicMethods&&R("enablePublicMethods option has been removed, public methods are now always available in the iFrame"),typeof n){case"undefined":case"string":Array.prototype.forEach.call(document.querySelectorAll(n||"iframe"),t.bind(d,e));break;case"object":t(e,n);break;default:throw new TypeError("Unexpected data type ("+typeof n+")")}return o}}function q(e){e.fn?e.fn.iFrameResize||(e.fn.iFrameResize=function(i){return this.filter("iframe").each(function(e,n){s(n,i)}).end()}):E("","Unable to bind to jQuery, it is not fully loaded.")}"undefined"!=typeof window&&(c=0,a=u=!1,v="message".length,I=(x="[iFrameSizer]").length,M=null,r=window.requestAnimationFrame,f=Object.freeze({max:1,scroll:1,bodyScroll:1,documentElementScroll:1}),k={},i=null,l=Object.freeze({autoResize:!0,bodyBackground:null,bodyMargin:null,bodyMarginV1:8,bodyPadding:null,checkOrigin:!0,inPageLinks:!1,enablePublicMethods:!0,heightCalculationMethod:"bodyOffset",id:"iFrameResizer",interval:32,log:!1,maxHeight:1/0,maxWidth:1/0,minHeight:0,minWidth:0,mouseEvents:!0,resizeFrom:"parent",scrolling:!1,sizeHeight:!0,sizeWidth:!1,warningTimeout:5e3,tolerance:0,widthCalculationMethod:"scroll",onClose:function(){return!0},onClosed:function(){},onInit:function(){},onMessage:function(){R("onMessage function not defined")},onMouseEnter:function(){},onMouseLeave:function(){},onResized:function(){},onScroll:function(){return!0}}),z={},window.jQuery!==d&&q(window.jQuery),"function"==typeof define&&define.amd?define([],n):"object"==typeof module&&"object"==typeof module.exports&&(module.exports=n()),window.iFrameResize=window.iFrameResize||n())}(); //# sourceMappingURL=iframeResizer.map
11-07
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值