Quantcast
Channel: Sandstorm's Blog (Home of ssUltimate Library)
Viewing all 160 articles
Browse latest View live

SpinnerX (New), FaderX (Enhancement) & Others

$
0
0
I was creating this before but got busy with simultaneous projects.  But since I wanted to give some more feature for FaderX as I need it, then I decided to utilize this now instead of VFP's Native Spinner object. Right now this SpinnerX is very young that it has not much features yet but enough to be used as replacement for VFP Native Spinner class if you want something unique.  Its current properties follow:

- Appearance - Right now it has 3 different look as shown below:


- Value = Is the initial value fo the spinner
- ValueLow = Is the lowest value it can accept
- ValueHigh = Is the highest value it can accept
- Interval = Is the value to increase up or down

As with the native spinners, you can hover your mouse on it and use the mouse scroll to scroll up or down, or click on the buttons, or enter on the textbox.


FaderX Enhancements




- SpinnerX instead of Native Spinner object for better look and feel (uniqueness)

- Added _isHollow and _RGBHollow properties.   _isHollow requires a logical value of either .T. or .F. (default) meaning if you set this property to .T. and you put an RGB color on the _RGBHollow property, then objects on form that are colored like that will be totally transparent (see-through).  This actually can work better than TransparentX as you can control now the color that will be made transparent here.

- Hey, did you noticed the ControlBox is on the left and the Caption and Icon is on the right? Well I added _CtrlBoxLeft property here so you guys can make your forms look like Mac or Linux where those are on the left.    Changing its value to .T. will make the form's controlboxes on the left instead of the right.  Be informed though that this works only on top-level forms or those with Desktop = .T.

Other Enhancements in ssUltimate Libarary

I did made some more enhancements inside, plus minor bug fixes.  For instance, ExcelPivot I noticed sometimes do not follow the gap of 2 rows between the title and the column headers.  Fixed that.  I also had to change some themes and skins used by ButtonX, ContainerX and StickyX to avoid flicker due to my usage of some .PNG formats on some of the themes.  Replaced those themes with non-flickering images and actually with smoother and cooler ones.  Here is new Theme 4 which I decided to be flat for my end's need too:


Just a quick peek, here are the latest blue colors of 9 Themes:


Heck, why leave your forms looking like the old VFP forms when you can make it awesome with the help of ssUltimate Library?  http://sandstorm36.blogspot.com/2015/12/whats-on-ssultimate-library.html





CtrlBox New Features

$
0
0
Brief History

Originally CtrlBox is intended solely as a slave of my TitleBarX class when I decided to experiment with shapes for the controls there (minimize, maximize/restore, and close).  Later I decided to create it as a master of its own (while still being used by TitleBarX).  So now, it can be dragged and dropped onto a form and that will replace your form's native titlebar.

New Features

  1. Now it auto hides the form's native titlebar
  2. Now it auto repositions itself onto top-right corner
  3. New built-in titlebar of its own.  I added this today as sometimes I wanted to make the form movable but still using this class.  For this new titlebar to appear, you have to set _WithBar = .T. (default is .F. or no titlebar).  This is affected further by the following:
  • Caption - the caption to be shown on the titlebar


Properties affecting Caption follow:

  • FontName = default is Calibri
  • FontSize = default is 12
  • FontBold = default is .T.
  • ForeColor = default is RGB(255,255,255)
  • Alignment = default is 2 (center).  I know some of you will love to have a caption in the middle
  • BarHeight = Default is 20.  The height of the titlebar. This also affects the caption height 


Properties affecting Titlebar

  • BackColor = default is RGB(0,0,0)
  • BarHeight = Default is 20.  


Old Features

  • _NoMax = default is .F., set to .T. to hide maximize/restore control
  • _NoMin = default is .F.,  set to .T. to hide minimize control
Here is the class showing its own titlebar (_WithBar = .T.):


Cheers!

xBox via SQL Pass-Through and Stored Procedures

$
0
0
Since xBox was created while I am still working with pure VFP, the approach I did was designed to work only on VFP's native backend.  So to have something like this:



You have to put in its InteractiveChange Event codes like this:

Local lcSQL
TEXT TO lcSQL NOSHOW 
Select vesselname,vesselid from Vessels
      where [MySearch] $ vesselname 
      order By
      into Cursor curVessel NOFILTER
ENDTEXT
This._searchgrid(m.lcSQL,'curVessel.VesselName')

Where [MySearch] will be automatically replaced by xBox class with whatever you have already typed.  And curvessel.VesselName is the value it will return, the one that will be held by its .Value property.  There is secondary value it can return after that via .ExtraValue property but that is another topic.  If you are already a subscriber or user of ssUltimate's xBox, then you already know about this.

