8

I have a user control which has a DateTimePicker overlaid with the single line text box(Tb)
covering the text part of DateTimePicker. The textbox Tb (green) is anchored (L T R B).

Datepicker  overlaid with textbox

The problem comes when the usercontrol is resized.
The overlaid datetimepicker shows up behind the text Tb.

The overlaid datetimepicker shows up behind the green textbox Tb.

Currently the user control is 242(W) x 20(H) . Problems shows up when control is resized below 100 (Width) .

I tried the blank the text with CustomFormat set to " " but the user control supports many of the datetimepicker functionalities so that was not an option.

I tried to set the minimum size on the user control but that doesn't work too.

Edit:
The control is already in use in few applications.In those applications the minimum size of the control is 90X20 whereas the minimum size which we set now is 97x20.
Will VS designer size the existing controls to 97x20 after making the change?
How to achieve this effect(re-size to 97x20)?

Edit ****code after adding minimum size****

namespace WindowsFormsApplication1
{
    partial class CDatePicker
    {
        private System.ComponentModel.IContainer components = null;


        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code


        private void InitializeComponent()
        {
            this.splitContainer1 = new System.Windows.Forms.SplitContainer();
            this.datepanel = new System.Windows.Forms.Panel();
            this.datetxt = new System.Windows.Forms.TextBox();
            this.dateTimePicker1 = new System.Windows.Forms.DateTimePicker();
            this.timepanel = new System.Windows.Forms.Panel();
            this.timetxt = new System.Windows.Forms.TextBox();
            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
            this.splitContainer1.Panel1.SuspendLayout();
            this.splitContainer1.Panel2.SuspendLayout();
            this.splitContainer1.SuspendLayout();
            this.datepanel.SuspendLayout();
            this.timepanel.SuspendLayout();
            this.SuspendLayout();
            // 
            // splitContainer1
            // 
            this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.splitContainer1.Location = new System.Drawing.Point(0, 0);
            this.splitContainer1.Name = "splitContainer1";
            // 
            // splitContainer1.Panel1
            // 
            this.splitContainer1.Panel1.Controls.Add(this.datepanel);
            this.splitContainer1.Panel1MinSize = 105;
            // 
            // splitContainer1.Panel2
            // 
            this.splitContainer1.Panel2.Controls.Add(this.timepanel);
            this.splitContainer1.Size = new System.Drawing.Size(236, 20);
            this.splitContainer1.SplitterDistance = 178;
            this.splitContainer1.SplitterWidth = 1;
            this.splitContainer1.TabIndex = 0;
            // 
            // datepanel
            // 
            this.datepanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
            | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.datepanel.Controls.Add(this.datetxt);
            this.datepanel.Controls.Add(this.dateTimePicker1);
            this.datepanel.Location = new System.Drawing.Point(0, 0);
            this.datepanel.Margin = new System.Windows.Forms.Padding(0);
            this.datepanel.Name = "datepanel";
            this.datepanel.Size = new System.Drawing.Size(175, 20);
            this.datepanel.TabIndex = 0;
            // 
            // datetxt
            // 
            this.datetxt.BackColor = System.Drawing.Color.Gainsboro;
            this.datetxt.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.datetxt.Location = new System.Drawing.Point(0, 0);
            this.datetxt.MinimumSize = new System.Drawing.Size(60, 20);
            this.datetxt.Name = "datetxt";
            this.datetxt.Size = new System.Drawing.Size(71, 20);
            this.datetxt.TabIndex = 3;
            this.datetxt.Text = "date";
            // 
            // dateTimePicker1
            // 
            this.dateTimePicker1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.dateTimePicker1.Format = System.Windows.Forms.DateTimePickerFormat.Short;
            this.dateTimePicker1.Location = new System.Drawing.Point(0, 0);
            this.dateTimePicker1.Margin = new System.Windows.Forms.Padding(0);
            this.dateTimePicker1.MinimumSize = new System.Drawing.Size(65, 20);
            this.dateTimePicker1.Name = "dateTimePicker1";
            this.dateTimePicker1.Size = new System.Drawing.Size(175, 20);
            this.dateTimePicker1.TabIndex = 2;
            this.dateTimePicker1.Value = new System.DateTime(2012, 11, 15, 0, 0, 0, 0);
            // 
            // timepanel
            // 
            this.timepanel.Controls.Add(this.timetxt);
            this.timepanel.Dock = System.Windows.Forms.DockStyle.Fill;
            this.timepanel.Location = new System.Drawing.Point(0, 0);
            this.timepanel.Margin = new System.Windows.Forms.Padding(0);
            this.timepanel.Name = "timepanel";
            this.timepanel.Size = new System.Drawing.Size(57, 20);
            this.timepanel.TabIndex = 0;
            // 
            // timetxt
            // 
            this.timetxt.Dock = System.Windows.Forms.DockStyle.Fill;
            this.timetxt.Location = new System.Drawing.Point(0, 0);
            this.timetxt.Name = "timetxt";
            this.timetxt.Size = new System.Drawing.Size(57, 20);
            this.timetxt.TabIndex = 1;
            this.timetxt.Text = "time";
            this.timetxt.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
            // 
            // CDatePicker
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.AutoSize = true;
            this.Controls.Add(this.splitContainer1);
            this.MaximumSize = new System.Drawing.Size(236, 20);
            this.MinimumSize = new System.Drawing.Size(100, 20);
            this.Name = "CDatePicker";
            this.Size = new System.Drawing.Size(236, 20);
            this.splitContainer1.Panel1.ResumeLayout(false);
            this.splitContainer1.Panel2.ResumeLayout(false);
            ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit();
            this.splitContainer1.ResumeLayout(false);
            this.datepanel.ResumeLayout(false);
            this.datepanel.PerformLayout();
            this.timepanel.ResumeLayout(false);
            this.timepanel.PerformLayout();
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.SplitContainer splitContainer1;
        private System.Windows.Forms.Panel datepanel;
        private System.Windows.Forms.TextBox datetxt;
        private System.Windows.Forms.DateTimePicker dateTimePicker1;
        private System.Windows.Forms.Panel timepanel;
        private System.Windows.Forms.TextBox timetxt;
    }
}

Edit : After adding min sizes, it looks better but the calender drop down disappears
when the control is resized to min and the project is rebuilt..
Also the text box doesn't remain anchored near the calender button..

edit: pic added for above..calender disappears.

date calender disappears

Edit : Oh i missed one more thing both the date field and time field are collapsible.thats why the split container is there. Minimum size calculations must also be aware of this.(i guess 2 different min. sizes depending on visibility of time field.)

Questions:

a) How can i prevent the usercontrol from resizing beyond certain min limit to avoid above problem? (pic1 seems to be idea size)

