Click here to Skip to main content
15,881,812 members
Articles / Programming Languages / C#

Custom ComboBoxes with Advanced Drop-down Features

Rate me:
Please Sign up or sign in to vote.
4.95/5 (55 votes)
22 Apr 2003CPOL4 min read 313.4K   12.1K   125  
Contains several ComboBoxes which uses Windows themes and contains ComboBoxes with CheckBoxed lists and TreeViews
using System;
using System.Collections;
using System.Drawing;
using System.Drawing.Design;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;

using UtilityLibrary.Wizards.Utility;
using UtilityLibrary.Win32;

using System.Diagnostics;


namespace UtilityLibrary.DateTimeControls
{
  [ToolboxItem(true), Designer( typeof( CalendarViewExDesigner ) )]
  [ToolboxBitmap(typeof(UtilityLibrary.DateTimeControls.CalendarViewEx), "UtilityLibrary.DateTimeControls.CalendarViewEx.bmp")]
  public class CalendarViewEx : System.Windows.Forms.Control
  {
    #region Class constants
    private const int DEF_HEADER_SIZE = 18;
    private const int DEF_ARROW_SIZE  = 3;
    private const int DEF_FOOTER_SIZE = 24;
    private const int DEF_BUTTON_WIDTH = 46;
    private const int DEF_BUTTON_HEIGHT = 20;
    private const int DEF_WEEK_DAY_HEIGHT = 18;

    private const int DEF_COLUMNS_COUNT = 7;
    private const int DEF_ROWS_COUNT = 6;

    private const int DEF_TODAY_TAB_INDEX = 100;
    private const int DEF_NONE_TAB_INDEX = 101;
    #endregion

    #region Declarations and internal classes
    [Flags]
    public enum TRectangleStatus
    {
      Normal    = 0x0000,
      Active    = 0x0001,
      Selected  = 0x0002,
      Focused   = 0x0004,
      ActiveSelect = Active | Selected,
      FocusSelect = Focused | Selected,
      All = Active | Selected | Focused
    };

    public enum TRectangleAction
    {
      None,
      MonthDown,
      MonthUp,
      YearDown,
      YearUp,
      TodayBtn,
      NoneBtn,
      MonthDay,
      WeekDay
    };

    public class ActRect
    {
      #region Class members
      private Control           m_parent;
      private Rectangle         m_rect;
      private TRectangleStatus  m_state;
      private bool              m_bInvalidate = true;
      private TRectangleAction  m_act;
      private object            m_tag;
      #endregion

      #region Class properties
      public Rectangle Rect
      {
        get
        {
          return m_rect;
        }
        set
        {
          m_rect = value;
        }
      }
      
      public TRectangleStatus State
      {
        get
        {
          return m_state;
        }
        set
        {
          if( value != m_state )
          {
            m_state = value;
            if( m_bInvalidate == true )
              m_parent.Invalidate( m_rect );
          }
        }
      }
      
      public bool InvalidateOnChange
      {
        get
        {
          return m_bInvalidate;
        }
        set
        {
          m_bInvalidate = value;
        }
      }

      public TRectangleAction Action
      {
        get
        {
          return m_act;
        }
        set
        {
          m_act = value;
        }
      }
      public object Tag
      {
        get
        { 
          return m_tag; 
        }
        set
        {
          m_tag = value; 
        }
      }

      public bool IsFocused
      {
        get
        {
          return (m_state & TRectangleStatus.Focused) == TRectangleStatus.Focused;
        }
      }
      
      public bool IsSelected
      {
        get
        {
          return (m_state & TRectangleStatus.Selected) == TRectangleStatus.Selected;
        }
      }

      public bool IsActive
      {
        get
        {
          return (m_state & TRectangleStatus.Active) == TRectangleStatus.Active;
        }
      }
      #endregion

      #region Class Constructors
      private ActRect(){} // disable default contructor

      public ActRect( Control parent, Rectangle rc, TRectangleStatus state, TRectangleAction act, bool invalidate )
      {
        m_parent      = parent;
        m_rect        = rc;
        m_state       = state;
        m_bInvalidate = invalidate;
        m_act         = act;
      }

      public ActRect( Control parent, Rectangle rc, TRectangleStatus state, TRectangleAction act )
        : this( parent, rc, state, act, true ) 
      {
      }

      public ActRect( Control parent, Rectangle rc, TRectangleAction act, object tag )
      {
        m_parent      = parent;
        m_rect        = rc;
        m_state       = TRectangleStatus.Normal;
        m_act         = act;
        m_tag         = tag;
      }
      public ActRect( Control parent, Rectangle rc, TRectangleAction act )
        : this( parent, rc, TRectangleStatus.Normal, act, true ) 
      {
      }

      public ActRect( Control parent, Rectangle rc, TRectangleStatus state )
        : this( parent, rc, state, TRectangleAction.None, true )
      {
      }

      public ActRect( Control parent, Rectangle rc )
        : this( parent, rc, TRectangleStatus.Normal, TRectangleAction.None, true )
      {
      }
      
      public ActRect( Control parent )
        : this( parent, Rectangle.Empty, TRectangleStatus.Normal, TRectangleAction.None, true ) 
      {
      
      }
      #endregion
    }

    public class DateTimeCollection : System.Collections.CollectionBase, IEnumerable
    {
      #region class events
      public delegate void OnChange();
      public event OnChange ItemChanged;
      #endregion

      #region Class constructors
      public DateTimeCollection()
      {
      }
      #endregion