Lately, I decided to use MariaDB and so to make xBox working there, I showed the way here: http://sandstorm36.blogspot.com/2017/04/xbox-on-mysql-backend.html

But that requires two jumps as follows:

* Get data from MariaDB/MySQL
SQLExec(gomyapp.dbHandle,'Select vesselname,vesselid from Vessels order by vesselname','vessels')
* Use generated cursor on the class
Local lcSQL
TEXT TO lcSQL NOSHOW  
Select vesselname,vesselid from Vessels
      where [MySearch] $ vesselname 
      order By
      into Cursor curVessel NOFILTER
ENDTEXT
This._searchgrid(m.lcSQL,'curVessel.VesselName')

Or you can reverse the process limiting the number of records of the  cursor generated from the other backend using WHERE clause on the SPT query instead of the one needed by xBox class.  

Today, I added two new direct calls to the other backend which will not require a 2nd jump anymore.  Two new class methods, 1st is for SQL Pass-Through query approach and another for Stored Procedure approach.  Let us see how the call is made on those two:

via _SPT() Method.  This expects parameters as follows:
Lparameters ndbHandle, cSQL, cCursor, vValue, vExtraValue


Where:
ndbHandle - is the numeric handle for the database connection
cSQL - is your SQL SELECT statement for the SPT
cCursor - is the cursor that will be generated on VFP
vValue = is the primary value you can get from the class
vExtraValue - is the secondary value of the class

The standard way:

PRIVATE lcSearch
lcSearch = '%'+this._searchkeys+'%'
This._SPT(gomyapp.dbHandle,;
      'Select vesselname, vesselid from vessels WHERE inactive = 0 AND vesselname LIKE ?m.lcSearch',;
      'curVessel','curVessel.vesselname')

Alternative way (which I prefer):

This._SPT(gomyapp.dbHandle,;
      TEXTMERGE('Select vesselname, vesselid from vessels WHERE inactive = 0 AND vesselname LIKE "%<<ALLTRIM(This._SearchKeys)>>%"'),;
      'curVessel','curVessel.vesselname')

And that is how xBox can directly interact with your other backend via SPT.  Now, if you have a stored procedure, then the call is more simple.  

via _SPCALL() Method.  This expects parameters as follows:
Lparameters ndbHandle, cStoredProc, cCursor, vValue, vExtraValue

Where:
ndbHandle - is the numeric handle for the database connection
cStoredProc - is the name of your stored procedure
cCursor - is the cursor that will be generated on VFP
vValue = is the primary value you can get from the class
vExtraValue - is the secondary value of the class

Let us say you have a Stored Procedure named GetVessel with a parameter named SearchKeys like this:

