viernes, 20 de enero de 2017

Formulario MDI (Evitar maximizado en cascada)

Bien!, como lo dice el titulo, todos los que trabajamos en .NET ya sabemos de antemano que cuando utilizamos un formulario MDI todos los formularios hijos cuando uno esta maximizado todos los demás se maximizan sin importar que tengan desactivado el botón de maximizar o que le hayamos indicado una valor al valor máximo de maximizado, Al MDIClient no le importa, si uno esta maximizado, todos se maximizaran.

bien!, la idea de este articulo es cambiar la funcionalidad del control MDIClient para que esto no suceda, ya que suele ser molesto y hace que no queramos trabajar con formularios MDI.

así que si estas interesado en saber como cambiar esta funcionalidad, no dejes de estar pendiente de este articulo.


viernes, 15 de julio de 2016

Nullable DateTimePicker (DateEditPicker)

Bueno, luego de haberme ausentado tanto de mi blog y no es que lo haya abandonado o haya dejado de programar, solo que he estado ocupado en otras cosas personales.

Bien, recientemente desarrollando una aplicación para un cliente me toco enfrentarme de frente con un campo fecha nulo, por la forma en que se desarrollo el proyecto y las necesidades del cliente tuve que dejar con valores nulos la fecha de nacimiento de los clientes, luego al iniciar a desarrollar la parte que corresponde a los clientes me vi en la necesidad de adaptar el control DateTimePicker para poder usarlo con fechas nulas, para no hacer muy largo el cuento, como hacemos todos los programadores para evitar la fatiga comense a googlear pero no me gustaron las opciones que encontré; entonces adapte un control para tal propósito, pero luego me vi en la necesidad de hacer que dicho control ademas de fecha también pudiera capturar la hora, pero como era un control hechizo no se podía, así que me tome mi tiempo para poder escribir un control para tal proposito.

En fin, la idea del control es que le permita al usuario digitar la fecha parte por parte y cuando digo parte por parte me refiero a digitar el día, el mes y el año "dd/MMM/yyyy" sin auto-completar la fecha con el mes y año actual como lo hacen la mayoría de los controles que aceptan fechas nulas.

La idea de digitar la fecha parte por parte me llevo a un dilema, ¿Como hago para hacerle saber al usuario que parte de la fecha debe digitar o que parte de la fecha esta editando cuando sea nula? (Día)/(Mes)/(Año)
como también el dilema ¿Como lo hago?

Luego de eso, era también que el control funcionar como el DateTimePicker, usando formatos ya definidos o usar un formato personalizado.

Así que lo ideal fue ver detenidamente como funcionaba el DateTimePicker hasta que logre reproducir el funcionamiento de este y como resultado:

Formato: Long




Formato: Short




Formato:




Formato: Custom (dddd, dd/MMM/yyyy HH:mm:ss tt)




Ademas de poderle agregar botones como también una imagen como lo hago con el control TextEditor, estas dos opciones eran las mas fáciles, el problema era saber según el formato donde quedaba cada parte de la fecha a editar ya que en un formato personalizado podemos usar como por ejemplo:

dddd, dd/MMM/yyyy ----- d/M,

Ejemplo en imagen:










y como sabemos el Control DateTimePicker nos permitirá editar la parte día y mes en ambas ocurrencias.

así que como resultado he creado un control que he llamado DateEditPicker que permite editar fechas nulas indicando al usuario que parte de la fecha esta editando cuando la fecha es nula.

Pueden descargar este proyecto para que prueben el control y me hagan saber sus come ntarios.

Así que espero les guste y les sea de utilidad.

Salu2,





lunes, 8 de abril de 2013

TextBox con Icon/Imagen

Bien, continuando con este articulo: TextBox con borde personalizado, ahora le dare la funcionalidad de poder mostrar un icono o imagen dentro del Control TextBox.

Existen dos maneras de hacer esto:
  • Pintar el icono/imagen dentro del control o
  • Pintar el icono/imagen dentro del Non-Client Area del control.
Pintar el icono/imagen dentro del control.

Antes de escribir el código decidi googlear un poco, para ver si alguien más ya habia tenido la misma idea de usar el mensaje EM_SETMARGINS para dejar el espacio necesario para pintar el icono o imagen ya sea a la derecha o izquierda y me he encontrado con este articulo.


