Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   C++ Builder (https://www.clubdelphi.com/foros/forumdisplay.php?f=13)
-   -   Rellenar StringGrid con base de datos (https://www.clubdelphi.com/foros/showthread.php?t=81275)

borlandpablo 29-10-2012 11:03:22

Rellenar StringGrid con base de datos
 
Buenas, estoy intentando llevar un control de catalogos de forma visual. Me he creado un calendario personalidazo donde me aparecen en cada fila el nombre del catálogo y en cada columna la fecha. La fecha de inicio siempre empieza en la fecha actual. Ahora quiero rellenar de color verde desde la fecha de inicio de un catalogo hasta la fecha final. Esto es lo que tengo hecho, pero no me rellena nada.
Código:

void __fastcall TXVista::FormShow(TObject *Sender)
{
  XDatos->QDistribuidor->Open();
  XDatos->QTienda->Open();
  XDatos->QCatalogos->Open();

  TDate fecha = Date();

  for(int i=1; i<=63; i++) //Meses, columnas
  {
      bool encontrado = false;
      StringGrid1->ColCount = i;
      StringGrid1->Cells[i][0]= fecha;
      fecha = fecha + 1;
      StringGrid1->RowCount = XDatos->QCatalogos->RecordCount + 1;

      for(int j=1; j<=XDatos->QCatalogos->RecordCount; j++) //catalogos, filas
      {
        while(!XDatos->QCatalogos->Eof)
        {
            StringGrid1->Cells[0][j] = XDatos->QCatalogosTitulo->Value;
            if(StringGrid1->Cells[i][0] == XDatos->QCatalogosTitulo->Value && StringGrid1->Cells[0][j] == XDatos->QCatalogosFecha_ini->Value)
              encontrado = true;

            if(encontrado)
              StringGrid1->Color = clLime;

            XDatos->QCatalogos->Next();
            j = j + 1;
        }
      }
  }
}

Un saludo

ecfisa 29-10-2012 13:43:31

Hola.

Es que para rellenar las celdas con color, vas a tener que usar el evento OnDrawCell del StringGrid.

Como para darte una idea, te pongo un ejemplo que pinta las filas cuyas celdas [0][n] tengan una fecha comprendida entre otras dos.

Código:


...

void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender,
  int ACol, int ARow, TRect &Rect, TGridDrawState State) {

  TCanvas *CV = static_cast<TCanvas*>(StringGrid1->Canvas);
  TDate FechaCell = StrToDate(StringGrid1->Cells[0][ARow]);

  if (FechaCell >= FechaInicio && FechaCell <= FechaFin) {
    CV->Brush->Color = clLime;
    CV->FillRect(Rect);
    CV->TextOut(Rect.Left+1,Rect.top+1,StringGrid1->Cells[ACol][ARow]);
  }
}
...

Saludos.

borlandpablo 30-10-2012 10:16:28

Buenas, he puesto eso que me dices, he cambiado algunas cosillas porque las fechas están en la base de datos y hago la llamada al campo de la tabla. Me da un error que no sé solucionar. El error dice:

Proyect Promociones.exe raised exeception class EConvertError with message "' is not valid date'. Process stopped. Use Step or Run to continue.

La modificación que hice:

Código:

void __fastcall TXVista::StringGrid1DrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State)
{
  TCanvas *CV = static_cast<TCanvas*>(StringGrid1->Canvas);
  TDate FechaCell = StrToDate(StringGrid1->Cells[0][ARow]);

  while(!XDatos->QCatalogos->Eof)
  {
    if (FechaCell >= XDatos->QCatalogosFecha_ini->Value && FechaCell <= XDatos->QCatalogosFecha_fin->Value)
    {
      CV->Brush->Color = clLime;
      CV->FillRect(Rect);
      CV->TextOut(Rect.Left+1,Rect.top+1,StringGrid1->Cells[ACol][ARow]);
    }
    XDatos->QCatalogos->Next();
  }
}


ecfisa 30-10-2012 17:36:04

Hola.

El error se produce, con seguridad, en la línea:
Código:

    TDate FechaCell = StrToDate(StringGrid1->Cells[0][ARow]);