b) Can i draw the datetimepicker such that the text is blanked out and we only see the
calender dropdown button? If so will i still need to handle resize events?

c) How can i keep resizing the textbox so that it always covers the date text part of DTP.

Amitd
  • 4,769
  • 8
  • 56
  • 82
  • What do you mean by *"I tried to set the minimum size on the user control but that doesnt work too"*? It should force the control to resize only to a certain minimum limit. What exactly didn't work? – alex Apr 05 '12 at 05:54
  • From the Visual studio designer i had set the Minimum size on the usercontrol .. but when the control is placed on a form ,the end user can change that min size to (0,0) and then the control can be resized below the minimum size(as you see in the 2nd pic) – Amitd Apr 05 '12 at 06:07
  • Can I ask why your overlaying a textbox control on the DateTimePicker (dtp), I haven't delve down into the control but I could guess the dtp would be a textbox with a dropdown calendar control. I could be wrong but it seems like you might want to make your own usercontrol going off the dtp control - use reflector or dotpeek on the System.Windows.Forms dll – Jeremy Thompson Apr 05 '12 at 06:11
  • I tried setting the MinimumSize on a textbox to (100; 0) and it works. The textbox can't be shrunk any lower than 100 px. How can you get it lower than that? – alex Apr 05 '12 at 06:14
  • @JeremyThompson I have no clue actually.. was a client requirement ..And it is been used by few products..this is another new change. – Amitd Apr 05 '12 at 06:54
  • @alex The controls are placed in a panel with datepicker(dock fill) overlaid with textbox(no dock,anchored on all).and the panel is docked 'fill' in the usercontrol...then resize the usercontrol after palcing it on the form.. – Amitd Apr 05 '12 at 06:57
  • If it's docked the minimum size won't work. You could manually resize the textbox to fill the panel, make the DateTimePicker the same size as the textbox (and the panel), set the anchors to top, left and right and set the minimum size of the textbox to whatever you want. – alex Apr 05 '12 at 09:38
  • @alex i have added sample code..pls can u suggest from that? – Amitd Apr 05 '12 at 15:25
  • 1
    You are having this problem because DTP dynamically lays out its appearance, dropping parts of the button on the right when there's not enough space. You can do something about it by pinvoking GetThemeRect(). – Hans Passant Apr 05 '12 at 16:59
  • @HansPassant yes thats one of the problems but how can restrict the usercontrol such that dtp always has enough space? – Amitd Apr 05 '12 at 17:23
  • I don't know why MinimumSize doesn't work or why you even use a UserControl instead of just deriving from DateTimePicker. But you can always override SetBoundsCore() to force a minimum size. – Hans Passant Apr 05 '12 at 17:29
  • @HansPassant i have added min sizes ..also added edit below it? – Amitd Apr 05 '12 at 19:02
  • Are you want to clear the date time in datetimepicker ? Whats ur exact requirement? – Even Apr 06 '12 at 06:08
  • it should function similar to the DTP but textbox will show the date and its has few other things present in it..but by appearance should look and behave like a DTP.. pls see the code i posted.. – Amitd Apr 06 '12 at 06:29
  • 1
    What are trying to achieve? Is it use the DatePicker's drop down, but have a custom display format? Obviously DatePicker.CustomFormat is no use to you or you wouldn't be asking this question. Will the displayed date be read-only or need to be editable via the keyboard? – Phil Apr 07 '12 at 13:47
  • exactly..you are right..yes the green textfield is editable by the user via keyboard.also the time field too.pls check the code i posted to see how it looks. – Amitd Apr 07 '12 at 14:03

