New to Telerik UI for WinForms? Download free 30-day trial

Resize Columns in RadScheduler

Environment

Product Version Product Author
2022.2.511 RadScheduler for WinForms Dinko Krastev

Description

By default, the columns can't be resized in the RadScheduler. However, such functionality can achieve the desired behavior by manually handling the mouse events of the control and calculating the width while dragging a particular column.

Solution

In order to resize the columns, we need to subscribe to the MouseUp, MouseDown, MouseMove, and MouseLeave events of the RadScheduler. In their event handlers, we are going to execute custom logic to handle this scenario.

scheduler-resize-columns

public partial class Form1 : Form
{
    private SchedulerBindingDataSource schedulerBindingDataSource;
    private DataSet ds;

    private float minCellWidthInPixels = 50;

    private bool resizingResourceHeader = false;
    private bool resizeStarted = false;
    private bool isResizing = false;
    private bool isResizeAllowed = false;

    private float initialSizeFactor;
    private float initialSizeInPixels;
    private float initialMouseOffset;
    private float initialNextCellFactor;

    private SchedulerDayViewElement dayViewElement;
    private SchedulerMonthViewElement monthViewElement;
    private SchedulerTimelineViewElement timeLineViewElement;

    private SchedulerHeaderCellElement headerCell;
    private SchedulerHeaderCellElement nextHeaderCell;

    private SchedulerResourceHeaderCellElement resourceHeaderCell;
    private SchedulerResourceHeaderCellElement resourceNextCell;

    public Form1()
    {
        InitializeComponent();

        this.ds = new DataSet();
        this.schedulerBindingDataSource = new SchedulerBindingDataSource();

        this.radSchedulerNavigator1.AssociatedScheduler = this.radScheduler1;

        this.radScheduler1.ActiveViewChanged += RadScheduler1_ActiveViewChanged;

        this.radScheduler1.MouseUp += radScheduler1_MouseUp;
        this.radScheduler1.MouseDown += radScheduler1_MouseDown;
        this.radScheduler1.MouseMove += radScheduler1_MouseMove;
        this.radScheduler1.MouseLeave += radScheduler1_MouseLeave;
    }