En algún punto, el contenido de Cells[0][ARow] no es un valor susceptible de ser convertido a fecha, situación que sucedería por ejemplo, si en la fila 0 existiesen títulos para las columnas.

Una solución es:
Código:

void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State)
{
  TCanvas *CV = static_cast<TCanvas*>(StringGrid1->Canvas);
  TDate FechaCell;

  if (TryStrToDate(StringGrid1->Cells[0][ARow],FechaCell))
    if (FechaCell >= FechaInicio && FechaCell <= FechaFin) {
      CV->Brush->Color = clLime;
      CV->FillRect(Rect);
      CV->TextOut(Rect.Left+1,Rect.top+1,StringGrid1->Cells[ACol][ARow]);
    }
}

(Siempre suponiendo que en la columna 0 del StringGrid haya un contenido de fecha válido.)

Saludos.

borlandpablo 31-10-2012 09:40:31

Buenas, tuve que variar el código porque seguía dándome errores. También donde me mencionaste. Ahora tengo otro problema. Ahora parece que rellena, pero solo el ultimo registro de la base de datos. Pero si hago un while (para que recorra la base de datos) y poniéndolo en first(primer registro), se vuele loco el programa me lo va a rellenando hipersupermegalento (podría tardar horas en rellenarse el stringgrid). Pienso que a lo mejor no está bien colocado el while o algo, pero no sé donde queda el fallo. Dejo lo que tengo hecho de código.

Código:

void __fastcall TXVista::StringGrid1DrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State)
{
  TRect Casilla;

  for(int ACol=1; ACol<=StringGrid1->ColCount; ACol++)
  {
      for(int ARow=1; ARow<=StringGrid1->RowCount; ARow++)
      {
        XDatos->QCatalogos->First();
        while(!XDatos->QCatalogos->Eof)
        {
            if(StringGrid1->Cells[ACol][0] == DateToStr(XDatos->QCatalogosFecha_ini->Value) && StringGrid1->Cells[0][ARow] == XDatos->QCatalogosTitulo->Value)
            {
              StringGrid1->Canvas->Brush->Color=clLime;
              Casilla= StringGrid1->CellRect(ACol, ARow);
              StringGrid1->Canvas->FillRect(Casilla);
            }
            XDatos->QCatalogos->Next();
        }

      }
  }
}

Un saludo.

ecfisa 31-10-2012 13:26:59

Hola.

Cita:

tuve que variar el código porque seguía dándome errores
Los errores se van a producir en la medida que los valores evaluados en las celdas no sean aptos para ser convertidos al tipo TDate. Con la función TryStrToDate esto se evita, pero de no ser posible la conversión no se pintará la celda del color buscado.

Resumiendo, el código del mensaje #4, no elevará una excepción fuere cuál fuere el valor almacenado en la columna 0 del StringGrid. Ya que si el valor de la celda no se puede convertir TryStrToDate devuelve false y no se ejecuta alteración alguna. El error sin dudas se produce en otra parte de tu código.

Cita:

Pero si hago un while (para que recorra la base de datos) y poniéndolo en first(primer registro), se vuele loco el programa me lo va a rellenando hipersupermegalento...
Y no es para menos...

El evento OnDrawCell se dispara cada vez que una celda del StringGrid necesita ser pintada. No es buena idea recorrer una tabla o consulta dentro de este evento, imagina la cantidad de veces que realizará ese recorrido...

Como verás, en el código que te puse, los valores FechaInicio y FechaFin toman valores fuera de este evento y no se realiza búsqueda alguna dentro del mismo.

Saludos. :)

borlandpablo 04-11-2012 15:54:01

Cita:

Empezado por ecfisa (Mensaje 448414)
Hola.


Los errores se van a producir en la medida que los valores evaluados en las celdas no sean aptos para ser convertidos al tipo TDate. Con la función TryStrToDate esto se evita, pero de no ser posible la conversión no se pintará la celda del color buscado.

Resumiendo, el código del mensaje #4, no elevará una excepción fuere cuál fuere el valor almacenado en la columna 0 del StringGrid. Ya que si el valor de la celda no se puede convertir TryStrToDate devuelve false y no se ejecuta alteración alguna. El error sin dudas se produce en otra parte de tu código.