Pintar el icono/imagen dentro del Non-Client Area del control.
Usando el Non-Client Area no encontre resultados googleando, así que es la forma que usare para dibujar un icono o imagen dentro de un control TextBox.

En el control TextEditor que escribí, utilizo esta manera para pintar el icono o imagen dentro del TextBox, así que, si cambian el estilo del borde a "FixedSingle" vera un pequeño problema que no considere al escribir este control, pero!!, veremos la solución a este problema.   ;-)

Imagen del problema.



Si leyeron el articulo anterior a este (TextBox con borde personalizado), veran que menciono la solución al problema, pero no esta completa, no.... como en este articulo no modifique el Non-Client Area para pintar el borde de un color diferente, la solución no esta completa, pero la completaremos en este articulo.

Así que empecemos.

WM_NCCALCSIZE: Con este mensaje dejaremos el espacio a la izquierda o derecha para pintar el icono o imagen.

C#
private bool WmNCCalcSize(ref Message m)
{
    if (this.Image == null)
    {
        this.intClientArea = Rectangle.Empty;
        return true;
    }

    if (m.WParam == (IntPtr)1)
    {
        NativeMethods.NCCALCSIZE_PARAMS ncParams = (NativeMethods.NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NativeMethods.NCCALCSIZE_PARAMS));
        int top = 0, right = 0, bottom = 0, left = 0;
        if (this.BorderStyle == BorderStyle.Fixed3D)
        {
            top = right = bottom = left = 2;
        }

        if (this.ImageAlign == ImageAlign.Left)
        {
            left += this.Image.Width + 2;
            left += this.BorderStyle != BorderStyle.Fixed3D ? 2 : 0;
        }
        else
        {
            right += this.Image.Width + 2;
            right += this.BorderStyle != BorderStyle.Fixed3D ? 2 : 0;
        }

        ncParams.rgrc0.Top += top;
        ncParams.rgrc0.Left += left;
        ncParams.rgrc0.Right -= right;
        ncParams.rgrc0.Bottom -= bottom;

        this.intClientArea = new Rectangle(left, top, ncParams.rgrc0.Right - ncParams.rgrc0.Left, ncParams.rgrc0.Bottom - ncParams.rgrc0.Top);

        Marshal.StructureToPtr(ncParams, m.LParam, false);
        return false;
    }

    return true;
}

La idea de este método privado es, si hemos seleccionado una imagen para mostrar en el control, según donde queremos que la imagen aparezca,  con este mensaje modificare el Non-Client Area del control para pintar la imagen luego en el mensaje wm_ncpaint, ademas definido el valor para la variable "intClientArea" tipo Rectangle, este sera el rectángulo a excluir para evitar el parpadeo del texto.

WM_NCPAINT: En este mensaje pintaremos la imagen.

C#
private bool WmNCPaint(ref Message m)
{
    if (!this.IsHandleCreated || 
         this.IsDisposed || 
         (this.Image == null && this.BorderColor.IsEmpty))
        return true;

    Rectangle winRect = this.WindowRect;
    if (winRect.IsEmpty)
        return true;

    Rectangle bounds = new Rectangle(0, 0, winRect.Width, winRect.Height);
    if (this.BorderStyle == BorderStyle.Fixed3D && this.BorderColor.IsEmpty)
        bounds = new Rectangle(2, 2, winRect.Width - 4, winRect.Height - 4);

    IntPtr hDC = NativeMethods.GetDCEx(this.Handle, IntPtr.Zero, NativeMethods.DCXFlags.DCX_WINDOW | NativeMethods.DCXFlags.DCX_CACHE | NativeMethods.DCXFlags.DCX_CLIPSIBLINGS);
    if (hDC != IntPtr.Zero)
    {
        Rectangle excludeClip = new Rectangle(2, 2, bounds.Width - 4, bounds.Height - 4);
        if (!this.intClientArea.IsEmpty)
            excludeClip = new Rectangle(this.intClientArea.Left, this.intClientArea.Top, this.intClientArea.Right - this.intClientArea.Left, this.intClientArea.Bottom - this.intClientArea.Top);

        NativeMethods.ExcludeClipRect(hDC, excludeClip.Left, excludeClip.Top, excludeClip.Right, excludeClip.Bottom);
        using (BufferedGraphics bg = BufferedGraphicsManager.Current.Allocate(hDC, bounds))
        {
            using (SolidBrush brush = new SolidBrush(this.BackColor))
            {
                bg.Graphics.FillRectangle(brush, bounds);
            }
            this.OnNCPaint(bg.Graphics, bounds);
            bg.Render(hDC);
        }

        NativeMethods.ReleaseDC(this.Handle, hDC);
    }

    if (this.BorderColor.IsEmpty)
        base.DefWndProc(ref m);

    return false;
}


