合并两个excel表格工具

前言

我们有时候需要合并两个相关的表格。例如表格1有“考生号, 姓名, 语文成绩”字段,表格2有“考生号, 姓名, 数学成绩”,那么此时就需要将两个表格合并为 “考生号, 姓名, 语文成绩, 数学成绩”。


示例

原来a, b表格




导出后:




代码

main.py

"""
Author: Andy Dennis
Date: 2022.7.29
Details: 其中ComBoPicker代码来自https://blog.csdn.net/weixin_45774074/article/details/123293411
"""
from tkinter import *
import tkinter as tk
from ComBoPicker import Combopicker# 导入自定义下拉多选框
import tkinter.font as tf
from tkinter import filedialog, messagebox
import traceback
import pandas as pd


main_xlsx = ''
aux_xlsx = ''
join_df = None


# 选择主表
def select_main_xlsx():
    global main_xlsx
    global join_df

    fp = filedialog.askopenfilename(title=u'请选择主表')
    print(fp)
    try:
        if fp is not None and len(fp) > 0:
            if fp.split('.')[-1] not in ['xls', 'xlsx']:
                tk.messagebox.showerror('error', '请选择xls/xlsx的文件')
                return
            
            main_xlsx = fp
            lb1['text'] = fp
            join_df = None
            print('main_xlsx: ', main_xlsx)
            lb5['text'] = '暂无提示'


            
            # tk.messagebox.showinfo('success', '选择原始目录成功')
        else:
            tk.messagebox.showerror('error', '请选择主表失败')
    except:
        tk.messagebox.showerror('error', '请选择主表失败')
        print(traceback.format_exc())


# 选择副表
def select_aux_xlsx():
    global aux_xlsx
    global join_df

    fp = filedialog.askopenfilename(title=u'请选择副表')
    print(fp)
    try:
        if fp is not None and len(fp) > 0:
            if fp.split('.')[-1] not in ['xls', 'xlsx']:
                tk.messagebox.showerror('error', '请选择xls/xlsx的文件')
                return
            
            aux_xlsx = fp
            lb2['text'] = fp
            join_df = None
            print('aux_xlsx: ', aux_xlsx)
            lb5['text'] = '暂无提示'
            
            # tk.messagebox.showinfo('success', '选择原始目录成功')
        else:
            tk.messagebox.showerror('error', '请选择副表失败')
    except:
        tk.messagebox.showerror('error', '请选择副表失败')
        print(traceback.format_exc())


# 删除下拉框并新建一个
def destroy_and_new_one(values):
    global COMBOPICKER1
    global window
    COMBOPICKER1.destroy()
    COMBOPICKER1 = Combopicker(window, values=values, entrywidth=50)
    COMBOPICKER1.place(x=100, y=160)


def join_xlsx():
    global COMBOPICKER1
    global join_df

    if main_xlsx == '' or aux_xlsx == '':
        tk.messagebox.showerror('error', '请先选择好主表和副表')
        return

    main_df = pd.read_excel(main_xlsx)
    aux_df = pd.read_excel(aux_xlsx)
    main_df_cols = main_df.columns
    aux_df_cols = aux_df.columns

    print('main_df_cols: ', main_df_cols)
    print('aux_df_cols:  ', aux_df_cols)

    join_cols_set = set(main_df_cols) & set(aux_df_cols)
    # 主键, 这里我直接让相同的字段名(列名)作为主键了
    key_cols = [item for item in main_df_cols if item in join_cols_set]
    print('key_cols:    ', key_cols)

    union_cols = [item for item in main_df_cols]

    # 添加附表其他列
    for item in aux_df_cols:
        if item not in union_cols:
            union_cols.append(item)

    print('union_cols:  ', union_cols) 


    join_df = pd.merge(main_df, aux_df, on=key_cols, how='left')
    print('join_df:\n', join_df)

    destroy_and_new_one(union_cols)
    lb5['text'] = '暂无提示'


def save_a_excel(save_df):
    fp = filedialog.asksaveasfilename(defaultextension='合并后的文件',filetypes=[("excel文件", ".xlsx")])
    print(fp)
    try:
        if fp is not None and len(fp) > 0:
            if fp.split('.')[-1] not in ['xls', 'xlsx']:
                tk.messagebox.showerror('error', '请保存为xls/xlsx格式的文件')
                return
            
            save_df.to_excel(fp, index=False,header=True)
            
            # tk.messagebox.showinfo('success', '选择原始目录成功')
        else:
            tk.messagebox.showerror('error', '保存合并的表格失败')
    except:
        tk.messagebox.showerror('error', '保存合并的表格失败')
        print(traceback.format_exc())


def save_xlsx():
    global join_df

    if main_xlsx == '' or aux_xlsx == '':
        tk.messagebox.showerror('error', '请先选择好主表和副表')
        return
    
    if join_df is None:
        tk.messagebox.showerror('error', '请先点击合并表格按钮')
        return

    choose_items = COMBOPICKER1.get()
    print(choose_items)

    if len(choose_items) == 0:
        tk.messagebox.showerror('error', '请选择要导出的列名')
        return
    else:
        choose_items = choose_items.split(',')

    union_cols = join_df.columns
    save_cols = [item for item in union_cols if item in choose_items]
    print('导出列: ', save_cols)

    lb5['text'] = '导出列: ' + ' '.join(save_cols)

    save_df = join_df[save_cols]
    print(save_df)

    save_a_excel(save_df)




