# -*- coding: utf-8 -*-
# !/usr/bin/env python
import ast

import PySimpleGUI as Sg
import pandas as pd
import numpy as np
import copy
import os
import sys
import shutil
from pathlib import Path
from openpyxl import load_workbook
import inspect
from dataclasses import dataclass

SYSTEM_ICON = 'config/img/table_arrow9_transparent_64.ico'
SYSTEM_DATA_FILES = [SYSTEM_ICON]
INPUTFILE_SRTING_COLUMNS = '-string-columns'
OUTPUTFILE_RENAME_KEY = '-output'

# 表格tab中的最大阈值，表格数及每张表格的最大列数
MAX_TABLE_NUMBER = 15  # 最大配置，大于18运行错误
MAX_TABLE_SHOW_COLUMNS = 20


colornames = {
    'aliceblue':            '#F0F8FF',
    'antiquewhite':         '#FAEBD7',
    'aqua':                 '#00FFFF',
    'aquamarine':           '#7FFFD4',
    'azure':                '#F0FFFF',
    'beige':                '#F5F5DC',
    'bisque':               '#FFE4C4',
    'black':                '#000000',
    'blanchedalmond':       '#FFEBCD',
    'blue':                 '#0000FF',
    'blueviolet':           '#8A2BE2',
    'brown':                '#A52A2A',
    'burlywood':            '#DEB887',
    'cadetblue':            '#5F9EA0',
    'chartreuse':           '#7FFF00',
    'chocolate':            '#D2691E',
    'coral':                '#FF7F50',
    'cornflowerblue':       '#6495ED',
    'cornsilk':             '#FFF8DC',
    'crimson':              '#DC143C',
    'cyan':                 '#00FFFF',
    'darkblue':             '#00008B',
    'darkcyan':             '#008B8B',
    'darkgoldenrod':        '#B8860B',
    'darkgray':             '#A9A9A9',
    'darkgreen':            '#006400',
    'darkkhaki':            '#BDB76B',
    'darkmagenta':          '#8B008B',
    'darkolivegreen':       '#556B2F',
    'darkorange':           '#FF8C00',
    'darkorchid':           '#9932CC',
    'darkred':              '#8B0000',
    'darksalmon':           '#E9967A',
    'darkseagreen':         '#8FBC8F',
    'darkslateblue':        '#483D8B',
    'darkslategray':        '#2F4F4F',
    'darkturquoise':        '#00CED1',
    'darkviolet':           '#9400D3',
    'deeppink':             '#FF1493',
    'deepskyblue':          '#00BFFF',
    'dimgray':              '#696969',
    'dodgerblue':           '#1E90FF',
    'firebrick':            '#B22222',
    'floralwhite':          '#FFFAF0',
    'forestgreen':          '#228B22',
    'fuchsia':              '#FF00FF',
    'gainsboro':            '#DCDCDC',
    'ghostwhite':           '#F8F8FF',
    'gold':                 '#FFD700',
    'goldenrod':            '#DAA520',
    'gray':                 '#808080',
    'green':                '#008000',
    'greenyellow':          '#ADFF2F',
    'honeydew':             '#F0FFF0',
    'hotpink':              '#FF69B4',
    'indianred':            '#CD5C5C',
    'indigo':               '#4B0082',
    'ivory':                '#FFFFF0',
    'khaki':                '#F0E68C',
    'lavender':             '#E6E6FA',
    'lavenderblush':        '#FFF0F5',
    'lawngreen':            '#7CFC00',
    'lemonchiffon':         '#FFFACD',
    'lightblue':            '#ADD8E6',
    'lightcoral':           '#F08080',
    'lightcyan':            '#E0FFFF',
    'lightgoldenrodyellow': '#FAFAD2',
    'lightgreen':           '#90EE90',
    'lightgray':            '#D3D3D3',
    'lightpink':            '#FFB6C1',
    'lightsalmon':          '#FFA07A',
    'lightseagreen':        '#20B2AA',
    'lightskyblue':         '#87CEFA',
    'lightslategray':       '#778899',
    'lightsteelblue':       '#B0C4DE',
    'lightyellow':          '#FFFFE0',
    'lime':                 '#00FF00',
    'limegreen':            '#32CD32',
    'linen':                '#FAF0E6',
    'magenta':              '#FF00FF',
    'maroon':               '#800000',
    'mediumaquamarine':     '#66CDAA',
    'mediumblue':           '#0000CD',
    'mediumorchid':         '#BA55D3',
    'mediumpurple':         '#9370DB',
    'mediumseagreen':       '#3CB371',
    'mediumslateblue':      '#7B68EE',
    'mediumspringgreen':    '#00FA9A',
    'mediumturquoise':      '#48D1CC',
    'mediumvioletred':      '#C71585',
    'midnightblue':         '#191970',
    'mintcream':            '#F5FFFA',
    'mistyrose':            '#FFE4E1',
    'moccasin':             '#FFE4B5',
    'navajowhite':          '#FFDEAD',
    'navy':                 '#000080',
    'oldlace':              '#FDF5E6',
    'olive':                '#808000',
    'olivedrab':            '#6B8E23',
    'orange':               '#FFA500',
    'orangered':            '#FF4500',
    'orchid':               '#DA70D6',
    'palegoldenrod':        '#EEE8AA',
    'palegreen':            '#98FB98',
    'paleturquoise':        '#AFEEEE',
    'palevioletred':        '#DB7093',
    'papayawhip':           '#FFEFD5',
    'peachpuff':            '#FFDAB9',
    'peru':                 '#CD853F',
    'pink':                 '#FFC0CB',
    'plum':                 '#DDA0DD',
    'powderblue':           '#B0E0E6',
    'purple':               '#800080',
    'red':                  '#FF0000',
    'rosybrown':            '#BC8F8F',
    'royalblue':            '#4169E1',
    'saddlebrown':          '#8B4513',
    'salmon':               '#FA8072',
    'sandybrown':           '#FAA460',
    'seagreen':             '#2E8B57',
    'seashell':             '#FFF5EE',
    'sienna':               '#A0522D',
    'silver':               '#C0C0C0',
    'skyblue':              '#87CEEB',
    'slateblue':            '#6A5ACD',
    'slategray':            '#708090',
    'snow':                 '#FFFAFA',
    'springgreen':          '#00FF7F',
    'steelblue':            '#4682B4',
    'tan':                  '#D2B48C',
    'teal':                 '#008080',
    'thistle':              '#D8BFD8',
    'tomato':               '#FF6347',
    'turquoise':            '#40E0D0',
    'violet':               '#EE82EE',
    'wheat':                '#F5DEB3',
    'white':                '#FFFFFF',
    'whitesmoke':           '#F5F5F5',
    'yellow':               '#FFFF00',
    'yellowgreen':          '#9ACD32'}

# 当单元readonly时，无法修改背景色，需要分别设置不同颜色方案
specialcolordefineforuneditable = [("red", "white"), ("green", "white"), ("blue", "white")]
specialcolordefineforeditable = [("white", "red"), ("white", "green"), ("white", "blue")]
TAB_GROUP_EDITABLE = True


def columntypebyname(df: pd.DataFrame, columnnamelist: list) -> list:
    """
    将行值list按对应的列值类型进行转换
    :param df:                  dataframe
    :param columnnamelist:        list： 要处理的列名列表
    :return:                    list： 处理后的列类型 list
    """
    # 首先获取主类型
    typenamelist = [df[x].dtypes.name for x in columnnamelist]
    # 根据第一行数据将其中的"object"类型更换为更细的子类型
    # 需要找到该列第一个不为np.nan的值确定本列类型，完全无值或者全部空值使用'float64'
    # 首先获取相应列的空值矩阵df，不做全表检测，减少计算量
    dfnulljudge = df[columnnamelist].isnull()
    # 指定类的单元格值类型分布df， 后续可以用此细细化禁用第一个非空值判断类型的方案
    dftypejudge = df[columnnamelist].applymap(type)
    # 0、首先排除无数据和所有列均为非object的情况
    if df.shape[0] == 0 or 'object' not in typenamelist:
        typesubnamelist = typenamelist
    else:
        # 主类型是object并且有数据时
        typesubnamelist = []
        for x in zip(columnnamelist, typenamelist):
            typeset = set(dftypejudge[x[0]])
            # 1、非object，直接使用
            if x[1] != "object":
                typesubnamelist.append(x[1])
            # 2、其次全列nan则默认为float64，之后的else情况一定有非nan
            elif False not in dfnulljudge[x[0]].tolist():
                typesubnamelist.append("float64")
            # 3、全列类型set 后只有一种，直接用str(set.pop())
            elif len(typeset) == 1:
                typesubnamelist.append(str(typeset.pop()).replace('class ', ""))
            # 4、全列两个set，列中有nan， 其中nan个数和非nan个数与float个数一致，使用该类别
            elif len(typeset) == 2 and True in dfnulljudge[x[0]].tolist() \
                    and dfnulljudge[x[0]].tolist().count(True) == dftypejudge[x[0]].tolist().count(type(np.nan)):
                # 移除nan对应的类型
                typeset.remove(type(np.nan))
                typesubnamelist.append(str(typeset.pop()).replace('class ', ""))
            # 完全没有类型， 使用主类型
            elif len(typeset) == 0:
                typesubnamelist.append(x[1])
            # 6、本列不止一个非nan类型，全部通过set放到类型中,忽略其中的“str"简化为“s“
            else:
                typesubnamelist.append(
                    "-".join([str(x).replace('class ', "") if "str" not in str(x) else "<s>" for x in typeset]))
                # typesubnamelist.append(str(type(df.loc[df.index[dfnulljudge[x[0]].tolist().index(False)], x[0]])))

    return typesubnamelist


# 根据函数返回值，如果是错误则修饰器打印带函数名称的错误信息
def errprintwithtunctionname(func):
    """
    修饰函数，用于对df处理函数的返回值添加函数名信息
    首先执行函数，根据返回值[True, ....] 和 [False, errstr]
    :param func:                    # 修饰的函数
    :return:                        [True, ....] | [False, 修饰函数名 + errstr]
    """

    def run(*argc, **argv):
        result = func(*argc, **argv)
        if result[0] is True:
            return result
        else:
            return [False, "\n=========== 函数{}执行错误 ===========\n{}".format(func.__name__, result[1])]

    return run


# 用于输出错误信息， 并返回给主函数
def errprintwithname(errstr: str):
    """
    返回带函数名称的错误信息
    :param errstr:                      str：错误信息
    :return:
    """
    # print(inspect.stack()[1].function)
    # print(type(inspect.stack()[1].function))
    return [False, "\n=========== 函数{}执行错误 ===========\n{}".format(inspect.stack()[1].function, errstr)]


# 定义显示数据类型和内部数据类型的关系
VALIE_DATA_TYPE = {
    "字符串": "str",
    "浮点": "float",  # 等价于 float64
    "浮点(32位)": "float32",
    "浮点(64位)": "float64",
    "整型(32位)": "int32",
    "整型": "int64",
    "日期时间": "datetime",
    "日期": "date",
    "列表": "list",
    "python非字符串对象": "str2object"
}

# 快速提示消息显示的时间
AUTO_CLOSE_DURATION = 0.5
# GUI中表格处理编辑、新增模式时用到的定义
EDIT_METHOD = 1
ADD_METHOD = 2
# GUI中用到的默认字体
deffont = ("宋体", 15)
DEF_FT = ('黑体', 10)
SPECIAL_TABLE_DEF_FT = ('黑体', 10)
MAX_SPECIAL_TABLE_ROWS = 16
MAX_SPECIAL_TABLE_COLUMNS = 10
SPECIAL_TABLE_CELL_INPUT = 1
SPECIAL_TABLE_CELL_COMBOX = 2
SPECIAL_TABLE_CELL_TEXT = 3
DEF_CELL_SIZE = (10, 1)
DEF_WINDOW_SIZE = (1378, 390)
DEF_PAD = (1, 1)
DEF_BD = 0

# for i in range(1, 100):
#     w = (mainwindow[(tablekey, -1, -1)].char_width_in_pixels(("黑体", i), 'w'))
#     h = (mainwindow[(tablekey, -1, -1)].char_height_in_pixels(("黑体", i)))
#     print("{} : [{}, {}],".format(i, w, h))

FONT_SIZE_DICT = {
    1: [1, 2], 2: [2, 3], 3: [2, 4], 4: [3, 5], 5: [4, 7], 6: [4, 8], 7: [5, 9], 8: [6, 11], 9: [6, 12],
    10: [7, 13], 11: [8, 15], 12: [8, 16], 13: [9, 17], 14: [10, 19], 15: [10, 20], 16: [11, 21], 17: [12, 23],
    18: [12, 24], 19: [13, 25], 20: [14, 27], 21: [14, 28], 22: [15, 29], 23: [16, 31], 24: [16, 33], 25: [17, 33],
    26: [18, 35], 27: [18, 36], 28: [19, 37], 29: [20, 39], 30: [20, 40], 31: [21, 41], 32: [22, 43], 33: [22, 44],
    34: [23, 45], 35: [24, 47], 36: [24, 48], 37: [25, 49], 38: [26, 51], 39: [26, 52], 40: [27, 53], 41: [28, 55],
    42: [28, 56], 43: [29, 57], 44: [30, 59], 45: [30, 60], 46: [31, 61], 47: [32, 63], 48: [32, 64], 49: [33, 65],
    50: [34, 67], 51: [34, 68], 52: [35, 69], 53: [36, 71], 54: [36, 72], 55: [37, 73], 56: [38, 75], 57: [38, 76],
    58: [39, 77], 59: [40, 79], 60: [40, 80], 61: [41, 81], 62: [42, 83], 63: [42, 84], 64: [43, 85], 65: [44, 87],
    66: [44, 88], 67: [45, 89], 68: [46, 91], 69: [46, 92], 70: [47, 93], 71: [48, 95], 72: [48, 97], 73: [49, 97],
    74: [50, 99], 75: [50, 100], 76: [51, 101], 77: [52, 103], 78: [52, 104], 79: [53, 105], 80: [54, 107],
    81: [54, 108], 82: [55, 109], 83: [56, 111], 84: [56, 112], 85: [57, 113], 86: [58, 115], 87: [58, 116],
    88: [59, 117], 89: [60, 119], 90: [60, 120], 91: [61, 121], 92: [62, 123], 93: [62, 124], 94: [63, 125],
    95: [64, 127], 96: [64, 128], 97: [65, 129], 98: [66, 131], 99: [66, 132],
}