2 Answers2

4

You have a simple problem and a hard problem. The simple problem is evident from the screenshots. The DateTimePicker is not resizing correctly, note how the dropdown button is getting clipped on the right-hand side. The UserControl is too strange to really pinpoint the problem but I think it is the datePanel. You are using Anchor.Right instead of Dock.Fill.

The hard problem is that the dropdown button resizes itself dynamically, based on the amount of space that's available to render the DTP content. At least on Windows 7, earlier versions use a different rendering strategy. Sadly, the VisualStyles api doesn't return the actual size of the button. The only decent way to deal with it is to make sure the textbox is large enough to cover the icon so only the dropdown arrow is visible.

I can't do much with the UserControl, I'll propose a much simpler solution that just derives from the DateTimePicker class and embeds a TextBox inside of it. Also a lot cheaper at runtime:

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

class MyDateTimePicker : DateTimePicker {
    private TextBox editbox;
    private int buttonWidth;

    public MyDateTimePicker() {
        editbox = new TextBox();
        editbox.BorderStyle = BorderStyle.None;
        editbox.BackColor = Color.Gold;   // debugging
        this.Controls.Add(editbox);
    }

    public override Font Font {
        get { return base.Font; }
        set { base.Font = editbox.Font = value; }
    }

    protected override void OnResize(EventArgs e) {
        if (buttonWidth == 0) measureButtonWidth();
        var margin = (this.ClientSize.Height - editbox.PreferredHeight) / 2;
        editbox.Location = new Point(margin, margin);
        editbox.Width = this.ClientSize.Width - margin - buttonWidth;
        base.OnResize(e);
    }

    private void measureButtonWidth() {
        if (!Application.RenderWithVisualStyles) buttonWidth = 21;   // TODO: measure
        else {
            var renderer = new VisualStyleRenderer("DATEPICKER", 3, 1);
            using (var gr = CreateGraphics()) {
                buttonWidth = renderer.GetPartSize(gr, ThemeSizeType.True).Height;
            }
        }
    }

    protected override void Dispose(bool disposing) {
        if (disposing) editbox.Dispose();
        base.Dispose(disposing);
    }
}

The OnResize override takes care of keeping the TextBox the right size. Add whatever code you have to process the editbox.Text property. The only detail not taken care of is the size of the dropdown button when visual styles are disabled. Uncommon these days but still possible. Turn off theming on your machine and tweak the hard-coded size so it matches the appearance.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • thx @Hans Passant ... I ended up following your approach .. much more simpler and easier to understand. Bit of mix and match. – Amitd Apr 11 '12 at 18:21
  • Another interesting thing i found out was this SO question http://stackoverflow.com/questions/154543/panel-dock-fill-ignoring-other-panel-dock-setting – Amitd Apr 11 '12 at 18:35
0

I don't think you are going to get a good result by overlaying the DateTimePicker with a text box. There are always going to be resize and focus issues.

I would use one of these options:

  • Use a third party control like DevExpress PopupContainerEdit. You could then popup the standard Windows Forms MonthCalendar and customize the displayed text as required. I'm sure similar controls are available from Telerik, etc.
  • Use or customize a similar drop down container control from somewhere like the Code Project. There are at least two you could use as inspiration:

  • You could even adapt the drop down part of my colour picker on the Code Project

A final option, that would give you more flexibility, is move from Windows Forms to WPF. Creating custom controls like this is so much easier (well after the steep learning curve anyway).

Phil
  • 42,255
  • 9
  • 100
  • 100