if __name__ == "__main__":
    window = Tk()
    # 标题
    window.title('合并XLSX小软件')
    window.geometry("500x300")
    # 不允许重新设置大小
    window.resizable('true', 'true')

    bt1 = tk.Button(window, text='选择主表', width=12, height=1, font=tf.Font(size=12), command=select_main_xlsx)
    bt1.place(x=60, y=24)

    bt2 = tk.Button(window, text='选择副表', width=12, height=1, font=tf.Font(size=12), command=select_aux_xlsx)
    bt2.place(x=220, y=24)

    lb1 = tk.Label(window, text='未选择主表', font=tf.Font(size=10))
    lb1.place(x=10, y=68)

    lb2 = tk.Label(window, text='未选择副表', font=tf.Font(size=10))
    lb2.place(x=10, y=90)


    bt3 = tk.Button(window, text='合并表格', width=18, height=1, font=tf.Font(size=12), command=join_xlsx)
    bt3.place(x=120, y=120)

    lb4 = tk.Label(window, text='导出字段:', font=tf.Font(size=10))
    lb4.place(x=10, y=160)

    COMBOPICKER1 = Combopicker(window, values=['请读取表格', '读取表格'], entrywidth=50)
    # COMBOPICKER1.pack(anchor="w")
    COMBOPICKER1.place(x=100, y=160)

    bt4 = tk.Button(window, text='导出合并后的表格', width=18, height=1, font=tf.Font(size=12), command=save_xlsx)
    bt4.place(x=120, y=200)

    lb5 = tk.Label(window, text='暂无提示', font=tf.Font(size=10))
    lb5.place(x=10, y=240)

    window.mainloop()



ComBoPicker.py

'''
    引用自https://blog.csdn.net/weixin_45774074/article/details/123293411
	自定义多选下拉列表
'''
import tkinter.font as tkFont
import tkinter.ttk as ttk
from tkinter import *
class Picker(ttk.Frame):

    def __init__(self, master=None,activebackground='#b1dcfb',values=[],entry_wid=None,activeforeground='black', selectbackground='#003eff', selectforeground='white', command=None, borderwidth=1, relief="solid"):

        self._selected_item = None

        self._values = values

        self._entry_wid = entry_wid

        self._sel_bg = selectbackground 
        self._sel_fg = selectforeground

        self._act_bg = activebackground 
        self._act_fg = activeforeground

        self._command = command
        ttk.Frame.__init__(self, master, borderwidth=borderwidth, relief=relief)

        self.bind("<FocusIn>", lambda event:self.event_generate('<<PickerFocusIn>>'))
        self.bind("<FocusOut>", lambda event:self.event_generate('<<PickerFocusOut>>'))

        self._font = tkFont.Font()

        self.dict_checkbutton = {
    
    }
        self.dict_checkbutton_var = {
    
    }
        self.dict_intvar_item = {
    
    }

        for index,item in enumerate(self._values):

            self.dict_intvar_item[item] = IntVar()
            self.dict_checkbutton[item] = ttk.Checkbutton(self, text = item, variable=self.dict_intvar_item[item],command=lambda ITEM = item:self._command(ITEM))
            self.dict_checkbutton[item].grid(row=index, column=0, sticky=NSEW)
            self.dict_intvar_item[item].set(0)


class Combopicker(ttk.Entry, Picker):
    def __init__(self, master, values= [] ,entryvar=None, entrywidth=None, entrystyle=None, onselect=None,activebackground='#b1dcfb', activeforeground='black', selectbackground='#003eff', selectforeground='white', borderwidth=1, relief="solid"):
        
        self.values=values

        if entryvar is not None:
            self.entry_var = entryvar
        else:
            self.entry_var = StringVar()

        entry_config = {
    
    }
        if entrywidth is not None:
            entry_config["width"] = entrywidth

        if entrystyle is not None:
            entry_config["style"] = entrystyle

        ttk.Entry.__init__(self, master, textvariable=self.entry_var, **entry_config, state = "readonly")

        self._is_menuoptions_visible = False

        self.picker_frame = Picker(self.winfo_toplevel(), values=values,entry_wid = self.entry_var,activebackground=activebackground, activeforeground=activeforeground, selectbackground=selectbackground, selectforeground=selectforeground, command=self._on_selected_check)

        self.bind_all("<1>", self._on_click, "+")

        self.bind("<Escape>", lambda event: self.hide_picker())

    @property
    def current_value(self):
        try:
            value = self.entry_var.get()
            return value
        except ValueError:
            return None

    @current_value.setter
    def current_value(self, INDEX):
        self.entry_var.set(self.values.index(INDEX))

    def _on_selected_check(self, SELECTED):

        value = []
        if self.entry_var.get() != "" and self.entry_var.get() != None:
            temp_value = self.entry_var.get()
            value = temp_value.split(",")

        if str(SELECTED) in value:
            value.remove(str(SELECTED))

        else:    
            value.append(str(SELECTED))

        value.sort()

        temp_value = ""
        for index,item in enumerate(value):
            if item!= "":
                if index != 0:
                    temp_value += ","
                temp_value += str(item)

        self.entry_var.set(temp_value)

    def _on_click(self, event):
        str_widget = str(event.widget)

        if str_widget == str(self):
            if not self._is_menuoptions_visible:
                self.show_picker()
        else:
            if not str_widget.startswith(str(self.picker_frame)) and self._is_menuoptions_visible:
                self.hide_picker()

    def show_picker(self):
        if not self._is_menuoptions_visible:
            self.picker_frame.place(in_=self, relx=0, rely=1, relwidth=1 )
            self.picker_frame.lift()

        self._is_menuoptions_visible = True

    def hide_picker(self):
        if self._is_menuoptions_visible:
            self.picker_frame.place_forget()

        self._is_menuoptions_visible = False

猜你喜欢

转载自blog.csdn.net/weixin_43850253/article/details/126052975