简体   繁体   中英

Cannot access a disposed object “MonthCalendar”

I have this code to create and show a form with monthcalendar control on it.

private void showcalendar_Click(object sender, EventArgs e)
{
    ShowCalendar();
}

void ShowCalendar()
{
    DateTime current5 = DateTime.Now.AddDays(-5);

    MonthCalendar cal = new MonthCalendar();
    Panel panel = new Panel();
    Form f = new Form();

    cal.MaxSelectionCount = 1;
    cal.SetDate(current5);
    cal.DateSelected += new DateRangeEventHandler(DateSelected);
    cal.ShowToday = true;
    panel.Width = cal.Width;
    panel.Height = cal.Height;
    panel.BorderStyle = BorderStyle.FixedSingle;
    panel.Controls.Add(cal);
    f.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
    f.ShowInTaskbar = false;
    f.Size = panel.Size;
    f.Location = MousePosition;
    f.StartPosition = FormStartPosition.Manual;
    f.Controls.Add(panel);
    f.Deactivate += delegate { f.Close(); };
    f.Show();
}

void DateSelected(object sender, DateRangeEventArgs e)
{
    MonthCalendar cal = (MonthCalendar)sender;
    Form f = cal.FindForm();
    f.Close();
}

When I invoke ShowCalendar monthcalendar control is displayed and I can select date within it. The problem is that when I click on a certain area(the lowest one with current date depicted) I'm getting an exception - "Cannot access a disposed object. Object name: 'MonthCalendar'." I don't know how this exception arises at all and how to get rid of it. Maybe you have any thoughts?

My application is not multithreaded, just simple form with a button which invokes ShowCalendar function.

选定区域的日历

I have a quick solution in 3 steps.

Fixes and enhancements:

  • Rectangle dynamic size fixed under different versions of windows.
  • Validate if principal form is topmost.
  • Unload calendar form from memory without bug.
  • Good behavior between MonthCalendar and MaskedTextBox controls

Steps:

1) Create a new windows forms application, view code in form1 and replace all text with this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Globalization;
using System.Threading;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        private System.Windows.Forms.CheckBox chkShowWeeksNumbers;
        private System.Windows.Forms.CheckBox chkThisFormTopMost;
        private System.Windows.Forms.MaskedTextBox maskedInputBox;
        private System.Windows.Forms.Button btnShowFloatingCalendar;

        public Form1()
        {
            this.chkShowWeeksNumbers = new System.Windows.Forms.CheckBox();
            this.chkThisFormTopMost = new System.Windows.Forms.CheckBox();
            this.maskedInputBox = new System.Windows.Forms.MaskedTextBox();
            this.btnShowFloatingCalendar = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // chkShowNumbersOfWeeks
            // 
            this.chkShowWeeksNumbers.AutoSize = true;
            this.chkShowWeeksNumbers.Location = new System.Drawing.Point(18, 116);
            this.chkShowWeeksNumbers.Name = "chkShowWeeksNumbers";
            this.chkShowWeeksNumbers.Size = new System.Drawing.Size(137, 17);
            this.chkShowWeeksNumbers.TabIndex = 1;
            this.chkShowWeeksNumbers.Text = "Show number of weeks";
            this.chkShowWeeksNumbers.UseVisualStyleBackColor = true;
            // 
            // chkThisFormTopMost
            // 
            this.chkThisFormTopMost.AutoSize = true;
            this.chkThisFormTopMost.Location = new System.Drawing.Point(18, 139);
            this.chkThisFormTopMost.Name = "chkThisFormTopMost";
            this.chkThisFormTopMost.Size = new System.Drawing.Size(124, 17);
            this.chkThisFormTopMost.TabIndex = 2;
            this.chkThisFormTopMost.Text = "This form TopMost";
            this.chkThisFormTopMost.UseVisualStyleBackColor = true;
            this.chkThisFormTopMost.CheckedChanged += new EventHandler(chkThisFormTopMost_CheckedChanged);
            // 
            // maskedInputBox
            // 
            this.maskedInputBox.Location = new System.Drawing.Point(18, 53);
            this.maskedInputBox.Mask = "00/00/0000 00:00";
            this.maskedInputBox.Name = "maskedInputBox";
            this.maskedInputBox.Size = new System.Drawing.Size(115, 20);
            this.maskedInputBox.TabIndex = 3;
            this.maskedInputBox.ValidatingType = typeof(System.DateTime);
            // 
            // btnShowFloatingCalendar
            // 
            this.btnShowFloatingCalendar.Location = new System.Drawing.Point(139, 49);
            this.btnShowFloatingCalendar.Name = "btnShowFloatingCalendar";
            this.btnShowFloatingCalendar.Size = new System.Drawing.Size(65, 27);
            this.btnShowFloatingCalendar.TabIndex = 4;
            this.btnShowFloatingCalendar.Text = "Calendar";
            this.btnShowFloatingCalendar.UseVisualStyleBackColor = true;
            this.btnShowFloatingCalendar.Click += new EventHandler(btnShowFloatingCalendar_Click);

            // 
            // Form1
            // 
            this.Controls.Add(this.btnShowFloatingCalendar);
            this.Controls.Add(this.maskedInputBox);
            this.Controls.Add(this.chkThisFormTopMost);
            this.Controls.Add(this.chkShowWeeksNumbers);

            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //DateTime format using in United States
            //More info: http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo%28v=vs.71%29.aspx
            //           http://msdn.microsoft.com/en-us/library/5hh873ya.aspx

            Thread.CurrentThread.CurrentCulture = new CultureInfo(0x0409);

            CultureInfo cultureInfoUSA = new CultureInfo(0x0409, false);
            this.maskedInputBox.Culture = cultureInfoUSA;
        }

        //Constructor
        clsMonthCalendarBehavior userCalendar = new clsMonthCalendarBehavior();

        private void btnShowFloatingCalendar_Click(object sender, EventArgs e)
        {           
            userCalendar.ShowCalendar(this.maskedInputBox,
                                      this.chkShowWeeksNumbers.Checked,
                                      this.chkThisFormTopMost.Checked);
        }

        private void chkThisFormTopMost_CheckedChanged(object sender, EventArgs e)
        {
            this.TopMost = this.chkThisFormTopMost.Checked;
        }
    }
}

