tag:blogger.com,1999:blog-223747732024-03-14T08:01:12.789+01:00Soluciones con PowerBuilder (Español)¿Cual es el contenido de este Blog? Cualquier cosa relacionada con Powerbuilder. Principalmente soluciones, como resolví tal cosa o tal otra, referencias interesantes, etc. La idea fundamental es emplear el tiempo una sola vez para resolver algo, y no tener que hacerlo cada vez que me enfrente al mismo problema. Encontraras: Métodos aplicados, códigos fuentes, programas relacionados o web con contenido útil, etc. Espero que también a tí te sea útil.Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.comBlogger19125tag:blogger.com,1999:blog-22374773.post-46175088134294023662007-09-24T07:55:00.000+02:002007-09-24T08:34:58.780+02:00Poner una ventana en primer plano<div align="justify">En este caso, el empleo dado a la función API setWindowPos ha sido para que la ventana de "splash" de inicio de una aplicación sea siempre visible, y no se quede en segundo plano mientras esta abierta, pero podría usarse para la típica aplicación que deseamos que siempre se quede en primer plano, usando dicha función con su ventana principal.</div></br><div align="justify">En primer lugar debemos declararla como función externa del siguiente modo:</div></br><textarea class="miTextArea" rows="2" wrap="off" cols="45">
Function Boolean SetWindowPos(long hwnd, long hmode, integer ix, integer iy, integer cx, integer cy, ulong flags) library "user32.dll"</textarea></br></br><div align="justify">Después, sobre la ventana de "splash", en el propio evento open podemos usar el siguiente código:</div></br><textarea class="miTextArea" rows="10" wrap="off" cols="45">
// Declaración de constantes y variables.
CONSTANT ulong HWND_TOPMOST = -1
CONSTANT ulong SWP_NOSIZE = 1
CONSTANT ulong SWP_NOMOVE = 2
CONSTANT ulong SWP_SHOWWINDOW = 64
long ll_hWnd
// Recogemos el handle de la ventana y la ponemos en primer plano.
ll_hWnd = Handle(This)
SetWindowPos(ll_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE + SWP_NOMOVE + SWP_SHOWWINDOW)
</textarea></br></br><div align="justify">Si queréis información completa de la función SetWindowPos, la podéis encontrar en <a href="http://msdn2.microsoft.com/en-us/library/ms633545.aspx" target="_blank">el MSDN de Microsoft</a>. Respecto a los valores de las constantes los dejo aquí para mayor facilidad:</div></br><textarea class="miTextArea" rows="10" wrap="off" cols="45">
// Para establecer el orden Z de la ventana.
CONSTANT ulong HWND_TOP = 0
CONSTANT ulong HWND_BOTTOM = 1
CONSTANT ulong HWND_TOPMOST = -1
CONSTANT ulong HWND_NOTOPMOST = -2
// Parámetros del falg de SetWindowPos
CONSTANT ulong SWP_NOSIZE = 1
CONSTANT ulong SWP_NOMOVE = 2
CONSTANT ulong SWP_NOZORDER = 4
CONSTANT ulong SWP_NOREDRAW = 8
CONSTANT ulong SWP_NOACTIVATE = 16
CONSTANT ulong SWP_FRAMECHANGED = 32
CONSTANT ulong SWP_SHOWWINDOW = 64
CONSTANT ulong SWP_HIDEWINDOW = 128
CONSTANT ulong SWP_NOCOPYBITS = 256
CONSTANT ulong SWP_NOOWNERZORDER = 512
CONSTANT ulong SWP_NOSENDCHANGING = 1024
CONSTANT ulong SWP_DRAWFRAME = SWP_FRAMECHANGED
CONSTANT ulong SWP_NOREPOSITION = SWP_NOOWNERZORDER
CONSTANT ulong SWP_DEFERERASE = 8192
CONSTANT ulong SWP_ASYNCWINDOWPOS = 16384
</textarea></br></br><div align="justify">Como siempre, espero que os sea de utilidad. Hasta pronto.</div>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-11987490721951778802007-07-09T08:00:00.000+02:002007-07-09T16:21:09.338+02:00Como clonar dw/ds<div align="justify">Desde luego, si hubiera caído antes en la posibilidad de usar esta opción para clonar dw o crear ds usando dw o viceversa, me hubiera ahorrado muchos quebraderos de cabeza, fijaros sino en la entrada de "<a href="http://conpb.blogspot.com/2007/02/encabezados-dinamicos-con-un-par.html">Encabezados dinámicos. Con un par...</a>" o en la de "Copiar "<a href="http://conpb.blogspot.com/2007/02/copiar-retrieval-arguments-entre-dwds.html">Retrieval Arguments" entre dw/ds</a>", podrían haber cambiado mucho en el primer caso e incluso no ser necesario montar todo el tinglado de la copia de retrieval arguments, ya que esta solución, realiza un clonado completo, incluidos los argumentos...</div></br><div align="justify"></div><div align="justify">El único matiz que hay que tener en cuenta, es que hay que usar sobre el objeto clonado la función <em>setTransObject(Transaction)</em> si queremos que tenga capacidad de trabajar contra la BD directamente.</div></br><div align="justify"></div><div align="justify">En primer lugar tendremos que crear (si no usamos ya uno) un objeto de tipo (dw/ds) al que en nuestro ejemplo nada más crearlo lo guardaremos con el nombre "u_dw", es importante guardarlo para poder usar el nombre del objeto a continuación como parámetro de la función de clonado.</div></br><div align="justify">Creamos una función que en nuestro caso llamamos of_clonedw y que tendrá un parámetro, este le llamaremos adw_aux y el tipo corresponderá con el del objeto creado: "u_dw" y utilizaremos el siguiente código:</div></br>
<textarea class="miTextArea" rows="9" wrap="off" cols="45">
blob blb_aux
This.getFullState(blb_aux)
If isValid(adw_aux) Then
adw_aux.acceptText()
adw_aux.setFullState(blb_aux)
End If
</textarea></br></br>
<div align="justify">Lo que estamos haciendo en este código es almacenar en una variable de tipo blob la información del dw/ds, esto en realidad, si se inspecciona la variable, guarda la información del dw como si de un "psr" se tratase. Luego se comprueba si el argumento recibido es válido y en tal caso se realiza un acceptText para fijar el contenido del último campo editado (por si no perdió el foco) y por último se transfiere la información al dw deseado.</div></br><div align="justify">Como ejemplo de llamada, supongamos que estamos en una ventana con un dw_1 con datos y queremos copiar este sobre el dw_2:</div></br><textarea class="miTextArea" rows="2" wrap="off" cols="45">dw_1.of_cloneDW(dw_2)</textarea></br></br><div align="justify">Recordar que no se transfiere la información de la transacción asignada.</div></br><div align="justify">Espero que os permita quitaros muchos dolores de cabeza, y sino, al menos, que os facilite el trabajo. Un saludo para todos.</div>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-19747422617083175262007-06-15T07:21:00.000+02:002007-06-15T10:47:11.139+02:00Prefijos estándar de PowerBuilder<br>
En más de una ocasión utilizamos tipos, objetos, etc., que no son los habituales, y cuando pretendemos usar sus prefijos correspondientes andamos con dudas o perdidos, por eso, y con el objetivo de siempre, de tener centralizada la información que he necesitado en más de una ocasión y he tenido que buscar repetidas veces, he decidido poner aquí también la lista de prefijos y calificadores estándar de PowerBuilder:
<h4>Calificadores de alcance</h4><table cellpadding="3" border="1"><tbody><tr bgcolor="#204063"><td width="140" style="color:#809fbd;"><span style="font-size:130%;"><strong><span style="color:#809fbd;">Alcance</span></strong> </span></td><td width="140" style="color:#809fbd;"><span style="font-size:130%;"><span style="color:#809fbd;"><strong>Prefijo</strong> </span></span></td><td width="140" style="color:#809fbd;"><span style="font-size:130%;"><strong><span style="color:#809fbd;">Ejemplo</span></strong> </span></td></tr><tr><td width="140">Argument</td><td width="140"><p align="left">a</p></td><td width="140">al_NameId</td></tr><tr><td width="140">Global</td><td width="140"><p align="left">g</p></td><td width="140">gs_Name</td></tr><tr><td width="140">Instance</td><td width="140"><p align="left">i</p></td><td width="140">ii_Count</td></tr><tr><td width="140">Local</td><td width="140"><p align="left">l</p></td><td width="140">ls_Foo</td></tr><tr><td width="140">Shared</td><td width="140">s</td><td width="140">si_Number</td></tr></tbody></table>
<h4>Prefijos de tipos de datos estándar</h4><table BORDER="1" cellpadding="3"> <tr bgcolor="#204063"> <td width="140" width="140"><font size="4" color="#809fbd">Tipo de dato </font></td> <td width="140" width="140"><font size="4" color="#809fbd">Prefijo </font></td> <td width="140" width="140"><font size="4" color="#809fbd">Ejemplo </font></td> </tr> <tr> <td>Any</td> <td>a</td> <td>la_Raw</td> </tr> <tr> <td>Blob</td> <td>blb</td> <td>ablb_Image</td> </tr> <tr> <td>Boolean</td> <td>b</td> <td>lb_Exit</td> </tr> <tr> <td>Character</td> <td>c</td> <td>lc_Name</td> </tr> <tr> <td>Date</td> <td>d</td> <td>ld_BirthDate</td> </tr> <tr> <td>DateTime</td> <td>dt</td> <td>ldt_Wreck</td> </tr> <tr> <td>Decimal</td> <td>dec</td> <td>ldec_Salary</td> </tr> <tr> <td>Double</td> <td>db</td> <td>gdb_OverTime</td> </tr> <tr> <td>Integer</td> <td>i</td> <td>li_Count</td> </tr> <tr> <td>Long</td> <td>l</td> <td>ll_RowCount</td> </tr> <tr> <td>Real</td> <td>r</td> <td>sr_Illusion</td> </tr> <tr> <td>String</td> <td>s</td> <td>ls_Bean</td> </tr> <tr> <td>Time</td> <td>tm</td> <td>itm_MrWolf</td> </tr> <tr> <td>Unsigned Integer</td> <td>ui</td> <td>lui_Handle</td> </tr> <tr> <td>Unsigned Long</td> <td>ul</td> <td>gul_Ken</td> </tr> </table>
<h4>Prefijos de tipos de datos de objetos estándar</h4><table BORDER="1" cellpadding="3" width="435"> <tr bgcolor="#204063"> <td width="100"><font size="4" color="#809fbd">Objeto </font></td> <td width="65"><font size="4" color="#809fbd">Prefijo </font></td> <td width="100"><font size="4" color="#809fbd">Ejemplo </font></td> </tr> <tr> <td VALIGN="TOP">Application</td> <td VALIGN="TOP">app</td> <td VALIGN="TOP">gapp_PBDelta</td> </tr> <tr> <td VALIGN="TOP">ArrayBounds</td> <td VALIGN="TOP">ab</td> <td VALIGN="TOP">lab_Bound</td> </tr> <tr> <td VALIGN="TOP">CheckBox</td> <td VALIGN="TOP">cbx</td> <td VALIGN="TOP">icbx_Male</td> </tr> <tr> <td VALIGN="TOP">ClassDefinition</td> <td VALIGN="TOP">cldef</td> <td VALIGN="TOP">lcldef_Object</td> </tr> <tr> <td VALIGN="TOP">ClassDefinitionObject</td> <td VALIGN="TOP">cldefo</td> <td VALIGN="TOP">lcdefo_ThatObject</td> </tr> <tr> <td VALIGN="TOP">CommandButton</td> <td VALIGN="TOP">cb</td> <td VALIGN="TOP">lcb_Cancel</td> </tr> <tr> <td VALIGN="TOP">Connection</td> <td VALIGN="TOP">cn</td> <td VALIGN="TOP">lcn_Known</td> </tr> <tr> <td VALIGN="TOP">ConnectionInfo</td> <td VALIGN="TOP">cni</td> <td VALIGN="TOP">lcni_ServerInfo</td> </tr> <tr> <td VALIGN="TOP">ConnectObject</td> <td VALIGN="TOP">cno</td> <td VALIGN="TOP">scno_ConObj</td> </tr> <tr> <td VALIGN="TOP">ContextInformation</td> <td VALIGN="TOP">cxinfo</td> <td VALIGN="TOP">lcxinfo_ObjContext</td> </tr> <tr> <td VALIGN="TOP">ContextKeyword</td> <td VALIGN="TOP">cxk</td> <td VALIGN="TOP">lcxk_TheKey</td> </tr> <tr> <td VALIGN="TOP">CPlusPlus</td> <td VALIGN="TOP">cpp</td> <td VALIGN="TOP">lcpp_CModule</td> </tr> <tr> <td VALIGN="TOP">Datastore</td> <td VALIGN="TOP">ds</td> <td VALIGN="TOP">lds_StockData</td> </tr> <tr> <td VALIGN="TOP">Datawindow</td> <td VALIGN="TOP">dw</td> <td VALIGN="TOP">ldw_Employee</td> </tr> <tr> <td VALIGN="TOP">DatawindowChild</td> <td VALIGN="TOP">dwc</td> <td VALIGN="TOP">ldwc_States</td> </tr> <tr> <td VALIGN="TOP">DragObject</td> <td VALIGN="TOP">drg</td> <td VALIGN="TOP">ldrg_ByTheHair</td> </tr> <tr> <td VALIGN="TOP">DrawObject</td> <td VALIGN="TOP">drw</td> <td VALIGN="TOP">ldrw_PaintBrush</td> </tr> <tr> <td VALIGN="TOP">DropDownListBox</td> <td VALIGN="TOP">ddlb</td> <td VALIGN="TOP">lddlb_States</td> </tr> <tr> <td VALIGN="TOP">DropDownPictureListBox</td> <td VALIGN="TOP">ddplb</td> <td VALIGN="TOP">lddplb_StatesWithFlags</td> </tr> <tr> <td VALIGN="TOP">dwObject</td> <td VALIGN="TOP">dwo</td> <td VALIGN="TOP">ldwo_Column</td> </tr> <tr> <td VALIGN="TOP">DynamicDescriptionArea</td> <td VALIGN="TOP">dda</td> <td VALIGN="TOP">ldda_Execute</td> </tr> <tr> <td VALIGN="TOP">DynamicStagingArea</td> <td VALIGN="TOP">dsa</td> <td VALIGN="TOP">ldsa_MyQuery</td> </tr> <tr> <td VALIGN="TOP">EditMask</td> <td VALIGN="TOP">em</td> <td VALIGN="TOP">lem_PhoneNo</td> </tr> <tr> <td VALIGN="TOP">EnumerationDefinition</td> <td VALIGN="TOP">enum</td> <td VALIGN="TOP">lenum_Cycle</td> </tr> <tr> <td VALIGN="TOP">EnumerationItemDefinition</td> <td VALIGN="TOP">enumi</td> <td VALIGN="TOP">ienumi_Item</td> </tr> <tr> <td VALIGN="TOP">Environment</td> <td VALIGN="TOP">env</td> <td VALIGN="TOP">lenv_System</td> </tr> <tr> <td VALIGN="TOP">Error</td> <td VALIGN="TOP">err</td> <td VALIGN="TOP">gerr_Snarl</td> </tr> <tr> <td VALIGN="TOP">ExtObject</td> <td VALIGN="TOP">exto</td> <td VALIGN="TOP">gexto_Outside</td> </tr> <tr> <td VALIGN="TOP">Function_Object</td> <td VALIGN="TOP">fo</td> <td VALIGN="TOP">lfo_Function</td> </tr> <tr> <td VALIGN="TOP">Graph</td> <td VALIGN="TOP">gr</td> <td VALIGN="TOP">lgr_Sales</td> </tr> <tr> <td VALIGN="TOP">GraphObject</td> <td VALIGN="TOP">gro</td> <td VALIGN="TOP">lgro_Line</td> </tr> <tr> <td VALIGN="TOP">GrAxis</td> <td VALIGN="TOP">grx</td> <td VALIGN="TOP">lgrx_Profit</td> </tr> <tr> <td VALIGN="TOP">GrDispAttr</td> <td VALIGN="TOP">grda</td> <td VALIGN="TOP">igrda_Value</td> </tr> <tr> <td VALIGN="TOP">GroupBox</td> <td VALIGN="TOP">gb</td> <td VALIGN="TOP">igb_Employee</td> </tr> <tr> <td VALIGN="TOP">HScrollBar</td> <td VALIGN="TOP">hsb</td> <td VALIGN="TOP">ihsb_Percent</td> </tr> <tr> <td VALIGN="TOP">iNet</td> <td VALIGN="TOP">inet</td> <td VALIGN="TOP">linet_Web</td> </tr> <tr> <td VALIGN="TOP">InternetResult</td> <td VALIGN="TOP">ir</td> <td VALIGN="TOP">lir_Page</td> </tr> <tr> <td VALIGN="TOP">Line</td> <td VALIGN="TOP">li</td> <td VALIGN="TOP">lli_Arrow</td> </tr> <tr> <td VALIGN="TOP">ListBox</td> <td VALIGN="TOP">lb</td> <td VALIGN="TOP">llb_States</td> </tr> <tr> <td VALIGN="TOP">ListView</td> <td VALIGN="TOP">lv</td> <td VALIGN="TOP">llv_Filenames</td> </tr> <tr> <td VALIGN="TOP">ListViewItem</td> <td VALIGN="TOP">lvi</td> <td VALIGN="TOP">llvi_Item</td> </tr> <tr> <td VALIGN="TOP">MailFileDescription</td> <td VALIGN="TOP">mfd</td> <td VALIGN="TOP">lmfd_Mail</td> </tr> <tr> <td VALIGN="TOP">MailMessage</td> <td VALIGN="TOP">mm</td> <td VALIGN="TOP">lmm_Mail</td> </tr> <tr> <td VALIGN="TOP">MailRecipient</td> <td VALIGN="TOP">mr</td> <td VALIGN="TOP">lmr_Mail</td> </tr> <tr> <td VALIGN="TOP">MailSession</td> <td VALIGN="TOP">ms</td> <td VALIGN="TOP">lms_Session</td> </tr> <tr> <td VALIGN="TOP">MDIClient</td> <td VALIGN="TOP">mdi</td> <td VALIGN="TOP">lmdi_Frame</td> </tr> <tr> <td VALIGN="TOP">Menu</td> <td VALIGN="TOP">m</td> <td VALIGN="TOP">lm_Menu</td> </tr> <tr> <td VALIGN="TOP">MenuCascade</td> <td VALIGN="TOP">mc</td> <td VALIGN="TOP">lmc_WaterFall</td> </tr> <tr> <td VALIGN="TOP">Message</td> <td VALIGN="TOP">msg</td> <td VALIGN="TOP">lmsg_Whisper</td> </tr> <tr> <td VALIGN="TOP">MultiLineEdit</td> <td VALIGN="TOP">mle</td> <td VALIGN="TOP">lmle_Text</td> </tr> <tr> <td VALIGN="TOP">NonVisualObject</td> <td VALIGN="TOP">nvo</td> <td VALIGN="TOP">lnvo_Invisible</td> </tr> <tr> <td VALIGN="TOP">OLEControl</td> <td VALIGN="TOP">oc</td> <td VALIGN="TOP">loc_Bulls</td> </tr> <tr> <td VALIGN="TOP">OLECustomControl</td> <td VALIGN="TOP">occ</td> <td VALIGN="TOP">locc_Dial</td> </tr> <tr> <td VALIGN="TOP">OLEObject</td> <td VALIGN="TOP">oo</td> <td VALIGN="TOP">loo_Ghost</td> </tr> <tr> <td VALIGN="TOP">OLEStorage</td> <td VALIGN="TOP">ostg</td> <td VALIGN="TOP">gostg_WordFile</td> </tr> <tr> <td VALIGN="TOP">OLEStream</td> <td VALIGN="TOP">ostm</td> <td VALIGN="TOP">lostm_River</td> </tr> <tr> <td VALIGN="TOP">OMControl</td> <td VALIGN="TOP">omc</td> <td VALIGN="TOP">iomc_Temp</td> </tr> <tr> <td VALIGN="TOP">OMCustomControl</td> <td VALIGN="TOP">omcc</td> <td VALIGN="TOP">lomcc_Dial</td> </tr> <tr> <td VALIGN="TOP">OMEmbeddedControl</td> <td VALIGN="TOP">omec</td> <td VALIGN="TOP">lomec_Micro</td> </tr> <tr> <td VALIGN="TOP">OMObject</td> <td VALIGN="TOP">omo</td> <td VALIGN="TOP">iomo_Bob</td> </tr> <tr> <td VALIGN="TOP">OMStorage</td> <td VALIGN="TOP">omstg</td> <td VALIGN="TOP">somstg_Drive</td> </tr> <tr> <td VALIGN="TOP">OMStream</td> <td VALIGN="TOP">omstm</td> <td VALIGN="TOP">lomstm_Mersey</td> </tr> <tr> <td VALIGN="TOP">Oval</td> <td VALIGN="TOP">ov</td> <td VALIGN="TOP">lov_Circle</td> </tr> <tr> <td VALIGN="TOP">PBtoCPPObject</td> <td VALIGN="TOP">pb2cpp</td> <td VALIGN="TOP">lpb2cpp_Quad</td> </tr> <tr> <td VALIGN="TOP">Picture</td> <td VALIGN="TOP">p</td> <td VALIGN="TOP">lp_Smile</td> </tr> <tr> <td VALIGN="TOP">PictureButton</td> <td VALIGN="TOP">pb</td> <td VALIGN="TOP">lpb_Click</td> </tr> <tr> <td VALIGN="TOP">PictureListBox</td> <td VALIGN="TOP">plb</td> <td VALIGN="TOP">lplb_Drives</td> </tr> <tr> <td VALIGN="TOP">Pipeline</td> <td VALIGN="TOP">pl</td> <td VALIGN="TOP">lpl_OilandGas</td> </tr> <tr> <td VALIGN="TOP">Powerobject</td> <td VALIGN="TOP">po</td> <td VALIGN="TOP">lpo_Source</td> </tr> <tr> <td VALIGN="TOP">ProfileCall</td> <td VALIGN="TOP">prc</td> <td VALIGN="TOP">lprc_Face</td> </tr> <tr> <td VALIGN="TOP">ProfileClass</td> <td VALIGN="TOP">prcl</td> <td VALIGN="TOP">lprcl_Room</td> </tr> <tr> <td VALIGN="TOP">ProfileLine</td> <td VALIGN="TOP">prl</td> <td VALIGN="TOP">iprl_Bob</td> </tr> <tr> <td VALIGN="TOP">ProfileRoutine</td> <td VALIGN="TOP">prr</td> <td VALIGN="TOP">lprr_Cat</td> </tr> <tr> <td VALIGN="TOP">Profiling</td> <td VALIGN="TOP">pr</td> <td VALIGN="TOP">lpr_Outline</td> </tr> <tr> <td VALIGN="TOP">RadioButton</td> <td VALIGN="TOP">rb</td> <td VALIGN="TOP">lrb_Male</td> </tr> <tr> <td VALIGN="TOP">Rectangle</td> <td VALIGN="TOP">rec</td> <td VALIGN="TOP">irec_Tum</td> </tr> <tr> <td VALIGN="TOP">RemoteObject</td> <td VALIGN="TOP">ro</td> <td VALIGN="TOP">iro_Employee</td> </tr> <tr> <td VALIGN="TOP">RichTextEdit</td> <td VALIGN="TOP">rte</td> <td VALIGN="TOP">lrte_Script</td> </tr> <tr> <td VALIGN="TOP">RoundRectange</td> <td VALIGN="TOP">rr</td> <td VALIGN="TOP">lrr_Lion</td> </tr> <tr> <td VALIGN="TOP">ScriptDefinition</td> <td VALIGN="TOP">sdef</td> <td VALIGN="TOP">lsdef_Mycode</td> </tr> <tr> <td VALIGN="TOP">Service</td> <td VALIGN="TOP">srv</td> <td VALIGN="TOP">lsrv_Charge</td> </tr> <tr> <td VALIGN="TOP">SimpleTypeDefinition</td> <td VALIGN="TOP">std</td> <td VALIGN="TOP">istd_MyType</td> </tr> <tr> <td VALIGN="TOP">SingleLineEdit</td> <td VALIGN="TOP">sle</td> <td VALIGN="TOP">lsle_Name</td> </tr> <tr> <td VALIGN="TOP">StaticText</td> <td VALIGN="TOP">st</td> <td VALIGN="TOP">lst_Prompt</td> </tr> <tr> <td VALIGN="TOP">Structure</td> <td VALIGN="TOP">str</td> <td VALIGN="TOP">lstr_Data</td> </tr> <tr> <td VALIGN="TOP">SystemFunctions</td> <td VALIGN="TOP">sf</td> <td VALIGN="TOP">lsf_Bay</td> </tr> <tr> <td VALIGN="TOP">Tab</td> <td VALIGN="TOP">tab</td> <td VALIGN="TOP">ltab_Strip</td> </tr> <tr> <td VALIGN="TOP">Timing</td> <td VALIGN="TOP">tmg</td> <td VALIGN="TOP">ltmg_Clock</td> </tr> <tr> <td VALIGN="TOP">TraceActivityNode</td> <td VALIGN="TOP">tran</td> <td VALIGN="TOP">ltran_Node</td> </tr> <tr> <td VALIGN="TOP">TraceBeginEnd</td> <td VALIGN="TOP">trbe</td> <td VALIGN="TOP">ltrbe_Start</td> </tr> <tr> <td VALIGN="TOP">TraceError</td> <td VALIGN="TOP">tre</td> <td VALIGN="TOP">ltre_Error</td> </tr> <tr> <td VALIGN="TOP">TraceFile</td> <td VALIGN="TOP">trf</td> <td VALIGN="TOP">itrf_TraceFile</td> </tr> <tr> <td VALIGN="TOP">TraceGarbageCollect</td> <td VALIGN="TOP">trgc</td> <td VALIGN="TOP">itrgc_Call</td> </tr> <tr> <td VALIGN="TOP">TraceLine</td> <td VALIGN="TOP">trln</td> <td VALIGN="TOP">strln_Rope</td> </tr> <tr> <td VALIGN="TOP">TraceObject</td> <td VALIGN="TOP">tro</td> <td VALIGN="TOP">ltro_Bob</td> </tr> <tr> <td VALIGN="TOP">TraceRoutine</td> <td VALIGN="TOP">trr</td> <td VALIGN="TOP">ltrr_Tiger</td> </tr> <tr> <td VALIGN="TOP">TraceSQL</td> <td VALIGN="TOP">trsql</td> <td VALIGN="TOP">ltrsql_MySQL</td> </tr> <tr> <td VALIGN="TOP">TraceTree</td> <td VALIGN="TOP">trt</td> <td VALIGN="TOP">ltrt_Birch</td> </tr> <tr> <td VALIGN="TOP">TraceTreeError</td> <td VALIGN="TOP">trte</td> <td VALIGN="TOP">ltrte_Err</td> </tr> <tr> <td VALIGN="TOP">TraceTreeGarbageCollect</td> <td VALIGN="TOP">trtgc</td> <td VALIGN="TOP">ltrtgc_Trash</td> </tr> <tr> <td VALIGN="TOP">TraceTreeLine</td> <td VALIGN="TOP">trtl</td> <td VALIGN="TOP">ltrtl_String</td> </tr> <tr> <td VALIGN="TOP">TraceTreeNode</td> <td VALIGN="TOP">trtn</td> <td VALIGN="TOP">ltrtn_Kilt</td> </tr> <tr> <td VALIGN="TOP">TraceTreeObject</td> <td VALIGN="TOP">trto</td> <td VALIGN="TOP">ltrto_Trace</td> </tr> <tr> <td VALIGN="TOP">TraceTreeRoutine</td> <td VALIGN="TOP">trtr</td> <td VALIGN="TOP">ltrtr_BobGrimmer</td> </tr> <tr> <td VALIGN="TOP">TraceTreeUser</td> <td VALIGN="TOP">trtu</td> <td VALIGN="TOP">ltrtu_User</td> </tr> <tr> <td VALIGN="TOP">TraceUser</td> <td VALIGN="TOP">tru</td> <td VALIGN="TOP">ltru_Blue</td> </tr> <tr> <td VALIGN="TOP">Transaction</td> <td VALIGN="TOP">tr</td> <td VALIGN="TOP">ltr_Trans</td> </tr> <tr> <td VALIGN="TOP">Transport</td> <td VALIGN="TOP">tp</td> <td VALIGN="TOP">ltp_Server</td> </tr> <tr> <td VALIGN="TOP">Treeview</td> <td VALIGN="TOP">tv</td> <td VALIGN="TOP">ltv_Directory</td> </tr> <tr> <td VALIGN="TOP">TreeviewItem</td> <td VALIGN="TOP">tvi</td> <td VALIGN="TOP">ltvi_Node</td> </tr> <tr> <td VALIGN="TOP">TypeDefinition</td> <td VALIGN="TOP">typdef</td> <td VALIGN="TOP">ltypdef_Details</td> </tr> <tr> <td VALIGN="TOP">UserObject</td> <td VALIGN="TOP">uo</td> <td VALIGN="TOP">luo_Control</td> </tr> <tr> <td VALIGN="TOP">VariableCardinalityDefinition</td> <td VALIGN="TOP">vcd</td> <td VALIGN="TOP">lvcd_Type</td> </tr> <tr> <td VALIGN="TOP">VariableDefinition</td> <td VALIGN="TOP">vd</td> <td VALIGN="TOP">lvd_Sore</td> </tr> <tr> <td VALIGN="TOP">VerticalScrollBar</td> <td VALIGN="TOP">vsb</td> <td VALIGN="TOP">lvsb_Amount</td> </tr> <tr> <td VALIGN="TOP">Window</td> <td VALIGN="TOP">w</td> <td VALIGN="TOP">w_PleaseWait</td> </tr> <tr> <td VALIGN="TOP">WindowObject</td> <td VALIGN="TOP">wo</td> <td VALIGN="TOP">lwo_WomanElement</td> </tr> </table>
<h4>Convención de nombres de clases</h4><table BORDER="1" width="435"> <tr bgcolor="#204063"> <td width="100"><font size="4" color="#809fbd">Clase </font> </td> <td width="65"><font size="4" color="#809fbd">Prefijo </font> </td> <td width="120"><font size="4" color="#809fbd">Ejemplo</font> </td> </tr> <tr> <td>menu</td> <td>m_</td> <td>m_principal</td> </tr> <tr> <td>Standard class user object</td> <td>n_</td> <td>n_ds</td> </tr> <tr> <td>Custom class user object</td> <td>n_cst_</td> <td>n_cst_customer</td> </tr> <tr> <td>Global structure</td> <td>s_</td> <td>s_point</td> </tr><tr> <td>Visual user object</td> <td>u_</td> <td>u_pb</td> </tr><tr> <td>Window</td> <td>w_</td> <td>w_mdi</td> </tr> </table>
<h4>Otras consideraciones</h4>Las funciones globales usan el prefijo gf_ y las funciones de objetos utilizan el prefijo of_<br>Las constantes no suelen tener prefijo y se escriben en mayúsculas.Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-4238902785341602392007-02-12T16:58:00.000+01:002007-02-12T17:37:10.300+01:00Encabezados dinamicos. Con un par...<div align="justify">Como poner un encabezado dinamico a un dw... en mi caso nunca imprimo directamente el dw, sino que paso la info. a un ds, le planto el encabezado dinámico y luego lo imprimo.<br /><br />En este post solo aparece el código para poner el encabezado que deseo, si quereis ampliarlo para poder imprimir datawindows según lo he descrito podéis mirar el post anterior.<br /><br />Aunque ya lo he comentado en otros post, como dato curioso, si os fijáis, sería el primer argumento de la función, que correspondería al dw o ds al que le pondremos el encabezado, pues resulta que el tipo en un powerObject, precisamente para poder aplicar el encabezado tanto a dw como a ds.<br /><br />El resto de argumentos podrían eliminarse, todo depende del encabezado que deseemos, en mi caso para el argumento as_application le paso una cosntante que corresponde con el nombre de la aplicación para saber desde donde se ha impreso; para el argumento as_title, le paso el title de la ventana donde esta el dw que corresponde siempre con lo que muestra; y por último un argumento con un string para poner información en el pie de la impresión (privado, confidencial, borrador, etc.).<br /><br />En siguiente ilustración representa una imagen de como quedaría... emborrone el logo por temas de confidencialidad, por cierto, si vuestro logo difiere en dimensiones, tal vez fuese necesario modificar alguna medida.<br /><br />
<a href="http://4.bp.blogspot.com/_gYDF7YyKgLQ/RdCWtKh9IdI/AAAAAAAAALs/XOvR08X19MY/s1600-h/report.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://4.bp.blogspot.com/_gYDF7YyKgLQ/RdCWtKh9IdI/AAAAAAAAALs/XOvR08X19MY/s400/report.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5030686486437896658" /></a><br />Y para finalizar aquí desempolvado y listo para usar el código responsable de añadido dinámico:</div><br />
<textarea rows="20" wrap="off" cols="45" class="miTextArea">
string ls_objects, ls_object, ls_where, ls_type, ls_create
string ls_aux, ls_modify
long ll_len, ll_from, ll_to, ll_x, ll_widthObject, ll_width
long ll_addWidth, ll_posY, ll_posY1, ll_posY2, ll_height
integer li_r, li_orientation, li_width
datawindow ldw_aux
datastore lds_aux
string ls_typeObject
CONSTANT integer DISPLACEMENT = 300
CONSTANT integer WIDTHA4V = 3140
CONSTANT integer WIDTHA4H = 4640
ls_objects = apo_aux.DYNAMIC describe('datawindow.objects')
/******************************************************************/
/* OBTENEMOS LA ORIENTACIÓN ADECUADA Y DESPLAZAMOS LOS OBJETOS */
/******************************************************************/
// Por defecto será vertical.
li_orientation = 2
// Recorremos los todos objetos.
ll_len = len(ls_objects)
If ll_len > 0 Then
ll_from = 1
Do
ll_to = pos(ls_objects, "~t", ll_from)
If ll_to = 0 Then
ls_object = mid(ls_objects, ll_from)
Else
ls_object = mid(ls_objects, ll_from, ll_to - ll_from)
End If
If len(ls_object) > 0 Then
ll_x = long(apo_aux.DYNAMIC describe(ls_object + '.x'))
ll_widthObject = long(apo_aux.DYNAMIC describe(ls_object + '.width'))
// Obtenemos hasta donde llegan horizontalmente.
If ll_x + ll_widthObject > ll_width Then
ll_width = ll_x + ll_widthObject
End If
// Lo desplazamos verticalmente si esta en el encabezado.
ls_where = apo_aux.DYNAMIC describe(ls_object + '.band')
If ls_where = 'header' Then
ls_type = apo_aux.DYNAMIC describe(ls_object + '.type')
If ls_type = 'line' Then
ll_posY1 = long(apo_aux.DYNAMIC describe(ls_object + '.y1'))
ll_posY2 = long(apo_aux.DYNAMIC describe(ls_object + '.y2'))
ls_aux = apo_aux.DYNAMIC modify(ls_object + ".y1 = ~'" + string(ll_posY1 + DISPLACEMENT) + "~'")
ls_aux = apo_aux.DYNAMIC modify(ls_object + ".y2 = ~'" + string(ll_posY2 + DISPLACEMENT) + "~'")
Else
ll_posY = long(apo_aux.DYNAMIC describe(ls_object + '.y'))
ls_aux = apo_aux.DYNAMIC modify(ls_object + ".y = ~'" + string(ll_posY + DISPLACEMENT) + "~'")
End If
End If
End If
ll_from = ll_to + 1
Loop While (ll_to > 0)
// Determinamos la orientación.
If ll_width > WIDTHA4V Then
// Horizontal
li_orientation = 1
ll_addWidth = WIDTHA4H - WIDTHA4V
End If
/******************************************************************/
/* ASIGNAMOS LAS OPCIONES DE IMPRESIÓN */
/******************************************************************/
ls_aux = as_application + "/" + as_title
If len(ls_aux) > 31 Then
ls_aux = left(ls_aux, 28) + "..."
End If
ls_modify = "datawindow.print.margin.top=212 datawindow.print.margin.left=242 " &
+ "datawindow.print.margin.right=242 datawindow.print.margin.bottom=212" &
+ "datawindow.print.orientation=" + string(li_orientation) + " " &
+ "datawindow.print.documentname=~"" + ls_aux + "~""
ls_aux = apo_aux.DYNAMIC modify(ls_modify)
/******************************************************************/
/* MODIFICAMOS EL ENCABEZADO DINÁMICAMENTE */
/******************************************************************/
// Modificamos la altura del encabezado.
ll_height = long(apo_aux.DYNAMIC describe("datawindow.header.height"))
ll_height += DISPLACEMENT
ls_aux = apo_aux.DYNAMIC modify("datawindow.header.height=" + string(ll_height))
// Creamos el rectangulo superior.
ls_create = "create rectangle(band=header x=~"0~" y=~"0~" height=~"224~" " &
+ "width=~"" + string(WIDTHA4V + ll_addWidth) + "~" " &
+ "brush.hatch=~"7~" brush.color=~"553648127~" pen.style=~"0~" pen.width=~"5~" " &
+ "pen.color=~"0~" background.mode=~"2~" background.color=~"16777215~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
// Creamos el rectangulo inferior.
ls_create = "create rectangle(band=header x=~"0~" y=~"220~" height=~"80~" " &
+ "width=~"" + string(WIDTHA4V + ll_addWidth) + "~" " &
+ "brush.hatch=~"6~" brush.color=~"14869218~" pen.style=~"0~" pen.width=~"5~" " &
+ "pen.color=~"0~" background.mode=~"1~" background.color=~"553648127~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
// Creamos el bitmap.
ls_create = "create bitmap(band=Header x=~"25~" y=~"25~" height=~"180~" width=~"750~" " &
+ "filename=~"G:\Data\desdes01\Bmpaplic\logo.bmp~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
// Creamos la etiqueta con la entidad.
ls_create = "create text(band=header color=~"0~" alignment=~"0~" border=~"0~" x=~"27~" y=~"228~" " &
+ "height=~"64~" width=~"530~" text=~"Nombre organización.~" font.face=~"Arial~" " &
+ "font.height=~"55~" font.weight=~"400~" font.family=~"2~" font.pitch=~"2~" " &
+ "font.charset=~"0~" background.mode=~"1~" background.color=~"553648127~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
// Creamos la etiqueta con el usuario que lanzó la impresión.
ls_create = "create text(band=header color=~"0~" alignment=~"1~" border=~"0~" " &
+ "x=~"" + string((WIDTHA4V + ll_addWidth) - 526) + "~" y=~"228~" " &
+ "height=~"64~" width=~"503~" text=~"" + upper(gs_usuario) + "~" font.face=~"Arial~" " &
+ "font.height=~"55~" font.weight=~"400~" font.family=~"2~" font.pitch=~"2~" " &
+ "font.charset=~"0~" background.mode=~"1~" background.color=~"553648127~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
// Creamos etiqueta con el título del informe.
If li_orientation = 1 Then
// Horizontal
li_width = 66
Else
// Verical
li_width = 36
End If
If len(as_title) > li_width Then
as_title = left(as_title, li_width - 3) + '...'
End If
ls_create = "create text(band=header color=~"0~" alignment=~"2~" border=~"0~" x=~"791~" y=~"60~" " &
+ "height=~"104~" width=~"" + string((WIDTHA4V + ll_addWidth) - (791 * 2)) + "~" text=~"" + as_title + "~" " &
+ "font.face=~"Arial~" font.height=~"85~" font.weight=~"700~" " &
+ "font.family=~"2~" font.pitch=~"2~" font.charset=~"0~" background.mode=~"1~" " &
+ "background.color=~"553648127~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
// Creamos la etiqueta con el nombre de la aplicación.
ls_create = "create text(band=header color=~"0~" alignment=~"2~" border=~"0~" x=~"791~" y=~"228~" " &
+ "height=~"60~" width=~"" + string((WIDTHA4V + ll_addWidth) - (791 * 2)) + "~" " &
+ "text=~"" + as_application + "~" font.face=~"Arial~" " &
+ "font.height=~"55~" font.weight=~"400~" font.family=~"2~" font.pitch=~"2~" " &
+ "font.charset=~"0~" background.mode=~"1~" background.color=~"553648127~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
// Montamos la etiqueta con la fecha y hora.
ls_create = "create text(band=header color=~"0~" alignment=~"1~" border=~"0~" " &
+ "x=~"" + string((WIDTHA4V + ll_addWidth) - 526) + "~" y=~"24~" " &
+ "height=~"64~" width=~"503~" text=~"" + string(today(),'d/mm/yyyy hh:mm') + "~" " &
+ "font.face=~"Arial~" font.height=~"55~" font.weight=~"400~" font.family=~"2~" " &
+ "font.pitch=~"2~" font.charset=~"0~" background.mode=~"1~" background.color=~"553648127~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
// Montamos el computado con la información de las páginas.
ls_create = "create compute(band=header color=~"0~" alignment=~"1~" border=~"0~" " &
+ "x=~"" + string((WIDTHA4V + ll_addWidth) - 526) + "~" y=~"92~" " &
+ "height=~"64~" width=~"503~" format=~"[GENERAL]~" " &
+ "expression=~"page() + '/' + pageCount()~" " &
+ "font.face=~"Arial~" font.height=~"55~" font.weight=~"400~" font.family=~"2~" " &
+ "font.pitch=~"2~" font.charset=~"0~" background.mode=~"1~" background.color=~"553648127~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
/******************************************************************/
/* MODIFICAMOS EL PIE DINÁMICAMENTE */
/******************************************************************/
// Recogemos y modificamos la altura del pie.
If trim(as_footer) <> '' Then
ll_height = long(apo_aux.DYNAMIC describe("DataWindow.Footer.Height"))
ls_aux = apo_aux.DYNAMIC modify("DataWindow.Footer.Height=" + string(ll_height + 100))
// Montamos la etiqueta solo si corresponde.
ls_create = "create text(band=footer alignment=~"2~" text=~"" + as_footer + "~" border=~"0~" color=~"0~" " &
+ "x=~"0~" y=~"" + string(ll_height) + "~" height=~"80~" width=~"558~" font.face=~"Arial~" font.height=~"-12~" " &
+ "font.weight=~"700~" font.family=~"2~" font.pitch=~"2~" font.charset=~"0~" " &
+ "background.mode=~"2~" background.color=~"16777215~")"
ls_aux = apo_aux.DYNAMIC modify(ls_create)
End If
End If
Return li_r
</textarea><br />
Se aceptan sugerencias y mejoras. Saludos.Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-80494822786597215372007-02-12T11:34:00.000+01:002007-02-12T13:21:33.136+01:00Copiar "Retrieval Arguments" entre dw/ds<div align="justify">Si has intentando traspasar la información de un dw/ds y existían campos computados u otros elementos que hiciesen referencia a los "Retrieval Argument" (sin considerar la SQL), habrás podido observar que dicha información no se procesa de modo correcto, si son computados no aparecen y si por ejemplo un grupo se basaba en ellos tampoco aparecía el grupo. <br /><br />Este problema no sucede solo al hacer copia de datos entre los dw/ds, también sucede con en método ShareData.<br /><br />Hasta aquí podríamos pensar que habría que buscar mediante "describe" o "modify" las opciones necesarias para conseguir nuestros objetivos, traspasar la información de los "Retrieval Arguments", pues bien, casi, casi... resulta que podemos obtener los "Retrieval Arguments", pero no existe medio material de asignarlos a un dw o ds sin tener que recurrir al método retrieve, y en algunos casos esta operación puede ser pesada y costosa, y necesitaremos evitarla a toda costa, bueno, pues como siempre depende del objetivo final, en mi caso necesitaba conseguir que los computados funcionasen y que los grupos también lo hiciesen, así que si no tenía opción de asignar la información directamente al control, lo que hice fue modificar dinámicamente los controles afectados.<br /><br />Por un lado tiré en parte con código de las PFCs pare recuperar los "Retrieval Arguments", ampliando la función para poder obtener además el valor asociado, y por otro lado, preparé una función que permitía modificar los controles que contenían información asociada a los argumentos.<br /><br />Aquí os muestro una función que usaremos en nuestro código y esta sacada de las PFCs.</div><br />
<textarea rows="12" wrap="off" cols="45" class="miTextArea">
// Definición: integer of_parseToArray(string as_source, string as_delimiter, ref string as_array[])
long ll_DelLen, ll_Pos, ll_Count, ll_Start, ll_Length, ll_null
string ls_holder
// Comprobación de nulos.
IF IsNull(as_source) or IsNull(as_delimiter) Then
SetNull(ll_null)
Return ll_null
End If
// Comprobamos que exista algún texto.
If Trim (as_source) = '' Then
Return 0
End If
// Obtenemos el tamaño del delimitador.
ll_DelLen = Len(as_Delimiter)
// Obtenemos la posición de la primera ocurrencia dentro del origen.
ll_Pos = Pos(Upper(as_source), Upper(as_Delimiter))
// Solo si existe una entrada.
If ll_Pos = 0 Then
as_Array[1] = as_source
Return 1
End If
// Si hay más de una entrada entramos en un bucle para recoger y asignar
// al array cada una de ellas.
ll_Count = 0
ll_Start = 1
Do While ll_Pos > 0
// Establecemos la entrada actual.
ll_Length = ll_Pos - ll_Start
ls_holder = Mid(as_source, ll_start, ll_length)
// Actualizamos la entrada actual y el contador.
ll_Count ++
as_Array[ll_Count] = ls_holder
// Establecemos la nueva posición de inicio.
ll_Start = ll_Pos + ll_DelLen
// Obtenemos la siguiente posición de la siguiente ocurrencia.
ll_Pos = Pos(Upper(as_source), Upper(as_Delimiter), ll_Start)
Loop
// Establecemos la última entrada.
ls_holder = Mid(as_source, ll_start, Len(as_source))
// Actualizamos el array y el contador si es necesario.
If Len(ls_holder) > 0 Then
ll_count++
as_Array[ll_Count] = ls_holder
End If
// Devolvemos el número de entradas encontradas.
Return ll_Count
</textarea>
<br />
<div align="justify">Ahora que ya tenemos esta función que nos facilitará la vida para este y otros códigos, vamos a recoger el dw/ds los argumentos ("Retrieval Arguments") que queremos traspasar.<br /><br />Fijaros como detalle en la definición de esta función que en lugar de usar como tipo de argumento dw o ds e utilizado uno de tipo powerobject para que la misma función sirva para ambos casos y después, como consecuencia lógica, tengo que hacer las llamadas de modo dinámico:</div><br />
<textarea rows="12" wrap="off" cols="45" class="miTextArea">
// Definición: integer of_dwGetArguments(ref powerObject apo_aux, ref string as_argName[], ref string as_argDataTypes[], ref string as_argValues[])
string ls_dwargs, ls_dwargswithtype[], ls_args[], ls_types[]
long ll_a, ll_args, ll_pos, ll_index
n_cst_string luo_string
// Comprobamos los argumentos.
if IsNull(apo_aux) or not IsValid(apo_aux) then
return -1
end if
// Obtenemos el string con los argumentos del dw o ds.
ls_dwargs = apo_aux.DYNAMIC Describe ( "DataWindow.Table.Arguments" )
// Separamos los argumentos utilizando la un array y obtenemos el número total.
ll_args = luo_string.of_ParseToArray ( ls_dwargs, "~n", ls_dwargswithtype )
// Ahora separamos el nombre del argumento de su tipo y además obtenemos el valor.
For ll_a = 1 to ll_args
ll_pos = Pos ( ls_dwargswithtype[ll_a], "~t", 1 )
If ll_pos > 0 Then
ll_index = UpperBound(as_argnames) + 1
as_argNames[ll_index] = Left ( ls_dwargswithtype[ll_a], ll_pos - 1 )
as_argDataTypes[ll_index] = Mid ( ls_dwargswithtype[ll_a], ll_pos + 1 )
// Cargamos el valor correspondiente. Si es de tipo array ponemos cadena vacía.
If right(as_argDataTypes[ll_index], 4) = 'list' Then
as_argValues[ll_index] = ''
Else
as_argValues[ll_index] = apo_aux.DYNAMIC Describe("evaluate('" + as_argNames[UpperBound(as_argnames)] + "',1)")
End If
End If
Next
Return UpperBound ( as_argnames )
</textarea>
<br />
<div align="justify">Otra apreciación sobre la última función descrita es que solo se puede implementar sobre "Retrieval Arguments" que no sean de tipo array, pero esto no un impedimento para nuestros objetivos, ya que estos solo se puede usar en la sentencia SQL y nosotros los datos los traspasaremos con el método ShareData.<br /><br />Ahora nos queda la parte en la que ponemos los "Retrieval Arguments" en el datawindow o datastore destino. Nota: en mi caso se trataba de imprimir un ds que podría proceder de un dw u otro ds, por eso el argumento utilizado es de este tipo, pero si queréis utilizarlo para ds o dw podéis aplicar el truco comentado de la función anterior.</div><br />
<textarea rows="12" wrap="off" cols="45" class="miTextArea">
// Definición: of_dwSetArguments(Ref datastore ads_aux, string as_argNames[], string as_argDataTypes[], string as_argValues[])
string ls_object, ls_objects, ls_type, ls_expression
string ls_value, ls_aux
integer li_len, li_to, li_from, li_x, li_pos
// Obtenemos la colección de objetos de ds.
ls_objects = ads_aux.describe('datawindow.objects')
// Recorremos los todos objetos.
li_len = len(ls_objects)
If li_len > 0 Then
// Inicializamos la variable necesaria desde la que buscamos el siguiente objeto.
li_from = 1
// Recorremos todos los objetos.
Do
li_to = pos(ls_objects, "~t", li_from)
// Obtenemos el nombre del objeto.
If li_to = 0 Then
ls_object = mid(ls_objects, li_from)
Else
ls_object = mid(ls_objects, li_from, li_to - li_from)
End If
If len(ls_object) > 0 Then
// Obtenemos el tipo del objeto.
ls_type = ads_aux.describe(ls_object + '.type')
// Solo si es computado comprobamos si su expresión contiene "retrieval arguments".
If ls_type = "compute" Then
ls_expression = ads_aux.describe(ls_object + '.expression')
// Para cada objeto miramos todos los "retrieval arguments".
For li_x = 1 To upperBound(as_argNames)
// Solo tratamos argumentos que no sean array.
If right(as_argDataTypes[li_x], 4) = 'list' Then
Continue
Else
li_pos = pos(ls_expression, as_argNames[li_x])
Do While li_pos > 0
// Comprobamos que no sea otro identificador distinto, para lo que
// el carácter que lo precede y el que le sigue debe ser distinto
// de letra o número. (si buscamos 'numeropi' que no tome 'numeropista')
If ((li_pos = 1) Or match(mid(ls_expression, li_pos -1, 1), '[^A-Z^a-z^0-9]')) And &
((li_pos + len(as_argNames[li_x]) - 1 = len(ls_expression)) Or match(mid(ls_expression, li_pos + len(as_argNames[li_x]), 1), '[^A-Z^a-z^0-9]')) Then
ls_value = as_argValues[li_x]
// Obtenemos la nueva expresión para el computado en base al tipo de dato.
Choose Case as_argDataTypes[li_x]
Case 'number'
ls_aux = ls_value
Case 'string'
ls_aux = "'" + ls_value + "'"
Case 'date'
ls_aux = "date('" + ls_value + "')"
Case 'time'
ls_aux = "time('" + ls_value + "')"
Case 'datetime'
ls_aux = "datetime(date(left('" + ls_value + "', 10)), time(mid('" + ls_value + "', 12, 8)))"
Case Else
// En un computado no podría aparecer otro tipo.
End Choose
ls_expression = replace(ls_expression, li_pos, len(as_argNames[li_x]), ls_aux)
End If
// Buscamos si la misma ocurrencia aparece otra vez.
li_pos = pos(ls_expression, as_argNames[li_x])
Loop
End If
Next
// Si se modifico la expresión para el compute la sustituimos con la nueva.
If ls_expression <> ads_aux.describe(ls_object + '.expression') Then
// Antes de hacer el modify, hay que añadir delante de las comillas dobles la virgulilla.
li_pos = pos(ls_expression, '"')
do while li_pos > 0
ls_expression = replace(ls_expression, li_pos, 0, "~~")
li_pos = pos(ls_expression, '"', li_pos + 2)
Loop
ls_aux = ads_aux.Modify(ls_object + ".expression=~"" + ls_expression + "~"")
End If
End If
End If
li_from = li_to + 1
Loop While (li_to > 0)
End If
</textarea>
<br />
<div align="justify">Ya solo queda ver un ejemplo en el que se vea claramente como utilizar las funciones descritas hasta el momento. Ya comente que en mi caso utilizo este tinglado para añadir dinámicamente una cabecera standard a todos mis dw o ds en el momento de imprimirlos, para lo cual utilizo una función:</div><br />
<textarea rows="12" wrap="off" cols="45" class="miTextArea">
// Definición: of_printwithheader(ref datawindow adw_aux, string as_application, string as_title, string as_footer)
string ls_syntax, ls_err, ls_argNames[], ls_argDataTypes[], ls_argValues[]
integer li_r
datastore lds_aux
// Obtenemos la syntax del dw original.
ls_syntax = adw_aux.Describe("datawindow.syntax")
// Creamos el datastore para realizar los cambios sobre él y no sobre el dw original.
lds_aux = CREATE datastore
lds_aux.Create(ls_syntax, ls_err)
// Modificamos los campos calculados si se basan en los argumentos.
This.of_dwGetArguments(adw_aux, ls_argNames, ls_argDataTypes, ls_argValues)
This.of_dwSetArguments(lds_aux, ls_argNames, ls_argDataTypes, ls_argValues)
// Compartimos los datos del dw original con el ds.
adw_aux.shareData(lds_aux)
// Montamos los encabezados en el ds (NO DESCRITO EN ESTE POST).
li_r = This.of_setHeader(lds_aux, as_application, as_title, as_footer)
If li_r = 0 Then
// Imprimimos el ds.
li_r = lds_aux.print()
End If
// Dejamos de compartir los datos y eliminamos el ds temporal.
lds_aux.shareDataOff()
DESTROY lds_aux
// Retornamos el valor de retorno de la impresión.
Return li_r
</textarea>
<br />
<div align="justify">Espero haber solucionado más de un quebradero de cabeza. Hasta pronto.</div>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-59237535293105383022007-01-08T11:26:00.000+01:002007-01-09T15:26:03.481+01:00VerticalScrollPosition y HorizontalScrollPosition<div align="justify">Extendiendo el "Post" anterior podemos añadir el mensaje 276 para el control del desplazamiento horizontal. Para disponer de un control detallado de los posibles desplazamientos, las constantes que podríamos utilizar son:</div>
<br />
<textarea rows="12" wrap="off" cols="45" class="miTextArea">
// Mensajes de scroll.
WM_HSCROLL 276 //Scroll Horizontal
WM_VSCROLL 277 //Scroll Vertical
// El parámetro LowWord determina como desplazarnos.
CONTANT integer SB_LINEUP = 0 //Subir una línea.
CONTANT integer SB_LINELEFT = 0 //Una posición a la izq.
CONTANT integer SB_LINEDOWN = 1 //Bajar una línea
CONTANT integer SB_LINERIGHT = 1 //Una posición a la dcha.
CONTANT integer SB_PAGEUP = 2 //Subir una página.
CONTANT integer SB_PAGELEFT = 2 //Una página a la izq.
CONTANT integer SB_PAGEDOWN = 3 //Bajar una página.
CONTANT integer SB_PAGERIGTH = 3 //Una posición a la dcha.
CONTANT integer SB_PAGETOP = 6 //Ir arriba.
CONTANT integer SB_LEFT = 6 //Ir a la izq.
CONTANT integer SB_PAGEBOTTOM = 7 //Ir abajo.
CONTANT integer SB_RIGHT = 7 //Ir a la dcha.
CONTANT integer SB_ENDSCROLL = 8 //Ir al final.
</textarea>
<br />
<div align="justify">De todos modos, al final he optado por otra solución en la que utilizo describe y modify sobre VerticalScrollPosition, HorizontalScrollPosition y Horizontal2ScrollPosition, ya que en mi caso, me interesa desplazarme a una posición en la que se visualiza un objeto en concreto, y me permitiría el control horizontal de las dos áreas visibles del dw si esta activado el atributo HSplitScroll. Así que la parte del código que controla el error queda del siguiente modo (tras controlar el error y notificarlo):</div>
<br />
<textarea rows="5" wrap="off" cols="45" class="miTextArea">
dw_x.setColumn(ls_campo)
dw_x.modify("datawindow.VerticalScrollPosition = " &
+ dw_x.describe(ls_campo + ".y"))
dw_1.setFocus()
</textarea>
<br />
¿Qué tal se portaron los Reyes?Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-90898819783068518822007-01-05T14:38:00.000+01:002007-01-09T15:32:10.488+01:00Scroll en un datawindow con PowerScript<div align="justify">Tras un error controlado necesité poner el foco en el control que el usuario tiene que corregir, pero trabajando con un formulario de tipo freeform, con una fila que ocupa más de una página, si el control no esta visible, aunque reciba el foco no se desplaza el dw, por lo que es necesario desplazarlo manualmente desde código.
Mandando un mensaje al control podremos hacer scroll en los dw desde el código:</div>
<br />
<textarea class="miTextArea" rows="12" wrap="off" cols="45">
// scroll hacia arriba una lína
Send(Handle(dw_1), 277, 0, 0)
// scroll hacia abajo una línea
Send(Handle(dw_1), 277, 1, 0)
// scroll hacia arriba una página
Send(Handle(dw_1), 277, 2, 0)
// scroll hacia abajo un página
Send(Handle(dw_1), 277, 3, 0)
</textarea>
<br />
Felices ReyesMichel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-90093104253262647742007-01-04T17:03:00.000+01:002007-01-09T15:36:35.533+01:00Convertir a números cardinales<div align="justify">En más de una ocasión me he visto en la necesidad de convertir el contenido de una variable numérica a un string con el número en cardinal representado con texto, así que en adelante si me sucede ya sólo tendré que llegarme hasta aquí.
En esta ocasión principalmente encontrareis el código sin muchos comentarios como otras veces, deciros que es mejorable, que se puede hacer más genérica y tratar el género, la moneda para trabajar con distintas divisas, las apócopes, etc. En mi caso la he centrado en el Euro con sus correspondientes céntimos, si cambia la moneda que usáis y también tiene fracción, solo tendréis que cambiar los literales correspondientes.
La declaración de la función sería: string f_numberToCardinal(decimal adc_value) y el código quedaría así:</div>
<br />
<textarea rows="20" wrap="off" cols="45" class="miTextArea">
string ls_r, ls_cadena, ls_aux
int li_terna, li_fraccion, li_unidad
integer li_decena, li_centena
dec{0} ldc_entero
boolean lb_todoCeros, lb_soloLaUnidad
string ls_unidades[9], ls_decenas[9], ls_centenas[9]
string ls_decenas1[0 to 6], ls_decenas2[0 to 6]
ls_unidades = {'UN','DOS','TRES','CUATRO','CINCO', 'SEIS','SIETE','OCHO','NUEVE'}
ls_decenas = {'','','TREINTA','CUARENTA','CINCUENTA', 'SESENTA','SETENTA','OCHENTA','NOVENTA'}
ls_centenas = {'','DOSCIENTOS','TRESCIENTOS', 'CUATROCIENTOS','QUINIENTOS','SEISCIENTOS','SETECIENTOS','OCHOCIENTOS','NOVECIENTOS'}
ls_decenas1 = {'DIEZ','ONCE','DOCE','TRECE', 'CATORCE','QUINCE','DIECISEÍS'}
ls_decenas2 = {'VEINTE','VEINTIÚN','VEINTIDÓS', 'VEINTITRÉS','','','VEINTISÉIS'}
// Recogemos la parte entera y la fracción.
ldc_entero = truncate(adc_value, 0)
li_fraccion = (truncate(adc_value, 2) - ldc_entero) * 100
Do While ldc_entero > 0
// Indicamos la terna actual.
li_terna ++
// Se recorre el número por ternas.
ls_cadena = ''
li_unidad = mod(ldc_entero, 10)
ldc_entero = truncate(ldc_entero / 10, 0)
li_decena = mod(ldc_entero, 10)
ldc_entero = truncate(ldc_entero / 10, 0)
li_centena = mod(ldc_entero, 10)
ldc_entero = truncate(ldc_entero / 10, 0)
// Se analizan las unidades.
If li_unidad <> 0 Then
ls_cadena = ls_unidades[li_unidad] + ' '
End If
// Se analizan las decenas.
Choose Case li_decena
Case 1
Choose Case li_unidad
Case 0, 1, 2, 3, 4, 5, 6
ls_cadena = ls_decenas1[li_unidad] + ' '
Case Else
ls_cadena = 'DIECI' + ls_cadena
End Choose
Case 2
Choose Case li_unidad
Case 0, 1, 2, 3, 6
ls_cadena = ls_decenas2[li_unidad] + ' '
Case Else
ls_cadena = 'VEINTI' + ls_cadena
End Choose
Case 3, 4, 5, 6, 7, 8, 9
If li_unidad = 0 Then
ls_cadena = ls_decenas[li_decena] + ' ' + ls_cadena
Else
ls_cadena = ls_decenas[li_decena] + ' Y ' + ls_cadena
End If
End Choose
// Se analizan las centenas.
Choose Case li_centena
Case 1
If li_unidad = 0 And li_decena = 0 Then
ls_cadena = 'CIEN '
Else
ls_cadena = 'CIENTO ' + ls_cadena
End If
Case 2, 3, 4, 5, 6, 7, 8, 9
ls_cadena = ls_centenas[li_centena] + ' ' + ls_cadena
End Choose
// Analizamos la terna.
lb_todoCeros = ((li_unidad+li_decena+li_centena) = 0)
lb_soloLaUnidad = (li_unidad = 1 And &
(li_decena + li_centena = 0))
Choose Case li_terna
Case 2
If Not lb_todoCeros Then
If lb_soloLaUnidad Then
ls_cadena = 'MIL '
Else
ls_cadena += 'MIL '
End If
End If
Case 3
If Not lb_todoCeros Then
If lb_soloLaUnidad Then
ls_cadena += 'MILLÓN '
Else
ls_cadena += 'MILLONES '
End If
End If
Case 4
If lb_soloLaUnidad Then
ls_cadena = 'MIL MILLONES '
Else
ls_cadena += 'MIL MILLONES '
End If
Case Else
If li_terna <> 1 Then
ldc_entero = 0
ls_cadena = ''
End If
End Choose
// Se monta el sultado de la terna actual con el de las anteriores.
ls_r = ls_cadena + ls_r
Loop
// Utilizamos la recursividad para montar el literal de los decimales.
If li_fraccion <> 0 Then
ls_aux = trim(gf_numbertoletter(li_fraccion))
// Como se retorno como parte entera llega con EURO/EUROS y hay que quitarlo.
ls_aux = trim(left(ls_aux, len(ls_aux) - 5))
If li_fraccion = 1 Then
ls_aux = 'UN CÉNTIMO'
Else
ls_aux += ' CÉNTIMOS'
End If
End If
// Montamos la salida final según corresponda.
If li_terna = 0 Then
// Solo tiene parte decimal.
ls_r = ls_aux
Else
// Hay que añadir el modificar 'de' si es multiplo
// de 1000000.
If mod(truncate(adc_value,0),1000000)=0 Then ls_r+='DE '
// Añadimos la moneda a la parte entera.
If truncate(adc_value, 0) = 1 Then
ls_r = 'UN EURO'
Else
ls_r += 'EUROS'
End If
// Hay que añadir la parte decimal si la tiene.
If ls_aux<>'' Then ls_r = trim(ls_r) + ' CON ' + ls_aux
End If
Return ls_r
</textarea>
<br />
<p align="justify">
Algo que es interesante es el uso de la recursividad para resolver el <br>tema de los céntimos... ¿Qué os parece? Bueno, para no salirme de la tónica... espero que también os sirva a vosotros también.</p>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-36286998043160861792006-11-30T15:58:00.000+01:002007-01-10T09:59:04.231+01:00Números contenidos en otros<div align="justify">Si habéis trabajado con las APIs u otros lenguajes de programación conoceréis que determinados parámetros de muchas funciones están compuestos por la suma de más de un número, de tal modo que dicho número posteriormente permite conocer cuales son los que le componen.
Para empezar, los números utilizados para conseguir la suma deben ser el doble que el anterior o si se representan en binario solo los que tengan el primer bit a uno:
</div><pre class="micode"> 1 1
2 10
4 100
8 1000
16 10000
32 100000
64 1000000
128 10000000</pre><p align="justify">Según lo anterior, un parámetro que contenga el número 145 estará compuesto por los números 128 + 16 + 1, otros entornos tienen su propia función para determinar si un número esta contenido en otro (por decirlo de algún modo), y no es el caso de PowerBuilder, por lo que tendremos que crearnos nuestra propia función para determinar esto, y así poder utilizar este tipo de números para trabajar con Apis o para nuestro uso directo.
Según el ejemplo anterior el 145 en binario sería 10010001, de tal modo que es fácil determinar si el número decimal 16 (en binario 10000) esta dentro del 145, pasando ambos a binario y comprobando si la posición (de derecha a izquierda) del bit a 1 del número buscado, también es 1 en el número en el que se busca:
</p><pre class="micode">10010001 número en el que se busca
00010000 número buscado</pre><p align="justify">Con la lógica seguida hasta este punto ya solo queda pasar ambos número a binario, por lo que, de modo elegante, sería una función para la operación de conversión y otra para la comparación del bit:</p><br /><br /><u>Convertir en binario - string xx_aBinario(ulong aul_numero):</u><br /><br /><textarea class="miTextArea" rows="12" wrap="off" cols="45">
// Tras las comprobaciones correspondientes del parámetro proporcionado, quedaría algo así.
string ls_binario
int li_resto
Do Until aul_nummero = 0
li_resto = mod(aul_nummero ; 2)
aul_number = aul_nummero / 2
// Se monta el binario de derecha a izquierda.
ls_binario = string(li_resto) + ls_binario
Loop
Return ls_binario
</textarea><br /><br />
<u>Comparación del bit - contieneNumero(ulong aul_numeroContenedor,ulong aul_numeroContenido):</u><br /><br /><textarea class="miTextArea" rows="12" wrap="off" cols="45">
// Esta función se le llamaría del siguiente modo xx_contieneNumero(145, 16).
// Si lo contiene retorna True, en caso contrario False.
// Tras las comprobaciones pertinentes de los parámetros quedaría así:
string ls_cotenerdor, ls_contenido
ls_contenedor = string(aul_numeroContenedor)
ls_contenido = string(aul_numeroContenido)
If left(right(ls_contenedor,len(ls_conetido)),1)='1' Then
Return True
Else
Return False
End If
</textarea><br /><br /><p align="justify">Este es un método como otro cualquiera, hay muchos otros, recuerdo que antes de conocer el tema de los bit en los binarios me dedicaba a montar un bucle donde obtenía el número mayor doble del anterior partiendo desde uno y que fuera inferior al numero contenedor, el resultado era el mayor contenido, después al restar al contenedor el número mayor contenido, repetía la operación y obtenía así cada uno de los contenidos en la suma, claro esta que en cuanto aparecía el que buscaba salia, en caso contrario indicaba que no estaba contenido... y esto sigue, si os dais una vuelta por las PFCs encontrareis otro método.<br /><br />Como siempre, espero que os sirva. </p>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-1162195769026957532006-10-30T08:16:00.000+01:002007-01-10T10:09:10.476+01:00Trabajar con años y meses comerciales<div align="justify">He podido ver en más de un programa como se trabaja con formulas para cálculos de interés con años y meses comerciales (en adelante calendario comercial) y luego para realizar operaciones entre fechas o liquidaciones se utiliza el calendario natural, por lo que al final no corresponden los resultados finales. La cuestión de todo el descabale se basa en que se utilizan funciones convencionales para calcular la diferencia entre dos fechas, cuando en realidad este tipo de formulas no funcionan, ya que asignan a cada mes su día final correcto según el calendario natural, pero el comercial, siempre debe tener 30 días, tanto si es febrero, como bisiesto o tiene 31 días.<br /><br />Para evitar esto desarrollé un método que determina diferencia entre fechas utilizando el calendario comercial y con un par de funciones extras.<br /><br />El método utiliza como argumentos iniciales dos fechas (desde y hasta), que son entre las que se tiene que calcular la diferencia, por otro lado, aparece otro parámetro de tipo boleano que permite indicar si hay que realizar ajuste por día de inicio o final de mes de la fecha hasta... no me parece claro como lo explique, así que un ejemplo: Si el día de la fecha "desde" es superior al día de la fecha "hasta" y el día de la fecha "hasta" es final de mes, se realiza un ajuste para que cuadre el resultado en meses. Ahora con fechas y números reales, si a una fecha inicial 30/12/2006 le sumamos dos meses (que no 60 días) obtenemos una fecha real final 28/02/2007, si descomponemos ambas fechas pasando a días cada una de sus partes y obtenemos la diferencia, nos arroja en este caso un resultado de 58, sin embargo en este caso para cálculos comerciales, me puede interesar que nos de los 60 días correspondientes a los 2 meses comerciales, ya que el fecha final es la que es porque no existe 30/02/xxxx. Este caso se detecta comprobando que la fecha "hasta" es un final de mes y que el día de la fecha "desde" es superior al día de la fecha "hasta", para lo que habría que indicar que se tengan en cuenta los días de diferencia entre ambas fechas en el resultado final.<br /><br />Tener en cuenta si en nuestras cuentas de días se deben considerar los dos días pasados como argumentos o solo uno ellos. Fijaros que en el primer caso del 15/xx/xxxx al 15/xx+1/xxxx transcurren 31 días y no 30. Para considerar este último comentario en el algoritmo resultante solo tendríais que añadir, o no, una unidad al total resultante. Todo esto queda del siguiente modo:</div><br /><textarea rows="20" wrap="off" cols="45" class="miTextArea">
long ll_r, ll_daysInitial, ll_daysEnd, ll_ajuste
integer li_day
setNull(ll_r)
// Comprobamos los argumentos.
If IsNull(ad_initial) Or IsNull(ad_end) Then
Return ll_r
End If
// Calculamos el total de días para la fecha inicial.
li_day = day(ad_initial)
If li_day > 30 Then
li_day = 30
End If
ll_daysInitial = (year(ad_initial) * 360) + (month(ad_initial) * 30) + li_day
// Calculamos el total de días para la fecha final.
li_day = day(ad_end)
If li_day > 30 Then
li_day = 30
End If
ll_daysEnd = (year(ad_end) * 360) + (month(ad_end) * 30) + li_day
// Obtenemos la diferencia de días.
ll_r = ll_daysEnd - ll_daysInitial
// Según el tercer argumento booleano realizamos
// o no el ajuste.
If ab_ajustar Then
If month(relativeDate(ad_end, 1)) > month(ad_end) Then
ll_ajuste = day(ad_end) - day(ad_initial)
If ll_ajuste > 0 Then
ll_r += ll_ajuste
End If
End If
End If
Return ll_r
</textarea><br /><br />Como siempre, espero que os sea de utilidad al resto.Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com1tag:blogger.com,1999:blog-22374773.post-1159527547000958532006-09-29T12:28:00.000+02:002007-01-10T10:35:07.500+01:00Crear objetos de Forma dinámica en un UserObject<div align="justify">Este post se origina con la respuesta que he dado en un foro de Sybase a este tema. Me parece interesante dejarlo aquí para que no se quede en el tintero, luego es muy difícil localizar temas antiguos en los foros.<br /><br />La problemática surge de que en PB no se puede añadir un objeto, creado de modo dinámico, sobre un objeto de usuario visual. Sin embargo esto si se puede hacer sobre una ventana, así que utilizaremos esta opción para lograr lo que PB no nos permite hacer directamente.<br /><br />Como se puede resolver... pues muy sencillo, hacemos el CREATE del objeto que queremos poner dinámicamente, le asignamos false a la propiedad visible y utilizamos la función OpenUserObject para pegarlo sobre la propia ventana donde se encuentra nuestro objeto visual, como el objeto esta invisible no se ve, después cambiamos mediante una función de API el padre del objeto creado dinámicamente y le asignamos como nuevo padre nuestro objeto visual y para terminar y antes de cambiar el atributo de visible, asignamos las propiedades que queremos al objeto en cuestión... Así se pierde mucho uno, por lo que lo mostraré con código. Reutilizaré el del post con las modificaciones necesarias para que funcione correctamente. Para poneros en antecedentes se trata de un objeto visual de usuario que se utilizará de barra de herramientas:<br /><br /><u>VARIABLES DE INSTANCIA DEL UO_BARRA_BOTONES:</u></div><br /><textarea rows="9" wrap="off" cols="45" class="miTextArea">
PictureButton iPb_Button[]
Integer iIndex[], iLastIndex
window iw_win
Long iHandle[]
Integer iLastX = 4, iLastY = 4
Constant Integer HEIGHT_BUTTON = 88
Constant Integer WIDTH_BUTTON = 110
</textarea><br /><br /><u>DECLARACIÓN DE FUNCIÓN EXTERNA:</u><br /><br /><textarea rows="2" wrap="off" cols="45" class="miTextArea">
Function ulong SetParent (ulong hWndChild, ulong hWndNewParent) Library "USER32.DLL"
</textarea><br /><br /><u>CONSTRUCTOR DEL UO_BARRA_BOTONES:</u><br /><br /><textarea rows="2" wrap="off" cols="45" class="miTextArea">
iw_win = Parent
</textarea><br /><br /><u>MÉTODO ADD:</u><br /><br /><textarea rows="10" wrap="off" cols="45" class="miTextArea">
//////////////////////////////////////////////////
// Objeto: uo_barra_botones
//
// Función: of_add
//
// Argumentos: value integer ai_id
// value string as_picture
// value string as_tag
// value boolean ab_initialstate
//
// Retorno: integer
//
// Descripción: Añade un botón en la barra y
// recalcula la siguiente posición
//
//////////////////////////////////////////////////
iLastIndex = UpperBound(ipb_button)
iLastIndex ++
ipb_button[iLastIndex] = Create PictureButton
ipb_button[iLastIndex].visible = True
// Después de crear el PictureButton lo ponemos sobre la
// ventana donde esta el objeto visual de usuario.
iw_win.openUserObject(ipb_button[iLastIndex])
// Obtenemos el Handle del PictureButton.
iHandle[iLastIndex] = Handle(ipb_button[iLastIndex])
// Cambiamos el Padre del PictureButton asignandole ahora
// como tal al uo_barra_botoenes
SetParent( iHandle[iLastIndex], Handle(This))
// Aquí continuamos con el código normal... sin
// olvidarse de hacerlo visible.
ipb_button[iLastIndex].x = iLastX
ipb_button[iLastIndex].y = iLastY
ipb_button[iLastIndex].height = HEIGHT_BUTTON
ipb_button[iLastIndex].Width = WIDTH_BUTTON
ipb_button[iLastIndex].visible = True
ipb_button[iLastIndex].enabled = ab_InitialState
iLastX = iLastX + HEIGHT_BUTTON
iIndex[iLastIndex] = ai_Id
Return 1
</textarea><br /><br />Espero que os sirva a todos.Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com4tag:blogger.com,1999:blog-22374773.post-1155737690605855752006-08-16T16:04:00.000+02:002007-01-10T11:36:05.692+01:00VScrollBar siempre visibles<div align="justify">Cuando la cantidad de páginas mostradas en un dw es solo una, la barra de desplazamiento vertical no aparece y en ocasiones estéticamente puede molestar. PowerBuilder no permite dejar la barra siempre visible y que esta este activa o no según corresponda, pero podemos simularlo utilizando APIS de Windows:</div><br /><u>Declaración de funciones externas:</u><br /><br /><textarea rows="3" wrap="off" cols="45" class="miTextArea">
Function boolean ShowScrollBar(long hWnd, long wBar, boolean bShow) Library 'user32'
Function boolean EnableScrollBar(long hWnd, ulong wSBflags, ulong wArrows) Library 'user32'
</textarea><br /><br /><u>Función:</u><br /><br /><textarea rows="10" wrap="off" cols="45" class="miTextArea">
// Esta función debe llamarse siempre que se inserten o eliminen filas en el dw.
Constant long SB_HORZ = 0
Constant long SB_VERT = 1
Constant long SB_CTL = 2
Constant long SB_BOTH = 3
Constant Long ESB_ENABLE_BOTH = 0
Constant Long ESB_DISABLE_BOTH = 3
long ll_pageCount
string ls_aux
ls_aux = dw_1.describe("evaluate('pageCount()', 0)")
ll_pageCount = long(ls_aux)
If ll_pageCount = 1 Then
If dw_1.vScrollBar Then
dw_1.vScrollBar = False
ShowScrollBar(handle(dw_1),SB_VERT, True)
EnableScrollBar(handle(dw_1),SB_VERT,ESB_DISABLE_BOTH)
End If
Else
If Not dw_1.vScrollBar Then
dw_1.vScrollBar = True
EnableScrollBar(Handle(dw_1),SB_VERT,ESB_ENABLE_BOTH)
End If
End If
</textarea>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-1149176630185964012006-06-01T17:30:00.000+02:002007-01-10T11:39:40.132+01:00BreakPoints en el Debug (Los menos usados)<div align="justify">Todos utilizamos el debug, pero por lo general no aprovechamos toda su potencia. Ya estamos acostumbrados al Watch de PB, pero rara vez utilizamos las opciones avanzadas de los puntos de interrupción. Cada vez que me toca usarlas, tengo que tirar de la ayuda en html de PB, para recordar como se hacía, y en muchos casos tiramos por la calle de en medio poniendo un IF dentro del bucle que modifica una variable que queremos comprobar cuando alcanzo cierto valor.<br /><br />Hoy ha tocado, y por eso he querido poner aquí y compartir la sencillez y potencia de las opciones avanzadas de los puntos de interrupción.<br /><br /><strong>Parar cuando queremos parar si una variable cambia de valor:</strong><br /><br />El punto de interrupción se puede aplicar sobre cualquier variable dentro del alcance. Aquí hay una cosa importante, que es lo que siempre me ha estado confundiendo y es que si estamos en el Painter del Debug pero sin tener este arrancado, solo podemos indicar variables globales, para indicar que se pare cuando una variable local cambie, hay que lanzar el la aplicación en modo debug con una parada dentro del código donde se encuentra la variable local que deseamos utilizar para pausar el debug, y en ese momento ya podemos indicar que variable deseamos que pause el debug cuando esta cambie.<br /><br />Para poner los puntos de interrupción asociados al cambio de la variable podemos arrastrarla desde el watch o la zona de variables a la zona de BreakPoints, o hacer en dichos sitios click con el botón derecho sobre la variable e indicar “Break on Change”, también en la ventana “Edit BreakPoints” en la ficha de “Variable” podemos añadir una nueva.<br /><br /><strong>Como parar el debug cuando una variable alcance un valor determinado:</strong><br /><br />El método, creo que más sencillo, es poner un punto de interrupción normal y hacer doble click sobre él en la zona “Breakpoints” y en el cuadro de diálogo que aparece poner la condición deseada, por ej.: li_x = 5, al aceptar, podremos ver en la zona Breakpoints dicha condición del siguiente modo: objeto.evento.linea when li_x = 5.<br /><br />Además, podemos indicar el número total de ocurrencias que deben darse para realizar una parada, de tal modo que si ponemos un punto de interrupción e indicamos que han de darse 3 ocurrencias, has que no se pase tres veces por ese punto no se pausara el debug, una vez pausado se reinicia el contador interno y sucederá lo mismo si otra vez se llega a 3 veces (según este ejemplo).<br /><br />Si al introducir el número de ocurrencia lo hacemos junto con una condición no se pausará del debug has que dicha condición se de tres veces.<br /><br /><strong>Lo que falta:</strong><br /><br />Sería una gozada poder parar el debug cuando una variable cambiara X veces. Cuidado, no hay que confundir esta opción con la de que se pause el debug en un punto determinado del código cuando la variable alcance un valor. Si os fijáis en el cuadro de diálogo “Edit Breakpoints” en la ficha “Variables”, podemos introducir la variable que deseamos que pare el debug cuando cambie, pero sin poder indicarle aquí el número de veces.</div>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-1144145971995612472006-04-04T11:33:00.000+02:002007-01-10T11:38:09.721+01:00Evaluación de Traslation Toolkit de PB<div align="justify">Hemos tenido que traducir una aplicación en la empresa, y hemos evaluado tres posibilidades para realizar esta tarea: directamente sobre una copia de los fuentes, mediante una herramienta denominada <a href="http://www.enable-pb.com" target="_blank">Enable</a> de "Best In", o mediante PowerBuilder Translation Toolkit, herramienta de todos conocida, por incluirse en el paquete de aplicaciones de PB, pero que pocos han necesitado usar.<br /><br />Entre las tres opciones, y pensando que incialmente que deberían mantenerse tanto la versión en Español como la de Ingles, y no siendo un requerimiento cambiar de idioma en "Caliente", se decidió usar Translation Toolkit.<br /><br />Mi opinión es que es una herramienta útil inicialmente, ya que extrae frases de donde uno no se lo espera, como por ejemplo de los <em>checkBox</em>, de los <em>DisplayValue</em> de los <em>DropDawnListBox</em>, etc., pero peca en el tema de los redimensionamientos de las cajas donde aparecen los textos, y el problema no es que no ajuste los tamaños inicialmente, sino que biene despues.<br /><br />Una vez traducido todo en un diccionario almacenado en una BD, podemos generar el nuevo código con las frases traducidas, y luego nos queda la ardua labor de redimensionar las cajas necesarias... bueno, esto podría se parte del trabajo, pero la putada no es esta, la putada es que al cambiar una frase de un objeto de una librería y pedirle que genere otra vez la versión traducida, te machaca toda la pbl donde se encuentre dicho objeto, por lo que habría que volver a redimensionar todos los controles.<br /><br />Al final, en nuestro caso, después de la traducción inicial se decidió mantener solo la versión traducida, por lo que ya tocamos directamente sobre el código y nos quitamos de rollos.<br /><br />La siguiente imagen corresponde con el módulo que permite definir el proyecto y marcar los objetos del mismo que deben traducirse:<br /><br /></div><a href="http://photos1.blogger.com/blogger/2693/2275/1600/fig2.jpg" target="_blank"><img style="DISPLAY: block; MARGIN: 0px auto 10px; CURSOR: hand; TEXT-ALIGN: center" alt="" src="http://photos1.blogger.com/blogger/2693/2275/400/fig2.jpg" border="0" /></a>
Esta otra imagen corresponde con el módulo que permite traducir las frases, lo que esta bien para esto es que dispone de un acceso independiente para acceder solo a este módulo para que un traductor se pueda encargar de esta parte, sin acceso al resto de la herramienta:<br /><br /><a href="http://photos1.blogger.com/blogger/2693/2275/1600/fig3.jpg" target="_blank"><img style="DISPLAY: block; MARGIN: 0px auto 10px; CURSOR: hand; TEXT-ALIGN: center" alt="" src="http://photos1.blogger.com/blogger/2693/2275/400/fig3.jpg" border="0" /></a>
Y por último os muestro una pantalla donde se seleccionan los objetos que se quieren traducir, en este caso se seleccionan los objetos para los que hay que cambiar las frases:<br /><br /><a href="http://photos1.blogger.com/blogger/2693/2275/1600/fig4.jpg" target="_blank"><img style="DISPLAY: block; MARGIN: 0px auto 10px; CURSOR: hand; TEXT-ALIGN: center" alt="" src="http://photos1.blogger.com/blogger/2693/2275/400/fig4.jpg" border="0" /></a>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-1142587050289677702006-03-17T10:13:00.000+01:002007-01-10T11:41:52.782+01:00Evaluación Visual Expert<div align="justify">Este producto ya lo vi hace tiempo y recientemente he tenido la oportunidad de asistir a una presentación del mismo en mi empresa, ya que se plantean la posibilidad de adquirirlo. Los AP tenemos que dar el visto bueno, y después, como en cualquier gran multinacional, empiezan los trámites burocráticos, hasta conseguir la compra. En principio, los tres AP pensamos que pueden ayudarnos en nuestro trabajo, ya que da mucha facilidad a la hora de localizar objetos dentro de la aplicación, pero ante todo la mayor ventaja es la evaluación de impacto sobre el cambio de cualquier elemento, ya que podemos visualizar de un modo cómodo, el resto de elementos que hacen uso del que nosotros queremos cambiar. Saber usarlo no parece complicado, por lo menos en cuanto a las cosas sencillas y elementales, los detalles complejos supongo que se podrán resolver mediante la ayuda, aunque esta pueda ser algo escasa, o más que escasa, que carezca de suficientes palabras claves para localizar lo que nos interesa, esto lo comento, por que en la misma presentación tuvimos una duda de uso e intente buscar en la ayuda y fue imposible localizar lo que queríamos resolver. También facilita la generación de documentación técnica e informes como por ejemplo variables sin utilizar, pudiendo especificar el alcance de las mismas, y otra serie de filtros, en general este apartado esta bien, lastima que no exista aún aplicaciones que generen la documentación funcional y el manual de usuario. El producto es multilenguaje (francés, ingles y español) y puedes cambiar el idioma en caliente, pero hay cosas que no se traducen, por supuesto la ayuda siempre estará en correcto ingles.En definitiva son muchos más pros que contras y nos puede ayudar bastante, claro esta que hay que conocer sus apellidos, que en este caso, hoy por hoy ronda los 7.000 € e incluye tres licencias, y cuantas más licencias necesites, como es de esperar, más económica resulta cada una de ellas. Si queréis más información la podéis encontrar en la <a href="http://visual-expert.com/index_sp.htm" target="_blank">Web de Visual Expert</a>. Este es el aspecto general de la aplicación:</div><a href="http://photos1.blogger.com/blogger/2693/2275/1600/Visual%20Expert.0.jpg" target="_blank"></a><br /><br /><a href="http://photos1.blogger.com/blogger/2693/2275/1600/Visual%20Expert.1.jpg" target="_blank"><img style="CURSOR: hand" alt="" src="http://photos1.blogger.com/blogger/2693/2275/400/Visual%20Expert.1.jpg" border="0" /></a><br /><br /><div align="justify">Aun tendremos que realizar una revisión pendiente, por lo que iré actualizando directamente el post, para poner cualquier información que se interesante destacar.</div>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-1141810825882037122006-03-08T10:18:00.000+01:002007-01-15T11:51:51.274+01:00Función para obtener el módulo<div align="justify">Tal vez aquí se podría abrir un tema de discusión, en cuanto a la capacidad de las funciones de sistema de PB para tratar números de precisión <em>doble</em> o <em>real</em>. Muchas de estas funciones, como por ejemplo <em>truncate</em>, <em>round</em> o <em>string</em> (para conversiones de números), solo son capaces de trabajar adecuadamente con variables <em>decimal</em>, aunque la ayuda no lo especifique. Por otro lado, y centrandome en el tema que me ocupa en este post, la función <em>mod</em>, que si parece ser capaz de trabajar con variables de precisión <em>doble</em>, no figurá en el browser/funtions/systemfuntions que este sobrescrita, y por código o en el watch del debug, se puede comprobar que debe existir una definición para <em>doble</em> y otra para <em>long</em>.
Además, por razones de precisión cuando debe obtenerse los módulos de números muy grandes, se recomienda utilizar números enteros y no fraccionados o decimales, y el algoritmo que se necesita para dicha operación requiere cortar el número como si se tratase de una cadena, y en base de datos, el número con el que tenemos que trabajar en nuestro caso esta almacenado como cadena, ya que a excepción de la comprobación del dígito de control, no se realiza ninguna otra operación, por esto he creado una función que admite dos parámetros, por un lado un string con el número que del que hay que obtener el módulo y otro argumento con el divisor, en este caso un integer. La función queda así:<br /><br /><u>DEFINICIÓN:</u></div><br /><pre class="micode">integer uf_modlong(string as_valor, integer ai_divisor)</pre>
<u>CÓDIGO:</u><br /><br /><textarea rows="10" wrap="off" cols="45" class="miTextArea">
integer li_total, li_desde, li_hasta, li_r
string ls_mod, ls_aux
boolean lb_salir
li_total = len(as_valor)
Do
// Calculamos el fragmento del string que debemos tratar.
li_desde = li_hasta + 1
li_hasta = li_desde + 8 - len(ls_mod)
// Determinamos si hay que continuar partiendo.
If li_total >= li_desde Then
// Hay que continuar partiendo.
ls_aux = ls_mod
ls_aux += mid(as_valor, li_desde, li_hasta-li_desde+1)
// Calculamos el módulo.
ls_mod = string(mod(long(ls_aux), ai_divisor))
Else
// Hemos realizado todas las particiones necesarias.
lb_salir = True
li_r = integer(ls_mod)
End If
Loop Until lb_salir
Return li_r
</textarea>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com2tag:blogger.com,1999:blog-22374773.post-1141662893243729862006-03-06T16:58:00.000+01:002007-01-10T11:54:11.806+01:00Comprobación del dígito de control del IBAN<p align="justify">En este script, partimos de la premisa de disponer del IBAN formateado adecuadamente, así iremos al grano, y dejo que cada cual utilice las comprobaciones que considere oportunas para ver si el IBAN tiene el formato adecuado. Para concretar más el formato utilizado para el ejemplo será AANNCCCCCCCCCCCCCCCCCCCC, donde:</p><ul><li>AA: Código del país de la entidad (Letras en mayúsculas).</li><li>NN: Dígito de control del IBAN (Números).</li><li>CCCCCCCCCCCCCCCCCCCC: Número de cuenta.</li></ul><p align="justify">En primer lugar convertimos las letras a números, contando que la A vale 10, la B vale 11 y así sucesivamente, de tal modo que BE62510007547061 quedaría 111462510007547061.</p><br /><p class="micode">ls_letras = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'<br />
ls_aux = string(pos(ls_letras, left(as_IBAN, 1))+9)<br />ls_aux += string(pos(ls_letras, mid(as_IBAN, 2, 1))+9)</p><br /><p align="justify">En segundo paso consiste en mover los primeros seis caracteres a la derecha de tal modo que 111462510007547061 quedará 510007547061111462:</p><br /><p class="micode">as_IBAN = mid(as_IBAN, 5) + ls_aux + mid(as_IBAN, 3, 2)</p><br /><p align="justify">Y para finalizar hay que comprobar si el resto de la división del número obtenido entre 97 es 1. Si el resultado fuese distinto, el dígito de control sería incorrecto.</p><br /><p class="micode">mod(double(as_IBAN), 97)</p><br /><p align="justify">En este último punto hay un temita que puede tenerse en cuenta, y es que por razones de precisión se recomienda realizar esta operación con números enteros y no fraccionados o decimales, y como pordrá comprobarse el número con el que hay que operar es mayor que el que se puede almacenar en un unsignedlong (mayor entero en PB), como un entero de 32 bits o 64 bits representa un máximo de 9 ó 18 dígitos, se puede realizar la operación por partes tal y como describo a continuación:</p><ol><li>Cálculo del módulo 97 de los primeros 9 dígitos del número.<br /><br />Ej: <span class="micode">mod(510007547, 97) = 74</span></li><li><div align="justify">Construcción del siguiente entero de 9 dígitos, colocando en primer lugar el resultado del paso anterior. seguido por los siguientes 7/8 dígitos del número y calcular el módulo 97.<br /><br />Ej: <span class="micode">mod(<u>74</u>0611114, 97) = 12.</span></div></li><li>Repetir el paso dos hasta que todos los dígitos del número hayan sido procesados.
Ej: <span class="micode">mod(<u>12</u>62, 97) = 1</span></li></ol>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com4tag:blogger.com,1999:blog-22374773.post-1141656947672063862006-03-06T15:54:00.000+01:002007-01-10T11:56:11.180+01:00Script para eliminar caracteres no deseados<div align="justify">El siguiente script permite eliminar caracteres no deseados de un string, en este caso en concreto, elimina todos los que no sean una letra mayúscula o un número, pero si se quiere modificar los caracteres que han de eliminarse, simplemente se debe cambiar el argumento proporcionado a la función match y si fuese necesario jugar con la negación de las sentencias If.
<u>DEFINICIÓN:</u>
</div><pre class="micode">public string gf_limpiar(string as_texto)</pre>
<u>CÓDIGO:</u><br /><br /><textarea rows="10" wrap="off" cols="45" class="miTextArea">
string ls_aux
integer li_pos, li_len
ls_aux = '[^0-9^A-Z]'
If match(as_texto, ls_aux) Then
li_pos = 1
li_len = len(as_texto)
Do Until li_pos > li_len
If match(mid(as_texto, li_pos, 1), ls_aux) Then
as_texto = mid(as_texto, 1, li_pos -1) &
+ mid(as_texto, li_pos + 1)
li_len --
Else
li_pos ++
End If
Loop
End If
Return as_texto
</textarea>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0tag:blogger.com,1999:blog-22374773.post-1141295030141199272006-03-02T10:46:00.000+01:002006-11-15T07:25:08.960+01:00Standards ISO<table><tbody><tr><td valign="top"><p align="justify">Para la implementación de un algoritmo de validación del IBAN, que publicaré más adelante, he necesitado localizar la norma ISO que identifica que letras están asociadas con cada país. He encontrado por un lado la "<a href="http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html" target="_blank">English country names and code elements - ISO 3166 Code lists</a>" publicado por el propio organismo que la implemento, y por otro lado, como siempre días después intentando localizar la página mencionada, he llegado hasta la web ADAGO, concretamente a la <a href="http://www.adago.com/List_of_ISO_standards.html" target="_blank">lista de standards ISO</a>. Además de aparecer la lista de códigos de países hay un montón de standards interesantes.</p></td><td valign="top"><p><a href="http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html" target="_blank"><img style="MARGIN: 0px; CURSOR: hand" alt="" src="http://photos1.blogger.com/blogger/2693/2275/200/thumbnail.0.png" border="1" /></a></p><p><a href="http://www.adago.com/List_of_ISO_standards.html" target="_blank"><img style="MARGIN: 0px; CURSOR: hand" alt="" src="http://photos1.blogger.com/blogger/2693/2275/200/thumbnail2.0.png" border="1" /></a></p></td></tr></tbody></table>Michel Pérezhttp://www.blogger.com/profile/16404317239261051616noreply@blogger.com0