      #region IList interface support
      public int Add( DateTime valDate )
      {
        if( Contains( valDate ) ) 
          return -1;

        int index = InnerList.Add( valDate );
        FireItemChanged();
        return index;
      }

      public bool Contains( DateTime valDate )
      {
        return InnerList.Contains( valDate );
      }

      public int IndexOf( DateTime valDate )
      {
        return InnerList.IndexOf( valDate );
      }

      public void Insert(int index, DateTime value)
      {
        if( value.DayOfWeek != DayOfWeek.Sunday )
        {
          InnerList.Insert( index, value );
          FireItemChanged();
        }
      }

      public void Remove(DateTime value)
      {
        InnerList.Remove( value );
        FireItemChanged();
      }

      public DateTime this[int index]
      {
        get
        {
          return ( DateTime )InnerList[index];
        }
        set
        {
          InnerList[ index ] = value;
          FireItemChanged();
        }
      }
      #endregion

      #region Custom functions
      protected void FireItemChanged()
      {
        if( ItemChanged != null )
        {
          ItemChanged();
        }
      }
      #endregion
    }
    #endregion

    #region Class members
    private GDIUtils  m_gdi = new GDIUtils();
    private bool      m_bRectsCreated;
    
    private Rectangle m_rcHeader;
    private Rectangle m_rcFooter;
    private Rectangle m_rcBody;

    // array in which stored active rectangles
    private ArrayList m_rects = new ArrayList( 100 ); 

    private int m_iYear  = DateTime.Now.Year;
    private int m_iMonth = DateTime.Now.Month;
    private int m_iDay   = 1;
    
    private DateTime m_today = DateTime.Now;
    private DateTime m_dtSelected = DateTime.Now;

    private int   m_iLastFocused = 1; // 100 - Today, 101 - None... less values mean Day...
    private bool  m_bDropDownTab;

    private DateTimeCollection m_collSelected;
    private int m_iSelectionHeight;
    private int m_iSelectionWidth;
    private int m_iSelectionStart;
    #endregion

    #region Class events
    public event EventHandler ValueChanged;
    #endregion

    #region Class Properties
    internal int Year
    {
      get
      {
        return m_iYear;
      }
      set
      {
        if( value != m_iYear )
        {
          m_iYear = value;
          OnYearChanged();
        } 
      }
    }

    internal int Month
    {
      get
      {
        return m_iMonth;
      }
      set
      {
        if( value != m_iMonth )
        {
          if( value <= 0 || value > 12 )
          {
            throw new ArgumentOutOfRangeException( "Month", "Month value must be in diapason from 1 till 12" );
          } 

          m_iMonth = value;
          OnMonthChanged();
        } 
      }
    }

    internal int Day
    {
      get
      {
        return m_iDay;
      }
      set
      {
        if( value != m_iDay )
        {
          if( value <= 0 || value > 31 )
          {
            throw new ArgumentOutOfRangeException( "Day", "Day value must be in diapason from 1 till 31" );
          } 

          m_iDay = value;
          OnDayChanged();
        } 
      }
    }

    [Browsable(false)]
    new public System.Drawing.Size Size
    {
      get
      {
        return base.Size;
      }
      set
      {
        base.Size = new Size( 160, 152 );
      }
    }

    [Browsable(true)]
    public DateTime Value
    {
      get
      {
        if( m_iDay != 0 ) 
          return m_dtSelected;

        return DateTime.MinValue;
      }
      set
      {
        if( value != m_dtSelected || m_iDay == 0 )
        {
          OnValueChanged( value );
          RaiseValueChanged();
        } 
      }
    }
    
    [Browsable(true)]
    public bool DropDownTab
    {
      get
      {
        return m_bDropDownTab;
      }
      set
      {
        if( value != m_bDropDownTab )
        {
          m_bDropDownTab = value;
          OnDropDownTabChanged();
        } 
      }
    }

    [Browsable(false)]
    public DateTimeCollection Selected
    {
      get
      {
        if( m_collSelected != null )
        {
          m_collSelected.ItemChanged -= new DateTimeCollection.OnChange( OnSelectedCollectionChanged );
          m_collSelected.Clear();
        }

        m_collSelected = GetSeletedRects();
        m_collSelected.ItemChanged += new DateTimeCollection.OnChange( OnSelectedCollectionChanged );
        return m_collSelected;
      }
    }
    #endregion

    #region Class constructor
    public CalendarViewEx()
    {
      ControlStyles styleTrue = 
        ControlStyles.AllPaintingInWmPaint |
        ControlStyles.DoubleBuffer |
        ControlStyles.EnableNotifyMessage |
//        ControlStyles.ContainerControl |
        //ControlStyles.Opaque |
        ControlStyles.ResizeRedraw |
        //ControlStyles.Selectable |
        ControlStyles.FixedHeight |
        ControlStyles.FixedWidth |
        ControlStyles.UserPaint;

      SetStyle( styleTrue, true );

      base.Size = new Size( 160, 152 );
    }
    #endregion

    #region Class paint methods
    protected override void OnPaint( PaintEventArgs pe )
    {
      // active rectangles must be rebuild
      if( m_bRectsCreated == false || DesignMode == true )
      {
        m_rects.Clear();
      }

      OnPaintHeader( new PaintEventArgs( pe.Graphics, m_rcHeader ) );
      OnPaintFooter( new PaintEventArgs( pe.Graphics, m_rcFooter ) );

      if( m_bRectsCreated == false || DesignMode == true ) // recalculate Body
      {
        Rectangle rc = pe.ClipRectangle;
        m_rcBody = new Rectangle( rc.X, m_rcHeader.Bottom, rc.Width, m_rcFooter.Top - m_rcHeader.Bottom );
        m_rcBody = Rectangle.Inflate( m_rcBody, -4, -1 );
      }

      OnPaintBody( new PaintEventArgs( pe.Graphics, m_rcBody ) );

      m_bRectsCreated = true; // indicate that control calculate all own rectangles
    }

