BUG: Can’t choose dates on a DatePicker that fall outside a floating VSTO Add-In

“Floating” is the key to the problem here. What’s never not a problem (occasionally responsible for odd things) is relying on the message pump in Excel to dispatch Windows messages, the messages that make these controls respond to input. This goes wrong in WPF as much as Winforms, they have their own dispatch loop that filters messages before they are delivered to the window. Key things that go wrong when their respective dispatcher isn’t used are stuff like tabbing and short-cut keystrokes.

And then some, this kind of problem would be induced by Excel doing its own filtering before dispatching messages. I’d guess at an anti-malware feature, Microsoft is forever worried about programs messing with Office apps.

The Winforms solution is the same one as the WPF workaround, you need to pump your own message loop. That requires some surgery, DateTimePicker isn’t going to cooperate since it doesn’t allow its DropDown event to be cancelled and it is raised after the calendar is already shown. The workaround is silly but effective, add a button to your form that looks just like the dropdown arrow on the DTP and make it overlap the arrow so it gets clicked instead of the arrow.

Some example code for getting the button to overlap the dropdown arrow:

    public Form1() {
        InitializeComponent();
        var btn = new Button();
        btn.BackgroundImage = Properties.Resources.DropdownArrow;
        btn.FlatStyle = FlatStyle.Flat;
        btn.BackColor = Color.FromKnownColor(KnownColor.Window);
        btn.Parent = dateTimePicker1;
        btn.Dock = DockStyle.Right;
        btn.Click += showMonthCalendar;
        dateTimePicker1.Resize += delegate {
            btn.Width = btn.Height = dateTimePicker1.ClientSize.Height;
        };
    }

The Click event handler needs to show a dialog that contains a MonthCalendar:

    private void showMonthCalendar(object sender, EventArgs e) {
        dateTimePicker1.Focus();
        using (var dlg = new CalendarForm()) {
            dlg.DateSelected += new DateRangeEventHandler((s, ea) => dateTimePicker1.Value = ea.Start);
            dlg.Location = dateTimePicker1.PointToScreen(new Point(0, dateTimePicker1.Height));
            dlg.ShowDialog(this);
        }
    }

With CalendarForm a form you add that’s borderless and contains just a MonthCalendar:

public partial class CalendarForm : Form {
    public event DateRangeEventHandler DateSelected;

    public CalendarForm() {
        InitializeComponent();
        this.StartPosition = FormStartPosition.Manual;
        monthCalendar1.Resize += delegate {
            this.ClientSize = monthCalendar1.Size;
        };
        monthCalendar1.DateSelected += monthCalendar1_DateSelected;
    }

    void monthCalendar1_DateSelected(object sender, DateRangeEventArgs e) {
        if (DateSelected != null) DateSelected(this, e);
        this.DialogResult = DialogResult.OK;
    }
}

Leave a Comment