Thursday, May 3, 2018

Enhanced ImageCapture (an Imaging Capturing Tool)


I am currently working on a new project where I needed to capture images again for the Sellers to be used on their ID and on forms, so I fished out ImageCapture.prg which Gelson L. Bremm shared to us:  http://weblogs.foxite.com/vfpimaging/2006/03/20/capture-screen-portions/

But this time I decided to attempt to enhance it for my needs so I changed some approaches and used GDIPlus-X for the saving of the image instead of the original approach he has implemented.  Doing so, I was able to cut around 60% of the original codes too.   Check this for GDIPlus-X: https://github.com/VFPX/GDIPlusX in case you needed to download it.

Likewise incorporated here is a simple trick on form nudging capability so you can use the keyboard arrows to position the capture form which is easier than purely using mouse.  Also, when you adjust the form width and height, there is no need for you to worry adjusting the capture coordinates as well as that will also auto-adjust based on both form height and width set.

Here is the enhanced version of that:

* Enhanced version of ImageCapture.prg (May 3, 2018)
* Original Developer:  German L. Bremm
* http://weblogs.foxite.com/vfpimaging/2006/03/20/capture-screen-portions/

Lparameters toObject, cFileOutput
Public gToObject, gcFileOutput
gToObject = m.toObject
gcFileOutput = m.cFileOutput

Public oCapturaImg
oCapturaImg = Createobject("ImgCapture")
oCapturaImg.Show()

Define Class ImgCapture As Form
      Name = "ImgCapture"
      Height = 332
      Width = 300
      AutoCenter = .T.
      Caption = "Position over the Image you want to Capture"
      MaxButton = .F.
      MinButton = .F.
      AlwaysOnTop = .T.
      ShowWindow = 2
      BorderStyle = 0
      BackColor = Rgb(60,60,60)
      _nBorder = Sysmetric(10)
      _nTitleBarHeight = Sysmetric(9)
      _nFormWidth = 0
      _nFormHeight = 0

      Add Object cmdSnap As CommandButton With;
            Height = 28,;
            Caption = '\<Grab Image',;
            Width = 142,;
            Left = 5,;
            FontName = 'Arial',;
            FontSize = 14,;
            FontBold = .T.

      Procedure Init
            Declare Integer CombineRgn In "GDI32" Integer hDestRgn, Integer hRgn1, Integer hRgn2, Integer nMode
            Declare Integer CreateRectRgn In "GDI32" Integer X1, Integer Y1, Integer X2, Integer Y2
            Declare Integer SetWindowRgn In "user32" Integer HWnd, Integer hRgn, Integer nRedraw
            This.Resize()
      Endproc

      Procedure Resize
            With This
                  .cmdSnap.Left = .Width-.cmdSnap.Width-4
                  .cmdSnap.Top = .Height-.cmdSnap.Height-5
                  .cmdSnap.Tag = Allt(Str(.cmdSnap.Left))
                  .SetTransparent()
            Endwith
      Endproc

      Procedure SetTransparent
            Local lnInnerRgn, lnOuterRgn, lnRgnHandle
            With This
                  ._nFormWidth = .Width + (._nBorder * 2)
                  ._nFormHeight = .Height + ._nTitleBarHeight + ._nBorder
                  lnOuterRgn = CreateRectRgn(2, 2, ._nFormWidth+2, ._nFormHeight+2)
                  lnInnerRgn = CreateRectRgn(._nBorder+6,._nTitleBarHeight+8,._nFormWidth-._nBorder-4,._nFormHeight-._nBorder-34)
                  lnRgnHandle= CreateRectRgn(0, 0, 0, 0)
                  CombineRgn(m.lnRgnHandle, m.lnOuterRgn, m.lnInnerRgn, 4) 
                  SetWindowRgn(.HWnd , m.lnRgnHandle, .T.)
                  .BorderStyle = 1
            Endwith
      Endproc

      Procedure CopyToFile
            Do Locfile("system.app")
            Local locapturebmp As xfcbitmap, lcImage
            lcImage = m.gcFileOutput
            If Empty(m.lcImage)
                  lcImage = Getpict("png")
            Endif
            Clear Resources
            With _Screen.System.drawing
                  * Coordinates are X, Y, Width and Height
                  locapturebmp = .Bitmap.fromscreen(Thisform.HWnd,;
                        thisform._nBorder+6,;
                        thisform._nTitleBarHeight+8,;
                        thisform._nFormWidth-Thisform._nBorder-10,;
                        thisform._nFormHeight-Thisform._nBorder-Thisform.cmdSnap.Height-36,;
                        .F.)
                  locapturebmp.Save(m.lcImage, .imaging.imageformat.png,.imaging.imageformat.png)
            Endwith
            * Update target Image object
            gToObject.Picture = m.lcImage
            CLEAR RESOURCES m.lcImage
      Endproc

      Procedure KeyPress
            Lparameters nKeyCode, nShiftAltCtrl
            * Use Keyboard arrows to nudge frame
            Do Case
            Case m.nKeyCode = 24  && move down
                  Thisform.Top = Thisform.Top + 1
            Case m.nKeyCode = 5  && move up
                  Thisform.Top = Thisform.Top - 1
            Case m.nKeyCode = 19  && move left
                  Thisform.Left = Thisform.Left - 1
            Case m.nKeyCode = 4  && move right
                  Thisform.Left = Thisform.Left + 1
            Endcase
      Endproc

      Procedure cmdSnap.Click
            This.BackColor = Rgb(255,0,0)
            This.Caption = 'Capturing...'
            Thisform.CopyToFile()
            Thisform.Release()
      Endproc

      Procedure Destroy
            oCapturaImg = Null
            gToObject = Null
            gcFileOutput = Null
            Release oCapturaImg
            Release gToObject
            Release gcFileOutput
            Release CombineRgn
            Release CreateRectRgn
            Release SetWindowRgn
      Endproc