Y no es para menos...

El evento OnDrawCell se dispara cada vez que una celda del StringGrid necesita ser pintada. No es buena idea recorrer una tabla o consulta dentro de este evento, imagina la cantidad de veces que realizará ese recorrido...

Como verás, en el código que te puse, los valores FechaInicio y FechaFin toman valores fuera de este evento y no se realiza búsqueda alguna dentro del mismo.

Saludos. :)

Buenas, la cosa es que necesito los valores que tengo en la base de datos. De qué manera puedo hacerlo para que me lo pinte con las fechas de la base de datos?

Un saludo

ecfisa 05-11-2012 08:39:16

Hola borlandpablo.

No comentaste que condición debe cumplirse para que la celda se pinte. Pero por tu código del mensaje #5, deduzco que es cuando la fecha que está en la celda del StringGrid es igual (exíste) en determinado campo de la tabla en que realizas la búsqueda.

Si es así, de este modo hará lo que buscas:
Código:

...
#define COL_DATE 0  // Columna correspondiente a la fecha

...

void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State) {
  TCanvas *CV = static_cast<TCanvas*>(StringGrid1->Canvas);
  TDate FechaCell;

  if (ACol == COL_DATE && TryStrToDate(StringGrid1->Cells[COL_DATE][ARow],FechaCell)) {
    QryTmp->Close();
    QryTmp->SQL->Text = "SELECT FECHA FROM TABLA WHERE FECHA = :VALUE";
    QryTmp->ParamByName("VALUE")->AsString = StringGrid1->Cells[COL_DATE][ARow];
    QryTmp->Open();
    if (!QryTmp->IsEmpty()) {
      CV->Brush->Color = clLime;
      CV->FillRect(Rect);
      CV->TextOut(Rect.Left+1 ,Rect.top+1, StringGrid1->Cells[ACol][ARow]);
    }
  }
}

En la consulta SQL tenes que cambiar "TABLA" por el nombre de tu tabla y "FECHA" por el nombre del campo a consultar.
La sintáxis del query (QryTmp) puede variar de acuerdo a los componentes que estas usando y que desconozco por que tampoco encontré mención a ellos en tus mensajes.

Revisando a fondo el código del mensaje #5, no sólo recorre la tabla por cada celda ¡ Sino que por cada celda recorre todas las celdas leyendo secuencialmente la tabla cada vez ! de allí que el programa se volvía "loco"...

Este código sólo realiza una consulta (muchísimo mas veloz que una busqueda secuencial) cuando se va a pintar una celda de la columna COL_DATE (fecha) y su contenido es una fecha válida.
De todas formas no es lo óptimo, pienso que un TDBGrid cumpliría mejor con el cometido... pero para asegurarlo tendría que entender bién la lógica de lo que estas intentando hacer.

Saludos.

borlandpablo 06-11-2012 10:12:21

Buenas ecfisa, lo quiero hacer es un control de las promociones de los catálogos. Esas promociones tienen una fecha de inicio y una fecha final. En cada columna me aparecerá la fecha de hoy hasta el tiempo que yo quiera mostrar (solo necesito dos meses mostrados). Y en las filas quiero que me muestre el nombre del cátalogo. Entonces, quiero que por medio de una tabla visual (en este caso un StringGrid), quiero que me rellene en color aquellas celdas en que las fechas sean iguales. Por ejemplo:

Código:

Catálogo          Fecha_inicio      Fecha_fin
Renuevate      10/11/2012      10/12/2012
Navidades        15/11/2012      30/12/2012

Con esto quiero que me coloree todas las celdas que coincidan entre esas dos fechas en cada registro.

PD: Con la solución que me diste no me hace nada. Me compila bien pero no entra en la primera condición (He cambiado lo que me dijiste en el mensaje anterior a este).

Un saludo.

ecfisa 06-11-2012 15:52:11

Hola Pablo.

La situación sigue sin quedarme clara:
Cita:

