jueves, 22 de diciembre de 2011

Personalizar DataGridView (II.1) - Bloquear columnas de solo lectura

Bien, continuando con la personalización del control DataGridView y como ya hemos visto en los artículos anteriores.

Personalizar DataGridView (III) - Cambiar Diseñador.

Continuaremos con el articulo II, en ocasiones se nos presenta la necesidad de saltar "x" columna de este control sin necesidad de que esta este marcada como solo lectura, por el simple hecho de que el usuario no quiere pasar sobre esta columna porque no necesita modificar su contenido, salvo en aquellos casos que si se requiera, generalmente no se quiere atrasar pasando sobre estas columnas.

Para estos casos lo que yo he hecho es agregarle un evento al control al cual he llamado "BeforeFocusColumn" el cual se ejecuta antes de cambiar la columna activa, con la idea de validar la columna que recibirá el foco, validar y en caso de ser necesario cambiar esta columna por la que nosotros necesitemos que reciba el foco.

ejemplo:
 
private void view_BeforeFocusColumn(object sender, BeforeFocusColumnEventArgs e)
{
    if (e.NewColumnIndex == this.colDescripcion.Index)
    {
        if (e.OldColumnIndex == this.colPrecioVenta.Index)
        {
            e.NewColumnIndex = this.colCodigo.Index;
        }
        else
        {
            // descomente esta linea para provocar una excepción al intentar
            // asignar a CurrentCell una celda no visible.
            //e.NewColumnIndex = this.colMarca.Index;
            e.NewColumnIndex = this.colPrecioVenta.Index;
        }
    }
}
 
Como podemos ver en el código de ejemplo, el evento recibe dos parámetros:
object sender: que viene a ser el control que invoca el evento.
BeforeFocusColumnEventArgs e: un nuevo argumento de evento que he creado para pasar la información necesaria al evento.

