XtraSchedulerReport and the CalendarControl

XtraSchedulerReport
Achieving prev and next month calendars in report

My Scout Region does up a printed calendar every year based on inputs from their GSuite Google Calendar.

I was asked if I could look after it and naturally, I thought this would be an interesting exercise to use the DevExpress XtraScheduler and the Google Calendar API v3 to pull this together.

Google Api

Firstly, the Google Api is quite handy, it does the OAuth2 heavy lifting and to get started it is quick, however when it comes to Google .NET implmentation and it's way of dealing with it's API, there is a couple of things to watch out for, I will do up a separate blog post on it.

DevExpress XtraScheduler

The DevExpress XtraScheduler control is powerful, but with great power comes great responsibility. Using E502 example I created a quick WinForms app with a XtraScheduler control and a AppointmentSynchronizer which ensures the Google Calendar is updated with Appointments are touched. I will be posting more blogs on this app and running through the Google Api and how I have intergrated it with the XtraScheduler.

This blog is mainly about the CalendarControl which is a printable control within a custom XtraSchedulerReport

XtraSchedulerReport

You can use the pre-defined Styles within XtraScheduler to print, however you do have the option to create your own using the XtraSchedulerReport. It is derived from the DevExpress XtraReport suite and like XtraReports is very flexible.

In my report I wanted to show a Last Month and Next Month calender in the header. The reason for this is that commonly when you are talking about the common month, you sometimes go, We did that 2 weeks ago what date was that? and it has fallen into last month so you turn back and forward between the pages.

The default functionality of the CalendarControl is to show the current month, if you make it wider it can show the current month but also if it can fit more calendars in it will continue to show future months.

Start up Visual Studio and create a new XtraSchedulerReport, Add Item > DevExpress v16.1 Template Gallery

 

Then select the Scheduler Report, I find it easier to just type schedule in the top right hand corner to search the templates as DX include alot of item templates in their suite.

 

Name it and Add it to your solution.

 

Now by default a blank report will show up.

 

Right click on a blank space outside the report and select Load Template...

 

Go down and select Scheduler Report - Month View and click Load

 

You will see the default way the calendar works within XtraSchedulerReports, the width of the calendar is allowing 2 to be printed. The current month (which is already in view in the main month view) and the next month. Personally I understand why it works this way as if you are on a day view or week view, then having the current month in the calendar works. However, when displaying the Month View it doesn't make sense to duplicate the current month in a calendar above.

This is what I want to achieve.

 

After initial investigations there doesn't appear to be anyway to do this out of the box, so we need to break that box down and change it :)

Source Code Hunting

So, whenever you want to extend/change functionality of a control the best thing you can do is go through the source code and understand how the control operates to achieve it's values.

Those with an appropriate DX License get the source code with their subscription, so one method is to literally go through the source code. However, using a Decompiler is the easiest way.

CodeRush includes a Decompiler so you can use that, however I haven't used it much as I already had and used Reflector Pro.

Using your decompiler to look at source we can see a couple of things.

 

The PrintTimeInterval property looks promising and the associated GetPrintTimeInterval() method. Looking at these members however we find that although they are set and they are used when checking if the PrintInfo should be recalculated but that is it, so, what is being used to work out the print interval. We get a clue in the GetPrintTimeIntervalMethod()

protected TimeInterval GetPrintTimeInterval()
{
    ISupportPrintableTimeInterval timeCells = this.TimeCells;
    if (timeCells == null)
    {
        return TimeInterval.Empty;
    }
    return timeCells.GetPrintTimeInterval(PrintContentMode.CurrentColumn);
}

So timeCells.GetPrintTimeInterval() seems to be what returns the interval to print.

Lets have a look at the CalculatePrintInfo() method

protected override ControlPrintInfo CalculatePrintInfo(ControlLayoutInfo info)
{
    return this.CalculatePrintInfoCore(info);
}

So this is just calling the CalculatePrintInfoCore() method which is

protected virtual CalendarPrintInfo CalculatePrintInfoCore(ControlLayoutInfo info)
{
    CalendarPrintInfo info2 = new CalendarPrintInfo(this, this.GetSchedulerAdapter(), this.TimeCells, info.ControlPrintBounds) {
        DateNavigator = { CalendarIndent = this.CalendarIndent }
    };
    CustomDrawDayNumberCellEventHandler handler = base.Events[CustomDrawDayNumberCellEvent] as CustomDrawDayNumberCellEventHandler;
    if (handler != null)
    {
        info2.DateNavigator.CustomDrawDayNumberCell += handler;
    }
    if (!base.IsDesignMode)
    {
        info2.BoldAppointmentDates();
    }
    return info2;
}