Al seleccionar el estilo de borde "FixedSingle", tenemos el problema antes mencionado en el control TextEditor, pero para resolverlo tendremos que hacerle más cambios al código en el método WM_PAINT, veamos.


C#
private bool WmPaint(ref Message m)
{
    if (this.BorderStyle != BorderStyle.FixedSingle || 
        (this.Image == null && this.BorderColor.IsEmpty))
        return true;

    NativeMethods.PAINTSTRUCT ps = default(NativeMethods.PAINTSTRUCT);

    bool flag = false;
    IntPtr hDC = m.WParam;
    if (m.WParam == IntPtr.Zero)
    {
        hDC = NativeMethods.IntBeginPaint(new HandleRef(this, this.Handle), ref ps);
        flag = true;
    }

    // Excluimos el area del texto para evitar parpadeo.
    NativeMethods.ExcludeClipRect(hDC, this.ClientRectangle.Left + 1, this.ClientRectangle.Top + 1, this.ClientRectangle.Right - 1, this.ClientRectangle.Bottom - 1);
    using (BufferedGraphics bg = BufferedGraphicsManager.Current.Allocate(hDC, this.ClientRectangle))
    {
        using (SolidBrush brush = new SolidBrush(this.BackColor))
        {
            bg.Graphics.FillRectangle(brush, this.ClientRectangle);
        }
        this.PaintBorder(bg.Graphics, this.ClientRectangle);
        bg.Render();
    }

    // excluimos toda el area del cliente.
    NativeMethods.ExcludeClipRect(hDC, this.ClientRectangle.Left, this.ClientRectangle.Top, this.ClientRectangle.Right, this.ClientRectangle.Bottom);
    // creamos una nueva area de cliente para excluir el border pintado por nosotros
    // y evitar que lo pinte el control.
    IntPtr hRgn = NativeMethods.CreateRectRgn(this.ClientRectangle.Left + 1, this.ClientRectangle.Top + 1, this.ClientRectangle.Right - 1, this.ClientRectangle.Bottom - 1);
    // aplicamos la nueva area para que el control pinte su contenido o texto.
    NativeMethods.ExtSelectClipRgn(hDC, hRgn, 2);

    m.WParam = hDC;
    base.DefWndProc(ref m);

    if (flag)
        NativeMethods.IntEndPaint(new HandleRef(this, this.Handle), ref ps);

    return false;
}

Si leemos la documentación de este mensaje podemos leer que el parámetro wParam puede recibir un puntero a un "Device Context" y este sera utilizado para pintar el control en este.


For some common controls, the default WM_PAINT message processing checks the wParam parameter. If wParam is non-NULL, the control assumes that the value is an HDC and paints using that device context.


Así que para evitar el problema antes mencionado lo que hago es excluir el área donde se pintara el borde para evitar este problema, para lo cual he acudido al uso de otras API's del windows, forzando previamente a utilizar un "Device Context" que puedo controlar.

CreateRectRgn
ExtSelectClipRgn
IntBeginPaint
IntEndPaint

Obteniendo como resultado.




y listo... eso es todo por hoy, ahora tenemos un control TextBox al cual le podemos cambiar el color del borde y también podemos mostrar un icono/Imagen en este, cuyo valor agregado es muy útil para los usuarios de nuestras aplicaciones.

así que espero sea de utilidad para ustedes, cualquier critica o comentario, sera bienvenido.

Salu2,

Descargas:
CSharp.Windows.Forms.ControlEx(2).rar


Articulos Relacionados:
Nullable DateTimePicker


jueves, 28 de marzo de 2013