2) Add new class item into project and named clsMonthCalendarBehavior.cs , later replace all text with this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace WindowsFormsApplication1
{
    class clsMonthCalendarBehavior
    {
        private bool manualDateTimeIsDone
        {
            get
            {
                return (SetDateTimeManual(this._dateTimeInput.Text));
            }
        }

        private static DateTime dateTimeManual;

        //Determine if the user inserting a correctly date and time
        internal static bool SetDateTimeManual(string inputReference)
        {
            DateTime newDateTime = new DateTime(2000, 1, 1, 0, 0, 0);

            bool isDateTime = DateTime.TryParse(inputReference, out newDateTime);

            if (isDateTime)
                dateTimeManual = newDateTime;

            return (isDateTime ? true : false);
        }

        private MaskedTextBox _dateTimeInput;

        internal void ShowCalendar(MaskedTextBox dateTimeInput,
                                   bool showNumbersOfWeeks,
                                   bool principalFormIsTopMost)
        {
            MonthCalendar monthCalendarCustomized = new MonthCalendar();
            Panel popupPanel = new Panel();
            Form floatingForm = new Form();

            this._dateTimeInput = dateTimeInput;

            //OPTIONAL: Show week numbers
            monthCalendarCustomized.ShowWeekNumbers = showNumbersOfWeeks;
            monthCalendarCustomized.MaxSelectionCount = 1;

            if (manualDateTimeIsDone)
                monthCalendarCustomized.SetDate(dateTimeManual); //User, date and time selected
            else
                monthCalendarCustomized.SetDate(DateTime.Now); //System, actual date and time

            monthCalendarCustomized.DateSelected += new DateRangeEventHandler(DateSelected);
            monthCalendarCustomized.KeyDown +=new KeyEventHandler(KeyDown);

            monthCalendarCustomized.ShowToday = true;

            //IDEA: bolded dates about references, etc.
            monthCalendarCustomized.BoldedDates = new DateTime[]
            {
                DateTime.Today.AddDays(1),
                DateTime.Today.AddDays(2),
                DateTime.Today.AddDays(7),
                DateTime.Today.AddDays(31),
                DateTime.Today.AddDays(10)
            };

            popupPanel.BorderStyle = BorderStyle.FixedSingle;

            popupPanel.Controls.Add(monthCalendarCustomized);

            floatingForm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            floatingForm.ShowInTaskbar = false;

            floatingForm.Location = Control.MousePosition;
            floatingForm.StartPosition = FormStartPosition.Manual;

            floatingForm.Controls.Add(popupPanel);
            floatingForm.Deactivate += delegate { floatingForm.Close(); };

            //NOTE: if principal from is topmost, cannot show in front "floatingForm" with calendar 
            //      this option  fix the situation.
            floatingForm.TopMost = principalFormIsTopMost;

            //NOTE: set initial size of controls.
            floatingForm.Size = popupPanel.Size = new Size(20, 20);

            floatingForm.Show();

            popupPanel.Size = floatingForm.Size = monthCalendarCustomized.Size;

            popupPanel.Width = popupPanel.Width + 2;
            popupPanel.Height = popupPanel.Height + 2;

            floatingForm.Width = floatingForm.Width + 3;
            floatingForm.Height = floatingForm.Height + 3;
        }

        void DateSelected(object sender, DateRangeEventArgs e)
        {
            //Set data selected with culture info mask
            this._dateTimeInput.Text = SetTimeValue(e.Start).ToString("MM/dd/yyyy HH:mm");

            CloseFloatingForm(sender);
        }

        private static void CloseFloatingForm(object sender)
        {
            MonthCalendar monthCalendarCustomized = (MonthCalendar)sender;
            Form floatingForm = monthCalendarCustomized.FindForm();

            monthCalendarCustomized.Parent.Controls.Remove(monthCalendarCustomized);

            floatingForm.Close();
        }

        private DateTime SetTimeValue(DateTime selectedDateTime)
        {
            //Recovery time of after selection, because when user select a new date
            //Month Calendar reset the time
            if (manualDateTimeIsDone)
            {
                TimeSpan addTimeValue = new TimeSpan(dateTimeManual.Hour,
                                                     dateTimeManual.Minute,
                                                     dateTimeManual.Second);

                selectedDateTime = selectedDateTime.Add(addTimeValue);
            }

            return (selectedDateTime);
        }

        private void KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
                CloseFloatingForm(sender);                
        }
    }
}