    private void RadScheduler1_ActiveViewChanged(object sender, SchedulerViewChangedEventArgs e)
    {
        if (e.NewView.ViewType == SchedulerViewType.Month)
        {
            if (this.radScheduler1.GroupType == GroupType.Resource)
            {
                SchedulerMonthViewGroupedByResourceElement view = (SchedulerMonthViewGroupedByResourceElement)this.radScheduler1.ViewElement;
                IList<SchedulerMonthViewElement> childMonthElements = view.GetChildViewElements();
                foreach (SchedulerMonthViewElement childMonthElement in childMonthElements)
                {
                    foreach (var item in childMonthElement.MonthViewAreaElement.CellElements)
                    {
                        item.FindDescendant<SchedulerHeaderCellElement>().ShouldHandleMouseInput = true;
                    }
                }
            }
            else
            {
                SchedulerMonthViewElement view = (SchedulerMonthViewElement)this.radScheduler1.ViewElement;
                foreach (var item in view.MonthViewAreaElement.CellElements)
                {
                    item.FindDescendant<SchedulerHeaderCellElement>().ShouldHandleMouseInput = true;
                }
            }
        }
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        DataTable appointments = new DataTable("Appointments");
        appointments.Columns.Add("Id", typeof(int));
        appointments.Columns.Add("Start", typeof(DateTime));
        appointments.Columns.Add("End", typeof(DateTime));
        appointments.Columns.Add("Description", typeof(string));
        appointments.Columns.Add("Summary", typeof(string));
        for (int i = 1; i <= 100; i++)
        {
            appointments.Rows.Add(new object[] { i, DateTime.Now.AddDays(i), DateTime.Now.AddDays(i).AddHours(1), "Description " + i, "Summary " + i });
        }

        DataTable resources = new DataTable("Resources");
        resources.Columns.Add("Id", typeof(int));
        resources.Columns.Add("ResourceName", typeof(string));
        for (int i = 1; i <= 3; i++)
        {
            resources.Rows.Add(new object[] { i, "ResourceName" + i });
        }

        DataTable appointmentsResources = new DataTable("AppointmentsResources");
        appointmentsResources.Columns.Add("AppointmentId", typeof(int));
        appointmentsResources.Columns.Add("ResourceId", typeof(int));
        for (int i = 1; i < appointments.Rows.Count; i++)
        {
            appointmentsResources.Rows.Add(new object[] { appointments.Rows[i]["Id"], i % 2 == 0 ? 1 : 2 });
        }

        this.ds.Tables.AddRange(new DataTable[] { appointments, resources, appointmentsResources });
        this.ds.Relations.Add("Appointment_AppointmentsResources", this.ds.Tables["Appointments"].Columns["Id"], this.ds.Tables["AppointmentsResources"].Columns["AppointmentId"]);

        AppointmentMappingInfo appointmentMappingInfo = new AppointmentMappingInfo();
        appointmentMappingInfo.Start = "Start";
        appointmentMappingInfo.End = "End";
        appointmentMappingInfo.Description = "Description";
        appointmentMappingInfo.ResourceId = "ResourceId";
        appointmentMappingInfo.Resources = "Appointment_AppointmentsResources";
        appointmentMappingInfo.Summary = "Summary";
        this.schedulerBindingDataSource.EventProvider.Mapping = appointmentMappingInfo;

        ResourceMappingInfo resourceMappingInfo = new ResourceMappingInfo();
        resourceMappingInfo.Id = "Id";
        resourceMappingInfo.Name = "ResourceName";
        this.schedulerBindingDataSource.ResourceProvider.Mapping = resourceMappingInfo;

        schedulerBindingDataSource.ResourceProvider.DataMember = "Resources";
        schedulerBindingDataSource.ResourceProvider.DataSource = this.ds;

        schedulerBindingDataSource.EventProvider.DataMember = "Appointments";
        schedulerBindingDataSource.EventProvider.DataSource = this.ds;

        this.radScheduler1.DataSource = this.schedulerBindingDataSource;
        this.radScheduler1.GroupType = GroupType.Resource;
        this.radScheduler1.ActiveView.ResourcesPerView = 2;

        this.schedulerBindingDataSource.Rebind();
    }

    void radScheduler1_MouseDown(object sender, MouseEventArgs e)
    {
        if (isResizeAllowed)
        {
            isResizing = true;
            this.radScheduler1.Capture = true;
        }
    }

    void radScheduler1_MouseLeave(object sender, EventArgs e)
    {
        this.ResizeEnded();
    }

    void radScheduler1_MouseUp(object sender, MouseEventArgs e)
    {
        this.ResizeEnded();
    }

    private void ResizeEnded()
    {
        this.radScheduler1.Capture = false;
        resizeStarted = false;
        isResizing = false;
        dayViewElement = null;
        resourceHeaderCell = null;
        headerCell = null;
    }

    void radScheduler1_MouseMove(object sender, MouseEventArgs e)
    {
        if (isResizing)
        {
            Point mousePosition = Control.MousePosition;
            Point mouseLocation = this.PointToClient(mousePosition);

            if (resizingResourceHeader)
            {
                this.ResizeGroupedByResourcesViewElement(mouseLocation);
            }
            else
            {
                this.ResizeViewElementColumns(mouseLocation);
            }
        }
        else
        {
            this.CheckIsResizeAllowed(e);
        }
    }