    protected override void OnPaintBackground( PaintEventArgs pevent )
    {
      Graphics g = pevent.Graphics;
      Rectangle rc = ClientRectangle;

      g.FillRectangle( Brushes.White, rc );

      // draw header background
      m_rcHeader = new Rectangle( rc.X, rc.Y, rc.Width-1, DEF_HEADER_SIZE );
      g.FillRectangle( SystemBrushes.Control, m_rcHeader );
      m_gdi.Draw3DBox( g, m_rcHeader, Canvas3DStyle.Title );

      // draw footer background
      int yBott = rc.Bottom - DEF_FOOTER_SIZE - 1;
      m_rcFooter = new Rectangle( rc.X+6, yBott, rc.Width-12, DEF_FOOTER_SIZE );
      g.FillRectangle( Brushes.White, m_rcFooter );
      m_gdi.Draw3DLine( g, new Point( new Size( rc.X + 6, yBott ) ), new Point( new Size( rc.Right - 6, yBott ) ) );

      if( m_bRectsCreated == true && m_iDay != 0 )
      {
        foreach( ActRect rect in m_rects )
        {
          switch( rect.State & TRectangleStatus.ActiveSelect )
          {
            case TRectangleStatus.ActiveSelect:
              m_gdi.DrawActiveRectangle( g, rect.Rect, HightlightStyle.Selected, true );
              break;

            case TRectangleStatus.Active:
              m_gdi.DrawActiveRectangle( g, rect.Rect, HightlightStyle.Active, true );
              break;

            case TRectangleStatus.Selected:
              g.FillRectangle( Brushes.Silver, rect.Rect );
              break;
          }
        }
      }
    }

    protected virtual  void OnPaintHeader( PaintEventArgs pevent )
    {
      Rectangle rc = pevent.ClipRectangle;
      Rectangle rcOut = Rectangle.Inflate( rc, -6, -1 ); 

      PaintEventArgs ev = new PaintEventArgs( pevent.Graphics, rcOut ); 

      OnPaintMonthHeader( ev );
      OnPaintYearHeader( ev ); 
    }

    protected virtual  void OnPaintMonthHeader( PaintEventArgs pevent )
    {
      Graphics g = pevent.Graphics;
      Rectangle rc = pevent.ClipRectangle;
      
      DateTime date = new DateTime( m_iYear, m_iMonth, 1, 0, 0, 0 );
      string strMonth = date.ToString( "MMMM" );
      
      // Draw left arrow and add it as Active Rectangle
      Rectangle rect = DrawArrow( g, rc, true ); 
      AddActiveRect( rect, TRectangleAction.MonthDown ); 
      
      SizeF sz = g.MeasureString( strMonth, this.Font );
      Rectangle rcText = new Rectangle( rect.Right + 4, rc.Y, (int)sz.Width + 8, rc.Height );
      g.DrawString( strMonth, this.Font, SystemBrushes.WindowText, rcText, m_gdi.OneLineNoTrimming ); 
      
      // draw  right arrow and add it like Active Rectangle
      rect = DrawArrow( g, new Rectangle( rcText.Right + 4, rc.Y, 100, rc.Height ), false );
      AddActiveRect( rect, TRectangleAction.MonthUp ); 
    }
    
    protected virtual  void OnPaintYearHeader( PaintEventArgs pevent )
    {
      Graphics g = pevent.Graphics;
      Rectangle rc = pevent.ClipRectangle;
     
      DateTime date = new DateTime( m_iYear, m_iMonth, 1, 0, 0, 0 );
      string strYear = date.ToString( "yyyy" );

      Rectangle rect = DrawArrow( g, 
        new Rectangle( rc.Right - 4 - DEF_ARROW_SIZE - 2, rc.Y, DEF_ARROW_SIZE * 2, rc.Height ), 
        false );
      AddActiveRect( rect, TRectangleAction.YearUp ); 

      SizeF sz = g.MeasureString( strYear, this.Font );
      Rectangle rcText = new Rectangle( rect.Left - 4 - (int)sz.Width - 8, rc.Y, (int)sz.Width + 8, rc.Height );
      g.DrawString( strYear, this.Font, SystemBrushes.WindowText, rcText, m_gdi.OneLineNoTrimming ); 
      
      rect = DrawArrow( g,
        new Rectangle( rcText.Left - 4 - DEF_ARROW_SIZE - 2, rc.Y, DEF_ARROW_SIZE*2, rc.Height ),
        true );
      AddActiveRect( rect, TRectangleAction.YearDown ); 
    }

    protected virtual  void OnPaintFooter( PaintEventArgs pevent )
    {
      Graphics g = pevent.Graphics;
      Rectangle rc = pevent.ClipRectangle;
      
      Rectangle rcOut = Rectangle.Inflate( rc, -4, -1 ); 
      OnPaintFooterButtons( new PaintEventArgs( g, m_rcFooter ) );
    }

