26 de octubre de 2017

Propuesta de Código Óptimo

Ahora estoy implementando una rutina, bajo un código que quiero que sea el óptimo en resultados, en tamaño y en tiempo. Expongo aquí las pautas y algunas de las limitaciones con las que me he encontrado de momento:

Supongamos que para un Conjunto Dado C tenemos N elementos, estos N elementos tienen todos por un lado un valor (podria ser un importe, p.ej) y tienen todos también un coste derivado de la relación entre C para llegar a cada undo de los elementos N.

Prentendo poder pasar unos valores CMin y CMax para los que las suma de valores de Subconjuntos del Conjunto C esten entre esos dos parámetros y de todos los posibles resultados encontrar el que me de un valor cuya suma de Costes del Subconjunto resultante sea la mínima posible de entre todas las posibilidades.

Voy a explicarlo en un ejemplo que quedará muchisimo más claro que todo este rollo raro:

Supongamos que B son Bancos y que el Conjunto C (BSCH) es el conjunto de recibos (Elementos) que deseo/puedo llevar a ese banco para descontar en N58.

Para cada Recibo N, tengo guardados 2 valores: 1º el importe del recibo, 2º el coste que me supone (aplicando los % y comisiones pactadas con el banco tratado y que tengo en una tabla de Cond. Bancarias).

CMin es un importe mínimo que yo debo llevar al banco, y del que no puedo bajar porque tengo por ejemplo, unos pagos que sé que hay que cubrir.

CMax es un importe máximo porque tengo la linea de descuento muy saturada y sólo me permite enviarles ese importe, con lo que no puedo llevar recibos que sumados superen este parametro. (con un relativo margen razonable, exacto es imposible).

Se Trata de Tratar todos Los Recibos enviados para este Banco y buscar sus posibles sumas combinatorias entre TODOS ellos y encajar ese importe entre los dos valores CMin y CMax obteniendo un Subconjunto Resultante tal que el coste de los intereses y comisiones bancarios sea el mínimo posible de todas las opciones que cumplan estos requisitos.

Notas:

