vbAccelerator - Contents of code file: PopupControl_FormComboBox.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace PopupControl
{
   /// <summary>
   /// Implements a control which works like a Windows ComboBox but
   /// allows a form to be used as the drop-down.
   /// </summary>
   public class FormComboBox : System.Windows.Forms.UserControl, IFormComboBox
   {
      public event EventHandler DropDown;
      public event EventHandler CloseUp;

      private IFormComboBoxDropDown popup = null;
      private bool mouseOver = false;
      private bool entered = false;
      private bool dropped = false;
      private Rectangle dropDownButton = new Rectangle(0, 0, 0, 0);

      private System.Windows.Forms.TextBox textBox1;
      /// <summary> 
      /// Required designer variable.
      /// </summary>
      private System.ComponentModel.Container components = null;

      public FormComboBox() : base()
      {
         // This call is required by the Windows.Forms Form Designer.
         InitializeComponent();

         textBox1.MouseEnter += new EventHandler(textBox1_MouseEnter);
         textBox1.MouseLeave += new EventHandler(textBox1_MouseLeave);
         textBox1.KeyDown += new KeyEventHandler(textBox1_KeyDown);
      }

      /// <summary> 
      /// Clean up any resources being used.
      /// </summary>
      protected override void Dispose( bool disposing )
      {
         if( disposing )
         {
            if(components != null)
            {
               components.Dispose();
            }
         }
         base.Dispose( disposing );
      }

      #region Component Designer generated code
      /// <summary> 
      /// Required method for Designer support - do not modify 
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent()
      {
         this.textBox1 = new System.Windows.Forms.TextBox();
         this.SuspendLayout();
         // 
         // textBox1
         // 
         this.textBox1.BorderStyle = System.Windows.Forms.BorderStyle.None;
         this.textBox1.Location = new System.Drawing.Point(0, 4);
         this.textBox1.Name = "textBox1";
         this.textBox1.Size = new System.Drawing.Size(272, 13);
         this.textBox1.TabIndex = 0;
         this.textBox1.Text = "textBox1";
         // 
         // FormComboBox
         // 
         this.BackColor = System.Drawing.SystemColors.Window;
         this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                                        this.textBox1});
         this.Name = "FormComboBox";
         this.Size = new System.Drawing.Size(292, 20);
         this.ResumeLayout(false);

      }
      #endregion

      /// <summary>
      /// Request that the combo box is closed up
      /// </summary>
      public void RequestCloseUp()
      {
         if (dropped)
         {
            OnCloseUp(new EventArgs());
         }
      }

      /// <summary>
      /// Request that the selected index is changed
      /// </summary>
      /// <param name="index">New index to select</param>
      public void SelectedIndexChanged(int index)
      {
         if (this.popup != null)
         {
            string item = popup.Item(index).ToString();
            textBox1.Text = item;
         }
      }

      /// <summary>
      /// Gets/sets the Drop-Down form object that will be shown 
      /// when the combo-box is dropped.
      /// </summary>
      public IFormComboBoxDropDown DropDownForm
      {
         get
         {
            return this.popup;
         }
         set
         {
            this.popup = value;
         }
      }

      public new bool Enabled
      {
         get
         {
            return base.Enabled;
         }
         set
         {
            base.Enabled = value;
            if (value)
            {
               textBox1.BackColor = this.BackColor;
            }
            else
            {
               textBox1.BackColor = Color.FromKnownColor(KnownColor.Control);
            }
            Invalidate();
         }
      }

      /// <summary>
      /// Raises the SizeChanged event and sizes the text box within the
       control.
      /// </summary>
      /// <param name="e">Not used</param>
      protected override void OnSizeChanged(EventArgs e)
      {
         base.OnSizeChanged(e);

         int top = 1 + (this.ClientRectangle.Height - textBox1.Height) / 2;
         if (this.RightToLeft == RightToLeft.Yes)
         {
            textBox1.Location = new
             Point(SystemInformation.VerticalScrollBarWidth, top);
            dropDownButton = new Rectangle(0, 0, 
               SystemInformation.VerticalScrollBarWidth,
                this.ClientRectangle.Height);
         }
         else
         {
            textBox1.Location = new Point(2, top);
            dropDownButton = new Rectangle(this.ClientRectangle.Width -
             SystemInformation.VerticalScrollBarWidth, 0, 
               SystemInformation.VerticalScrollBarWidth,
                this.ClientRectangle.Height);
         }
         textBox1.Width = this.ClientRectangle.Width -
          SystemInformation.VerticalScrollBarWidth - 3;
      }

      /// <summary>
      /// Draws the border and drop-down button in the control and raises
      /// the Paint event.
      /// </summary>
      /// <param name="e">Paint event information</param>
      protected override void OnPaint(PaintEventArgs e)
      {
         base.OnPaint(e);

         if (this.Enabled)
         {
            // Draw the border:
            if (entered || mouseOver)
            {
               Rectangle borderRect = new
                Rectangle(this.ClientRectangle.Location,
                this.ClientRectangle.Size);
               borderRect.Width--;
               borderRect.Height--;

               e.Graphics.DrawRectangle(SystemPens.Highlight, borderRect);     
                      
            }
            
            // Draw the drop down button:
            bool doLine = false;
            Rectangle fillRect = new Rectangle(dropDownButton.Location,
             dropDownButton.Size);
            fillRect.Inflate(-1, -1);
            if (dropped)
            {         
               Brush br = new SolidBrush(VSNetPressedColor());
               e.Graphics.FillRectangle(br, fillRect);
               br.Dispose();
               doLine = true;
            }
            else if (entered || mouseOver)
            {
               Brush br = new SolidBrush(VSNetSelectionColor());
               e.Graphics.FillRectangle(br, fillRect);
               br.Dispose();
               doLine = true;
            }
            else
            {
               e.Graphics.FillRectangle(SystemBrushes.Control, fillRect);
            }

            if (doLine)
            {
               if (this.RightToLeft == RightToLeft.Yes)
               {
                  e.Graphics.DrawLine(SystemPens.Highlight, fillRect.Right,
                   fillRect.Top, fillRect.Right, fillRect.Bottom);
               }
               else
               {
                  e.Graphics.DrawLine(SystemPens.Highlight, fillRect.Left,
                   fillRect.Top, fillRect.Left, fillRect.Bottom);
               }
            }
            
            DrawComboDropDownGlyph(e.Graphics, fillRect,
             Color.FromKnownColor(KnownColor.WindowText));
         }
         else
         {
            e.Graphics.FillRectangle(SystemBrushes.Control, e.ClipRectangle);

            Rectangle borderRect = new Rectangle(this.ClientRectangle.Location,
             this.ClientRectangle.Size);
            borderRect.Width--;
            borderRect.Height--;

            e.Graphics.DrawRectangle(SystemPens.ControlDark, borderRect);

            DrawComboDropDownGlyph(e.Graphics, dropDownButton,
             Color.FromKnownColor(KnownColor.ControlDark));   
         }
      }

      /// <summary>
      /// Draws a combo-box drop-down glyph in the specified rectangle.
      /// </summary>
      /// <param name="gfx">Graphics object to draw on</param>
      /// <param name="rcButton">Rectangle to centre glyph within</param>
      /// <param name="color">Color to draw glyph</param>
      private void DrawComboDropDownGlyph(
         Graphics gfx,
         Rectangle rcButton,
         Color color
         )
      {
         int xC = rcButton.X + (rcButton.Width / 2) + 1;
         int yC = rcButton.Y + (rcButton.Height / 2) + 1;

         Pen pen = new Pen(color, 1);

         gfx.DrawLine(pen, xC - 2, yC - 1, xC + 2, yC - 1);
         gfx.DrawLine(pen, xC - 1, yC, xC + 1, yC);
         gfx.DrawLine(pen, xC, yC - 1, xC, yC + 1);

         pen.Dispose();

      }

      /// <summary>
      /// Raises the MouseEnter event and performs highlighting of the control.
      /// </summary>
      /// <param name="e"></param>
      protected override void OnMouseEnter(EventArgs e)
      {
         base.OnMouseEnter(e);
         ProcessMouseEnter();
      }
      private void textBox1_MouseEnter(object sender, EventArgs e)
      {
         ProcessMouseEnter();
      }

      protected override void OnMouseLeave(EventArgs e)
      {
         base.OnMouseLeave(e);
         ProcessMouseLeave();
      }
      private void textBox1_MouseLeave(object sender, EventArgs e)
      {
         ProcessMouseLeave();
      }

      private void ProcessMouseEnter()
      {
         if (!this.mouseOver)
         {
            this.mouseOver = true;
            Invalidate();
         }
      }

      private void ProcessMouseLeave()
      {
         Point cursorLocation = Cursor.Position;
         cursorLocation = this.PointToClient(cursorLocation);
         if (!this.ClientRectangle.Contains(cursorLocation))
         {
            if (this.mouseOver)
            {
               this.mouseOver = false;
               Invalidate();
            }
         }
      }

      protected override void OnMouseDown(MouseEventArgs e)
      {
         base.OnMouseDown(e);
         Point point = new Point(e.X, e.Y);
         if (dropDownButton.Contains(point))
         {
            if (dropped)
            {
               OnCloseUp(new EventArgs());
            }
            else
            {
               OnDropDown(new EventArgs());
            }
         }      
      }

      protected override void OnKeyDown(KeyEventArgs e)
      {
         base.OnKeyDown(e);
         ProcessKeyEvent(e);
      }
      private void textBox1_KeyDown(object sender, KeyEventArgs e)
      {
         ProcessKeyEvent(e);
      }

      private void ProcessKeyEvent(KeyEventArgs e)
      {
         if (e.KeyCode == Keys.F4)
         {
            if (dropped)
            {
               OnCloseUp(new EventArgs());
            }
            else
            {
               OnDropDown(new EventArgs());
            }
         }
         else if (dropped)
         {
            if (popup != null)
            {
               popup.KeyDown(e);
            }
         }
      }


      protected virtual void OnDropDown(EventArgs e)
      {
         dropped = true;
         if (DropDown != null)
         {
            DropDown(this, e);
         }
         if (popup != null)
         {
            /// TODO: right to left
            Point location = new Point(0, this.Bounds.Height);
            location = this.PointToScreen(location);
            popup.FormComboBox = this;
            popup.Show(location);
         }
         Invalidate();
      }

      protected virtual void OnCloseUp(EventArgs e)
      {
         dropped = false;
         if (CloseUp != null)
         {
            CloseUp(this, e);
         }
         if (popup != null)
         {
            popup.Hide();
         }
         Invalidate();
      }


      protected override void OnEnter(EventArgs e)
      {
         base.OnEnter(e);
         this.entered = true;
         Invalidate();
      }

      protected override void OnLeave(EventArgs e)
      {
         base.OnLeave(e);
         this.entered = false;
         if (this.dropped)
         {
            OnCloseUp(new EventArgs());
         }
         else
         {
            Invalidate();
         }
      }

      private Color BlendColor(
         Color colorFrom,
         Color colorTo,
         int alpha
         )
      {       
         return Color.FromArgb(
            ((colorFrom.R * alpha) / 255) + ((colorTo.R * (255 - alpha)) / 255),
            ((colorFrom.G * alpha) / 255) + ((colorTo.G * (255 - alpha)) / 255),
            ((colorFrom.B * alpha) / 255) + ((colorTo.B * (255 - alpha)) / 255)
            );      
      }

      private Color VSNetControlColor()
      {
         return BlendColor(
            Color.FromKnownColor(KnownColor.Control), 
            VSNetBackgroundColor(), 
            195);
      }

      private Color VSNetBackgroundColor()
      {
         return BlendColor(
            Color.FromKnownColor(KnownColor.Window), 
            Color.FromKnownColor(KnownColor.Control), 
            220);
      }

      private Color VSNetCheckedColor()
      {
         return BlendColor(
            Color.FromKnownColor(KnownColor.Highlight), 
            Color.FromKnownColor(KnownColor.Window),
            30);
      }
   
      private Color VSNetBorderColor()
      {
         return Color.FromKnownColor(KnownColor.Highlight);
      }

      private Color VSNetSelectionColor()
      {
         return BlendColor(
            Color.FromKnownColor(KnownColor.Highlight), 
            Color.FromKnownColor(KnownColor.Window), 
            70);
      }

      private Color VSNetPressedColor()
      {
         return BlendColor(
            Color.FromKnownColor(KnownColor.Highlight), 
            VSNetSelectionColor(), 
            70);
      }
      
   }

   /// <summary>
   /// The interface that the FormComboBox implements that is
   /// provided to a drop-down form attached to the control
   /// </summary>
   public interface IFormComboBox
   {
      /// <summary>
      /// Request that the combo box is closed up
      /// </summary>
      void RequestCloseUp();

      /// <summary>
      /// Request that the selected index is changed
      /// </summary>
      /// <param name="index">New index to select</param>
      void SelectedIndexChanged(int index);

   }

   /// <summary>
   /// The interface that the form shown from the FormComboBox
   /// must implement
   /// </summary>
   public interface IFormComboBoxDropDown
   {
      /// <summary>
      /// Sets the interface to owning form combo box.  This interface
      /// can be used to modify the selected item and to request that
      /// the combo box is closed up.
      /// </summary>
      IFormComboBox FormComboBox
      {
         set;
      }

      /// <summary>
      /// Gets the number of items in the drop-down
      /// </summary>
      int ItemCount
      {
         get;
      }

      /// <summary>
      /// Gets the item with the specified index
      /// </summary>
      /// <param name="index">0-based index of item</param>
      /// <returns>Item with specified index</returns>
      object Item(int index);

      /// <summary>
      /// Show the drop-down at the specified location.
      /// </summary>
      /// <param name="location">Point to show form at relative to the
       screen</param>
      void Show(Point location);

      /// <summary>
      /// Hide the drop-down if it is shown
      /// </summary>
      void Hide();

      /// <summary>
      /// Request to find an object which matches the specified string
      /// </summary>
      /// <param name="item">String to find</param>
      /// <returns>0-based index of item if found, 0 otherwise</returns>
      int FindString(String item);

      /// <summary>
      /// Forwards a key stroke from the control for processing in the 
      /// drop-down
      /// </summary>
      /// <param name="e">Key which was pressed</param>
      void KeyDown(KeyEventArgs e);

   }
}