[C#]
 
public class BeforeFocusColumnEventArgs : EventArgs
{
    private MEPDataGridView Owner = null;
    private int vNewColumnIndex = -1;
    public int OldColumnIndex { get; private set; }
    public int NewColumnIndex 
    {
        get { return vNewColumnIndex; }
        set
        {
            if (this.Owner != null && this.Owner.IsHandleCreated)
            {
                if (value < 0 || value > (this.Owner.Columns.Count - 1))
                    throw new Exception("Invalid current cell index");
                else if (!this.Owner.Columns[value].Visible)
                    throw new Exception("Current cell cannot be set to an invisible cell.");
                this.vNewColumnIndex = value;
            }
        }
    }

    public BeforeFocusColumnEventArgs(int currentColumnIndex, int focusColumnIndex, MEPDataGridView owner)
    {
        this.Owner = owner;
        this.OldColumnIndex = currentColumnIndex;
        this.NewColumnIndex = focusColumnIndex;
    }
}
 
como podemos observar en el constructor de la clase la cual hereda de "EventArgs",  también recibe como parámetro el control propietario, esto con la idea de; hay que validar la columna que el programador indique que recibirá el foco en sustitución de la columna que el control nos indica que recibirá el foco.

esta validación la puede haber hecho desde el evento que ejecuta el evento pero!!!!... al hacer la validación y provocar la excepción la linea donde se marcara la excepción sera dentro de la clase "Program" en el caso de [C#], en la linea "Application.Run(new Form1());" y aquí sera difícil saber donde se ocasiono la excepción; así que, para que se marque la linea que provoca la excepción es mejor provocar esta en la propiedad "NewColumnIndex" cuando su valor es cambiado.

Este nuevo evento "BeforeFocusColumn" se ejecuta o dispara desde del evento "SetCurrentCellAddressCore".

[C#]
 
protected override bool SetCurrentCellAddressCore(int columnIndex, int rowIndex, bool setAnchorCellAddress, bool validateCurrentCell, bool throughMouseClick)
{
    if (!throughMouseClick)
    {
        int currentCellIndex = -1;
        if (this.CurrentCell != null)
            currentCellIndex = this.CurrentCell.ColumnIndex;

        BeforeFocusColumnEventArgs ea = new BeforeFocusColumnEventArgs(currentCellIndex, columnIndex, this);
        this.OnBeforeFocusColumn(ea);
        if (ea.NewColumnIndex != columnIndex)
        {
            // unselect old column index
            this.SetSelectedCellCore(columnIndex, rowIndex, false);
            // select new column index
            this.SetSelectedCellCore(ea.NewColumnIndex, rowIndex, true);
            columnIndex = ea.NewColumnIndex;
        }
    }

    return base.SetCurrentCellAddressCore(columnIndex, rowIndex, setAnchorCellAddress, validateCurrentCell, throughMouseClick);
}
 
Ademas, como podemos ver en el código, nuestro nuevo evento es ejecutado solo si la celda es cambiada por el teclado, en caso de ser cambiada por el ratón (mouse) no se ejecutara permitiendo que la celda reciba el foco, ya que la idea es esta, que el usuario final pueda modificar el contenido de la celda, posicionándose en la celda usando el ratón (mouse) pero no el teclado.



Como siempre, espero les sea de utilidad y no olviden dejar sus comentarios.


Salu2,

8 comentarios:

  1. Funciona de P*** Madre, Marvin. Pero creo que le encontrado un fallito: Si AllowReadOnly está desactivado y el el foco está en la penúltima celda (Cantidad) y está en modo edición, el foco se pasa a la siguiente celda(Precio Extendido) y no a la siguiente línea.

    ResponderEliminar
    Respuestas
    1. ya lo tengo resuelto.... lo publico luego de un par de pruebas.

      Salu2,

      Eliminar
    2. Estoy aprendiendo vb.net despues de 13 con vb5/6 y otra cosa buena, que no doy solucionado sería que el editcontrol no se coloreara hasta que se coja el foco. Así como cambiarle el color.
      El segundo punto lo tengo solucionado, y el primero parcialmente, ya que solo lo puedo ocultar una vez que ya ha cogido el foco por primera vez. Al arrancar un fomrulario, si tengo el foco en otro control indicado por un color de fondo, que no me aparezca tambien en el datagridview. ¿Sabes como ocultarlo sin que coja el foco y que se active al recibirlo?

      Eliminar
    3. mmmmm.... no logre entender, jajajajajaja.... mira, cualquier duda fuera de mis inventos o controles personalizados, puedes preguntar en los foros de MSDN, te dejo el enlace: http://social.msdn.microsoft.com/Forums/es-ES/vbes/threads

      Salu2,

      Eliminar
  2. en principio es sobre tu control (o cualquier datagridview). Cuando abres un formulario con un datagridview, éste se inicia siempre con una casilla en azul, aunque no tenga el foco. Yo dirigo el control del formulario marcando la casilla con foco con un color de fondo amarillo. El problema es que si tengo el foco en otro control, me aparecen dos "casilleros" en amarillo (un textbox inicial y la primera casilla del datagridview)

    ResponderEliminar
    Respuestas
    1. mmmmm.... entiendo.... acabo de hacer un par de pruebas y solo me queda preguntarte como estas haciendo para cambiar el BackColor cuando el control recibe y pierde el foco.

      y como es una pregunta no precisa de mis controles, bien lo podrias postear en el foro para obtener otras sugerencias.

      Salu2,

      Eliminar
  3. Para activar el color del control, lo hago en el evento que se lanza en el momento de entrar una celda en modo edición:

    Protected Overrides Sub OnEditingControlShowing(e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs)
    e.CellStyle.BackColor = Color.Yellow
    MyBase.OnEditingControlShowing(e)
    End Sub

    Al perder el foco el DGV anulo el currentcell para que no se vea:

    Protected Overrides Sub OnLostFocus(e As System.EventArgs)
    MyBase.CurrentCell = Nothing
    MyBase.OnLostFocus(e)
    End Sub

    El problema es que el DGV siempre se inicia con el editcontrol siempre activado y por lo tanto siempre se muestra. Una vez recibe el foco y lo pierde ya funciona correctamente.

    ResponderEliminar