Esas promociones tienen una fecha de inicio y una fecha final. En cada columna me aparecerá la fecha de hoy hasta el tiempo que yo quiera mostrar
Pero en la tabla visual que mostras, ¿ Donde se hubicaría la fecha de hoy ? ¿ Que límite desempeña en el rango fecha_inicio/fecha_fin ?

Cita:

Entonces, quiero que por medio de una tabla visual (en este caso un StringGrid), quiero que me rellene en color aquellas celdas en que las fechas sean iguales
¿ Que sean iguales a que ?
  • Una fecha ingresada
  • Entre dos fechas de la misma columna
  • O, como mencionas mas arriba, que esten comprendidas entre dos fechas, y si es así ¿ Estas serían los valores de las columnas Fecha_inicio y Fecha_fin ?

Saludos.

borlandpablo 07-11-2012 10:09:38

1 Archivos Adjunto(s)
Buenas ecfisa. Te he adjuntado una imagen para que veas lo que quiero que muestre. Las fechas de los catálogos están asignadas en la base de datos. Tienen una fecha de inicio y una fecha fin para que me rellene de color las fechas comprendidas entre esas dos comparándola con las fechas que tengo creadas en el StringGrid. La tabla la creé de esta manera:

Código:

void __fastcall TXVista::FormShow(TObject *Sender)
{
  XDatos->QDistribuidor->Open();
  XDatos->QTienda->Open();
  XDatos->QCatalogos->Open();

  TDate fecha = Date();

  for(int i=1; i<=63; i++) //Rellena meses, columnas
  {
      StringGrid1->ColCount = i;
      StringGrid1->Cells[i][0]= fecha;
      fecha = fecha + 1;
      StringGrid1->RowCount = XDatos->QCatalogos->RecordCount + 1;

      for(int j=1; j<=XDatos->QCatalogos->RecordCount; j++) //Rellena catalogos, filas
      {
        while(!XDatos->QCatalogos->Eof)
        {
            StringGrid1->Cells[0][j] = XDatos->QCatalogosTitulo->Value;
            XDatos->QCatalogos->Next();
            j = j + 1;
        }
      }
  }
}

PD: Te mandé un mensaje privado.

Un saludo.

ecfisa 07-11-2012 14:11:32

Hola Pablo.

Ahora viendo el gráfico pude entender lo que buscas hacer. La fecha a comparar entre las del archivo es la que esta en la fila cero del StringGrid y el nombre a buscar en la tabla está en la columna cero de la misma.

Entonces, esta es la prueba que realicé y funciona bién:
Código:

...
#include <DateUtils.hpp>
...
void __fastcall TForm1::FormCreate(TObject *Sender) {

  TStringGrid *sg = static_cast<TStringGrid*>(StringGrid1);
  TDate dt = Date();
  AnsiString s;
  sg->ColCount = 31;
  sg->Cells[0][1] = "Renuevate";
  sg->Cells[0][2] = "Navidades";
  sg->Cells[0][3] = "Ocasion";
  for(int c =DayOf(Date()); c<= DaysInMonth(Date()); c++) {
      sg->Cells[c-DayOf(Date())+1][0] = DateToStr(dt);
      dt=IncDay(dt,1);
  }
  sg->Col = 1;
  sg->Row = 1;
  /* Asignar evento OnSelectCell por código */
  sg->OnSelectCell = StringGrid1SelectCell;
}

void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender, int ACol,
      int ARow, TRect &Rect, TGridDrawState State) {
  TStringGrid *sg = static_cast<TStringGrid*>(StringGrid1);

  if (ACol > 0 && ARow > 0 && sg->Cells[ACol][ARow] != "") {
    QryTmp->Close();
    QryTmp->SQL->Clear();
    QryTmp->SQL->Add("SELECT * FROM CATALOG");
    QryTmp->SQL->Add("WHERE NOMBRE_CATALOGO = :PNOMBRE");
    QryTmp->SQL->Add("AND :PFECHA >= FECHA_DESDE AND :PFECHA <= FECHA_HASTA");
    // En columna 0 estan los nombres
    QryTmp->ParamByName("PNOMBRE")->AsString = sg->Cells[0][ARow];
    // En fila 0 estan las fechas
    QryTmp->ParamByName("PFECHA")->AsString  = sg->Cells[ACol][0];
    QryTmp->Open();
    if (!QryTmp->IsEmpty()) {
      sg->Canvas->Brush->Color = clLime;
      sg->Canvas->FillRect(Rect);
      sg->Canvas->TextOut(Rect.Left+1 ,Rect.top+1, sg->Cells[ACol][ARow]);
    }
  }
}