    protected virtual  void OnPaintFooterButtons( PaintEventArgs pevent )
    {
      Graphics g = pevent.Graphics;
      Rectangle rc = pevent.ClipRectangle;
      Rectangle focus;
      
      Rectangle rcToday = new Rectangle( rc.X, rc.Y + rc.Height/2 - DEF_BUTTON_HEIGHT/2, 
        DEF_BUTTON_WIDTH, DEF_BUTTON_HEIGHT );

      g.FillRectangle( SystemBrushes.Control, rcToday ); 
      m_gdi.Draw3DBox( g, rcToday, Canvas3DStyle.Flat );
      g.DrawString( "&Today", this.Font, SystemBrushes.WindowText, rcToday, m_gdi.OneLineNoTrimming );
      
      if( m_iLastFocused == 100 && base.Focused == true )
      {
        focus = new Rectangle( rcToday.X + 2, rcToday.Y + 2, rcToday.Width - 3, rcToday.Height - 3 );
        ControlPaint.DrawFocusRectangle( g, focus );
      } 

      AddActiveRect( rcToday, TRectangleAction.TodayBtn, DEF_TODAY_TAB_INDEX ); 

      Rectangle rcNone = new Rectangle( rcToday.Right + 4, rcToday.Y, rcToday.Width, rcToday.Height ); 
      g.FillRectangle( SystemBrushes.Control, rcNone ); 
      m_gdi.Draw3DBox( g, rcNone, Canvas3DStyle.Flat );
      g.DrawString( "&None", this.Font, SystemBrushes.WindowText, rcNone, m_gdi.OneLineNoTrimming );
      
      if( m_iLastFocused == 101 && base.Focused == true )
      {
        focus = new Rectangle( rcNone.X + 2, rcNone.Y + 2, rcNone.Width - 3, rcNone.Height - 3 );
        ControlPaint.DrawFocusRectangle( g, focus );
      } 

      AddActiveRect( rcNone, TRectangleAction.NoneBtn, DEF_NONE_TAB_INDEX ); 
    }
    
    protected virtual  void OnPaintBody( PaintEventArgs pevent )
    {
      Graphics g = pevent.Graphics;
      Rectangle rc = pevent.ClipRectangle;
      DateTime date = new DateTime( m_iYear, m_iMonth, 1, 0, 0, 0 ); 
      
      if( date.DayOfWeek != DayOfWeek.Sunday )
      {
        date = date.Subtract( new TimeSpan( (int)date.DayOfWeek, 0, 0, 0, 0 ) );
      }
      else
      {
        date = date.Subtract( new TimeSpan( 7, 0, 0, 0, 0 ) );
      }

      int iColWidth = rc.Width / DEF_COLUMNS_COUNT;
      int iRowHeight= ( rc.Height - DEF_WEEK_DAY_HEIGHT ) / DEF_ROWS_COUNT;

      m_gdi.Draw3DLine( g, new Point( new Size( rc.X+2, rc.Y + DEF_WEEK_DAY_HEIGHT - 1 ) ),
        new Point( new Size( rc.Right-2, rc.Y + DEF_WEEK_DAY_HEIGHT - 1 ) ) );

      Rectangle rcHead = new Rectangle( rc.X, rc.Y, iColWidth, DEF_WEEK_DAY_HEIGHT - 2 );

      for( int i=0; i<DEF_COLUMNS_COUNT; i++ )
      {
        rcHead.X = rc.X + i*iColWidth;
        string strDayWeek = "" + date.ToString( "dddd" )[0];
        
        g.DrawString( strDayWeek, this.Font, 
          ( date.DayOfWeek != DayOfWeek.Sunday ) ? SystemBrushes.WindowText : Brushes.Red, 
          rcHead, m_gdi.OneLineNoTrimming ); 

        AddActiveRect( rcHead, TRectangleAction.WeekDay ); 
        
        if( m_iDay != 0 ) // We have correct day
        {
          DateTime dateNew = new DateTime( date.Ticks );
          Brush brush;
          Rectangle rcDay = new Rectangle( rcHead.X, rc.Y + DEF_WEEK_DAY_HEIGHT, rcHead.Width, iRowHeight );
        
          for( int j=0; j<DEF_ROWS_COUNT; j++ )
          {
            rcDay.Y = rc.Y + DEF_WEEK_DAY_HEIGHT + j*iRowHeight;
            string strDay = dateNew.ToString( "dd" );
            int    index = -1;

            if( m_iLastFocused == index && base.Focused == true )
            {
              g.FillRectangle( Brushes.Gray, rcDay );
            }

            if( dateNew.Day == m_today.Day && dateNew.Month == m_today.Month && dateNew.Year == m_today.Year )
            {
              g.DrawRectangle( Pens.Red, rcDay.X, rcDay.Y, rcDay.Width-1, rcDay.Height-1 );
            }
          
            if( dateNew.Month != m_iMonth )
            {
              brush = Brushes.Gray;
              index = -dateNew.Day;
            }
            else if( dateNew.DayOfWeek == DayOfWeek.Sunday )
            {
              brush = Brushes.Red;
              index = dateNew.Day;
            }
            else
            {
              brush = SystemBrushes.WindowText;
              index = dateNew.Day;
            }

            g.DrawString( strDay, this.Font, brush, rcDay, m_gdi.OneLineNoTrimming );
            AddActiveRect( rcDay, TRectangleAction.MonthDay, index );

            if( m_iLastFocused == index && base.Focused == true )
            {
              ControlPaint.DrawFocusRectangle( g, Rectangle.Inflate( rcDay, -1, -1 ) );
            }

            dateNew = dateNew.AddDays(7);
          } 
        }
        else // None day selected
        {
          Rectangle rcNone = new Rectangle( rc.X, rc.Y + DEF_WEEK_DAY_HEIGHT, 
            rc.Width, rc.Height - DEF_WEEK_DAY_HEIGHT );
          
          if( m_iLastFocused < DEF_TODAY_TAB_INDEX )
            ControlPaint.DrawFocusRectangle( g, Rectangle.Inflate( rcNone, -2, -2 ) );

          g.DrawString( "None", this.Font, SystemBrushes.WindowText, rcNone, m_gdi.OneLineNoTrimming );
        }

        date = date.AddDays(1);
      } 
    }
    
    
    protected override void OnResize(System.EventArgs e)
    {
      m_bRectsCreated = false;
      Invalidate();
    }

    protected Rectangle DrawArrow( Graphics g, Rectangle rc, bool isLeft )
    {
      int xLeft, xRight, yTop, yMidd, yBott;

      xLeft   = rc.Left + 1;
      xRight  = xLeft + DEF_ARROW_SIZE;
      yMidd   = rc.Top + (rc.Height / 2);
      yTop    = yMidd - DEF_ARROW_SIZE;
      yBott   = yMidd + DEF_ARROW_SIZE;

      Point[] array;

      if( isLeft == true )
      {
        array = new Point[]
        {
          new Point( new Size( xLeft,  yMidd ) ),
          new Point( new Size( xRight, yTop  ) ),
          new Point( new Size( xRight, yBott ) )
        };
      } 
      else
      {
        array = new Point[]
        {
          new Point( new Size( xLeft,  yTop  ) ),
          new Point( new Size( xLeft,  yBott ) ),
          new Point( new Size( xRight, yMidd ) )
        };
      }

      g.DrawPolygon( SystemPens.WindowText, array );
      g.FillPolygon( SystemBrushes.WindowText, array ); 

      return new Rectangle( xLeft - 2, yTop - 2, DEF_ARROW_SIZE + 4, DEF_ARROW_SIZE * 2 + 4 );
    }
    protected void AddActiveRect( Rectangle rc, TRectangleAction action, object tag )
    {
      if( m_bRectsCreated == false )
      {
        m_rects.Add( new ActRect( this, rc, action, tag ) ); 
      } 
    }
    protected void AddActiveRect( Rectangle rc, TRectangleAction action )
    {
      if( m_bRectsCreated == false )
      {
        m_rects.Add( new ActRect( this, rc, action ) ); 
      } 
    }
    #endregion

    #region Class overrides
    protected virtual void OnMonthChanged()
    {
      Value = new DateTime( m_iYear, m_iMonth, ( m_iDay < 1 ) ? m_iDay : 1, 0, 0, 0 ); 
    }
    
    protected virtual void OnYearChanged()
    {
      Value = new DateTime( m_iYear, m_iMonth, ( m_iDay < 1 ) ? m_iDay : 1, 0, 0, 0 ); 
    }

    protected virtual void OnDayChanged()
    {
      Value = new DateTime( m_iYear, m_iMonth, ( m_iDay < 1 ) ? m_iDay : 1, 0, 0, 0 ); 
    }

    protected virtual void OnValueChanged( DateTime value )
    {
      ResetAllRectangleStates();

      if( value == DateTime.MinValue )
      {
        m_iDay = 0;
        m_dtSelected = value;
        Invalidate();
        return;
      } 

      bool bRecreate = ( m_dtSelected.Month != value.Month || m_dtSelected.Year != value.Year );
      if( m_bRectsCreated == true && bRecreate == true )  m_bRectsCreated = false;
      
      m_dtSelected = value;
      m_iYear = m_dtSelected.Year;
      m_iMonth = m_dtSelected.Month;
      m_iDay = m_dtSelected.Day;
      
      if( bRecreate == true )
      { 
        Invalidate(); 
        Update(); 
      }

      ActRect rect = FindActiveRectByTag( m_iDay );
      if( m_iLastFocused < DEF_TODAY_TAB_INDEX )  m_iLastFocused = m_iDay;
      if( rect != null )  rect.State |= TRectangleStatus.FocusSelect;

      Invalidate();
      Update();
    }
    
    protected virtual void OnDropDownTabChanged()
    {
      
    }
    

    protected void OnSelectedCollectionChanged()
    {
      foreach( DateTime date in m_collSelected )
      {
        if( date.Month == m_dtSelected.Month && date.Year == m_dtSelected.Year )
        {
          ActRect rect = FindActiveRectByTag( date.Day );
          
          if( rect != null )
          {
            rect.State |= TRectangleStatus.Selected;
          } 
        } 
      } 
    }


    protected void ScrollDaysLeft()
    {
      if( m_iLastFocused < DEF_TODAY_TAB_INDEX )
        Value = m_dtSelected.AddDays( -1 );
    }

    protected void ScrollDaysRight()
    {
      if( m_iLastFocused < DEF_TODAY_TAB_INDEX )
        Value = m_dtSelected.AddDays( 1 );
    }

    protected void ScrollDaysUp()
    {
      if( m_iLastFocused < DEF_TODAY_TAB_INDEX )
        Value = m_dtSelected.AddDays( -7 );
    }

    protected void ScrollDaysDown()
    {
      if( m_iLastFocused < DEF_TODAY_TAB_INDEX )
        Value = m_dtSelected.AddDays( 7 );
    }


    protected void SetFocusOnNextControl()
    {
      ResetFocusedRectangleState();

      if( m_iLastFocused < DEF_TODAY_TAB_INDEX )
      {
        m_iLastFocused = DEF_TODAY_TAB_INDEX;
      } 
      else if( m_iLastFocused == DEF_TODAY_TAB_INDEX )
      {
        m_iLastFocused = DEF_NONE_TAB_INDEX;
      }
      else if( m_bDropDownTab == true )
      {
        if( m_iDay != 0 )
        {
          m_iLastFocused = m_iDay;
          
          ActRect rc = FindActiveRectByTag( m_iDay );
          if( rc != null )
          {
            rc.State |= TRectangleStatus.Focused | TRectangleStatus.Selected;
          }
        }
      }
      else
      {
        Control ctrl = this.FindForm().GetNextControl( this, true );
        if( ctrl != null ) ctrl.Focus();
      }

      Invalidate();
    }
    
    protected void SetFocusOnPrevControl()
    {
      ResetFocusedRectangleState();

      if( m_iLastFocused < DEF_TODAY_TAB_INDEX && m_bDropDownTab == true )
      {
        m_iLastFocused = DEF_NONE_TAB_INDEX;
      } 
      else if( m_iLastFocused == DEF_TODAY_TAB_INDEX && m_iDay != 0 )
      {
        m_iLastFocused = m_iDay;
        
        ActRect rc = FindActiveRectByTag( m_iDay );
        if( rc != null )
        {
          rc.State |= TRectangleStatus.Focused | TRectangleStatus.Selected;
        }
      }
      else if( m_iLastFocused == DEF_NONE_TAB_INDEX )
      {
        m_iLastFocused = DEF_TODAY_TAB_INDEX;
      }
      else
      {
        Control ctrl = this.FindForm().GetNextControl( this, false );
        if( ctrl != null ) ctrl.Focus();
      }
      
      Invalidate();
    }


    protected void ToNextYear()
    {
      Value = m_dtSelected.AddYears( 1 );
    }

    protected void ToPrevYear()
    {
      Value = m_dtSelected.AddYears( -1 );
    }

    protected void ToNextMonth()
    {
      Value = m_dtSelected.AddMonths( 1 );
    }
    
    protected void ToPrevMonth()
    {
      Value = m_dtSelected.AddMonths( -1 );
    }


    private void UpdateSelectionRectangle( int start, int width, int height )
    {
      int xLeft = start + width;
      int xTop  = start + height*7;

      // TODO: algorithm which can select rectangle of items
    }

    protected void RecalculateSelectionUp()
    {
      DateTime newDate = m_dtSelected.AddDays( -7 );

      if( newDate.Month != m_dtSelected.Month ) // switch to another month
      {
        ScrollDaysUp();
      }
      else
      {
        ResetFocusedRectangleState();
        if( Selected.Count == 1 ) m_iSelectionStart = m_dtSelected.Day;
        Value = newDate;
        UpdateSelectionRectangle( m_iSelectionStart, m_iSelectionWidth, ++m_iSelectionHeight );
      }
    }
    
    protected void RecalculateSelectionDown()
    {
      DateTime newDate = m_dtSelected.AddDays( 7 );

      if( newDate.Month != m_dtSelected.Month ) // switch to another month
      {
        ScrollDaysDown();
      }
      else
      {
        ResetFocusedRectangleState();
        if( Selected.Count == 1 ) m_iSelectionStart = m_dtSelected.Day;
        Value = newDate;
        UpdateSelectionRectangle( m_iSelectionStart, m_iSelectionWidth, --m_iSelectionHeight );
      }
    }
    
    protected void RecalculateSelectionLeft()
    {
      DateTime newDate = m_dtSelected.AddDays( -1 );

      if( newDate.Month != m_dtSelected.Month ) // switch to another month
      {
        ScrollDaysLeft();
      }
      else
      {
        ResetFocusedRectangleState();
        if( Selected.Count == 1 ) m_iSelectionStart = m_dtSelected.Day;
        Value = newDate;
        UpdateSelectionRectangle( m_iSelectionStart, ++m_iSelectionWidth, m_iSelectionHeight );
      }
    }
    
    protected void RecalculateSelectionRight()
    {
      DateTime newDate = m_dtSelected.AddDays( 1 );

      if( newDate.Month != m_dtSelected.Month ) // switch to another month
      {
        ScrollDaysRight();
      }
      else
      {
        ResetFocusedRectangleState();
        if( Selected.Count == 1 ) m_iSelectionStart = m_dtSelected.Day;
        Value = newDate;
        UpdateSelectionRectangle( m_iSelectionStart, --m_iSelectionWidth, m_iSelectionHeight );
      }
    }

    
    protected void OnRectangleClick( ActRect rc )
    {
      switch( rc.Action )
      {
        case TRectangleAction.MonthDown:  ToPrevMonth(); break;
        case TRectangleAction.MonthUp:    ToNextMonth(); break;
        case TRectangleAction.YearDown:   ToPrevYear();  break;
        case TRectangleAction.YearUp:     ToNextYear();  break;
        case TRectangleAction.TodayBtn:   SetTodayDay(); break;
        case TRectangleAction.NoneBtn:    SetNoneDay();  break;
        
        case TRectangleAction.MonthDay:
          if( m_iDay == 0 ) return;
          int index = (int)rc.Tag;
          DateTime newDate = new DateTime( m_iYear, m_iMonth, m_iDay, 0, 0, 0 ); 

          if( index < 0 && index > -10 )
          { 
            newDate = m_dtSelected.AddMonths(1);
            index = -index; 
          }
          else if( index < 0 && index < -20 )
          { 
            newDate = m_dtSelected.AddMonths(-1);
            index = -index; 
          }

          Value = new DateTime( newDate.Year, newDate.Month, index, 0, 0, 0 ); 
          break;
      }
    }