3) Run and test.

An interesting problem: the only way I could find to make it work is to keep the popup form as a property of the Main form and use Hide() instead of Close().

public partial class Form1 : Form
    {
        Form f = new Form();

        public Form1()
        {
            InitializeComponent();
        }

        private void showcalendar_Click(object sender, EventArgs e)
        {
            ShowCalendar();
        }

        void ShowCalendar()
        {
            DateTime current5 = DateTime.Now.AddDays(-5);

            MonthCalendar cal = new MonthCalendar();
            Panel panel = new Panel();

            cal.MaxSelectionCount = 1;
            cal.SetDate(current5);
            cal.DateSelected += new DateRangeEventHandler(DateSelected);
            cal.ShowToday = true;
            panel.Width = cal.Width;
            panel.Height = cal.Height;
            panel.BorderStyle = BorderStyle.FixedSingle;
            panel.Controls.Add(cal);
            f.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            f.ShowInTaskbar = false;
            f.Size = panel.Size;
            f.Location = MousePosition;
            f.StartPosition = FormStartPosition.Manual;
            f.Controls.Add(panel);
            f.Deactivate += delegate { f.Hide(); };
            f.Show();
        }

        void DateSelected(object sender, DateRangeEventArgs e)
        {
            DateTime selection = e.Start;
            Console.WriteLine("Selected: {0}", selection.ToLongDateString());

            this.Activate(); // Forces popup to de-activate
        }

    }

Workaround to this: remove MonthCalendar from it's parent before closing the form that hosts this MonthsCalendar. So the change is to add line cal.Parent.Controls.Remove(cal). The DateSelected method becomes:

void DateSelected(object sender, DateRangeEventArgs e)
{
    MonthCalendar cal = (MonthCalendar)sender;
    Form f = cal.FindForm();
    cal.Parent.Controls.Remove(cal);
    f.Close();
}

我认为问题在于您关闭了包含月历的表格,这使您的控件失效。

Interesting. I can reproduce this just fine here (running in VS 2008, target 3.5). There's quite a lot of noise in the code, the following causes the same behavior here, that is

  • Selecting any date by clicking on the day/number works
  • Selecting the "today" area at the bottom results in the ObjectDisposedException

Reduced/minimal complete test:

using System;
using System.Windows.Forms;

namespace Test
{
    public class Form1 : Form
    {
        public Form1()
        {
            var dateSelectionButton = new Button();
            SuspendLayout();
            dateSelectionButton.Text = "Pick Date";
            dateSelectionButton.Click += (SelectDateClick);
            Controls.Add(dateSelectionButton);
            ResumeLayout();
        }

        private void SelectDateClick(object sender, EventArgs e)
        {
            MonthCalendar cal = new MonthCalendar();
            Form f = new Form();
            cal.DateSelected += DateSelected;
            f.Controls.Add(cal);
            f.Show();
        }

        void DateSelected(object sender, DateRangeEventArgs e)
        {
            MonthCalendar cal = (MonthCalendar)sender;
            Form f = cal.FindForm();
            f.Close();
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM