├── .github └── FUNDING.yml ├── CTkTable ├── __init__.py └── ctktable.py ├── LICENSE └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: akascape 4 | 5 | -------------------------------------------------------------------------------- /CTkTable/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CustomTkinter Table widget 3 | Author: Akash Bora 4 | License: MIT 5 | This is a custom table widget for customtkinter. 6 | Homepage: https://github.com/Akascape/CTkTable 7 | """ 8 | 9 | __version__ = '1.1' 10 | 11 | from .ctktable import CTkTable 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Akash Bora 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTkTable 2 | 3 | **Here is a quick and simple table widget having all the basic features.** 4 | 5 | ![Screenshot](https://user-images.githubusercontent.com/89206401/233420929-bf210cb3-5b5f-49b2-ba7a-f01d187e72cf.jpg) 6 | 7 | ## Features: 8 | - Add columns/rows 9 | - Delete columns/rows 10 | - Edit rows/columns at once 11 | - Insert values to specific cell 12 | - delete values from specific cell 13 | - update all values at once 14 | - edit each cell value and options 15 | - entry editing 16 | - can be used with scrollable frame 17 | - Lots of other data operations 18 | 19 | ## Installation 20 | ``` 21 | pip install CTkTable 22 | ``` 23 | 24 | ### [GitHub repo size](https://github.com/Akascape/CTkTable/archive/refs/heads/main.zip) 25 | 26 | _Dont forget to leave a ⭐_ 27 | 28 | ## Usage 29 | ```python 30 | import customtkinter 31 | from CTkTable import * 32 | 33 | root = customtkinter.CTk() 34 | 35 | value = [[1,2,3,4,5], 36 | [1,2,3,4,5], 37 | [1,2,3,4,5], 38 | [1,2,3,4,5], 39 | [1,2,3,4,5]] 40 | 41 | table = CTkTable(master=root, row=5, column=5, values=value) 42 | table.pack(expand=True, fill="both", padx=20, pady=20) 43 | 44 | root.mainloop() 45 | ``` 46 | 47 | ## Methods 48 | - **.insert(row, column, value, *args)**: change specific cell index data 49 | - **.add_row(index, values)** 50 | - **.add_column(index, values)** 51 | - **.edit_row(row_num, *args)**: edit one full row at once 52 | - **.edit_column(column_num, *args)**: edit one full column at once 53 | - **.delete_row(index)**: remove one row 54 | - **.delete_column(index)**: remove one column 55 | - **.delete_rows(indices)**: remove mutliple rows 56 | - **.delete_columns(indices)**: remove multiple columns 57 | - **.edit(row, column)**: edit specific cell without changing the value 58 | - **.select(row, column)**: select one cell 59 | - **.select_row(row)**: select a row 60 | - **.get_selected_row()**: get the values of the selected row 61 | - **.deselect_row(row)**: deselect a row 62 | - **.select_column(column)**: select a column 63 | - **.get_selected_column()**: get the values of selected column 64 | - **.deselect_column(column)**: deselect a column 65 | - **.update_values(values)**: update all values at once 66 | - **.delete(row, column, *args)**: delete the data from specific index 67 | - **.get()**: get all values 68 | - **.get(row, column)**: get specific cell value 69 | - **.get_row(row)**: get all values of a specific row 70 | - **.get_column(column)**: get all values of a specific column 71 | - **.configure(arguments)**: change other table attributes 72 | 73 | _here, **args** means ctkbutton parameters which can also be passed_ 74 | 75 | **Note: treat all the table cells as a ctkbutton class** 76 | 77 | ## Arguments 78 | | Parameter | Description | 79 | |-----------| ------------| 80 | | **master** | parent widget | 81 | | **values** | the default values for table | 82 | | row | **optional**, set number of default rows | 83 | | column | **optional**, set number of default columns | 84 | | padx | add internal padding in x | 85 | | pady | add internal padding in y | 86 | | colors | set two fg_colors for the table (list), eg: `colors=["yellow", "green"]` | 87 | | color_phase | set color phase based on rows or columns, eg: `color_phase="vertical"` | 88 | | orientation | change the orientation of table, `vertical or horizontal` | 89 | | header_color | define the topmost row color | 90 | | corner_radius | define the corner roundness of the table | 91 | | hover_color | enable hover effect on the cells | 92 | | wraplength | set the width of cell text | 93 | | justify | anchor the position of the cell text | 94 | | **command** | specify a command when a table cell is pressed, [returns row, column, value] | 95 | | **other button parameters* | all other ctk button parameters can be passed | 96 | 97 | Note: This library is at early stage so there can be some performance issues. 98 | ### Thanks for visiting! Hope it will help :) 99 | -------------------------------------------------------------------------------- /CTkTable/ctktable.py: -------------------------------------------------------------------------------- 1 | # CTkTable Widget by Akascape 2 | # License: MIT 3 | # Author: Akash Bora 4 | 5 | import customtkinter 6 | import copy 7 | 8 | class CTkTable(customtkinter.CTkFrame): 9 | """ CTkTable Widget """ 10 | 11 | def __init__( 12 | self, 13 | master: any, 14 | row: int = None, 15 | column: int = None, 16 | padx: int = 1, 17 | pady: int = 0, 18 | width: int = 140, 19 | height: int = 28, 20 | values: list = None, 21 | colors: list = [None, None], 22 | orientation: str = "horizontal", 23 | color_phase: str = "horizontal", 24 | border_width: int = 0, 25 | text_color: str or tuple = None, 26 | border_color: str or tuple = None, 27 | font: tuple = None, 28 | header_color: str or tuple = None, 29 | corner_radius: int = 25, 30 | write: str = False, 31 | command = None, 32 | anchor: str = "c", 33 | hover_color: str or tuple = None, 34 | hover: bool = False, 35 | justify: str = "center", 36 | wraplength: int = 1000, 37 | **kwargs): 38 | 39 | super().__init__(master, fg_color="transparent") 40 | 41 | if values is None: 42 | values = [[None,None],[None,None]] 43 | 44 | self.master = master # parent widget 45 | self.rows = row if row else len(values) # number of default rows 46 | self.columns = column if column else len(values[0])# number of default columns 47 | self.width = width 48 | self.height = height 49 | self.padx = padx # internal padding between the rows/columns 50 | self.pady = pady 51 | self.command = command 52 | self.values = values # the default values of the table 53 | self.colors = colors # colors of the table if required 54 | self.header_color = header_color # specify the topmost row color 55 | self.phase = color_phase 56 | self.corner = corner_radius 57 | self.write = write 58 | self.justify = justify 59 | self.binded_objects = [] 60 | 61 | if self.write: 62 | border_width = border_width=+1 63 | 64 | if hover_color is not None and hover is False: 65 | hover=True 66 | 67 | self.anchor = anchor 68 | self.wraplength = wraplength 69 | self.hover = hover 70 | self.border_width = border_width 71 | self.hover_color = customtkinter.ThemeManager.theme["CTkButton"]["hover_color"] if hover_color is None else hover_color 72 | self.orient = orientation 73 | self.border_color = customtkinter.ThemeManager.theme["CTkButton"]["border_color"] if border_color is None else border_color 74 | self.inside_frame = customtkinter.CTkFrame(self, border_width=0, fg_color="transparent") 75 | super().configure(border_color=self.border_color, border_width=self.border_width, corner_radius=self.corner) 76 | self.inside_frame.pack(expand=True, fill="both", padx=self.border_width, pady=self.border_width) 77 | 78 | self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color 79 | self.font = font 80 | # if colors are None then use the default frame colors: 81 | self.data = {} 82 | self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if not self.colors[0] else self.colors[0] 83 | self.fg_color2 = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if not self.colors[1] else self.colors[1] 84 | 85 | if self.colors[0] is None and self.colors[1] is None: 86 | if self.fg_color==self.master.cget("fg_color"): 87 | self.fg_color = customtkinter.ThemeManager.theme["CTk"]["fg_color"] 88 | if self.fg_color2==self.master.cget("fg_color"): 89 | self.fg_color2 = customtkinter.ThemeManager.theme["CTk"]["fg_color"] 90 | 91 | self.frame = {} 92 | self.corner_buttons = {} 93 | self.draw_table(**kwargs) 94 | 95 | def draw_table(self, **kwargs): 96 | 97 | """ draw the table """ 98 | for i in range(self.rows): 99 | for j in range(self.columns): 100 | self.inside_frame.grid_rowconfigure(i, weight=1) 101 | self.inside_frame.grid_columnconfigure(j, weight=1) 102 | if self.phase=="horizontal": 103 | if i%2==0: 104 | fg = self.fg_color 105 | else: 106 | fg = self.fg_color2 107 | else: 108 | if j%2==0: 109 | fg = self.fg_color 110 | else: 111 | fg = self.fg_color2 112 | 113 | if self.header_color: 114 | if self.orient=="horizontal": 115 | if i==0: 116 | fg = self.header_color 117 | else: 118 | if j==0: 119 | fg = self.header_color 120 | 121 | corner_radius = self.corner 122 | if (self.border_width>=5) and (self.corner>=5): 123 | tr = self.border_color 124 | else: 125 | tr = "" 126 | if i==0 and j==0: 127 | corners = [tr, fg, fg, fg] 128 | hover_modify = self.hover 129 | 130 | elif i==self.rows-1 and j==self.columns-1: 131 | corners = [fg ,fg, tr, fg] 132 | hover_modify = self.hover 133 | 134 | elif i==self.rows-1 and j==0: 135 | corners = [fg ,fg, fg, tr] 136 | hover_modify = self.hover 137 | 138 | elif i==0 and j==self.columns-1: 139 | corners = [fg, tr, fg, fg] 140 | hover_modify = self.hover 141 | 142 | else: 143 | corners = [fg, fg, fg, fg] 144 | corner_radius = 0 145 | hover_modify = False 146 | 147 | if i==0: 148 | pady = (0, self.pady) 149 | else: 150 | pady = self.pady 151 | 152 | if j==0: 153 | padx = (0, self.padx) 154 | else: 155 | padx = self.padx 156 | 157 | if i==self.rows-1: 158 | pady = (self.pady,0) 159 | 160 | if j==self.columns-1: 161 | padx = (self.padx,0) 162 | 163 | if self.values: 164 | try: 165 | if self.orient=="horizontal": 166 | value = self.values[i][j] 167 | else: 168 | value = self.values[j][i] 169 | except IndexError: value = " " 170 | else: 171 | value = " " 172 | 173 | if value=="": 174 | value = " " 175 | 176 | if (i,j) in self.data.keys(): 177 | if self.data[i,j]["args"]: 178 | args = self.data[i,j]["args"] 179 | else: 180 | args = copy.deepcopy(kwargs) 181 | else: 182 | args = copy.deepcopy(kwargs) 183 | 184 | 185 | self.data[i,j] = {"row": i, "column" : j, "value" : value, "args": args} 186 | 187 | args = self.data[i,j]["args"] 188 | 189 | if "text_color" not in args: 190 | args["text_color"] = self.text_color 191 | if "height" not in args: 192 | args["height"] = self.height 193 | if "width" not in args: 194 | args["width"] = self.width 195 | if "fg_color" not in args: 196 | args["fg_color"] = fg 197 | if args["fg_color"]!=fg: 198 | args["fg_color"] = fg 199 | if "corner_radius" in args: 200 | del args["corner_radius"] 201 | if "border_color" in args: 202 | del args["border_color"] 203 | if "border_width" in args: 204 | del args["border_width"] 205 | if "color_phase" in args: 206 | del args["color_phase"] 207 | if "orientation" in args: 208 | del args["orientation"] 209 | if "write" in args: 210 | del args["write"] 211 | 212 | if self.write: 213 | if "anchor" in args: 214 | del args["anchor"] 215 | if "hover_color" in args: 216 | del args["hover_color"] 217 | if "hover" in args: 218 | del args["hover"] 219 | if "justify" not in args: 220 | args["justify"] = self.justify 221 | 222 | self.frame[i,j] = customtkinter.CTkEntry(self.inside_frame, 223 | font=self.font, 224 | corner_radius=0, 225 | **args) 226 | if value is None: 227 | value = " " 228 | self.frame[i,j].insert(0, str(value)) 229 | self.frame[i,j].bind("", lambda e, row=i, column=j, data=self.data: self.after(100, lambda: self.manipulate_data(row, column))) 230 | self.frame[i,j].grid(column=j, row=i, padx=padx, pady=pady, sticky="nsew") 231 | 232 | if self.header_color: 233 | if i==0: 234 | self.frame[i,j].configure(state="readonly") 235 | 236 | else: 237 | if "anchor" not in args: 238 | args["anchor"] = self.anchor 239 | if "hover_color" not in args: 240 | args["hover_color"] = self.hover_color 241 | if "hover" not in args: 242 | args["hover"] = self.hover 243 | if "justify" in args: 244 | anchor = args["justify"] 245 | if anchor=="center": 246 | anchor="c" 247 | elif anchor=="left": 248 | anchor="w" 249 | elif anchor=="right": 250 | anchor="e" 251 | args.update({"anchor": anchor}) 252 | del args["justify"] 253 | if value is None: 254 | value = " " 255 | self.frame[i,j] = customtkinter.CTkButton(self.inside_frame, background_corner_colors=corners, 256 | font=self.font, 257 | corner_radius=corner_radius, 258 | text=value, 259 | border_width=0, 260 | command=(lambda e=self.data[i,j]: self.command(e)) if self.command else None, **args) 261 | self.frame[i,j].grid(column=j, row=i, padx=padx, pady=pady, sticky="nsew") 262 | if self.frame[i,j]._text_label is not None: 263 | self.frame[i,j]._text_label.config(wraplength=self.wraplength) 264 | 265 | if hover_modify: 266 | self.dynamic_hover(self.frame[i,j], i, j) 267 | 268 | self.rowconfigure(i, weight=1) 269 | self.columnconfigure(j, weight=1) 270 | for x in self.frame: 271 | for y in self.binded_objects: 272 | self.frame[x].bind(*y) 273 | 274 | def dynamic_hover(self, frame, i, j): 275 | """ internal function to change corner cell colors """ 276 | self.corner_buttons[i,j] = frame 277 | fg = self.data[i,j]["args"]["fg_color"] 278 | hv = self.data[i,j]["args"]["hover_color"] 279 | if (self.border_width>=5) and (self.corner>=5): 280 | tr = self.border_color 281 | else: 282 | tr = "" 283 | if i==0 and j==0: 284 | corners = [tr, fg, fg, fg] 285 | hover_corners = [tr, hv, hv, hv] 286 | elif i==self.rows-1 and j==self.columns-1: 287 | corners = [fg ,fg, tr, fg] 288 | hover_corners = [hv, hv, tr, hv] 289 | elif i==self.rows-1 and j==0: 290 | corners = [fg ,fg, fg, tr] 291 | hover_corners = [hv, hv, hv, tr] 292 | elif i==0 and j==self.columns-1: 293 | corners = [fg, tr, fg, fg] 294 | hover_corners = [hv, tr, hv, hv] 295 | else: 296 | return 297 | 298 | frame.configure(background_corner_colors=corners, fg_color=fg) 299 | frame.bind("", lambda e, x=i, y=j, color=hover_corners, fg=hv: 300 | self.frame[x,y].configure(background_corner_colors=color, fg_color=fg)) 301 | frame.bind("", lambda e, x=i, y=j, color=corners, fg=fg: 302 | self.frame[x,y].configure(background_corner_colors=color, fg_color=fg)) 303 | 304 | def manipulate_data(self, row, column): 305 | """ entry callback """ 306 | self.update_data() 307 | data = self.data[row,column] 308 | if self.command: self.command(data) 309 | 310 | def update_data(self): 311 | """ update the data when values are changes """ 312 | for i in self.frame: 313 | if self.write: 314 | self.data[i]["value"]=self.frame[i].get() 315 | else: 316 | self.data[i]["value"]=self.frame[i].cget("text") 317 | 318 | self.values = [] 319 | for i in range(self.rows): 320 | row_data = [] 321 | for j in range(self.columns): 322 | row_data.append(self.data[i,j]["value"]) 323 | self.values.append(row_data) 324 | 325 | def edit_row(self, row, value=None, **kwargs): 326 | """ edit all parameters of a single row """ 327 | for i in range(self.columns): 328 | self.frame[row, i].configure(require_redraw=True, **kwargs) 329 | self.data[row, i]["args"].update(kwargs) 330 | if value is not None: 331 | self.insert(row, i, value) 332 | if (row,i) in self.corner_buttons.keys(): 333 | self.dynamic_hover(self.corner_buttons[row,i],row,i) 334 | self.update_data() 335 | 336 | def edit_column(self, column, value=None, **kwargs): 337 | """ edit all parameters of a single column """ 338 | for i in range(self.rows): 339 | self.frame[i, column].configure(require_redraw=True, **kwargs) 340 | self.data[i, column]["args"].update(kwargs) 341 | if value is not None: 342 | self.insert(i, column, value) 343 | if (i, column) in self.corner_buttons.keys(): 344 | self.dynamic_hover(self.corner_buttons[i, column], i, column) 345 | self.update_data() 346 | 347 | def update_values(self, values, **kwargs): 348 | """ update all values at once """ 349 | for i in self.frame.values(): 350 | i.destroy() 351 | self.frame = {} 352 | self.values = values 353 | self.draw_table(**kwargs) 354 | self.update_data() 355 | 356 | def add_row(self, values, index=None, **kwargs): 357 | """ add a new row """ 358 | for i in self.frame.values(): 359 | i.destroy() 360 | self.frame = {} 361 | if index is None: 362 | index = len(self.values) 363 | try: 364 | self.values.insert(index, values) 365 | self.rows+=1 366 | except IndexError: pass 367 | 368 | self.draw_table(**kwargs) 369 | self.update_data() 370 | 371 | def add_column(self, values, index=None, **kwargs): 372 | """ add a new column """ 373 | for i in self.frame.values(): 374 | i.destroy() 375 | self.frame = {} 376 | if index is None: 377 | index = len(self.values[0]) 378 | x = 0 379 | for i in self.values: 380 | try: 381 | i.insert(index, values[x]) 382 | x+=1 383 | except IndexError: pass 384 | self.columns+=1 385 | self.draw_table(**kwargs) 386 | self.update_data() 387 | 388 | def delete_row(self, index=None): 389 | """ delete a particular row """ 390 | if len(self.values)==1: 391 | return 392 | if index is None or index>=len(self.values): 393 | index = len(self.values)-1 394 | self.values.pop(index) 395 | for i in self.frame.values(): 396 | i.destroy() 397 | self.rows-=1 398 | self.frame = {} 399 | self.draw_table() 400 | self.update_data() 401 | 402 | 403 | def delete_column(self, index=None): 404 | """ delete a particular column """ 405 | if len(self.values[0])==1: 406 | return 407 | if index is None or index>=len(self.values[0]): 408 | try: 409 | index = len(self.values)-1 410 | except IndexError: 411 | return 412 | for i in self.values: 413 | i.pop(index) 414 | for i in self.frame.values(): 415 | i.destroy() 416 | self.columns-=1 417 | self.frame = {} 418 | self.draw_table() 419 | self.update_data() 420 | 421 | 422 | def delete_rows(self, indices=[]): 423 | """ delete a particular row """ 424 | if len(indices)==0: 425 | return 426 | self.values = [v for i, v in enumerate(self.values) if i not in indices] 427 | for i in indices: 428 | for j in range(self.columns): 429 | self.data[i, j]["args"] = "" 430 | for i in self.frame.values(): 431 | i.destroy() 432 | self.rows -= len(set(indices)) 433 | self.frame = {} 434 | self.draw_table() 435 | self.update_data() 436 | 437 | def delete_columns(self, indices=[]): 438 | """ delete a particular column """ 439 | if len(indices)==0: 440 | return 441 | x = 0 442 | 443 | for k in self.values: 444 | self.values[x] = [v for i, v in enumerate(k) if i not in indices] 445 | x+=1 446 | for i in indices: 447 | for j in range(self.rows): 448 | self.data[j, i]["args"] = "" 449 | 450 | for i in self.frame.values(): 451 | i.destroy() 452 | self.columns -= len(set(indices)) 453 | self.frame = {} 454 | self.draw_table() 455 | self.update_data() 456 | 457 | def get_row(self, row): 458 | """ get values of one row """ 459 | return self.values[row] 460 | 461 | def get_column(self, column): 462 | """ get values of one column """ 463 | column_list = [] 464 | for i in self.values: 465 | column_list.append(i[column]) 466 | return column_list 467 | 468 | def select_row(self, row): 469 | """ select an entire row """ 470 | self.edit_row(row, fg_color=self.hover_color) 471 | if self.orient!="horizontal": 472 | if self.header_color: 473 | self.edit_column(0, fg_color=self.header_color) 474 | else: 475 | if self.header_color: 476 | self.edit_row(0, fg_color=self.header_color) 477 | return self.get_row(row) 478 | 479 | def select_column(self, column): 480 | """ select an entire column """ 481 | self.edit_column(column, fg_color=self.hover_color) 482 | if self.orient!="horizontal": 483 | if self.header_color: 484 | self.edit_column(0, fg_color=self.header_color) 485 | else: 486 | if self.header_color: 487 | self.edit_row(0, fg_color=self.header_color) 488 | return self.get_column(column) 489 | 490 | def deselect_row(self, row): 491 | """ deselect an entire row """ 492 | self.edit_row(row, fg_color=self.fg_color if row%2==0 else self.fg_color2) 493 | if self.orient!="horizontal": 494 | if self.header_color: 495 | self.edit_column(0, fg_color=self.header_color) 496 | else: 497 | if self.header_color: 498 | self.edit_row(0, fg_color=self.header_color) 499 | 500 | def deselect_column(self, column): 501 | """ deselect an entire column """ 502 | for i in range(self.rows): 503 | self.frame[i,column].configure(fg_color=self.fg_color if i%2==0 else self.fg_color2) 504 | if self.orient!="horizontal": 505 | if self.header_color: 506 | self.edit_column(0, fg_color=self.header_color) 507 | else: 508 | if self.header_color: 509 | self.edit_row(0, fg_color=self.header_color) 510 | 511 | def select(self, row, column): 512 | """ select any cell """ 513 | if row == 0 and column == 0: 514 | hover_corners = ["", self.hover_color, self.hover_color, self.hover_color] 515 | elif row == self.rows - 1 and column == self.columns - 1: 516 | hover_corners = [self.hover_color, self.hover_color, "", self.hover_color] 517 | elif row == self.rows - 1 and column == 0: 518 | hover_corners=[self.hover_color, self.hover_color, self.hover_color, ""] 519 | elif row == 0 and column == self.columns - 1: 520 | hover_corners = [self.hover_color, "", self.hover_color, self.hover_color] 521 | else: 522 | hover_corners = [self.hover_color, self.hover_color, self.hover_color, self.hover_color] 523 | self.frame[row, column].configure(background_corner_colors=hover_corners, fg_color=self.hover_color) 524 | 525 | def deselect(self, row, column): 526 | """ deselect any cell """ 527 | self.frame[row,column].configure(fg_color=self.fg_color if row%2==0 else self.fg_color2) 528 | 529 | def insert(self, row, column, value, **kwargs): 530 | """ insert value in a specific block [row, column] """ 531 | if kwargs: self.data[row,column]["args"].update(kwargs) 532 | if self.write: 533 | self.frame[row,column].delete(0, customtkinter.END) 534 | self.frame[row,column].insert(0, value) 535 | self.frame[row,column].configure(**kwargs) 536 | else: 537 | self.frame[row,column].configure(require_redraw=True, text=value, **kwargs) 538 | if (row, column) in self.corner_buttons.keys(): 539 | self.dynamic_hover(self.corner_buttons[row, column], row, column) 540 | 541 | self.update_data() 542 | 543 | def edit(self, row, column, **kwargs): 544 | """ change parameters of a cell without changing value """ 545 | if kwargs: self.data[row,column]["args"].update(kwargs) 546 | if self.write: 547 | self.frame[row,column].configure(**kwargs) 548 | else: 549 | self.frame[row,column].configure(require_redraw=True, **kwargs) 550 | if (row, column) in self.corner_buttons.keys(): 551 | self.dynamic_hover(self.corner_buttons[row, column], row, column) 552 | 553 | self.update_data() 554 | 555 | def delete(self, row, column, **kwargs): 556 | """ delete a value from a specific block [row, column] """ 557 | if self.write: 558 | self.frame[row,column].delete(0, customtkinter.END) 559 | self.frame[row,column].configure(**kwargs) 560 | else: 561 | self.frame[row,column].configure(require_redraw=True, text="", **kwargs) 562 | if kwargs: self.data[row,column]["args"].update(kwargs) 563 | self.update_data() 564 | 565 | def get(self, row=None, column=None): 566 | """ get the required cell """ 567 | if row is not None and column is not None: 568 | return self.data[row,column]["value"] 569 | else: 570 | return self.values 571 | 572 | def get_selected_row(self): 573 | """ Return the index and data of the selected row """ 574 | selected_row_index = None 575 | for i in range(self.rows): 576 | if self.frame[i, 0].cget("fg_color") == self.hover_color: 577 | selected_row_index = i 578 | break 579 | selected_row_data = self.get_row(selected_row_index) if selected_row_index is not None else None 580 | return {"row_index": selected_row_index, "values": selected_row_data} 581 | 582 | def get_selected_column(self): 583 | """ Return the index and data of the selected row """ 584 | selected_column_index = None 585 | for i in range(self.columns): 586 | if self.frame[0, i].cget("fg_color") == self.hover_color: 587 | selected_column_index = i 588 | break 589 | selected_column_data = self.get_column(selected_column_index) if selected_column_index is not None else None 590 | return {"column_index": selected_column_index, "values": selected_column_data} 591 | 592 | def configure(self, **kwargs): 593 | """ configure table widget attributes""" 594 | 595 | if "colors" in kwargs: 596 | self.colors = kwargs.pop("colors") 597 | self.fg_color = self.colors[0] 598 | self.fg_color2 = self.colors[1] 599 | if "fg_color" in kwargs: 600 | self.colors = (kwargs["fg_color"], kwargs.pop("fg_color")) 601 | self.fg_color = self.colors[0] 602 | self.fg_color2 = self.colors[1] 603 | if "bg_color" in kwargs: 604 | super().configure(bg_color=kwargs["bg_color"]) 605 | self.inside_frame.configure(fg_color=kwargs["bg_color"]) 606 | if "header_color" in kwargs: 607 | self.header_color = kwargs.pop("header_color") 608 | if "rows" in kwargs: 609 | self.rows = kwargs.pop("rows") 610 | if "columns" in kwargs: 611 | self.columns = kwargs.pop("columns") 612 | if "values" in kwargs: 613 | self.values = kwargs.pop("values") 614 | if "padx" in kwargs: 615 | self.padx = kwargs.pop("padx") 616 | if "pady" in kwargs: 617 | self.pady = kwargs.pop("pady") 618 | if "wraplength" in kwargs: 619 | self.wraplength = kwargs.pop("wraplength") 620 | 621 | for i in range(self.rows): 622 | for j in range(self.columns): 623 | self.data[i,j]["args"].update(kwargs) 624 | 625 | if "hover_color" in kwargs: 626 | self.hover_color = kwargs.pop("hover_color") 627 | if "text_color" in kwargs: 628 | self.text_color = kwargs.pop("text_color") 629 | if "border_width" in kwargs: 630 | self.border_width = kwargs.pop("border_width") 631 | super().configure(border_width=self.border_width) 632 | self.inside_frame.pack(expand=True, fill="both", padx=self.border_width, pady=self.border_width) 633 | if "border_color" in kwargs: 634 | self.border_color = kwargs.pop("border_color") 635 | super().configure(border_color=self.border_color) 636 | if "hover" in kwargs: 637 | self.hover = kwargs.pop("hover") 638 | if "anchor" in kwargs: 639 | self.anchor = kwargs.pop("anchor") 640 | if "corner_radius" in kwargs: 641 | self.corner = kwargs.pop("corner_radius") 642 | super().configure(corner_radius=self.corner) 643 | if "color_phase" in kwargs: 644 | self.phase = kwargs.pop("color_phase") 645 | if "justify" in kwargs: 646 | self.justify = kwargs.pop("justify") 647 | if "orientation" in kwargs: 648 | self.orient = kwargs.pop("orientation") 649 | if "write" in kwargs: 650 | self.write = kwargs.pop("write") 651 | if "width" in kwargs: 652 | self.width = kwargs.pop("width") 653 | if "height" in kwargs: 654 | self.height = kwargs.pop("height") 655 | 656 | self.update_values(self.values, **kwargs) 657 | 658 | def cget(self, param): 659 | if param=="width": 660 | return self.frame[0,0].winfo_reqwidth() 661 | if param=="height": 662 | return self.frame[0,0].winfo_reqheight() 663 | if param=="colors": 664 | return (self.fg_color, self.fg_color2) 665 | if param=="hover_color": 666 | return self.hover_color 667 | if param=="text_color": 668 | return self.text_color 669 | if param=="border_width": 670 | return self.border_width 671 | if param=="border_color": 672 | return self.border_color 673 | if param=="hover": 674 | return self.hover 675 | if param=="anchor": 676 | return self.anchor 677 | if param=="wraplength": 678 | return self.wraplength 679 | if param=="padx": 680 | return self.padx 681 | if param=="pady": 682 | return self.pady 683 | if param=="header_color": 684 | return self.header_color 685 | if param=="row": 686 | return self.rows 687 | if param=="column": 688 | return self.columns 689 | if param=="values": 690 | return self.values 691 | if param=="color_phase": 692 | return self.phase 693 | if param=="justify": 694 | return self.justify 695 | if param=="orientation": 696 | return self.orient 697 | if param=="write": 698 | return self.write 699 | 700 | return super().cget(param) 701 | 702 | def bind(self, sequence: str = None, command = None, add = True): 703 | """ bind all cells """ 704 | self.binded_objects.append([sequence, command, add]) 705 | 706 | super().bind(sequence, command, add) 707 | for i in self.frame: 708 | self.frame[i].bind(sequence, command, add) 709 | self.inside_frame.bind(sequence, command, add) 710 | 711 | def unbind(self, sequence: str = None, funcid: str = None): 712 | for i in self.binded_objects: 713 | if sequence in i: 714 | self.binded_objects.remove(i) 715 | 716 | super().unbind(sequence, funcid) 717 | for i in self.frame: 718 | self.frame[i].unbind(sequence, funcid) 719 | self.inside_frame.unbind(sequence, funcid) 720 | --------------------------------------------------------------------------------