TextBox con Borde Personalizado

Bien, retomando nuevamente mi blog, luego de tanto tiempo ausente, veremos como personalizar el borde del control TextBox con un color diferente.

hace poco vi en los foros de MSDN, en el foro de VB.Net esta pregunta, ¿Cómo puedo cambiar el color del borde de un control TextBox?, anteriormente también se hizo la misma pregunta en este mismo foro donde yo respondí como hacerlo VB2010 4.0 - Como crear un textbox personalizado.

no hay manera fácil de personalizar un control, generalmente se tiene que sobre escribir el evento WndProc para escuchar los mensajes de window y reemplazar la funcionalidad de estos según sea la necesidad o el control.

bien, para cambiar el color del borde del control TextBox sin mucha funcionalidad, se debe de escuchar y reemplazar el funcionamiento de los mensajes WM_PAINT y WM_NCPAINT. ¿Porque WM_PAINT?, porque cuando cambiamos la propiedad BorderStyle de este control a FixedSingle, quien pinta el borde es el mensaje WM_PAINT no asi el WM_NCPAINT.

Para la demostración creare una clase que herede de TextBox la cual llamaremos TextBoxEx, ademas, agregare una nueva propiedad que llamare "BorderColor" con la cual indicaremos el color que tendrá el borde de este control. Cuando esta propiedad este en su valor predeterminado "Empty", el control pintara su borde.

Vista preliminar:
Ademas, se hace uso de algunas API de Windows, usadas en el mensaje WM_NCPAINT.

GetDCEx
ReleaseDC
ExcludeClipRect
GetWindowRect
RedrawWindow

Descargar proyectos tipo Librería:
VB.Windows.Forms.ControlExt.Zip
CSharp.Windows.Forms.ControlExt.Rar

Espero sea de mucha ayuda y aprendizaje el artículo.

Artículos relacionados:

TextBox con Icon/Image
Nullable DateTimePicker


miércoles, 26 de diciembre de 2012

DataGridViewTextEditorColumn (IV Parte)

Ahora veremos la clase DataGridViewTextEditorCell, esta clase como ya había comentado al inicio de este articulo, hereda de DataGridViewTextBoxCell, esta clase se encargara de pintar los botones e interactuar con los eventos del ratón (Mouse). no publicare mucho código de esta clase, solo mostrare y explicare los eventos y métodos principales.

en esta clase he sobre escrito el evento Paint(), veamos el código:
 