    private void CheckIsResizeAllowed(MouseEventArgs e)
    {
        headerCell = this.radScheduler1.ElementTree.GetElementAtPoint(e.Location) as SchedulerHeaderCellElement;
        resourceHeaderCell = this.radScheduler1.ElementTree.GetElementAtPoint(e.Location) as SchedulerResourceHeaderCellElement;

        if (headerCell != null || resourceHeaderCell != null)
        {
            resizingResourceHeader = headerCell == null;
            Point mousePosition = Control.MousePosition;
            Point mouseLocation = this.PointToClient(mousePosition);
            RectangleF rect = resizingResourceHeader ? resourceHeaderCell.ControlBoundingRectangle : headerCell.ControlBoundingRectangle;

            TimelineGroupingByResourcesElement timelineViewGroupedElement = this.radScheduler1.ViewElement as TimelineGroupingByResourcesElement;
            if (timelineViewGroupedElement != null && resourceHeaderCell != null)
            {
                float variationRange = 10;
                float mouseYTopRange = e.Location.Y - variationRange;
                float mouseYBottomRange = e.Location.Y + variationRange;
                float rectBottomY = rect.Y + rect.Height;

                if (rectBottomY >= mouseYTopRange && rectBottomY <= mouseYBottomRange)
                {
                    Cursor.Current = Cursors.SizeNS;
                    isResizeAllowed = true;
                }
                else
                {
                    Cursor.Current = Cursors.Default;
                    isResizeAllowed = false;
                }
            }
            else
            {
                float variationRange = 20;
                float mouseXLeftRange = mouseLocation.X - variationRange;
                float mouseXRightRange = mouseLocation.X + variationRange;
                float rectRightX = rect.X + rect.Width;

                if (rectRightX >= mouseXLeftRange && rectRightX <= mouseXRightRange)
                {
                    if (headerCell != null && !(headerCell.Parent is MonthViewVerticalHeader || headerCell.Parent is MonthViewHeader) ||
                        resourceHeaderCell != null)
                    {
                        Cursor.Current = Cursors.SizeWE;
                        isResizeAllowed = true;
                    }

                }
                else
                {
                    Cursor.Current = Cursors.Default;
                    isResizeAllowed = false;
                }
            }
        }
    }