/* Impedir que seleccionen columna 0 o fila 0 */
void __fastcall TForm1::StringGrid1SelectCell(TObject *Sender, int ACol,
      int ARow, bool &CanSelect) {
 CanSelect = ACol > 0 && ARow > 0;
}

...

void __fastcall TForm1::FormDestroy(TObject *Sender) {
 StringGrid1->OnSelectCell = NULL;
}

La tabla que cree para la prueba tiene el siguiente formato:
Código:

NOMBRE_CATALOGO | FECHA_DESDE | FECHA_HASTA
----------------+-------------+------------
Renuevate      | 07.11.2012  | 08.11.2012
Navidades      | 09.11.2012  | 10.11.2012
Ocasion        | 08.11.2012  | 10.11.2012

Los valores son iguales a los de tu imágen.

Como verás en el resultado, el pintado considera las fechas límites de la tabla en relación a la columna y de acuerdo al nombre de la fila:


No olvides quitar el evento OnSelectCell desde el Object Inspector, lo asigno por código para que permita el posicionamiento en Col = 1, Row = 1.

Saludos.

Edito: Me olvidaba... Y si deseas que se pinten las celdas aún estando vacías, cambiá:
Código:

  if (ACol > 0 && ARow > 0 && sg->Cells[ACol][ARow] != "") {
  // por:
  if (ACol > 0 && ARow > 0) {


borlandpablo 08-11-2012 10:40:34

Qué maquina ecfisa! Muchísimas gracias! es lo que ando buscando! Yo lo acabo de hacer de otra manera, sin utilizar consulta. Lo hago con bucles y pienso que es mucho mas lento que como tu lo tienes. Voy a cambiarlo!

Un saludo!

borlandpablo 08-11-2012 12:29:21

Buenas ecfisa, acabo de cambiarlo y no me lo pinta. Parece extraño porque compila bien. He copiado el código tal cual está y solo he modificado el nombre de la query y los campos. Creo que la consulta no me lo está haciendo bien. En la consulta en vez ->Add no sería ->Text = "consulta"; ? No que sea de otro error.

Un saludo.

ecfisa 08-11-2012 19:21:12

Cita:

Empezado por borlandpablo (Mensaje 448925)
En la consulta en vez ->Add no sería ->Text = "consulta"; ? No que sea de otro error.

No Pablo, no hay ninguna diferencia en asignar la cadena a la propiedad Text o hacerlo mediante el método Add aunque de este modo previamente hay que utilizar el método Clear para eliminar posible texto residual.

El problema debe estar en otro lado, seguramente en la sintáxis SLQ. Lo que nos lleva a lo que te pregunté mensajes atras: ¿ Que manejador de base de datos y que componentes utilizas ?

Saludos.

borlandpablo 09-11-2012 09:34:13

Utilizo un ADO para la conexión a la base de datos creada en access y ADOQuerys para los campos de las tablas

Un saludo.

borlandpablo 13-11-2012 14:08:05

Buenas ecfisa, alguna solución a mi problema? Perdona por mi impaciencia.

Un saludo

ecfisa 13-11-2012 16:55:45

Cita:

Empezado por borlandpablo (Mensaje 449319)
Buenas ecfisa, alguna solución a mi problema? Perdona por mi impaciencia.

Un saludo

Hola Pablo.

El problema es que no uso Ms Office y me encuentro impedido de realizar una tabla con esa estructura en en Access, eso me permitiría realizar las pruebas necesarias para reacomodar la sentencia SQL.

Tál como está el código, funciona correctamente en Firebird. Quizá una solución sea que pongas la sentencia SQL en el foro Tablas planas y ver si algún compañero que use Access pueda decirte si existe alguna irregularidad de sintáxis con respecto a ese manejador.

Saludos.


La franja horaria es GMT +2. Ahora son las 19:22:25.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi