Resize Column to fit it's contents

46 posts, 0 answers
  1. Deborah
    Deborah avatar
    154 posts
    Member since:
    Sep 2010

    Posted 18 Oct 2010 Link to this post

    In doing more research on this ... I have the AutoSizeColumnsMode set to Fill. So I assume sometime *after* the cell formatting event that the grid is resetting the widths to fill the grid?

    Is there an event I can use at that point to readjust the column widths if the total width is then too large?
  2. Emanuel Varga
    Emanuel Varga avatar
    1336 posts
    Member since:
    May 2010

    Posted 18 Oct 2010 Link to this post

    OK, i don't understand exactly what you are trying to say here, but i think you also misunderstood my suggestion, it goes something like this, setting a maximum percentage of the ClientSize.Width that a column may occupy, I've created a new dictionary here, but i would suggest to create a struct, or a class to hold these values, it would be cleaner, but just as a proof of concept take a look at this:

    Here the max width for the ID column is 10% and for the Date is 20%

    C#:
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Windows.Forms;
    using Telerik.WinControls.UI;
     
    public partial class Form1 : Form
    {
        private RadGridView radGridView1;
        private TestsCollection collection = new TestsCollection(1000);
     
        public Form1()
        {
            InitializeComponent();
            this.Controls.Add(radGridView1 = new RadGridView());
            radGridView1.Dock = DockStyle.Fill;
            radGridView1.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill;
        }
     
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            radGridView1.AddNewRowPosition = SystemRowPosition.Top;
             
            radGridView1.DataBindingComplete += new GridViewBindingCompleteEventHandler(radGridView1_DataBindingComplete);
            radGridView1.CellFormatting += new CellFormattingEventHandler(radGridView1_CellFormatting);
            SetColumnPercentage("Id", 10);
            SetColumnPercentage("Date", 20);
            radGridView1.DataSource = collection;
        }
     
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            radGridView1.Refresh();
        }
     
        Dictionary<string, string> dictionary = new Dictionary<string, string>();
        Dictionary<string, int> maxColumnWidthPercentage = new Dictionary<string, int>();
     
        public void SetColumnPercentage(string headerText, int percentage)
        {
            if (maxColumnWidthPercentage.ContainsKey(headerText))
            {
                maxColumnWidthPercentage[headerText] = percentage;
                return;
            }
     
            maxColumnWidthPercentage.Add(headerText, percentage);
        }
     
        void radGridView1_CellFormatting(object sender, CellFormattingEventArgs e)
        {
            var grid = radGridView1;
            var column = e.CellElement.ColumnInfo;
            var text = e.CellElement.Text;
     
            if (string.IsNullOrEmpty(text))
            {
                return;
            }
     
            var maxPercentage = maxColumnWidthPercentage.ContainsKey(column.HeaderText) ? maxColumnWidthPercentage[column.HeaderText] : 0;
            var maxWidth = grid.ClientSize.Width * maxPercentage / 100;
            if (dictionary[column.HeaderText].Length <= text.Length && maxPercentage != 0 && column.MinWidth < maxWidth || maxPercentage != 0 && column.MinWidth > maxWidth)
            {
                dictionary[column.HeaderText] = text;
     
                var requiredWidth = (int)(this.CreateGraphics().MeasureString(text, this.Font).Width + 8);
                column.MinWidth = requiredWidth <= maxWidth ? requiredWidth : maxWidth;
                column.MaxWidth = column.MinWidth;
                column.Width = column.MaxWidth;
            }
        }
     
        void radGridView1_DataBindingComplete(object sender, GridViewBindingCompleteEventArgs e)
        {
            var grid = sender as RadGridView;
     
            var dateTimeColumm = grid.Columns[1] as GridViewDateTimeColumn;
            dateTimeColumm.FormatString = "{0:MM/dd/yy}";
            dateTimeColumm.Format = DateTimePickerFormat.Short;
     
            foreach (var column in grid.Columns)
            {
                dictionary.Add(column.HeaderText, string.Empty);
                //column.MinWidth = (int)(this.CreateGraphics().MeasureString(text, this.Font).Width + 8);
                //column.MaxWidth = column.MinWidth;
                //column.Width = column.MaxWidth;
            }
        }
    }
     
    public class Test
    {
        public Test()
        {
        }
     
        public decimal Id { get; set; }
     
        public DateTime Date { get; set; }
     
        public string Text { get; set; }
    }
     
    public class TestsCollection : BindingList<Test>
    {
        public TestsCollection(int noItems)
        {
            for (decimal i = 10000000000000; i < noItems + 10000000000000; i++)
            {
                this.Add(
                new Test
                {
                    Id = i,
                    Date = DateTime.Now.AddDays((double)(i % 17)),
                    Text = "text" + i.ToString()
                });
            }
        }
    }

    But this will be have a great impact on performance because on size change i have to call a refresh in order for the grid to recalculate the columns.

    Hope this helps, if you have any other questions or comments, please let me know,

    Best Regards,
    Emanuel Varga
  3. UI for WinForms is Visual Studio 2017 Ready
  4. Deborah
    Deborah avatar
    154 posts
    Member since:
    Sep 2010

    Posted 18 Oct 2010 Link to this post

    What I was trying to say is that I am having trouble with this: grid.ClientSize.Width

    In the CellFormatting event, this appears to be set to the design time size, not the runtime size.

    For example, I build the form at design time and make the grid 900 wide. At runtime, the grid is set to fill the form and the form is resizable. So the user may have it set to 450 wide.

    When the CellFormatting event is executed, the grid width is being calculated based on the 900 instead of the 450.

    So I am currently working on shifting the calculations into the Resize event, when the grid width seems to be correctly set to 450. However, this is causing other problems in that it generates more formatting events that then resize the columns back to there original size (not the percentage).

    Does this make my problem a little more clear? What happens with your code if you make the grid really small or really large at design time. Does it appear correctly at run time?
  5. Emanuel Varga
    Emanuel Varga avatar
    1336 posts
    Member since:
    May 2010

    Posted 18 Oct 2010 Link to this post

    Hello Deborah,

    I'm not using the designer, i prefer doing thing in code, i had a lot of problems in the past with lost values and serialization data lost, so i did everything by code, so that you can be able to test it, with just copy and paste into an empty project, if you want me to convert it to VB please let me know, but i guess you can use the telerik code converter as well.

    The only thing i had to add which i don't really like is calling a refresh on the SizeChanged event, to force a recalculation of the maxWidth of the columns, but please try it for yourself and let me know.

    Best Regards,
    Emanuel Varga
  6. Deborah
    Deborah avatar
    154 posts
    Member since:
    Sep 2010

    Posted 18 Oct 2010 Link to this post

    I ended up using the technique used by the Microsoft DataGridView that allows defining a "fill" column. That is the column that will fill any remaining space after the other columns are fit to their size. (It would be REALLY great if your product provided this feature!)

    My Cell Formatting then ended up like this:
    Private Sub SCStarDataGrid_CellFormatting(ByVal sender As Object, ByVal e As Telerik.WinControls.UI.CellFormattingEventArgs) Handles Me.CellFormatting
        Dim column As GridViewColumn = e.CellElement.ColumnInfo
        Dim cellText As String = e.CellElement.Text
        ' If the cell has wrapped text, ignore it.
        ' If the cell is empty, ignore it.
        If Not column.WrapText AndAlso
                Not String.IsNullOrWhiteSpace(cellText) Then
            Dim columnSize = CType(Me.CreateGraphics.MeasureString(cellText, Me.Font).Width, Integer) + 5
            ' Don't set the size larger than the maximum size
            If column.MaxWidth > 0 AndAlso columnSize > column.MaxWidth Then
                columnSize = column.MaxWidth
            End If
            ' If the text in this cell is bigger than the longest string for this column, add it to the dictionary.
            If columnSizeDictionary(column.Index) < columnSize Then
                columnSizeDictionary(column.Index) = columnSize
                column.MinWidth = columnSize
                column.Width = column.MinWidth
            End If
        End If
    End Sub

    Notice the extra code to handle the wrapped columns and columns with a defined maximum size.

    As per my prior message, I then used the resize event to resize the "fill" column if the other controls don't show all of their contents:
    Dim totalColumnSize As Integer = columnSizeDictionary.Values.Sum()
    If totalColumnSize > Me.ClientSize.Width Then
        Dim fillColumn = Me.Columns(FillColumnName)
        Dim fillColumnSize = Me.Width - (totalColumnSize - columnSizeDictionary(fillColumn.Index))
        columnSizeDictionary(fillColumn.Index) = fillColumnSize
        fillColumn.MaxWidth = fillColumnSize
        fillColumn.Width = fillColumn.MinWidth
    End If

    Notice how this just sets the maxWidth. This prevents the Formatting event from reassigning the size again.

    Let me know if you see any issues with this or if there is an easier way.

    Thanks again for your help.
  7. Emanuel Varga
    Emanuel Varga avatar
    1336 posts
    Member since:
    May 2010

    Posted 18 Oct 2010 Link to this post

    Me for once, i would prefer to be able to say, that one column can take no more than 10% of the grid, but of course, less would be great, if you are setting the maxwidth, how will you define it in code? x pixels?
    In my approach you can define a percentage for the columns you want to limit, and the rest can take / will take all of the remaining space, so you can have more than one fill column.

    In my opinion it would offer a more range of usage.

    Other than that, i cannot see anything wrong.

    Best Regards,
    Emanuel Varga
  8. Deborah
    Deborah avatar
    154 posts
    Member since:
    Sep 2010

    Posted 18 Oct 2010 Link to this post

    I am using this control in an application whereby 90+% of the time the user interface design rules are:
    • Set the column width to display its contents.
    • If the column width is too long and the defined text field can be wrapped, set it to wrap.
    • If the column width is too long and the defined text field cannot be wrapped (by UI design decision), truncate the field to fit.

    This worked great when using the Microsoft DataGridView because you could define one column as the Fill column and it took care of everything else for you.

    I don't want to add the extra work for each developer to define percentages for all of their grids when the above rules match to our application. Plus, defining the columns as percentages could cause further problems because some fields *must* appear in their entirety (per the UI design).

    If these rules change at some future point, I will be back to copy your code instead.

    Thanks again for all of your help!

  9. Deborah
    Deborah avatar
    154 posts
    Member since:
    Sep 2010

    Posted 18 Oct 2010 Link to this post

    I was going to mark several of your replies as an answer, but realized that I had hijacked someone else's thread, so I cannot access the "mark as answer" feature.

    But I am *very* impressed by the level of support Telerik provides.

    Thanks again!
  10. Emanuel Varga
    Emanuel Varga avatar
    1336 posts
    Member since:
    May 2010

    Posted 18 Oct 2010 Link to this post

    I'm just glad to be able to help, and at least i hope i cleared some of the issues that were causing problems.

    As for the support, for me it's a great way of keeping my mind sharp and helping others if and when i can. ;)

    P.S. if i remember correctly, you created one or more threads with calculating widths of some specific column types, can you at please close those, and maybe redirect other people who might be interested to this thread?

    Once again, always glad to help, and like always if you have any other questions or comments, please let me know,

    Best Regards,
    Emanuel Varga
  11. Deborah
    Deborah avatar
    154 posts
    Member since:
    Sep 2010

    Posted 19 Oct 2010 Link to this post

    The only problem I still have with this is scrolling.

    Do you want me to start a new thread, or continue this one to explain the problem?
  12. Emanuel Varga
    Emanuel Varga avatar
    1336 posts
    Member since:
    May 2010

    Posted 19 Oct 2010 Link to this post

    Hello Deborah,

    Didn't i already answered the Horizontal Scroll problem in this thread ?

    Or is it another scrolling issue?

    Best Regards,
    Emanuel Varga
  13. Deborah
    Deborah avatar
    154 posts
    Member since:
    Sep 2010

    Posted 19 Oct 2010 Link to this post

    I missed that you had replied to that. I'll take a look. Thanks!
  14. Oliver
    Oliver avatar
    3 posts
    Member since:
    Jul 2012

    Posted 06 Nov 2013 Link to this post

    Hello folks,

    if you want to manually implement a glued column, you can append this function to your RadGridView's Resize event

    private void FitColumns(object sender, EventArgs e)
    {
        // get out if working base is not given
        var grid = sender as RadGridView;
        if (grid == null || !grid.Rows.Any()) return;
     
        // Set this property to prevent BestFitColumns() from shrinking columns whose cells don't
        // contain values to a minimum. I know there was some fitting style thing (HeaderCells
        // or something) but I fogot what it was, so this basically works fine too
        grid.AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.None;
     
        // Then call BestFitColumns(), it does almost all the resizing work for you
        // well, except glueing columns
        grid.BestFitColumns();
     
        // Now that all columns are best fitted, let's see if there is still space after the last column
        var visibleColumns = grid.Columns.Where(c => c.IsVisible);
        var parentWidth = grid.Parent.Width;
        var sumVisisbleColumnsWidth = visibleColumns.Sum(c => c.Width);
        var emptySpace = parentWidth - sumVisisbleColumnsWidth;
        if (emptySpace > 0)
        {
            // If so, modify the width of our glued column
            // In this example, the last Column is glued column
            visibleColumns.Last().Width += emptySpace;
        }
    }

    I hope this helps some of you out.

    Kind regards,
    Oliver
  15. Chris
    Chris avatar
    6 posts
    Member since:
    Dec 2012

    Posted 07 Jul 2014 in reply to Jack Link to this post

    RadGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill does seem to work.  I've got Dock = Fill...columns fit the data, column headers are truncated...what else do I neeed?
  16. Chris
    Chris avatar
    6 posts
    Member since:
    Dec 2012

    Posted 07 Jul 2014 in reply to Jack Link to this post

    Sorry, post should read "Does NOT work"
  17. Dimitar
    Admin
    Dimitar avatar
    1410 posts

    Posted 10 Jul 2014 Link to this post

    Hi Chris,

    Thank you for writing.

    When the AutoSizeColumnsMode is set to Fill the columns will fill the entire available space. And if there is no enough space for the header text it will be truncated. For such cases you can set MinWidth for the columns. For example:
    void Form1_Load(object sender, EventArgs e)
    {
        foreach (var item in radGridView1.Columns)
        {
            item.MinWidth = 100;
        }
    }

    This way when the form size is smaller that the total column mininmum size a scrollbar will appear. However I am not sure what is the exact layout behavior that you want to create. So can you please specify your exact goals? This will allow me to provide you with a proper solution for your case.

    I hope this will be useful.

    Regards,
    Dimitar
    Telerik
     
    Check out Telerik Analytics, the service which allows developers to discover app usage patterns, analyze user data, log exceptions, solve problems and profile application performance at run time. Watch the videos and start improving your app based on facts, not hunches.
     
Back to Top
UI for WinForms is Visual Studio 2017 Ready