    private void ResizeViewElementColumns(Point mouseLocation)
    {
        if (headerCell == null)
        {
            return;
        }

        TimeSpan span = TimeSpan.MinValue;
        int column = -1;
        int nextIndex = -1;
        SchedulerViewElement element;

        switch (this.radScheduler1.ActiveViewType)
        {
            case SchedulerViewType.Day:
            case SchedulerViewType.Week:
            case SchedulerViewType.WorkWeek:
                dayViewElement = headerCell.FindAncestor<SchedulerDayViewElement>();
                column = dayViewElement.GetColumnForDate(headerCell.Date);
                element = dayViewElement;
                break;
            case SchedulerViewType.Month:
                monthViewElement = headerCell.FindAncestor<SchedulerMonthViewElement>();
                span = headerCell.Date - headerCell.View.StartDate;
                int weekCount = monthViewElement.GetMonthView().WeekDaysCount;
                column = (int)span.TotalDays - (int)span.TotalDays / weekCount * weekCount;
                element = monthViewElement;
                break;
            case SchedulerViewType.Timeline:
                timeLineViewElement = headerCell.FindAncestor<SchedulerTimelineViewElement>();
                span = headerCell.Date - headerCell.View.StartDate;
                column = (int)span.TotalDays;
                element = timeLineViewElement;
                break;
        }

        if (column < 0)
        {
            return;
        }

        if (!resizeStarted)
        {
            switch (this.radScheduler1.ActiveViewType)
            {
                case SchedulerViewType.Day:
                case SchedulerViewType.Week:
                case SchedulerViewType.WorkWeek:
                    nextHeaderCell = dayViewElement.GetCellAtPosition(0, column + 1) as SchedulerHeaderCellElement;
                    initialSizeFactor = dayViewElement.GetColumnWidth(column);
                    if (nextHeaderCell != null)
                    {
                        initialNextCellFactor = dayViewElement.GetColumnWidth(column + 1);
                    }
                    break;
                case SchedulerViewType.Month:
                    initialSizeFactor = monthViewElement.GetColumnWidth(column);
                    nextIndex = column + 1;
                    if (nextIndex < monthViewElement.Header.CellElements.Count - 1)
                    {
                        nextHeaderCell = monthViewElement.Header.CellElements[nextIndex] as SchedulerHeaderCellElement;
                        initialNextCellFactor = monthViewElement.GetColumnWidth(nextIndex);
                    }
                    break;
                case SchedulerViewType.Timeline:
                    initialSizeFactor = timeLineViewElement.GetColumnWidth(column);
                    nextIndex = column + 1;
                    if (nextIndex < timeLineViewElement.Header.CellElements.Count - 1)
                    {
                        nextHeaderCell = timeLineViewElement.Header.CellElements[nextIndex] as SchedulerHeaderCellElement;
                        initialNextCellFactor = timeLineViewElement.GetColumnWidth(nextIndex);
                    }
                    break;
            }

            resizeStarted = true;
            initialMouseOffset = mouseLocation.X;
            initialSizeInPixels = headerCell.ControlBoundingRectangle.Width;
        }
        else
        {
            float currentMouseXLocation = mouseLocation.X;
            float difference = currentMouseXLocation - initialMouseOffset;
            float newColumnWidthInPixels = initialSizeInPixels + difference;
            float newWidthFactor = initialSizeFactor * (newColumnWidthInPixels / initialSizeInPixels);

            if (newColumnWidthInPixels <= minCellWidthInPixels ||
                (nextHeaderCell != null && nextHeaderCell.ControlBoundingRectangle.Width <= minCellWidthInPixels && currentMouseXLocation >= nextHeaderCell.ControlBoundingRectangle.X))
            {
                return;
            }

            float newNextCellWidthFactor = 0;
            if (nextHeaderCell != null)
            {
                newNextCellWidthFactor = initialSizeFactor + initialNextCellFactor - newWidthFactor;
                float newNextCellWidth = newColumnWidthInPixels * (newNextCellWidthFactor / newWidthFactor);
                if (newNextCellWidth <= minCellWidthInPixels)
                {
                    return;
                }
            }

            switch (this.radScheduler1.ActiveViewType)
            {
                case SchedulerViewType.Day:
                case SchedulerViewType.Week:
                case SchedulerViewType.WorkWeek:
                    dayViewElement.SetColumnWidth(column, newWidthFactor);
                    if (nextHeaderCell != null)
                    {
                        dayViewElement.SetColumnWidth(column + 1, newNextCellWidthFactor);
                    }
                    break;
                case SchedulerViewType.Month:
                    monthViewElement.SetColumnWidth(column, newWidthFactor);
                    if (nextHeaderCell != null)
                    {
                        monthViewElement.SetColumnWidth(column + 1, newNextCellWidthFactor);
                    }
                    break;
                case SchedulerViewType.Timeline:
                    IList<SchedulerTimelineViewElement> childTimelineElements = new List<SchedulerTimelineViewElement>() { timeLineViewElement };
                    TimelineGroupingByResourcesElement timelineViewGroupedElement = this.radScheduler1.ViewElement as TimelineGroupingByResourcesElement;
                    if (timelineViewGroupedElement != null)
                    {
                        childTimelineElements = timelineViewGroupedElement.GetChildViewElements();
                    }

                    foreach (SchedulerTimelineViewElement childTimelineElement in childTimelineElements)
                    {
                        childTimelineElement.SetColumnWidth(column, newWidthFactor);
                        if (nextHeaderCell != null)
                        {
                            childTimelineElement.SetColumnWidth(column + 1, newNextCellWidthFactor);
                        }
                    }
                    break;
            }
        }
    }