    protected void OnSelectionClick( ActRect rc )
    {
      if( rc.Action == TRectangleAction.WeekDay )
      {

      }
      else if( rc.Action == TRectangleAction.MonthDay )
      {
        if( rc.IsSelected == false )
          rc.State |= TRectangleStatus.Selected;
        else
          rc.State = (TRectangleStatus)((int)rc.State & ~(int)TRectangleStatus.Selected);

        m_iLastFocused = (int)rc.Tag;
      }
    }

    protected void OnEnterPressed()
    {
      ResetSelectedRectangleState();

      ActRect rect = FindActiveRectByTag( m_iLastFocused );

      if( rect != null )
      {
        switch( rect.Action ) 
        {
          case TRectangleAction.TodayBtn:
            SetTodayDay();
            break;
          
          case TRectangleAction.NoneBtn:
            SetNoneDay();
            break;
        }
      }
    }

    #endregion

    #region Class Public Methods
    public void SetNoneDay()
    {
      Value = DateTime.MinValue;
    }

    public void SetTodayDay()
    {
      Value = DateTime.Today;
    }

    #endregion

    #region Class helper methods
    private ActRect FindActiveRectByPoint( Point pnt )
    {
      foreach( ActRect rc in m_rects )
      {
        if( rc.Rect.Contains( pnt ) == true )
          return rc;
      }

      return null;
    }

    private ActRect FindActiveRectByTag( object tag )
    {
      foreach( ActRect rect in m_rects )
      {
        if( rect.Tag != null && rect.Tag.Equals( tag ) == true )
          return rect;
      }

      return null;
    }
    private void ResetActiveRectanglesState()
    {
      foreach( ActRect rc in m_rects )
      {
        if( (rc.State & TRectangleStatus.Active) > 0 )
        {
          rc.State = (TRectangleStatus)((int)rc.State & ~(int)TRectangleStatus.Active);
        }
      }
    }

    private void ResetSelectedRectangleState()
    {
      foreach( ActRect rc in m_rects )
      {
        if( (rc.State & TRectangleStatus.Selected) > 0 )
        {
          rc.State = (TRectangleStatus)((int)rc.State & ~(int)TRectangleStatus.Selected);
        }
      }
    }
    
    private void ResetFocusedRectangleState()
    {
      foreach( ActRect rc in m_rects )
      {
        if( (rc.State & TRectangleStatus.Focused) > 0 )
        {
          rc.State = (TRectangleStatus)((int)rc.State & ~(int)TRectangleStatus.Focused);
        }
      }
    }

    private void ResetAllRectangleStates()
    {
      foreach( ActRect rc in m_rects )
      {
        rc.State = TRectangleStatus.Normal;
      }
    }
    private DateTimeCollection GetSeletedRects()
    {
      DateTimeCollection coll = new DateTimeCollection();

      foreach( ActRect rc in m_rects )
      {
        if( rc.IsSelected == true && rc.Tag != null )
        {
          int index = (int)rc.Tag;
          DateTime newDate = new DateTime( m_iYear, m_iMonth, m_iDay, 0, 0, 0 ); 

          if( index < 0 && index > -10 )
          { 
            newDate = m_dtSelected.AddMonths(1);
            index = -index; 
          }
          else if( index < 0 && index < -20 )
          { 
            newDate = m_dtSelected.AddMonths(-1);
            index = -index; 
          }

          coll.Add( new DateTime( newDate.Year, newDate.Month, index, 0, 0, 0 ) );
        }
      }

      return coll;
    }


    protected void RaiseValueChanged()
    {
      if( ValueChanged != null )
      {
        ValueChanged( this, EventArgs.Empty );
      } 
    }
    #endregion

    #region Mouse events
    protected override void OnMouseEnter(System.EventArgs e)
    {
      Point pnt = MousePosition;
      pnt = PointToClient( pnt );

      ResetActiveRectanglesState();

      ActRect rect = FindActiveRectByPoint( pnt );
      
      if( rect != null && rect.Action != TRectangleAction.WeekDay )
      {
        rect.State |= TRectangleStatus.Active;
      }
    }

    protected override void OnMouseLeave(System.EventArgs e)
    {
      ResetActiveRectanglesState();
    }

    protected override void OnMouseWheel(System.Windows.Forms.MouseEventArgs e)
    {
      if( e.Delta < 0 )
      {
        ScrollDaysLeft();
      }
      else
      {
        ScrollDaysRight();
      }
    }

    protected override void OnMouseMove(System.Windows.Forms.MouseEventArgs e)
    {
      Point pnt = MousePosition;
      pnt = PointToClient( pnt );

      ResetActiveRectanglesState();

      ActRect rect = FindActiveRectByPoint( pnt );
      
      if( rect != null && rect.Action != TRectangleAction.WeekDay )
      {
        rect.State |= TRectangleStatus.Active;
      }
    }
    protected override void OnClick( System.EventArgs e )
    {
      this.Focus();

      Point pnt = MousePosition;
      pnt = PointToClient( pnt );

      ActRect rect = FindActiveRectByPoint( pnt );
      
      if( rect != null && rect.Action != TRectangleAction.WeekDay )
      {
        ResetActiveRectanglesState();
        ResetFocusedRectangleState();

        // if selection begin
        if( (ModifierKeys & (Keys.Control | Keys.Shift)) == 0 )
        {
          ResetSelectedRectangleState();
          OnRectangleClick( rect );
        }
        else
        {
          OnSelectionClick( rect );
        }
      }

      base.OnClick( e );
    }