Enddefine

If you notice, I am passing two parameters.  First parameter is the image object to show the result of the capturing and the second one is the fullpath and name of the file to be saved.  Calling it is like this:

Do Form snagface With thisform.Image1, csrGrid.sellerid

So I can call that from anywhere on my form and immediately after grabbing an image, it gets displayed on an Image object.  And here is how it looks like (using my favorite cat as the model):




Darn, I really love that cat's expression.  Always makes me smile. :)

Special thanks to both Gelson L. Bremm and Cesar Chalom for this and other tools Cesar has shared to us.

Tuesday, May 1, 2018

ssUltimate May 1, 2018 release



DtPickerx. DtPeriod

Per request of one subscriber (Patrick Bibro)

1.  Added _ShowNotes property. When this is set to .F., the adding of notes will be disabled as well as the colored shapes below the calendar will disappear

2.  Added capability to change the time portion by typing on the textbox itself when nLevel is 3.  Now aside from capability to scroll the time, you can type direct on the textbox and change time without the need to show dropdown calendar section.



ExcelExport 

1.  Fixed bug on lower versions of excel on Header Styles (works okay on Excel 2016 64-bit but have not checked on lower versions, as notified to me by Ugur Yilmaz) 

2.  Adjusted numeric formatting and decimal precision to a better approach with the help of our friend Ugur Yilmaz.

3.  Added a new property _isNegative:
  - .F., default, negative numbers will be enclosed with open and close parenthesis, e.g., (900.00)

  - .T., negative will be shown instead, e.g., -900.00

4.  Added _RedNegative.  Default is .T. so negative values will be colored in red.  Set it to .F. if you do not like this

5.  MK Sharma requested sub-totalling capability so these properties:

                a.  _SubTotalGroup = is the column number where you want for sub-total to be based on (grouping by).  E.g., 1
                b.  _SubTotalCols = is the columns where sub-total will be performed with comma as delimiter, e.g., 2,4 
                c.  _SubTotalForeColor = is the forecolor of the sub-totals
                d.  _SubTotalBackColor = is the backcolor of the sub-Totals

Special thanks to our friend Vilhem-Ion Praisach as I adopted his approach on tracing sub-totals via Formula value.

                Note that _subtotalgroup and _subtotalcols should not be empty.  If any one of those is empty, then sub-totalling will be ignored as sub-totals need both.

For a lack of better sample (and I have no time to prepare one), I just tested this to a borrower's statement on one of my apps; just so you can see about the sub-total and the new properties for negative values



Cheers!


Wednesday, April 25, 2018

ExcelExport New Enhancements

1.  New property named Automation.  

If you set it up to .T., it will seed excel via pure automation on a cell per cell basis.  This is so we do not need to perform CAST() anymore on memo fields.  It will get its contents including CRLF (paragraphing), if any. 

If you set it up to .F. (default), it will perform the legacy way which is via EXPORT TO.  Here, you have to perform CAST() on memo fields like this:

Select CAST(CHRTRAN(MyMemo,CHR(13),'') as C(240)) as MyMemo

Otherwise memo fields won't be recognized by Excel.  One disadvantage of this legacy approach is the memo fields will be of contiguous nature as CRLF will be forced removed.  Also, Excel truncates the date beyond 255,

2.  Fixed bug on fields of currency format being ignored on decimal places.  Now it will also show the currency symbol.

3.  If it detects that Excel supports xlsx format, then its final output will be of xlsx nature

Here is the output when Automation is set to .T.



Cheers!

Wednesday, January 24, 2018

GridX Class

Imagine yourself designing a grid and saying to yourself "it looks awesome, they will definitely love this!", then installing it on your clients' units then instead of admiration receiving complaints from some users such as "my eyes are weak, can't you make the font bigger?" or "the colors are hurting my eyes" or "can you change the order of the columns please? I want this one to come first, then this one, then...."; and some more airing of dissatisfaction.  Well, either you will go back to the drawing board and change the appearances of your grids and recompile to satisfy them or you will argue back saying "that is the way it is, live with it!".  And either one of those will dissatisfy either you or your client.

Well worry no more, here now is my latest class on ssUltimate library that deals with Grid, i.e., GridX.  This combines all the power of all my other classes for grid object (AnchorSizer, AnchorX, GridSort, GridLock, and GridSortLock) into this one class, and more.