OK, now this looks interesting. We can see that there is a new CalendarPrintInfo being initiated. Lets look at the constructor

public CalendarPrintInfo(CalendarControl control, SchedulerPrintAdapter schedulerAdapter, ISupportPrintableTimeInterval supportTimeInterval, Rectangle bounds) : base(control)

 

So we can see that a ISupportPrintableTimeInterval is being passed through, however when we look at the CalculatePrintInfoCore code, it is passing a this.TimeCells which is a TimeCellsControlBase which supports that interface. When we look at the interface it is has one method

public interface ISupportPrintableTimeInterval
{
    // Methods
    TimeInterval GetPrintTimeInterval(PrintContentMode displayMode);
}

I tried for a while to work out ways to manipulate the TimeCells property however that affects everything on the report as it is the HorizontalWeek component which provides what dates to display. We need to just pass a different date to the CalendarPrintInfo constructor.

Time to inherit and override some things.

Inheriting and Overriding

So quite simply I created a new Class in my project and then inherited the DevExpress.XtraScheduler.Reporting.CalendarControl. It is at this point that you start to look at what members you have access too. When engineering component suites, manufacturers like DevExpress have to be careful what they expose so that they can control what can be changed.

DX do a great job at giving access to extend functionality of their suite, one of the biggest advantages is how much they extend their own controls within the different products. For example, this Reporting CalendarControl is actually created a XtraEditors.CalendarControl and then using the functionality of that to add bold appointments, special days etc and getting the printable representation of it. This means you don't have things looking different across products within the suite as well as any work effort that goes into a product benefits the other products.

The solution I have found so far is to override the CalculatePrintInfoCore method and implement the ISupportPrintableTimeInterval interface on the inherited CalendarControl itself.

public class AlfCalendarControl : DevExpress.XtraScheduler.Reporting.CalendarControl, ISupportPrintableTimeInterval
    {
        public AlfCalendarControl() : base()
        {

        }
        public AlfCalendarControl(DevExpress.XtraScheduler.Reporting.ReportViewBase view)
            : base(view)
        {
            
        }
        private int monthAdjustment;
        public int MonthAdjustment
        {
            get
            {
                return monthAdjustment;
            }
            set
            {
                monthAdjustment = value;
            }
        }

        protected override CalendarPrintInfo CalculatePrintInfoCore(ControlLayoutInfo info)
        {
            if (DesignMode)
                return base.CalculatePrintInfoCore(info);

            var newResult = new CalendarPrintInfo(this, this.SchedulerReport.SchedulerAdapter, this, info.ControlPrintBounds);

            return newResult;
        }

        public TimeInterval GetPrintTimeInterval(PrintContentMode displayMode)
        {
            var result = ((ISupportPrintableTimeInterval)TimeCells).GetPrintTimeInterval(PrintContentMode.CurrentColumn);
            result.Start = result.Start.AddMonths(MonthAdjustment);
            result.End = new DateTime(result.Start.Year, result.Start.Month, DateTime.DaysInMonth(result.Start.Year, result.Start.Month), 23, 59, 59);

            return result;
        }
    }

There is some issues with this implementation, firstly, I don't bold the appointments of the CalendarControl and secondly, no support for CustomDrawDayNumberCell.

This would technically be possible by using Reflection to access of internal members to access the DateNavigator on the CalendarControl to add the handler for CustomDrawDayNumberCell and the internal method for BoldAppointmentDates() which is on the CalendarPrintInfo class.

DevExpress Support

So, this is being a little daring by posting before I actually see the response however, experience tells me DX is very good with their support and they usually are very responsive to extending the library (so long as there isn't any breaking changes or complexity issues).

I have posted my question and then subsequent investigation findings on this support ticket T437065. I recommend if you come across something that you can't figure out then definitely post on the DX Support Center as their support staff are very responsive and do endeavor to give you a solution.

Any Questions

This is my first DX blog on my new site, please don't hesitate to drop me a comment below if you have any questions or if you have something else that you are trying to achieve. I am available for contract/consulting work as well.

About the author

Michael Proctor is the owner of ALFWare and provides consulting/contracting services to the technology and mining industries.

comments powered by Disqus

Providing a powerful set a components across a diverse range of technologies you will find what you need for your next project whether it be WinForms, WPF, Silverlight, WinRT, Windows
Universal, ASP.NET WebForns, MVC Extensions or HTML5 Controls.

eXpress Application Framework (XAF) is a RAD style development platform based on their components.

CodeRush is a powerful IDE productivity tool which speeds up your development and makes you more efficient.