Tengo desarrollados dos métodos.

  1. En método iterativo, es decir la programación habitual de todos nosotros, en general y la más rapida de pensar, pero no me acaba de encajar bien los resultados en cuanto al mejor coste posible.
  2. Lo tengo hecho por métodos de programación avanzada (y muy teoricos) por los teoremas de Dijkstra, Ford y Floyd... resumiendo... Backtraking de Seguimiento de Arboles y diagramas con el objetivo de obtener los caminos mínimos entre los distintos nodos del grafo (árbol). Este segundo la verdad es que me funciona muy bien al menos de momento, pero y parece ser el óptimo en resultados.... pero de momento me hallo en algún apurillo técnico... os lo resumo:
    1. El orden resultante de operaciones (sin optimizar demasiado)
    2. Es de 2^n és decir 2 a la n, siendo n el nº de recibos. o sea que para n = 4 recibos, 6 recibos, 8, recibos, 16 recibos y 50 recibos me da un total de operaciones:

      2^4 = 16 operaciones

      2^6 = 64 operaciones

      2^8 = 256 operaciones

      2^16 = 65536 operaciones

      2^25 = 33554432 operaciones

      2^50 = 1125899906842624 operaciones

      Quereis que os diga el problema o SALTA A LOS PUT... Ojos.... Perdonad !!!! :(

      Es decir, que sera la mar de óptimo y pijo... pero al menos por el momento me es totalmente inviable... Alguien no conoce alguna empresa que lleve más de 50 recibos a descontar N58, o da igual, 25 que ya ronda los 33 millones de operaciones ????

      Bueno, cabe decir que esto es para acojonar un poco, pq. hay mejoras relativamente fáciles de implementar que mejoran en algo el resultado, pero aún así ya aviso que no lo suficiente... Si estoy en algunas otras que creo que me lo pueden rebajar bastante y sino he pensado en la posibilidad de mezclar partes de los dos códigos (1º Iterativo y 2º Recursivo) y creo que por ahí tb. podria funcionar mejor (pq. así, ya es para dejarlo ahora mismo).

    3. Este problema... es peor que el anterior....
    4. Ehhh... continuad leiendo... pq. a ver si me equivoco y tiene fácil solución...

      Uso Técnicas de Backtracking y recursividad, eso significa llamadas dentro de la función X a la misma Función X, que pasa ??? que si no logro hallar un resultado o cortar (podar) el árbol de llamadas, llega un momento en que me salta un error de OverFlow por Anidamiento... no recuerdo cual es el máximo de llamadas anidadas permitidas en Vfp. 6.0 pero Toni Planas (Buen Compañero mío, saludos REY !!!!) me comento que le suena o cree que son 27... por lo que tengo el problema que aún solucionando lo de las combinaciones que seria resolver el tiempo de ejecución, estaria delante del problema técnico de que si no resuelvo (por bueno) o corto (por malo) el proceso de cada posibilidad antes de llegar a este "suspuesto" limite técnico... me "petará" por desborde.

La pregunta es ??? es evitable modif. algun parametro o set ??? pasa en Vfp. 8 o en Vfp. 9... y en ASP (pq. seguramente lo acabaré traduciendo) ???

Os pongo un ejemplo (con nº's totalmente aleatorios, pero para que veais el funcionamineto):

CMax = 9999999 (sin limite), pero CMin=15000

Conjunto C (Vector)   && Valores aleatorios, son sólo para pruebas !!!!

Pos  Imp   Coste
[1] 8,727 (20.40)
[2] 9,147 (22.15)
[3] 1,142 ( 6.34)
[4] 4,111 (11.67)
[5] 3,152 (35.36)
[6] 2,164 (19.09)
******************************************************************************************************************************
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 17,874 * (42.55)
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 21,985 * (54.22)
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 21,026 * (77.91)
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 16,410 * (69.18)
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 15,990 * (67.43)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 20,038 * (61.64)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 15,422 * (52.91)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 15,002 * (51.16)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 23,190 * (97.00)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 18,574 * (88.27)
[1]     0 ( 0.00)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 18,154 * (86.52)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 19,016 * (48.89)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 23,127 * (60.56)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 22,168 * (84.25)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 17,552 * (75.52)
[1] 1,142 ( 6.34)+[2]     0 ( 0.00)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 17,132 * (73.77)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 21,180 * (67.98)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 16,564 * (59.25)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3]     0 ( 0.00)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 16,144 * (57.50)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 15,605 * (82.94)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 15,185 * (81.19)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5]     0 ( 0.00)+[6] 9,147 (22.15) = 19,716 * (94.61)
[1] 1,142 ( 6.34)+[2] 2,164 (19.09)+[3] 3,152 (35.36)+[4] 4,111 (11.67)+[5] 8,727 (20.40)+[6]     0 ( 0.00) = 19,296 * (92.86)
******************************************************************************************************************************
[1]     0 ( 0.00)+[2]     0 ( 0.00)+[3]     0 ( 0.00)+[4]     0 ( 0.00)+[5] 8,727 (20.40)+[6] 9,147 (22.15) = 17,874 *(42.55)

Notas: Cabe decir que en este ejemplo ya se ve una de las mejoras, pero no siempre sale así de rapido, aunque es bueno que tengamos este.