    private void ResizeGroupedByResourcesViewElement(Point mouseLocation)
    {
        if (resourceHeaderCell == null)
        {
            return;
        }

        int column = this.radScheduler1.Resources.IndexOf(this.radScheduler1.Resources.GetById(resourceHeaderCell.ResourceId));
        column -= ((SchedulerViewGroupedByResourceElementBase)this.radScheduler1.ViewElement).ResourceStartIndex;

        if (column < 0)
        {
            return;
        }

        if (!resizeStarted)
        {
            if (column < this.radScheduler1.ActiveView.ResourcesPerView)
            {
                int resourceNextCellIndex = int.Parse(resourceHeaderCell.ResourceId.KeyValue.ToString()) + 1;
                resourceNextCell = this.GetResourceHeaderCellByResourceId(resourceNextCellIndex);
            }

            resizeStarted = true;
            TimelineGroupingByResourcesElement timelineViewGroupedElement = this.radScheduler1.ViewElement as TimelineGroupingByResourcesElement;
            if (timelineViewGroupedElement != null)
            {
                initialMouseOffset = mouseLocation.Y;
                initialSizeInPixels = resourceHeaderCell.ControlBoundingRectangle.Height;
            }
            else
            {
                initialMouseOffset = mouseLocation.X;
                initialSizeInPixels = resourceHeaderCell.ControlBoundingRectangle.Width;
            }

            initialSizeFactor = ((SchedulerViewGroupedByResourceElementBase)this.radScheduler1.ViewElement).GetResourceSize(column);
            if (resourceNextCell != null)
            {
                initialNextCellFactor = ((SchedulerViewGroupedByResourceElementBase)this.radScheduler1.ViewElement).GetResourceSize(column + 1);
            }
        }
        else
        {
            float currentMouseOffset = -1;
            float boundingRectangleSize = -1;
            float boundingRectangleOffset = -1;
            TimelineGroupingByResourcesElement timelineViewGroupedElement = this.radScheduler1.ViewElement as TimelineGroupingByResourcesElement;
            if (timelineViewGroupedElement != null)
            {
                currentMouseOffset = mouseLocation.Y;
                boundingRectangleSize = resourceNextCell.ControlBoundingRectangle.Height;
                boundingRectangleOffset = resourceNextCell.ControlBoundingRectangle.Y;
            }
            else
            {
                currentMouseOffset = mouseLocation.X;
                boundingRectangleSize = resourceNextCell.ControlBoundingRectangle.Width;
                boundingRectangleOffset = resourceNextCell.ControlBoundingRectangle.X;
            }

            float difference = currentMouseOffset - initialMouseOffset;

            // You can reduce the resize lag by (un)commenting the following code.
            if (difference % 2 != 0)
            {
                return;
            }

            float newColumnWidthInPixels = initialSizeInPixels + difference;
            float newWidthFactor = initialSizeFactor * (newColumnWidthInPixels / initialSizeInPixels);

            if (newColumnWidthInPixels <= minCellWidthInPixels ||
                (resourceNextCell != null && boundingRectangleSize <= minCellWidthInPixels && currentMouseOffset >= boundingRectangleOffset))
            {
                return;
            }

            float newNextCellWidthFactor = 0;
            if (resourceNextCell != null)
            {
                newNextCellWidthFactor = initialSizeFactor + initialNextCellFactor - newWidthFactor;
                float newNextCellWidth = newColumnWidthInPixels * (newNextCellWidthFactor / newWidthFactor);
                if (newNextCellWidth <= minCellWidthInPixels)
                {
                    return;
                }
            }

                ((SchedulerViewGroupedByResourceElementBase)this.radScheduler1.ViewElement).SetResourceSize(column, newWidthFactor);

            if (resourceNextCell != null)
            {
                ((SchedulerViewGroupedByResourceElementBase)this.radScheduler1.ViewElement).SetResourceSize(column + 1, newNextCellWidthFactor);
            }
        }
    }

    private SchedulerResourceHeaderCellElement GetResourceHeaderCellByResourceId(int resourceId)
    {
        foreach (var item in ((SchedulerViewGroupedByResourceElementBase)this.radScheduler1.ViewElement).ResourcesHeader.Children)
        {
            SchedulerResourceHeaderCellElement resourcesHeaderCellElement = item as SchedulerResourceHeaderCellElement;
            if (resourcesHeaderCellElement != null)
            {
                int resourceIndex = int.Parse(resourcesHeaderCellElement.ResourceId.KeyValue.ToString());
                if (resourceIndex == resourceId)
                {
                    return resourcesHeaderCellElement;
                }
            }
        }

        return null;
    }
}

In this article