CREATE PROCEDURE `getvessel`(
      IN `SearchKeys` VARCHAR(50)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
Select vesselname, vesselid from vessels WHERE inactive = 0 AND vesselname LIKE SearchKeys;
END

Then you can take advantage of xBox' new _SPCALL() method instead of its _SPT() method.  The call is much simpler like this:

this._SPCALL(gomyapp.dbHandle,'getvessel','curvessel','curvessel.Vesselname')


Have you noticed? There is no mention of the value of the class or what has been typed so far?  Well, the class takes care already of that.  All you need to do is pass the name of the handle, the stored procedure name, the resultant cursor, and its return value (plus secondary value if you want to use that too).

And there you have it.  Working with other backend with xBox is now faster and simpler than ever.!!! Cheers!

SwitchX/OptionSwitchX Bold New Themes

$
0
0
Since I adjusted the library to make it more flexible, I decided to replace some themes with bolder new look to spice up further our app.  And here are the latest themes for both classes


This and other more classes are part of ssUltimate Library.  Cheers!

ExcelExport

$
0
0
This is the counterpart of ssToExcel2 of ssClasses.  




Unlike ExcelPivot, this one simply transfers your cursor/table into excel with added formats, titles, etc.  An example is a ledger as shown at the bottom hereof

New properties


  • _PaperSize (1 = letter (Default), 3 = tabloid, 4 = ledger, 5= legal, 7 = executive, 8 = A3, 9 = A4, 11 = A5, 66 = A2, 119 = 11x17 size)

  • _MarginLeft, _MarginRight, _MarginTop, _MarginBottom (all Defaults to 1 inch)

  • _WithFooter (Default is .F.)

  • _Orientation (1 = Portrait (Default), 2 = Landscape)

  • _scaling (For zooming of sheet, default is 100)

  • _isProper = to turn the field names into PROPER() (Default or .T.) or UPPER() (.F.)





Miscellanous:


  • Added Progressbar (ProgBarX class)

  • Replaced Default body font from Tahoma to Calibri

  • Cleaned codes, speed up automation a bit






Shown below is what I meant by exporting cursor result to excel using this class on a spreadsheet with added features. Please note that with this class, it us entirely possible to do that even when you have zero automation knowledge.













Available only to ssUltimate subscribers.




APPEND FROM fails on Memo (VFP)

$
0
0
It appears that APPEND FROM command has an undocumented behaviour which is it cannot transfer its values into a Memo Field.  So in cases where you want to append data from say a csv, text or other types and your destination table has a memo field, then forget about this command.

Inside foxite forum, this exact problem causes a member to search for a way to achieve getting the values of a column of a CSV file that has more than 256 characters (definitely a candidate for a memo field type).  And APPEND FROM cannot simply just do that.

Having said, I showed him an alternative way to transfer data from a CSV file to our table with memo fields via excel automation.  It is quite simple really to do that.  Here is a small snippet for that:


Create Cursor junk (familyid i, Name c(60), Description M)

Local lcFile, loExcel As Excel.Application, lnLastRow
lcFile = Getfile("csv")
If !Empty(m.lcFile)
      loExcel = Createobject("excel.application")
      With loExcel
            .Workbooks.Open(m.lcFile)
            For lnloop = 2 To .Cells(1, 1).CurrentRegion.Rows.Count
                  Insert Into junk (familyid, Name, Description) Values ;
                        (.Cells(m.lnloop,1).Value,.Cells(m.lnloop,2).Value,;
                        .Cells(m.lnloop,3).Value)
            Next
            .ActiveWorkBook.Close
            .Quit
      Endwith
      Go Top
      Browse Normal
Endif



Using his sample data, here is the result of said automation:




And that is it, an easy way to import data from a CSV file (or any other files that excel supports) into our tables including ones for Memo fields.  Cheers!


dtPickerX - December 2017 version

$
0
0
Okay, so I changed some things (again) inside, for the better.  And aside from listing what those are here, I decided to make this post as the official guide post for dtPickerX class too.  So what are these enhancements and what are the previous features which I may have not highlighted before?


Enhancements:

1. Better Year Spinner.  I have improved SpinnerX class so I can use it here and this, IMHO, gives a better look and faster reaction than the native Spinner object I have used on the earlier versions of this class.

2. Better Time Picker.  I modified ScrollTime class too to give it a better look and new attitude too, then used that for the time picking section here instead of the previous DropTime class.

3. Dates Color Explosion.  I added back the capability to put notes and holidays found in one older version, but this time in a much easier fashion plus with more color markings to choose from (7 colors, the 8th box is for HotKey Tooltip).  Also now, the initial set of holidays are all set to blue color (first color).  Nevertheless, users can choose to use any of the colors later for that but it would be better to leave the blue for holidays.

Why different colors?  Well, you may wish to use the colors to indicate the urgency of a note.  Say for those you need to remember such as birthdays of your loved ones, then you can set those to a strong color like pink.  So immediately when you see a date that is encased in a pink box, you know that that is a birthday occassion.  Or for your wedding anniversary, you may want to set that to red as when you forgot such occassion, you are definitely in trouble.


To mark dates, all you need to do is click on a target date then click on any of the color boxes.  That will in turn open the notes section where you can enter the notes. To remove a marking, just do the same but this time make sure the notes is empty, as in addition to the dates it also detects whether the note is empty or not.



Please be aware that the class ignores the year so all of those holidays and notes will be repeated each year.  Deleting old notes anyway is very easy, just empty the previous note.

4.  Notes Privacy.  Unlike with all previous versions of this class, this one no longer shares the holiday.dbf across the app.  Each user/machine now gets its own holiday.dbf to ensure that user's personal notes won't be shared across to others using the app. 

5.  Developer Color Preference

I got tired of the plain white look of the class so I decided to give subscribers the capability to now change several color of the class.  But for backward compatibility reasons, I kept the old colors as the default.  These new look is made possible by these new properties:

MoYrBackColor = as the name implies, it changes the backcolor of the months and year section

MoYrForeColor = changes the forecolor of months and year section

MoYrHighlight = controls the backcolor of the highlight for months.  Pardon the Yr there if that confuses you since it won't affect the spinner year.  But MoYr means the section for the months and year.

nCalShade = this is for the calendar face to give it a shade a bit different than the backcolor of the months and year (MoYrBackColor).  Default is -.8.  If you want it to be the same as the background color of months and year section, change this into 1.

Here is how it looks with my test color schemes on my end:

MoYrBackColor = RGB(96,124,159)
MoYrForeColor = RGB(255,255,255)
MoYrHighlight = RGB(0,45,89)
nCalShade = -0.8



Other Features:

For those who are not yet a subscriber of ssUltimate Library, be aware that this class has three (3) levels as follows:

  • nLevel = 1 (Month Picker)
  • nLevel = 2 (Date Picker)
  • nLevel = 3 (DateTime Picker)


Shown above is nLevel 2 and 3.  And here is how it is when set as plain Month Picker (nLevel = 1).  Notice the class only shows the month and year both on the class and the dropdown section.



Return Values

Okay, maybe even some subscribers are not aware that the class can return a lot of values aside from .Value property, so here are those:

Value = the date or datetime (depending on nLevel value)

SQLDate = is the date value fixed on YYYY/MM/DD format.  So if you are after using this class to perform an SPT for other backends such as SQLServer, MySQL, MariaDB or others that rely on the aforementioned format, use .SQLDate instead of .Value property; so you do not need to convert anymore into that format.

_BOM = Always gives the beginning of the selected month, e.g., 1/12/2017

_EOM = Always gives the end of the selected month, e.g., 31/12/2017

*Note: all return values follow your SET DATE settings, except that .SQLDate

_DateTime = always returns the datetime() value no matter what level it is in.  For nLevel 1 or 2, it will pick up the OS clock value for the time portion.  For nLevel = 3, just the same as .Value

_cTime = always return the time section of the class.  This is useful too if you are on nLevel = 3 and you want to dictate the class' initial time value.

nDay = will always return the numeric day portion of the class

nMonth = will always return the numeric month portion of the class

nYear = will always return the numeric year of the class

Limiting of the date scopes

There are two (2) properties built for that as follows:

ValueMax = here is where you can set the maximum date a user can choose or enter, either by manually encoding the date or by choosing the picker.  Leave this as .F. if you do not want to limit your user's picking option.  Using this is something like this:

this.ValueMax = date(2017,12,25)

And while the users can click on the dropdown section dates beyond 25, the returned value of those will only be still up to the ValueMax set.

ValueMin = well the opposite of the ValueMax.  This is the minimum date a user can pick, again for limiting date scopes.

Miscellaneous:

IdleTime = is the number of seconds the popup section will remain open until it detects the number of seconds the class remains idle for any keyboard or mouse movements. By default it is 50 seconds, adjust it if you want.

Language = Currently the class supports 8 Languages as follows:
            0 = Indonesian
            1 = English
            2 = Italian
            3 = Spanish
            4 = Turkish
            5 = Romanian
            6 = German
            7 = Czech

            8 = Slovak

HotKeys = This is to easily navigate within the class by means of keyboard.  Here are the navigation keys:

            +,=,Up Arrow = Next day
            -,Down Arrow = Previous day
            T,t = Today
            M,m = First day of month, then back 1 month
            H,h = Last day of month, then to end of next month
            Y,y = First day of year, then back one year

            R/r = Last day of year, then to end of next year

There you go!  I may have forgotten some more but as you can see, this class is full-packed with features which some subscribers may have not been even aware. So I hope this post will make them aware of all those features. Oh and yes, all of those features are also forwarded onto dtPeriod and CalendarX classes.  Cheers!



Alter Table Trick

$
0
0
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 ExcelPivotclass 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.

And that is how it is done.  Correcting fields to proper names and types. 

However, there is a simpler way to do that which I remembered to share here just now as I know this can come handy to you guys plus VFP help itself is not clear about this usage too.  Here is a counterpart of that, a single ALTER TABLE command that will perform all of those changes in one go.  Here goes:

ALTER Table (m.lcDBFALTER Column a c(8RENAME Column To po RENAME Column To supplier ALTER Column c D RENAME Column To Date RENAME Column To Status RENAME Column To workorder RENAME Column To location ALTER Column g N(12,2RENAME Column To amount ALTER Column h N(12,2RENAME Column To UpdAmt

And as you can see, all you need to do is put a space inbetween each alteration syntax until you  applied all.  But that is a bit confusing to read so better break those up to several lines like this (also adding extra space on ALTER so those will align better):

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

And there you go!  I hope this comes useful to you when the time comes!  Happy New Year!!!

Google Calendar on VFP Form

$
0
0
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.

GridX Class

$
0
0
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 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 runtimebased 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.





Features
- Retains Column positions, so your users can rearrange columns via drag and drop based on how they like it
- Retains Column Widths. So your users can adjust the widths of individual columns per their satisfaction
- Hide/Unhide Columns
- Retains the last order selection and which column was ordered
- Retains which column is locked or if lock is removed

Grid Appearance
- Allows your user to toggle RecordMark and retain it
- Allows your user to toggle Theme and retain it
- Allows your user to toggle Grid Lines and retain it
- Allows your user to toggle Scrollbars and retain it
- Allows your user to toggle Highlight and retain it
- Allows your user to change grid's FontName, FontSize, FontBold and FontItalic; and retain those
- Allows your user to change grid's BackColor & ForeColor, and retain those
- Allows your user to change grid's Highlight BackColor & ForeColor, and retain those
- Allows your user to change gridlines' color and retain it
- Allows your user to adjust Header Height, and retain it
- Allows your user to adjust Row Height and retain it

Miscellaneous
- Gives user the ability to issue AutoFit (of all columns, if button for that is hidden)on-the-ply
- Allows your user to reset it back the way you originally set it up during design time.

Anchoring
Logical Anchor property was set here. This is to either resize the inside columns of the grid or not.  By default I set this as .F..  This one is not available during runtime so is beyond user control.

Depending on the number of columns used on a grid, you may want to use this or not.  For instance, if you have more columns than the grid can display and you plan to use the horizontal scrollbar for user to see those, then you leave this as .F..  If you have only few and you wanted that when the form (and Grid if you set its Anchor property other than zero) gets resized, to resize all columns as well, then set Anchor property of this class to .T..

Mouse Clicks
- Left click on header to sort, click on the same header to toggle order
- Right-click on header for some popup options such as locking and hiding of columns and unlocking and showing those back
- Right-click on grid cells for the popup shown above

Popups' appearance are controlled by FontName and FontSize of the class.  Default is Calibri 10.

How to Use it?
- Drop the class on form
- Double-click on target grid and on its Init event, type this: thisform.gridx1._bindgrid(this)

You can additionally set the popup font via FontName and FontSize and as mentioned, if you want the columns to be resized, change Anchor property value; on the class itself.  The rest will be handled by the class itself.

One grid, one instance of the class.  The class will latch onto which grid so it can restore it back to its last appearance.

User Satisfaction
The goal of this class is to provide user satisfaction.  Some of our users may have poor eyesight so they may want bigger fonts.  Some may be color blind and this class will enable them to adjust our grids to something that is comfortable with their eyes.  Some simply just have their own taste on fonts and colors.

Future Enhancements
I may add some more features in the future.  Since this has everything the aforementioned classes have, then all my future works for grids will now only be done on this one. So if you are a new subscriber and hasn't used yet any of those now legacy classes, then just use this and forget about the others mentioned above.

Subscribe
If you haven't subscribed yet to ssUltimate library, I urge you to subscribe now before I suddenly increase subscription again to USD 50 (which I will do, I won't tell you when).  Subscription is just one time so if you got this at a lower price like my first subscribers (originally at USD 20),  I won't require anyone to send me the difference.  Cheers!

ExcelExport New Enhancements

$
0
0
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!

ssUltimate May 1, 2018 release

$
0
0


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!


Enhanced ImageCapture (an Imaging Capturing Tool)

$
0
0

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.prgwhich Gelson L. Bremmshared 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/GDIPlusXin 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 bothGelson L. Bremm and Cesar Chalom for this and other tools Cesar has shared to us.

Grabbing UNC path from a Mapped Drive

$
0
0
I am among those who stores only the fullpath() of the outside files into a field as I do not want to store the actual files inside the tables as that will make the tables huge as time goes by.

I am also not an admirer of a mapped drive as that will lead to a lot of problems later such as giving users easy access to the folder for the app, issue of sleeping mapped drives, or workstations using different mapped drive letters.

Anyway, this is so in case you have mapped drives in your workstations and you want to get a fullpath() of a file using GETFILE(), then you can always get the UNC path if file is coming from a network drive.  For instance, if you have a mapped drive Z:\ and you point to a file there, instead of getting Z:\myFile.jpg you will get \\192.168.1.1\YourFolder\myFile.jpg or something like that instead; where drive Z: will be replaced by the actual UNC path.

Here is a code for that:


Local lcFile
lcFile = GETUNC(Getfile())
Messagebox(m.lcFile)

********
Function GETUNC(cFile)
********
Local lcFullPath, lcDrive, lcUNC, lnSize
If !Empty(m.cFile)
      lcDrive = Justdrive(m.cFile)
      lcFullPath = Fullpath(m.cFile)
      If Drivetype(m.lcDrive) = 4
            lnSize = 600
            lcUNC = Space(lnSize)
            Declare Integer WNetGetConnection In WIN32API String @lcDrive, String @lcUNC, Integer @lnSize
            WNetGetConnection(@lcDrive, @lcUNC, @lnSize)
            lcFullPath = Strtran(m.lcFullPath,m.lcDrive,Left(m.lcUNC,Len(Alltrim(m.lcUNC))-1))
            Clear Dlls WNetGetConnection
      Endif
Endif
Return(m.lcFullPath)

What it will do is if you get a file that is stored on your local drive, then it will just show the normal fullpath(). If you get a file on a mapped drive, then it will replace the mapped drive with the actual UNC Path and combine that with the file to present you with a new fullpath using UNC path instead. The advantage of this is it won't be tied up to a mapped drive which may change from workstation to workstation.  Even if on one workstation, it is mapped to z:\ and on another to x:\, inside your app since it will be stored using the UNC path, it will always point to the proper location.

Cheers!

CtrlBox on Left Side

$
0
0
Since some are using Arabic/Urdu which deals with the right-to-left reading and data entry, then this trick might be useful to them. Which is to transpose too the position of the ControlBox of Titlebar.

This trick is really quite simple requiring only 3 lines of codes involving GetWindowLong and SetWindowLong.  I just added some codes to show how it looks like.  See if this can come handy to you:


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


Define Class form1 As Form
      AutoCenter = .T.
      Caption = 'ControlBox on Left side'
      ShowWindow = 2

      Add Object label1 As Label With ;
            top = 20,;
            left = 10,;
            FontSize = 16,;
            width = Thisform.Width -20,;
            height = Thisform.Height - 20,;
            WordWrap = .T.,;
            Caption = "This shows how to reverse the title bar's OBJECT positions such as "+;
            "controlbox, icon and labels while leaving the inside of the form on normal left to right positions"

      Procedure Load
            Declare Integer SetWindowLong In user32 Integer HWnd, Integer nIndex, Integer dwNewLong
            Declare Integer GetWindowLong In user32 Integer HWnd, Integer nIndex
            SetWindowLong(Thisform.HWnd, -20, Bitor(GetWindowLong(Thisform.HWnd, -16), 0x80000))
      Endproc

      Procedure Destroy
            Clear Events
      Endproc
Enddefine





Cheers!

Enumerating Path Links and Installed Apps

$
0
0
A friend requested for my assistance as they were recently hit by a ransomware so he wants to reformat all machines.  But before that, since there are around 60 machines to be reformatted, he wants to create a list of the path location of the shortcuts on its Desktop plus a list of all installed apps; so he can bring those back properly after reformatting and re-installation. 

Anyway, here is what I am able to come up with based on his request and desire, in a simplified way.  I will add this here in my blogs as this may be useful for others too.  You may adjust it further to suit your needs.

Getting paths of shortcut items (.lnk) as well as enumerating installed Apps

Local loShell, loReg, lcFolder, loFolder, lcUnit, lnLoop, loFile, lcFile, loLink, lcKey, lcApp

Create Cursor LinksNApps (xUnit c(20), xType c(4), Link c(40), xPath c(200))

* Get Links
lcFolder = Getenv("USERPROFILE")+'\Desktop\'
loShell = Createobject("Shell.Application")
loFolder = loShell.Namespace(m.lcFolder)
lcUnit = Getenv("COMPUTERNAME")
Set Default To (m.lcFolder)
For lnLoop = 1 To Adir(aLink,m.lcFolder+'*.lnk')
      lcLink = aLink[m.lnLoop,1]
      loFile = loFolder.ParseName(m.lcLink)
      loLink = loFile.GetLink
      Insert Into LinksNApps Values (m.lcUnit,'Link', m.lcLink, loLink.Path)
Endfor

* Get installed apps
#Define HKLM 0x80000002
Set Procedure To Locfile(Home(2)+"classes\registry.prg"
Dimension akeys(1)
loReg = Createobject('REGISTRY')
lcKey ='SOFTWARE\WOW6432Node'
loReg.OpenKey(lcKey,HKLM,.F.)
loReg.EnumKeys(@akeys)
For lnLoop = 1 To Alen(akeys)
      lcApp = akeys(1,m.lnLoop)
      If !Inlist(m.lcApp,'Classes','Clients','Policies','RegisteredApplications') And Left(m.lcApp,1) <> '{'
            Insert Into LinksNApps Values (m.lcUnit, 'App',akeys(1,m.lnLoop),'')
      Endif
Next

Go Top
Browse NORMAL




IE inside the Form

$
0
0
Unlike with placing internet explorer browser inside our form via Internet Explorer Shell, this is similar to my other blogs of opening the URL directly on a browser like Chrome and Firefox, capturing its window handle, adjusting window style and placing said browser inside of our form.  This time though, it is by using Internet Explorer as the Browser as we have more handles on it.  Not to mention that IE automatically comes with the OS unlike the other browsers which our clients may not choose to have.

So why am I suggesting to do it this way when we can do the same via Shell.Explorer?   Well, it is not quite the same actually.  Shell.Explorer for one adjusts the site to the basic design without CSS applied.  So the display you will see will not be entirely the same when you open it via a browser.  Sometimes also, JavaScript hampers its execution especially with the new OS now in place.  Now, opening the browser itself will ensure that it has the latest features, based on your browser version. 

The reason I created this entry is because there is a question inside Foxite forum that if we do it this way, then how to position IE inside our form at specific coordinates and  not as a full screen filling the entire form?   So I am showing here how we can achieve that, as a guide.  Improve it further based on your needs.  Codes follow:



Declare Integer GetWindowLong In User32 Integer HWnd, Integer nIndex
Declare Integer SetWindowLong In user32 Integer HWnd,;
      INTEGER nIndex, Integer dwNewLong
Declare Integer SetParent In user32;
      INTEGER hWndChild,;
      INTEGER hWndNewParent

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

Define Class Form1 As Form
      Caption = 'IE inside a VFP Form'
      AutoCenter = .T.
      Height = 500
      Width = 1000
      ShowWindow = 2
      oIE = .F.

      Procedure Init
            Local lcURL, lnStyle, loHWnd, loIE As internetexplorer.Application
            lcURL = 'https://www.mediwaresas.it/news'
            loIE = Createobject("InternetExplorer.Application")
            Thisform.oIE = m.loIE
            Wait Window 'Please wait, loading site...'Nowait
            With loIE
                  .Visible = .F.
                  .Silent = .T.
                  .AddressBar=.F.
                  .MenuBar=.F.
                  .Toolbar=.F.
                  .StatusBar=.F.
                  .Resizable=.F.
                  .Width = 500

                  * Load URL
                  .Navigate2(m.lcURL)
                  Do While .ReadyState <> 4
                  Enddo

                  loHWnd = .HWnd
                  lnStyle = GetWindowLong(m.loHWnd, -16)
                  SetWindowLong(m.loHWnd, -16, Bitxor(lnStyle, 0x00400000))
                  SetParent(m.loHWnd,Thisform.HWnd)
                  .ClientToWindow(.Width,.Height)
                  This._Resize()
                  .Visible = .T.
                  Wait Clear
            Endwith
            Bindevent(This,'Resize',This,'_Resize')
      Endproc

      Procedure _Resize
            With Thisform.oIE
                  .Left = Thisform.Width - 710
                  .Top = 100
                  .Height = Thisform.Height - 120
            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
 

Drag, Drop and Restrict

$
0
0
I have just read a problem inside foxite where when a user accidentally moves the mouse beyond the form boundaries, then drag and drop fails because the objects disappear on the areas beyond the form. 

The solution to that is to restrict the drag and drop movements within your form, or dimension within objects in your form.  Here are two samples showing how to achieve that:

Sample 1:

* Restricting drag and drop within the form

Local oForm As Form
oForm = Createobject('TestForm')
oForm.Show(1)
Return

Define Class TestForm As Form
      AutoCenter = .T.
      Width = 900
      Height = 440
      Caption = 'Drag, Drop & Restrict Inside Form'
      Add Object container1 As Mycontainer With Top = 30, Left = 50
Enddefine

Define Class Mycontainer As Container
      Height = 100
      Width = 100
      Procedure MouseMove
      Lparameters nButton, nShift, nXCoord, nYCoord
      If m.nButton = 1 And Between(m.nYCoord,0,Thisform.Height-This.Height) And ;
                  BETWEEN(m.nXCoord,0,Thisform.Width-This.Width)
            This.Move(m.nXCoord, m.nYCoord)
      Endif
      Endproc
Enddefine

Sample 2:

* Restricing within objects on form, in this case above or below the lines
Local oForm As Form
oForm = Createobject('TestForm')
oForm.Show(1)
Return

Define Class TestForm As Form
      AutoCenter = .T.
      Width = 900
      Height = 440
      Caption = 'Drag, Drop & Restrict'
      Add Object Shape1 As shape With Top = 30, Left = 0, Width = 900, height = 1
      Add Object Shape2 As shape With Top = 200, Left = 0, Width = 900, height = 1
      Add Object Command1 As MyButton With Caption='Move Me outside of the lines', Top = 35, Left = 5, width = 200, height = 30
Enddefine

Define Class MyButton As CommandButton
      Procedure MouseMove
            Lparameters nButton, nShift, nXCoord, nYCoord
            If m.nButton = 1 AND BETWEEN(m.nYCoord,30,171)
                  This.Move(m.nXCoord, m.nYCoord)
                  WAIT WINDOW m.nYCoord nowait
            Endif
      Endproc
Enddefine

In case you need it.  Cheers!

VFPA, the way forward for VFP Lovers!

$
0
0

Some of us (including yours truly) finds it very hard to move away from VFP.  Not because I can’t learn a new language but the moment I started trying to learn a new one, my mind always compares the syntax with VFP which seems to be embedded already in my being.  So I always ended up with thoughts like “if it is done in VFP9, there are only fewer lines to do such!” or “Heck, why did they made it like this when in VFP9…”.  In the end I lose interest in the new language.  Not to mention that there really is no demand for me on said language.

Still, learning and knowing more than VFP is really beneficial to us as developers.  Anyway, finally I tried VFPA by Chuanbing Chen, basically because I want its protection against decompilation; of course at first with some inhibitions.  So I decided to try and use that for a new client.

The moment I realized that it actually does not overwrite the copy of my VFP9 SP2 and instead creates its own folder, I said to myself “Hey, this is great!  I can run both VFP9 and VFPA in a single machine and in case something goes wrong, I can simply reopen the project back to VFP9 and recompile there!”.  

So that was the plan!  A plan that never happened because I observed VFPA to be more stable than VFP9.  And that is not a surprise considering Chen has fixed a lot of bugs that remain with VFP9.   So since I started using VFPA, I never went back to VFP9 anymore.  And when my other client requested new enhancements on my existing app, I moved them as well to VFPA.

Right now, I am still using just VFPA-32 bit.  But Chen offers more than that such as VFPA-64 bit and VFPCompiler.

If you are interested in checking Chuanbing Chen’s VFPA in its original language, please click this link.  

If you want to read his site in English, you can use thislink  

Also the list ofFixed VFP9 Bugs in VFPA:  

If you want to learn further about VFPA, you can view this video  

Anyway, the purpose of this entry here is to introduce VFP Advancedto my readers, if you still are not aware of it.  Personally?  Though I have just recently used VFPA, and by recently I mean since December 2020 only, I am very much satisfied with it.  As a parting words, move to VFPA.


Backup Progress, How?

$
0
0

 I have been wondering how to do this as we need to allow users to perform backup and give them a good estimate on when said backup will be completed; as in my end where the database is external (MariaDB), then I need to run an external MySQLDump.exe.  The beauty of this is while backup is in progress running on a separate thread, they can continue working on their app without waiting for the backup completion.

Why is it important for me to show a progress bar as an indicator that the backup process has been completed?  Well it is because if they quit the app ahead of the backup completion, it will also quit the backup process and the backup will most probably be incomplete as harddisk writing is done gradually by the backup process.  So we need something like this that shows them when the backup process is still in progress:




But again how?  The backup completion is affected by file size and whether said database is local or on a remote location.  So originally, we perform guesstimates of how long it normally takes but that is not good enough.  And we definitely cannot use:

DO WHILE .T.
   IF FSIZE(m.lcBackup) > 0
      Exit
   ENDIF
ENDDO

As depending on your RAM and free memory, a diskwrite for the backup may happen several times gradually increasing its size; and when the first HDD write happens, your app with the approach above will deem it already complete.  

This morning, an idea suddenly hit me and so I tried this trick now and it is working good, so I am sharing the trick here now.  

For a backup progress and its possible completion to be determined, we have to at least know how much MB the backup will consume.  So here is how:

1.  Check your archive folder and get all the backup files there via ADIR().
2.  Get the latest backup via checking its date and time stamps
3.  Get the filesize of that latest backup.  

Now, we have a basis of a possible size of the next backup. Either it will be of the same size but most probably it will be a bit bigger.  And that will be your basis for your progress bar indicator as well as possible completion.  I have a timer on my main form which checks the users that remain online every 5 seconds so I put this there too:

If gomyapp.isbackup = 1
      * Check if previous max backup bytes is not zero
      If Thisform._bytes > 0
            * Compare new size written vs last backup size
            Set Compatible On
            Local lnWidth, lnSize
            lnSize = Fsize(gomyapp.archive)
            lnWidth =  Int((m.lnSize/Thisform._bytes)*133)
            Thisform.coBackup.shpProgress.Width = m.lnWidth
            If m.lnWidth >= 133
                  Thisform.coBackup.Visible = .F.
                  gomyapp.isbackup = 0
            Endif
            Set Compatible Off
      Else
            * 6 minutes
            Thisform.coBackup.shpProgress.Width = Int((Thisform._backctr/360)*133)
            If m.lnWidth >= 133
                  Thisform.coBackup.Visible = .F.
                  gomyapp.isbackup = 0
            Endif
      Endif
Endif

Thisform._bytes holds the bytes of the latest backup file.  That is what I use now for comparison with the new backup as bytes are written down to the file in batches.  So it is slowly getting bigger, and with it the progress bar is increasing too.


Now if you notice, I took the possibility that Thisform._bytes will have zero value.  That will happen if there is no backup file yet; meaning they will perform the very first backup process.  In that case I have that ELSE which is geared for 6 minutes, just to ensure it will be completed. You can increase or decrease the number of minutes by doing an initial backup test, check how long the backup took to complete and use the number of minutes on the above instead of that 360 value.  I did the 6 minutes because our server is on the cloud and the speed of completion is dependent on the connection speed plus the size of the database.  Truth is it is just taking a few minutes on my end for a 126MB backup file but I am just playing safe on that initial backup run.  After that, the system will no longer switch onto that as there are now files for comparison.

So there it is.  A way to give a user a good estimate of a backup completion.  HTH!



Viewing all 160 articles
Browse latest View live