# ICON IMAGE Base64 file
icondict = {
    "HOUSE64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAF50lEQVRIiYWVX2wc1RWHf+ece+/szu7a47Vjx+s42CRA/hASAQFCEgcTgkjAjVryQFNXtJUqFfJQqe0DbZ+KKvEcVU1VpAYa+idSq1IKFFTVgUBccKAJSYkViC2TxCZZx2uv7V3Wu56Z24fZNU4aykhXGmnune9+v3N0L/AlDzEDAC/JZPDS/v1bsod++7M9u3cnAUCJ0Jetl//3kYnIWiuu54W/ePKJrV3DIwcnXnn1a11bu+KX6+r6Bs+eDYmIAFw7EIvFKJlM8hcCmBnWWhZjwj88/fS9D50bfqH/9ZfaBsq5ibaPPtmx6/7ulmE38erQuXOWKRJREv3fAojH45xKpei6ACKCtZabMpnw+R8/1dV95Ohf33y7LzW8LTWf2FTvDQ5dydW9eaqrZ3v30nwm8974TPHb8VjdrkKhsEk75sEg8I+JSCAi/wtYiCWdDn/5rccf2nni5AvH3u93L25vDNdvu8Fb1d7K0/WhPjdemHTfOrl16+13ZG7rufv+W9p574ab0tuD0PJYNv9cMpm0nufJVYCFWOLx8I8//MEDO//17sHj/Ucbzj/aMX/nfcu9zuYMnHgSbU0xKTSTHhotzKijH9x6g5nVD3x9nfPIfTerDz8afea9wcvvl8tlmpqaCtXiWMIw5KZly8Jf9e7d0f27w38ZmPrUXnx8bXn5inpv5FIdLs1YGH8KFeXZ1kTFyGNO6sIrF/P5F4+3FGdLvPknXwVMLA0ATU1N3NLSEhV5IZbGxvDArp27H/7HPw+dmByT7N5bg7VbOrxsVuF5vxctG7+BN05fwgdrfk7rVRY3t8xJsDQu2aLvF45+rFS+RBdSDX9/++TQO77vU6EwGwozk7WWxHXDw729PY/0HXn2dPZC4tPvbvRX3NPhtTUtQ25iBqpcwio3j/riEO5p9XFj+RQSDR7S6ZSybUpPTPnFXN+gWellMNnZ+efzo6NBZmmrklq3HNqz5ys7f3/4T/+hEmef3OyvvKvDW+K1QZTG5VwJL8tuxFd349hYgA+XPIq73AtI6RmIU2/TqQTplQmaKFGucuTf63esXr1uMpPpGzhxYla8pia7/95Nj+3pe+PgGVWxk9/bHLRv7PAaU60gHYMii9x0gPrOTdiyKgFz5WPcvmYV1pcHAKqAdIy0E0d9IiZ6uauuVChXev2dO+7u7Owotbe/RU/19Gx4ZnTsxbPDg61jP314rvW2ZfUNiWYQKwAWREC5UIQjAsfRoPIsyCSB8gxKbhrWAhYAgTA3N4Wx8fHKmd8M5KXvTPPaffsOSEtb21wq5mSGNjevuGXHusYGt4XYuCCSCEIKM8U55D+bQ75YQd5nTBXnkPcVtIlBm1h1LkPrpHUNK789Redn1fFxN31IvdzfP/038PefaNsg23R8nziuZRICRa3r+wGe/fVhTI1nobWCDUMABD+0+OZ3enHnxnWoVCogEIjFBkWhlTfeVHxtNf1o/4Hn3lVB4HMQhEEIzivtQMSAWQOwYCIEoY+gOINEZRocEmAtCEChAlT8EErFEAQEIgKRgJWGk6ifDwOaBAAFWzsiWEQ0SEw1/8iAQkY8ZsBJBZKoLgwAcxaiTDRf7OcAMWBisgglAtQIQAhisDgQqRowQUKBUQw3rhYKL2QRIASzgigHEmABQJ/fALYKWHSKgqIdiAEQgplBwnCMQrMxoGp0IMK8nQexBosDFiwyuPr8VFfhiEDVmCIhBgnBKIWkdgBWMBzik4KDXOUzKJFFEQFECqAvANQcWAxYG8BWDXyCoxW8pAFV76c1MYsEEcAGrAw4iADMGrQAoGsBkbqIA2GnGpFAhGG0IOkQQARrAaMY0yUBiQJLDCKIDLjWIMH1DagWkXIAG4JYQAI4WuC5GiCBBaAZSDgqqolyQP4iA2ZY68Pa8HoRMZgNRMwCgNlCaY2GlAsihrWAVoRUwYJZAWwgEkYGYmqFtlqbawC1biWORu2dGT40ZoK4BTMsABUQKmGZ3Gjb1TVR7o4Tw8jISHDy1OkyAPwXWfQkSWcWg6cAAAAASUVORK5CYII=',
    "TIMER64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAGgElEQVRIiaVVbUxb1xl+3nOvr++1Y2MbzJeB8Bk+CklDU7VTPjrIkoW00xoqRZVSlmrafk7VplWVqv3Ypkn5k1Vo035V2fajCsqqJo1SVU3TINKxJRVhNDVgMJhQwBiwjT+xjX3vOftjGO02qdPO73Oe933O+zzvI+H/OD0njvc/3X34BZMkP/e95/s6ykpdzsDjxWUAfOeO9L8AEhEAQNU0nP5O7/etFkv2+s1bQxuRyL2tTGaipbmps9xdVvF48cvFnTfsm4IzxsAYIwBQVbNHU9WGRDpzu+9sX++rFy9emPXPce+078O6mtp6ImjfmIEkSUREEuechBASAG5WlKbNzc18taeGXjj7/DsNDfU/PvPdU+2Li0vDDx+OP7udL0zqup77rwyKnTIAMAxDEJHh8Xh4U1OTYbfbkclmlrs6n7D9YOCVN00muWV+zo/llZWDNpvN2d52IEJEhR0s+evgRMSEEADAy8vL5XPn+g/Z7LZT3Ye7KzWLxTQx8Y9EKpn6m9vlUGempy+oFgs2o1FUVHl+k4zHPBWVFVld19O7eF8DhxCCqqqqxKVLl851P/XU64uBwLfWQ6vCMHTSdR2ZbBbEJCEr5g3f1GRFIZ9PWCzalGEY1+/d+3Tc558bISISxS53Z8AYIyEE+vv7Sy5fvvzLUpfrrU9HRvZ75xaQZiqEtRS0zwVDsSCTzVE8GrZwbtD+/fXBjXDkV29f+ePQ4cPdoWPHjr4sSZIWCoVWiIq6K1ZEVVWVGBoa+q0kST+7du0vhrX2AD3Te4a1tjVDcAOFbQMWu4KtWAbzvknhfziK0GKAuBCfEdFPjh49+nNNNZ+Px2IP3rk61Dc8PByX/vU7JAYHB3/oLCm5dO3au6Lt5IvU92I/M/M8woksgutRJDJZRDZiyORycDhc1Nb9LOWzaawuBjyqaj4X24wemp70yi6nazYajY1MTk1GWVExoqenp+TIkSOv//3+fXI0d9FzvSdZIhKBN7CMx0vLYCYFFus+GHoe8fAaTKoGa4kNTx7rRXPbE3xmZtady20/0CyWH733/s2Xb31wy78jUwKA4ydOnJ7xTbdtZgo4dqqPsolNTExOIZPLora+AZIQSG6E4HA44Kmrh2pWkI3HQQCePv5t7nS5IJlM3o8/Gb4yPDwcy2azBACMc47a2lp0dnb2htfX4PDUi+aWOkzN+iGbNcRWHuPDP/8Bqeg6XGVlyCRjcJTYkQyvYXl+BnbbPjS0dkgHDz2J0dHR09PT03WSJBlCCNphwIUQ5vz2dlVqK4tKTw0yGQ5buQfNHV04+dIFqIoZ77/9FoKBGVRX10CRJVRVV6O+sQmMG2AQKC0rAxFpQgjJMAwUVbrrVlNma0vLGwY0VRHzU58jvLQAGYCJEQZ++gZqGw7gxpXfQ1NMMDGCqpiQikWxODuN6NoqJNkEs6Jw7Nmku06WZXkbRClwA8Lg1HSwG654GmZFgQQOkS/g1dfeQDYVh8QAmQQkAloOtIAZjVBkBv8X40il07IQghUNu8uACSEKhYK+QIJjc20VigTwQhb6dgYyI0gkoMgM5eXlUBjBxAgobCO/lYJJYpBJiGg4DKvVGtI0LSmE2F3tEhFRMpkU7R0d3GKxvpJOJ5nDXY2FmUlkUwlUVlZCNZnAwMEEh2IiWFUZM94vsB5cBoFjK5U0blx/T3I4HO+mUqkbkUhEYoxxIQQkxpgQQsBqtX7Z0NjYsxZcqdcsFv7MybO0z2rF8twsSkrsKLFbYVUlZJJJBGamUVdbi9b2dtitmhj+5GPp0eeP4sFg8M3x8fEVxhjjnItdmRIR3blzh3u93l87HY7w2Mhttu73Gno2DX07A0WWEFwIwDfxCDIjyIwQj4bBuMHHx8bERx/dhtvt/l0wGLxf9JWxmyd7YyAUCi00NTenIcTZiQejrMxZond1HxFlZU6KhFYRXQ+hs7MDddVVopDPG38dGWZDV68yIrq5srLy2tjYmAFgd8BfWdfFyTO73c4HBgZe0jRt0O/317S2tomOzi7a39gIu82G2GYUG2shMen1ks/nM5xO5+DS0tIv7t69myviiT1NfzUPGGPgnJPD4RDnz5/v4JxfjEYjZ6wWa51JUSxmRWEFXc+l0+lIPp//LBAI/CmRSIwEg8FtXdf3xsB/LrCXiaqqvLS0FDU1NRWqqnatra2V53I5pbS0NOp2u+eXlpZmfT4fL25i/Bty8fwTRd0OV+xMEysAAAAASUVORK5CYII=',
    "CLOSE64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAE30lEQVRIiZ2VXYgdRRqGn6+quvucM/85iRoTNevMBJFEWY0GFQTBC1HBlaz/jMpoFFfXBdmFvdiLvRIEFRHFGBXMjUQhF/6Bol6sSNaIruCNir/R/Dlx5iRzck736e6qby/6JDlx9CIWFN10Ue/7vW+9X7XcDn8bryWPL2vERkNQQPj9Q72K7F3s7Hxb9bZ98L0bj91jt1y23kxNTxIEGUQ/aTYR6WW9cud/Prx01zf7/7FP5EHXHG7Y6bVTpBPLMSegCWKEEMKvkihgjEWDP+FbEjxTa1bjv9l/CsIKF3ypHhUDSFGACCKC956iKKjV6/hfkCjgUNK0TW1oCA3h+EJk8UUBYFCsQaSyRajArUWLnEONcTrT68nTLtZaEKmmMTiUlsREGy9HO0dgcL1y6lgtZrAsEYFexhwxq2buYfru+1mcOo+828UYg4rgUH7OSkY3zbDq1lkaV1yFP9TqEyy18jiBCMF7DjYmOOu+hxifnCSKItZuvp/F6fPJ05TEwE+dHhN33MfpGy4iFAVjf7qF8etvBV9y1IilBApGIMt6TExOM372JKqKqhLFMdOz93Jk6jx+bHVoztzLyj9eiHqP2Gq7O3UlGAuq1RwYDlUwhoChMdSAz3ZxaEeD8T/fBggaAnGtxpqZWdKFBSbOPLMCCQGJItJPdrHw4lOYRgNsBM6dSCDGErIuodtGkhoyPEr68U5svcbI1ZsQY0CV2vAw9ZGRKjEiSBTR/fQjDm9/AddcjqoSul182kYHVDhJauRffUH7wD7ilatxzVOwI6PM7XiJLO2x4rob0CgGVTSEKigidD94j/ltW9Dg0b0/4BfmyQ8ewKUdWLZ6wCIB9SXFXJvQ+hLkc6QeEznHf199jY1rpjh1w0ZUFTGm7z18/tSj2Hffor5shKLdhhJCADMcw7IlKRIkAqkJRIa4LPl6d5c/PPJkBd5vpArcArD+ue101l1Md08bFxuIBUlOyOUggUIAVIl94Kv5wKqtz7L+7r/0bRHEmApcFbwnHhljw6tv0b3kEtK5gDWmj/GbfQAWZbdaztjyPOfP3oN6D8GDCO133uDAvx9CyxKsRX1JMjbBBa+8Rnbl5RSpR35RfXUGfVLnYGFBcTfdwLo77yLkPYy14CLa773JngfuoNy7QOh2WPnw09WVkufUm8s598G/s+eT9wmBJZ1m+sVTFNBc4Wi8vJ3v//kAJk7AOhbf3MGezTfjWwuYCcv8s1s58K+/okWOxDGdjz5g7+YZtKRSoL+igCp5FKVntGk48sTTzDWb1C+4mB833wgETD2CELBjEfNbtyAjo4xdcz27N11L6B5GGoZQhN+26KiSoII9LebnJx9BkggzNIQkyfEdItiRQGvbM7S2bQHJMGN1NO8ds2dQhBORYBCjAFEE1kFSw0QxuAiTJCAGce64vz4gviTkOTJcErIMMRbyDIxg7bHTFnc47clcmpdj43VkeBRJEkytgdTqSL2OiRMkSRDroH9t4EtCUaBZhmYpIUurZ9pFfVnuX+w62xfjeq3D3/6vbifXrT1XkzgWdREmipA4RlwMUYRY21cg/X+lJ5gSbIHGOVovCHmOCSX7DrbMx599icIhVI2cA5c5mC1gbGnITm4oqAOr0PoOXs9g51HAGiITyCDByXDp4KuiaoESmP8/YC0Y5GajmEsAAAAASUVORK5CYII=',
    "PSG64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAABlbkiVhkyZikyFjmShjkyhlly1mli5nlylnmS5olyppnC5qmi5rmzBpmDBpmTFqmDFqmTFqmjNrmzFrnDNsmzJsnDJtnTFunjNvnzRsmTRtmzVunTVunjVvnzZwnjlxny5toDFvozJwoTNzpjVwoDRzpDRzqDd4rThyojp0ozl1pTt2pjt3pz11ozt4qDl4qTp6rTl6rzx4qD55qTx5qj97qz17rT58rD98rT9+rz5+sEB3pEF9rkB+r0F+sEF/sT2AtD6AtT6Btk+Aqk2CrEOBs0GBtEKCtEGCtUSBskWBs0SCtEWDtUaEtkWFt0aGt0KFuUCEukOGu0eFuEWHu0eJvEiGuUiHukiIuUiIukmJu0uKvEyKvFCAp1CCqlODrVKCrl2Kr1OEs1KGsFWGsFaIsVaPvFqKsl6NsmKNsWeTuG6YvHadvlySwV6UxF+YxW+bxW6dxnKewHGex3SdwHSfx3egwXGizHmgwHqkxnyjxHio0f/RMf7QMv/TMf/SMv3SNf7UOv7UO//UPP/UPf/UPv/VP//WPP/XPv/VQ//WQv7XQ//WRP/XRf/WR//YRv/YSP/YSf/YSv/ZS//aS//ZTf/aTP7aTf7aTv7bT//cT//bUP/cUP7cUv/cU/7cVf/eVf/fVv/eV/7dWP/eWP/eWf/fWv/fW//fYvzcaf/hW//gXP/gXv/gX//hYP/hYf/hY//iYP/jY//gZf/iZP7iZf/jZv/iZ//lZv/jaf/kav7ka//maf/ma//kbf/lbv7mbP/mbv/mb//id//mcP/ncv/nc//ld//ndv/meP/ocf/ocv/oc//odP/odf/odv/peP/pff/qfY+11ZSzz5G41qC81aW/1P/jgf/qiv/qjv7qoMnZ5szb587d6eDm2+fo1+7v3e/x3vXw1fHx3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJblQd8AAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAABcQAAAXEAEYYRHbAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjFjKpxLAAABzUlEQVQoU2P4jwNAJZIEuLJA9M2u/iNgAaiEELOgIFPc//+r6puaalvAQmAJdg4pPj4h5oT/2+raWtqaGmAS/PxC/IJAGdbEB7saW5pb20AyQAkFNiFhUSEgkGZNfjizua29u70XJJHr8j+eV0RGVkRMTJb56u2mvt7eSR0gCT2gPsbMGzbi8hJyPDl3OidPnDRlwhagRHbG/zTXe5WSqqqqmpzXb/VMmz5jztSVIDtSWFLvl3Jrampq8ZY8WThj1tx586ZCXFV9t1xRR1tbR6Lw0f6ZC+YvWDAb6tz/xUom+rrGymWPD8xaunjZ0oUgMZBEsYqZqampWsnTY/PWLF+xZhFIHCRRpW5raWFhUPT/3IJ1a9euW/H//5oTYAlDezs7Kwvv//+XbN6wcev6//+3/z8FltDwcrC3N8/7v3rHtu07Nv3/vxVo0CWQhJGPm5ubdf7/TXt279699//JnTA70j38fH19wv//33b00OGj+w6fPXz5KMRVTiH+/gHuFf//7zl+5szZs2fO7YPo+H/FOSIyPMqz5v//g+dAMocvQCX+XwsMjYmNdgSy9p0/d/bgRZAYWOL//4LgoDAwY+++02AaJoEJcEj8/w8A4UqG4COjF7gAAAAASUVORK5CYII=',
    "CPU64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAFi0lEQVRIiZ1WS2wbRRj+/5l92O6u7WycTW03xLEd6uBUjVO1hfbSByLQEwhxoIhbCwcuvApUiEOFhAQVEiAOvK4gqoojSEhtgUNApaJKG5qkdWpa1JfcZL3teu3sZmeGQ9ZVGhIJ8Z/+2Vn93//45psBWMMqlcoXxWLxEACAaZpju3btOkkIoZRSsnv37hOmaY4BABSLxUOVSuXzteJIq32klMqyLBuqqhoAAKqqGpTSpKIoSUQESZK6OnuKohiKohiUUpkxtvivWCvWBABEOp1+sr+//5V169ZtnJub+6FUKh3Rdf3hVqv1l6Zp5Ww2ezASifQ0Go3fhoaGjsZisYdardaM4zjTiEgAQHQC4j0HkQghAAC4oiiJRCKxBQBIs9m8oOt6iRASa7VaVwEAYrFYP+e85TjOpXg8PiyE4LZtn/F93wYAgogghOD3AYS+UFW1q1AovGoYxp4wGxIEgS2EEIQQCQCAcx7gkslCCB8AwLbt07Va7SPP8xqdWPdmIElSxDTNfZZlncrn828MDg6+VavVPvF9fy4Wi/X19fUdWJHMfSaEYJlMZgwRpVqtdtQwjD31ev2HIAgWpJGRkS8VRTEMw9g9OTm5v7u7+9GpqamXq9XqxwAAmzZt+oBzjpzzYC0QIQRDRJpIJLanUqmdw8PDX1mW9ZPv+5bkOM5FVVVTiURia1i24rruDQCAUqn09sDAwCHGGEdEadnwlgOJZT5BRMIYc5rN5iXP8+ax0y9N04qc84Vt27aduHjx4uuEED46Ovo95xxEOH1ExKWEhQh9DPe4JEl0fn7+14mJiecQUWo2m7MAgNQ0zb3d3d3bhoaGjrTb7Wld1x/p6uoa2bBhw4uyLGsAEFBKKSIi51xQSjFcIiICIQRDAhDXdWue502Vy+X3hRALqqr2SoODg2/KsmzE4/GNlNJ1nPOF9evXPxYEAbiue7lWq72rKIphmub+GzdufBeNRg1d14cZYx4hhBJClFQqNRbOQlBKo8lkcms+n48vLi5a0vj4+OOKoiTT6fQzjuNcJYRIQRCALMswOzv7LSEk0tPT85TjOBeCIKi12+1rtm3/ruv6FgDgAMB7e3vHgiAAQgh1HOfquXPnXr958+Zx3/dtshopltp7nyEiUtd1rxuG8URfX99B13Un2+32rKIo3ZzztRgMdOfOnT/mcrkX+vv79zcajVOapm3XNC3HGINoNNpnWdZJz/P+TiQSOzRNK6bT6WcjkUh/q9WaQUTIZrMHEFEjhECz2fzL9/2ZkZGRz0zT3JfNZp+WqtXq+5FIJJXL5V5kjLVDdgDnnMVisYFyufxVSFHgnO9gjDFElIvF4jth34ExxgCAIiIyxtq2bZ+5cuXK5wsLC3NSvV4/BQDCsqw/hBBBLpeTO+WF/KdhC0TIHAoAIggCjogYMnjpEBAi27Z96ezZsy90aCoVCoXXVFVNZbPZ/TMzMy9xzr1ljSdhYLHicN0DCkFYWKFnGMamUqn06fXr17/xPG9e0nV9Y6jnWqiAPCydrTm5laxY+pcCABdCcEqprmnag4qiWNLExMTBZWI3Ho/Hd2Qymb1CCBpm+V8AQJZluHPnzum5ubnx8+fPH+iI3apync/nX04mk9vDXihCiMX/K9drXTjJZDK5FRHJ3bt3/9R1/cH/e+Esb0FnkKK3t3ff5s2bv+7p6Rm7devWsXK5/GGhUDjsOM5kNBp9oFKpfKNp2kC9Xv9xdHT0eCaTed513fPhlYmd4CsBOiDQarVmu7q6KpZl/XLt2rVjQggvHo8PTE9PH242m1PpdPrRy5cvf3L79u2fo9GoyRi7U61W3wsDL5fv1V8VjLFF3/ct3/ctAADP86wgCBq+7zcAABljtud5FgCA7/uWLMvWai8KAIB/ACsf4Gh+DNwbAAAAAElFTkSuQmCC',
    "CAMERA64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAF4ElEQVRIiaWVS2xcVxnHf+fc58ydh2fGnthO7Dixa6etQtQKaChKqUCVCqFSN92yQEKqVFGExKZSJcQCZcWSTReIFXQDCKkIVEAqURVQCImdtLVJmiaxM+PXjOc99965557DYkwSVJAqcY7O5nv99T/6f98n+Kznwo3ngDeBFWD+gd2whuDrvHF6+7+l2Z8ZIDW/zbjWxPe+WOb8Yp52pPnp1SZ/WO+ewZO/Ac7+XwC+awU/Olfl22cmqGTGaVOBjRCC36+2nvlfeYILN5ZJzR+fms3ML5dcImU+HaUMx8quev3zFXsqYzFKDUIILAHvbQ146+9NtkLFZlcxiFKAHSzxHd44/Y7gwo29Y3ln6s1nJzl/Mk87TsdFH8URgiQ1tKKURD90SAFZR5IauLIz5OpOzOV6yL1OzCglQXLeBsovLxepBh6rjQRlDAIwBsQjGA/LCkAghAEDw3jsXS5n+cJsnienBvzyozYf7g1tjfixDUKUAhfhOHRHGsTD4kYYHsCYMRMBYARGHBoPg402tLopC6UMXzuhOYiUqPWTeZtGJH/2bo23HUmSaowxaGMwevyEZSEtMEahVESaJqg0QZsUKS1s4eC5rjpSzo/OPj6TOTWbE6V8hsVSloOwZ9sME2rDIVgCXAfPd/F9Fy/j4Gd8wiim1WhCMmC+5DI3nWO2nCHrWvRCxVZzyGZzaH24uevebw7155Zm5BMnyuJYJce1nUFgozUyq/EyFkHgUCxmKRZy5PMBtitp7TdYCFyerBb50mOTHK8UOFrxCDzohrDVHLJR74iLG7v2pY0Dc+n6phnEmsJUTiCFtFEpaIHRAq0hVYYkSYnjhP3dFtlRh3MnJnjhzHGCwGV/YFjdUShtsKWk6Gd5ZiXLyekCxyo18c7lOmsb9/VEuyK1NMImScBYCAHSgEBgDMRRiBn0OLtU5htPz5FKh19ca3G5NqDWSYhVim9bLFZ8nl8IeHE5z4un5+jHWrx7dUfubO1ru1KQNumYwaE4xnI0mngQslTx+crKFLbj8Ku1Jr9b26G/t0vY65MmCbHrsD5RotOZRqA5f2qCc49N8sl2n96dgUm6obYZKTAWCDlWuACDQcUjVhYnWKoGfNCIeW9jj+7uPvNZw5mlaaZyLlutkGu1AbWtXf7sWzy3kOfUdIGVuYJYr4dWchBKySgBYxjfsbQFgB6xXA2YLVjsdSI+rnexdcJCtcBctcj0ZIGF6QnmygFJFPHPWodeOOJE2eXkVB5HGpF2htJmNAITfKpjPVswmXUpWJDEMckgwi5kiaXH7XaK3U1RGpTrI72Ubm+IVoqSC5XAxRGg+xH2vxmIwy9CCIQQZD0fNZ4GeCikUBi/yN2BxI00UkBqIFQWTjaL0+3iCYMyoAHPcRCJujlmoFMwGmM0JlVobeN4LvuDEX0Fk77FkbxFbAxtbSMeTClBqlMskbBQsCh6klYIrWGCLYSxBf+QKI1IYtJ4QNTt0Nnbp7G9Tb8/pNaO6UaaJ6ZzPDuXp9/cR+sUy/dxggLS8xjFEUnngK8ul5gtOjSHCfVmSNweKmHSS4d9YEBIkBJpW1jSJjEWdzsj1uo9vjyf5/svLIJWXLx1j/pGCCMNnsXCdMA3n57h1eeXsG2LD3ZD7tR6tG83Wkl78GubUdLHdXPC87AMOJ6Pm3HRlsv20HClFlINXE7NFPnhS4+zXmuz140YqRTfsThWDjh1tMREwWe1lXDl1gG3rtWS3t3GK+bGa3UbpUK9dZAVOV861RJexiOTy+BnPRLf4ZPU5i97isiyeGqmyOmjxf9cdkAtgvdrA/56q8WlP91M7l+99630xmsXAWxS/ZJZr/9cWdZygpQjy0JmfUzGIbFdhhIanuFuxWXtSMDxis/RskfgWnQjxeZBzN1GxM16T6/+7U5//f2PXx1d/+7bj64nWP7JCsa8heFhQ4jDpkg1Xs5jZrHK/Mo01fkKlaNF/KzLsBfR3O7QrHdU4/7B1u3VrR9E11+/9yjDfwGSndm1qwVxegAAAABJRU5ErkJggg==',
    "CHECKMARK64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAD2ElEQVRIibWVe4hUVRzHP+feuTOz82gfrqwu5gM0zTQpS1yfoGQPyc3IjOwlYRElPcDICEP6Q6i/IvojCCIpTA0tSlSqJSvbTLZE0dbWxz4nXWdnxrk7M/feOY/+sHyEa7pt57/z45zP53x/B84R/B/jzeNLgeXADDHE4IeBV2Ihpo6LK1rzNqEhAW9oGw18UhelYeWUGHFL0tx5lqPZyBAI3mi9o8YRm16cWTlsVFLQVwjY2+4SSI2W+j8KXj+ybmwytO69xjo7Wyqzr8sldbaE60ksIdBlhTVo+KuHXppY5azftKzeNsbQkurntOuTKQQobYhFbCgPNsGaA5NDWm94ZlYV7fmAX3pcenIlTucDAqlJRENUxcJQLgwiwfMtYcpq4503JMJjq8M0d+XpyBRJnfUpBpJwyKYqFqbCcSCQg0gQyCeq4qHp90yr5Pd0kY6+ImnXJ1CaeDhEdSJCTSJKzLEHLXhu4oQEuWKZ79uzZAoX2hKPhOn+I6DtuEdfLriC4NE9L4CYhzEP8dH84Hz9kT0NBHLqvMlJmo5nyBQDylITj4RwM5rmw70orcEA0AL8Q/DgN8OBr/DltL8q64G1F52+obomwr6US7boE0hNhRPiVIdHx7H+EvA2sJ0tC3/+e8uFS27c/SS+7ElGrGkbnp5EfV0UArmGxt0Lzq/x5YzKWocz/T4FXyGEINvj0XE410QgJ7Fl4dqL4ecS3PVlJYgdllKzx04ZxqolY8h4mkm315JPl+z+nP8Bd++4hZ2LM/hyuokLCr7Eti28TJnOA5ndGLOUnYtLl+u2YMHnJ4BxY2bWsWj2SA72eoBBG4PnBvy2qwvpq81gVjhJp1Q7q9axLIFVMqSaz3ytfLWEpsbLwgFs6pc1o/R9+e7+eK9joSMWvjR4gSLA4FSGKLS7UyirUmRkbJFTG0VI6N17+oR0/bl8d/+A8HMJAG7bPB7BTmGL8TVz64mMiKGNQSuN0hqvq59CS59Kzq2zo8MrcH/s1V6qMIf9y5uvBL8gALj54xpgG5aYH589klB9BdoYjDY0XJ9k9HURPj2aRZ/ycL/tfouDK17+N/ilAoAbP6wAsRGLB8INI7BGJUAYLGEhLAtLCApfnDymc95NtD4eDMC8ZNiXzNKfSdLbt5K8N6o68nNMwoHqKCAwlkVwKI06ln2MtpWtVwMHBnjspHyNQO1Xe7pRbTmUEchCGbk/laKsdl0tfGBB51OKQM0hUD/ppk7kkTTy11NQku/TuUpdi+DKn/7wdyuAHzDcii0Uykwg/ezJoRMAVL9TCWwFjpJdvfpa4AB/Akx4zQw8GDagAAAAAElFTkSuQmCC',
    "COOKBOOK64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAIAAAB1KUohAAAACXBIWXMAAAsSAAALEgHS3X78AAADMUlEQVQ4jY1UQUgCWxS9782M5hQzle3NTRGEQdBipCKKECoEg4JaJAqCmyBsYauEaNOqjQtXLSKINuFiKMiwIMtF1iLK2kWRZLUoDRxo5s37ixfSt4+/u7xzz51z7r3nwc7Ojt1uBwCEEPwtWKXL5eIvLy+HhoYIIYIgCIJAKa0Do5RyHPfy8nJycnJ1dcUjhJxOZygUOj09zefzFoulDp5SihCKRqPLy8vJZBI4jgOAo6Mjj8cDABjj/6WdTqdDoRAAfJeyFn8MQohhGADAY4xFUSyVSpIkAYBpmgih+soRQmxm2GazbW5u7u7ujoyMKIrCmP+ePMdxv9nhSqXi8/lmZmb29vay2Syrs1gs8EM/QogQQgipBWOMOzs7397eWlpabDYbAMiyHAwGu7u7mQTWzu/3R6PRxsZG+HERvNVqjcVix8fHfX19Nzc3T09PHo+HUjo1NVUulx8fHwFgbW0tEolQSguFwtbWVpU/rlQqs7Ozc3NzqqrmcjmXy9Xe3m61WgcGBubn5wGgo6NjYWEBAEql0t3dHQBUx8ljjNva2orFYnNzM8/zBwcHFoslGo329/cXCgUA6OnpwRh/fHwsLS3lcjm2qm9wQ0NDPB7f398fHBx8eHjIZrOqqhaLRUmSwuFwPB53OBw+ny+dTn9+ftYujed5AEilUhMTE9U9saTX66WUJhKJmv0dHh4Gg0FgF4YxJoQwANNjGIaiKLFYbHp62ul0Li4umqb5H5crSVIymQwEAolEwu12s6SiKNfX15OTkwDgcDguLi4ikUgVUv0zCIJgs9lUVWWlrP3q6qrf72dfAaCrq2tjY0OW5RowTynVNM1qteq6XqW9srJiGAZCSNd1hNDt7W04HGZm+NeFiaKYTCa3t7fHx8fdbjez+9fXV7UR87Cu66Zp1oI1TQsEAl6vN51Os9smhCCEfpbWmMw0TZbBpmm+v7+3traWy2VKKdP825I/M7Isi6IIAFxTU9P6+nomk+nt7X19fX1+fsYY1/ez0+k8Pz+/v7/nMMblcnl4eDifz5+dnWmaVgfGolQq2e32sbGx7wcok8mMjo7C396wVCpFKSWE/ANWXYLwO0+V8wAAAABJRU5ErkJggg==',
    "DOWNLOAD64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAEl0lEQVRIia2WS2hdZRDHf/Odc+7NvbZpYky1Gk2rEbRa26pt8UWLitYHIqigCD426kIQxCfWhYu68U0RBBERXCq+WrXoorYIFWpbEY0lpk0NTWKtuUlzn+d834yLm5smqQspzurMcM785j+c+eYTTtUcESAzvhIAm/+azHacA4FodszAVNFTrUPm+Q6iecmUyJkEna5OiDCCXCtvJ2cnV+Ep+9R7/VIfQhmeryKeySywok+SSMMKMwqAihDXPIcPDDMURUQCgiPWjJCck6x87ZXXV3cXu3XTO5tkYOvAHbnIfZipTpghLdAMIEngi1cXvtlzwfrHpG0x5ismzsvo0E9D9z7z++M799s2EcSm67OUkAs5cpbzkkoMtPtAAzdXQ9zqjHkt1Ol5SHofx0KWYRUxrdiS3FlLtzz51wd7+v2OQl7qHnPtorUXS3ZxPRUKUT5x4mTDWu559LbCNS+9X9v025Duc4KoYdMAA7A4Mk92EMp/JFIZwR/rx9dL1teVdC2/Qe8yzQg+pS0JvLUzx3hjioPVQamGGlcu47KNq6qrPj+fsd+GeAEYA2SmRQiCNSJKP1Ad3IVaG0nnlWRxKqkkVlYxJxGZwhmFIo34U/fh0Hv4v6YYrY+ihYtkorDUNj+298GPvzv6ZRrkMzA/oyCXh9rEMOOHfiLfcx+5zhXkOnppswxEpJHVxdTjs0CycDHy9XcMlwc5a0E3EoTconOls/dyBsb6lYRLY4m/9T6blDgi8oHw3rPx83fesubl4oVPWFvXBUKoQzqB92Xitpite77n/k/epaN7AZO1CTIROtZ14fJC6ccS9ndGUhRLK0Eum1h2YGpH5eFfD47sjluzcFo+f+vp655F03alNhZhASMjloA1qtzedzab125kiw2QLhHaQ0zIFM2MztUdkBcqx1Lp+0o59NGRP49OVQs0Z3d6nEyMUMP8OGgVtAJaA19CagP4xn4e6DPuPhox1V9HTRFr/h9mRmWkwbJtGSsHK4xXq4cQGQDCDABM0ClEy6DlJiA9DLV90BgktirFzhrPXX0mT6Y9lAaqkAhRItRKGT3bjetTYd2aYM7JYcwm5wwaAP44hDyQYukokg5jliICZoFIoNjZ4Ol1HdhueOPgCLlFjt7twvo63HwztGuipml20lEBBlrGfBXzR5BsDGjOPBrAAkJKRKBwuuepNUXyP5/HN7tKXFGvcuMGY/3qhAO/NLCTJ7kFmIT0OPgjmAhiYKYIASFgGoCUyAILu+o8ckng0jSwsF1YuzxP0hYwm3tizwIIpKPQOIY4BXUYCiiYYWSIKYYHMoRAV1fKTddFxJKQOA/mmW9zFWRjoCmYw6R1lrcg2kxgAfCIeRxKMa+YBSw0Vc7fOScAZuAnMXWYE8yaIUFBDFSbS8sCgscsayZWD3jMAmhT7b8CnDPIeZw6RGTOLmwWFRALMA3BZvkamoBcwM3Zh7MA9Yb5I3v/YKoKTlr9sROKZVrlTGDWsylmkMTGxCQ4h0ObGaT1aRJzHsbtwJJmWSet0/9kIpB69gPbgersJA4oMm/pn6JlQI1/uWX87/YP06p9rkZQnAYAAAAASUVORK5CYII=',
    "GITHUB64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAADAwMDQ0NAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhyjGAAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdUAAA3VAT3WWPEAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAABzSURBVChTbYxRFoAgDMPQ+98Z1zbIeJqPbU3RMRfDECqyGpjMg6ivT6NBbKTw5WySq0jKt/sHrXiJ8PwpAAVIgQGkwABSYAApMIAUGEAalFmK9UJ24dC1i7qdj6IO5F+xnxfLu0jS0c7kqxd3Dk+JY8/5AKFrLuM7mfCAAAAAAElFTkSuQmCC',
    "RUN64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAszD0iAAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdUAAA3VAT3WWPEAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAABqSURBVChTpY5JDsAwCMTy/09TMGvFpVF9aAZPRHpkcXC7OIodPg0uCjPq+MwCrWRGKkiIvLyTqzw3aqoI73eqUNAoXBXlg4zudxF+NONfPIVvbSZPgww5oW0Vz8T4Lgbt/xbjia+rahR5AEYEg4vdzh2JAAAAAElFTkSuQmCC',
    "STORAGE64": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAABwcHDQ0NDg4ODw8PFxcXGRkZGhoaGxsbHh4eIyMjJSUlJiYmJycnKCgoMTExMjIyNTU1NjY2Nzc3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAouNksgAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdQAAA3UAe+RuhUAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAC5SURBVChTfZLbDsMgDEPpbb3TDv7/W7PYuAztYUeqhO2QAGowkXIMIeYkaSU4QsNBi4GcyhNINpTglmq4GWSphvy/ldkuLXZ4HmAxy3NmFJaA4guKGCwsjClfV05+fWdhYBtFw+amB292aygW3M7fsPTwjmadZkCvHEtWaAYTViBqVwgTA3tJVnB6D/xhaimItDhjMBvlhtFsaIafnEtOaAY/twAw/eslK70CbX8obUvgJNw9Jv0+Zh8D4s5+VAm/LwAAAABJRU5ErkJggg==',
    "LITTLEBEAR64": 'data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEUAAAAAADMAAGYAAJkAAMwAAP8AKwAAKzMAK2YAK5kAK8wAK/8AVQAAVTMAVWYAVZkAVcwAVf8AgAAAgDMAgGYAgJkAgMwAgP8AqgAAqjMAqmYAqpkAqswAqv8A1QAA1TMA1WYA1ZkA1cwA1f8A/wAA/zMA/2YA/5kA/8wA//8zAAAzADMzAGYzAJkzAMwzAP8zKwAzKzMzK2YzK5kzK8wzK/8zVQAzVTMzVWYzVZkzVcwzVf8zgAAzgDMzgGYzgJkzgMwzgP8zqgAzqjMzqmYzqpkzqswzqv8z1QAz1TMz1WYz1Zkz1cwz1f8z/wAz/zMz/2Yz/5kz/8wz//9mAABmADNmAGZmAJlmAMxmAP9mKwBmKzNmK2ZmK5lmK8xmK/9mVQBmVTNmVWZmVZlmVcxmVf9mgABmgDNmgGZmgJlmgMxmgP9mqgBmqjNmqmZmqplmqsxmqv9m1QBm1TNm1WZm1Zlm1cxm1f9m/wBm/zNm/2Zm/5lm/8xm//+ZAACZADOZAGaZAJmZAMyZAP+ZKwCZKzOZK2aZK5mZK8yZK/+ZVQCZVTOZVWaZVZmZVcyZVf+ZgACZgDOZgGaZgJmZgMyZgP+ZqgCZqjOZqmaZqpmZqsyZqv+Z1QCZ1TOZ1WaZ1ZmZ1cyZ1f+Z/wCZ/zOZ/2aZ/5mZ/8yZ///MAADMADPMAGbMAJnMAMzMAP/MKwDMKzPMK2bMK5nMK8zMK//MVQDMVTPMVWbMVZnMVczMVf/MgADMgDPMgGbMgJnMgMzMgP/MqgDMqjPMqmbMqpnMqszMqv/M1QDM1TPM1WbM1ZnM1czM1f/M/wDM/zPM/2bM/5nM/8zM////AAD/ADP/AGb/AJn/AMz/AP//KwD/KzP/K2b/K5n/K8z/K///VQD/VTP/VWb/VZn/Vcz/Vf//gAD/gDP/gGb/gJn/gMz/gP//qgD/qjP/qmb/qpn/qsz/qv//1QD/1TP/1Wb/1Zn/1cz/1f///wD//zP//2b//5n//8z///8AAAAAAAAAAAAAAADZ9vIoAAAA/XRSTlP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8A9k80AwAAAYtJREFUeJxdkr1y5CAMx93lmsuzWG7AXd7DFaSx3ey9R1zJV4C3yT7INmivWNysHyRXsQ1qTjgfkzkxwwz6SX+QRMWfli5+Wb9OXO17Xu1jOw6t/w8kNSzjuJyOlrcvkDN7vQyH4rmBjd8yDs+/f5Vz7jucJ6BPMAzDK2/b3QLOwRFZULgD2668eehmct6FcPa9hSxgvevBA/pAIZDbl6kLeGgaC0QiEojI09U7gsuNq0HrxhU/XUnE3iZjiEzkCjRYCST6myQBqXeecKpjxXKBiAQ0EExtUqRIdDYkdUR0iAGRYoyYMtIbvkCuNl4MTJ24bWLq6hqzaEJf8TarmYBiQgWYc2IBc38XqX6myZAEJk652NVJIRVnOJPtgK7FJyQRln5V3NeedPNj9nQrKEqvLBTwU2vdtq0GkCdlr2xjGVcB8DSOT8c/GprmARcFuo0cewHqdDqu68YRQHIP66BKs8s8bvvUpVCt7eH0umhm0ALi8gGYN3UBgGZjpaVX8X3M+y9Y7PsHmp/jPzIEhv9pBFozAAAAAElFTkSuQmCC'
}

def isinnerrightclickmenu(currentwindow, currentevent) -> bool:
    """
    判断事件串是否为
    :param currentwindow： 当前事件的window
    :param currentevent:   当前读取的事件
    :return:               bool True：是innerrightmenuclick event， False:不是
    """
    # 如果事件不是字符串，直接返回不成功
    if not isinstance(currentevent, str):
        return False

    # 找寻字符串中代表右键菜单事件的关键字
    returnindex = currentevent.find(Sg.MENU_KEY_SEPARATOR)
    # 如果找不到，返回不成功
    if returnindex < 0:
        return False
    else:
        try:
            # 从字符串事件中恢复右键菜单元组事件并回写到主窗口
            currentwindow.write_event_value(eval(currentevent[returnindex + len(Sg.MENU_KEY_SEPARATOR):]), None)
        except Exception as err:
            # 回写失败则返回不成功
            print("反向回送表格内部右键事件{}失败：{}".format(currentevent, err))
            return False
        print("反向回送表格内部右键事件{}".format(currentevent))
        return True


class SgKeyGenerate(Sg.Text):
    """
    用于给自定义类Sg类产生key的隐藏实用类， 方便系统使用windows[tablekey].eventprocess(event, window, values) 及 update
    初始化
    SgKeyGenerate(eventprocessfunct, updatefunc, "", k=tablekey, visible=False)
    使用:
    if isinstance(event, tuple):
        windows[event[0]].eventprocess(event, window, values)
        windows[event[0]].update(.....)
    所有事件的处理，全部通过update方式传给类内部进行处理
    """

    def __init__(self, updatefunc, getfunc, *args, **kwargs):
        self.updatefunc = updatefunc
        self.getfunc = getfunc
        super().__init__(*args, **kwargs)

    # 用于设置类的各种参数
    def update(self, *args, **kwargs):
        # print("hidingkey udpate:{}, {}".format(args, kwargs))
        return self.updatefunc(*args, **kwargs)

    # 用于获取类的各种属性
    def get(self, **kwargs):
        # print("hidingkey get:type: {} args:{} kwargs:{}".format(attribute, args, kwargs))
        return self.getfunc(**kwargs)