protected override void Paint
(
    Graphics graphics, 
    Rectangle clipBounds, 
    Rectangle cellBounds, 
    int rowIndex, 
    DataGridViewElementStates cellState, 
    object value, 
    object formattedValue, 
    string errorText, 
    DataGridViewCellStyle cellStyle, 
    DataGridViewAdvancedBorderStyle advancedBorderStyle, 
    DataGridViewPaintParts paintParts
)
{
    RepositoryButtonCollection btnCollection = null;
    if (this.columnButtons.ContainsKey(this.RowIndex))
        btnCollection = this.columnButtons[this.RowIndex] as RepositoryButtonCollection;

    //if (btnCollection != null && btnCollection.Count > 0)
    paintParts &= ~DataGridViewPaintParts.ContentForeground;

    using (BufferedGraphics bg = BufferedGraphicsManager.Current.Allocate(graphics, cellBounds))
    {
        base.Paint(bg.Graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
        this.InternalPaintButtons(bg.Graphics, cellBounds);
        bg.Render();
    }

    int totalBtnsWidth = 0;
    if (btnCollection != null)
    {
        totalBtnsWidth = 1;
        foreach (RepositoryButton btn in btnCollection)
        {
            if (btn.Visible)
                totalBtnsWidth += btn.Bounds.Width;
        }
    }

    Color foreColor = cellStyle.ForeColor;
    if ((cellState & DataGridViewElementStates.Selected) > 0)
        foreColor = cellStyle.SelectionForeColor;

    TextFormatFlags flags = TextFormatFlags.VerticalCenter;
    if (cellStyle.Alignment == DataGridViewContentAlignment.BottomLeft ||
        cellStyle.Alignment == DataGridViewContentAlignment.MiddleLeft ||
        cellStyle.Alignment == DataGridViewContentAlignment.TopLeft)
        flags |= TextFormatFlags.Left;
    if (cellStyle.Alignment == DataGridViewContentAlignment.BottomCenter ||
        cellStyle.Alignment == DataGridViewContentAlignment.MiddleCenter ||
        cellStyle.Alignment == DataGridViewContentAlignment.TopCenter)
        flags |= TextFormatFlags.HorizontalCenter;
    if (cellStyle.Alignment == DataGridViewContentAlignment.BottomRight ||
        cellStyle.Alignment == DataGridViewContentAlignment.MiddleRight ||
        cellStyle.Alignment == DataGridViewContentAlignment.TopRight)
        flags |= TextFormatFlags.Right;

    Rectangle contentForegroundBounds = new Rectangle(cellBounds.X, cellBounds.Y, cellBounds.Width - totalBtnsWidth, cellBounds.Height);
    TextRenderer.DrawText
        (
            graphics,
            formattedValue.ToString(),
            cellStyle.Font,
            contentForegroundBounds,
            foreColor,
            flags
        );
}
 
bueno, me disculparan un poco por el código pero como recientemente lo escribí no me ha quedado tiempo de depurar o organizarlo bien y he estado haciendo pruebas según lo uso... por ejemplo.

se llama el Paint base de la clase pero se le indica que no pinte el ContentBackground con la idea de pintarlo más abajo, porque?, como podrán ver el evento Paint base se llamada dentro de un buffer gráfico para evitar el parpadeo de los botones al mover el cursor repetidamente y constantemente sobre los botones, esto lo hice para probarlo y hasta ahorita fue la manera en que pensé evitarlo, aunque un problema secundario era que no se pintaba el texto de la celda, porque? ni idea... seguro algún problema con mi buffer y el código en la clase base, algo esta entrando en conflicto y probando seguro la clase base usa la clase TextRenderer para dibujar el texto ya que haciendo pruebas también me dio ese problema, por eso deje por fuera del buffer el dibujado del texto.

luego de invocar el Paint base llamo un método interno que se encarga de pintar los botones de cada celda en cada linea según la propiedad ShowButtonMode en la clase DataGridViewTextEditorColumn

 
private void InternalPaintButtons(Graphics g, Rectangle bounds)
{
    int btnsWidth = 1;
    foreach (RepositoryButton cellButton in this.OwnerColumn.ColumnEditor.Buttons)
        btnsWidth += cellButton.Width < 0 ? this.defaultBtnWidth : cellButton.Width;

    RepositoryButtonCollection buttonCollection = new RepositoryButtonCollection();

    int x = bounds.Right - btnsWidth - 1;
    foreach (RepositoryButton cellButton in this.OwnerColumn.ColumnEditor.Buttons)
    {
        if (!cellButton.Visible)
            continue;

        int w = cellButton.Width < 0 ? this.defaultBtnWidth : cellButton.Width;

        RepositoryButton btn = new RepositoryButton();
        btn.Image = cellButton.Image;
        btn.Visible = cellButton.Visible;
        btn.Enabled = cellButton.Enabled;
        btn.Width = cellButton.Width;
        btn.Bounds = new Rectangle(x, bounds.Y, w, bounds.Height - 1);
        buttonCollection.Add(btn);

        x += w;
    }

    if (this.columnButtons.ContainsKey(this.RowIndex))
        this.columnButtons[this.RowIndex] = buttonCollection;
    else
        this.columnButtons.Add(this.RowIndex, buttonCollection);

    foreach (RepositoryButton btn in buttonCollection)
    {
        if (btn.Visible)
        {
            bool showBtns = false;
            if (this.OwnerColumn.Site != null)
                showBtns = true;
            else if (this.OwnerColumn.ShowButtonMode == ShowButtonMode.FocusedRow)
                showBtns = this.DataGridView.CurrentRow != null &&
                           this.DataGridView.CurrentRow.Index == this.RowIndex;
            else if (this.OwnerColumn.ShowButtonMode == ShowButtonMode.FocusedCell)
                showBtns = this.DataGridView.CurrentCell != null &&
                           this.DataGridView.CurrentCell.ColumnIndex == this.ColumnIndex &&
                           this.DataGridView.CurrentCell.RowIndex == this.RowIndex;
            else
                showBtns = true;

            if (showBtns)
                this.InternalPaintButton(g, btn);
        }
    }
}
 
... siempre se me olvida documentar cada linea y luego tengo problemas para recordar para que sirven, generalmente primero escribo y después lo documento si funciona... ;-), pero... así se vuelve más interesante para los lectores ;-)