Los elementos, se deben procesar (TODOS CON TODOS, no es necesario un orden concreto, siempre que su suma supere CMIN y por debajor de CMAX (más o menos, este valor es más de referencia)

Mejoras propuestas (algunas ya implementadas, otras aún no):

  1. Ordenar los Elementos de de C por Importe ASC, de esta forma, hallando el 1er elemento que supere CMIN, podemos obtener un primer SubConjC que seran todos los valores que de por si ya superen CMin y de ellos buscar el de mín. coste con lo que obtendriamos un primer candidato a falta de procesar ahora ya con otro metodo los que han quedado por debajo de CMIN.
  2. Si al vector que nos ha quedado ahora con los elementos de C cuyo importe sea menor q CMIN y por tanto necesitaremos sumarlos con otro/s de este vector para cubrir el minimo requerido. Si ordenamos estos por Importe DESC... las primeras sumas llegaran mucho más rapido al mín. que si lo hacemos al revés... y de esta forma ya empezamos a tener unos costes mín. que nos permitiran (ver. Punto 3)
  3. Teniendo almacenado el Subconjunto que de momento nos de el coste mín. y la suma de costes de esos elementos nos dará ese valor... si procesamos otra posibilidad, y aunque no la hayamos terminado, en algun momento su mín ya supera el que nosotros tenemos como óptimo... no hace falta continuar... pq. no lo mejorariamos... ok ???

Buenos... Mentes !!!! Os pido, si teneis ganas... porque es un poco galimatias... que penseis en mejoras o si de existir en limitaciones técnicas... sabéis de formas de eviatrlas o de versiones que las amplien...

Y por dudas, ya sabeis... un mensaje y hablamos...

Grácias a todos y un fuerte abrazo.....

VIsual FoxPro ForEver !!!

Josep Mª Picañol

21 de octubre de 2017

Aplicaciones VFP a traves de Internet usando VPN

Hace algunos años, allá por la versión FoxPro 2.6 para Windows, necesitaba ejecutar una aplicación a través de Internet. Desde entonces hasta la fecha he probado un sin fin de productos. Desde FoxWeb, FoxNet, WestWind Connection, AFP, Fox#,VFPServer, VFP Framework, etc. Desde servidores construidos por el propio código VFP hasta CGI o interacción con ASP.

Primeramente, que es una aplicación VFP a través de Internet? Necesito en verdad crear una interface a través de un navegador para acceder a las bases de datos de la aplicación? Necesito controles llamativos como DBI o Voodo para una interfaz mas rica al usuario? Necesito solamente la transferencia de datos desde un lugar remoto para actualizar, capturar, consultar?

Por cierto que hay varios tipos de necesidades, desde las del usuario (interfaz, facilidad, compatibilidad) hasta las técnicas (sobrecarga del servidor, memoria de la aplicación, instalaciones en los clientes, etc.) Es decir, en algunos casos es necesario tener un servidor web configurado para traer los datos bajo demanda, rápidamente y sin tener que instalar aplicación alguna en los clientes a control remoto, basta un simple navegador para tener los datos. Pero en otros casos, quizá lo que realmente necesitamos es: CONECTAR NUESTRA APLICACION CON LOS DATOS A TRAVES DE INTERNET COMO SI LO HICIERAMOS A TRAVES DE LA RED LOCAL.

Para ello necesitamos lo siguiente:

  1. Una VPN
  2. Una aplicación VFP cualquiera que una pequeña modificación para acceder a la base de datos.
  3. Una conexión a Internet (banda ancha de preferencia)
  4. Un servidor y un cliente.

Recordemos que necesitamos un servidor donde residen nuestra base de datos, esto funciona bien para nuestra LAN, y un cliente o clientes donde tendremos nuestra aplicación. Es posible que varias PCs, laptops tengan el sistema VFP que queremos usar, así que este estará instalado completamente con todas sus pantallas e interfaces, lo único que haremos será conectarnos con el servidor de datos para trabajar con ellos.

PASOS A SEGUIR

El primer paso es usar una VPN (Virtual Private Network). Se que hay varias soluciones tanto de hardware como de software, pero aquí mismo me encontré hace tiempo una excelente: HAMACHI. Pequeña, gratuita y fácil de manejar.

Así, descarguemos Hamachi desde su pagina https://secure.logmein.com/hamachi.msi. En 10 minutos estaremos usando nuestra VPN.

Una vez descargado, necesitamos instalar el Hamachi en lo que será nuestro servidor, puesto que funciona de manera transparente a través de Firewalls, la instalación es por demás sencilla. Necesitamos crear nuestra red, y dar de alta el nombre de nuestro server, así como su password. La empresa entonces nos dará una IP especial a esta computadora. Podemos hacer que Hamachi cargue de manera automática al encender la PC Servidor (aunque si es Servidor, este nunca se apagara).

Ahora, instalamos Hamachi en la computadora cliente, siguiendo casi los mismos pasos, pero en este caso, al ya tener una red hecha, nos incorporaremos a esta, de tal manera que tanto el servidor como el cliente estén en la misma red para que puedan reconocerse. Como los grupos de trabajo de Windows.

Carguemos el Hamachi entonces para conectarnos con el Servidor.

Aquí vemos el Hamachi en acción. Para proteger la identidad se han borrado los nombres de la red y números de IPs.

En este caso, la computadora cliente esta conectada a 3 redes Hamachi distintas. 2 en la misma empresa y una externa. Vemos que solamente 1 PC esta en línea (la segunda) que es el servidor que nos queremos conectar.

Aquí suponemos que el Hamachi esta ejecutándose del lado del servidor.

Y ESO ES TODO LO QUE NECESITAMOS PARA QUE NUESTRAS APLICACIONES FUNCIONEN A TRAVES DE INTERNET. No necesitamos un IP Fija, o conocer direcciones o dominios. Ya que Hamachi ha hecho todo esto por nosotros. Lo importante aquí, es conocer el numero de IP que este le esta dando al servidor.

Ahora, veamos como hacemos la conexión de nuestra aplicación.

En la imagen vemos que para comenzar a usar la aplicación primeramente tenemos que seleccionar la empresa. En este caso tenemos 2 empresas pero se acceden de 3 maneras diferentes:

  1. De manera local (en la propia PC o laptop)
  2. De manera Red (LAN)
  3. O por Internet.

Aunque eliminare algo de código por cuestiones de seguridad, creo que tendrán la idea. Esto funciona teniendo una tabla DBF (que puede ser libre) con unos cuantos campos como nombre de la empresa, fecha de alta, etc. Pero un campo donde pondremos la dirección donde reside.

Así, en el primer caso de manera local, tendremos:

C:\sistemas\clientes\basedato\empresa\ 

Para la red, tendremos algo así:

\\servidor\e\clientes\basedato\empresa\. 

(Noten que no estoy usando un mapeo de la unidad sino una dirección de red, que se puede sustituir por su IP si se desea).

Para internet:

\\5.61.46.25\e\clientes\basedato\empresa 

Aquí notaran la primera parte que es la dirección IP de nuestro VPN Hamachi. Estamos diciéndole que acceda al servidor que esta en esta red. El único requisito es que si vamos a usar esta aplicación con datos a través de internet, es necesario primero activar la Hamachi y que este conectado, de otra manera fallara. Y listo. Una vez configurada la ruta de los archivos, abrimos los formularios, reportes, consultas y estaremos trabajando directamente con el servidor.

Esta ruta la almacenamos en una variable publica que esta presente en toda la sesión de trabajo.

Una vez terminado, cerramos la aplicación, cerramos el Hamachi (si queremos) y listo.

La ventaja de esta técnica es que disponemos de toda la riqueza visual de VFP en nuestros formularios, controles ActiveX, etc., y el código sin grandes modificaciones ya que no nos preocupamos donde reside. He trabajado con esto desde hace meses con tablas que rondan los 200MB y solamente hay un pequeño tiempo de espera la primera vez que se conecta, pero una vez abiertos los archivos, la captura, edición, borrado de los registros es extremadamente rápida, los índices se actualizan sin problema, casi no notamos que estamos trabajando a través de internet. En lo personal me ha servido para actualizar ciertos datos desde casa en el trabajo. O hacer consultas a la base de datos.

Desventajas. Por cierto que las hay. Aunque Hamachi es gratuito, tiene un limite de 16 clientes en una misma red, es decir, solo 16 PC conectadas. Si su ancho de banda es buena, y su código es eficiente, no debe tener problemas para acceder a varios clientes al mismo tiempo, (yo lo he usado con hasta 4 clientes a la vez). El cliente tiene que tener la aplicación compilada y sus runtimes.

A su favor, es que no modificamos gran cosa en el código, mas que la ruta de los archivos, no tardamos mas de 10 min en instalar y configurar Hamachi... y es gratuito... además, no solamente nos servirá para esto, ya que al tener la VPN, podremos hacer muchas mas cosas como control remoto a distancia (con VNC por ejemplo), mejor que un ftp y mas rápido, conexiones seguras y algunas futuras aplicaciones.

Por supuesto para conexiones mas robustas y con mayor cantidad de clientes en la conexión, habrá que usar otras herramientas, pero si desean hacer una conexión rápida, barata, sencilla, fácil de administrar, creo que esta opción es bastante recomendable.

Que estén bien.

Sergio Hugo Sanchez

12 de octubre de 2017

Como crear texto como un archivo de imagen con GdiPlusX

Artículo original: How to create text as image file with GdiPlusX
http://weblogs.foxite.com/vfpimaging/archive/2007/11/24/5428.aspx
Autor: Cesar Ch.
Traducido por: Luis María Guayán


Más que una vez que he visto a la gente pedir como crear imágenes que contengan algún texto. El ejemplo de abajo es realmente muy simple.

  • Crear una fuente
  • Medir el espacio que el texto necesitará
  • Crear una imagen con el tamaño necesario
  • Dibujar el texto
  • Guardar en el disco

IMPORTANTANTE

Para ejecutar se requiere VFP9 y GdiPlusX.

Por favor, asegúrese de que tiene la última versión, porque VFPPaint utiliza algunas de las funciones que se han añadido recientemente.

https://github.com/VFPX/GDIPlusX

DO LOCFILE("System.prg")

WITH _SCREEN.System.Drawing
   LOCAL lcText
   lcText = "GdiPlusX is Cool !!!"

   * Crear una fuante
   LOCAL loFont as xfcFont
   loFont = _screen.system.Drawing.Font.New("Verdana", 32, .FontStyle.BoldItalic)

   LOCAL loTmpBmp as xfcBitmap
   loTmpBmp = .Bitmap.New(1,1)

   * Recuperar el objeto gráfico
   LOCAL loTmpGfx AS xfcGraphics
   loTmpGfx = .Graphics.FromImage(loTmpBmp)

   * Medir la cadena
   * tomar el tamaño necesario para el texto
   LOCAL loSize as xfcSize
   loSize = loTmpGfx.MeasureString(lcText, loFont)

   LOCAL loNewBmp as xfcBitmap
   loNewBmp = .Bitmap.New(loSize.Ceiling)

   LOCAL loNewGfx as xfcGraphics
   loNewGfx = .Graphics.FromImage(loNewBmp)

   * Hacer el fondo amarillo
   loNewGfx.Clear(.Color.Yellow)

   * Crear un pincel sólido
   LOCAL loBrush as xfcSolidBrush
   loBrush = .SolidBrush.New(.Color.FromRGB(255,0,0)) && Rojo

   * Crear un objeto StringFormat para dibujar el texto centrado en la imagen
   LOCAL loStringFmt as xfcStringFormat
   loStringFmt = .StringFormat.New()
   loStringFmt.Alignment = .StringAlignment.Center

   * Crear un ractángulo con las medidas del Bitmap
   LOCAL loRect as xfcRectangleF
   loRect = loNewBmp.GetBounds()

   * Dibujar la cadena
   loNewGfx.DrawString(lcText, loFont, loBrush, loRect, loStringFmt)

   * Finalmente guardar la imagen
   loNewBmp.Save("c:\MyText.Png", .Imaging.ImageFormat.Png)

   * Mostrar la imagen
   RUN /N Explorer.exe c:\Mytext.Png
ENDWITH


7 de octubre de 2017

Multiselección en un Grid

Esta la saque de http://www.tek-tips.com/faqs.cfm?fid=433. Aquí la pongo traducida:

¿Cómo agregar la funcionalidad de multiselección en un Grid?
faq184-433
Posted: 29 Jan 01 (Edited 30 Sep 06)

Si desean agregar la funcionalidad multiselección a un Grid para que emule la del explorador de Windows, intenten lo siguiente:

Subclaseen un Grid en una librería de clases y agregen 4 nuevas propiedades:

  • lMultiSelect con valor .F.
  • nActiveRow con valor 0 (cero)
  • nLastRow con valor 0 (cero)
  • nRecs2Change con valor 0 (cero)

Y un nuevo método:

  • mSelectRecords()

Asegurense de que RecordSource del Grid no este indexado, lo crean con una sentencia SELECT-SQL. El RecordSource necesita un campo logico adicional, "Selected".

En el evento AfterRowColChange() pongan:

THIS.mSelectRecords()

En el método mSelectRecords() del Grid pongan:

LOCAL lcSelected,lcRecordSource

#DEFINE VK_lSHIFT 0x10 && Relocate to a header file
#DEFINE VK_lCONTROL 0x11 && Relocate to a header file

DECLARE INTEGER GetKeyState IN WIN32API INTEGER && Relocate to where WinAPI calls are declared

WITH THIS
  .nActiveRow = .ACTIVEROW && Assign value to class property
  lcSelected = .RECORDSOURCE + [.selected] && Assign value to local variable
  lcRecordSource = .RECORDSOURCE && Assign value to local variable

  DO CASE
    CASE GetKeyState(VK_lSHIFT)    < 0    ;
        OR GetKeyState(VK_lSHIFT) > 1 && Check for shift key press

      DO CASE
        CASE .nLastRow > .nActiveRow && Last recd below current recd in grid

          .nRecs2Change = .nLastRow - .nActiveRow && Calculate no of recds to change

          REPLACE (lcSelected) WITH .T. IN (lcRecordSource) && Replace current recd
          FOR i = 1 TO .nRecs2Change
            REPLACE (lcSelected) WITH .T. IN (lcRecordSource)
            SKIP IN (lcRecordSource)
          ENDF

        CASE .nLastRow < .nActiveRow && Last recd above current recd in grid

          .nRecs2Change = .nActiveRow - .nLastRow && Calculate no of recds to change
          REPLACE  (lcSelected) WITH .T. IN (lcRecordSource) && Replace current recd

          GO .nLastRow IN (lcRecordSource) && Goto the last recd
          FOR i = 1 TO .nRecs2Change
            REPLACE (lcSelected) WITH .T. IN (lcRecordSource)
            SKIP IN (lcRecordSource)
          ENDF
      ENDC

      .lMultiSelect = .T.

    CASE GetKeyState(VK_lCONTROL) < 0 ;
        OR GetKeyState(VK_lCONTROL) > 1 && Check for control key press

      REPLACE (lcSelected) WITH .T. IN (lcRecordSource)

      .lMultiSelect = .T.

    OTHERWISE && Neither shift or ctrl pressed
      DO CASE
        CASE .lMultiSelect
          REPLACE (lcSelected) WITH .F. ;
            ALL IN (lcRecordSource) && Update all recds
        CASE .nLastRow    # 0
          TRY
            GO .nLastRow IN (lcRecordSource)
          CATCH
            GO BOTTOM IN (lcRecordSource)
          ENDTRY
          REPLACE (lcSelected) WITH .F. IN (lcRecordSource)
      ENDCASE

      GO .nActiveRow IN (lcRecordSource) && Change new value
      REPLACE (lcSelected) WITH .T. IN (lcRecordSource)

      .lMultiSelect = .F.
  ENDC

  IF RECCOUNT(lcRecordSource) > 0
    DO CASE && Set colours according to OS
      CASE UPPER(OS(1)) = [WINDOWS 5.00] && Win 2K
        .SETALL([DynamicBackColor], ;
          "IIF(&lcSelected, RGB(10,36,106), RGB(255,255,255))", ;
          [Column])
      CASE UPPER(OS(1)) = [WINDOWS 5.01] && Win XP
        .SETALL([DynamicBackColor], ;
          "IIF(&lcSelected, RGB(49,106,197), RGB(255,255,255))", ;
          [Column])
    ENDCASE

    .SETALL([DynamicForeColor], ; && All OS
      "IIF(&lcSelected, RGB(255,255,255), RGB(0,0,0))", ;
      [Column])

    .nLastRow = .nActiveRow && Mark current row for next time through
  ENDIF
ENDWITH

Seran seleccionados solos registros que se cliqueen con Mayusculas o Control. Usando los cursores direccionales no seleccionara registros.

Programaticamente pueden determinar si hay selecciones multiples de registros con:

IF THISFORM.grid1.lMultiSelect
  *!* Code
ENDIF

Otra solucion mas simple es agregar un campo numerico al cursor que esta en el RecordSource del Grid. Por ejemplo mselec n(5).

En el Init del Form poner:

THISFORMSET.frm_enviaryrecibir.grd_recibidos.SETALL("dynamicbackcolor", ;
  "IIF(mselec = 1,RGB(49,106,197), RGB(255,255,255))", "Column")
THISFORMSET.frm_enviaryrecibir.grd_recibidos.SETALL("dynamicforecolor", ;
  "IIF(mselec = 1,RGB(255,255,255), RGB(0,0,0))", "Column")

Y en el Click del Grid:

LOCAL lcSelected,lcRecordSource

#DEFINE VK_lSHIFT 0x10 && Relocate to a header file
#DEFINE VK_lCONTROL 0x11 && Relocate to a header file

DECLARE INTEGER GetKeyState IN WIN32API INTEGER && Relocate to where WinAPI calls are declared
DO CASE
  CASE GetKeyState(VK_lSHIFT) < 0 OR GetKeyState(VK_lSHIFT) > 1 && Check for shift key press
    IF mselec = 0
      REPLACE mselec WITH 1
    ELSE
      REPLACE mselec WITH 0
    ENDIF

  CASE GetKeyState(VK_lCONTROL) < 0 OR GetKeyState(VK_lCONTROL) > 1 && Check for control key press
    IF mselec = 0
      REPLACE mselec WITH 1
    ELSE
      REPLACE mselec WITH 0
    ENDIF
ENDCASE

con esto cada vez que hagan Click con Mayúscula o Control presionado van a seleccionar los registros y el Grid los pinta.

Para poder hacer algo con la selección hacen:

SELEC MICURSOR
SCAN FOR mselec = 1
  *! mi codigo para la seleccion
ENDSCAN

Suerte!!!!

Carlos Caremi

4 de octubre de 2017

Regenerar Indices de las Tablas de un DBC

Quizás no sea la gran cosa, pero, a todos nos pasa que tenemos que regenerar indices, y reindexar nuestras tablas... etc etc etc.

En la empresa donde trabajo usamos este proceso cuando las sucursales reciben los DBF en las transferencias de datos, entonces los CDX están en las sucursales y solo enviamos los DBF (en un archivo comprimido, gracias a PortalFox).

Necesitamos abrir los DBF en forma exclusiva. Y este código solo tiene en cuenta indice primarios y regulares, se puede retocar para quienes necesiten.

Bueno, vamos al código... 'databaseconteiner.DBC' es el nombre del Contenedor de las Tablas...

SELECT * FROM databaseconteiner.DBC ;
  WHERE OBJECTTYPE = 'Table' ;
  INTO CURSOR TABLAS
*
SELECT TABLAS
SCAN
  TABLA1 = TABLAS.OBJECTNAME
  USE &TABLA1 IN 0 EXCLUSIVE
  SELECT &TABLA1
  WAIT WINDOWS 'Tabla ' + ALLTRIM(TABLA1) + ' (' + TRANSFORM(RECNO('TABLAS')) + ;
    '/' + TRANSFORM(RECCOUNT('TABLAS')) + ')' NOWAIT
  FOR I=1 TO TAGCOUNT()
    IF !EMPTY(TAG(I))
      INDICE = SYS(14,I)
      NOMBRE = TAG(I)
      PRINCI = PRIMARY(I)
      IF PRINCI
        ALTER TABLE &TABLA1 DROP PRIMARY KEY
        ALTER TABLE &TABLA1 ADD PRIMARY KEY &INDICE TAG &NOMBRE
      ELSE
        INDEX ON &INDICE TAG &NOMBRE ADDITIVE
      ENDIF
    ELSE
      REINDEX
      EXIT
    ENDIF
    REINDEX
  ENDFOR
  PACK
ENDSCAN

Guillermo Gastón Giménez

1 de octubre de 2017

El alcance de CREATEOBJECT

Artículo original: The scope of CREATEOBJECT
http://www.foxpert.com/KnowlBits_200701_3.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York


En ocasiones, uno llega a entender que algunas cosas son absolutamente desaprovechadas. Una de ellas es el alcance de CREATEOBJECT(). La primera vez que usted instancia un objeto desde una clase, Visual FoxPro crea una plantilla del objeto con todas los valores de las propiedades definidas en la clase. Si la definición de clases contiene una expresión para alguna de las propiedades, Visual FoxPro la evalúa en ese momento. Para futuras instancias, Visual FoxPro utiliza el valor de la expresión evaluada anteriormente.

Ahora, la parte desaprovechada de este artículo: Todas las propiedades de clases son evaluadas en el ámbito del procedimiento o método que instancia el objeto. Esto significa, que en las expresiones de propiedades de clases puede acceder a todas las variables locales en ese procedimiento:

Local lcVar, loRef
lcVar = "Hi"
loRef = CreateObject("Test")
? loRef.cTest

Define Class Test as Custom
 cTest = m.lcVar
EndDefine