With this class now, you are giving your users the power to manipulate further grid on runtime based on their taste.  This class retains their last selections that whatever have been set when they exit the form, the grid will be loaded back to the last arrangement and look; just the way they left it.  And the good thing is, customization is per workstation.  Different users, different tastes.

So when they started complaining, you can casually tell them "Oh, you can adjust the appearance based on your taste", with a smile.



Wednesday, January 3, 2018

Google Calendar on VFP Form

Well, this comes from a request inside Foxite on how we can embed Google Calendar on form.  Being foxy, I originally pointed to the late Guillermo Carrero's FoxScheduler instead:  
https://sites.google.com/site/foxscheduler/

However, while it looks okay, there is an exchange of thoughts about the advantage of using Google Calendar instead of that which is the ability to share data not only within our app but  also to our personal computers and mobile devices.  That is a valid point but the thing I dislike is in order to do that, is to rely on Google's own APIs as they may change those now and then; for the betterment of google products.

And so with that in mind, and taking into consideration the argument about that sharing capability, here instead is my preferred way.  Not using google API but to embed Google Calendar via Internet Explorer automation within our form:



Declare Integer GetWindowLong In User32 Integer HWnd, Integer nIndex
Declare Integer SetWindowLong In user32 Integer HWnd,;
      INTEGER nIndex, Integer dwNewLong
Declare Integer SetWindowPos In user32;
      INTEGER HWnd,;
      INTEGER hWndInsertAfter,;
      INTEGER x,;
      INTEGER Y,;
      INTEGER cx,;
      INTEGER cy,;
      INTEGER uFlags
Declare Integer SetParent In user32;
      INTEGER hWndChild,;
      INTEGER hWndNewParent

loTest = Createobject("Form1")
loTest.Show(1)
Read Events

Define Class Form1 As Form
      Caption = 'Google Calendar on Form'
      AutoCenter = .T.
      Height = 700
      Width = 900
      ShowWindow = 2
      oIE = .F.

      Procedure Init
            Local lcURL, lnStyle, loHWnd, loIE As internetexplorer.Application
            lcURL = 'https://calendar.google.com/calendar/r'

            loIE = Createobject("InternetExplorer.Application")
            Thisform.oIE = m.loIE
            With loIE
                  .Visible = .F.
                  .Silent = .T.
                  .FullScreen=.T.

                  .Navigate2(m.lcURL)
                  .ClientToWindow(.Width,.Height)

                  Do While .ReadyState <> 4
                  Enddo

                  loHWnd = .HWnd
                  lnStyle = GetWindowLong(m.loHWnd, -6)
                  SetWindowLong(m.loHWnd, -12, Bitxor(lnStyle, 0x00400000))
                  SetParent(m.loHWnd,Thisform.HWnd)
                  This._Resize()
                  .Visible = .T.
            Endwith
            Bindevent(This,'_Resize',This,'Resize')
      Endproc

      Procedure _Resize
            With Thisform
                  SetWindowPos(.oIE.HWnd, 1, .Left, .Top, .Width, .Height,0x0001)
            Endwith
      Endproc

      Procedure Destroy
            Clear Events
            This.oIE.Quit
            This.oIE = Null
      Endproc

      Procedure KeyPress
            Lparameters nKeyCode, nShiftAltCtrl
            If m.nKeyCode = 27
                  Thisform.Release
            Endif
      Endproc

Enddefine

If you haven't logged in at your google account yet when you first run this, it will require you to. After that, IE will remember it and the next runs will go straight to Google Calendar.

Tuesday, January 2, 2018

Alter Table Trick

Okay so it is now 2018 and I want to greet every one a more prosperous new year than before.  I am now back at work and immediately I got a request from a Colleague to create a small app that can import data from an excel generated by another app, and later use my ExcelPivot class to provide the needed reports.  Instead of them doing those manually.  So the raw data from excel looks like this:


Then of course the easiest way for me to do that is to import the file and then later adjust fields:

Import From (m.lcFile) Type Xl5

That resulted to 8 columns bearing the field names a,b,c,d,e,f,g and h.  So after that,  we need to change the field names and types to proper ones like this:

Alter Table (m.lcDBF) Alter Column a c(8)
Alter Table (m.lcDBF) Rename Column a To po
Alter Table (m.lcDBF) Rename Column b To supplier
Alter Table (m.lcDBF) Alter Column c D
Alter Table (m.lcDBF) Rename Column c To Date
Alter Table (m.lcDBF) Rename Column d To Status
Alter Table (m.lcDBF) Rename Column e To workorder
Alter Table (m.lcDBF) Rename Column F To location
Alter Table (m.lcDBF) Alter Column g N(12,2)
Alter Table (m.lcDBF) Rename Column g To amount
Alter Table (m.lcDBF) Alter Column h N(12,2)
Alter Table (m.lcDBF) Rename Column h To UpdAmt

As you can see above, some involves ALTER COLUMN which changes the type of that column to a target one and all involves RENAME COLUMN to give those fields proper names.