tratare de explicarlo de una forma breve:

el primer foreach calcula el ancho de los botones en la celda:
 
int btnsWidth = 1;
foreach (RepositoryButton cellButton in this.OwnerColumn.ColumnEditor.Buttons)
    btnsWidth += cellButton.Width < 0 ? this.defaultBtnWidth : cellButton.Width;
 
el segundo foreach estable la posición de dibujado de cada botón:
 
int x = bounds.Right - btnsWidth - 1;
foreach (RepositoryButton cellButton in this.OwnerColumn.ColumnEditor.Buttons)
{
    if (!cellButton.Visible)
        continue;

    int w = cellButton.Width < 0 ? this.defaultBtnWidth : cellButton.Width;

    RepositoryButton btn = new RepositoryButton();
    btn.Image = cellButton.Image;
    btn.Visible = cellButton.Visible;
    btn.Enabled = cellButton.Enabled;
    btn.Width = cellButton.Width;
    btn.Bounds = new Rectangle(x, bounds.Y, w, bounds.Height - 1);
    buttonCollection.Add(btn);

    x += w;
}
 
y el tercer foreach se encarga de pintar cada botón:
 
foreach (RepositoryButton btn in buttonCollection)
{
    if (btn.Visible)
    {
        bool showBtns = false;
        if (this.OwnerColumn.Site != null)
            showBtns = true;
        else if (this.OwnerColumn.ShowButtonMode == ShowButtonMode.FocusedRow)
            showBtns = this.DataGridView.CurrentRow != null &&
                       this.DataGridView.CurrentRow.Index == this.RowIndex;
        else if (this.OwnerColumn.ShowButtonMode == ShowButtonMode.FocusedCell)
            showBtns = this.DataGridView.CurrentCell != null &&
                       this.DataGridView.CurrentCell.ColumnIndex == this.ColumnIndex &&
                       this.DataGridView.CurrentCell.RowIndex == this.RowIndex;
        else
            showBtns = true;

        if (showBtns)
            this.InternalPaintButton(g, btn);
    }
}
 
si se esta en tiempo de diseño siempre se mostraran los botones y si esta en ejecución se pintaran según la propiedad ShowButtonMode de clase DataGridViewTextEditorColumn como lo mencione anteriormente.

Evento InitializeEditingControl, este evento se ha sobre escrito para inicializar el editor de la celda cambiando algunas propiedades de este según se haya indicado en la clase RepositoryTextEditor.

veamos el código:
 
public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
    base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
    DataGridViewTextEditorControl editor = this.DataGridView.EditingControl as DataGridViewTextEditorControl;
    editor.Buttons.Clear();

    if (this.columnButtons.ContainsKey(this.RowIndex))
    {
        RepositoryButtonCollection btnCollection = this.columnButtons[this.RowIndex] as RepositoryButtonCollection;
        foreach (RepositoryButton btn in btnCollection)
        {
            TextEditor.TextEditorButton editorBtn = new TextEditor.TextEditorButton()
            {
                Image = btn.Image,
                Width = btn.Width,
                Visible = btn.Visible,
                Enabled = btn.Enabled
            };
            editor.Buttons.Add(editorBtn);
        }
    }

    editor.MaskType = this.OwnerColumn.ColumnEditor.MaskType;
    editor.Text = initialFormattedValue.ToString();
    editor.TextAlign = this.OwnerColumn.ColumnEditor.TextAlign;
    editor.AllowWhiteSpace = this.OwnerColumn.ColumnEditor.AllowWhiteSpace;
    editor.CharacterCasing = this.OwnerColumn.ColumnEditor.CharacterCasing;
    editor.BorderStyle = TextEditor.BorderStyle.NoBorder;
    editor.AutoHeight = false;
    editor.Dock = DockStyle.Fill;
    editor.EnterMoveNextControl = false;
}
 