    protected override void OnDoubleClick( System.EventArgs e )
    {
      this.Focus();

      Point pnt = MousePosition;
      pnt = PointToClient( pnt );

      ActRect rect = FindActiveRectByPoint( pnt );
      
      if( rect != null && rect.Action != TRectangleAction.WeekDay )
      {
        ResetActiveRectanglesState();
        ResetSelectedRectangleState();
        ResetFocusedRectangleState();

        OnRectangleClick( rect );
      }

      base.OnDoubleClick( e );
    }
    #endregion

    #region Keyboard and focus event handlers
    protected override void OnGotFocus(System.EventArgs e)
    {
      Invalidate();
    }

    protected override void OnLostFocus(System.EventArgs e)
    {
      ResetFocusedRectangleState();
    }

    protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
    {
      switch( e.Modifiers & (Keys.Alt | Keys.Control | Keys.Shift) )
      {
        // only shift pressed
        case Keys.Shift:
          switch( e.KeyCode )
          {
            case Keys.Tab:    SetFocusOnPrevControl(); break;
            case Keys.Down:   RecalculateSelectionDown();  break;
            case Keys.Up:     RecalculateSelectionUp();  break;
            case Keys.Left:   RecalculateSelectionLeft();  break;
            case Keys.Right:  RecalculateSelectionRight();  break;
          }
          break;

        // only alt pressed
        case Keys.Alt:
          switch( e.KeyCode )
          {
            case Keys.Left:  ToPrevMonth(); break;
            case Keys.Right: ToNextMonth(); break;
            case Keys.N:     SetNoneDay();  break;
            case Keys.T:     SetTodayDay(); break;
          }
          break;

        // only control pressed
        case Keys.Control:
          switch( e.KeyCode )
          {
            case Keys.Up:   ToNextYear(); break;
            case Keys.Down: ToPrevYear(); break;
          }
          break;

        default:
          switch( e.KeyCode )
          {
            case Keys.Down: 
              if( m_iLastFocused == DEF_TODAY_TAB_INDEX || m_iLastFocused == DEF_NONE_TAB_INDEX )
                SetFocusOnNextControl(); 
              else
                ScrollDaysDown();   
              break;
            case Keys.Up:
              if( m_iLastFocused == DEF_TODAY_TAB_INDEX || m_iLastFocused == DEF_NONE_TAB_INDEX )
                SetFocusOnPrevControl();
              else 
                ScrollDaysUp();     
              break;
            case Keys.Left: 
              if( m_iLastFocused == DEF_TODAY_TAB_INDEX || m_iLastFocused == DEF_NONE_TAB_INDEX )
                SetFocusOnPrevControl();
              else 
                ScrollDaysLeft();   
              break;
            case Keys.Right: 
              if( m_iLastFocused == DEF_TODAY_TAB_INDEX || m_iLastFocused == DEF_NONE_TAB_INDEX )
                SetFocusOnNextControl(); 
              else
                ScrollDaysRight(); 
              break;
            case Keys.Tab:  
              SetFocusOnNextControl(); 
              break;
            
            case Keys.Space:
            case Keys.Enter:  
              OnEnterPressed(); 
              break;
            
          }
          break;
      }

      base.OnKeyDown( e );
    }
    #endregion

    #region Control Creation
    protected override void WndProc(ref System.Windows.Forms.Message m)
    {
      base.WndProc( ref m );

      if( m.Msg == (int)Msg.WM_GETDLGCODE )
      {
        m.Result = new IntPtr( (int)DialogCodes.DLGC_WANTCHARS | 
          (int)DialogCodes.DLGC_WANTARROWS | 
          (int)DialogCodes.DLGC_WANTTAB |
          m.Result.ToInt32() );
      }
    }

    protected override System.Windows.Forms.CreateParams CreateParams
    {
      get
      {
        CreateParams param = base.CreateParams;
        param.ExStyle |= (int)WindowExStyles.WS_EX_CONTROLPARENT;
        return param;
      }
    }
    #endregion
  }

  #region Calendar View designer
  public class CalendarViewExDesigner : ParentControlDesigner
  {
    protected override void PreFilterProperties( IDictionary properties )
    {
      this.DrawGrid = false;

      base.PreFilterProperties(properties);

      properties.Remove("Dock");
      properties.Remove("Anchor");
      properties.Remove("AutoScroll");
      properties.Remove("AutoScrollMargin");
      properties.Remove("AutoScrollMinSize");
      properties.Remove("DockPadding");
      properties.Remove("Size");
      properties.Remove("DrawGrid");
    }
  }
  #endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO ArtfulBits Inc.
Ukraine Ukraine
Name:Kucherenko Oleksandr

Born:September 20, 1979

Platforms: Win32, Linux; - well known and MS-DOS; Win16; OS/2 - old time not touched;

Hardware: IBM PC

Programming Languages: Assembler (for Intel 80386); Borland C/C++; Borland Pascal; Object Pascal; Borland C++Builder; Delphi; Perl; Java; Visual C++; Visual J++; UML; XML/XSL; C#; VB.NET; T-SQL; PL/SQL; and etc.

Development Environments: MS Visual Studio 2001-2008; MS Visual C++; Borland Delphi; Borland C++Builder; C/C++ any; Rational Rose; GDPro; Together and etc.

Libraries: STL, ATL, WTL, MFC, NuMega Driver Works, VCL; .NET 1.0, 1.1, 2.0, 3.5; and etc.

Technologies: Client/Server; COM; DirectX; DirectX Media; BDE; HTML/DHTML; ActiveX; Java Servlets; DCOM; COM+; ADO; CORBA; .NET; Windows Forms; GDI/GDI+; and etc.

Application Skills: Databases - design and maintain, support, programming; GUI Design; System Programming, Security; Business Software Development. Win/Web Services development and etc.

Comments and Discussions