class SpecialCombox(Sg.Combo):
    """
    自定义Combo用于过滤update text_color， background_color, 因为Sg.Combo没有此option
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    # 用于设置类的各种参数
    def update(self, *args, **kwargs):
        # 从combo 参数中去掉 text_color 和background_color, 因为combo不支持，后续可以改写相关方法，支持相关color的设置
        newkwargs = kwargs
        tempkwargs = {}

        # PysimpleGUI.py line: 15409
        # combostyle = ttk.Style()
        # if element.TextColor not in (None, COLOR_SYSTEM_DEFAULT):
        #     combostyle.configure(style_name, foreground=element.TextColor)
        #     combostyle.configure(style_name, selectbackground=element.TextColor)
        #     combostyle.configure(style_name, insertcolor=element.TextColor)
        # if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT):
        #     combostyle.configure(style_name, selectforeground=element.BackgroundColor)
        #     combostyle.map(style_name, fieldbackground=[('readonly', element.BackgroundColor)])
        #     combostyle.configure(style_name, fieldbackground=element.BackgroundColor)
        #
        # if element.button_arrow_color not in (None, COLOR_SYSTEM_DEFAULT):
        #     combostyle.configure(style_name, arrowcolor=element.button_arrow_color)
        # if element.button_background_color not in (None, COLOR_SYSTEM_DEFAULT):
        #     combostyle.configure(style_name, background=element.button_background_color)

        # 获取传入的“text_color"
        tempresult1 = newkwargs.get("text_color", None)
        # 如果存在就截获选项并调用configure进行设置
        if tempresult1 is not None:
            tempkwargs["foreground"] = tempresult1
            # 从参数中去掉"text_color"
            newkwargs.pop("text_color")
        # 获取传入的“background_color"
        tempresult2 = newkwargs.get("background_color", None)
        # 如果存在就截获选项并调用configure进行设置
        if tempresult2 is not None:
            tempkwargs["fieldbackground"] = tempresult2
            # 从参数中去掉"text_color"
            newkwargs.pop("background_color")
        if tempkwargs != {}:
            # self.TKCombo.configure(**tempkwargs) 此方法只能设置textcolor, 需要是用ttk底层的configure
            # 首先获取ttk.Style的configure需要的stylename
            styletype = self.TKCombo.configure("style")
            stylename = styletype[4]
            # print("stlye get: {}".format(styletype))
            # print("stlye name : {}".format(stylename))
            # 然后创建combostyle实例
            combostyle = Sg.ttk.Style()
            # 使用实例处理各个要设置的参数
            combostyle.configure(stylename, **tempkwargs)
            # combostyle.configure(stylename, foreground="red")
            # combostyle.configure(stylename, selectbackground="red")
            # combostyle.configure(stylename, insertcolor="red")
            # combostyle.configure(stylename, selectforeground="red")
            # combostyle.configure(stylename, fieldbackground="red")
            # combostyle.configure(stylename, arrowcolor="red")
            # combostyle.configure(stylename, background="red")
        # print("Special combox preprocess :{}".format(tempkwargs))
        super().update(*args, **newkwargs)


SPECIAL_TABLE_GET_MAXROWNUMBER = 'get_max_row_number'
SPECIAL_TABLE_GET_MAXCOLUMNNUMBER = 'get_max_column_number'
SPECIAL_TABLE_GET_CURRENTROW = 'get_current_row'
SPECIAL_TABLE_GET_CURRENTROW_CONTENT = "get_current_row_content"
SPECIAL_TABLE_GET_SPECIALEVENTLIST = 'get_specail_eventlist'
SPECIAL_TABLE_GET_RESULT_DICT = 'get_result_dict'
SPECIAL_TABLE_GET_EVENTINFLUENCEROW = 'event_influnce_row'
SPECIAL_TABLE_GET_SELECTED_ROWCOLUMN = 'get_selected_row_column'
SPECIAL_TABLE_GET_SELECTED_ROWCONTENT = 'get_selected_row_content'
SPECIAL_TABLE_GET_COLUMNN_NAME = 'get_column_name'

SPECIAL_TABLE_SET_ROWCOLOR = 'set_row_color'
SPECIAL_TABLE_SET_CELLVALUE = 'set_cell_value'
SPECIAL_TABLE_SET_ROWVALUE = 'set_row_value'
SPECIAL_TABLE_SET_CURRENTROW = 'set_current_row'
SPECIAL_TABLE_SET_WHOLEVALUE = 'set_whole_value'
SPECIAL_TABLE_SET_BIND = 'set_bind_event'
SPECIAL_TABLE_TUPLE_EVENTPROCESS = 'tuple_event_process'
SPECIAL_TABLE_SET_MOUSEWHEEL = 'set_mouse_wheel'
SPECIAL_TABLE_SET_ADD_NEWROW = 'set_new_row'
SPECIAL_TABLE_SET_SAVETOFILE = 'save_to_file'
SPECIAL_TABLE_SET_LOADFROMFILE = 'load_from_file'

SPECIAL_TABLE_GROUP_REFRESH = '_table_group_refresh_'  # 子类使用的命令，刷新整个group
SPECIAL_TABLE_GROUP_TRIGGER_TAB = '_table_group_tigger_tab_'

SPECIAL_TABLE_CHANGE_EVENT_TRIGGER = 'data_change_event_trigger'

SPECIAL_TABLE_SELECT_MODE_NONE = Sg.TABLE_SELECT_MODE_NONE  # 不能选择
SPECIAL_TABLE_SELECT_MODE_BROWSE = Sg.TABLE_SELECT_MODE_BROWSE  # 只能单选，但是再点击取消
SPECIAL_TABLE_SELECT_MODE_EXTENDED = Sg.TABLE_SELECT_MODE_EXTENDED  # 可以多选
SPECIAL_TABLE_LISTBOX_SELECT_MODE_SINGLE = Sg.LISTBOX_SELECT_MODE_SINGLE  # 模仿list选择，增加一个选项(点击后不取消）

MOUSEWHEEL_UP = "MouseWheel:Up"
MOUSEWHEEL_DOWN = "MouseWheel:Down"

ROWSTYLE_ROWCOLOR = 'rowcolor'
COLSTYLE_DTYPE = 'dtype'


class SpecialDf(object):
    """
    将dataframe数据进行各种df操作后返回，
    """

    def __init__(self,
                 dfdata: pd.DataFrame = None,
                 headings: list = None,
                 ):
        """
        :param dfdata:                原始数据，pd格式
        :type dfdata:                 dataframe格式
        :param headings:              数据列名
        :type headings:               list; [列名1， 列名2......]
        """
        # 用于存储输出给显示的list
        self.headings = headings
        # 用于存储完整的数据
        self.dfdata = None
        # 用于存储处理后的df，与tablevalue一致
        self.dffilter = None
        self.currentrow = None
        self.CURRENTCOLINDEX = 0
        self.STARTROWINDEX = 1
        self.ENDROWINDEX = 2
        self.LIKEINDEX = 3
        self.currentfilter = [None, None, None, None]
        # 数据来源，文件或者数据库表信息，导入、导出时做记录
        self.dataimportsource = ""
        self.dataexporttarget = ""

        # 初始化数据
        if dfdata is not None:
            self.dfdata = dfdata
            if headings is None:
                self.headings = list(self.dfdata)
            self.dffilter = self.dfdata
        else:
            self.dfdata = pd.DataFrame([], columns=headings)  # 出入df为None
        # 为了df显示，需要保存dfcolumns，然后使用headings
        self.dfcolumns = list(self.dfdata)
        self.dfdata.columns = self.headings
        self.text_color = "black"
        self.text_background_color = "white"
        self.head_text_color = "white"
        self.head_background_color = "dark blue"
        self.input_text_color = "blue"
        self.input_text_background_color = "white"
        self.selected_text_color = "dark gray"
        self.selected_background_color = "blue"
        self.combox_button_background_color = "blue"
        self.combox_button_arrow_color = "white"
        self.cell_editable = True
        self.defaultcolor = (self.input_text_color, self.input_text_background_color) if self.cell_editable \
            else (self.text_color, self.text_background_color)

        # 以下部分如果在GroupTab中切换保存到SpecicalTable层级，可以放回SpecialTable，
        # 主要是考虑SpecialGroupTable备份不同表格的动态数据，所以迁移到此
        self.showstartpoint = [0, 0]
        self.selectedrawrow = set()
        self.currentshowrow = set()    
        self.selectedrawcolumn = set()
        self.currentshowcolumn = set()
        self.currentrowpage = 0
        self.currentcolumnpage = 0
        self.colnamelist = self.headings
        self.colnameusedlist = [""] + self.headings
        self.colnamefilterlist = [""] + self.headings
        self.visible_column_map = [True for _ in range(len(self.headings))]
        self.column_edit_element = []
        self.infilterstatus = False
        self.filter_startrow = ""
        self.filter_endrow = ""
        self.filter_filter_column = ""
        self.filter_filter_like = ""
        self.filter_set_value_column = ""
        self.filter_set_column_used_value = ""
        self.v_slider_position = 0
        self.h_slider_position = 0
        self.indexcol = []
        self.col_value_scale = None
        self.visible_column_map = []
        self.rowstyleinfo = None
        self.columnstyleinfo = None

        # 用于保存每行的文本和背景颜色，填写到行格式信息字典中
        self.dfbuildrowstyleinfo()
        self.dfbuildcolumnstyleinfo()

    def dfbuildrowstyleinfo(self):
        # 用于保存每行的文本和背景颜色，填写到行格式信息字典中，只保存有非默认颜色的行信息，从每行都有的list改变为行号字典方式，
        # "row":{ROWSTYLE_ROWCOLOR: ("color", "backgroundcolor")}
        # self.rowstyleinfo = {}
        # [{ROWSTYLE_ROWCOLOR: ("color", "backgroundcolor")}, {}, ....]
        if self.dfdata is not None:
            self.rowstyleinfo = [{} for _ in range(self.dfdata.shape[0])]
        else:
            self.rowstyleinfo = None

    def refreshcolumntype(self, columnnamelist=None):
        """
        根据列名清单刷列类型
        :param columnnamelist:
        :return:                与列名匹配的列类型list
        """
        if self.dfdata is not None:
            currentcolumnsname = list(self.dfdata) if columnnamelist is None else columnnamelist
            currenttypelist = columntypebyname(self.dfdata, currentcolumnsname)
            for i, columname in enumerate(currentcolumnsname):
                self.columnstyleinfo[columname] = {COLSTYLE_DTYPE: currenttypelist[i]}
            return

    def getcolumnstype(self, columnnamelist=None) -> list:
        """
        获取列名对应的列类型
        :param columnnamelist:              列名称列表
        :return:                            对应列名称的列类型列表
        """
        if self.dfdata is not None:
            currentcolumnsname = list(self.dfdata) if columnnamelist is None else columnnamelist
            typelist = [self.columnstyleinfo[x].get(COLSTYLE_DTYPE, 'float64') for x in currentcolumnsname]
        else:
            typelist = []
        return typelist

    def dfbuildcolumnstyleinfo(self):
        # 用于保存每列的格式信息，首先保存类型信息
        if self.dfdata is not None:
            self.columnstyleinfo = {x: {} for x in list(self.dfdata)}
            self.refreshcolumntype(columnnamelist=None)
        else:
            self.columnstyleinfo = None

    def filterdf2table(self):
        return self.dffilter.values.tolist()

    def dfsetfilter(self, columnname=None, startrow=None, endrow=None, like=None):
        # if columnname is not None or startrow is not None or endrow is not None or like is not None:
        if [columnname, startrow, endrow, like] not in [[None, None, None, None]]:
            # 外部输入过滤条件 保存
            self.currentfilter[self.CURRENTCOLINDEX] = columnname
            self.currentfilter[self.STARTROWINDEX] = startrow
            self.currentfilter[self.ENDROWINDEX] = endrow
            self.currentfilter[self.LIKEINDEX] = like

        querystr = ""
        if self.currentfilter == [None, None, None, None]:
            # 无过滤条件
            self.dffilter = self.dfdata
        else:
            # 有过滤条件
            if (self.currentfilter[self.CURRENTCOLINDEX] is not None and
                    self.currentfilter[self.LIKEINDEX] is not None):
                querystr = " {}.str.contains('{}') ".format(self.currentfilter[self.CURRENTCOLINDEX],
                                                            self.currentfilter[self.LIKEINDEX])
            if (self.currentfilter[self.STARTROWINDEX] is not None and
                    self.currentfilter[self.ENDROWINDEX] is not None):
                querystr += " and " if querystr != "" else ""
                querystr += " index >= {} and index <= {} ".format(self.currentfilter[self.STARTROWINDEX],
                                                                   self.currentfilter[self.ENDROWINDEX])
        # print(querystr)
        setflag = False
        if querystr != "":
            self.dffilter = self.dfdata.query(querystr)
            setflag = True
        self.currentrow = None
        return setflag

    def dffilterclear(self):
        self.currentfilter = [None, None, None, None]
        self.dffilter = self.dfdata
        self.currentrow = None
        self.infilterstatus = False

    def dfbatsetcolumnvalue(self, columnname, value=None):
        # 对过滤项指定列进行批量赋值
        optiontemp = pd.get_option('mode.chained_assignment')
        # 暂时关闭告警
        pd.set_option('mode.chained_assignment', None)
        self.dffilter.loc[:, columnname] = value
        # 恢复告警
        pd.set_option('mode.chained_assignment', optiontemp)
        filterindex = 0
        self.currentrow = None

        # 将过滤表的处理结果一致化到原始数据df，注意filter的index和iloc的下标不同，
        for index in self.dffilter.index:
            # print(index)
            try:
                self.dfdata.iloc[index, :] = self.dffilter.iloc[filterindex, :]
                filterindex += 1
            except Exception as err:
                print("df copy from index: {}  to index :{} error:{}".format(filterindex, index, err))

    def setcurrentrow(self, tablerownumber):
        if tablerownumber is None:
            self.currentrow = None
        else:
            self.currentrow = self.dfdata.index[tablerownumber]

    def dfdeleterow(self):
        if self.currentrow is not None:
            # 首先删除原始表中的该项
            self.dfdata.drop(self.currentrow, axis=0, inplace=True)
            # 删除该行的rowstyleinfo
            self.rowstyleinfo.pop(self.currentrow)
            # 删除后重新排序
            self.dfreindex()

    def dfaddrow(self, rowvalue: list):
        # 判断数据量是否合法，
        if len(rowvalue) == len(self.headings):
            # 合法则在原始数据末尾添加
            # self.dfdata = self.dfdata.append(pd.Series(rowvalue, self.headings), ignore_index=True)
            # valueseries = pd.Series(rowvalue, self.headings)
            # self.dfdata = pd.concat([self.dfdata, valueseries], axis=1)
            # or
            self.dfdata.loc[len(self.dfdata.index)] = rowvalue
            # 增加该行的rowstyleinfo
            self.rowstyleinfo.append({})
            # 用当前过滤器重新过滤形成过滤数据和过滤列表
            self.dfreindex()

    def dfmoverow(self, targetrownumber):
        # 判断数据量是否合法，
        if self.currentrow is not None:
            # 获取当前行的值
            movevalueseries = self.dfdata.iloc[self.currentrow, :]
            # 获取当前行的styleinfo, 并删除
            temprowstyleinfo = self.rowstyleinfo.pop(self.currentrow)
            # movevalue = movevalueseries.tolist() 使用series 和 list都可以？
            # 删除当前行
            self.dfdata.drop(self.currentrow, axis=0, inplace=True)
            # 当前行值插入到指定行，该操作影响了df的columns，需要复原
            self.dfdata = pd.DataFrame(np.insert(self.dfdata.values, targetrownumber,
                                                 values=movevalueseries, axis=0))
            # 将当前行rowstyleinfo插入到目标行位置
            self.rowstyleinfo.insert(targetrownumber, temprowstyleinfo)
            self.dfdata.columns = self.headings
            self.dfreindex()

    def dfgetrowvalue(self):
        if self.currentrow is not None:
            return self.dfdata.iloc[self.currentrow, :]
        else:
            return None

    def dfsetrowvalue(self, rowvalue: list):
        # 判断数据量是否合法，
        if len(rowvalue) == len(self.headings):
            # 合法则在原始数据末尾添加
            # valueseries = pd.Series(rowvalue, self.headings)
            self.dfdata.iloc[self.currentrow, :] = rowvalue
            # 用当前过滤器重新过滤形成过滤数据和过滤列表
            self.dfsetfilter()
            self.dfreindex()

    def dfsetcellvalue(self, location: tuple, cellvalue: str):
        """
        设置单元格的值
        :param location:      元组 ， （x, y） 赋值的单元格位置
        :param cellvalue:     值
        :return:
        """
        # 判断坐标是否合法
        if location[0] <= self.dfdata.shape[0] and location[1] <= self.dfdata.shape[1]:
            self.dfdata.iloc[location[0], location[1]] = cellvalue
            # 用当前过滤器重新过滤形成过滤数据和过滤列表
            self.dfsetfilter()
            self.dfreindex()

    def dfsetwholevalue(self, dfdata: pd.DataFrame = None, headings: list = None):
        if dfdata is None and headings is None:
            print("dfsetwholevalue 输入数据均为空值！")
            return
        self.dfdata = dfdata if dfdata is not None else pd.DataFrame([], columns=headings)
        self.dfcolumns = list(self.dfdata)
        if headings is None:
            self.headings = list(self.dfdata)
        else:
            self.headings = headings
        self.dfdata.columns = self.headings
        self.dffilter = self.dfdata
        self.currentfilter = [None, None, None, None]
        self.dfsetfilter()
        self.dfreindex()
        self.dfbuildcolumnstyleinfo()
        self.dfbuildrowstyleinfo()

    def dfsetcolumnname(self, column: list, columnname: list):
        """
        设置列名
        :param column:
        :param columnname:
        :return:
        """
        for i, columnid in enumerate(column):
            # 修改heading
            self.headings[columnid] = columnname[i]

        self.dfdata.columns = self.headings
        self.dffilter.columns = self.headings

    def dfreindex(self):
        self.dfdata.reset_index(drop=True, inplace=True)
        # 用当前过滤器重新过滤形成过滤数据和过滤列表
        self.dfsetfilter()
        self.currentrow = None

    def getrowoutputstyle(self, rowcontent: pd.Series, rowid: int) -> list:
        """
        根据行号，将行styleinfo转换为实际的输出文件条件格式style
        :param rowcontent:          本行的内容，, 暂时未使用
        :param rowid:               int, row 行号
        :return: str
        """
        try:
            color = self.rowstyleinfo[rowid][ROWSTYLE_ROWCOLOR]
            # 在gui方式下，颜色可以有light，在excel输出时没有，系统默认替换为black，因此需要提前去掉light
            return ["color:{}; background-color: {}".format(
                color[0].replace("light ", ""), color[1].replace("light ", ""))] * self.dfdata.shape[1]
        except Exception as err:
            # print("行：{}无色彩信息：{}".format(rowid, err))
            return [""] * self.dfdata.shape[1]

    def getrowcolor(self, row: int) -> tuple:
        """
        获取指定行的颜色配置，没有时使用默认配置
        :param row:             int ： 行号
        :return:                tuple     color
        """
        try:
            color = self.rowstyleinfo[row][ROWSTYLE_ROWCOLOR]
        except Exception as err:
            # print("行：{}无色彩信息：{}".format(row, err))
            color = self.defaultcolor
        return color

    @property
    def maxrownumber(self):
        return self.dfdata.shape[0]

    @property
    def maxcolnumber(self):
        return self.dfdata.shape[1]

    def getmaxrownumber(self):
        return self.dfdata.shape[0]

    def getmaxcolnumber(self):
        return self.dfdata.shape[1]


class SpecialTable(object):
    def __init__(self, tablekey, specialdf: SpecialDf = None, dfshowdata: pd.DataFrame = None,
                 tabledatadictlist: list = None,  itemeditfunc=None, itemeditfuncargsdic: dict = {},
                 itemeditusecolvaluescale=False, indexcol=None, visible_column_map=None,
                 def_col_width=DEF_CELL_SIZE[0], auto_size_columns=True, max_col_width=20, cell_justification='r',
                 col_widths=None, size=DEF_WINDOW_SIZE, cell_size=DEF_CELL_SIZE, scrollable=False,
                 col_value_scale: list = None, filter_visible=False, edit_button_visible=True,
                 filter_set_value_visible=False, border_width=DEF_BD, use_inner_right_click_menu=False,
                 move_row_button_visible=True, visible=True, headings=None, right_click_menu=None,
                 vertical_scroll_only=True, expand_x=False, expand_y=False, select_mode=None, font=SPECIAL_TABLE_DEF_FT,
                 pad=DEF_PAD, max_rows=MAX_SPECIAL_TABLE_ROWS, max_columns=MAX_SPECIAL_TABLE_COLUMNS,
                 text_color='black', text_background_color='white', head_text_color='white', show_cell_tooltips=True,
                 head_background_color='dark blue', input_text_color='blue', input_text_background_color='white',
                 selected_text_color='dark gray', selected_background_color='blue', combox_button_arrow_color="blue",
                 combox_button_background_color="white", column_cell_edit_type_map=None,
                 v_slider_resolution: float = 0.1, h_slider_resolution: float = 0.1, slider_every_row_column=True,
                 v_slider_show=True, h_slider_show=True, relief=Sg.RELIEF_FLAT, cell_editable=True,
                 slider_relief=Sg.RELIEF_FLAT, disable_slider_number_display=False, datachangeeventtrigger=False,
                 showcolnamewithid=False, showcolumnname=True, display_row_numbers=True,
                 enable_events=False, enable_click_events=False, importsource=""):

        """
        表格编辑框类
        :param tablekey:                Used with window.find_element and with return values to uniquely identify
                                        this element to uniquely identify this element
        :type tablekey:                 str | int | tuple | object
        :param specialdf:               初始化时优先使用specialdf，其次使用dfshowdata + headings，
                                        再次使用tabledatadictlist + headings
        :param dfshowdata:               pd.DataFrame, 用于显示的数据
        :param tabledatadictlist:        dict格式的数据，dfshowdata数据为None时，使用tabledatadict的数据
        :param itemeditfunc:             单条记录编辑时用到的界面处理函数，输入参数需要用字典方式带入。以适应可变参数
        :param itemeditfuncargsdic:      dict:  itemeditfunc函数的参数，row编辑函数，
        :param itemeditusecolvaluescale:    bool: True, itemeditfunc 需要将col_value_scale传递过去，
        :param indexcol:                  索引列序号（从0开始,目前只支持单列索引， None为没有），
                                          如果有索引列，会根据索引列判断是否有重复值并强制提醒
                                          None|int|[int,int...]
        :param visible_column_map:        是否显示该栏，
        :type visible_column_map:         List[bool]
        :param def_col_width:              默认列宽
        :param auto_size_columns:          列宽根据初始化自适应
        :param max_col_width：              最大列宽
        :param col_widths:                该栏显示宽度
        :type col_widths:                  List[int]
        :param size:                        tuple(int,int) , 整个lay的尺寸
        :param col_value_scale:            list , None or [[], [], None, ......]
                                           None: 没有任何限制，全部列都可以修改
                                          （二维list， 对应每列的取值范围，用于列快速填充）
                                           [None]:该列不允许批量编辑
                                           []该列任意取值范围
                                           [a,b,c]不为空，该列取值限制范围
        :param use_inner_right_click_menu: bool: True, 显示并内部右键菜单
        :param filter_visible:             bool: True, 显示筛选功能，False：关闭筛选功能
        :param filter_set_value_visible:   bool: True, 显示列赋值功能，False：关闭列赋值功能
        :param edit_button_visible:        bool: True, 各种编辑按钮，False：关闭各种编辑按钮，
        :param move_row_button_visible:    bool: True, 各种移动行按钮，False：关闭各种移动行按钮，
        :param right_click_menu:           A list of lists of Menu items to show when this element is right clicked.
        :type right_click_menu:            List[List[ List[str] | str ]]
        :param vertical_scroll_only:       if True only the vertical scrollbar will be visible
        :param select_mode:                Valid values include:
                                            TABLE_SELECT_MODE_NONE          不能选择
                                            TABLE_SELECT_MODE_BROWSE        只能单选
                                            TABLE_SELECT_MODE_EXTENDED      可以多选
                                            TABLE_LISTBOX_SELECT_MODE_SINGLE      增加一个选项 (点击后不取消）
        :param font:                        窗口中使用的字体大小
        :param pad:                         窗口中元素间的pad
        :param max_rows:                   最大表行数
        :param max_columns:                最大表列数
        :param expand_x:                   横向自动扩展
        :param expand_y:                   纵向自动扩展
        :param show_cell_tooltips:         cell方式下显示tooltips
        :param text_color:                 表格文本颜色
        :param text_background_color:      表格文本背景色
        :param border_width:                 表格边界线宽度
        :param head_text_color:             标题及序号文本颜色
        :param head_background_color:       标题及序号背景色
        :param selected_text_color:         选中文本颜色
        :param selected_background_color:   选中文本背景色
        :param combox_button_arrow_color:   cell为combox时，下拉箭头的颜色
        :param combox_button_background_color:  cell为combox时，下拉箭头的背景色
        :param input_text_color:            可编辑框的文本颜色
        :param input_text_background_color:  可编辑框的背景颜色
        :param column_cell_edit_type_map:       None, 所有单元格使用默认Sg.Input,
                                            list: 长度和表格数据的列数一致（该种方式下，所有列在一个max_column 页内显示，
                                            即表格页不要横行左右翻转，
                                            SPECIAL_TABLE_CELL_INPUT, SPECIAL_TABLE_CELL_COMBOX, SPECIAL_TABLE_CELL_TEXT
        :param relief:                      cell的relief
        :param cell_editable:               表格单元是否可以直接编辑
        :param slider_relief:               slider relief  style.Use constants -
                        RELIEF_RAISED  RELIEF_SUNKEN  RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID
        :param v_slider_resolution:         v  slider分辨率，默认0.1
        :param h_slider_resolution:         h slider 分辨率， 默认0.1
        :param slider_every_row_column:    是否可以逐行、逐列滚动
        :param v_slider_show:               bool, 是否显示垂直滚动条
        :param h_slider_show:               bool, 是否显示水平滚动条
        :param disable_slider_number_display:          bool: True 不显示， False：显示
        :param datachangeeventtrigger:      bool: 数据修改后是否向主窗口发送数据变化事件
        :param showcolnamewithid:           bool: True, 显示列号， False：不显示列号
        :param showcolumnname:              bool: True, 显示列名， False：显示列名
        :param display_row_numbers:                bool: True, 显示序号列， False：不显示
        :param enable_events:           Turns on the element specific events. Table events happen when row is clicked
        :type enable_events:            (bool)
        :param enable_click_events:     (NO USE) Turns on the element click events that will give you (row, col)
                                        click data when the table is clicked
        :type enable_click_events:      (bool)
        :param importsource:            str: 用于记录数据来源文件名（主要用于TabGroup的tab tips）
        :return:                           用于Sg.window的页面layout list
        """
        self.max_rows = max_rows
        self.max_columns = max_columns
        self.datachangeeventtrigger = datachangeeventtrigger
        self.use_inner_right_click_menu = use_inner_right_click_menu
        self.enable_click_events = enable_click_events
        self.enable_events = enable_events
        self.dataimportsource = importsource
        if itemeditfunc is None:
            self.itemeditfunc = self.itemeditgeneral
            self.itemeditfuncargsdic = {}
            self.itemeditusecolvaluescale = False
        else:
            self.itemeditfunc = itemeditfunc
            self.itemeditfuncargsdic = itemeditfuncargsdic
            self.itemeditusecolvaluescale = itemeditusecolvaluescale

        # 优先使用 specialtable
        # 本地化记录Specialdf
        self.specialdf = specialdf
        if self.specialdf is not None:
            if headings is not None:
                self.specialdf.headings = headings
                # self.specialdf.tabledatadictlist = dfshowdata.to_dict(orient='records')
        # 输入数据是df，只有dfshowdata和headings有效，其他全部从df和headings推算
        elif dfshowdata is not None:
            headings = headings if headings is not None else list(dfshowdata)
        # 数据输入格式是字典list，不是dfdat，使用字典格式相关的参数以及字段说明字典
        elif tabledatadictlist is not None and tabledatadictlist != []:
            headings = headings if headings is not None else list(tabledatadictlist[0])
            # 如果有数据 , dict 的list理论上可以直接转换成df，但是有顺序问题，
            # dfshowdata = pd.DataFrame(self.tabledatadictlist)         # 不采用此方式，因为转换后数据顺序有问题
            dfshowdata = pd.DataFrame([list(item.values()) for item in tabledatadictlist],
                                      columns=list(tabledatadictlist[0]))
        else:  # specialdf , dfshowdata 和 tabledatadictlist 都为空，只是为了创建空表，此时headings必须有值，
            # 对dfshowdata使用空df
            dfshowdata = pd.DataFrame([], columns=headings)
        if specialdf is None:
            self.specialdf = SpecialDf(dfdata=dfshowdata, headings=headings)
        # 以上完成后，形成self.specialdf 及 self.specialdf.headings ，！！！
        # 在Specialdf初始化时已经执行，此处不应该重复执行, 除非dfcolumns未赋值
        if self.specialdf.dfcolumns is None:
            self.specialdf.dfcolumns = list(self.specialdf.dfdata)
            self.specialdf.columns = self.specialdf.headings
        # 每项的合法范围，df方式下如果没有输入就用df生成默认
        if visible_column_map is None:
            self.specialdf.visible_column_map = [True for _ in range(len(dfshowdata.columns))]
        else:
            self.specialdf.visible_column_map = visible_column_map
        # 对于不足最大列数的部分，进行补足
        self.specialdf.visible_column_map += [True for _ in range(len(self.specialdf.visible_column_map),
                                                                  self.max_columns)]
        # 从values获取对应的字典keys名，便于save时获取结果dict
        self.specialdf.indexcol = indexcol
        self.specialdf.col_value_scale = col_value_scale
        self.filter_visible = filter_visible
        self.filter_set_value_visible = filter_set_value_visible
        self.edit_button_visible = edit_button_visible
        self.move_row_button_visible = move_row_button_visible
        self.tablekey = tablekey
        self.scrollable = scrollable
        self.visible = visible
        self.def_col_width = def_col_width
        self.auto_size_columns = auto_size_columns
        self.max_col_width = max_col_width
        self.showcolnamewithid = showcolnamewithid
        self.showcolumnname = showcolumnname
        self.display_row_numbers = display_row_numbers
        if col_widths is None:
            self.col_widths = [cell_size[0] for _ in range(len(self.specialdf.headings))]
        else:
            collen = len(col_widths)
            headlen = len(self.specialdf.headings)
            if collen == headlen:
                self.col_widths = col_widths
            else:
                if collen > headlen:
                    self.col_widths = col_widths[:headlen]
                else:
                    self.col_widths = col_widths + [cell_size[0] for _ in range(headlen - collen)]

        self.vertical_scroll_only = vertical_scroll_only
        self._tablelayout = None
        self.expand_x = expand_x
        self.expand_y = expand_y
        self.border_width = border_width
        self.font = font
        self.pad = pad
        self.lineheight = FONT_SIZE_DICT[self.font[1]][1]
        tablelendiff = {
            # filter_visible, filter_set_value_visible, edit_button_visible,  move_row_button_visible,
            (True, True, True, True): (6.5 + 3.5) * self.lineheight,
            (True, True, True, False): (6.5 + 3.5) * self.lineheight,
            (True, True, False, True): (6.5 + 3.5) * self.lineheight,
            (True, True, False, False): (6.5 + 0) * self.lineheight,
            (True, False, True, True): (4 + 3.5) * self.lineheight,
            (True, False, True, False): (4 + 3.5) * self.lineheight,
            (True, False, False, True): (4 + 3.5) * self.lineheight,
            (True, False, False, False): (4 + 0) * self.lineheight,
            (False, True, True, True): (0 + 3.5) * self.lineheight,
            (False, True, True, False): (0 + 3.5) * self.lineheight,
            (False, True, False, False): (0 + 0) * self.lineheight,
            (False, False, True, True): (0 + 3.5) * self.lineheight,
            (False, False, True, False): (0 + 3.5) * self.lineheight,
            (False, False, False, True): (0 + 3.5) * self.lineheight,
            (False, False, False, False): 10,
        }
        self.size = size
        self.window_size = self.size[0], self.size[1] - int(tablelendiff[(
            self.filter_visible, self.filter_set_value_visible,
            self.edit_button_visible, self.move_row_button_visible)]),
        self.cell_size = cell_size
        self.relief = relief
        self.slider_relief = slider_relief
        self.slider_every_row_column = slider_every_row_column
        if self.slider_every_row_column:
            self.v_slider_resolution = 1 / self.max_rows
            self.h_slider_resolution = 1 / self.max_columns
        else:
            self.v_slider_resolution = v_slider_resolution
            self.h_slider_resolution = h_slider_resolution
        self.v_slider_show = v_slider_show
        self.h_slider_show = h_slider_show
        self.cell_justification = cell_justification
        self.show_cell_tooltips = show_cell_tooltips
        self.disable_slider_number_display = disable_slider_number_display
        self.lay_col_size = \
            (self.window_size[0] - (45 if self.disable_slider_number_display else 66),
             self.window_size[1] - (1 if self.disable_slider_number_display else 10))
        self.table_col_size = (self.lay_col_size[0] - 6, self.lay_col_size[1] - 20)
        self.datalay_size = (self.table_col_size[0] - 15, self.table_col_size[1] - 15)
        # H_SLIDER_SIZE = (int(DEF_CELL_SIZE[0] * MAX_SPECIAL_TABLE_COLUMNS * 1.2) - 6, 10)
        # V_SLIDER_SIZE = (int(DEF_CELL_SIZE[1] * (MAX_SPECIAL_TABLE_ROWS + 1) * 1.2), 10)
        # slider的size是为字符个数而不是pixel，所以需要根据font进行换算
        self.h_slider_size = \
            (int(self.lay_col_size[0] / FONT_SIZE_DICT[self.font[1]][0]), 10)
        self.v_slider_size = \
            (int(self.lay_col_size[1] / FONT_SIZE_DICT[self.font[1]][1] / 1.4), 10)
        self.specialdf.text_color = text_color
        self.specialdf.text_background_color = text_background_color
        self.specialdf.head_text_color = head_text_color
        self.specialdf.head_background_color = head_background_color
        self.specialdf.selected_text_color = selected_text_color
        self.specialdf.selected_background_color = selected_background_color
        self.specialdf.input_text_background_color = input_text_background_color
        self.specialdf.input_text_color = input_text_color
        self.specialdf.combox_button_background_color = combox_button_background_color
        self.specialdf.combox_button_arrow_color = combox_button_arrow_color
        self.specialdf.cell_editable = cell_editable
        self.specialdf.defaultcolor = (self.specialdf.input_text_color, self.specialdf.input_text_background_color) \
            if self.specialdf.cell_editable else (self.specialdf.text_color, self.specialdf.text_background_color)

        if column_cell_edit_type_map is None:
            self.specialdf.column_cell_edit_type_map = [SPECIAL_TABLE_CELL_INPUT
                                                        if self.specialdf.cell_editable else SPECIAL_TABLE_CELL_TEXT
                                                        for _ in range(len(self.specialdf.headings))]
        else:
            # 如果非编辑方式，原来的INPUT换成TEXT
            self.specialdf.column_cell_edit_type_map = [
                SPECIAL_TABLE_CELL_TEXT if not self.specialdf.cell_editable and x == SPECIAL_TABLE_CELL_INPUT else x
                for x in column_cell_edit_type_map]

        celltypedict = {
            SPECIAL_TABLE_CELL_COMBOX: SpecialCombox,
            SPECIAL_TABLE_CELL_INPUT: Sg.Input,
            SPECIAL_TABLE_CELL_TEXT: Sg.Text
        }
        # 形成针对各个列的element
        if self.specialdf.column_cell_edit_type_map is not None and self.specialdf.col_value_scale is not None:
            self.specialdf.column_edit_element = []
            for item, celltype in enumerate(self.specialdf.column_cell_edit_type_map):
                elementtype = celltypedict.get(celltype, None)
                if elementtype is SPECIAL_TABLE_CELL_COMBOX:
                    # combox模式下，要确定有多选的list且内容不能为空，否则根据cell_editable 确认input或者text
                    if self.specialdf.col_value_scale[item] is not None:
                        if len(self.specialdf.col_value_scale[item]) > 0:
                            self.specialdf.column_edit_element.append(elementtype)
                        else:
                            self.specialdf.column_edit_element.append(
                                Sg.Input if self.specialdf.cell_editable else Sg.Text)
                    else:
                        self.specialdf.column_edit_element.append(
                            Sg.Input if self.specialdf.cell_editable else Sg.Text)
                else:
                    self.specialdf.column_edit_element.append(elementtype)
        else:
            self.specialdf.column_edit_element = [Sg.Input if self.specialdf.cell_editable
                                                  else Sg.Text for _ in range(len(self.specialdf.headings))]
        # 对于不足显示最大列的部分，补足Sg.input
        for _ in range(len(self.specialdf.column_edit_element), self.max_columns):
            self.specialdf.column_edit_element.append(Sg.Input)

        # # 用于保存每行的文本和背景颜色，填写到行格式信息字典中
        self.buildrowstyleinfo()
        self.buildcolumnstyleinfo()

        # combox只显示可以编辑的列名
        self.select_mode = select_mode if select_mode is not None else SPECIAL_TABLE_SELECT_MODE_NONE
        if self.specialdf.col_value_scale is None:  # 全部列都可以修改
            for i in range(len(self.specialdf.headings)):
                # 排除不显示的列
                if self.specialdf.visible_column_map is not None:  # 当该项会在表格显示
                    if self.specialdf.visible_column_map[i]:
                        # 当该项会在表格显示
                        self.specialdf.colnameusedlist.append(self.specialdf.headings[i])
                        self.specialdf.colnamefilterlist.append(self.specialdf.headings[i])
                else:
                    self.specialdf.colnameusedlist = [""] + self.specialdf.headings
                    self.specialdf.colnamefilterlist = [""] + self.specialdf.headings
        else:
            for i in range(len(self.specialdf.headings)):
                # 排除不允许批量编辑的列和不显示的列
                if self.specialdf.visible_column_map is not None:  # 当该项会在表格显示
                    # 过滤列不用考虑是否可以编辑
                    self.specialdf.colnamefilterlist.append(self.specialdf.headings[i])
                    if self.specialdf.col_value_scale[i] is not None:  # 当该项允许被批量编辑
                        if self.specialdf.visible_column_map[i]:
                            self.specialdf.colnameusedlist.append(self.specialdf.headings[i])
        self.specialdf.currentrow = None
        self.tablelay = None
        # mousewheel事件是否已经绑定的标志
        self.mousewheelbind = False
        # 组成函数处理字典
        self.eventprocessdic = {}
        # 列批量复制相关elements的key及事件
        # 对于数据表格改变后发送给主窗口的触发事件，如果又回到本类，说明主窗口没有处理，本类也不做处理
        self.TABLE_CHANGE_EVENT_TRIGGER = (self.tablekey, SPECIAL_TABLE_CHANGE_EVENT_TRIGGER)
        self.eventprocessdic[self.TABLE_CHANGE_EVENT_TRIGGER] = self.donothing
        self.COLUMNFILTER = (self.tablekey, '_colunmfilter_')
        self.eventprocessdic[self.COLUMNFILTER] = self.donothing
        self.ROW_START = (self.tablekey, '_row_start_')
        self.eventprocessdic[self.ROW_START] = self.updaterowscale
        self.ROW_END = (self.tablekey, '_row_end_')
        self.eventprocessdic[self.ROW_END] = self.updaterowscale
        self.COL_BAT_VALUE = (self.tablekey, '_col_bat_value_')
        self.eventprocessdic[self.COL_BAT_VALUE] = self.donothing
        self.COL_BAT_OK = (self.tablekey, '_col_bat_ok_')
        self.eventprocessdic[self.COL_BAT_OK] = self.columnbatsetvalue
        # 表格附加按钮的相关事件及处理函数
        self.EDITROW = (self.tablekey, '_edit_')
        self.eventprocessdic[self.EDITROW] = self.editrow
        self.ADDROW = (self.tablekey, '_add_')
        self.eventprocessdic[self.ADDROW] = self.addrow
        self.DELETEROW = (self.tablekey, '_del_')
        self.eventprocessdic[self.DELETEROW] = self.deleterow
        self.CLEARALL = (self.tablekey, '_clear_')
        self.eventprocessdic[self.CLEARALL] = self.clearall
        self.MOVEROWUP = (self.tablekey, '_up_')
        self.eventprocessdic[self.MOVEROWUP] = self.moverowup
        self.MOVEROWDOWN = (self.tablekey, '_down_')
        self.eventprocessdic[self.MOVEROWDOWN] = self.moverowdown
        self.MOVEROWTOTOP = (self.tablekey, '_top_')
        self.eventprocessdic[self.MOVEROWTOTOP] = self.moverowtotop
        self.MOVEROWTOBOTTOM = (self.tablekey, '_bottom_')
        self.eventprocessdic[self.MOVEROWTOBOTTOM] = self.moverowtobottom
        self.MOVEROWTOCERTAIN = (self.tablekey, '_moveto_')
        self.eventprocessdic[self.MOVEROWTOCERTAIN] = self.moverowtocertain
        self.TARGETROWNUMBER = (self.tablekey, '_torownumber_')
        self.eventprocessdic[self.TARGETROWNUMBER] = self.moverowtocertain
        self.FILTERCONDITIONOK = (self.tablekey, '_fillterok_')
        self.eventprocessdic[self.FILTERCONDITIONOK] = self.setfilter
        self.FILTERCONDITIONCANCEL = (self.tablekey, '_filltercancel_')
        self.eventprocessdic[self.FILTERCONDITIONCANCEL] = self.filterclear
        self.FILTERFRAME = (self.tablekey, '_filterframe_')
        self.FILTERSETVALUEFRAME = (self.tablekey, '_filtersetvalueframe_')
        self.CONTENTLIKE = (self.tablekey, '_contentlike_')
        self.eventprocessdic[self.CONTENTLIKE] = self.donothing
        self.COLCHOOSE = (self.tablekey, '_col_choose_')
        self.eventprocessdic[self.COLCHOOSE] = self.colnameselected
        self.V_SLIDER_KEY = (self.tablekey, '_v_slider_')
        self.eventprocessdic[self.V_SLIDER_KEY] = self.sliderclicked
        self.H_SLIDER_KEY = (self.tablekey, '_h_slider_')
        self.eventprocessdic[self.H_SLIDER_KEY] = self.sliderclicked
        self.EDIT_ROW_FRAME = (self.tablekey, '_edit_row_frame_')
        self.MOVE_ROW_FRAME = (self.tablekey, '_move_row_frame_')
        self.getattrdict = {
            SPECIAL_TABLE_GET_MAXROWNUMBER: self.specialdf.getmaxrownumber,
            SPECIAL_TABLE_GET_MAXCOLUMNNUMBER: self.specialdf.getmaxcolnumber,
            SPECIAL_TABLE_GET_SPECIALEVENTLIST: self.getspecialeventlist,
            SPECIAL_TABLE_GET_CURRENTROW: self.getcurrentrow,
            SPECIAL_TABLE_GET_CURRENTROW_CONTENT: self.getcurrentrowcontent,
            SPECIAL_TABLE_GET_RESULT_DICT: self.getresultdic,
            SPECIAL_TABLE_GET_EVENTINFLUENCEROW: self.getcommandinfluncerow,
            SPECIAL_TABLE_GET_SELECTED_ROWCOLUMN: self.getselectedrowcolumn,
            SPECIAL_TABLE_GET_SELECTED_ROWCONTENT: self.getselectedrowcontent,
            SPECIAL_TABLE_GET_COLUMNN_NAME: self.getcolumnname
        }

        # 用于记录非表格单元的事件及右键菜单的字典
        self.specialeventdict = {
            self.EDITROW: " 编  辑 ",
            self.ADDROW: " 增  加 ",
            self.DELETEROW: " 删  除 ",
            self.CLEARALL: "全部清除",
            self.MOVEROWUP: "记录上移",
            self.MOVEROWDOWN: "记录下移",
            self.MOVEROWTOTOP: "移至表首",
            self.MOVEROWTOBOTTOM: "移至表尾",
            self.MOVEROWTOCERTAIN: "移至行号"  # 该项对于right_click_menu 将清除
        }

        # 需外部特殊处理的事件list, 记录非表格单元的事件
        self.specialeventlist = list(self.specialeventdict)
        # 定义内部inner_right_click_menu, 只加入主要的编辑按钮，不考虑移动到（因为需要定义目标行号）
        # 单个窗口多个相同的menu处理的底层思路
        # https://www.pysimplegui.org/en/latest/#right-click-menus
        #
        # Keys for Menus
        # Beginning in version 3.17 you can add a key to your menu entries.
        # The key value will be removed prior to be inserted into the menu.
        # When you receive Menu events, the entire menu entry, including the key is returned.
        # A key is indicated by adding :: after a menu entry, followed by the key.
        # To add the key _MY_KEY_ to the Special menu entry, the code would be:
        # ['&Edit', ['Paste', ['Special::_MY_KEY_', 'Normal',], 'Undo'],]
        # If you want to change the characters that indicate a key follows from '::' to something else,
        # change the variable MENU_KEY_SEPARATOR
        # 根据此特性，将SpecialTable的默认right_click_menu关联相关编辑Button，
        # 如：（"删除"-->("删除::" + str(self.DELETEROW))， window事件处理中，先拆分为元组事件，然后再返还给SpecialTable类
        # 相当于self.specialeventlist 去掉最后一个事件
        self.inner_right_click_menu = [["", [
                                                "{}{}{}".format(value, Sg.MENU_KEY_SEPARATOR, key) for key, value in
                                                self.specialeventdict.items()
                                            ][0: -1]]]
        # 用于判断鼠标进去的Frame
        self.TABLE_FRAME = (self.tablekey, '_table_frame_')
        self.MOUSE_IN = 'mouse in'
        self.MOUSE_LEAVE = 'mouse leave'
        self.MOUSE_WHEEL = 'mouse wheel'
        self.MOUSE_CLICK = 'mouse click'
        self.mousein = False
        # 用于记录所有的类事件
        self.eventlist = [a for a in self.eventprocessdic.keys()]
        # 追加二维显示表的所有事件
        self.tablecellkeylist = \
            [(self.tablekey, x, y) for y in range(-1, self.max_columns)
             for x in range(-2, self.max_rows)]
        self.eventlist += self.tablecellkeylist
        # 优先获取及使用外部定义的right_click_menu
        self.right_click_menu = right_click_menu
        # 当外部没有定义right_click_menu并且允许使用内部默认right_click_menu时，改写right_click_menu
        if self.use_inner_right_click_menu and self.right_click_menu is None:
            self.right_click_menu = self.inner_right_click_menu
        if isinstance(self.right_click_menu, list):
            menulistlen = len(self.right_click_menu)
            self.row_right_click_menu = self.right_click_menu[0] if menulistlen >= 1 else None
            self.column_right_click_menu = self.right_click_menu[1] if menulistlen >= 2 else self.row_right_click_menu
            self.tabledata_right_click_menu = self.right_click_menu[2] \
                if menulistlen >= 3 else self.row_right_click_menu
        else:
            self.row_right_click_menu, self.column_right_click_menu, self.tabledata_right_click_menu = None, None, None

        # self.v_slider_range = (int(self.specialdf.dfdata.shape[0] / self.max_rows) - 1 +
        #                        (1 if (self.specialdf.dfdata.shape[0] % self.max_rows) > 0 else 0), 0),
        # self.h_slider_range = (0, int(self.specialdf.dfdata.shape[1] / self.max_columns) - 1 +
        #                        (1 if (self.specialdf.dfdata.shape[1] % self.max_columns) > 0 else 0)),

        # 形成开始、结束行号的combolist
        rowcombolist = [""] + [str(a) for a in range(0, self.specialdf.dfdata.shape[0])]

        self._tablelayout = []
        # 先添加过滤选项
        if self.filter_visible:
            self._tablelayout.append(
                [
                    Sg.Col([[Sg.Frame(
                        "按列值筛选" if self.filter_set_value_visible else '按列值筛选:', [[
                            Sg.Col([[Sg.Frame("开始行号", [
                                [Sg.Combo(rowcombolist, readonly=True, k=self.ROW_START, enable_events=True,
                                          pad=(10, 3), size=(8, 1))]],
                                              border_width=0,
                                              title_location=Sg.TITLE_LOCATION_LEFT
                                              if not self.filter_set_value_visible else Sg.TITLE_LOCATION_TOP)]]),
                            Sg.Col([[Sg.Frame("结束行号", [
                                [Sg.Combo(rowcombolist, readonly=True, k=self.ROW_END, enable_events=True,
                                          pad=(10, 3), size=(8, 1))]],
                                              border_width=0,
                                              title_location=Sg.TITLE_LOCATION_LEFT
                                              if not self.filter_set_value_visible else Sg.TITLE_LOCATION_TOP)]]),
                            Sg.Col([[Sg.Frame("筛选列名", [
                                [Sg.Combo(self.specialdf.colnamefilterlist, readonly=True, k=self.COLUMNFILTER,
                                          enable_events=False, pad=(10, 3), size=(20, 1))]],
                                              border_width=0,
                                              title_location=Sg.TITLE_LOCATION_LEFT
                                              if not self.filter_set_value_visible else Sg.TITLE_LOCATION_TOP)]]),
                            Sg.Col([[Sg.Frame("包含内容", [
                                [Sg.Input("", readonly=False, k=self.CONTENTLIKE,
                                          enable_events=False, pad=(10, 3), size=(20, 1))]],
                                              border_width=0,
                                              title_location=Sg.TITLE_LOCATION_LEFT
                                              if not self.filter_set_value_visible else Sg.TITLE_LOCATION_TOP)]]),
                            Sg.Col([[
                                Sg.Button('筛选确认', k=self.FILTERCONDITIONOK, pad=(3, 1)),
                                Sg.Button('筛选取消', k=self.FILTERCONDITIONCANCEL, disabled=True, pad=(3, 1))]])
                        ]], pad=(10, 3), visible=self.filter_visible, k=self.FILTERFRAME,
                        border_width=None if self.filter_set_value_visible else 0,
                        # title_color='blue',
                        title_color='white' if self.filter_set_value_visible else 'blue',
                        title_location=Sg.TITLE_LOCATION_LEFT
                        if not self.filter_set_value_visible else Sg.TITLE_LOCATION_TOP)]], justification='c'),
                    Sg.Col([[Sg.Frame(
                        "列批量赋值", [[
                            Sg.Col([[Sg.Frame(
                                "赋值列名", [[Sg.Combo(self.specialdf.colnameusedlist, readonly=True, k=self.COLCHOOSE,
                                                   enable_events=True, pad=(10, 3), size=(20, 1))]],
                                border_width=0, title_location=Sg.TITLE_LOCATION_TOP)]]),
                            Sg.Col([[Sg.Frame(
                                "赋值内容",
                                [[Sg.Combo([], readonly=False, k=self.COL_BAT_VALUE, pad=(1, 1), size=(20, 1))]],
                                border_width=0, title_location=Sg.TITLE_LOCATION_TOP)]]),
                            Sg.Col([[Sg.Button(' 赋值 ', k=self.COL_BAT_OK, pad=(10, 3))]])
                        ]], pad=(10, 3), visible=self.filter_set_value_visible and self.filter_visible,
                        title_color='white', k=self.FILTERSETVALUEFRAME,
                        title_location=Sg.TITLE_LOCATION_TOP)]], justification='c')]),

        # 添加表格, 不同方式下加入的layout不同
        self._tablelayout.append(self.elementtablelay())
        # 用来产生tablekey的隐藏element
        layhiding = [Sg.Col([[SgKeyGenerate(self.update, self.get, "", k=self.tablekey, visible=False)]],
                            visible=False, justification='center', element_justification='center')]
        self._tablelayout.append(layhiding)
        # 添加编辑按钮
        if self.edit_button_visible or self.move_row_button_visible:
            self._tablelayout.append([
                Sg.Col([[
                    Sg.Frame("", [[
                        Sg.Button(self.specialeventdict[self.EDITROW], k=self.EDITROW, pad=(1, 1)),
                        Sg.Button(self.specialeventdict[self.ADDROW], k=self.ADDROW, pad=(1, 1)),
                        Sg.Button(self.specialeventdict[self.DELETEROW], k=self.DELETEROW, pad=(1, 1)),
                        Sg.Button(self.specialeventdict[self.CLEARALL], k=self.CLEARALL, pad=(1, 1)),
                    ]], pad=(1, 1), visible=self.edit_button_visible, k=self.EDIT_ROW_FRAME),
                    Sg.Frame("", [[
                        Sg.Button(self.specialeventdict[self.MOVEROWUP], k=self.MOVEROWUP, pad=(1, 1)),
                        Sg.Button(self.specialeventdict[self.MOVEROWDOWN], k=self.MOVEROWDOWN, pad=(1, 1)),
                        Sg.Button(self.specialeventdict[self.MOVEROWTOTOP], k=self.MOVEROWTOTOP, pad=(1, 1)),
                        Sg.Button(self.specialeventdict[self.MOVEROWTOBOTTOM], k=self.MOVEROWTOBOTTOM, pad=(1, 1)),
                        Sg.Button(self.specialeventdict[self.MOVEROWTOCERTAIN], k=self.MOVEROWTOCERTAIN, pad=(1, 1)),
                        Sg.Input('0', size=(5, 0), justification='r', k=self.TARGETROWNUMBER, pad=(1, 1))
                    ]], pad=(1, 1), visible=self.move_row_button_visible, k=self.MOVE_ROW_FRAME)]],
                    justification='center', visible=self.edit_button_visible or self.move_row_button_visible)
            ])

    def usedfdatainit(self, dfshowdata):
        # 只是用dfdata初始化，无法知道取值范围
        self.specialdf.col_value_scale = None
        # 过滤用的变量
        self.specialdf.colnameusedlist = [""] + self.specialdf.headings
        self.specialdf.colnamefilterlist = [""] + self.specialdf.headings
        self.specialdf.infilterstatus = False

    def elementtablelay(self):  # 返回element 拼接table的layout
        # 初始化所有的layout
        # 获取显示区域的数据(超出df范围的区域不会返回dfresult, 利用此特性不用单独计算当前是否到最后页)
        dfresult = self.specialdf.dfdata.iloc[
                   self.specialdf.showstartpoint[0]: self.specialdf.showstartpoint[0] + self.max_rows,
                   self.specialdf.showstartpoint[1]: self.specialdf.showstartpoint[1] + self.max_columns
                   ]
        # 生成空白的完整填充表
        dfnull = pd.DataFrame(["" for _ in range(self.max_columns)] for _ in range(self.max_rows))

        # 形成完整的显示数据（有数据部分会覆盖"", 无数据部分依然是""）
        dfnull.iloc[0:dfresult.shape[0], 0:dfresult.shape[1]] = dfresult
        # 为 使用elements方式准备
        tempcolwidths = [self.cell_size[0]] + self.col_widths
        # 列序号行，根据需要再拼接
        laycolumnnumber = [[
            Sg.T(
                "[{}]".format(s) if i > 0 and s != "" else "列序号" if i == 0 else "",
                tooltip="[{}]".format(s) if self.show_cell_tooltips else None,
                k=(self.tablekey, -2, i - 1),  # headings 加上了序号列，左上角从 （-2， -1#）开始
                justification='c',
                enable_events=True,
                # 序号列-1根据是否显示序号判断
                visible=(self.specialdf.visible_column_map[i - 1] and self.showcolnamewithid)
                if i > 0 else (self.display_row_numbers and self.showcolnamewithid),
                font=self.font,
                border_width=1 if self.border_width == 0 else self.border_width,
                relief=Sg.RELIEF_RIDGE,
                background_color=self.specialdf.head_background_color,
                text_color=self.specialdf.head_text_color,
                # size=self.cell_size,
                size=(tempcolwidths[i], 1) if i < len(tempcolwidths) else (self.cell_size[0]),
                right_click_menu=self.column_right_click_menu,
                # expand_x=False,
                # expand_y=False,
                pad=self.pad) for i, s in enumerate(
                (['列序号'] + [str(x) for x in range(len(self.specialdf.headings))] +
                 ['' for _ in range(len(self.specialdf.headings) + 1, self.max_columns + 1)])[:self.max_columns + 1])
        ]]
        layheading = [[
            Sg.T(
                s,
                tooltip=("[{}]".format(i - 1) if i > 0 else "") + str(s) if self.show_cell_tooltips else None,
                k=(self.tablekey, -1, i - 1),  # headings 加上了序号列，左上角从 （-1， -1#）开始
                justification='c',
                enable_events=True,
                # 序号列-1根据是否显示序号判断
                visible=(self.specialdf.visible_column_map[i - 1] and self.showcolumnname)
                if i > 0 else (self.display_row_numbers and self.showcolumnname),
                font=self.font,
                border_width=1 if self.border_width == 0 else self.border_width,
                relief=Sg.RELIEF_RIDGE,
                background_color=self.specialdf.head_background_color,
                text_color=self.specialdf.head_text_color,
                # size=self.cell_size,
                size=(tempcolwidths[i], 1) if i < len(tempcolwidths) else (self.cell_size[0]),
                right_click_menu=self.column_right_click_menu,
                # expand_x=False,
                # expand_y=False,
                pad=self.pad) for i, s in enumerate(
                (['列  名'] + self.specialdf.headings +
                 ['' for _ in range(len(self.specialdf.headings) + 1, self.max_columns + 1)])[:self.max_columns + 1])
        ]]

        laydata = []
        for r in range(self.max_rows):
            # 行序号列
            temprownumber = [Sg.T(
                r,
                tooltip=r if self.show_cell_tooltips else None,
                k=(self.tablekey, r, -1),
                # size=self.cell_size,
                size=(tempcolwidths[0], 1),
                pad=self.pad,
                font=self.font,
                border_width=1 if self.border_width == 0 else self.border_width,
                relief=Sg.RELIEF_RIDGE,
                enable_events=True,
                justification='center',
                background_color=self.specialdf.head_background_color,
                expand_x=False,
                expand_y=False,
                right_click_menu=self.row_right_click_menu,
                visible=self.display_row_numbers,
                text_color=self.specialdf.head_text_color)]
            temprowdata = []
            for c in range(self.max_columns):
                # 对于combox、text、input类型，第一个输入参数不同，另外combox需要额外的default_value参数，
                if self.specialdf.column_edit_element[c] == SpecialCombox:
                    firstparam = self.specialdf.col_value_scale[c]
                    kwargsdict = {
                        "default_value": dfnull.iloc[self.specialdf.showstartpoint[0] + r, 
                                                     self.specialdf.showstartpoint[1] + c],
                        # "disabled": not self.cell_editable,
                        # "readonly": not self.cell_editable,
                        "button_arrow_color": self.specialdf.combox_button_arrow_color,
                        "button_background_color": self.specialdf.combox_button_background_color,
                        "size": (tempcolwidths[c + 1] - 2, 1)
                        if c + 1 < len(tempcolwidths) else (self.cell_size[0] - 2, self.cell_size[1])
                    }
                else:
                    # Input， text共用部分
                    firstparam = "{}".format(
                        dfnull.iloc[self.specialdf.showstartpoint[0] + r, self.specialdf.showstartpoint[1] + c])
                    kwargsdict = {
                        "justification": self.cell_justification,
                        "right_click_menu": self.tabledata_right_click_menu,
                        "border_width": self.border_width,
                        "size": (tempcolwidths[c + 1], 1) if c + 1 < len(tempcolwidths) else self.cell_size
                    }

                # input 可以设置disable,readonly， use_readonly_for_disable, Text没有该选项。
                # if self.specialdf.column_edit_element[c] == Sg.Input:
                #     kwargsdict["disabled"] = not self.cell_editable
                #     kwargsdict["readonly"] = not self.cell_editable
                #     kwargsdict["use_readonly_for_disable"] = True
                #     kwargsdict["disabled_readonly_text_color"] = \
                #         self.input_text_color if self.cell_editable else self.text_color,

                temprowdata += [self.specialdf.column_edit_element[c](
                    firstparam,
                    # enable_events=self.cell_editable or self.enable_events,
                    enable_events=True,
                    visible=self.specialdf.visible_column_map[c],
                    tooltip=
                    dfnull.iloc[self.specialdf.showstartpoint[0] + r, self.specialdf.showstartpoint[1] + c]
                    if self.show_cell_tooltips else None,
                    text_color=self.specialdf.input_text_color if self.specialdf.cell_editable 
                    else self.specialdf.text_color,
                    background_color=self.specialdf.input_text_background_color
                    if self.specialdf.cell_editable else self.specialdf.text_background_color,
                    font=self.font,
                    k=(self.tablekey, r, c),
                    expand_x=False, expand_y=False,
                    pad=self.pad,
                    **kwargsdict
                )]
            # 组织一行的数据，给laydata1添加一行
            laydata.append(temprownumber + temprowdata)

        dftablelay = [Sg.Col(
            [[Sg.Frame("", laycolumnnumber + layheading + laydata,
                       pad=(0, 0),
                       k=self.TABLE_FRAME)]],
            visible=True, vertical_alignment='top', pad=(0, 0),
            expand_x=False,
            right_click_menu=self.tabledata_right_click_menu,
            # expand_y=True,
            scrollable=self.scrollable, size=self.table_col_size, justification='center')]

        # hr = (0, int(self.specialdf.dfdata.shape[1] / self.max_columns) - 1 +
        #                                  (1 if (self.specialdf.dfdata.shape[1] % self.max_columns) > 0 else 0))
        self.h_slider_range = (0, int(self.specialdf.dfdata.shape[1] / self.max_columns) - 1 +
                               (1 if (self.specialdf.dfdata.shape[1] % self.max_columns) > 0 else 0))

        # vr = (int(self.specialdf.dfdata.shape[0] / self.max_rows) - 1 +
        #                                  (1 if (self.specialdf.dfdata.shape[0] % self.max_rows) > 0 else 0), 0)
        self.v_slider_range = (int(self.specialdf.dfdata.shape[0] / self.max_rows) - 1 +
                               (1 if (self.specialdf.dfdata.shape[0] % self.max_rows) > 0 else 0), 0)
        totallay = [
            Sg.Col(
                [
                    dftablelay,
                    [
                        Sg.Slider(orientation='h',
                                  k=self.H_SLIDER_KEY,
                                  resolution=self.h_slider_resolution,
                                  range=self.h_slider_range,
                                  border_width=2,
                                  relief=self.slider_relief,
                                  disable_number_display=self.disable_slider_number_display,
                                  size=self.h_slider_size,
                                  tooltip="左右翻页",
                                  visible=self.h_slider_show,
                                  enable_events=True,
                                  pad=(0, 0),
                                  # expand_x=True
                                  )
                    ]
                ],
                visible=True,
                # vertical_alignment='bottom',
                pad=(0, 0),
                expand_x=False,
                # expand_y=True,
                # scrollable=False,
                size=self.lay_col_size,
                justification='center'
            ),
            Sg.Col(
                [
                    [
                        Sg.Slider(orientation='v',
                                  border_width=2,
                                  # relief=Sg.RELIEF_FLAT,
                                  enable_events=True,
                                  default_value=0,
                                  k=self.V_SLIDER_KEY,
                                  resolution=self.v_slider_resolution,
                                  relief=self.slider_relief,
                                  range=self.v_slider_range,
                                  size=self.v_slider_size,
                                  pad=(0, 0),
                                  visible=self.v_slider_show,
                                  tooltip="上下翻页",
                                  disable_number_display=self.disable_slider_number_display,
                                  expand_x=True
                                  )
                    ]
                ], pad=(0, 0), size=self.lay_col_size, expand_x=False, justification='left'
            )
        ]

        return [Sg.Col([totallay], justification='center', element_justification='center')]

    def recoverspecialdf(self, newspecialdf: SpecialDf = None):
        if newspecialdf is None:
            return
        self.specialdf = newspecialdf

    @property
    def layout(self):
        # 如果_tablelayout已经创建（调用过),直接返回该值
        if self._tablelayout is not None:
            return self._tablelayout

    def eventprocess(self, eventstr: tuple, window, values):
        """
        事件处理函数，从主windows送达的事件以及windows的values 清单
        :param eventstr:                事件
        :param window:                  主window
        :param values:                  windwos的 values
        :return:
        """
        # TABLE 被点击后，会连续输出两个事件，一个是元组，格式如下，包含了点击的（行号，列号），行号-1表示点击的是heading, -2点击列序号
        # TABLE CLICKED Event has value in format ('-TABLE=', '+CLICKED+', (row,col))
        # 第二个事件是表格本身key事件
        # 表格点击事件
        if isinstance(eventstr, tuple):
            # print("Special Table event :{}".format(eventstr))
            # 首先查看bind是否已经完成，没有则做一次
            if not self.mousewheelbind:
                self.setbind(window)
                self.mousewheelbind = True
                print("{}, binding mouse event.".format(eventstr))
            # bind 事件， 查找是否鼠标滚轮事件
            if eventstr[0][0] == self.tablekey and eventstr[1] == self.MOUSE_WHEEL:
                # userbindevent = window.find_element(eventstr[0]).user_bind_event
                userbindevent = window[eventstr[0]].user_bind_event
                # print("mousewheel event :{}".format(userbindevent))
                if userbindevent.delta > 0:
                    mouseevent = MOUSEWHEEL_UP
                    # 获取滚轮滚动的格子数
                    mousetimes = int(userbindevent.delta / 120)
                elif userbindevent.delta < 0:
                    mouseevent = MOUSEWHEEL_DOWN
                    # 获取滚轮滚动的格子数
                    mousetimes = int((-userbindevent.delta) / 120)
                else:
                    print("unknow mousewheel event :{}".format(userbindevent))
                    return
                for _ in range(mousetimes):
                    self.mousewheelupdown(currentwindow=window, currentvalues=values, mousewheelupdown=mouseevent)
                return

        if len(eventstr) == 3 and eventstr[0] == self.tablekey:
            self.resetselected(eventstr, window, values)

        elif eventstr == self.MOVEROWTOCERTAIN:
            # 最后事件需要指定移动到的行数，其他不需要参数
            # 获取目标行序号
            torownumber = values[self.TARGETROWNUMBER]
            self.moverowtocertain(eventstr, window, values, torownumber)

        elif eventstr == self.COL_BAT_OK:
            # 列批量赋值
            self.columnbatsetvalue(window, values[self.COLCHOOSE], values[self.COL_BAT_VALUE])

        elif eventstr == self.FILTERCONDITIONOK:
            # 设置过滤值
            templikestr = str(values[self.CONTENTLIKE]).strip()
            columnname = str(values[self.COLUMNFILTER]).strip()
            startrow = str(values[self.ROW_START]).strip()
            endrow = str(values[self.ROW_END]).strip()
            self.setfilter(currentwindow=window, columnname=columnname, startrow=startrow,
                           endrow=endrow, like=templikestr)
        else:
            self.eventprocessdic[eventstr](eventstr, window, values)
        return

    def addrow(self, currentevent, currentwindow, currentvalues, newrowcontent=None):
        if self.itemeditfunc is None:
            return False
        if self.specialdf.indexcol is not None:
            # 需要判断索引项下是否有重复值 如果需要，把当前编辑方式、列序号、列内容传递给行编辑页面
            self.itemeditfuncargsdic["method"] = ADD_METHOD
            self.itemeditfuncargsdic["current_row"] = self.specialdf.currentrow
            # indexcol输入时没有考虑行序号，在父类中加入了序号
            self.itemeditfuncargsdic["index_col_value"] = \
                self.specialdf.dfdata.iloc[:, self.specialdf.indexcol].values.tolist() \
                    if self.specialdf.dfdata.shape[0] > 0 else []
        if self.itemeditusecolvaluescale is True:
            self.itemeditfuncargsdic["col_value_scale"] = self.specialdf.col_value_scale
        # 首先判断是否外部调用addrow而不是类内部的增加按钮
        if self.specialdf.currentrow is not None and newrowcontent in [None, []]:
            # 行记录编辑函数的第一个参数固定是menuitem，其他可变
            # indexcol输入时没有考虑行序号，在父类中加入了序号, 需要去掉
            temprowcontent = self.specialdf.dfdata.iloc[self.specialdf.currentrow, :]
        elif newrowcontent not in [None, []]:
            # 判断行内容是否合法
            if len(newrowcontent) != len(self.specialdf.headings) and self.specialdf.currentrow is None:
                print("添加的行内容{}列数{}和需要列数{}不符！".format(
                    newrowcontent, len(newrowcontent), len(self.specialdf.headings)))
                return False
            temprowcontent = newrowcontent
        else:
            # 行记录编辑函数的第一个参数固定是menuitem，其他可变
            # 传递["","".....]改为传递series
            # temprowcontent = ['' for _ in range(len(self.columninfodic))]
            temprowcontent = pd.Series(data=['' for _ in range(len(self.specialdf.headings))],
                                       index=self.specialdf.headings)
        ret = self.itemeditfunc(
            temprowcontent,
            **self.itemeditfuncargsdic)
        if ret is not None:
            # 修改后台缓冲记录，增加该行内容
            self.specialdf.dfaddrow(ret)
            # # 刷新显示, 设置数据改变标志
            self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)
            return True
        else:
            return False

    def deleterow(self, currentevent, currentwindow, currentvalues):
        if self.specialdf.currentrow is not None:
            # 弹出确认窗口
            poprow = self.specialdf.currentrow
            if Sg.popup_ok_cancel("删除记录项:{}\n{}".format(
                    self.specialdf.currentrow, self.specialdf.dfdata.iloc[self.specialdf.currentrow, :]),
                    keep_on_top=True, modal=True, icon=SYSTEM_ICON) == "OK":
                self.specialdf.dfdeleterow()
                # # 刷新显示, 设置数据改变标志
                self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)
        else:  # 没有选择行
            Sg.popup_ok("当前没有选择唯一记录项！", keep_on_top=True, modal=True, icon=SYSTEM_ICON)

    def clearall(self, currentevent, currentwindow, currentvalues):
        if Sg.popup_ok_cancel("确定清除所有记录？？？！！！",
                              keep_on_top=True, modal=True, icon=SYSTEM_ICON) == "OK":
            # 从表尾开始删除，避免currentrow改变
            for row in range(self.specialdf.maxrownumber - 1, -1, -1):
                self.specialdf.setcurrentrow(row)
                self.specialdf.dfdeleterow()
            self.specialdf.currentrow = None
            # # 刷新显示, 设置数据改变标志
            self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)

    def editrow(self, currentevent, currentwindow, currentvalues):
        # 判断是否选择了记录
        if self.itemeditfunc is None:
            return
        if self.specialdf.currentrow is not None:
            # 判断是否需要根据索引列保证数据不重复
            if self.specialdf.indexcol is not None:
                # 需要判断索引项下是否有重复值，如果需要，把当前编辑方式、列序号、列内容传递给行编辑页面，必须先判断是否选择了当前编辑行
                self.itemeditfuncargsdic["method"] = EDIT_METHOD
                self.itemeditfuncargsdic["current_row"] = self.specialdf.currentrow
                # indexcol输入时没有考虑行序号，在父类中加入了序号
                self.itemeditfuncargsdic["index_col_value"] = \
                    self.specialdf.dfdata.iloc[:, self.specialdf.indexcol].values.tolist() if self.specialdf.dfdata.shape[0] > 0 else []
            if self.itemeditusecolvaluescale is True:
                self.itemeditfuncargsdic["col_value_scale"] = self.specialdf.col_value_scale
            # 调用编辑子窗口，行记录编辑函数的第一个参数固定是menuitem，其他可变
            ret = self.itemeditfunc(
                # indexcol输入时没有考虑行序号，在父类中加入了序号
                self.specialdf.dfdata.iloc[self.specialdf.currentrow, :],
                **self.itemeditfuncargsdic)
            if ret is not None:
                # 修改后台缓冲记录
                self.specialdf.dfsetrowvalue(ret)
                # # 刷新显示, 设置数据改变标志
                self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)
        else:
            # 没有选择行
            Sg.popup_ok("当前没有选择唯一记录项！", keep_on_top=True, modal=True, icon=SYSTEM_ICON)

    def setrowvalue(self, currentwindow, rownumber, rowcontent):
        """
        外部调用update设置指定行的值
        :param currentwindow:
        :param rownumber:
        :param rowcontent:
        :return:
        """
        # 临时获取当前row，
        tempcurrentrow = self.getcurrentrow()
        # 将当前row设置为需要设置值的row
        self.specialdf.setcurrentrow(rownumber)
        # 设置row值
        self.specialdf.dfsetrowvalue(rowcontent)
        # 恢复当前row
        self.specialdf.setcurrentrow(tempcurrentrow)
        # # 刷新显示, 设置数据改变标志
        self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)

    def setcellvalue(self, currentwindow, location: tuple, cellvalue: str):
        """
        设置单元格的值
        :param currentwindow:       表格所在的窗口
        :param location:      元组 ， （x, y） 赋值的单元格位置
        :param cellvalue:     值
        :return:
        """
        self.specialdf.dfsetcellvalue(location, cellvalue)
        # 对于在显示范围内的row进行页面刷新
        # 获取页面显示的序号
        row = location[0]
        showrowstr = [currentwindow[(self.tablekey, x, -1)].get() for x in range(self.max_rows)]
        showrowlist = [int(x) if x != "" else "" for x in showrowstr]
        # 如果当前赋值行在显示窗内，做相应设置
        if row in showrowlist:
            for column in range(self.max_columns):
                currentwindow[(self.tablekey, showrowlist.index(row), location[1])].update(value=cellvalue)

        # 如果设置了数据修改事件，触发该事件
        if self.datachangeeventtrigger:
            currentwindow.write_event_value((self.tablekey, SPECIAL_TABLE_CHANGE_EVENT_TRIGGER), None)

    def moverowup(self, currentevent, currentwindow, currentvalues):
        # 判断是否选择了记录
        if self.specialdf.currentrow is not None:
            # 判断当前行是否第一行
            if self.specialdf.currentrow == 0:
                Sg.popup_quick_message("当前行{}已是第一行，无法上移！".format(self.specialdf.currentrow),
                                       modal=True, keep_on_top=True,
                                       auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                       text_color='white', background_color='dark blue')
                return
            # 调用父类移动行方法
            self.specialdf.dfmoverow(self.specialdf.currentrow - 1)
            # 刷新显示
            # # 刷新显示, 设置数据改变标志
            self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)
        else:
            # 没有选择行
            Sg.popup_ok("当前没有选择唯一记录项！", keep_on_top=True, modal=True, icon=SYSTEM_ICON)

    def moverowdown(self, currentevent, currentwindow, currentvalues):
        # 判断是否选择了记录
        if self.specialdf.currentrow is not None:
            # 判断当前行是否最后一行
            if self.specialdf.currentrow == (self.specialdf.maxrownumber - 1):
                Sg.popup_quick_message("当前行{}已是最后一行，无法下移！".format(self.specialdf.currentrow),
                                       auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                       modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
                return
            # 调用父类移动行方法
            self.specialdf.dfmoverow(self.specialdf.currentrow + 1)
            # # 刷新显示, 设置数据改变标志
            self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)
        else:
            # 没有选择行
            Sg.popup_ok("当前没有选择唯一记录项！",
                        keep_on_top=True, modal=True, icon=SYSTEM_ICON)

    def moverowtotop(self, currentevent, currentwindow, currentvalues):
        # 判断是否选择了记录
        if self.specialdf.currentrow is not None:
            # 判断当前行是否第一行
            if self.specialdf.currentrow == 0:
                Sg.popup_quick_message("当前行{}已是第一行，无法上移！".format(self.specialdf.currentrow),
                                       auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                       modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
                return
            # 调用父类移动行方法
            self.specialdf.dfmoverow(0)
            # # 刷新显示, 设置数据改变标志
            self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)
        else:
            # 没有选择行
            Sg.popup_ok("当前没有选择唯一记录项！", keep_on_top=True, modal=True, icon=SYSTEM_ICON)

    def moverowtobottom(self, currentevent, currentwindow, currentvalues):
        # 判断是否选择了记录
        if self.specialdf.currentrow is not None:
            # 判断当前行是否最后一行
            if self.specialdf.currentrow == (self.specialdf.maxrownumber - 1):
                Sg.popup_quick_message("当前行{}已是最后一行，无法下移！".format(self.specialdf.currentrow),
                                       auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                       modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
                return
            # 调用父类移动行方法
            self.specialdf.dfmoverow(self.specialdf.maxrownumber - 1)  # 需要当前df的最大行数
            # # 刷新显示, 设置数据改变标志
            self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)
        else:
            # 没有选择行
            Sg.popup_ok("当前没有选择唯一记录项！",
                        keep_on_top=True, modal=True, icon=SYSTEM_ICON)

    def moverowtocertain(self, currentevent, currentwindow, currentvalues, torownumber: str):
        # 获取目标行号
        try:
            targetrownumber = int(torownumber)
        except Exception as err:
            Sg.popup_quick_message("目标行{}不正确:{}！".format(torownumber, err),
                                   auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                   modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
            return
        # 判断是否超出范围
        if targetrownumber < 0 or targetrownumber >= self.specialdf.maxrownumber:
            Sg.popup_quick_message("目标行{}超出范围！".format(torownumber),
                                   auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                   modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
            return
        # 判断是否选择了记录
        if self.specialdf.currentrow is not None:
            # 判断当前行是否目标行
            if self.specialdf.currentrow == targetrownumber:
                Sg.popup_quick_message("当前行{}已经是目标行{}！".format(self.specialdf.currentrow, targetrownumber),
                                       auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                       modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
                return
            # 调用父类移动行方法
            self.specialdf.dfmoverow(targetrownumber)
            # # 刷新显示, 设置数据改变标志
            self.resetshow(currentwindow=currentwindow, dffilterchoose=False, datachanged=True)
        else:
            # 没有选择行
            Sg.popup_ok("当前没有选择唯一记录项！", modal=True, keep_on_top=True, icon=SYSTEM_ICON)

    def buildrowstyleinfo(self):
        self.specialdf.dfbuildrowstyleinfo()

    def buildcolumnstyleinfo(self):
        self.specialdf.dfbuildcolumnstyleinfo()

    def setrowcolor(self, tablekey, currentwindow, row: int, color: tuple):
        """
        设置行的text，background颜色
        :param tablekey:            操作的表格key
        :param currentwindow:       表格所在的窗口
        :param row:                 行号,绝对行号
        :param color:               元组 ， （文本颜色， 背景颜色）
        :return:
        """
        # 更新保存的颜色
        self.specialdf.rowstyleinfo[row][ROWSTYLE_ROWCOLOR] = color
        # 对于在显示范围内的row进行页面刷新
        # 获取页面显示的序号
        showrowstr = [currentwindow[(self.tablekey, x, -1)].get() for x in range(self.max_rows)]
        showrowlist = [int(x) if x != "" else "" for x in showrowstr]
        # 当操作行在当前显示页面时，进行相应操作
        if row in showrowlist:
            for column in range(self.max_columns):
                currentwindow[(tablekey, showrowlist.index(row), column)].update(
                    text_color=color[0], background_color=color[1])

    def recoverrowcolor(self, tablekey, currentwindow, row: int):
        """
        恢复行的text，background原颜色
        :param tablekey:            操作的表格key
        :param currentwindow:       表格所在的窗口
        :param row:                 行号，页面显示
        :return:
        """
        # 当前相对行号转变为绝对行号
        rawrowstr = currentwindow[(self.tablekey, row, -1)].get()
        if rawrowstr == '':
            return
        rawrow = int(rawrowstr)
        # 更新保存的颜色
        color = self.specialdf.getrowcolor(rawrow)
        # 对于在显示范围内的row进行页面刷新
        for column in range(self.max_columns):
            currentwindow[(tablekey, row, column)].update(text_color=color[0], background_color=color[1])

    def recovercolumncolor(self, tablekey, currentwindow, column: int):
        """
        恢复行的text，background原颜色
        :param tablekey:            操作的表格key
        :param currentwindow:       表格所在的窗口
        :param column:              页面相对列号
        :return:
        """
        # 获取当前页面所有行
        # 获取页面显示的序号
        showrowstr = [currentwindow[(self.tablekey, x, -1)].get() for x in range(self.max_rows)]
        # 获取每行对应的颜色， 当页面有行无数据时，列选择会造成越界错误。不足showrowlist 不足max_rows部分利用"" 返回默认color
        currentcolumncolor = list(map(self.specialdf.getrowcolor, 
                                      [ast.literal_eval(x) if x != "" else "" for x in showrowstr]))

        # 填写该列每行的颜色
        # 3.2清理可能在当前页反显的列
        for x in range(self.max_rows):
            currentwindow[(tablekey, x, column)].update(text_color=currentcolumncolor[x][0],
                                                        background_color=currentcolumncolor[x][1])

    def setcolumnname(self, windows, column: list, columnname: list) -> bool:
        """
        设置列名称
        :param windows:       表格所在的窗口
        :param column:                  list 用于设置的列序号列表
        :param columnname:              list 用于设置的列名称列表，
        :return:                        bool: True 成功， False 失败
        """
        # 检查输入合法性
        if len(column) != len(columnname):
            print("设置列名指定列{}与列名{}数目不符".format(column, columnname))
            return False

        for x in column:
            if x >= len(self.specialdf.headings):
                print("指定列{}与超出现有列数{}范围".format(x, len(self.specialdf.headings)))
                return False

        # 修改原始数据
        self.specialdf.dfsetcolumnname(column, columnname)
        # 逐项设置列名称
        for i, j in enumerate(column):
            windows[(self.tablekey, -1, j)].update(value=columnname[i])
        return True

    def judgesetcurrentrow(self):
        # 选中项判断完成后，重新设置currentrow，给单选中移动及编辑、删除使用
        # 当前已选所有行是否是1， 如果是1， 则currentrow设置，否则清空
        if len(self.specialdf.selectedrawrow) != 1:
            self.specialdf.setcurrentrow(None)
        else:
            self.specialdf.setcurrentrow([x for x in self.specialdf.selectedrawrow][0])

    def getresultdic(self):
        # 回传整个表格
        # 回传需要修改dfdata.columns为columndict的字典名字, 转换为二维listdict后，再修改回来
        self.specialdf.dfdata.columns = self.specialdf.dfcolumns
        resultdictlist = self.specialdf.dfdata.to_dict(orient='records')
        self.specialdf.dfdata.columns = self.specialdf.headings
        return resultdictlist

    def getresultdf(self):
        return self.specialdf.dfdata

    def geteventlist(self):
        return self.eventlist

    def colnameselected(self, currentevent, currentwindow, currentvalues):
        # 如果当前列的col_value_scale 为None, 将赋值框清空改变为可以任意填写值
        colindex = self.specialdf.headings.index(currentvalues[self.COLCHOOSE])
        if self.specialdf.col_value_scale is not None:
            if len(self.specialdf.col_value_scale[colindex]) != 0:  # None列已经在形成combo时排除,为0表示任意值
                # 如果该列取值有范围，将范围挂接到赋值框，并且把赋值框变成只读
                currentwindow[self.COL_BAT_VALUE].update(values=self.specialdf.col_value_scale[colindex],
                                                         value=self.specialdf.col_value_scale[colindex][0],
                                                         readonly=True)
        else:
            # 如果取值范围是None，关闭只读，清空combx values及values
            currentwindow[self.COL_BAT_VALUE].update(value="", values=[], readonly=False)
        # 更新row的范围信息
        self.updaterowscale(currentevent, currentwindow, currentvalues)
        return

    def updaterowscale(self, currentevent, currentwindow, currentvalues):
        """
        根据最新记录数，更新开始、结束行号的combo
        :param currentevent:        event
        :param currentwindow:      主window
        :param currentvalues:      和主window相关的values
        :return:
        """
        rowcount = self.getrowcount()
        rowspinlist = [""] + [str(a) for a in range(0, rowcount)]
        # 更新开始行号
        value = currentvalues[self.ROW_START]
        currentwindow[self.ROW_START].update(values=rowspinlist, value=value)
        # 更新结束行号
        value = currentvalues[self.ROW_END]
        currentwindow[self.ROW_END].update(values=rowspinlist, value=value)
        return

    def columnbatsetvalue(self, currentwindow, columnname, colvalue: str):
        if columnname.strip() == "":
            Sg.popup_quick_message("请选择赋值的列！", auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                   modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
            return

        # 修改数据前，先再次确认
        if (Sg.popup_ok_cancel("提示", "将筛选结果中列<{}>赋值为：{}，请再次确认！".format(columnname, colvalue),
                               modal=True, keep_on_top=True, icon=SYSTEM_ICON) == "OK"):
            # 更新窗口
            self.specialdf.dfbatsetcolumnvalue(columnname=columnname, value=colvalue)
            self.resetshow(currentwindow=currentwindow, dffilterchoose=False)
        return

    def setfilter(self, currentwindow=None, columnname=None, startrow=None, endrow=None, like=None):
        # 判断列名是否合法
        tempcolumnname = None if columnname.strip() == "" else columnname.strip()
        tempstartrow = None if startrow.strip() == "" else startrow.strip()
        tempendrow = None if endrow.strip() == "" else endrow.strip()
        templike = None if like.strip() == "" else like.strip()

        if [tempcolumnname, tempstartrow, tempendrow, templike] == [None, None, None, None]:
            Sg.popup_quick_message("没有设置任何筛选条件！",
                                   modal=True, keep_on_top=True,
                                   auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                   icon=SYSTEM_ICON, text_color='white', background_color='dark blue')
            return

        if (tempcolumnname is not None and templike is None) or (tempcolumnname is None and templike is not None):
            Sg.popup_quick_message("筛选列及筛选内容不匹配！",
                                   modal=True, keep_on_top=True,
                                   auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                   icon=SYSTEM_ICON, text_color='white', background_color='dark blue')
            return

        # 判断行号范围
        try:
            if tempstartrow is not None and tempendrow is not None:
                if int(tempstartrow) > int(tempendrow):
                    temp = tempstartrow
                    tempstartrow = tempendrow
                    tempendrow = temp
            elif (tempstartrow is not None and tempendrow is None) or (tempstartrow is None and tempendrow is not None):
                Sg.popup_quick_message("开始和结束行号数据不完整！",
                                       modal=True, keep_on_top=True,
                                       auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                       icon=SYSTEM_ICON, text_color='white', background_color='dark blue')
                return
        except Exception as err:
            Sg.popup_quick_message("开始行号<{}>或结束行号<{}>不正确：{}！".format(startrow, endrow, err),
                                   modal=True, keep_on_top=True,
                                   auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                   icon=SYSTEM_ICON, text_color='white', background_color='dark blue')
            return

        try:
            if self.specialdf.dfsetfilter(columnname=tempcolumnname, startrow=tempstartrow, endrow=tempendrow,
                                like=templike) is True:
                # 设置成功，修改“筛选取消”按钮
                if currentwindow is not None:
                    currentwindow[self.FILTERCONDITIONCANCEL].update(disabled=False)
                self.specialdf.infilterstatus = True
                # 刷新已经选择的行列
                self.resetselected(currentevent=self.V_SLIDER_KEY,
                                   currentwindow=currentwindow, currentvalues=None)

        except Exception as err:
            Sg.popup_quick_message("表格过滤错误：{}！".format(err),
                                   modal=True, keep_on_top=True,
                                   auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                   icon=SYSTEM_ICON, text_color='white', background_color='dark blue')
            return

        # 设置过滤后，使用过滤df进行显示，所以不能调用setwholevalue，会覆盖原始dfdata
        # # 刷新显示，使用过滤的数据
        # 此处使用过滤，dffilterchoose=True
        self.resetshow(currentwindow=currentwindow, dffilterchoose=True)

    def filterclear(self, currentevent, currentwindow, currentvalues):
        self.specialdf.dffilterclear()
        # 设置成功，修改“筛选取消”按钮
        if currentwindow is not None:
            currentwindow[self.FILTERCONDITIONCANCEL].update(disabled=True)

        # 取消过滤后，使用原始dfdata
        # # 刷新显示
        self.resetshow(currentwindow=currentwindow, dffilterchoose=False)

    def getrowcount(self):
        return self.specialdf.maxrownumber

    def refreshcurrentshow(self, currentwindow, dffilterchoose=False):
        # 获取显示区域的数据(超出df范围的区域不会返回dfresult, 利用此特性不用单独计算当前是否到最后页)
        # 当主动调用了dffileterchoose 或者dffilter ！= dfdata，说明刚产生过过滤
        if dffilterchoose is False and id(self.specialdf.dfdata) == id(self.specialdf.dffilter):
            # 显示dfdata
            dfresult = self.specialdf.dfdata.iloc[
                       self.specialdf.showstartpoint[0]: self.specialdf.showstartpoint[0] + self.max_rows,
                       self.specialdf.showstartpoint[1]: self.specialdf.showstartpoint[1] + self.max_columns
                       ]
            # 刷新滚动条, 该update会产生slider事件！
            self.v_slider_range = (0, int(self.specialdf.dfdata.shape[0] / self.max_rows) - 1 +
                                   (1 if (self.specialdf.dfdata.shape[0] % self.max_rows) > 0 else 0))
            currentwindow[self.V_SLIDER_KEY].update(range=self.v_slider_range)
            self.h_slider_range = (0, int(self.specialdf.dfdata.shape[1] / self.max_columns) - 1 +
                                   (1 if (self.specialdf.dfdata.shape[1] % self.max_columns) > 0 else 0))
            currentwindow[self.H_SLIDER_KEY].update(range=self.h_slider_range)
            # 确定拷贝的行列数，将剩余行序号和列名清空
            copyrows = dfresult.shape[0]
            copycolumns = dfresult.shape[1]
            currentcolor = \
                list(map(self.specialdf.getrowcolor,
                         range(self.specialdf.showstartpoint[0], self.specialdf.showstartpoint[0] + self.max_rows))) + \
                [self.specialdf.defaultcolor for _ in range(self.max_rows - copyrows)]
        else:
            # 显示dffilter
            dfresult = self.specialdf.dffilter.iloc[
                       self.specialdf.showstartpoint[0]: self.specialdf.showstartpoint[0] + self.max_rows,
                       self.specialdf.showstartpoint[1]: self.specialdf.showstartpoint[1] + self.max_columns
                       ]

            # 刷新滚动条, 该update会产生slider事件！
            self.v_slider_range = (0, int(self.specialdf.dffilter.shape[0] / self.max_rows) - 1 +
                                   (1 if (self.specialdf.dffilter.shape[0] % self.max_rows) > 0 else 0))
            currentwindow[self.V_SLIDER_KEY].update(range=self.v_slider_range)
            self.h_slider_range = (0, int(self.specialdf.dffilter.shape[1] / self.max_columns) - 1 +
                                   (1 if (self.specialdf.dffilter.shape[1] % self.max_columns) > 0 else 0))
            currentwindow[self.H_SLIDER_KEY].update(range=self.h_slider_range)
            # 确定拷贝的行列数，将剩余行序号和列名清空
            copyrows = dfresult.shape[0]
            copycolumns = dfresult.shape[1]
            # 获取当前颜色，不足最大显示行部分补充普通颜色，根据是否editable
            currentcolor = \
                list(map(self.specialdf.getrowcolor, self.specialdf.dffilter.index.to_list())) + \
                [self.specialdf.defaultcolor for _ in range(self.max_rows - copyrows)]

        # 生成空白的完整填充表
        dfnull = pd.DataFrame(["" for _ in range(self.max_columns)] for _ in range(self.max_rows))

        # 形成完整的显示数据（有数据部分会覆盖"", 无数据部分依然是""）
        dfnull.iloc[0:dfresult.shape[0], 0:dfresult.shape[1]] = dfresult
        # 刷新完整显示内容
        for x in range(self.max_rows):
            for y in range(self.max_columns):
                currentwindow[(self.tablekey, x, y)].update(value="{}".format(dfnull.iloc[x, y]),
                                                            text_color=currentcolor[x][0],
                                                            background_color=currentcolor[x][1])
                if self.show_cell_tooltips:
                    # 获取当前每个单元格的对象类型tips，只有显示列号时才需要类型展示
                    if self.showcolnamewithid:
                        typetip = "- ({})".format(str(type(dfresult.iloc[x, y])).replace("class ", "")) \
                            if x < dfresult.shape[0] and y < dfresult.shape[1] else ""
                    else:
                        typetip = ""
                    currentwindow[(self.tablekey, x, y)].set_tooltip(
                        "{}{}".format(dfnull.iloc[x, y], typetip) if dfnull.iloc[x, y] != "" else "" + typetip)

        # 要填写的行序号(不能直接计算，而是要根据结果dfresult获取）
        rowindex = dfresult.index.to_list() + ["" for _ in range(copyrows, self.max_rows)]
        # 更新行序号
        for x in range(self.max_rows):
            currentwindow[(self.tablekey, x, -1)].update(value=rowindex[x])
            if self.show_cell_tooltips:
                currentwindow[(self.tablekey, x, -1)].set_tooltip(rowindex[x])
        # 要填写的列名，当要求显示列号+列名时，加上列号
        columnname = [self.specialdf.headings[self.specialdf.showstartpoint[1] + y] for y in range(copycolumns)] + \
                     ["" for _ in range(copycolumns, self.max_columns)]
        # 更新列名 以及列序号
        for y in range(self.max_columns):
            currentwindow[(self.tablekey, -1, y)].update(value=columnname[y])
            currentwindow[(self.tablekey, -2, y)].update(
                value="[{}]".format(self.specialdf.showstartpoint[1] + y) if y >= 0 and columnname[y] != "" else "")
            if self.show_cell_tooltips:
                currentwindow[(self.tablekey, -1, y)].set_tooltip(
                    "[{}]".format(self.specialdf.showstartpoint[1] + y if y >= 0 else "") + str(columnname[y]))
                if self.showcolnamewithid:
                    # -2列序号的tips加上列类型
                    # 获取当前的类型
                    typename = self.specialdf.getcolumnstype([columnname[y]]) \
                        if y >= 0 and columnname[y] != "" else [""]
                    currentwindow[(self.tablekey, -2, y)].set_tooltip(
                        "[{}]-({})".format(self.specialdf.showstartpoint[1] + y, typename[0])
                        if y >= 0 and columnname[y] != "" else "")

    def resetselected(self, currentevent, currentwindow, currentvalues):
        """
        清理当前选中的行或者列：
        :param currentwindow                    操作的窗口
        :param currentevent                     操作的事件
        :param  currentvalues                    当前的窗口values
        :return:
        """
        # 没有选中模式，直接返回
        if self.select_mode == SPECIAL_TABLE_SELECT_MODE_NONE:
            return

        # self.cell_editable , self.enable_events 可以产生该事件，所以对于数据单元要区分：
        # a、self.cell_editable， True ==> 更新df数据
        # b、self.cell_editable, False ==> 从df读原始数覆盖输入单元
        # c、self.enable_events, 产生行点击事件（tablekey, 行号， -1）
        # 行点击事件产生row选择变化， 如果self.enable_events , 产生tablekey 事件，value为当前行content

        # 1、获取当前事件,以及点击行列类型
        if len(currentevent) == 3:  # 当前命令是表格点击,没有翻页，showstartpoint 没有改变
            # 事件
            # 获取当前行号
            if currentevent[1] not in [-1, -2] and currentevent[2] != -1:
                try:
                    changedrawrow = int(currentwindow[(currentevent[0], currentevent[1], -1)].get())
                except Exception as err:
                    print("获取当前行号失败，超出表格范围，不处理")
                    # 清空当前非法输入
                    currentwindow[currentevent].update(value="")
                    Sg.popup_quick_message("获取当前行号失败，超出表格范围，不处理",
                                           modal=True, keep_on_top=True,
                                           auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                           text_color='white', background_color='dark blue')
                    return
                # 如果点击的是数据单元, cell_editable = True 则更新后台df， False ， 则反向用原始数据覆盖显示
                changedrawcolumn = self.specialdf.showstartpoint[1] + currentevent[2]
                # 如果修改在dfdata的合法单元格，修改相关数据、tooltips和dffilter
                if changedrawrow < self.specialdf.dfdata.shape[0] and changedrawcolumn < self.specialdf.dfdata.shape[1]:
                    if self.specialdf.cell_editable:
                        self.specialdf.dfdata.iloc[changedrawrow, changedrawcolumn] = currentvalues[currentevent]
                        # 同步更新tooltip
                        if self.show_cell_tooltips:
                            currentwindow[currentevent].set_tooltip(currentvalues[currentevent])
                        if id(self.specialdf.dfdata) != id(self.specialdf.dffilter):  # 当前是filter有效状态，需要同步更新dffilter
                            if changedrawrow in self.specialdf.dffilter.index.to_list():  # 判断当前行是否在dffliter中
                                # 当前过滤后的df， index、列名未变，但是绝对位置变化，所以使用loc而不是iloc
                                self.specialdf.dffilter.loc[changedrawrow, self.specialdf.dffilter.columns[changedrawcolumn]] = \
                                    currentvalues[currentevent]
                    else:
                        # 反向覆盖输入数据, 获取原始数覆盖显示
                        currentwindow[currentevent].update(self.specialdf.dfdata.iloc[changedrawrow, changedrawcolumn])
                    # 合法单元格，如果enable_events,
                    if self.enable_events:
                        # 如果enable_event, 发送改行序号被点击事件
                        print("send event ({}, {}, -1)".format(self.tablekey, currentevent[1], -1))
                        currentwindow.write_event_value((self.tablekey, currentevent[1], -1), None)
                else:
                    # 清空当前非法输入
                    currentwindow[currentevent].update(value="")
                    Sg.popup_quick_message("当前单元格超出表格范围，输入无效！",
                                           modal=True, keep_on_top=True,
                                           auto_close_duration=AUTO_CLOSE_DURATION, non_blocking=False,
                                           text_color='white', background_color='dark blue')
                return

            # 行点击时，如果enable_events, 发送表格tablekey事件, value为当前行内容
            tempevent = currentevent[0]
            # 如果点击坐标不是(-1, -1)并且行为-2，-1或者列为-1，说明点击了选择行、列
            # clickedtable = False if currentevent[1] == -1 and currentevent[2] == -1 else True
            clickedrow = currentevent[1] if currentevent[1] not in [-1, -2] and currentevent[2] == -1 else None
            clickedcolumn = currentevent[2] if currentevent[1] in [-1, -2] and currentevent[2] != -1 else None
            try:
                clickedrawrow = \
                    int(currentwindow[(currentevent[0], currentevent[1], -1)].get()) if clickedrow is not None else None
            except Exception as err:
                print("选中行号无法转换为int")
                return
            # clickedrawrow = self.specialdf.showstartpoint[0] + currentevent[1] if clickedrow is not None else None
            clickedrawcolumn = self.specialdf.showstartpoint[1] + currentevent[2] if clickedcolumn is not None else None
            if clickedrawrow is not None:
                if clickedrawrow >= self.specialdf.dfdata.shape[0]:
                    print("{}坐标在表格数据范围({}, {})之外，不处理".format(currentevent, self.max_rows, self.max_columns))
                    return
            if clickedrawcolumn is not None:
                if clickedrawcolumn >= self.specialdf.dfdata.shape[1]:
                    print("{}坐标在表格数据范围({}, {})之外，不处理".format(currentevent, self.max_rows, self.max_columns))
                    return
        else:  # 当前没有点击表格，上下翻页
            tempevent, clickedtable, clickedrow, clickedcolumn, clickedrawrow, clickedrawcolumn = \
                currentevent[0], False, None, None, None, None
        """
        如果是BROWSE单选模式，先做清理，再做设置
        如果是EXTEND多选模式，不需要清理raw set，只需要清理 show set, 但是 raw set 需要根据当前点击和showset进行调整
            点中某项时，判断是否需要选中或者去选中
        """
        # 因为随后会再设置，所以清理相对简单，只需要全部做清理，否则挑选出不做清理的内容会相对复杂的逻辑
        # 2、重新梳理不同模式下的set
        # 2.1、多选模式下的新set
        rowselectedflag = False  # 由于标记是否点击了行并选择
        if self.select_mode == SPECIAL_TABLE_SELECT_MODE_EXTENDED:
            if clickedrow is not None:
                rowselectedflag = True
                if clickedrow in self.specialdf.currentshowrow:
                    self.specialdf.selectedrawrow.remove(clickedrawrow)
                else:
                    self.specialdf.selectedrawrow.add(clickedrawrow)

            if clickedcolumn is not None:
                if clickedcolumn in self.specialdf.currentshowcolumn:
                    self.specialdf.selectedrawcolumn.remove(clickedrawcolumn)
                else:
                    self.specialdf.selectedrawcolumn.add(clickedrawcolumn)

        # 2.2、单选模式下的新set
        if self.select_mode in [SPECIAL_TABLE_SELECT_MODE_BROWSE, SPECIAL_TABLE_LISTBOX_SELECT_MODE_SINGLE]:
            # 重新设置单选模式下的set
            if clickedrawrow is not None:
                rowselectedflag = True
                self.specialdf.selectedrawcolumn.clear()
                # 确定当前行是否要加入set
                if clickedrawrow not in self.specialdf.selectedrawrow:
                    # 如果点击的行不是已选行，则放入已选行set中，准备刷新
                    self.specialdf.selectedrawrow.clear()
                    self.specialdf.selectedrawrow.add(clickedrawrow)
                elif self.select_mode == SPECIAL_TABLE_SELECT_MODE_BROWSE:
                    # 如果点击的行为已选行且BROWSE模式，则从已选行set中清理，不再显示
                    self.specialdf.selectedrawrow.clear()

            if clickedrawcolumn is not None:
                self.specialdf.selectedrawrow.clear()
                # 确定当前列是否要加入set
                if clickedrawcolumn not in self.specialdf.selectedrawcolumn:
                    # 如果点击的列不是已选列，则放入已选行set中，准备刷新
                    self.specialdf.selectedrawcolumn.clear()
                    self.specialdf.selectedrawcolumn.add(clickedrawcolumn)
                elif self.select_mode == SPECIAL_TABLE_SELECT_MODE_BROWSE:
                    # 如果点击的列为已选行且BROWSE模式，则从已选行set中清理，不再显示
                    self.specialdf.selectedrawcolumn.clear()

        # 2.3、选中项判断完成后，重新设置currentrow，给单选中移动及编辑、删除使用
        self.judgesetcurrentrow()

        # 3、清理所有当前的反显
        # 3.1、清理可能在当前页选中的行
        for item in self.specialdf.currentshowrow:
            self.recoverrowcolor(self.tablekey, currentwindow, item)
        self.specialdf.currentshowrow.clear()
        # 3.2清理可能在当前页反显的列
        for item in self.specialdf.currentshowcolumn:
            self.recovercolumncolor(self.tablekey, currentwindow, item)
        self.specialdf.currentshowcolumn.clear()

        # 4、根据新set情况重新刷新
        # 4.1、刷新行
        # 获取当前显示页中的行号列表
        currentshowrowstr = [(currentwindow[(currentevent[0], x, -1)].get()) for x in range(self.max_rows)]
        currentshowrow = [int(x) if x != "" else None for x in currentshowrowstr]
        for item in self.specialdf.selectedrawrow:
            # 查看是否需要刷新的行,考虑过滤行引起的特殊性，不能使用showstartpoint[0],必须使用实际的当前页中的行序号
            if item in currentshowrow:
                # 添加到当前显示set
                tempshowrow = currentshowrow.index(item)
                self.specialdf.currentshowrow.add(tempshowrow)
                for y in range(self.max_columns):
                    currentwindow[(tempevent, tempshowrow, y)].update(
                        text_color=self.specialdf.selected_text_color,
                        background_color=self.specialdf.selected_background_color)

        # 4.2、刷新列
        for item in self.specialdf.selectedrawcolumn:
            # 查看是否存在需要刷新的列
            if self.specialdf.showstartpoint[1] <= item < self.specialdf.showstartpoint[1] + self.max_columns:
                # 添加到当前显示set
                self.specialdf.currentshowcolumn.add(item - self.specialdf.showstartpoint[1])
                for x in range(self.max_rows):
                    currentwindow[(tempevent, x, item - self.specialdf.showstartpoint[1])].update(
                        text_color=self.specialdf.selected_text_color,
                        background_color=self.specialdf.selected_background_color)

        # 5、判断是否发送表格tablekey事件, 只有在某些事件时才发送该事件
        if self.enable_events and rowselectedflag:
            currentwindow.write_event_value(self.tablekey, self.getselectedrowcontent())

    def sliderclicked(self, currentevent, currentwindow, currentvalues):
        """
        翻页当前的行或者列：
        :param currentevent                窗口event
        :param currentwindow               窗口
        :param currentvalues               窗口values
        :return:
        """
        # 获取要翻到的页
        if currentevent == self.V_SLIDER_KEY:
            # 将精度改为float，可以逐行上下翻
            # torowpage = int(currentvalues[currentevent])
            torowpage = float(currentvalues[currentevent])
            # print("上下翻页到第{}页".format(torowpage))
            tocolumnpage = self.specialdf.currentcolumnpage
        else:
            torowpage = self.specialdf.currentrowpage
            # 将精度改为float，可以逐列左右翻
            # tocolumnpage = int(currentvalues[currentevent])
            tocolumnpage = float(currentvalues[currentevent])
            # print("左右翻页到第{}页".format(tocolumnpage))

        #   如果依然是原行、列页，直接返回
        if self.specialdf.currentrowpage == torowpage and self.specialdf.currentcolumnpage == tocolumnpage:
            return
        else:
            # 更新起始点
            self.specialdf.showstartpoint = [int(torowpage * self.max_rows), int(tocolumnpage * self.max_columns)]
            self.specialdf.currentrowpage = torowpage
            self.specialdf.currentcolumnpage = tocolumnpage
            self.refreshcurrentshow(currentwindow)
            # 重新刷新选中的行、列情况
            self.resetselected(currentevent, currentwindow, currentvalues)

    def setwholevalue(self, currentwindow, dfdata: pd.DataFrame = None,
                      infodict: dict = None, headings: list = None):
        """
        全部更新数据表和显示
        :param currentwindow:           当前窗口
        :param dfdata:                  新数据
        :param infodict                 字典方式数据
        :param headings                list方式下的列headings信息
        :return:
        """
        if dfdata is not None:
            # 首先更新dflist class数据
            self.specialdf.dfsetwholevalue(dfdata=dfdata, headings=headings)
            # print("设置全部数据:\n{}".format(dfdata))
            self.usedfdatainit(dfdata)  # 设置colnamelist、tableinfodictlist等
        elif infodict is not None and headings is not None:
            # 将命令相关参数信息字典保存到二维list中，对应于界面表格的数据
            if infodict == []:  # 空list必须有columns
                dfdata = pd.DataFrame(infodict, columns=headings)
            else:  # 非空list不能有columns，否则结果不对， 先生成df，再赋值columns
                dfdata = pd.DataFrame(infodict)
            # 首先更新df数据
            self.specialdf.dfsetwholevalue(dfdata=dfdata, headings=headings)
        # 需要更新filter的行数，筛选列，以及可以批量赋值的列
        self.specialdf.colnameusedlist = [""] + self.specialdf.headings
        self.specialdf.colnamefilterlist = [""] + self.specialdf.headings
        rowlist = [""] + [str(a) for a in range(0, self.specialdf.maxrownumber)]
        if self.filter_visible:
            currentwindow[self.ROW_START].update(values=rowlist)
            currentwindow[self.ROW_END].update(values=rowlist)
            currentwindow[self.COLUMNFILTER].update(values=self.specialdf.colnamefilterlist)
            currentwindow[self.COLCHOOSE].update(values=self.specialdf.colnameusedlist)

        # 数据全部更新，需要更新rowcolor
        # 用于保存每行的文本和背景颜色，
        self.buildrowstyleinfo()
        self.buildcolumnstyleinfo()

        # 更新table显示
        self.resetshow(currentwindow=currentwindow, dffilterchoose=False)

    def showfilterresult(self, currentwindow, dffiler: pd.DataFrame):
        if dffiler is None:
            return
        self.resetshow(currentwindow=currentwindow, dffilterchoose=False)

    def resetshow(self, currentwindow, dffilterchoose=False, datachanged=False):
        """
        :param currentwindow               当前窗口
        :param dffilterchoose               当前是否过滤方式
        从0， 0开始，刷新整个显示页面
        :param datachanged                 本次刷新数据是否变化
        :return:
        """
        # 1、初始化所有相关信息
        # 1.1、显示起始坐标， 三个必须全部清零，因为slider的update会触发slider点击事件
        self.specialdf.showstartpoint = [0, 0]
        self.specialdf.currentrowpage = 0
        self.specialdf.currentcolumnpage = 0
        # 1.2、当前选中行列清除
        for item in self.specialdf.currentshowrow:
            for y in range(self.max_columns):
                currentwindow[(self.tablekey, item, y)].update(
                    text_color=self.specialdf.input_text_color
                    if self.specialdf.cell_editable else self.specialdf.text_color,
                    background_color=self.specialdf.input_text_background_color
                    if self.specialdf.cell_editable else self.specialdf.text_background_color)
        self.specialdf.currentshowrow.clear()
        # 1.3、清理可能在当前页反显的列
        for item in self.specialdf.currentshowcolumn:
            for x in range(self.max_rows):
                currentwindow[(self.tablekey, x, item)].update(
                    text_color=self.specialdf.input_text_color
                    if self.specialdf.cell_editable else self.specialdf.text_color,
                    background_color=self.specialdf.input_text_background_color
                    if self.specialdf.cell_editable else self.specialdf.text_background_color)
        self.specialdf.currentshowcolumn.clear()
        # 1.4、全局选中行列清理
        self.specialdf.selectedrawrow.clear()
        self.specialdf.selectedrawcolumn.clear()

        # 2、刷新数据
        self.refreshcurrentshow(currentwindow, dffilterchoose)
        # 3、选中项判断完成后，重新设置currentrow，给单选中移动及编辑、删除使用
        self.judgesetcurrentrow()

        # 4、判断是否要发送datachanged事件
        if self.datachangeeventtrigger and datachanged:
            currentwindow.write_event_value((self.tablekey, SPECIAL_TABLE_CHANGE_EVENT_TRIGGER), None)

    def donothing(self, currentevent, currentwindow, currentvalues):
        print("eventprocess {} do nothing!".format(currentevent))
        return

    def getcolnameusedlist(self):
        return self.specialdf.colnameusedlist

    def getcurrentrow(self):
        return self.specialdf.currentrow

    def getcurrentrowcontent(self):
        if self.specialdf.currentrow is None:
            return None
        else:
            return self.specialdf.dfdata.iloc[self.specialdf.currentrow, :].tolist()

    def getcommandinfluncerow(self, currentevent=None, currentwindow=None, currentvalues=None, executednumber=None):
        """
        根据当前指定的行号，返回当前命令、currrentrow、tonumber结合对比，返回不受影响的行数（不是行号），没有则None
        :param currentevent             当前的事件
        :param currentwindow            当前窗口
        :param currentvalues           当前事件值
        :param executednumber            用于比较的行号
        :return:
        """
        # 如果是CLEARALL，全部会被清空，不受影响的行数是0
        if currentevent == self.CLEARALL:
            return 0
        # 没有选中currentrow
        if self.specialdf.currentrow is None:
            return executednumber

        # ADDROW命令不影响任何已经存在的行
        if currentevent == self.ADDROW:
            return executednumber

        # currentrow >= excutednumber 保持原excutednumber及颜色
        # currentrow < excutednumber  最小currentrow - 1
        if currentevent in (self.EDITROW, self.DELETEROW):
            return self.specialdf.currentrow if self.specialdf.currentrow < executednumber else executednumber

        if currentevent == self.MOVEROWTOTOP:
            tonumber = 0

        if currentevent == self.MOVEROWUP:
            tonumber = self.specialdf.currentrow - 1 if self.specialdf.currentrow - 1 >= 0 else 0

        if currentevent == self.MOVEROWDOWN:
            tonumber = self.specialdf.currentrow + 1 if self.specialdf.currentrow + 1 < self.specialdf.dfdata.shape[0] else self.specialdf.currentrow

        if currentevent == self.MOVEROWTOBOTTOM:
            tonumber = self.specialdf.dfdata.shape[0] - 1 if self.specialdf.dfdata.shape[0] - 1 >= 0 else 0

        if currentevent == self.MOVEROWTOCERTAIN:
            try:
                tonumber = int(currentwindow[self.TARGETROWNUMBER].get())
            except Exception as err:
                print("获取要到达的行数失败：{}".format(err))
                tonumber = self.specialdf.dfdata.shape[0] - 1 if self.specialdf.dfdata.shape[0] - 1 >= 0 else 0

        # excutednumber - 1 <  currentrow、tonumber 中的小者  保持原excutednumber及颜色
        # excutednumber - 1 >=  currentrow、tonumber 中的小者	 执行到currentrow \tonumber中的小者
        if currentevent in (self.MOVEROWTOCERTAIN, self.MOVEROWTOBOTTOM, self.MOVEROWTOTOP,
                            self.MOVEROWDOWN, self.MOVEROWUP):
            #  根据 executednumber 和 currentrow、tonumber的关系判断处理
            comparevalue = self.specialdf.currentrow if self.specialdf.currentrow < tonumber else tonumber
            if executednumber - 1 < comparevalue:
                return executednumber
            else:
                return comparevalue

        return executednumber

    def getselectedrowcolumn(self):
        return [self.specialdf.selectedrawrow, self.specialdf.selectedrawcolumn]

    def getselectedrowcontent(self):
        # 将所选中的所有行内容形成列表返回
        return self.specialdf.dfdata.iloc[list(self.specialdf.selectedrawrow), :].values.tolist()

    def getcolumnname(self):
        return list(self.specialdf.dfdata)

    def getspecialeventlist(self):
        return self.specialeventlist

    def itemeditgeneral(self, rowcontent: list,
                        method: int = None,
                        current_row: int = None,
                        index_col_value: list = None):
        """
        df方式时，如果没有指定的钩子函数，用此通用行编辑函数
        :param rowcontent:              list:当前编辑行的值list
        :param method:                  EDIT_METHOD or ADD_METHOD
        :param current_row:             当前编辑的行号，用于判断是否重复
        :param index_col_value:         索引列的当前列值，用于判断是否重复值
        :return:
        """
        # 当前表的列序号list
        currentcolumnsindex = [x for x in range(len(self.specialdf.headings))]
        # 当前设置的列名list
        currentvaluelist = rowcontent
        defaultkey = "_itemedit_general_"
        # ROW_EDIT_GUI_KEY = "".join([self.tablekey, defaultkey])
        ROW_EDIT_GUI_KEY = (self.tablekey, defaultkey)
        ROW_EDIT_OK = (ROW_EDIT_GUI_KEY, '_ok_')
        ROW_EDIT_CANCEL = (ROW_EDIT_GUI_KEY, '_cancel_')
        # 序列旧名称和新名称的二维列表
        tablevalues = [[self.specialdf.headings[x], currentvaluelist[x]] for x in currentcolumnsindex]
        # 显示的表格头
        tempcellsize = (18, 1)
        tempvaluesize = (56, 1)
        tempargvlist = []
        # 确定使用Input还是Combo作为cell编辑的类型
        if self.specialdf.col_value_scale is not None:
            tempcelltypelist = [Sg.Combo if (isinstance(x, list) and len(x) > 0)
                                else Sg.Input for x in self.specialdf.col_value_scale]
        else:
            tempcelltypelist = [Sg.Input for _ in self.specialdf.headings]

        for i, item in enumerate(tempcelltypelist):
            if item == Sg.Combo:
                # 形成每个编辑单元格需要的差异化参数
                tempargvlist.append(
                    [
                        self.specialdf.col_value_scale[i],
                        {
                            "default_value": tablevalues[i][1],
                            "button_arrow_color": self.specialdf.combox_button_arrow_color,
                            "button_background_color": self.specialdf.combox_button_background_color,
                            "size": (tempvaluesize[0] - 2, tempvaluesize[1])
                        }
                    ]
                )
            else:
                # 形成每个编辑单元格需要的差异化参数
                tempargvlist.append(
                    [
                        tablevalues[i][1],
                        {
                            "justification": "l",
                            # "border_width": self.border_width,
                            "size": tempvaluesize
                        }
                    ]
                )

        headinglayout = [[
            Sg.T(s,
                 k=(ROW_EDIT_GUI_KEY, -1, i - 1),  # headings 加上了序号列，左上角从 （-1， -1）开始
                 justification='center',
                 enable_events=False,
                 font=self.font,
                 border_width=1 if self.border_width == 0 else self.border_width,
                 relief=Sg.RELIEF_RIDGE,
                 background_color=self.specialdf.head_background_color,
                 text_color=self.specialdf.head_text_color,
                 size=tempvaluesize if s == "列值" else tempcellsize,
                 pad=self.pad) for i, s in enumerate(['列序号', '列名', '列值'])
        ]]
        # 显示表格内容,key用元组（ROW_EDIT_GUI_KEY，行, 列），只有第2列可以编辑，后续事件处理只需要判断key元组的[0],[2]即可
        datalayout = [
            [Sg.T(r,
                  k=(ROW_EDIT_GUI_KEY, r, -1),
                  size=tempcellsize,
                  pad=self.pad,
                  font=self.font,
                  enable_events=False,
                  justification='center',
                  relief=Sg.RELIEF_RIDGE,
                  visible=self.specialdf.visible_column_map[r],
                  border_width=1 if self.border_width == 0 else self.border_width,
                  background_color=self.specialdf.head_background_color,
                  text_color=self.specialdf.head_text_color)] +
            [Sg.T(
                tablevalues[r][0],
                justification='right',
                # disabled=True,
                background_color=self.specialdf.text_background_color,
                enable_events=False,
                text_color=self.specialdf.text_color,
                font=self.font,
                relief=self.relief,
                border_width=self.border_width,
                visible=self.specialdf.visible_column_map[r],
                k=(ROW_EDIT_GUI_KEY, r, 0),
                size=tempcellsize,
                pad=self.pad)] +
            [tempcelltypelist[r](
                tempargvlist[r][0],
                **tempargvlist[r][1],
                background_color=self.specialdf.input_text_background_color,
                enable_events=False,
                text_color=self.specialdf.input_text_color,
                font=self.font,
                visible=self.specialdf.visible_column_map[r],
                k=(ROW_EDIT_GUI_KEY, r, 1),
                pad=self.pad)] for r in range(len(tablevalues))
        ]
        taillayout = [
            [Sg.Col([[
                Sg.Frame("",
                         [[Sg.Button('确定', k=ROW_EDIT_OK),
                           Sg.Button('取消', k=ROW_EDIT_CANCEL)]],
                         vertical_alignment='top', element_justification='center', expand_x=True, border_width=0)
            ]], justification='center', expand_x=True)]]

        currentlayout = [[Sg.Col(headinglayout + datalayout, size=(686, 500), expand_x=True, expand_y=False,
                                 justification='center', scrollable=True)]] + taillayout

        # 开启window
        itemwindow = Sg.Window(
            '表格行编辑',
            currentlayout,
            font=SPECIAL_TABLE_DEF_FT,
            icon=SYSTEM_ICON,
            return_keyboard_events=True,
            resizable=True,
            size=(706, 600),
            modal=True,
            # element_justification='center',
            keep_on_top=True,
            finalize=True
        )
        rowresult = None
        while True:
            event, values = itemwindow.read()
            if event in (Sg.WIN_CLOSED, '_close_'):
                rowresult = None
                break

            elif event == ROW_EDIT_OK:
                rowresult = [itemwindow[(ROW_EDIT_GUI_KEY, x, 1)].get() for x in range(len(self.specialdf.headings))]
                # 添加模式下判断index是否重复
                # 首先判断是否有indexcol
                if self.specialdf.indexcol is not None:
                    # 获取当前编辑行indexcol的值
                    currentindexvalue = [rowresult[x] for x in self.specialdf.indexcol] \
                        if isinstance(self.specialdf.indexcol, list) else rowresult[self.specialdf.indexcol]
                    if currentindexvalue in index_col_value:
                        # 获取该值在整个索引列中位置信息
                        cmdindex = index_col_value.index(currentindexvalue)
                        if not (method == EDIT_METHOD and cmdindex == current_row):  # 排除本行编辑的情况
                            # 不是编辑当前行，说明和某行的index重复
                            # 新增内容和原有内容重复
                            valuevalide = False
                            Sg.popup_error("{}和第{}条记录同名！".format(currentindexvalue, cmdindex),
                                           keep_on_top=True, modal=True, icon=SYSTEM_ICON)
                            continue
                # 没有重名情况， 关闭窗口，返回修改或者新增的值
                break

            elif event == ROW_EDIT_CANCEL:
                rowresult = None
                break
            else:
                continue

        itemwindow.close()
        # 返回结果字典
        return rowresult

    def setbind(self, currentwindow=None):
        """
        window初始化后，调用update bind，内部调用该函数，初始化相关的bind事件
        :param currentwindow:
        :return:
        """
        # currentwindow[self.TABLE_FRAME].bind("<Enter>", self.MOUSE_IN, propagate=True)
        # currentwindow[self.TABLE_FRAME].bind("<Leave>", self.MOUSE_LEAVE, propagate=True)
        # self.windowmousedict = windowmousedict
        # currentwindow[self.TABLE_FRAME].bind("<MouseWheel>", self.MOUSE_WHEEL, propagate=True)
        # 将表格中所有单元格绑定mousewheel事件，包括-1行 -1列,
        # print("bind mouse wheel！")
        for row in range(-2, self.max_rows):
            for column in range(-1, self.max_columns):
                currentwindow[(self.tablekey, row, column)].bind("<MouseWheel>", self.MOUSE_WHEEL, propagate=True)

    def mousewheelupdown(self, currentwindow=None, currentvalues=None, mousewheelupdown=None):
        """
        接手窗口的wheelupdown并且进行处理
        :param currentwindow:               当前主窗口
        :param currentvalues:               当前窗口values
        :param mousewheelupdown:            MOUSEWHEEL_UP, MOUSEWHEEL_DOWN
        :return:
        """
        # print("{}:{}".format(self.tablekey, mousewheelupdown))
        # 获取slider的当前值
        value = currentvalues[self.V_SLIDER_KEY]
        rangetemp = [self.v_slider_range[0], self.v_slider_range[1]]
        rangetemp.sort()
        rangemin, rangemax = rangetemp
        # 计算新的value
        newvalue = value
        if mousewheelupdown == MOUSEWHEEL_DOWN:
            newvalue = value + self.v_slider_resolution if value + self.v_slider_resolution < rangemax else rangemax
        elif mousewheelupdown == MOUSEWHEEL_UP:
            newvalue = rangemin if value - self.v_slider_resolution < rangemin else value - self.v_slider_resolution

        # 设置v_slider的新值
        currentwindow[self.V_SLIDER_KEY].update(value=newvalue)
        # 模拟sliderclick事件
        self.sliderclicked(self.V_SLIDER_KEY, currentwindow, currentvalues)

    def loaddatafromfile(self, currentwindow, inputfilename: str = None, headline: int = 0, tailline: int = 0,
                         sheetnumber: int = 0, headname: int = 0) -> bool:
        """
        从文件导入数据到df
        :param currentwindow:                           当前主窗口，为后续刷新显示用
        :param inputfilename:                           str： 文件全路径名
        :param headline:                                int: 导出表首丢弃行数
        :param tailline:                                int： 导出表尾丢弃行数
        :param sheetnumber:                             int： 导出的子表号
        :param headname:                                int: 丢弃表首后，列名所在行，>=0有效
        :return:                                        bool：True 导入成功 False 导入失败
        """
        # 读取指定的文件
        fileresult = os.path.splitext(inputfilename)
        # print(fileresult[-1])
        # 如果是xlsx文件
        # 2、pandas读取csv文件
        # import pandas as pd
        # data = pd.read_csv('path', sep=',', header=0, names=["第一列"，"第二列"，"第三列"]，encoding = 'utf-8')
        # path: 要读取的文件的绝对路径
        # sep: 指定列和列的间隔符，默认sep =‘, ’
        # 若sep =‘’\t",即列与列之间用制表符\t分割，相当于tab——四个空格
        # header: 列名行，默认为0
        # names: 列名命名或重命名
        # encoding: 指定用于unicode文本编码格式
        # 3、pandas读取txt文件
        # read_csv
        # data = pd.read_table('path', sep='\t', header=None, names=['第一列', '第二列', '第三列'])

        myhead = headname if headname >= 0 else None
        if fileresult[-1] == ".xlsx":
            try:
                # # 读取时跳过表头行和表尾行
                readdf = pd.read_excel(inputfilename, header=myhead,
                                       skiprows=headline,
                                       skipfooter=tailline, sheet_name=sheetnumber)
                # head需要转换为str以便后续处理
                readdf.columns = readdf.columns.map(str)
            except Exception as err:
                Sg.popup_error("读取{}失败:{}！".format(inputfilename, err),
                               modal=True, keep_on_top=True, icon='ic_umdos.ico')
                return False
        # TODO 增加read_csv时分隔符设置。
        elif fileresult[-1] == ".csv":
            try:
                # # 读取时跳过表头行和表尾行
                readdf = pd.read_csv(inputfilename, header=myhead,
                                     skiprows=headline,
                                     skipfooter=tailline, encoding='utf-8')
                # head需要转换为str以便后续处理
                readdf.columns = readdf.columns.map(str)
            except Exception as err:
                Sg.popup_error("读取{}失败:{}！".format(inputfilename, err),
                               modal=True, keep_on_top=True, icon='ic_umdos.ico')
                return False

        elif fileresult[-1] == ".txt":
            try:
                # # 读取时跳过表头行和表尾行
                readdf = pd.read_csv(inputfilename, header=myhead,
                                     skiprows=headline, sep=',',
                                     skipfooter=tailline, encoding='utf-8')
                # head需要转换为str以便后续处理
                readdf.columns = readdf.columns.map(str)
            except Exception as err:
                Sg.popup_error("读取{}失败:{}！".format(inputfilename, err),
                               modal=True, keep_on_top=True, icon='ic_umdos.ico')
                return False
        else:
            Sg.popup_error("无法识别后缀的文件:{}！".format(inputfilename),
                           modal=True, keep_on_top=True, icon='ic_umdos.ico')
            return False

        self.setwholevalue(currentwindow=currentwindow, dfdata=readdf)
        Sg.popup_quick_message("导入文件{}完成".format(inputfilename),
                               modal=True, keep_on_top=True, text_color='white', background_color='dark blue')

        return True

    def savedatatofile(self, outputfilename=None, modelfilename=None, index: bool = True,
                       sheetnumber: int = 0, headline: int = 0, tailline: int = 0) -> bool:
        """
        将df内容保存到文件
        :param outputfilename:                  str：输出文件全路径名
        :param modelfilename:                   str：输出模板文件全路径名
        :param index:                           bool: True 输出index， False： 不输出
        :param sheetnumber:                     int: 输入模板的子表号
        :param headline:                        int：输出模板表首跳过行数
        :param tailline:                        int: 输出模板表尾跳过行数
        :return:                                bool： True 导出成功， False 导出失败
        """
        if outputfilename is None:
            return False

        if modelfilename is None:  # 没有模板文件，直接导出到excel
            try:
                self.specialdf.dfdata.to_excel(outputfilename, index=index)
            except Exception as err:
                print("导出数据到文件{}失败：{}！".format(outputfilename, err))
                if guiflag:
                    Sg.popup_quick_message("导出数据到文件{}失败：{}！".format(outputfilename, err),
                                           modal=True, keep_on_top=True, text_color='white',
                                           background_color='dark blue')
                return False
        else:
            # model 方式导出
            return self.savedftomodelexcelfile(outputfilename=outputfilename,
                                               modelfilename=modelfilename,
                                               sheetnumber=sheetnumber,
                                               headline=headline,
                                               tailline=tailline)

        print("导出数据到文件{}子表{}完成！".format(outputfilename, sheetnumber))
        Sg.popup_quick_message("导出数据到文件{}完成！".format(outputfilename),
                               modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
        return True

    def savedftomodelexcelfile(self, outputfilename: str = None,
                               modelfilename: str = None, sheetnumber: int = 0,
                               headline: int = 0, tailline: int = 0) -> bool:
        """
        将df内容保存到文件
        :param outputfilename:                  str：输出文件全路径名
        :param modelfilename:                   str：输出模板文件全路径名
        :param sheetnumber:                     int: 输出到文件的子表号
        :param headline:                        int：输出模板表首跳过行数
        :param tailline:                        int: 输出模板表尾跳过行数
        :return:                                bool： True 导出成功， False 导出失败
        """
        # 打开模板文件，处理结束后保存到目标文件
        global guiflag
        try:
            wb = load_workbook(modelfilename)
            ws = wb.worksheets[sheetnumber]
        except Exception as err:
            print("打开模板文件{}子表{}失败：{}！".format(modelfilename, sheetnumber, err))
            if guiflag:
                Sg.popup_error("打开模板文件{}子表{}失败：{}！！".format(modelfilename, sheetnumber, err),
                               modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
            return False

        # 将结果数据填入模板
        # 逐行\逐列处理，笨办法
        # ws 起始行
        # 旧残留数据行数
        olddatanumber = ws.max_row - headline - tailline
        # 插入行数累计
        insertednumber = 0
        rownumber = headline + 1  # 跳过表头
        # 能够拷贝的列数，小于模板列数时，使用源数据列数
        maxcolnumber = self.specialdf.dfdata.shape[1] if self.specialdf.dfdata.shape[1] < ws.max_column else ws.max_column  # 获取模板列数
        # 是否在处理第一行，从数据第二行开始，复制第一行的格式
        isfirstrow = True
        # 逐行获取源数据信息进行处理
        for index, rowseries in self.specialdf.dfdata.iterrows():
            row = rowseries.values
            # ws 起始列，ws列号从1开始，但是dataframe从0开始
            # 不是第一行时插入新行，将表尾行往下推，复制上一行的各个单元格格式
            # 复制单元格属性
            if isfirstrow is False:
                ws.insert_rows(rownumber)
                for j in range(1, ws.max_column + 1):
                    lastrow = rownumber - 1
                    ws.cell(rownumber, j).number_format = ws.cell(lastrow, j).number_format
                    ws.cell(rownumber, j).font = copy.copy(ws.cell(lastrow, j).font)
                    ws.cell(rownumber, j).alignment = copy.copy(ws.cell(lastrow, j).alignment)
                    ws.cell(rownumber, j).fill = copy.copy(ws.cell(lastrow, j).fill)
                    ws.cell(rownumber, j).border = copy.copy(ws.cell(lastrow, j).border)

            # ws的列数从1开始，但是dataframe从0 开始，所以此处range如此设定
            for colnumber in range(maxcolnumber):
                # ws的列数从1开始，但是dataframe从0 开始，所以此处需要加1
                ws.cell(rownumber, colnumber + 1, row[colnumber])
                # 本列处理完成，列数+1
                colnumber += 1
            # 本行处理完成，行数+1
            rownumber += 1
            insertednumber += 1
            # 翻转标志，保证从第二行开始复制第一行数据单元的格式。
            isfirstrow = False

        # 清理模板文件表头和表尾之间原来残留的数据，为了保持格式完整，先插入新数据，再删除老数据
        deleterownumber = headline + insertednumber + 1
        # 根据要删除的行数，持续删除head后的第一行，直到行尾
        for _ in range(olddatanumber + 1):
            ws.delete_rows(deleterownumber)

        # 保存处理好的数据表到输出目标excel文件
        try:
            wb.save(outputfilename)
        except Exception as err:
            print("输出文件{}错误：{}".format(outputfilename, err))
            if guiflag:
                Sg.popup_error("输出文件{}错误：{}".format(outputfilename, err),
                               modal=True, keep_on_top=True, icon='ic_umdos.ico')
            return False

        print("{}处理完成！".format(outputfilename))
        if guiflag:
            Sg.popup_quick_message("将数据按模板文件{}导出到文件{}完成".format(modelfilename, outputfilename),
                                   modal=True, keep_on_top=True, text_color='white', background_color='dark blue')
        return True

    def update(self, commandtype=None, currentevent=None, currentwindow=None, currentvalues=None,
               dfshowdata: pd.DataFrame = None, infodict: dict = None, rownumber=None, color=None,
               location=None, value=None,windowmousedict: dict = None, headings=None,
               mousewheelupdown=None, newrowcontent: list = None, filename: str = None, sheetnumber: int = 0,
               modelfilename: str = None, headline: int = 0, tailline: int = 0, headname: int = -1,
               # citemeditfunc: object = None,
               # itemeditfuncargsdic: dict = {}, indexcol: int = None, visible_column_map=None,
               # col_widths=None, size=DEF_WINDOW_SIZE, cell_size=DEF_CELL_SIZE, scrollable=False,
               # col_value_scale: list = None, filter_visible=True, edit_button_visible=True,
               # visible=True,   right_click_menu=None, vertical_scroll_only=True,
               # expand_x=False, expand_y=False, select_mode=None, font=SPECIAL_TABLE_DEF_FT, pad=DEF_PAD,
               # max_rows=MAX_SPECIAL_TABLE_ROWS, max_columns=MAX_SPECIAL_TABLE_COLUMNS,
               # text_color='black', text_background_color='white',
               # head_text_color='white', head_background_color='dark blue',
               # selected_text_color='white', selected_background_color='blue',
               # disable_slider_number_display=False
               ):
        """
        本类的类似Sg update处理
        :param commandtype:                        设置的命令类型
                                            SPECIAL_TABLE_SET_ROWCOLOR = 'set_row_color'
                                            SPECIAL_TABLE_SET_CELLVALUE = 'set_cell_value'
                                            SPECIAL_TABLE_SET_ROWVALUE = 'set_row_value'
                                            SPECIAL_TABLE_SET_CURRENTROW = 'set_current_row'
                                            SPECIAL_TABLE_SET_WHOLEVALUE = 'set_whole_value'
                                            SPECIAL_TABLE_SET_BIND
                                            SPECIAL_TABLE_SET_MOUSEWHEEL
                                            SPECIAL_TABLE_SET_ADD_NEWROW
                                            SPECIAL_TABLE_SET_SAVETOFILE
                                            SPECIAL_TABLE_SET_LOADFROMFILE
        :param currentevent:                当前的事件
        :param currentwindow:               当前的窗口
        :param currentvalues:               当前的窗口values
        :param dfshowdata:                  当前传递过来的df参数，用于刷新当前数
        :param infodict:                    当前传递过来的list data dict，用于刷新当前数
        :param rownumber:                   要处理的行
        :param windowmousedict:             dict, 主窗口用于记录内部表格mouse in，leave状态的字典
        :param mousewheelupdown:            MOUSEHWEEL_UP , MOUSEWHEEL_DOWN
        :param newrowcontent:               SET_ADDNEWROW 时的行内容
        :param color:                       要设置的颜色
        :param location:                    当前要设置的表格单元坐标
        :param value:                       当前要设置的值
        :param filename:                     str = None, 保存df到文件和调入文件数据的文件名
        :param sheetnumber:                  int = 0,读取和写入的子表号
        :param modelfilename:                str = None,输出使用的模板文件
        :param headline:                     int = 0, 输入输出文件表头的行数
        :param tailline:                     int = 0, 输入输出文件表尾的行数
        :param headname:                 文件去掉表头后， 如果有列名，所处的文件行数（0开始） int = -1,
        :return:
        """
        # 更新整张表的数据
        if currentwindow is None:
            print("SpecialTable update 参数currrenwindow or currentevent 无效")
            return False

        def innersetwholevalue(myself):
            # update table data
            if dfshowdata is not None or infodict is not None and not isinstance(currentevent, tuple):
                myself.setwholevalue(currentwindow, dfdata=dfshowdata, infodict=infodict, headings=headings)

        def innersetrowcolor(myself):
            if rownumber is not None:
                if color is not None:
                    if currentwindow is not None:
                        myself.setrowcolor(myself.tablekey, currentwindow, rownumber, color)
                else:
                    # 有行号但是没有色号，设置当前行
                    myself.specialdf.setcurrentrow(rownumber)

        def innersetcellvalue(myself):
            # 设置单元格值
            if location is not None and currentwindow is not None:
                myself.setcellvalue(currentwindow, location, value)

        def innersetrowvalue(myself):
            # 设置行的值
            myself.setrowvalue(currentwindow, rownumber, newrowcontent)

        def innersetcurrentrow(myself):
            myself.specialdf.setcurrentrow(rownumber)

        def innersetbind(myself):
            myself.setbind(currentwindow)

        def innermousewheel(myself):
            myself.mousewheelupdown(currentwindow, currentvalues, mousewheelupdown)

        def inneraddnewrow(myself):
            return myself.addrow(currentevent, currentwindow, currentvalues, newrowcontent=newrowcontent)

        def innersavedatatofile(myself):
            return myself.savedatatofile(outputfilename=filename, modelfilename=modelfilename,
                                         sheetnumber=sheetnumber, headline=headline, tailline=tailline)

        def innerloaddatafromfile(myself):
            return myself.loaddatafromfile(currentwindow=currentwindow,
                                           inputfilename=filename, sheetnumber=sheetnumber,
                                           headline=headline, tailline=tailline, headname=headname)

        processdic = {
            SPECIAL_TABLE_SET_ROWCOLOR: innersetrowcolor,
            SPECIAL_TABLE_SET_CELLVALUE: innersetcellvalue,
            SPECIAL_TABLE_SET_ROWVALUE: innersetrowvalue,
            SPECIAL_TABLE_SET_CURRENTROW: innersetcurrentrow,
            SPECIAL_TABLE_SET_WHOLEVALUE: innersetwholevalue,
            SPECIAL_TABLE_SET_BIND: innersetbind,
            SPECIAL_TABLE_SET_MOUSEWHEEL: innermousewheel,
            SPECIAL_TABLE_SET_ADD_NEWROW: inneraddnewrow,
            SPECIAL_TABLE_SET_SAVETOFILE: innersavedatatofile,
            SPECIAL_TABLE_SET_LOADFROMFILE: innerloaddatafromfile
        }

        # 先处理表格内部的元组事件
        if isinstance(currentevent, tuple):
            if isinstance(currentevent[0], tuple):  # 是否鼠标事件
                if currentevent[0][0] == self.tablekey and currentevent[1] == self.MOUSE_WHEEL:
                    # print("binded mousewheel event:{}".format(currentevent))
                    self.eventprocess(currentevent, currentwindow, currentvalues)

            else:
                # print("update processing event:{}".format(currentevent))
                self.eventprocess(currentevent, currentwindow, currentvalues)

        # 此外的事件为update属性的事件
        tempfunc = processdic.get(commandtype, None)
        if tempfunc is not None:
            return tempfunc(self)

    def get(self, attribute: str = SPECIAL_TABLE_GET_SELECTED_ROWCONTENT, **kwargs):
        """
        本类的get处理
        :param attribute:                   获取的属性, 默认SPECIAL_TABLE_GET_CURRENTROW_CONTENT,get()相当于默认获取当前行内容
                                            SPECIAL_TABLE_GET_MAXROWNUMBER = 'get_max_row_number'
                                            SPECIAL_TABLE_GET_MAXCOLUMNNUMBER = 'get_max_column_number'
                                            SPECIAL_TABLE_GET_CURRENTROW = 'get_current_row'
                                            SPECIAL_TABLE_GET_CURRENTROW_CONTENT = "get_current_row_content"
                                            SPECIAL_TABLE_GET_SPECIALEVENTLIST = 'get_specail_eventlist'
                                            SPECIAL_TABLE_GET_RESULT_DICT = 'get_result_dict'
                                            SPECIAL_TABLE_GET_EVENTINFLUENCEROW = 'event_influnce_row'
                                            SPECIAL_TABLE_GET_SELECTED_ROWCOLUMN = 'get_selected_row_column'
                                            SPECIAL_TABLE_GET_SELECTED_ROWCONTENT
                                            SPECIAL_TABLE_GET_COLUMNN_NAME

        """
        # 获取attribute对应的取值函数
        tempfunction = self.getattrdict.get(attribute, None)
        # 如果函数存在则返回值，否则返回None
        return None if tempfunction is None else tempfunction(**kwargs)


@dataclass
class SpecialTableGroup(SpecialTable):
    tablegroupkey: str
    specialdflist: list = None
    dflist: list = None
    tabname: str = "表"
    tabledatadictlist: list = None
    itemeditfunc: dict = None
    itemeditfuncargsdic: dict = None
    itemeditusecolvaluescale: bool = False
    indexcol: list = None
    visible_column_map: list = None
    def_col_width: int = DEF_CELL_SIZE[0]
    auto_size_columns: bool = True
    max_col_width: int = 20
    cell_justification: str = 'r'
    col_widths: list = None
    size: tuple = DEF_WINDOW_SIZE
    cell_size: tuple = DEF_CELL_SIZE
    scrollable: bool = False
    col_value_scale: list = None
    filter_visible: bool = False
    edit_button_visible: bool = True
    filter_set_value_visible: bool = False
    border_width: int = DEF_BD
    use_inner_right_click_menu: bool = False
    move_row_button_visible: bool = True
    visible: bool = True
    headings: list = None
    right_click_menu: list = None
    vertical_scroll_only: bool = True
    expand_x: True = False
    expand_y: True = False
    select_mode: str = None
    font: tuple = SPECIAL_TABLE_DEF_FT
    pad: tuple = DEF_PAD
    max_rows: int = MAX_SPECIAL_TABLE_ROWS
    max_columns: int = MAX_SPECIAL_TABLE_COLUMNS
    text_color: str = 'black'
    text_background_color: str = 'white'
    head_text_color: str = 'white'
    show_cell_tooltips: bool = True
    head_background_color: str = 'dark blue'
    input_text_color: str = 'blue'
    input_text_background_color: str = 'white'
    selected_text_color: str = 'dark gray'
    selected_background_color: str = 'blue'
    combox_button_arrow_color: str = "blue"
    combox_button_background_color: str = "white"
    column_cell_edit_type_map: list = None
    v_slider_resolution: float = 0.1
    h_slider_resolution: float = 0.1
    slider_every_row_column: bool = True
    v_slider_show: bool = True
    h_slider_show: bool = True
    relief: str = Sg.RELIEF_FLAT
    cell_editable: bool = True
    slider_relief: str = Sg.RELIEF_FLAT
    disable_slider_number_display: bool = False
    datachangeeventtrigger: bool = False
    showcolnamewithid: bool = False
    showcolumnname: bool = True
    display_row_numbers: bool = True
    enable_events: bool = False
    enable_click_events: bool = False

    """
    表格编辑框类， 该类集成了SpecialTable，事件有两种
    1、（tablegroupkey, talblegroupkey) 该元组事件代表tab相关的事件
    2、（tablegroupkey，x， y) | (tablegroupkey , EDIT|.....) 都是父类的表格相关事件
    3、需要重载父类的eventprocess 对1、2分别处理
    4、 update和get直接对应父类的 tablegroupkey

    :param tablegroupkey:           Used with window.find_element and with return values to uniquely identify
                                    this element to uniquely identify this element
    :type tablegroupkey:            str | int | tuple | object
    :param specialdflist:           SpecialDf list, 用于显示的数据 SpecialDf 格式，优先使用
    :param dflist:                  pd.DataFrame list, 用于显示的数据
    :param tabname                  str: tab的标签前缀
    :param tabledatadictlist:        dict格式的数据，dfshowdata数据为None时，使用tabledatadict的数据
    :param itemeditfunc:             单条记录编辑时用到的界面处理函数，输入参数需要用字典方式带入。以适应可变参数
    :param itemeditfuncargsdic:      dict:  itemeditfunc函数的参数，row编辑函数，
    :param itemeditusecolvaluescale:    bool: True, itemeditfunc 需要将col_value_scale传递过去，
    :param indexcol:                  索引列序号（从0开始,目前只支持单列索引， None为没有），
                                      如果有索引列，会根据索引列判断是否有重复值并强制提醒
                                      None|int|[int,int...]
    :param visible_column_map:        是否显示该栏，
    :type visible_column_map:         List[bool]
    :param def_col_width:              默认列宽
    :param auto_size_columns:          列宽根据初始化自适应
    :param max_col_width：              最大列宽
    :param col_widths:                该栏显示宽度
    :type col_widths:                  List[int]
    :param size                        tuple(int,int) , 整个lay的尺寸
    :param size                        tuple(int,int), 表格单元的默认尺寸
    :param col_value_scale:            list , None or [[], [], None, ......]
                                       None: 没有任何限制，全部列都可以修改
                                      （二维list， 对应每列的取值范围，用于列快速填充）
                                       [None]:该列不允许批量编辑
                                       []该列任意取值范围
                                       [a,b,c]不为空，该列取值限制范围
    :param use_inner_right_click_menu       bool: True, 显示并内部右键菜单
    :param filter_visible:             bool: True, 显示筛选功能，False：关闭筛选功能
    :param filter_set_value_visible:   bool: True, 显示列赋值功能，False：关闭列赋值功能
    :param edit_button_visible:        bool: True, 各种编辑按钮，False：关闭各种编辑按钮，
    :param move_row_button_visible:    bool: True, 各种移动行按钮，False：关闭各种移动行按钮，
    :param right_click_menu:           A list of lists of Menu items to show when this element is right clicked.
    :type right_click_menu:            List[List[ List[str] | str ]]
    :param vertical_scroll_only:       if True only the vertical scrollbar will be visible
    :param select_mode:                Valid values include:
                                        TABLE_SELECT_MODE_NONE          不能选择
                                        TABLE_SELECT_MODE_BROWSE        只能单选
                                        TABLE_SELECT_MODE_EXTENDED      可以多选
                                        TABLE_LISTBOX_SELECT_MODE_SINGLE      增加一个选项 (点击后不取消）
    :param font                        窗口中使用的字体大小
    :param pad                         窗口中元素间的pad
    :param max_rows:                   最大表行数
    :param max_columns:                最大表列数
    :param expand_x:                   横向自动扩展
    :param expand_y:                   纵向自动扩展
    :param show_cell_tooltips:         cell方式下显示tooltips
    :param text_color:                 表格文本颜色
    :param text_background_color:      表格文本背景色
    :param border_width:                 表格边界线宽度
    :param head_text_color:             标题及序号文本颜色
    :param head_background_color:       标题及序号背景色
    :param selected_text_color:         选中文本颜色
    :param selected_background_color:   选中文本背景色
    :param combox_button_arrow_color:   cell为combox时，下拉箭头的颜色
    :param combox_button_background_color:  cell为combox时，下拉箭头的背景色
    :param input_text_color:            可编辑框的文本颜色
    :param input_text_background_color:  可编辑框的背景颜色
    :param column_cell_edit_type_map:       None, 所有单元格使用默认Sg.Input,
                                        list: 长度和表格数据的列数一致（该种方式下，所有列在一个max_column 页内显示，
                                        即表格页不要横行左右翻转，
                                        SPECIAL_TABLE_CELL_INPUT, SPECIAL_TABLE_CELL_COMBOX, SPECIAL_TABLE_CELL_TEXT
    :param relief:                      cell的relief
    :param cell_editable:               表格单元是否可以直接编辑
    :param slider_relief:               slider relief  style.Use constants -
                    RELIEF_RAISED  RELIEF_SUNKEN  RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID
    :param v_slider_resolution:         v  slider分辨率，默认0.1
    :param h_slider_resolution:         h slider 分辨率， 默认0.1
    :param slider_every_row_column:    是否可以逐行、逐列滚动
    :param v_slider_show:               bool, 是否显示垂直滚动条
    :param h_slider_show:               bool, 是否显示水平滚动条
    :param disable_slider_number_display:          bool: True 不显示， False：显示
    :param datachangeeventtrigger      bool: 数据修改后是否向主窗口发送数据变化事件
    :param showcolnamewithid           bool: True, 显示列号， False：不显示列号
    :param showcolumnname              bool: True, 显示列名， False：显示列名
    :param display_row_numbers                bool: True, 显示序号列， False：不显示
    :param enable_events:           Turns on the element specific events. Table events happen when row is clicked
    :type enable_events:            (bool)
    :param enable_click_events:     (NO USE) Turns on the element click events that will give you (row, col) click data when the table is clicked
    :type enable_click_events:      (bool)
    :return:                           用于Sg.window的页面layout list
    """

    def __post_init__(self):
        self.enable_events = False
        self.currenttable = None
        self.tabledynamicinfo = {}
        if self.specialdflist is None or self.specialdflist == []:
            if self.dflist is not None and self.dflist != []:
                self.specialdflist = [SpecialDf(x) for x in self.dflist]
        else:
            self.dflist = [x.dfdata for x in self.specialdflist]
        super().__init__(
            tablekey=self.tablegroupkey,
            specialdf=None if self.specialdflist is None else None
            if len(self.specialdflist) == 0 else self.specialdflist[0],
            dfshowdata=None if self.dflist is None else None
            if len(self.dflist) == 0 else self.dflist[0],
            tabledatadictlist=self.tabledatadictlist,
            itemeditfunc=self.itemeditfunc,
            itemeditfuncargsdic=self.itemeditfuncargsdic,
            itemeditusecolvaluescale=self.itemeditusecolvaluescale,
            indexcol=self.indexcol,
            visible_column_map=self.visible_column_map,
            def_col_width=self.def_col_width,
            auto_size_columns=self.auto_size_columns,
            max_col_width=self.max_col_width,
            cell_justification=self.cell_justification,
            col_widths=self.col_widths,
            size=self.size,
            cell_size=self.cell_size,
            scrollable=self.scrollable,
            # col_value_scale=self.col_value_scale,
            col_value_scale=None,
            filter_visible=self.filter_visible,
            edit_button_visible=self.edit_button_visible,
            filter_set_value_visible=self.filter_set_value_visible,
            border_width=self.border_width,
            use_inner_right_click_menu=self.use_inner_right_click_menu,
            move_row_button_visible=self.move_row_button_visible,
            visible=self.visible,
            headings=self.headings,
            right_click_menu=self.right_click_menu,
            vertical_scroll_only=self.vertical_scroll_only,
            expand_x=self.expand_x,
            expand_y=self.expand_y,
            select_mode=self.select_mode,
            font=self.font,
            pad=self.pad,
            max_rows=self.max_rows,
            max_columns=self.max_columns,
            text_color=self.text_color,
            text_background_color=self.text_background_color,
            head_text_color=self.head_text_color,
            show_cell_tooltips=self.show_cell_tooltips,
            head_background_color=self.head_background_color,
            input_text_color=self.input_text_color,
            input_text_background_color=self.input_text_background_color,
            selected_text_color=self.selected_text_color,
            selected_background_color=self.selected_background_color,
            combox_button_arrow_color=self.combox_button_arrow_color,
            combox_button_background_color=self.combox_button_background_color,
            column_cell_edit_type_map=self.column_cell_edit_type_map,
            v_slider_resolution=self.v_slider_resolution,
            h_slider_resolution=self.h_slider_resolution,
            slider_every_row_column=self.slider_every_row_column,
            v_slider_show=self.v_slider_show,
            h_slider_show=self.h_slider_show,
            relief=self.relief,
            cell_editable=self.cell_editable,
            slider_relief=self.slider_relief,
            disable_slider_number_display=self.disable_slider_number_display,
            datachangeeventtrigger=self.datachangeeventtrigger,
            showcolnamewithid=self.showcolnamewithid,
            showcolumnname=self.showcolumnname,
            display_row_numbers=self.display_row_numbers,
            enable_events=self.enable_events,
            enable_click_events=self.enable_click_events
        )
        self._dftabgroup = []
        # 1、先初始化tablelayout，然后叠加tab类后返回整个layout
        # SpecailTable 总长度和表格实际显示长度的差异，需要根据以下的设置确定 :
        # filter_visible, filter_set_value_visible, edit_button_visible,  move_row_button_visible,
        # 四项设置的对应关系
        # 保存tabgroup的每个tab的layout以便后续处理
        if self.specialdflist is None:
            self.specialdflist = []
        # 针对表数建立标签组， 并将第一张表作为当前显示表格， 标签都无实际内容，使用空格[]
        # 标签的key采用（tablegroupkey, i)，
        # 当标签组产生（tablegroupkey, tablegroupkey)事件后，
        # 获取具体标签值为（tablegroupkey, i)，根据i确定切换的表specialdflist序号
        for i in range(MAX_TABLE_NUMBER):
            self._dftabgroup.append(Sg.Tab("{}[{}]".format(self.tabname, i), [],
                                           tooltip=self.specialdflist[i] if i < len(self.specialdflist) else "",
                                           visible=True if i < len(self.specialdflist) else False,
                                           k=(self.tablegroupkey, i)))
        self.currenttable = (self.tablegroupkey, 0) if len(self.specialdflist) > 0 else None

        # 形成有tab的整体layout,
        self._grouplayout = [[Sg.Frame("", [
            [Sg.TabGroup(
                [self._dftabgroup], k=(self.tablegroupkey, self.tablegroupkey), expand_x=False, expand_y=False,
                size=(self.size[0], 5), visible=self.visible, enable_events=True)],
            [Sg.Frame("", self._tablelayout, size=self.size, border_width=0)]
        ])]]

    @property
    def layout(self):
        return self._grouplayout

    def tabgroupeventprocess(self, event, window, values):
        """
        目前切换恢复页面位置，暂未考虑动态切换表格时，保留过滤项，
        :param event:
        :param window:
        :param values:
        :return:
        """
        # 获取当前点击标签，如果和上次不同，需要切换表格， 如果相同，不需要处理
        currenttable = values[event]

        # 保存现有表格的动态信息，以备恢复
        # 先判断要刷新的表格是否已经没有
        if len(self.specialdflist) == 0:
            self.currenttable = None
            # 没有任何可以显示的表格，用空值表格作为显示底图
            self.setwholevalue(currentwindow=window, dfdata=pd.DataFrame([], columns=[]))
            self.refreshcurrentshow(currentwindow=window)
            self.refreshwholegroup(event, window, values)
            return

        # 切换前，将必要的动态数据保存到SpecialDf基类中
        if self.currenttable is not None:
            # 备份当前表的动态显示项
            # 进度条位置
            self.specialdf.v_slider_position, self.specialdf.h_slider_position = \
                values[self.V_SLIDER_KEY], values[self.H_SLIDER_KEY]
            # 保留过滤条件
            if self.filter_visible:     # self.filter_visible is True, 相关的elements才会存在
                self.specialdf.filter_startrow, self.specialdf.filter_endrow, \
                self.specialdf.filter_filter_column, self.specialdf.filter_filter_like, \
                self.specialdf.filter_set_value_column, self.specialdf.filter_set_column_used_value = \
                    values[self.ROW_START], values[self.ROW_END], values[self.COLUMNFILTER], \
                    values[self.CONTENTLIKE], values[self.COLCHOOSE], values[self.COL_BAT_VALUE]
            # print("current {} dynamic info backup：{} ".format(self.currenttable, self.specialdf))

        # 开始切换
        self.currenttable = currenttable
        # 切换表格数据到新的选择
        self.specialdf = self.specialdflist[self.currenttable[1]]

        # 重置选中的标签，因为事件可能是点击产生，也可能是外部程序write_event_value
        window[self.currenttable].select()  #
        # 重新设置tooltip
        window[self.currenttable].set_tooltip("{}{}{}{}{}".format(
                "[输入] " if self.specialdf.dataimportsource != "" else "",
                self.specialdf.dataimportsource,
                " -> " if self.specialdf.dataimportsource != "" and self.specialdf.dataexporttarget != ""
                else "",
                "[输出] " if self.specialdf.dataexporttarget != "" else "",
                self.specialdf.dataexporttarget))
        # 恢复备份的当前表格的动态信息
        # print("new table {} using self backup info and specialdf: {}".format(self.currenttable, self.specialdf))
        window[self.V_SLIDER_KEY].update(value=self.specialdf.v_slider_position)
        window.write_event_value(self.V_SLIDER_KEY,  self.specialdf.v_slider_position)
        window[self.H_SLIDER_KEY].update(value=self.specialdf.h_slider_position)
        window.write_event_value(self.H_SLIDER_KEY, self.specialdf.h_slider_position)
        if self.filter_visible:
            # 恢复过滤项开始、结束行号等选择项
            rowlist = [""] + [str(a) for a in range(0, self.specialdf.maxrownumber)]
            window[self.ROW_START].update(values=rowlist, value=self.specialdf.filter_startrow)
            window[self.ROW_END].update(values=rowlist, value=self.specialdf.filter_endrow)
            window[self.COLUMNFILTER].update(
                values=self.specialdf.colnamefilterlist, value=self.specialdf.filter_filter_column)
            window[self.CONTENTLIKE].update(value=self.specialdf.filter_filter_like)
            window[self.COLCHOOSE].update(
                values=self.specialdf.colnameusedlist, value=self.specialdf.filter_set_value_column)
            window[self.COL_BAT_VALUE].update(value=self.specialdf.filter_set_column_used_value)
        self.refreshcurrentshow(currentwindow=window)
        self.resetselected(event, window, values)
        if self.specialdf.infilterstatus:
            # 如果条件不是全为空，
            window.write_event_value(self.FILTERCONDITIONOK, None)
        window.refresh()

    def refreshwholegroup(self, event, window, values):
        """
        根据当前specialdflist，刷新tablegroup的显示
        :param event:
        :param window:
        :param values:
        :return:
        """

        # 判断当前数据表格数是否大于系统设置的最大表数，如果是，提醒用户后自动增加，并建议用户扩大系统配置
        if len(self.specialdflist) > len(self._dftabgroup):
            Sg.popup_ok(
                "当前表格数{}将超过系统最大配置显示数{}，将临时动态增加，建议在系统配置中扩大！".format(
                    len(self.specialdflist), MAX_TABLE_NUMBER),
                keep_on_top=True, modal=True, icon=SYSTEM_ICON)
            for i in range(len(self._dftabgroup),
                           len(self._dftabgroup) + (len(self.specialdflist) - len(self._dftabgroup)) + 5):
                self._dftabgroup.append(Sg.Tab("{}[{}]".format(self.tabname, i), [],
                                               tooltip=self.specialdflist[i].dataimportsource
                                               if i < len(self.specialdflist) else "",
                                               visible=True if i < len(self.specialdflist) else False,
                                               k=(self.tablegroupkey, i)))
                window[(self.tablegroupkey, self.tablegroupkey)].add_tab(self._dftabgroup[-1])
        # 关闭大于specialdflist的表格显示以及tab的tooltip
        for i in range(len(self.specialdflist), len(self._dftabgroup)):
            window[(self.tablegroupkey, i)].update(visible=False)
            window[(self.tablegroupkey, i)].set_tooltip(None)
        for i in range(len(self.specialdflist)):
            window[(self.tablegroupkey, i)].update(visible=True)
            window[(self.tablegroupkey, i)].set_tooltip("{}{}{}{}{}".format(
                "[输入] " if self.specialdflist[i].dataimportsource != "" else "",
                self.specialdflist[i].dataimportsource,
                " -> " if self.specialdflist[i].dataimportsource != "" and self.specialdflist[i].dataexporttarget != ""
                else "",
                "[输入] " if self.specialdflist[i].dataexporttarget != "" else "",
                self.specialdflist[i].dataexporttarget))

        # 全面刷新后回到第0表
        if len(self.specialdflist) > 0:
            window[(self.tablegroupkey, 0)].select()  # 选择0
            # 当values没有值，说明不是内部处理，直接外部事件触发，values有值，说明是内部调用，不菜触发外部事件
            if values is None:
                window.write_event_value((self.tablegroupkey, self.tablegroupkey), (self.tablegroupkey, 0))
            else:
                values[(self.tablegroupkey, self.tablegroupkey)] = (self.tablegroupkey, 0)
                self.tabgroupeventprocess((self.tablegroupkey, self.tablegroupkey), window, values)

    def tiggertabclicked(self, event, window, values, triggertable=None):
        """

        :param window:
        :param values:
        :param triggertable:
        :return:
        """
        # 确定要激活的tab号, 没有指定表格如果表0存在就激活表0, 否则直接返回
        if len(self.specialdflist) == 0:
            print("表格为空，没有可以激活的表格！")
            return
        localtable = 0 if triggertable is None else 0 if triggertable >= len(self.specialdflist) else triggertable
        # 不采用模拟发送事件，因为会回到主程序，直接调用事件处理函数
        # window.write_event_value((self.tablegroupkey, self.tablegroupkey), (self.tablegroupkey, localtable))
        # simulate values
        values[(self.tablegroupkey, self.tablegroupkey)] = (self.tablegroupkey, localtable)
        self.tabgroupeventprocess((self.tablegroupkey, self.tablegroupkey), window, values)

    def eventprocess(self, event, window, values):
        # 判断是否点击tablegroup标签事件，重载父类方法，对于父类事件调用父类方法
        if event == (self.tablegroupkey, self.tablegroupkey):
            self.tabgroupeventprocess(event, window, values)
        else:  # 父类表格事件
            # print("tabgroup got table event: {}, currenttable:{}".format(event, self.currenttable))
            super().eventprocess(event, window, values)

    def update(self, commandtype=None, currentevent=None, currentwindow=None,
               currentvalues=None, triggertable=None, **kwargs):
        """

        :param commandtype:
        :param currentevent:
        :param currentwindow:
        :param currentvalues:
        :param triggertable:                SPECIAL_TABLE_GROUP_TRIGGER_TAB 激活得表号
        :param kwargs:
        :return:
        """
        # 分离出父类要处理的事件，目前仅fresh
        if commandtype in [SPECIAL_TABLE_GROUP_REFRESH, SPECIAL_TABLE_GROUP_TRIGGER_TAB]:
            if commandtype == SPECIAL_TABLE_GROUP_REFRESH:  # 刷新整个group
                self.refreshwholegroup(currentevent, currentwindow, currentvalues)
                return True
            elif commandtype == SPECIAL_TABLE_GROUP_TRIGGER_TAB:  # 触发点击group某张表格，没有指定表格如果表0存在就激活表0
                self.tiggertabclicked(currentevent, currentwindow, currentvalues, triggertable)
                return True
        else:
            result = super().update(commandtype=commandtype, currentevent=currentevent,
                                    currentwindow=currentwindow, currentvalues=currentvalues, **kwargs)

        # 对于子类处理后，父类需要善后的事件，单独处理
        if result is not False:
            if commandtype in [SPECIAL_TABLE_SET_LOADFROMFILE, SPECIAL_TABLE_SET_LOADFROMFILE]:
                self.refreshwholegroup(currentevent, currentwindow, currentvalues)
            return True
        else:
            return False

    def get(self, attribute=None, **kwargs):
        """
        如果没有命令，默认返回当前表号（类似TabGroup），其他情况传递给父类
        :param attribute:
        :param kwargs:
        :return:
        """
        if attribute is None:
            return self.currenttable
        else:
            return super().get(attribute=attribute, **kwargs)


# 根据系统配置拷贝相应的数据文件和配置文件到本地目录
def sys_data_files_copy(sysdatafiles: list) -> list:
    for file in sysdatafiles:
        result = sys_config_copy(file)
        if result[0] is False:
            return result
    return [True, ""]


# 系统初始化，判断fileprocconfig是否存在，如果不存在，尝试从系统config目录（pip，python的安装目录拷贝），系统目录也不存在则报错退出
# 判断本地目录是否存在fileprocconfig
def sys_config_copy(localconfigfile: str) -> list:
    if Path(localconfigfile).is_file() is False:
        try:
            # 组合系统配置目录对应文件
            sysfileprocconfig = os.path.join(sys.prefix, localconfigfile)
            if Path(sysfileprocconfig).is_file() is False:
                print("本地配置文件：{}及系统原始配置文件{} 均不存在！".format(localconfigfile, sysfileprocconfig))
                sys.exit(-1)
            else:
                # 判断本地配置目录是否存在，如果不存在则创建目录，逐层创建
                localdir = os.path.dirname(localconfigfile)
                if Path(localdir).is_dir() is False:
                    os.makedirs(localdir)
                # 拷贝系统配置文件到本地
                shutil.copy(sysfileprocconfig, localconfigfile)
        except Exception as err:
            return [False, "拷贝系统原始配置到本地配置文件：{} 失败：{}".format(localconfigfile, err)]

    return [True, ""]


def main_test():
    from PySimpleGUI import THEME_CLAM, THEME_DEFAULT, THEME_ALT, THEME_CLASSIC
    Sg.set_options(ttk_theme=THEME_DEFAULT)
    dfdata1 = pd.DataFrame(
        ["数据{}-{}".format(x, y) for y in range(MAX_SPECIAL_TABLE_COLUMNS * 2)]
        for x in range(MAX_SPECIAL_TABLE_ROWS * MAX_SPECIAL_TABLE_ROWS - 10))
    dfdata2 = pd.DataFrame(
        ["测试{}-{}".format(x, y) for y in range(MAX_SPECIAL_TABLE_COLUMNS * 2)]
        for x in range(MAX_SPECIAL_TABLE_ROWS * MAX_SPECIAL_TABLE_ROWS - 10))
    dfdata3 = pd.DataFrame(
        ["新数据{}-{}".format(x, y) for y in range(MAX_SPECIAL_TABLE_COLUMNS * 2)]
        for x in range(MAX_SPECIAL_TABLE_ROWS * MAX_SPECIAL_TABLE_ROWS - 10))
    test_parminfo = [
    ]
    test_column_widths = [30, 1, 8, 3, 16, 40, 1, 6, 4, 4]
    test_visible_column_map = [True, False, True, True, True, True, False, True, True, True]
    test_columninfodic = {
        '命令': 'exe_unit',
        '账号需求': 'need_password',
        '账号类型 ': 'account_type',
        '选项': 'cmd_option',
        '项目描述': 'item_name',
        '默认文件': 'file_name',
        '文件类型': 'file_type_info',
        '文件后缀': 'file_type',
        '读/写': 'save_mode',
        '可更改': 'changable'
    }
    test_accounttypedic = {
        "JDE": {
            "default_account": "f9b531805c280b9f61e0dfdd8e69c2fd",
            "account_password": {
            }
        },
        "ERP": {
            "default_account": "f9b531805c280b9f61e0dfdd8e69c2fd",
            "account_password": {}
        },
        "EAS": {
            "default_account": "45d2a8efdea386c3b3f400c5961cf84a",
            "account_password": {}
        }
    }
    test_errdefinedic = {
        "101": "帐号为空",
        "102": "密码为空",
        "103": "密码错误",
        "104": "未知错误",
        "201": "命令所需文件不全",
        "202": "读取文件失败",
        "203": "文件格式不匹配",
        "204": "写入文件错误",
        "205": "未选择账号"
    }
    test_functioncalldic = {
        "无": "",
    }
    test_colvaluescale = [
        [],  # 命令
        ['是', '否'],  # 是否需要账号运行
        [a for a in test_accounttypedic.keys()],  # 账号类型
        [''] + ['-' + chr(i) for i in range(ord('a'), ord('z') + 1)],  # 命令选项
        [],  # 项目描述
        [],  # 默认文件
        None,  # 文件类型,与文件后缀需要互动， 因此不允许批量更改
        None,  # 文件后缀,与文件类型需要互动， 因此不允许批量更改
        ['读', '写'],  # 文件为读还是写
        ['是', '否']  # 是否可更改
    ]
    dfdata4 = pd.DataFrame([["1", "2", "3"], ["4", "5", "6"], ['7', "8", "9"]], columns=list('abc'))
    dfdata5 = pd.DataFrame([], columns=list("ABC"))
    layoutstatic = []
    tempfuncargs = {
        "accounttypedic": test_accounttypedic,
        "functioncalldic": test_functioncalldic,
    }
    commandtypelist = ["本地文件处理", 'RPA处理', '测试文件处理']
    colvaluesecale = [
        None,  # 菜单名称，索引列，建议不要批处理修改
        None,
        None,
        None,
        None,
        None,
        [],  # 菜单配置文件
        ['是', '否'],
        commandtypelist,  # 命令类型
        []  # 图标文件
    ]

    # 拷贝系统文件
    result = sys_data_files_copy(SYSTEM_DATA_FILES)
    if result[0] is False:
        print(result[1])
        sys.exit(-1)

    tablegroupkey = '_table_group_'
    headings1 = ["数据列{}".format(x) for x in range(MAX_SPECIAL_TABLE_COLUMNS * 2)]
    headings2 = ["测试列{}".format(x) for x in range(MAX_SPECIAL_TABLE_COLUMNS * 2)]
    headings3 = ["新数据列{}".format(x) for x in range(MAX_SPECIAL_TABLE_COLUMNS * 2)]
    dfdata1.columns = headings1
    dfdata2.columns = headings2
    dfdata3.columns = headings3
    tablemenu = ["测试菜单", ['test1', 'test2']]
    visible_column_map = [True, True, True, True, True, True, True, True, True, True]
    # visible_column_map = [True, False, False, False, False, False, False, False, False, False]
    col_widths = [16, 16, 24, 24, 15, 18, 18, 20, 20, 20]
    column_cell_edit_type_map = [
        SPECIAL_TABLE_CELL_INPUT,
        SPECIAL_TABLE_CELL_INPUT,
        SPECIAL_TABLE_CELL_INPUT,
        SPECIAL_TABLE_CELL_INPUT,
        SPECIAL_TABLE_CELL_INPUT,
        SPECIAL_TABLE_CELL_INPUT,
        SPECIAL_TABLE_CELL_INPUT,
        SPECIAL_TABLE_CELL_COMBOX,
        SPECIAL_TABLE_CELL_COMBOX,
        SPECIAL_TABLE_CELL_INPUT,
    ]
    rightclickmenu = [
        ["单表格操作", ["行操作", ["删除行::('table0', 1, -1)", "添加行", "行求和"], "行操作1", ["删除行", "添加行", "行求和"]]],
        ["列操作", ["删除列", "和并列", "列和"]],
        ["多表格操作", ["表格融合", "纵向拼接", "横向拼接"]],
        ["格式化输出"]]
    # RELIEF_RAISED  RELIEF_SUNKEN  RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID
    dflist = [dfdata1, dfdata2, dfdata3]
    specialdflist = [SpecialDf(dfdata=x) for x in dflist]
    layouttable = SpecialTableGroup(
        tablegroupkey,
        specialdflist=specialdflist,
        # dflist=dflist,
        # dflist=None,
        tabname="测试表",
        max_rows=19,
        max_columns=11,
        enable_events=True,
        display_row_numbers=True,
        showcolnamewithid=True,
        showcolumnname=True,
        combox_button_arrow_color='blue',
        combox_button_background_color='white',
        use_inner_right_click_menu=True,
        input_text_color='blue',
        input_text_background_color='white',
        # tabledatadictlist=test_parminfo,
        # itemeditfunc=cmd_page_config_item_edit,
        itemeditfunc=None,
        itemeditfuncargsdic=tempfuncargs,
        v_slider_resolution=0.1,
        h_slider_resolution=0.1,
        slider_every_row_column=True,
        col_widths=col_widths,
        pad=(0, 0),
        border_width=1,
        text_color="blue",
        scrollable=False,
        size=(1400, 400),
        # col_widths=test_column_widths,
        # headings=headings,
        slider_relief=Sg.RELIEF_FLAT,
        v_slider_show=True,
        h_slider_show=True,
        relief=Sg.RELIEF_SUNKEN,
        disable_slider_number_display=True,
        # select_mode=SPECIAL_TABLE_LISTBOX_SELECT_MODE_SINGLE,
        # select_mode=SPECIAL_TABLE_SELECT_MODE_BROWSE,
        # select_mode=SPECIAL_TABLE_SELECT_MODE_NONE,
        select_mode=SPECIAL_TABLE_SELECT_MODE_EXTENDED,
        cell_editable=True,
        edit_button_visible=True,
        move_row_button_visible=True,
        indexcol=[0, 1],
        filter_visible=True,
        filter_set_value_visible=False,
        # visible_column_map=visible_column_map,
        # right_click_menu=rightclickmenu,
        column_cell_edit_type_map=column_cell_edit_type_map,
        col_value_scale=None,
    ).layout

    layouttail = [
        [
            Sg.Text("".rjust(20)),
            Sg.Button('保存&退出', k='_save_', pad=(10, 10)),
            Sg.Text("".rjust(20)),
            Sg.Button('换数据', k='_change_', pad=(10, 10)),
            Sg.Text("".rjust(20)),
            Sg.Button('导入数据', k='_loaddata_', pad=(10, 10)),
            Sg.Text("".rjust(20)),
            Sg.Button(' 关  闭 ', k='_close_', pad=(10, 20)),
        ]
    ]
    # 拼接页面配置
    layout = layoutstatic + layouttable + layouttail

    mainwindow = Sg.Window('SPECIAL TABLE 功能测试',
                           layout,
                           font=SPECIAL_TABLE_DEF_FT,
                           icon=SYSTEM_ICON,
                           # return_keyboard_events=True,
                           resizable=False,
                           size=(1500, 660),
                           modal=True,
                           # keep_on_top=True,
                           finalize=True)

    # 初始化mousewheel updown相关的环境
    mainwindow[tablegroupkey].update(commandtype=SPECIAL_TABLE_SET_BIND, currentwindow=mainwindow)
    mainwindow[tablegroupkey].update(commandtype=SPECIAL_TABLE_SET_ADD_NEWROW, currentwindow=mainwindow,
                                     newrowcontent=["addrowtest{}".format(x) for x in range(MAX_SPECIAL_TABLE_COLUMNS)])

    while True:
        event, values = mainwindow.read()
        # print("getevent:{}".format(event))
        # --- Process buttons --- #
        if event in (Sg.WIN_CLOSED, '_close_'):
            break
        elif event == '_loaddata_':
            mainwindow[tablegroupkey].update(commandtype=SPECIAL_TABLE_SET_LOADFROMFILE,
                                             currentwindow=mainwindow,
                                             currentevent=event,
                                             filename="d:/temp/aaa.xlsx",
                                             headline=0,
                                             tailline=0,
                                             sheetnumber=0,
                                             headname=0)

        elif event == '_change_':
            specialdflist.pop(0)
            dflist.pop(0)
            mainwindow[tablegroupkey].update(
                commandtype=SPECIAL_TABLE_GROUP_REFRESH,
                currentevent=event, currentwindow=mainwindow, currentvalues=values)
            # 刷新tabgroup

        elif isinstance(event, tuple):
            # 兼容Sg.table和Special element方式, 已经不用考虑
            # 以及鼠标wheel处理, 目前 （tablegroupkey,x, y), "mousewheel"
            currenttable = event[0][0] if isinstance(event[0], tuple) else event[0]

            mainwindow[currenttable].update(
                commandtype=SPECIAL_TABLE_TUPLE_EVENTPROCESS,
                currentevent=event, currentwindow=mainwindow, currentvalues=values)
            continue
        elif event == "_save_":
            print(mainwindow[tablegroupkey].get(SPECIAL_TABLE_GET_RESULT_DICT))
            continue
        elif event == tablegroupkey:
            mainwindow[tablegroupkey].eventprocess(event, mainwindow, values)
            continue

        elif event == tablegroupkey:
            print("table event:{}".format(tablegroupkey))
            print("values[tablegroupkey]: {}".format(values[tablegroupkey]))
            print("mainwindow[tablegroupkey].get(): {}".format(mainwindow[tablegroupkey].get()))
            print("mainwindow[tablegroupkey].get(): {}".format(
                mainwindow[tablegroupkey].get(attribute=SPECIAL_TABLE_GET_SELECTED_ROWCONTENT)))
        else:
            # 判断是否为innerrightmenu事件，如果是就触发该事件, 当窗口中有表格设置了 use_inner_right_click_menu，
            # 需要调用isinnerrightclickmenu 进行回送事件处理
            tempresult = isinnerrightclickmenu(mainwindow, event)
            if tempresult is False:
                print("configwindow unknow event:{}".format(event))
            continue

    mainwindow.close()


if __name__ == '__main__':
    main_test()