y con este último capitulo cerramos este artículo, espero sea de utilidad esta clase DataGridViewTextEditorColumn y cualquier problema o duda, estamos para ayudar y aclarar.

Código Fuente:
CSharp_DataGridViewTextEditorColumn


martes, 18 de diciembre de 2012

DataGridViewTextEditorColumn (III Parte)

Continuando con el articulo, ahora veremos la clase DataGridViewTextEditorColumn, esta clase hereda de DataGridViewColumn y le he agregado dos propiedades para nuestro propósito  ShowButtonMode y ColumnEditor, esta ultima propiedad es del tipo RepositoryTextEditor.

Los valores para ShowButtonMode son:
  • Always: Indica que los botones de la celda siempre estaran visibles.
  • FocusedRow: Indica que los botones solo se mostraran en la fila activa en el momento.
  • FocusedCell: Indica que los botones solo se mostraran en la celda activa en el momento.
Veamos el código:
 
[ToolboxBitmap(typeof(System.Windows.Forms.TextBox))]
public class DataGridViewTextEditorColumn : DataGridViewColumn
{
    private ShowButtonMode showButtonMode;
    private RepositoryTextEditor repositoryTextEditor;
    private IDesignerHost IDesignerHost
    {
        get
        {
            return this.DataGridView.Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
        }
    }
    
    // Crea el editor de la celda en caso de no existir, en caso de existir
    // se estable el DataGridView que contiene la columna.
    private void CreateColumnEditor()
    {
        if (this.DataGridView == null || this.repositoryTextEditor != null)
        {
            if (this.repositoryTextEditor != null)
                this.repositoryTextEditor.DataGridViewOwner = this.DataGridView;
            return;
        }

        IDesignerHost host = this.IDesignerHost;
        this.ColumnEditor = host.CreateComponent(typeof(RepositoryTextEditor)) as RepositoryTextEditor;
        this.ColumnEditor.DataGridViewOwner = this.DataGridView;
    }
    public DataGridViewTextEditorColumn() : base(new DataGridViewTextEditorCell())
    {
        this.showButtonMode = ShowButtonMode.Always;
    }

    [DefaultValue(typeof(ShowButtonMode), "Always")]
    public ShowButtonMode ShowButtonMode
    {
        get { return this.showButtonMode; }
        set
        {
            this.showButtonMode = value;
        }
    }
    public RepositoryTextEditor ColumnEditor
    {
        get
        {
            this.CreateColumnEditor();
            return this.repositoryTextEditor;
        }
        set 
        { 
            this.repositoryTextEditor = value;
            if (this.DataGridView != null)
                this.DataGridView.Invalidate();
        }
    }
    public override DataGridViewCell CellTemplate
    {
        get
        {
            return base.CellTemplate;
        }
        set
        {
            if (value != null && !value.GetType().IsAssignableFrom(typeof(DataGridViewTextEditorCell)))
            {
                throw new InvalidCastException("Must be a DataGridViewTextEditorCell");
            }
            base.CellTemplate = value;
        }
    }

    public override object Clone()
    {
        DataGridViewTextEditorColumn clone = base.Clone() as DataGridViewTextEditorColumn;
        clone.ColumnEditor = this.ColumnEditor;
        clone.ShowButtonMode = this.ShowButtonMode;

        return clone;
    }
}
 
y el código de ShowButtonMode:
 
public enum ShowButtonMode
{
    Always,
    FocusedRow,
    FocusedCell
}
 

DataGridViewTextEditorColumn (II Parte)
DataGridViewTextEditorColumn (IV Parte)



sábado, 8 de diciembre de 2012

DataGridViewTextEditorColumn (II Parte)

Bien, continuando con el articulo.... la primera clase que veremos sera RepositoryTextEditor, este heredara de Component ya que no tendrá una UI que interactue con el usuario.
 
[ToolboxBitmap(typeof(System.Windows.Forms.TextBox))]
[DesignerSerializer(typeof(RepositoryButtonSerializer), typeof(CodeDomSerializer))]
public class RepositoryTextEditor : Component
{
    private string name;
    private bool allowWhiteSpace;
    private HorizontalAlignment textAlign;
    private CharacterCasing characterCasing;
    private TextEditor.MaskType maskType;
    private RepositoryButtonCollection buttons;
    private static readonly object buttonClick;
    internal System.Windows.Forms.DataGridView DataGridViewOwner { get; set; }

