|
|
|
Ir a otros temas... |
Esta herramienta de programación es la elegida por muchos
desarrolladores en lenguaje C++ por su facilidad para crear aplicaciones Windows,
casi de una manera tan fácil como en Visual Basic.
Algunos apuntes del día a día...
CONTENIDO
Formas que
automáticamente se crean (en las opciones del proyecto)
Ojo con las formas que automáticamente se crean, porque estando así
definidas en las propiedades del proyecto, pues sin querer pueden abrir tablas
etc.
Un proyecto en zeos no se debe conectar en tiempo de diseño, porque si
luego no esta disponible el servidor, bloquea al C Builder.
Trabajar con objetos dinámicos tiene
una ventaja cuando no se desea que el constructor sea invocado en el punto
h al declarar la variable, ejemplo:
TPermisos Permisos vs TPermisos *Permisos
Si el incremental
linker se queda pegado, revisar en opciones del proyecto, las rutas
de los directorios de las lib y los nclude. Quitar la de c:\ si aparece
Note: Placing datasets
(and data sources) directly on forms is recommended only for very
simple database applications. Even for moderately simple database applications
that only work with a few tables or queries on a few forms, using a DATA MODULE
eases development and maintenance.
Cadenas en C++ Builder:
1. Están las tradicionales del c: char Cadena[20];
2. La clase standar del C++: string;
3. Las nuevas cadenas largas del Pascal: AnsiString que se redefine como typedef
AnsiString String en sysdefs.h. Son las que se utilizan en todos los componentes
visuales de la vcl. AnsiString es una clase que soporta el operador + para
concatenar.
4. Para arreglos, TStringList, en vcl\classes.hpp
5. Las wchar para manejar UNICODE
Para colocar una cadena que en medio tenga un carácter como \x14 (20
decimal):
Lista->Add("5011 187018E41401003\x14""5012 187203");
Ocurrió en depurar.cpp de signal.
Filtros
Para colocar filtros que dependan de edits, combobox etc.:
String Filtro; // AnsiString (Clase emuladora string tipo pascal) Filtro="IGondola="+CBGondolas->Text; // Construimos la expresión TblKarMaest->Filtered= true; TblKarMaest->Filter=Filtro; TblKarMaest->RecordCount!=0 // Solo cuenta los del filtro!!Para colocar filtros a campos fecha:
Para colocar expresiones caracter entre comillas:
Filter=String("IGrado=")+IGrado+" and "+IGrupo='"+IGrupo+"'";
Para distinguir de campos vacios
IFactAgua<>NULL
Para recorrer los controles de una forma:
void TfrmNuevoArt::InitDatos() { int i; TControl *Control= dynamic_cast <TControl *>(this); // Recorrer los controles de la forma for(i=0; i < Control->ComponentCount; i++) { //ShowMessage(Control->Components[i]->Name); if (Control->Components[i]->Name.SubString(1,2)=="ed%quot;) dynamic_cast <TEdit *>(Control->Components[i])->Text=""; } }
DBGrids
Un DBGrid es una simple ventana cuyas celdas se dibujan, la celda
no es un TControl ni nada por el estilo, ver los ejemplos pruebas\dbgrid)
y se vé que para dibujar simplemente se usa el canvas del grid.
Seguramente que al momento de escribir se crea un edit para capturarlo unicamente.
Para colocarle la flechita abajo o el boton con ... en una columna toca con
el editor de columnas añadir la del caso y ya sea colocarle
en la propiedad picklist las que deseamos que aparezcan o en el butonstyle
colocar ellipse y definir un evento tal (estando ahi
pulsar la ayuda).
El evento que responde a cambio de fila o de record es el ondatachange de
un datasource, con Field igual a NULL.
Ver Contador para un ejemplo de como actualizaLabels a medida que se mueven
por el grid.
Ejemplo para que la tecla Ctrl-suprimir no funcione en un Grid
void __fastcall TFrmConsFacturacion::DBGrid1KeyDown(TObject *Sender,WORD&Key, TShiftState Shift){
if (Key==VK_DELETE && Shift.Contains(ssCtrl))
Key=NULL; // Como es por referencia, se cambia y el evento
// default la recibe como null
}
void __fastcall TFrmMateriasPeriodo::DBGrid1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { (Key==VK_DOWN && DBGrid1->DataSource->DataSet->RecNo== DBGrid1->DataSource->DataSet->RecordCount) Key= NULL; }
Otra manera para cuando el Recno no coincida con las lineas del grid, tomando previamente el RecNo del ultimo registro del grid:
void __fastcall TFrmGrabarNotas::DBGrid1KeyDown(TObject *Sender, WORD&Key, TShiftState Shift) {
// Para desactivar Ctrl-Suprimir en el grid.
if (Key==VK_DELETE && Shift.Contains(ssCtrl))
Key=NULL;
else if (Key==VK_DOWN && Query1->RecNo==LastRecord)
Key= NULL;
}
Para poder tener campos de diferentes tablas en un mismo dbgrid, debe colocarse
asociado al grid una TTable con características muy especiales:
Primero debe ser un campo añadido con fields editor (boton derecho en
ttable) usando new field y no add fields
keyFields= IUsuario (Contenido en table1 a buscar en la otra)
LookUpDataSet=Table2 (Tiene a IUsuario y a NUsuario, con llave IUsuario)
LookKeyFields=IUsuario (Campo a buscar en la tabla 2 que case con
keyfield)
LookUpResultField=NUsuario (Campo a devolver de la tabla 2)
LookUp=True
No se requiere colocar propiedades de lookup en la tabla, ni se requiere indexado, pero con éste funciona mas rápido
Estos campos solo son de lectura y deben definirse en el evento
OnCalcFields.
Si se quiere que aparezca el valor tomado de otra tabla debe ser un lookup,
que tambien queda de solo lectura.
Si se quiere mostrar un valor en el campo, pero que no sea calculado ni lookup,de
modo que se pueda cambiar, debe colocarse un evento en el DBGrid en OnColEnter,
que asigne al campo el valor de la otra tabla, (no se puede en el OnCalcFields
porque se produce StackOverFlow), tal como ocurre en kardex,
ej:
if (DbGridFact->SelectedField->FieldName=="ENTRADAS") { if (DataModule1->Art->Locate("CodiArti", DataModule1->MovTemp->FieldByName("CodiArti")->AsString, TLocateOptions())) { DataModule1->MovTemp->Edit(); DataModule1->MovTemp->FieldValues["Valor"] =DataModule1->Art->FieldValues["ValoComp"]; } }Para chequear la columna actual:
Para darle el foco a una columna dada:
DBGrid1->SelectedIndex=3;
DBGrid1->SetFocus();
Si se quieren los titulos etc.:
if(DBGrid1->Columns->Items[DBGrid1->SelectedIndex]->Title- >Caption=="PrVenta")
Para poner una columna read only:
DBGrid1->Columns->Items[0]->ReadOnly= true;
Para validar a la salida de una celda:
En el OnColExit Colocar el codigo validador, y si es falso,
en el colenter colocar el SelectedIndex en donde estaba. Ver el código
fuente del programa de contabilidad, al capturar el asiento.
En un DBGrid para que el texto que escriban este en mayusculas toca definirlo
en el On DrawColumnCell asi:
String Texto= DataModule1->MovTemp->FieldByName("CodiArti")->AsString;
if (DataCol==1) { // la columna de interes
DbFact->Canvas->TextOut(Rect.Left+2,Rect.Top+2,Texto.UpperCase());
}
else
DbFact->DefaultDrawColumnCell(Rect, DataCol, Column, State);
Pero para que esto realmente se tome el dato entrado en la tabla toca en
el evento OnColEnter del grid, colocar:
CodiArti=DataModule1->MovTemp->FieldByName("CodiArti")->AsString;
DataModule1->MovTemp->FieldValues["CodiArti"]=CodiArti.UpperCase();
La clave para que no se mueva el grid en las validaciones y en los for recorriendo
la tabla, es desconectar el grid del datasource en los procesos y justo antes
de que se vuelva a visualizar volverlo a conectar.
Una manera de hacerlo es con DataSet->DisableControls, por ejemplo si el
dataset es un Query1: (Ver agua Reporte para lecturas)
Query1->DisableControls();
FrmRptLeerLectura->QuickReport1->Preview();
Query1->EnableControls();
Técnica para hacer que la fila del DBGrid se ilumine en la medida que nos movemos, pero con edicion y todo: (Grabarnotasgrupo en colegios)
En el datasource, evento datachange:
void __fastcall TFrmGrabarNotasGrupo::Datasource1DataChange(TObject *Sender,
TField *Field)
{
RecNum=DSNotasTemp->DataSet->RecNo;
}
En el ondrawColumnCell del dbgrid:
void __fastcall TFrmGrabarNotasGrupo::DBGNotasDrawColumnCell( TObject *Sender, const TRect &Rect, int DataCol, TColumn *Column, TGridDrawState State) { //La propiedad DefaultDrawing del grid=false en tiempo de diseño. //Nosotros le dibujamos el texto try { if (DM->TblNotasTemp->RecNo==RecNum) DBGNotas->Canvas->Brush->Color=clInfoBk; DBGNotas->DefaultDrawColumnCell(Rect, DataCol, Column, State); } catch (MyException &e) { ShowMessage(e.Mensaje()); } }
Ejemplo de código para validar una columna en un dbgrid. Se hace en el evento updateData del datasource, y si no cumple con la especificación se tira una exception. (tomado de main.cpp en colegios)
void __fastcall TFrmMain::DSEstudiantesUpdateData(TObject *Sender) { try { String situacion; situacion=DM->TblEstudiantes->FieldByName("Situacion")->AsString; if (situacion.IsEmpty()) return; if (islower(situacion[1])) { DM->TblEstudiantes->Edit(); DM->TblEstudiantes->FieldValues["Situacion"]=situacion.UpperCase(); situacion=situacion.UpperCase(); } if (situacion!="" && situacion!="C" && situacion!="D" && situacion!="M") { DM->TblEstudiantes->Edit(); DM->TblEstudiantes->FieldValues["Situacion"]=""; throw MyException("La situación debe ser solamente C o D o M (Cancelado Desertor o Matriculado"); } } catch (Exception &exception) { Application->ShowException(&exception); } catch (MyException &e) { Application->MessageBox(e.Mensaje(),"Error",MB_OK|MB_ICONHAND); } catch (...) { ShowMessage( String("An exception of type ") + __ThrowExceptionName() + "\nwas thrown by line " + AnsiString(__ThrowLineNumber()) + "\nof file " + __ThrowFileName() ); } }
Variants:
Para convertir un variant que contenga un int a una cadena AnsiString simplemente
crear la instancia String(variante). Ej:
String(DataModule1->TblKarMaest->FieldByName("IGondola")->Value); Para busquedas:
DataModule1->TblGondolas->SetKey(); DataModule1->TblGondolas->Fields[0]->AsString = IGondola; if (!DataModule1->TblGondolas->GotoKey()) // Buscar en carga académica la materia y obtener el código del docente DataModule1->TblCargaAcad->SetKey(); DataModule1->TblCargaAcad->Fields[1]->AsString = "INF95"; DataModule1->TblCargaAcad->IndexName="Materia"; if (DataModule1->TblCargaAcad->GotoKey()) IDocente= DataModule1->TblCargaAcad->FieldByName("IDocente")->Value; else ShowMessage("NO Encontrado");
Con locate: bool LocateSuccess; TLocateOptions SearchOptions;SearchOptions << loPartialKey; // Opcional LocateSuccess = CustTable->Locate("Company", "Professional Divers, Ltd.", SearchOptions); // campo contenido }
/*
Para asignarle
eventos a TApplication
ver en src\cbuilder\pruebas o ConsFacturacion en agua
En el constructor;
DataModule1->TblAbaBancos->BeforeDelete=TFrmBancos::NoDelete;
En el desctructor
DataModule1->TblAbaBancos->BeforeDelete=NULL;
En la implementación del metodo a llamar, en este caso para que no borre.
void __fastcall TFrmBancos::NoDelete(TDataSet *) {
Abort();
}
*/
/*
Paso de parámetros
puntero como de salida: (Se cambia la dirección del puntero en la función
y sale cambiada.)
Ver ejemplo punt_in_out.cpp en carpeta universidad nacional Ilustra como a
semejanza de los enteros etc. que si se desean pasar
como parámetro de salida, se debe pasar su dirección, a los punteros
les ocurre lo mismo.
bool buscar(int cod, struct biblioteca **anodo /*direccion de la
variable puntero*/) {
bool Resultado= false;
temp=lst;
while(temp->sgte!=NULL) {
if (temp->codigo==cod) {
Resultado=true;
*anodo= temp; // El contenido de Anodo es cambiado
break;
}
temp=temp->sgte;
}
// Estamos en el ultimo nodo
if(temp->codigo==cod) {
Resultado= true;
*anodo=temp;
}
return Resultado;
}
*/
Formas con métodos similares
Como aprovechar un metodo de una forma en otra que tambien tiene un metodo
similar solo con algunas diferencias sin heredar?
Si se maneja friend se puede acceder al metodo de la amiga pero en el metodo
la forma se refiere a sus propios edits y demas y no a la que lo llama.
/
Texto de una listBox
Para sacar el texto de una ListBox (es un AnsiString):
ListBox1->Items->Strings[ListBox1->ItemIndex];
Para que en una combo box no
se pueda escribir:
usar estilo csDropDownList
Operador incremento con campos de tablas
No se pueden hacer operaciones como esta:
table1->FieldValues["campo1"]+=8;
Toca sacar primero el valor del campo a una variable y luego
table1->FieldValues["campo1"]=variable+8;
Linker failed to create map file error code 0
FAQ913C.txt Linker failed to create map file error code 0
Category :Linker
Platform :All
Product :C++Builder 1.x
Question:
I am getting a "fatal linker error: linker failed to create map file : error code 0." when running an example project.
What do I do to make this error go away?
Answer:
(1) Delete the *.il? in the project
(2) Delete the *.csm files in the lib directory
(3) Delete the deflink.il? files in the bin directory
Para volver a crearlos: bcb -deflink
Para detectar teclas
en Windows con C++ Builder
void __fastcall TFrmMain::FormKeyDown(TObject *Sender, WORD &Key,
TShiftState Shift)
{
// Las teclas estan definidas en: Virtual-Key Codes
// Aqui se cheque que pulsen F5 y Ctrl y Shift al mismo tiempo:
if (Key==VK_F5 && Shift.Contains(ssCtrl) && Shift.Contains(ssShift))
sndPlaySound("copyright.wav", SND_SYNC);
Para enviar una tecla
a un edit, de modo que el curso se mueva al final.
PostMessage(EdICuenta->Handle, WM_KEYDOWN,VK_END,0);
Para definir eventos dinamicamente, solo para una forma:
// Definir eventos dinamicamente que nazcan y mueran en esta forma:
DataModule1->DSFacturas- >OnDataChange=TFrmConsFacturacion::FacturasDataChange;
DataModule1->TblFacturas- >AfterPost=TFrmConsFacturacion::FacturasAfterPost;
DataModule1->TblFacturas- >BeforePost=TFrmConsFacturacion::FacturasBeforePost;
Luego en el destructor:
// Quitar los eventos definidos al empezar
DataModule1->DSFacturas->OnDataChange=NULL;
DataModule1->TblFacturas->AfterPost=NULL;
DataModule1->TblFacturas->BeforePost=NULL;
Ver Agua ConsFacturacion
Ejemplo para detectar
que esta corriendo otra instancia de un
programa en WIN32:
try
{
CreateMutex(NULL, false, Application->ExeName.c_str());
if (ERROR_ALREADY_EXISTS==GetLastError()) {
ShowMessage("Ya se está ejecutando el programa. Observe en la
barra
de tareas.");
PostQuitMessage(0);
}
else {
Application->Initialize();
Application->CreateForm(__classid(TDataModule1), &DataModule1);
Application->CreateForm(__classid(TFrmMain), &FrmMain);
Application->Run();
}
}
*/
/*
Para crear accesos
directos en el menú del botón inicio,
Aún se sigue utilizando DDE para conversar con el Program Manager.
La VCL tiene un componente que encapsula el inicio y final de la conversación:
TDdeClientConv
Basta colocarle en las propiedades, DdeService y DdeTopic, "program" en cada
uno, y luego DdeClientConv1- >ExecuteMacro("[CreateGroup(tigre2)][AddItem(e:\\ut\\fconvert.exe,ivan)
]",
false))
Ve en Pruebas, PrDde
*/
/*
Para detener una instruccion
for, while etc.
Para permitir que un botón Cancelar actúe y detenga un ciclo, debe
colocarse dentro del ciclo: Application->ProcessMessages();
Y El código asociado al botón debe ser colocar una variable a false,
variable que debe ser una de las condiciones del ciclo, por supuesto.
No funciona colocar PostQuitMessage() o Close() en el código asociado
al click del boton, porque regresa al ciclo y continúa dando vueltas.
Retornar un TDateTime en una
función
Si una funcion necesita retornar un TDateTime debe ser así, pues usando
FieldByName sale Error de generacion de codigo en CBuilder 1.0:
TDateTime TUsuario::GetFechaPago() {
return DataModule1->TblUsuarios->FieldValues["FPagoAnt"];
}
Posicionando el cursor en TMemo
Para posicionarse al inicio de un TMemo
Memo1->SelStart=0;
Memo1->SelLength = 0;
Memo1->Perform(EM_SCROLLCARET, 0, 0);
Memo1->SetFocus();
Para ubicarse en cierto punto y seleccionando texto: (didactica.exe)
Memo1->SetFocus();
Memo1->SelStart=1416;
Memo1->SelLength = 10;
Fechas y horas
Ejemplo de resta dados dos fechas y dos horas, mostrando la diferencia en
horas (tanto la resta de fecha como de horas debe multiplicarse por 24)
TDateTime FechaAct, HoraAct,FechaReg, HoraReg;
int DifHoras;
FechaAct=TDateTime().CurrentDate();
HoraAct=TDateTime().CurrentTime();
FechaReg=TDateTime().CurrentDate()-2;
HoraReg=TDateTime().CurrentTime()+6./24;
DifHoras=(int)(FechaAct-FechaReg)*24+(double)(HoraAct-HoraReg)*24;
ShowMessage(DifHoras);*/
Mas sencillo así:
TDateTime().CurrentTime().DecodeTime(&ha, &ma, &sa, &msa);
Config.GetHoraTest().DecodeTime(&ht, &mt, &st, &mst);
if (ha==ht && ma==mt && abs(sa-st)<=15) {
ç
// Funcion del API para mostrar el formato de fecha del sistema
::GetLocaleInfo(
LOCALE_SYSTEM_DEFAULT,
LOCALE_SSHORTDATE, // locale identifier
BufDate, // address of buffer for information
100 // size of buffer
);
Label2->Caption=String("Fecha de inventarios iniciales: (")+BufDate+")";
imprime ("mm/dd/yyyy")
Si se quiere trabajar con campos time como llave, debe grabarse con cero milisegundos
para que cuando se vaya a buscar haya coincidencia.
Recordar que es un doble y de lo contrario no coinciden lo que se grabo con
lo que se escribe en el sql. Ejemplo de signal, primero
decodificamos la hora:
HoraSignal.DecodeTime(&Hora, &Min, &Sec, &MilSec);
Y al grabarla la ponemos con cero milisegundos
FrmDMSignal->TblHistoria- >FieldValues["THora"]=TDateTime(Hora,Min,Sec,0);
Luego en la manipulacion de la tabla:
THora=FrmDMMonitor->TblCola->FieldByName("THora")->AsDateTime;
os<<"update historia set TNotas=\""<<Memo1->Text.c_str()<<"\",
IMonitor=\""<<Login.c_str()
<<"\" where dfecha=\""<<MesDiaAnio(LblFecha->Caption).c_str()
<<"\" and THora=\""<<THora.c_str()<<"\" and Event=\""
<<Evento.c_str()<<"\""<<ends;
P ara utilizar la hora en SQL toca utilizar el formato militar pues el a.m.
de la cadena no lo acepta. Para no cambiar el formato de hora del sistema
operativo se debe usar el método FormatString() así:
String HoraActual=TDateTime().CurrentTime().FormatString("hh:mm:ss")
Que nos retorna la hora en formato militar: Ej: 14:15:08
Ejemplo que muestra como seleccionar eventos que ocurrieron 24 horas antes:
select icuenta,event from historia where ("06/25/2003"-dfecha)<=1 and "06/25/2003">dfecha
and (thora-"08:15:30")*24<=24 and icuenta=6837
os<<"select ICuenta, Event from historia " <<"where (\""<<aFecha.FormatString("mm/dd/yyyy").c_str()<<"\"-dfecha)<=1
and
(\"" <<aFecha.FormatString("mm/dd/yyyy").c_str()<<"\">=dfecha)
and (thora-\""<< HoraActual.c_str()<<"\")*24<=24 and icuenta="<<aICuenta<<ends;
Uso de los manipulators
si no se usa "using namespace std"
#include<iomanip>
#include <sstream>
void __fastcall TForm1::Button1Click(TObject *Sender)
{
std::ostringstream os;
char b[255];
os<<"1012 "<<std::setfill(' ')<<std::setw(4)<<89<<"
00"<<std::ends;
ShowMessage(os.str().c_str());
ShowMessage(itoa( os.str().length(),b,10) );
}
El método length
de las std:string cuenta inclusive el null!
std::ostringstream os;
char b[255];
os<<"01234567890123456789"<<std::ends;
ShowMessage(os.str().c_str());
ShowMessage(itoa( os.str().length(),b,10) ); // imprime 21
Para extraer los parametros de la línea
de comandos (SAB)
//LPTSTR GetCommandLine(VOID) para hacerlo con el API de windows
// Con las variables globales del IDE (#include DOS)
if (_argc==2 && strchr(strupr(_argv[1]),'C'))
else if (_argc==2 && strchr(strupr(_argv[1]),'U'))
Para evitar warnings de que tal
constructor hidden tal otro, y que de ahi para adelante en la herencia ya
no se verá el base etc.
Crear otro constructor, que en C++ se puede, y a ese añadirle el parámetro
que necesitemos, que el compilador llamará al que se
necesite según tenga o no parámetro.
Ver src\cbuilder\repositorio\repair.cpp y h.
__fastcall TRepairDialog(TComponent* Owner);
__fastcall TRepairDialog(TComponent* Owner, String aRuta);
Para cambiar las
propiedades de los hint con el fin de mostrar mensajes con mas duracion etc:
En el constructor o similar:
Application->OnShowHint=DoShowHint;
Y luego:
void __fastcall TfrmCategoria::DoShowHint(System::AnsiString &HintStr,
bool &CanShow,
THintInfo &HintInfo)
{
DBGrid1->Hint="Hola";
HintInfo.HintColor = clAqua;// Changes only for this hint
HintInfo.HintMaxWidth = 120; // Hint text word wraps if width is greater than
120
//HintInfo.HintPos.x += SpeedButton3->Width; // Move hint to right edge
HintInfo.HideTimeout=10000; // Poner a 10 segundos la mostrada del hint
}
Detectar dos instancias ejecutandose de una misma aplicación
Para evitar que haya dos instancias de una misma aplicación corriendo:
CreateMutex(NULL, false, GetExeName.c_str()); //GetExeName está en clUtil
// solo es correcto el nombre sin la extensión y sin path
if (ERROR_ALREADY_EXISTS==GetLastError()) {
ShowMessage("Ya se está ejecutando el programa. Observe en la barra de
tareas.");
PostQuitMessage(0);
}
// Una forma general de manejar exceptions de todo tipo:
try
{
Application->Initialize();
Application->CreateForm(__classid(TDM), &DM);
Application->CreateForm(__classid(TFrmMain), &FrmMain);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
catch (MyException &e) {
Application->MessageBox(e.Mensaje(),"Error",MB_OK|MB_ICONHAND);
}
catch (...)
{
// Por aca entran las C++ Exception.
//WriteToFile("falla.txt", (String( itoa(i,Temp,10))+"\n").c_str());
ShowMessage( String("An exception of type ") + __ThrowExceptionName()
+ "\nwas thrown by line " + AnsiString(__ThrowLineNumber())
+ "\nof file " + __ThrowFileName() );
}
return 0;
}
Como colocar una fecha
en un control, con el formato del fecha del sistema:
char BufDate[20];
// Funcion del API para mostrar el formato de fecha del sistema, ej: mm/dd/yyyy
::GetLocaleInfo(LOCALE_SYSTEM_DEFAULT,LOCALE_SSHORTDATE, BufDate,);
TDateTime fTemp(GetAnioActual(), 12,31);
edFechaVenc->Text=fTemp.FormatString(BufDate);