ragardner / tksheet

Python tkinter table widget for displaying tabular data
https://pypi.org/project/tksheet/
MIT License
400 stars 48 forks source link

Resizing columns automatically to fit the frame if they would be smaller in total than the available space #227

Open DoomOfMax97 opened 4 months ago

DoomOfMax97 commented 4 months ago

Hello,

I have been trying a lot but I can't seem to make this happen reliably, do you have any tips/ideas?

ragardner commented 4 months ago

Hi there,

Have you tried the option auto_resize_columns= which resizes them to an int amount of pixels?

Or do you mean resizing them to fit the cell text rather than a width in pixels?

DoomOfMax97 commented 4 months ago

In my testing just now self.set_options(auto_resize_columns=self.winfo_width(), redraw=True) self.redraw(True, True) seems to work for resizing a single column correctly, but not if you have multiple, there could be an issue with my subclass though. The idea is, at least that's what my code tried to do initially is:

  1. Calculate a weight for each column based on the largest of the first n cells of the current column.
  2. If winfo_width() < sum(weights): 2a. Set each column to size "weight", they won't fit, but have an efficient column width 2b. Multiply each weight by winfo_width()/sum(weights) and fudge the last one, so all columns get resized to fit the frame

Is there a good/an easier way to do this?

ragardner commented 4 months ago

Sorry about the confusion with auto_resize... you can find the docs here: https://github.com/ragardner/tksheet/wiki/Version-7#auto-resize-column-widths-to-fit-the-window

Basically the value is for the minimum width in pixels of each individual column, so you could set this to 50 for example

About your question, I'm afraid I can only point to the relevant functions at the moment, especially the one below

There is an internal function in the ColumnHeaders class, accessible from the Sheet() class, but considering the things above I might have to make changes to it in the future so maybe you don't want to use it,

self.CH.set_col_width(
    col=column int,
    width=None, # set to text size
    only_set_if_too_small=False,
    displayed_only=True, # if you have a lot of rows
    return_new_width=True or False, # when True this will not set the column width but return the width it was going to be set to
)

Looking at the available Sheet() functions and their uses I think there is a lack of functionality, such as text measuring functionality for columns, getting the currently visible columns, etc. This would make work like yours a lot easier

I will have to add some more functions for dealing with row heights / column widths, they may not have the functionality you seek above but by using them maybe you'll get to what you want. I can't offer a timeframe on this work though sorry

DoomOfMax97 commented 4 months ago

I see, thank you for your help anyways. This is my current code, maybe it helps you in any way.

    def resize_columns_to_fit(self, n=5):
        minimum_scale_factor = 15
        scrollbar_width = 17

        if (self._headers is None
                or self._is_shown is False
                or self._show_mode == NO_DATA):
            return

        # Calculate the weight for each column based on the largest of the first n cells
        weights = []
        for col_idx in range(len(self._headers)):
            # Get the first n cells in the current column
            col_data = [len(str(self._data[row_idx][col_idx])) for row_idx in range(min(n, len(self._data)))]
            # Include the header in the calculation
            col_data.append(len(str(self._headers[col_idx])))
            # Calculate the weight based on the largest item
            max_width = max(col_data)
            weights.append(max_width)

        # Get the width of the sheet widget
        root_window.update_idletasks()  # Ensure the window is updated before getting its width
        winfo_width = self._sheet.winfo_width()
        total_weight = sum(weights)
        # Proportionally adjust the weights to fit the widget width
        if total_weight == 0:
            scale_factor = minimum_scale_factor
        else:
            scale_factor = max(winfo_width / total_weight, minimum_scale_factor)
        weights = [int(weight * scale_factor) for weight in weights]
        # Adjust the last column to ensure the total width matches the widget width
        if scale_factor > minimum_scale_factor:
            weights[-1] += winfo_width - sum(weights)
        if len(self._headers) == 1:
            self._sheet.set_all_column_widths(weights[-1] - scrollbar_width)
            return

        # Set each column width
        for col_idx, weight in enumerate(weights):
            self._sheet.column_width(col_idx, weight)
ragardner commented 4 months ago

Hello,

Thanks for adding your code,

In version 7.2.2 I have added a few functions you may find useful:

Cheers

DoomOfMax97 commented 4 months ago

No worries,

looks good, will give them a try.

Thank you