    public delegate void RepositoryButtonPressEventHandler(object sender, RepositoryButtonArgs args);
    public event RepositoryButtonPressEventHandler ButtonClick
    {
        add
        {
            base.Events.AddHandler(buttonClick, value);
        }
        remove
        {
            base.Events.RemoveHandler(buttonClick, value);
        }
    }

    static RepositoryTextEditor()
    {
        buttonClick = new object();
    }
    public RepositoryTextEditor()
    {
        this.allowWhiteSpace = true;
        this.characterCasing = CharacterCasing.Normal;
        this.textAlign = HorizontalAlignment.Left;
        this.maskType = TextEditor.MaskType.None;
        this.buttons = new RepositoryButtonCollection(this);
    }

    [DefaultValue(typeof(TextEditor.MaskType), "None")]
    public TextEditor.MaskType MaskType
    {
        get { return this.maskType; }
        set { this.maskType = value; }
    }
    [DefaultValue(typeof(HorizontalAlignment), "Left")]
    public HorizontalAlignment TextAlign
    {
        get { return this.textAlign; }
        set { this.textAlign = value; }
    }
    [DefaultValue(true)]
    public bool AllowWhiteSpace
    {
        get { return this.allowWhiteSpace; }
        set { this.allowWhiteSpace = value; }
    }
    [DefaultValue(typeof(CharacterCasing), "Normal")]
    public CharacterCasing CharacterCasing
    {
        get { return this.characterCasing; }
        set { this.characterCasing = value; }
    }
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public RepositoryButtonCollection Buttons
    {
        get
        {
            return this.buttons;
        }
    }
    [Browsable(false), DefaultValue("")]
    public string Name
    {
        get
        {
            if (this.Site != null)
                return this.Site.Name;
            return this.name;
        }
        set
        {
            if (value != null)
                this.name = value;
        }
    }

    internal void OnButtonClick(RepositoryButtonArgs args)
    {
        RepositoryButtonPressEventHandler handler = (RepositoryButtonPressEventHandler)base.Events[buttonClick];
        if (handler != null)
            handler.Invoke(this, args);
    }
    internal void InvalidateDataGridView()
    {
        if (this.DataGridViewOwner != null)
            this.DataGridViewOwner.Invalidate();
    }
}
 
No crean que me gusta la idea de mostrar mucho código, porque en muchas ocasiones no se logra entender nada... pero bien, el editor de esta columna sera mi control TextEditor y este componente le dirá al editor que propiedades deberá de cambiar para cumplir bien con su función, por ende en este componente he agregado las siguientes propiedades:

Propiedades
  • Buttons, Colección de botones que alojara la columna como el editor.
  • MaskType, que le indicara al editor que tipo de entrada aceptara cuando se edite la celda. (esta propiedad o enum es del control TextEditor).
  • TextAlign, indicara como se alineara el texto en el editor.
  • AllowWhiteSpace, Indicara si el editor permitirá espacios en blanco entre caracteres.
  • CharacterCasingObtiene o establece si el Editor modifica la condición de mayúscula o minúscula de los caracteres a medida que se escriben.
  • Name, que indicara el nombre del componente.
Eventos
  • ButtonClick, sera el evento que se invocara al presionar cualquiera de los botones de la celda como del editor, la idea de usar un componente es poder tener acceso al evento desde el diseñador sin necesidad de instanciarlo desde el Load del contenedor del DataGridView y recibe la siguiente clase como argumento:
 
    public class RepositoryButtonArgs : EventArgs
    {
        public RepositoryButtonArgs(int rowIndex, int buttonIndex)
        {
            this.RowIndex = rowIndex;
            this.ButtonIndex = buttonIndex;
        }
        public int RowIndex { get; private set; }
        public int ButtonIndex { get; private set; }
    }
 
Ademas podemos ver entre los atributos de este componente el uso de CodeDomSerializer, para serializar o persistir en el diseñador la propiedad Buttons de una manera personalizada y ya habiamos visto su utilización con el control ComboBoxMulticolumns.

DataGridViewTextEditorColumn (III Parte).