Setting calendar size when overriding DateTimePicker to add week numbers

c#, datetimepicker, winforms

Solution

Finally found a solution that seems to work - at least for now.

It seems there are two windows in the calendar part of the `DateTimePicker`. Apparently my code would automatically find the correct size for the inner one (more or less at least?), but not the outer one.

A bit of research has led to the code below. The following links provide some useful and relevant info:

- GetWindowLong function (Used for getting info about the window to edit)

- GetParent function (Finding the outer window, so we could apply settings to that too)

The trick was to add a little to the height and width of the (inner) window, then apply the same height and width to the outer window (which I access using the `GetParrent()` function). I found the "correct" size by trial and error: When the size matched what was needed for the contents of the calendar, it could not be resized any longer.

Yes, this feels a little like a hack, and no, I haven't been able to verify that it works perfectly on other computers than my own yet. I'm a little worried about having to give specific values for height and width, but I'm hoping this won't be affected by screen resolutions or whatever else.

Hope someone else in a similar situation will find the code useful. (The following can directly replace a regular `DateTimePicker` to show week numbers in the calendar)

using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class DatePickerWithWeekNumbers : DateTimePicker
{
    [DllImport("User32.dll")]
    private static extern int GetWindowLong(IntPtr handleToWindow, 
                                            int offsetToValueToGet);

    [DllImport("User32.dll")]
    private static extern int SetWindowLong(IntPtr h, 
                                            int index, 
                                            int value);

    private const int McmFirst = 0x1000;
    private const int McmGetminreqrect = (McmFirst + 9);
    private const int McsWeeknumbers = 0x4;
    private const int DtmFirst = 0x1000;
    private const int DtmGetmonthcal = (DtmFirst + 8);


    [DllImport("User32.dll")]
    private static extern IntPtr SendMessage(IntPtr h,
                                             int msg, 
                                             int param, 
                                             int data);

    [DllImport("User32.dll")]
    private static extern IntPtr GetParent(IntPtr h);

    [DllImport("User32.dll")]
    private static extern int SendMessage(IntPtr h, 
                                          int msg,
                                          int param, 
                                          ref Rectangle data);

    [DllImport("User32.dll")]
    private static extern int MoveWindow(IntPtr h, 
                                         int x, 
                                         int y,
                                         int width, 
                                         int height, 
                                         bool repaint);

    [Browsable(true), DesignerSerializationVisibility(
        DesignerSerializationVisibility.Visible)]
    public bool DisplayWeekNumbers { get; set; }

    protected override void OnDropDown(EventArgs e)
    {
        // Hex value to specify that we want the style-attributes
        // for the window:
        const int offsetToGetWindowsStyles = (-16);

        IntPtr pointerToCalenderWindow = SendMessage(Handle, 
                                                     DtmGetmonthcal,  
                                                     0,  
                                                     0);
        int styleForWindow = GetWindowLong(pointerToCalenderWindow, 
                                           offsetToGetWindowsStyles);

        // Check properties for the control - matches available 
        // property in the graphical properties for the DateTimePicker:
        if (DisplayWeekNumbers)
        {
            styleForWindow = styleForWindow | McsWeeknumbers;
        }
        else
        {
            styleForWindow = styleForWindow & ~McsWeeknumbers;
        }

        // Get the size needed to display the calendar (inner window)
        var rect = new Rectangle();
        SendMessage(pointerToCalenderWindow, McmGetminreqrect, 0, ref rect);

        // Add to size as needed (I don't know why 
        // this was not correct initially!)
        rect.Width = rect.Width + 28;
        rect.Height = rect.Height + 6;

        // Set window styles..
        SetWindowLong(pointerToCalenderWindow, 
                      offsetToGetWindowsStyles, 
                      styleForWindow);

        // Dont move the window - just resize it as needed:
        MoveWindow(pointerToCalenderWindow, 
                   0, 
                   0, 
                   rect.Right, 
                   rect.Bottom, 
                   true);

        // Now access the parrent window..
        var parentWindow = GetParent(pointerToCalenderWindow);
        // ...and resize that the same way:
        MoveWindow(parentWindow, 0, 0, rect.Right, rect.Bottom, true);

        base.OnDropDown(e);
    }
}

Problem

I'm trying to create a `DateTimePicker` with week numbers displayed, as shown here (Code project example). It works fairly well, except for one minor detail; The calender popping up when trying to select a date is not the right size. As you can see, the calendar area is sort of "cramped", especially along the right edge. I can click the bottom right corner here, and drag it out a little - just enough to expand it so that it looks right: I can't seem to find any way to force the calendar to be the correct/full size from the beginning, or to resize it.

Original source