File: winforms\Managed\System\WinForms\DataGridBoolColumn.cs
Project: ndp\fx\src\System.Windows.Forms.csproj (System.Windows.Forms)
//------------------------------------------------------------------------------
// <copyright file="DataGridBoolColumn.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Windows.Forms {
 
    using System.Diagnostics;
 
    using System;
    
    using System.Windows.Forms;
    using System.ComponentModel;
    using System.Drawing;
    using Microsoft.Win32;
 
    /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn"]/*' />
    /// <devdoc>
    ///    <para>Specifies a column in
    ///       which each cell contains a check box for representing
    ///       a boolean value.</para>
    /// </devdoc>
    public class DataGridBoolColumn : DataGridColumnStyle {
        private static readonly int idealCheckSize = 14;
 
        private bool isEditing = false;
        private bool isSelected = false;
        private bool allowNull = true;
        private int  editingRow = -1;
        private object currentValue = Convert.DBNull;
        
        private object trueValue = true;
        private object falseValue = false;
        private object nullValue = Convert.DBNull;
 
        private static readonly object  EventTrueValue      = new object();
        private static readonly object  EventFalseValue     = new object();
        private static readonly object  EventAllowNull      = new object();
        
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.DataGridBoolColumn"]/*' />
        /// <devdoc>
        /// <para>Initializes a new instance of the <see cref='System.Windows.Forms.DataGridBoolColumn'/> class.</para>
        /// </devdoc>
        public DataGridBoolColumn() : base() {}
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.DataGridBoolColumn1"]/*' />
        /// <devdoc>
        /// <para>Initializes a new instance of a <see cref='System.Windows.Forms.DataGridBoolColumn'/> with the specified <see cref='System.Data.DataColumn'/>.</para>
        /// </devdoc>
        public DataGridBoolColumn(PropertyDescriptor prop)
            : base(prop) {}
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.DataGridBoolColumn2"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public DataGridBoolColumn(PropertyDescriptor prop, bool isDefault)
            : base(prop, isDefault){}
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.TrueValue"]/*' />
        /// <devdoc>
        ///    <para>Gets or sets the actual value used when setting the 
        ///       value of the column to <see langword='true'/> .</para>
        /// </devdoc>
        [TypeConverterAttribute(typeof(StringConverter)),
        DefaultValue(true)]
        public object TrueValue {
            get {
                return trueValue;
            }
            set {
                if (!trueValue.Equals(value)) {
                    this.trueValue = value;
                    OnTrueValueChanged(EventArgs.Empty);
                    Invalidate();
                }
            }
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.TrueValueChanged"]/*' />
        public event EventHandler TrueValueChanged { 
            add {
                Events.AddHandler(EventTrueValue, value);
            }
            remove {
                Events.RemoveHandler(EventTrueValue, value);
            }
        }
        
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.FalseValue"]/*' />
        /// <devdoc>
        ///    <para>Gets or sets the actual value used when setting the value of the column to 
        ///    <see langword='false'/>.</para>
        /// </devdoc>
        [TypeConverterAttribute(typeof(StringConverter)), DefaultValue(false)]
        public object FalseValue {
            get {
                return falseValue;
            }
            set {
                if (!falseValue.Equals(value)) {
                    this.falseValue = value;
                    OnFalseValueChanged(EventArgs.Empty);
                    Invalidate();
                }
            }
        }
        
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.FalseValueChanged"]/*' />
        public event EventHandler FalseValueChanged { 
            add {
                Events.AddHandler(EventFalseValue, value);
            }
            remove {
                Events.RemoveHandler(EventFalseValue, value);
            }
        }
        
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.NullValue"]/*' />
        /// <devdoc>
        ///    <para>Gets or sets the actual value used when setting the value of the column to 
        ///    <see langword='null'/>.</para>
        /// </devdoc>
        [TypeConverterAttribute(typeof(StringConverter))]
        public object NullValue {
            get {
                return nullValue;
            }
            set {
                if (!nullValue.Equals(value)) {
                    this.nullValue = value;
                    OnFalseValueChanged(EventArgs.Empty);
                    Invalidate();
                }
            }
        }
        
        // =------------------------------------------------------------------
        // =        Methods
        // =------------------------------------------------------------------
 
        // when the grid is in addNewRow it means that the user did not start typing
        // so there is no data to be pushed back into the backEnd.
        // make isEditing false so that in the Commit call we do not do any work.
        //
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.ConcedeFocus"]/*' />
        protected internal override void ConcedeFocus() {
            base.ConcedeFocus();
            this.isSelected = false;
            this.isEditing = false;
        }
 
        private Rectangle GetCheckBoxBounds(Rectangle bounds, bool alignToRight) {
            if (alignToRight)
                return new Rectangle(bounds.X +((bounds.Width - idealCheckSize) /2),
                                     bounds.Y +((bounds.Height - idealCheckSize) / 2),
                                     bounds.Width < idealCheckSize ? bounds.Width : idealCheckSize,
                                     idealCheckSize);
            else
                return new Rectangle(Math.Max(0,bounds.X +((bounds.Width - idealCheckSize) /2)),
                                     Math.Max(0,bounds.Y +((bounds.Height - idealCheckSize) / 2)),
                                     bounds.Width < idealCheckSize ? bounds.Width : idealCheckSize,
                                     idealCheckSize);
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.GetColumnValueAtRow"]/*' />
        /// <devdoc>
        ///    <para>Gets the value at the specified row.</para>
        /// </devdoc>
        protected internal override object GetColumnValueAtRow(CurrencyManager lm, int row) {
            object baseValue = base.GetColumnValueAtRow(lm, row);
            object value = Convert.DBNull;
            if (baseValue.Equals(trueValue)) {
                value = true;
            }
            else if (baseValue.Equals(falseValue)) {
                value = false;
            }
            return value;
        }
 
        private bool IsReadOnly() {
            bool ret = this.ReadOnly;
            if (this.DataGridTableStyle != null) {
                ret = ret || this.DataGridTableStyle.ReadOnly;
                if (this.DataGridTableStyle.DataGrid != null)
                    ret = ret || this.DataGridTableStyle.DataGrid.ReadOnly;
            }
            return ret;
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.SetColumnValueAtRow"]/*' />
        /// <devdoc>
        ///    <para>Sets the value a a specified row.</para>
        /// </devdoc>
        protected internal override void SetColumnValueAtRow(CurrencyManager lm, int row, object value) {
            object baseValue = null;
            if (true.Equals(value)) {
                baseValue = TrueValue;
            }
            else if (false.Equals(value)) {
                baseValue = FalseValue;
            }
            else if (Convert.IsDBNull(value)) {
                baseValue = NullValue;
            }
            currentValue = baseValue;
            base.SetColumnValueAtRow(lm, row, baseValue);
        }
        
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.GetPreferredSize"]/*' />
        /// <devdoc>
        ///    <para>Gets the optimum width and height of a cell given
        ///       a specific value to contain.</para>
        /// </devdoc>
        protected internal override Size GetPreferredSize(Graphics g, object value) {
            return new Size(idealCheckSize+2, idealCheckSize+2);
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.GetMinimumHeight"]/*' />
        /// <devdoc>
        ///    <para>Gets
        ///       the height of a cell in a column.</para>
        /// </devdoc>
        protected internal override int GetMinimumHeight() {
            return idealCheckSize+2;
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.GetPreferredHeight"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Gets the height used when resizing columns.
        ///    </para>
        /// </devdoc>
        protected internal override int GetPreferredHeight(Graphics g, object value)
        {
            return idealCheckSize + 2;
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.Abort"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Initiates a request to interrupt an edit procedure.
        ///    </para>
        /// </devdoc>
        protected internal override void Abort(int rowNum) {
            isSelected = false;
            isEditing = false;
            Invalidate();
            return;
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.Commit"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Initiates a request to complete an editing procedure.
        ///    </para>
        /// </devdoc>
        protected internal override bool Commit(CurrencyManager dataSource, int rowNum) {
            isSelected = false;
            // always invalidate
            Invalidate();
            if (!isEditing)
                return true;
 
            SetColumnValueAtRow(dataSource, rowNum, currentValue);
            isEditing = false;
            return true;
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.Edit"]/*' />
        /// <devdoc>
        ///    <para>
        ///       Prepares the cell for editing a value.
        ///    </para>
        /// </devdoc>
        protected internal override void Edit(CurrencyManager source,
                                    int rowNum,
                                    Rectangle bounds,
                                    bool readOnly,
                                    string displayText,
                                    bool cellIsVisible)
        {
            // toggle state right now...
            isSelected = true;
 
            // move the focus away from the previous column and give it to the grid
            //
            DataGrid grid = this.DataGridTableStyle.DataGrid;
            if (!grid.Focused)
                grid.FocusInternal();
 
            if (!readOnly && !IsReadOnly()) {
                editingRow = rowNum;
                currentValue = GetColumnValueAtRow(source, rowNum);
            }
 
            base.Invalidate();
        }
 
        /// <devdoc>
        ///    <para>
        ///       Provides a handler for determining which key was pressed, and whether to
        ///       process it.
        ///    </para>
        /// </devdoc>
        internal override bool KeyPress(int rowNum, Keys keyData) {
            if (isSelected && editingRow == rowNum && !IsReadOnly()) {
                if ((keyData & Keys.KeyCode) == Keys.Space) {
                    ToggleValue();
                    Invalidate();
                    return true;
                }
            }
            return base.KeyPress(rowNum, keyData);
        }
 
        /// <internalonly/>
        /// <devdoc>
        ///    <para>
        ///       Indicates whether the a mouse down event occurred at the specified row, at
        ///       the specified x and y coordinates.
        ///    </para>
        /// </devdoc>
        internal override bool MouseDown(int rowNum, int x, int y) {
            base.MouseDown(rowNum, x, y);
            if (isSelected && editingRow == rowNum && !IsReadOnly()) {
                ToggleValue();
                Invalidate();
                return true;
            }
            return false;
        }
 
        private void OnTrueValueChanged(EventArgs e) {
            EventHandler eh = this.Events[EventTrueValue] as EventHandler;
            if (eh != null)
                eh(this, e);
        }
        private void OnFalseValueChanged(EventArgs e) {
            EventHandler eh = this.Events[EventFalseValue] as EventHandler;
            if (eh != null)
                eh(this, e);
        }
        private void OnAllowNullChanged(EventArgs e) {
            EventHandler eh = this.Events[EventAllowNull] as EventHandler;
            if (eh != null)
                eh(this, e);
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.Paint"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// <para>Draws the <see cref='System.Windows.Forms.DataGridBoolColumn'/>
        /// with the given <see cref='System.Drawing.Graphics'/>,
        /// <see cref='System.Drawing.Rectangle'/> and row number.</para>
        /// </devdoc>
        protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum)
        {
            Paint(g,bounds,source, rowNum, false);
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.Paint1"]/*' />
        /// <internalonly/>
        /// <devdoc>
        /// <para>Draws the <see cref='System.Windows.Forms.DataGridBoolColumn'/>
        /// with the given <see cref='System.Drawing.Graphics'/>, <see cref='System.Drawing.Rectangle'/>,
        /// row number, and alignment settings. </para>
        /// </devdoc>
        protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum, bool alignToRight) {
            Paint(g,bounds,source, rowNum, this.DataGridTableStyle.BackBrush, this.DataGridTableStyle.ForeBrush, alignToRight);
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.Paint2"]/*' />
        /// <devdoc>
        /// <para>Draws the <see cref='System.Windows.Forms.DataGridBoolColumn'/> with the given <see cref='System.Drawing.Graphics'/>, <see cref='System.Drawing.Rectangle'/>,
        ///    row number, <see cref='System.Drawing.Brush'/>, and <see cref='System.Drawing.Color'/>. </para>
        /// </devdoc>
        protected internal override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum,
                                     Brush backBrush, Brush foreBrush,
                                     bool alignToRight) {
            object value = (isEditing && editingRow == rowNum) ? currentValue : GetColumnValueAtRow(source, rowNum);
            ButtonState checkedState = ButtonState.Inactive;
            if (!Convert.IsDBNull(value)) {
                checkedState = ((bool)value ? ButtonState.Checked : ButtonState.Normal);
            }
 
            Rectangle box = GetCheckBoxBounds(bounds, alignToRight);
 
            Region r = g.Clip;
            g.ExcludeClip(box);
 
            System.Drawing.Brush selectionBrush = this.DataGridTableStyle.IsDefault ? this.DataGridTableStyle.DataGrid.SelectionBackBrush : this.DataGridTableStyle.SelectionBackBrush;
            if (isSelected && editingRow == rowNum && !IsReadOnly()) {
                g.FillRectangle(selectionBrush, bounds);
            }
            else
                g.FillRectangle(backBrush, bounds);
            g.Clip = r;
 
            if (checkedState == ButtonState.Inactive) {
                ControlPaint.DrawMixedCheckBox(g, box, ButtonState.Checked);
            } else {
                ControlPaint.DrawCheckBox(g, box, checkedState);
            }
 
            // if the column is read only we should still show selection
            if (IsReadOnly() && isSelected && source.Position == rowNum) {
                bounds.Inflate(-1,-1);
                System.Drawing.Pen pen = new System.Drawing.Pen(selectionBrush);
                pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
                g.DrawRectangle(pen, bounds);
                pen.Dispose();
                // restore the bounds rectangle
                bounds.Inflate(1,1);
            }
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.AllowNull"]/*' />
        /// <devdoc>
        ///    <para>Gets or sets a value indicating whether null values are allowed.</para>
        /// </devdoc>
        [
        SRCategory(SR.CatBehavior),
        DefaultValue(true),
        SRDescription(SR.DataGridBoolColumnAllowNullValue)
        ]
        public bool AllowNull {
            get {
                return allowNull;
            }
            set {
                if (allowNull != value)
                {
                    allowNull = value;
                    // if we do not allow null, and the gridColumn had
                    // a null in it, discard it
                    if (!value && Convert.IsDBNull(currentValue))
                    {
                        currentValue = false;
                        Invalidate();
                    }
                    OnAllowNullChanged(EventArgs.Empty);
                }
            }
        }
 
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.AllowNullChanged"]/*' />
        public event EventHandler AllowNullChanged { 
            add {
                Events.AddHandler(EventAllowNull, value);
            }
            remove {
                Events.RemoveHandler(EventAllowNull, value);
            }
        }
        
        /// <include file='doc\DataGridBoolColumn.uex' path='docs/doc[@for="DataGridBoolColumn.EnterNullValue"]/*' />
        /// <devdoc>
        /// <para>Enters a <see langword='null'/> into the column.</para>
        /// </devdoc>
        protected internal override void EnterNullValue()
        {
            // do not throw an exception when the column is marked as readOnly or
            // does not allowNull
            if (!this.AllowNull || IsReadOnly())
                return;
            if (currentValue != Convert.DBNull) {
                currentValue = Convert.DBNull;
                Invalidate();
            }
        }
 
        private void ResetNullValue() {
            NullValue = Convert.DBNull;
        }
 
        private bool ShouldSerializeNullValue() {
            return nullValue != Convert.DBNull;
        }
 
        private void ToggleValue() {
         
            if (currentValue is bool && ((bool)currentValue) == false) {
                currentValue = true;
            }
            else {
                if (AllowNull) {
                    if (Convert.IsDBNull(currentValue)) {
                        currentValue = false;
                    }
                    else {
                        currentValue = Convert.DBNull;
                    }
                }
                else {
                    currentValue = false;
                }
            }
            // we started editing
            isEditing = true;
            // tell the dataGrid that things are changing
            // we put Rectangle.Empty cause toggle will invalidate the row anyhow
            this.DataGridTableStyle.DataGrid.ColumnStartedEditing(Rectangle.Empty);
        }
    }
}