Quantcast
Channel: Cape Cod Gunny - Michael Riley
Viewing all 139 articles
Browse latest View live

Delphi Tip of the Day - Forward Declarations. Solving the Chicken or the Egg Problem.

$
0
0
Today's Delphi Tip of the Day is about resolving "which came first, the chicken or the egg". Because Delphi is a strongly typed language you can't refer to something until it's been defined. However, there are situations where you want to refer to something NOW, but unfortunately it hasn't been defined yet.

In other words "I want the egg of that chicken. I know that chicken hasn't laid that egg yet. However, since I know that chicken is going to lay that egg (trust me I know), let me have that egg now."

This is where forward declarations come into play.

I was brought here by something that came up while reading Pawel Glowacki's book Expert Delphi. I'm on page 233 from Chapter 7 - Sensing the world. Pawel discusses the TSensorManager class in the System.Sensors unit.

There is main TSensorManager class that acts as a gateway to all sensor information. It has a Current: TSensorManager class property that is used to reference all sensor information. At the top of the System.Sensors unit, you can find a TSensoryCategory enumerated type that provides the top level categorization of all possible sensors: 

At this point my head was about to explode so I decided to open the System.Sensors unit and have a look at the actual code Pawel was referring to. Then it hit me.

What the hell is this empty class doing here? How can you have an empty class?
TSensorManager = class;
I remember going down this empty class learning path a while ago. But do you think I can remember what it was, or means. No. Obviously, I had not associated a nice, simple "word picture" to the meaning of this empty class. Because if I had, I would have remembered.

So now I have the nice, easy to remember, "Oh that's Delphi's way of resolving the chicken or the egg thing" word picture.

Have a look at this type definition snippet from the System.Sensors unit:"
type

//
//other type declarations
//

TSensorManager = class; //←-- egg reference

TCustomSensor = class abstract
public type
TProperty = (UniqueID, Manufacturer, Model, SerialNo, Name, Description);
private
FOnDataChanged: TNotifyEvent;
FOnSensorRemoved: TNotifyEvent;
FOnStateChanged: TNotifyEvent;
FManager: TSensorManager; //←-- egg reference
FStarted: Boolean;
//
// other class definitions
//
end;

//
//other type declarations
//

TSensorManager = class abstract //←--- chicken reference
public type
TFilters = TDictionary‹string tsensorfilter=""›;
private class var
FCurrentManager: TSensorManager;
class function InternalGetSensorManager: TSensorManager; static;
class constructor Create;
class destructor Destroy;
protected type
TSensorManagerType = class of TSensorManager;
//
// other class definitions
//
end;


//
//other type declarations
//

implementation
The problem arises because there's a private field declaration in the TCustomerSensor class that references the TSensorManager class that doesn't exist yet. It's the damn "chicken or the egg" thing.

To get around this Delphi allows what is called a forward declaration.

This is not by any means the complete class definition. The complete class definition must be declared somewhere inside the same type declarations where the forward declaration resides.

Here are a couple useful links:

http://docwiki.embarcadero.com/RADStudio/Rio/en/Classes_and_Objects_(Delphi)#Forward_Declarations_and_Mutually_Dependent_Classes

http://www.delphibasics.co.uk/RTL.asp?Name=Class

Special Characters:
The  "‹" and "›" in the source code sections were created using the Alt + Keypad method.
https://www.keynotesupport.com/websites/special-characters-symbols.shtml


Enjoy!
Semper Fi
Gunny Mike
https://zilchworks.com


Say what you mean and mean what you say!

$
0
0
How often are you or your words misinterpreted? Do you even know? Would someone tell you? Or would you go on with life thinking everything is just honky dory. Only to find out later things were not just fine, you only thought they were.



I attended a Toastmasters Leadership Institute training session and was introduced to a "word exercise". This was the first-time I ever heard this done, or given any thought to how a simple phrase could possibly be misinterpreted. It never dawned on me that someone might take to mean what I said differently than I meant it.

Read this sentence to yourself: "I never said I thought your idea was bad."

Fairy straightforward, right. How did this sound to you? Is this something you may have even said to someone? I know I've said this or something very similar to someone.

Let's see how many different ways this can be interpreted. Read each sentence below putting emphasis on the "bolded" word below.

"I never said I thought your idea was bad."

"I never said I thought your idea was bad."

"I never said I thought your idea was bad."

"I never said I thought your idea was bad."

"I never said I thought your idea was bad."

"I never said I thought your idea was bad."

"I never said I thought your idea was bad."

"I never said I thought your idea was bad."

"I never said I thought your idea was bad."

Interesting right? Yeah that was the same reaction I had. It never occurred to me that someone might see or hear what I say in a different context than they way I mean it.

That explains a lot. Today, we are constantly texting or emailing people. It's not always your fault if you or what you say gets taken the wrong way. It depends on the way it gets interpreted by the recipient.

So even if you say what you mean and mean what you say it might still be taken differently.

Enjoy!

Semper Fi,
Gunny Mike
https://capecodgunny.com

Your feedback, and comments are always welcome. Don't forget to subscribe for email notifications of new posts.

Delphi Makes Updating Old Code Very Easy (Hidden Gem)

$
0
0
When my computer hard drive crashed in October 2018 I was really upset. The hard drive was completely shot and unrecoverable. I lost all of the code I had been working on... ARRRGH! The last backup I had was taken in March 2015. Yup, 3 1/2 years of Delphi, Evelated DB, and SQL code GONE!

Intellectually, I accepted this had happened and purchased Carbonite within a few days of getting my computer back this time with a solid state drive. However, I had not emotionally accepted I lost all that work. Well, last weekend I completely came to grips with the situation. Had a good cry. Decided to pickup what I could and start anew updating my old software.

The last time I compiled this project was 2008. So, I opened my Delphi 2005 project with Delphi 10.3 and clicked "Run" and held my breath.

It looked like crap. The default font is the old "System" font. The background colors didn't register properly. I couldn't see some of the stuff because the font color and background color was the same. This was broken. That didn't look right. Then the big "Ah Ha!" moment came...

Hey wait a minute... I just got a clean compile on code written with Delphi 2005 that is hasn't been touched since 2008 . That's amazing!


I'm slowly working my way through enhancing the look and feel. I'll be adding new functionality as well. It's amazing how a few minor changes takes thing to a whole new level.

Before: 2008 (Delphi 5)

After: 2019 (Delphi Rio)

So now I'm updating this code so I looks modern. In the process I will be replacing old functions with better, faster, more readable functions. Not all of them, just those using dumb code. I started swapping out function names in small little pieces. Repeating the Save, Run, Test, Close, Next Change. Save, Run, Test, Close, Next Change for every function I replaced.

Work Smarter Not Harder!


I wanted to see if I had replaced all the old occurrences of one function with the new function.

By pure dumb luck I right-clicked on a function name and found "Search For Usages...". Whoa. This is a great tool. And I'm going to show you how to use it.

Step 1: Click on a function in any unit.

Step 2: Right-Click on the function name and choose Search For Usages

Step 3: Click Search

Step 4: Expand the results



Step 5: Double-Clicking takes you to the source code

Step 6: Repeat the search making sure there are no more changes needed

Step 7: Clean up when done

I am so grateful I found this nice little utility. I just had to stop making code changes and share it with you guys.

Enjoy,
Gunny Mike
zilchworks.com

Delphi Tip of the Day - TODO or not TODO

$
0
0
I tend to be a scatter-brain when it comes to Delphi.

Really? Tend to be? "No Gunny. You are a scatter-brain when it comes to Delphi." I can hear all of you who know me saying this. That's fine.. I fully embrace it. I am a scatter-brain, especially when it comes to everything Delphi. There is so much I want to learn and do.

I have been trying to update my very old, flagship product Zilch Standard for 8 years. I've made zero progress. Why? Two reasons:
  1. I am a scatter-brain.
  2. I was of the mindset I needed to start from scratch and completely redesign everything.

When I was a teenager,
I would go to the ...


When I was a teenager, I would go to the library with a specific idea of what I wanted to read about. I'd head straight to those bookshelves and start eyeballing titles. "Nope.""Nope.""Uh Uh."'Nah."'Oh, that looks interesting.""Hey that reminds me of  X."  "Where is the 'X' section." Off I'd go to the "X" section,  and repeat this process until I found something all consuming.

This happens to me all the time with Delphi. My thirst for learning overtakes me and I lose focus on actually making progress inside the IDE. 

How about the other issue, having the mindset of completely redesigning everything from scratch? Well that was squelched when my hard drive crashed and I lost over three-and-a-half years of code. Gone! Poof! It actually turned out to be a blessing. It has forced me to look at things with a whole different perspective. 

That brings me to today's Delphi Tip of the Day.

As I was going through and porting over my old Delphi 5 code, I started doing that "scatter-brain" thing again. I first noticed it when I was updating the Main Menu functionality. "Hey, you know what would be cool, an MRU (Most Recently Used) file list.""Yeah lets add that". And off I went on a quest to learn how to build an MRU list.

Two hours later when I went to make another Nespresso (yeah I'm a coffee snob), it hit me. "Hey Gunny, an MRU list is not the objective. Put it on the back burner."

My back burner items tend to get lost and disappear.


My back burner items tend to get lost and disappear. Then I get frustrated because I can't remember all those "To Do" items I have on the back burner. "Hey wait a minute. Doesn't Delphi have a 'To Do' list function built in? 

Yes, Delphi does indeed have TODO functionality and I started using it the other day. However, in traditional Gunny Fashion it's not your normal TODO.
"If we are going to get wet up to our knees, we might as well get wet up to our waist and get the job done right."
I often use that phrase at my day job. Especially when there are two or three of us gathered around working on an immediate issue. It's different when it's an urgent need that must get done before you go home. 

So when I get ankle deep on a "new" feature I'd like to add, that is not a priority, I add it to my TODO list in Delphi. However, I also include the links to the website articles that will be useful when the time comes to implement them later. For example here is my current TODO list:


If you drill down into the highlighted "TODO" item it brings up the source code:

var
HWnd: THandle;
MR_Result : Word;

begin
{---------------------------------------------------------------}
{ Use this code for retail version }
{---------------------------------------------------------------}
Hwnd := FindWindow('TMainForm','Zilch Standard - Main Screen');
{---------------------------------------------------------------}
{ Use this code for trial version }
{---------------------------------------------------------------}
//Hwnd := FindWindow('TMainForm','Zilch Standard (Evaluation Copy) - Main Screen');

{---------------------------------------------------------------}
{ TODO 3 -oMJR -cPhase 2 : Set Focus to running application }
{---------------------------------------------------------------}
// Refer to these links when for implementing this TODO item
// https://codeoncode.blogspot.com/2016/12/get-processid-by-programname-include.html
// https://stackoverflow.com/questions/47598473/how-can-i-call-getwindowthreadprocessid-in-delphi-10-2
{---------------------------------------------------------------}
If Hwnd <> 0 then SetForeGroundWindow(GetForeGroundWindow())
else
begin
Application.Title := 'Zilch';
Application.CreateForm(TMainForm, MainForm);
Application.CreateForm(TNoPrintDlg, NoPrintDlg);
Application.CreateForm(TCreditorInfo, CreditorInfo);
Application.CreateForm(TDisclaim_Save, Disclaim_Save);
{---------------------------------------------------------------}
{ Use this code for US Marine Corps distribution }
{---------------------------------------------------------------}
{
MR_Result := USMC_End_User_License.ShowModal;
If MR_Result = idYes then
begin
Application.ProcessMessages;
Application.Run;
end;
}
{---------------------------------------------------------------}
{ Use this code for genral public distribution }
{---------------------------------------------------------------}
Application.ProcessMessages;
Application.Run;
end;
end.

This way I can do a little "ankle deep" research and keep it preserved on the back burner for latter use without the risk of loosing it. It allows me to scratch the itch at the time it itches and lets me go knee deep later on when the time comes to dive in.

Going forward I will make extensive use of the TODO feature in Delphi. I hope this has helped you better understand TODO's and how they can help you scratch the itch when it itches.

It's time to publish this and get back to work!

Enjoy,
Gunny Mike

Please use the comments section to share how you use Delphi's TODO feature or to recommend an alternative.

Stop saying "I don't have time" - Say "It's not a priority"

$
0
0
That little switch from saying "I don't have time", to saying "It's not a priority" is a HUGE game changer. I learned about this from a co-worker last week. She didn't invent it... she heard it and passed it on to me. So, I'm passing it on to you.

I started using it immediately. It's my new mantra. I've mostly said it quietly only too myself so far. An I can attest, it's already made a big difference in my life. I thought about making the title of the post click-bait. For example:

Stop saying "I don't have time", say this instead...


Making people click into the site to get the punchline. But that's not what this is about. This is about leveraging the smallest of things, and turning it into the biggest of life changers. As my good friend Bob Wight, would say, "Riley, that's what you call a woomera". A small little device that when used, gains a tremendous advantage over not using it. (R.I.P. Bob, I miss you a lot).

One of my favorite songs is "We will never pass this way again" by Seals and Crofts. It speaks to me in so many ways. And it's true! So while we go through life, why not pick up a little nugget every now, from wherever we find it, and pass it on to the rest of the world. You never know the significance one small little thought will have on someone.

Enjoy the rest of your day and tackle those priorities!

Semper Fi,
Gunny Mike
zilchworks.com

Delphi Tip of the Day - Rounding (Again!)

$
0
0
It's been over 7 years since I dove into the mystical world of rounding numbers within Delphi. It's amazing how much code actually gets written in support of the main stuff an application does. I am so glad I wrote my blog... it's the one place I can go back to when I find gaps from my hard drive crash of 2018.

I ran into the old familiar rounding issue - again. And of course, I didn't have the code I used to get it squared away.

My original blog post from 7 years ago, "Rounding the Currency type like they taught us in school", explains the issue in full detail and nothing has changed with regard to how Delphi handles rounding.

And as usual David Heffernan gave...


So I wen back to the Stack Overflow post  to revisit the answers given to my original question. And as usual David Heffernan gave a very straightforward and succinct answer.

{---------------------------------------------------------------}
{ Added 2019-08-07 }
{ }
{ Used to solve the Delphi's rounding function. }
{ }
{ https://stackoverflow.com/q/10965759/195983 }
{ https://stackoverflow.com/a/10970283/195983 }
{---------------------------------------------------------------}
Function RoundTo2dp (Value: Currency): Currency;
begin
Result := Trunc(Value*100+IfThen(Value>0, 0.5, -0.5))/100;
end;

I added the code to my StrStuff unit. (Does everyone have a StrStuff unit or is it just me?) And it did not compile. So, when I got done cussing out Heffernan for putting bum code out there, I did some digging.

It turns out there are two IfThen functions. One in the SystemStrUtils unit and the other one in the System.Math unit. And of course, I chose the wrong unit.

This specific IfThen function is from System.Math


This specific IfThen function is from the System.Math unit:
unit Strstuff;

interface

uses System.Sysutils, System.Math;

Function RoundTo2dp (Value: Currency): Currency;
And of course after I put the correct unit in the uses clause it worked like a champ. I must admit, it felt good cussing out Heffernan. But as usual... he's right. Again!

Thank you David.

Enjoy!
Semper Fi
Gunny Mike
https://zilchworks.com

Delphi Tip of the Day - Who's up First?

$
0
0
I'm working on updating my old flagship product and I ran into an issue where a file kept getting deleted on startup. I could not for the life of me figure out where this was happening. I wound up adding vcl.Dialogs to all of the units and then adding code to the Initialization section of each unit hoping I could find out where the damned delete code was.
initialization
begin
ShowMessage('Inside [Unit Name Here]');
end;
Adding this code to each and every unit was a pain in the butt. And it still didn't help me track down where the file was getting deleted. There had to be an easier way to figure out the order in which each unit was being created.

I turned to the SO community asking for an easy way to generate a list of forms/units in the order they are created. I received the simplest and most elegant solution from Uwe Rabbe. He recommended looking at the C=ICODE segments of the map file.

My first two questions were, "What's a map file? And how do you create one?"

Information on what a *.map file is can be found here.
http://docwiki.embarcadero.com/RADStudio/Rio/en/API_%28%2A.map%29

To create a .map file from inside the IDE click Project > Options> Delphi Compiler> Linking

Set Map file := Detailed


Click Save

Then build your project and you will have a *.map file.

The reason I was having such a hard time tracking down where the file deletion was happening was because it took place within a "dll" file.



Line 47 of the map file shows the first unit of my project is Zglobals (yes the dreaded global unit). Lines 58-60 show the next three units from my project.

Line 59 StdDlls is a precompiled dll unit that I am unable to modify the source code for because it uses "Quick Reports". I do not have Quick Reports installed on my new dev computer. Therefore I cannot actually modify the source code for this unit.

It turns out that the file delete was happening inside the dll.

I just thought that Uwe Rabbe's solution to creating an ordered list of form/units was so simple and elegant I needed to share it with you in case you ever need a quick and easy way to see the order in which each Delphi unit gets created inside one of your projects.

Cheers!
Semper Fi,
Gunny Mike

When You Don't Know, What You Don't Know

$
0
0
I've been in a rut for about the past six months. Not just a Delphi programming rut but a "life rut". What's worse, I knew I was in a rut and thought I was trying to get out of it only to realize I wasn't. Then this morning came and... I now know what I didn't know.

"I knew I was in a rut and thought
I was trying to get out of it..."


I was fascinated by a post in the Delphi Developer group on FaceBook written by a long-time VCL developer who is frustrated with making something work on an Android device. Admittedly, this person is not a GUI guy nor an OOP fundamentalist. I can totally relate to this. I have been using Delphi since it was Turbo Pascal 3. I am a traditional "top-down procedural" programmer who has been struggling to wrap my head around OOP fundamentals.

One of the comments on this post stopped me dead in my tracks. It simply stated... "Put your login form in a TFrame" and gave a link to a GitHub repository of FMX Cross-Platform-Samples.

https://github.com/FMXExpress/Cross-Platform-Samples

I've seen some of this FMXExpress stuff before but never knew about this GitHub repository. At the time of this writing there are 99 well organized Cross Platform Samples available. I've been looking for an excuse to rededicate myself to learning FMX cross application development in Delphi and this GitHub repository looks like the perfect spot for me to learn.

"I really don't know what GitHub is or how to use it."


Now, I've heard of GitHub before and I've been to a few different GitHub repositories, but I really don't know what GitHub is or how to use it. This was the my first acknowledgement of YDKWYDK. So I jumped on lynda.com and looked for a course about GitHub and found this one called "Learning GitHub".

About ten minutes into the course the instructor says "Projects allow you to visualize your work with Kanban-style boards." I've never heard of Kanban. What is Kanban? (More YDKWYDK).

"What is Kanban?"


I do another search within lynda.com and discoverd a course called "Stay Lean with Kanban".

Wow, this Kanban stuff is really cool. In it's simplest form Kanban is a way to visualize and prioritize the workload. You visually see what you are doing and what needs to get done. You also eliminate waste by focusing on what's most important. This is done by limiting the work in progress.

I'm about a third of the way through this tutorial and paused it to write this blog because of another YDKWYDK episode.

As I was getting to understand this Kanban stuff it hit me. Why can't I use Kanban for my personal life?

"Why can't I use Kanban for my personal life?"


I can and I intend to do just that. There's a website for "Personal Kanban". There's also a book called "Personal Kanban: Mapping Work | Navigating Life".

It's safe to say I have climbed out of my rut and I'm getting back at it. What a journey today has been...

FB Delphi Developer > FMXExpress > GitHub > Kanban

Semper Fi,
Gunny Mike






Delphi Revisited: Database Application Design

$
0
0
I have been creating database applications for over 30 years. Unfortunately, I have never been able to create a single database application in Delphi. That's a very sad fact. One that I'm not very proud of. Because I've been unable to create a database application with Delphi, I considered myself mediocre. And then something happened... Delphi turned 25.

"And then something happened... Delphi turned 25"


I, like so many of you, spent time this past week looking at all the commotion on the internet over Delphi turning 25. Then I looked at the picture of me, and my two boys hanging in my home office. I've been using Delphi since it was Turbo Pascal 3. 


That picture was taken in 1994 for an article that ran in the December issue of Military Lifestyle magazine.


"You don't need to be a programming guru to be successful"


Wow, my Zilch software is 29 years old. It's older than Delphi. I'm not mediocre. I'm living proof you don't need to be a programming guru to be successful. You just need determination.

And then, I had an epiphany regarding my lack of database design skills with Delphi. I realized I have been concentrating on the wrong stuff. I got so bogged down with the "How to do Data Entry" aspects that I totally missed out on everything else I can do with Delphi.

I actually do have one commercial product, Credit Card Math, that uses an ElevateDB database. But you know what, even though this program uses a database, it does not have any "Data Entry" functions.

"Data entry does not a database application make!"


That's it! Data entry does not a database application make! I concentrated on the wrong thing, data entry. I need to focus on the presentation and the analysis, and the reporting of the data and not the data entry. 

All I need is data in the database. I can build the whole presentation layer and reports without having any data entry functions. (light bulb moment)

I have such a renewed spirit. I'm done fiddling around making fancy grids that do all sorts or nifty data entry tricks. I'm going to focus on the meat and potatoes of my application. When the time comes I'll deal with the data entry stuff.

It's time to get ready for Zilch turning 30 in 2021. 

Happy Birthday Delphi!

Semper Fi,
Gunny Mike

Delphi Tip of the Day - Autosave Project Desktop

$
0
0
I am not a full-time Delphi guy. So, when I'm learning Delphi by reading OPC (other people's code) I want to make the best of my time. I was studying some code from Cary Jensen that goes with Chapter 6 of his Delphi in Depth: FireDAC book.

When I was done understanding a section of his code, I simply folded it. I figured I beat that code up enough in my mind. I understood it. And I don't need to look at it again, at least not right now. I also didn't want to be distracted by having those code blocks expanded. 

I started a brand new project in Delphi to write some of my own code that was similar but different, just to reinforce what I learned. When I was finished, I reopened Jensen's code and the pieces of code I had folded were no longer folded. 

It turns out this is an easy fix. Simply enable "Save project desktop when closing".
Tools > Options > IDE > Saving and Desktop


Not only did this keep sections of the code I had folded, folded, it remembered whether my last view was Code view or Design view. It also kept track of where in the code view I left off. I was looking at line 522 in the source code when I close the project, Delphi put me right back at line 522 the next time I reopened the project. How cool is that.

Enjoy!
Semper Fi
Gunny Mike


Delphi Tip of the Day - OnShow SetFocus or Form1.ActiveControl

$
0
0
I'm creating a simple database program to explore how SQLite works within Delphi. I want to be able to switch between three databases: Album, Test, and World. So I added a TComboBox and populated the Items property with three values; Album, Test, World. I also set TextHint := 'Choose Database...';

object ComboBox1: TComboBox
Left = 8
Top = 8
Width = 121
Height = 21
ParentShowHint = False
ShowHint = False
TabOrder = 0
TextHint = 'Choose database...'
Items.Strings = (
'Album'
'Test'
'World')

When I ran the program the TextHint for the ComboBox did not display:



That is not what I was expecting. The Combox was the active control which negates being able to see the TextHint "Choose database..."

So I added a little piece of code to the form's onShow event.

procedure TForm1.FormShow(Sender: TObject);
begin
StatusBar1.SetFocus;
end;

Now when I run the program right from the get-go the StatusBar componet has the focus and the ComboBox displays as intended.


Sometimes it's the small things that make all the difference!

An alternative  to using the OnShow event was suggested in the comments.

Form1.ActiveControl := StatusBar1


I love Delphi! 

Enjoy!
Semper Fi
Gunny Mike

Delphi - SQLite Sample Projects

$
0
0
I've been exploring SQLite within Delphi. There are several sample SQLite applications that come with Delphi if you selected to install the samples during setup. I've put together the list of SQLite samples that are available.

The common samples file path is located here:
..\Studio\20.0\Samples\Object Pascal\Database\FireDAC\Samples\

FireDAC.SQLiteIniFile Demo Sample 

This sample uses FireDAC to connect to an SQLite database that emulates the structure and the operations of an INI File.

AddOn\SQLiteIniFile\Demo\SQLiteIniDemo.dproj

FireDAC.TFDLocalSQL MegaFMX Sample 

The sample shows different TFDLocalSQL applications.

Comp Layer\TFDLocalSQL\MegaFMX\LSQLMega.dproj

FireDAC.SQLite Encryption Sample 

This sample demonstrates how to encrypt/decrypt an SQLite database.

DBMS Specific\SQLite\Encryption\SQLite_Encrypt.dproj

Controlling Database Access Rights 

The SQLite is an embedded DBMS. This implies that it is a single user DBMS and does not need such concepts as a user, access rights, and so on. Still, some application can benefit from an access right control, for example:

  •  An application can restrict rights depending on an end-user license. A demo license means less possibilities, a full license provides all possibilities. 
  • Multi-tier data access frameworks can use their own user concept and control data access rights using some generic approach. 

 Again, SQLite provides a unique feature allowing you to authorize or not SQL commands. To work with this API, a Delphi application should set the OnAutorize event handler of the TSQLiteDatabase object, which is a database connection wrapping object. Hook this event after a database connection is opened.

DBMS Specific\SQLite\OnAuthorize\SQLite_OnAuthorize.dproj

Hooking Database Updates 

SQLite provides an unique API allowing you to monitor all updates to a database. This feature can be used, for example, to log all updates to a DB. To work with this API, a Delphi application should set the OnUpdate event handler of the TSQLiteDatabase object, which is a database connection wrapping object. Hook this event after a database connection is opened.

DBMS Specific\SQLite\OnUpdate\SQLite_OnUpdate.dproj

Custom Collations 

SQLite stores and handles all character data either in UTF8 or UTF16, depending on the OpenMode connection parameter. When SQLite needs to compare or sort a character data, it has to know what rules to use for this. The rules are known as a collation.

DBMS Specific\SQLite\UserCollation\SQLite_UserColl.dproj

Custom Functions 

SQLite does not support the stored procedure or function concept, as it allows you to use the host language environment to extend the engine functionality. SQLite allows you to register host language functions in the SQLite engine and use them in the SQL commands. FireDAC simplifies this by introducing the TFDSQLiteFunction component.

DBMS Specific\SQLite\UserFunc\SQLite_UserFunc.dproj

The SQLite R*Tree Module 

An R-Tree is a special index that is designed for doing range queries. R-Trees are most commonly used in geospatial systems where each entry is a rectangle with minimum and maximum X and Y coordinates. Given a query rectangle, an R-Tree is able to quickly find all entries that are contained within the query rectangle or which overlap the query rectangle.

This idea is easily extended to three dimensions for use in CAD systems. R-Trees also find use in time-domain range look-ups. For example, suppose a database records the starting and ending times for a large number of events. A R-Tree is able to quickly find all events that were active at any time during a given time interval, or all events that started during a particular time interval, or all events that both started and ended within a given time interval. And so forth. 

DBMS Specific\SQLite\UserRTree\SQLite_UserRTree.dproj

Title: SQLite 

Brief Description: This snippet shows you how to populate a listbox from a SQLite database using FireDAC and LiveBindings. See the section called "Accessing a Database" in the Mobile Tuturials topic in the documentation for more information and licensing requirements.

Platforms supported: iOS, Android

..\Mobile Snippets\FireDACSQLite\FireDAC_SQLite.dproj

Enjoy
Semper Fi,
Gunny Mike

Delphi Tip of the Day - Object Inspector Search

$
0
0
I was watching an excellent video on how to use the batch move capabilities within Delphi to import text data into a database and had one of those "What the heck just happened" moments. The object inspector was only displaying the "Name" property.

What the heck just happened...


I rewound the video by about 20 seconds and paid careful attention to what was going on. The presenter used the "search" box associated with the Object Inspector. What? I had no idea you could do that. Wow! Game Changer!

I've been staring at the object inspector for years and never even saw the search box even though it's right there, plain as day, in my face. Sure beats scrolling up and down through all the properties anytime I wanted to make a change.

Game Changer!




Enjoy!
Semper Fi,
Gunny Mike

Delphi Tip of the Day - First Chance Exception

$
0
0
I'm in the process of upgrading a very old Delphi program that I wrote with my Turbo Pascal, top-down, structured-programming, mindset. One of the major updates I want is for the data to be in a database. So, I'm cruising right along making very good progress. After two days I finally get the algorithm working. And it's producing good results.

Now it's time to get those results into a database table. No big deal. FDMemtable and LocalSQL will do the trick. I'll just add a DataModule to the project. Drop in the necessary components. Add the fields using the fields editor.

I get everything working in the IDE. I'm good to go. All I need to do now is add the SQL. I code up the insert statements. I'm very excited. I will soon have the data in a database table. I push F9 and... BOOM!

"What the hell is a First chance exception?"




You've got to be kidding. I was so close. I started googling all over the internet trying to figure out what a First chance exception is. I just wanted an answer. After an hour or two I gave up. I was tired. I was frustrated. I'll deal with it in the morning. Off to bed I went.

"It turned out to be a very simple fix!"


The next day with a fresh attitude I returned to my project. It turns out this was a very simple fix. I didn't see or think about this the night before. When I added the DataModule to the project it was added to the bottom of the list.




The OnCreate method of Form1 calls the logic in another unit, which in-turn communicates with DataModule1. Because DataModule1 is created after Form1 gets created it caused an exception. All I had to do was move the DataModule1 before Form1.


I literally had the cart before the horse. I remember getting burned by this in the past but totally forgot about it.

Enjoy!
Semper Fi,
Gunny Mike

Feedback is always appreciated.

Delphi Tip of the Day - Export to CSV

$
0
0
This weekend I had a bunch of data inside a FireDAC FDMemtable and I wanted to play around with that data in Excel. So, the first thing I did was review which data formats Excel would accept.


That's excellent! Excel allows both importing from XML and JSON both of which can done using FDMemtable.SaveToFile method. ( SaveToFile documentation )

I created an XML file using the SaveToFile method and then tried to import that file into Excel.


That didn't go so well. Where's my data? I'm starting to feel that "I hate Delphi" thing happening again! Let's try JSON. So. I created a JSON file using the SaveToFile method and then tried importing into Excel. 

"I just want the data in Excel."



That didn't go so well either. I've imported text files hundreds of times into Excel and it just worked. I wasn't in the mood to figure out what was going on. I'll save that for another day. I just want the data in Excel. 

"I know CSV works. Let's find a solution."


I know that text or csv files import just fine into Excel so let's see how to create a CSV file using Delphi. A quick google search turned up exactly what I was looking for, a simple solution written by Uwe Rabbe six years ago. Thank you Uwe!


This works with any TDataSet descendent! And just like that I love Delphi again.




Enjoy - Semper Fi
Gunny Mike
https://zilchworks.com

Feedback is always appreciated!


Delphi Class References - Complete List

$
0
0
I mistakenly hit "Publish" instead of "Save" last night which wound up making this post go live before it was finished. Oh well... it is what it is.

 If you've read some of my earlier posts you know I have been struggling with thinking like a Delphi OOP programmer. I am self-taught. I started with Turbo Pascal 3. And I've been stuck in the top-down, structured mindset for a very long time. Until now.

A few days ago I rediscovered an older (still relevant) book that has been siting on my bookshelf for over twenty years. It's called Delevoping Custom Delphi 3 Components. It was written by Ray Kanopka ( https://delphibydesign.com/).

"This book is the greatest Delphi book ever."



I posted this image to the Delphi Developer group on Facebook.Warren Postma, who writes the Delphi Code Monkey blog (http://delphicodemonkey.blogspot.com/), made this comment about the book. "This book is the greatest Delphi book ever."

I have only read through the first 43 pages. And I agree 100% with Postma. This is probably the greatest Delphi book ever written.

I never heard of Class References before. Maybe I did and just don't remember. But it doesn't matter.  Kanopka introduces Delphi's Class References on page 42. He says, "Class references are typically used to treat a number of different classes as a single class." Then he goes on to explain how this works with a short, concise, extremely meaningful example. The Class Reference used is TControlClass.

Click! I get the concept. It's extremely powerful. Do I see an immediate need for implementing the use of a Class Reference? No. That doesn't mean it's not worth knowing about. Just having the knowledge that this is available is valuable.

I used to coach little league baseball when my boys were younger. There were usually twelve or thirteen kids on a team. You can only field nine kids at a time. So, a few of the kids sat on the bench and would get subbed into other positions as the game went on. One would become a right-fielder, one would become the second-baseman. And sometimes I'd do a double swap and replace the pitcher with a kid off the bench and replace the left-fielder with the kid who was pitching.

That's a nice little story Riley, but what does this have to do with Delphi's Class References?

Each position on a baseball team is filled by a baseball-player. Each individual position on the baseball team is slightly different but they all have one thing in common. They are all baseball-players for example;

  • TRightFielder : TBaseballPlayer;
  • TSecondBaseman : TBaseballPlayer;
  • TPitcher : TBaseballPlayer;
  • TLeftFielder : TBaseballPlayer;
There comes a time when you need to substitute a player or two, or three. Because we all know how mad the parents get when their kid doesn't play. So you have a player-substitute. But this player needs to play any position or quite possibly switch positions.

TBasballPlayerClass = class of TBaseballPlayer;

This lets you do suff like this:

Sub  : TBaseballPlayerClass;
Sub1 : TBaseballPlayer;
Sub2 : TBaseballPlayer;
Sub3 : TBaseballPlayer;

Sub1 :=  Sub.Create ( Self  ); //Sitting on the bench waiting to play
Sub2 :=  Sub.Create ( Self  ); //Sitting on the bench waiting to play
Sub3 :=  Sub.Create ( Self  ); //Sitting on the bench waiting to play

Sub1 :=  TBaseballPlayerClass ( TRightFielder  ); //Play Right Field
Sub2 :=  TBaseballPlayerClass ( TSecondBaseman ); //Play Second Base
Sub3 :=  TBaseballPlayerClass ( TPitcher ); //Go Pitch

or if you change your mind at the last minute

Sub1 :=  Sub3; //You pitch instead
Sub3 :=  TBaseballPlayerClass ( TRightFielder  ); //You go to Right Field

Once I understood the power and concept of Delphi's Class References I wondered just how many Class References are out there. So I did a search and created this list.

Enjoy
Semper Fi
Gunny Mike
https://zilchworks.com

Feedback is always welcome


File LocationClass Reference
data\Data.DB.pasTCheckConstraintClass = class of TCheckConstraint;
data\Data.DB.pasTCheckConstraintsClass = class of TCheckConstraints;
data\Data.DB.pasTDataSetClass = class of TDataSet;
data\Data.DB.pasTFieldClass = class of TField;
data\Data.DB.pasTFieldDefClass = class of TFieldDef;
data\Data.DB.pasTFieldDefListClass = class of TFieldDefList;
data\Data.DB.pasTFieldDefsClass = class of TFieldDefs;
data\Data.DB.pasTFieldListClass = class of TFieldList;
data\Data.DB.pasTFieldsClass = class of TFields;
data\Data.DB.pasTIndexDefClass = class of TIndexDef;
data\Data.DB.pasTIndexDefsClass = class of TIndexDefs;
data\Data.DB.pasTLookupListClass = class of TLookupList;
data\Data.DB.pasTParamClass = class of TParam;
data\Data.DB.pasTParamObjectClass = class of TParamObject;
data\Data.DB.pasTParamsClass = class of TParams;
.
data\datasnap\Datasnap.DSHTTPClient.pasTDSHTTPClass = class of TDSHTTP;
.
data\datasnap\Datasnap.DSReflect.pasTDSAdapterClassType = class of TDSAdapterClass;
.
data\datasnap\Datasnap.DSServer.pasTServerClassAdapterClass = class of TServerClassAdapter;
.
data\dbx\Data.DBXClient.pasTDBXClientDriverLoaderClass = class of TObject;
.
data\dbx\Data.DBXCommon.pasTDBXDriverClass = class of TDBXDriver;
data\dbx\Data.DBXCommon.pasTDBXDriverLoaderClass = class of TDBXDriverLoader;
data\dbx\Data.DBXCommon.pasTDBXPropertiesClass = class of TDBXProperties;
.
data\dbx\Data.DBXDynalink.pasTDBXDynalinkDriverCommonLoaderClass = class of TDBXDynalinkDriverCommonLoader;
data\dbx\Data.DBXDynalink.pasTDBXDynalinkDriverLoaderClass = class of TClassRegistryObject; // class of TObject;
.
data\dbx\Data.DBXPlatform.pasTObjectClass = class of TFactoryObject;
data\dbx\Data.DBXPlatform.pasTTransportFilterClass = class of TBaseTransportFilter;
.
data\dsnap\Datasnap.DataBkr.pasTRemoteDataModuleClass = class of TRemoteDataModule;
.
data\ems\EMS.DataSetResource.pasTEMSDataSetAdaptorClass = class of TEMSDataSetAdaptor;
.
data\firedac\FireDAC.Comp.BatchMove.pasTFDBatchMoveDriverClass = class of TFDBatchMoveDriver;
.
data\firedac\FireDAC.Comp.Client.pasTFDCustomConnectionClass = class of TFDCustomConnection;
data\firedac\FireDAC.Comp.Client.pasTFDCustomManagerClass = class of TFDCustomManager;
.
data\firedac\FireDAC.Comp.Script.pasTFDScriptCommandClass = class of TFDScriptCommand;
.
data\firedac\FireDAC.DatS.pasTFDDatSMechBaseClass = class of TFDDatSMechBase;
data\firedac\FireDAC.DatS.pasTFDDatSObjectClass = class of TFDDatSObject;
.
data\firedac\FireDAC.Phys.MongoDBWrapper.pasTOperationClass = class of TOperation;
.
data\firedac\FireDAC.Phys.ODBCWrapper.pasEODBCNativeExceptionClass = class of EODBCNativeException;
data\firedac\FireDAC.Phys.ODBCWrapper.pasTODBCLibClass = class of TODBCLib;
.
data\firedac\FireDAC.Phys.OracleWrapper.pasTOCILobLocatorClass = class of TOCILobLocator;
data\firedac\FireDAC.Phys.OracleWrapper.pasTOCICollectionClass = class of TOCICollection;
.
data\firedac\FireDAC.Phys.pasTFDPhysConnectionClass = class of TFDPhysConnection;
data\firedac\FireDAC.Phys.pasTFDPhysDriverClass = class of TFDPhysDriver;
.
data\firedac\FireDAC.Phys.PGWrapper.pasTPgVariableClass = class of TPgVariable;
.
data\firedac\FireDAC.Phys.SQLiteWrapper.pasTSQLiteExtensionClass = class of TSQLiteExtension;
.
data\firedac\FireDAC.Phys.SQLiteWrapper.pasTSQLiteVModuleClass = class of TSQLiteVModule;
.
data\firedac\FireDAC.Stan.Def.pasTFDDefinitionClass = class of TFDDefinition;
.
data\firedac\FireDAC.Stan.Error.pasEFDDBEngineExceptionClass = class of EFDDBEngineException;
data\firedac\FireDAC.Stan.Error.pasEFDExceptionClass = class of EFDException;
data\firedac\FireDAC.Stan.Error.pasTFDDBErrorClass = class of TFDDBError;
.
data\firedac\FireDAC.Stan.Factory.pasTFDObjectClass = class of TFDObject;
.
data\firedac\FireDAC.Stan.Intf.pasTFDConnectionDefParamsClass = class of TFDConnectionDefParams;
data\firedac\FireDAC.Stan.Intf.pasTFDStorableObjectClass = class of TFDStorableObject;
.
data\firedac\FireDAC.Stan.Option.pasTFDFetchOptionsClass = class of TFDFetchOptions;
data\firedac\FireDAC.Stan.Option.pasTFDResourceOptionsClass = class of TFDResourceOptions;
data\firedac\FireDAC.Stan.Option.pasTFDUpdateOptionsClass = class of TFDUpdateOptions;
.
data\firedac\FireDAC.Stan.Util.pasTFDThreadMsgClass = class of TFDThreadMsgBase;
.
data\rest\REST.Authenticator.OAuth.pasTOAuth1SignatureMethodClass = class of TOAuth1SignatureMethod;
.
data\rest\REST.Backend.EMSApi.pasTEMSClientHTTPErrorClass = class of EEMSClientHTTPError;
data\rest\REST.Backend.KinveyApi.pasTKinveyAPIErrorClass = class of EKinveyAPIError;
data\rest\REST.Backend.ParseAPI.pasTParseAPIErrorClass = class of EParseAPIError;
.
data\vclctrls\Vcl.DBGrids.pasTColumnClass = class of TColumn;
.
databinding\components\Data.Bind.Components.pasTBindEditorFactoryClass = class of TBindEditorFactory;
databinding\components\Data.Bind.Components.pasTContainedBindCompClass = class of TContainedBindComponent;
.
databinding\components\Data.Bind.DBLinks.pasTBaseDBGridLinkColumnClass = class of TBaseDBGridLinkColumn;
databinding\components\Data.Bind.DBLinks.pasTBindDBColumnFactoryClass = class of TBindDBColumnFactory;
.
databinding\components\Data.Bind.Grid.pasTBaseLinkGridToDataSourceColumnClass = class of TBaseLinkGridToDataSourceColumn;
databinding\components\Data.Bind.Grid.pasTLinkGridToDataSourceColumnFactoryClass = class of TLinkGridToDataSourceColumnFactory;
.
databinding\components\Data.Bind.ObjectScope.pasTBindFieldDefClass = class of TBindFieldDef;
databinding\components\Data.Bind.ObjectScope.pasTBindFieldDefsClass = class of TBindFieldDefs;
databinding\components\Data.Bind.ObjectScope.pasTGeneratorBindFieldDefsClass = class of TGeneratorFieldDefs;
databinding\components\Data.Bind.ObjectScope.pasTValueGeneratorClass = class of TValueGenerator;
.
databinding\engine\System.Bindings.CustomScope.pasTScopeClass = class of TCustomScope;
.
databinding\graph\GraphView.pasTGraphEdgeClass = class of TGraphEdge;
databinding\graph\GraphView.pasTGraphNodeClass = class of TGraphNode;
.
DUnit\Contrib\DUnitWizard\Source\Common\XPWinBase.pasTTEXPWin32 = class of EXPWin32;
"DUnit\Contrib\DUnitWizard\Source\DelphiExperts\DUnitProject\ dunit\Examples\TestedUnitParserTest_1.pas"TTClass = class of TXPStubClass1;
.
DUnit\examples\testexception\Unit1Test.pasExceptionClass = class of Exception;
.
DUnit\src\TestFramework.pasExceptionClass = class of Exception;
DUnit\src\TestFramework.pasTTestCaseClass = class of TTestCase;
.
DunitX\DUnitX.ComparableFormat.pasTDUnitXComparableFormatClass = class of TDUnitXComparableFormat;
.
DunitX\DUnitX.InternalDataProvider.pasTTestDataProviderClass = class of TTestDataProvider;
.
DunitX\DUnitX.Utils.pasTCustomAttributeClass = class of TCustomAttribute;
.
Experts\DSServerMethodsExpertsCreators.pasTDSServerMethodsCreatorModuleClass = class of TDSServerMethodsCreatorModule;
.
fmx\FMX.ActnList.pasTActionLinkClass = class of TActionLink;
.
fmx\FMX.Controls.Model.pasTDataModelClass = class of TDataModel;
.
fmx\FMX.Controls.pasTCustomTextSettingsClass = class of TCustomTextSettings;
fmx\FMX.Controls.pasTHintClass = class of THint;
.
fmx\FMX.Controls.Presentation.pasTPresentationProxyClass = class of TPresentationProxy;
.
fmx\FMX.Filter.pasTFilterClass = class of TFilter;
.
fmx\FMX.Gestures.pasTCustomGestureRecognizerClass = class of TCustomGestureRecognizer;
fmx\FMX.Gestures.pasTGestureEngineClass = class of TCustomGestureEngine;
fmx\FMX.Gestures.pasTGestureEngineType = class of TGestureEngine;
fmx\FMX.Gestures.pasTGestureStreamDataClass = class of TGestureStreamData;
.
fmx\FMX.Graphics.pasTCanvasClass = class of TCanvas;
fmx\FMX.Graphics.pasTCustomBitmapCodecClass = class of TCustomBitmapCodec;
fmx\FMX.Graphics.pasTFontClass = class of TFont;
fmx\FMX.Graphics.pasTFontColorForStateClass = class of TFontColorForState;
fmx\FMX.Graphics.pasTPrinterCanvasClass = class of TPrinterCanvas;
fmx\FMX.Graphics.pasTTextSettingsClass = class of TTextSettings;
.
fmx\FMX.Grid.iOS.pasTColumnLinkClass = class of TColumnLink;
fmx\FMX.Grid.iOS.pasTiOSCellControlClass = class of TiOSCellControl;
.
fmx\FMX.Grid.pasTColumnClass = class of TColumn;
.
fmx\FMX.Helpers.Mac.pasTOSXMessageClass = class of TOSXMessage;
.
fmx\FMX.ImgList.pasTDestinationItemClass = class of TCustomDestinationItem;
fmx\FMX.ImgList.pasTImageListClass = class of TCustomImageList;
fmx\FMX.ImgList.pasTLayerClass = class of TLayer;
fmx\FMX.ImgList.pasTSourceItemClass = class of TCustomSourceItem;
.
fmx\FMX.ListBox.pasTListBoxSelectorClass = class of TListBoxSelector;
.
fmx\FMX.ListView.Appearances.pasTGroupClass = class of TPresetItemObjects;
fmx\FMX.ListView.Appearances.pasTItemAppearanceObjectsClass = class of TItemAppearanceObjects;
fmx\FMX.ListView.Appearances.pasTListItemObjectClass = class of TListItemDrawable;
.
fmx\FMX.ListView.Types.pasTListItemViewType = class of TListItemView;
.
fmx\FMX.Media.pasTCaptureDeviceClass = class of TCaptureDevice;
fmx\FMX.Media.pasTCustomMediaCodecClass = class of TCustomMediaCodec;
.
fmx\FMX.MultiResBitmap.pasTFixedBitmapItemClass = class of TFixedBitmapItem;
fmx\FMX.MultiResBitmap.pasTCustomBitmapItemClass = class of TCustomBitmapItem;
.
fmx\FMX.MultiView.pasTMultiViewPresentationClass = class of TMultiViewPresentation;
.
fmx\FMX.Presentation.Android.pasTAndroidNativeViewClass = class of TAndroidNativeView;
.
fmx\FMX.Presentation.iOS.pasTiOSNativeViewClass = class of TiOSNativeView;
.
fmx\FMX.Presentation.Style.Common.pasTNativeStyledControlClass = class of TNativeStyledControl;
.
fmx\FMX.Presentation.Style.pasTStyledPresentationClass = class of TStyledPresentation;
.
fmx\FMX.Presentation.Win.pasTWinPresentationClass = class of TWinPresentation;
.
fmx\FMX.Printer.pasTPrinterClass = class of TPrinter;
.
fmx\FMX.StdActns.pasTCustomValueRangeClass = class of TCustomValueRange;
.
fmx\FMX.TabControl.pasTTabItemClass = class of TTabItem;
.
fmx\FMX.Text.pasTTextServiceClass = class of TTextService;
.
fmx\FMX.TextLayout.pasTTextLayoutClass = class of TTextLayout;
.
fmx\FMX.Types.pasTCaretClass = class of TCustomCaret;
fmx\FMX.Types.pasTFmxObjectClass = class of TFmxObject;
fmx\FMX.Types.pasTTabListClass = class of TTabList;
.
fmx\FMX.Types3D.pasTContextClass = class of TContext3D;
fmx\FMX.Types3D.pasTMaterialClass = class of TMaterial;
.
Indy10\Core\IdCommandHandlers.pasTIdCommandHandlerClass = class of TIdCommandHandler;
.
Indy10\Core\IdContext.pasTIdContextClass = class of TIdContext;
.
Indy10\Core\IdCustomTCPServer.pasTIdServerContextClass = class of TIdServerContext;
.
Indy10\Core\IdCustomTransparentProxy.pasTIdCustomTransparentProxyClass = class of TIdCustomTransparentProxy;
.
Indy10\Core\IdIOHandler.pasTIdIOHandlerClass = class of TIdIOHandler;
.
Indy10\Core\IdReply.pasTIdRepliesClass = class of TIdReplies;
Indy10\Core\IdReply.pasTIdReplyClass = class of TIdReply;
.
Indy10\Core\IdServerIOHandlerSocket.pasTIdIOHandlerSocketClass = class of TIdIOHandlerSocket;
.
Indy10\Core\IdThread.pasTIdThreadClass = class of TIdThread;
Indy10\Core\IdThread.pasTIdThreadWithTaskClass = class of TIdThreadWithTask;
.
Indy10\Core\IdUDPServer.pasTIdUDPListenerThreadClass = class of TIdUDPListenerThread;
.
Indy10\Protocols\IdAttachment.pasTIdAttachmentClass = class of TIdAttachment;
.
Indy10\Protocols\IdAuthentication.pasTIdAuthenticationClass = class of TIdAuthentication;
.
Indy10\Protocols\IdCoder.pasTIdDecoderClass = class of TIdDecoder;
Indy10\Protocols\IdCoder.pasTIdEncoderClass = class of TIdEncoder;
.
Indy10\Protocols\IdCoder3to4.pasTIdEncoder3to4Class = class of TIdEncoder3to4;
.
Indy10\Protocols\IdCookie.pasTIdCookieClass = class of TIdCookie;
.
Indy10\Protocols\IdDNSResolver.pasTResultRecordClass = class of TResultRecord;
.
Indy10\Protocols\IdDNSServer.pasTIdMWayTreeNodeClass = class of TIdMWayTreeNode;
.
Indy10\Protocols\IdFTPListParseBase.pasTIdFTPListParseClass = class of TIdFTPListBase;
.
Indy10\Protocols\IdHash.pasTIdHashClass = class of TIdHash;
.
Indy10\Protocols\IdHeaderCoderBase.pasTIdHeaderCoderClass = class of TIdHeaderCoder;
.
Indy10\Protocols\IdMessageCoder.pasTIdMessageEncoderClass = class of TIdMessageEncoder;
.
Indy10\Protocols\IdMessageCollection.pasTIdMessageItems = class of TIdMessageItem;
.
Indy10\Protocols\IdMessageParts.pasTIdMessagePartClass = class of TIdMessagePart;
.
Indy10\Protocols\IdSSL.pasTIdClientSSLClass = class of TIdSSLIOHandlerSocketBase;
Indy10\Protocols\IdSSL.pasTIdServerSSLClass = class of TIdServerIOHandlerSSLBase;
.
Indy10\Protocols\IdSSLOpenSSLHeaders.pasTIdOpenSSLAPICryptoError = class of EIdOpenSSLAPICryptoError;
Indy10\Protocols\IdSSLOpenSSLHeaders.pasTIdOpenSSLAPISSLError = class of EIdOpenSSLAPISSLError;
.
Indy10\Protocols\IdZLibCompressorBase.pasTIdZLibCompressorBaseClass = class of TIdZLibCompressorBase;
.
Indy10\System\IdException.pasTClassIdException = class of EIdException;
.
Indy10\System\IdStack.pasTIdSocketListClass = class of TIdSocketList;
Indy10\System\IdStack.pasTIdStackClass = class of TIdStack;
.
internet\WBComp.pasTBehaviorElementEventsClass = class of TBehaviorElementEvents;
internet\WBComp.pasTCustomElementBehaviorClass = class of TCustomElementBehavior;
.
internet\Web.CGIHTTP.pasTCGIRequestClass = class of TCGIRequest;
internet\Web.CGIHTTP.pasTCGIResponseClass = class of TCGIResponse;
internet\Web.CGIHTTP.pasTWinCGIRequestClass = class of TWinCGIRequest;
internet\Web.CGIHTTP.pasTWinCGIResponseClass = class of TWinCGIResponse;
.
internet\Web.DBWeb.pasTHTMLTableColumnClass = class of THTMLTableColumn;
.
internet\Web.HTTPApp.pasTContentParserClass = class of TAbstractContentParser;
.
internet\Web.Win.ISAPIThreadPool.pasTISAPIThreadPoolClass = class of TISAPIThreadPool;
.
Property Editors\BindCompEdit.pasTBindScopeFactoryClass = class of TBindScopeFactory;
Property Editors\BindCompEdit.pasTComponentClassFactoryClass = class of TControlClassFactory;
Property Editors\BindCompEdit.pasTControlFactoryClass = class of TControlFactory;
Property Editors\BindCompEdit.pas// TControlFrameFactoryClass = class of TControlFrameFactory;
Property Editors\BindCompEdit.pasTDataBindingListDesignerClass = class of TDataBindingListDesigner;
Property Editors\BindCompEdit.pasTNavigatorFactoryClass = class of TNavigatorFactory;
.
Property Editors\BindCompExprEdit.pasTBindCompExprDesignerClass = class of TBindCompExprDesigner;
Property Editors\BindCompExprEdit.pas// TItemStateClass = class of TItemState;
.
Property Editors\BindVisualizers.pasTBindExpressionVisualizerClass = class of TBindExpressionVisualizer;
.
Property Editors\ColnEdit.pasTCollectionEditorClass = class of TCollectionEditor;
.
Property Editors\DSDesign.pasTDSDesignerClass = class of TDSDesigner;
Property Editors\DSDefine.pasTFieldAccessClass = class of TFieldAccess;
.
Property Editors\OCXReg.pasTOleObjectEditorClass = class of TOleObjectEditor;
.
rtl\common\System.Actions.pasTContainedActionClass = class of TContainedAction;
rtl\common\System.Actions.pasTContainedActionLinkClass = class of TContainedActionLink;
rtl\common\System.Actions.pasTContainedActionListClass = class of TContainedActionList;
.
rtl\common\System.Classes.pasTBasicActionClass = class of TBasicAction;
rtl\common\System.Classes.pasTBasicActionLinkClass = class of TBasicActionLink;
rtl\common\System.Classes.pasTCollectionItemClass = class of TCollectionItem;
rtl\common\System.Classes.pasTComponentClass = class of TComponent;
rtl\common\System.Classes.pasTPersistentClass = class of TPersistent;
.
rtl\common\System.Notification.pasTBaseNotificationCenterClass = class of TBaseNotificationCenter;
.
rtl\common\System.Permissions.pasTPermissionsServiceClass = class of TPermissionsService;
.
rtl\common\System.Rtti.pasTRttiClass = class of TRttiObject;
.
rtl\common\System.Sensors.pasESensorExceptionClass = class of ESensorException;
rtl\common\System.Sensors.pasTGeocoderClass = class of TGeocoder;
rtl\common\System.Sensors.pasTGpsStatusClass = class of TGpsStatus;
rtl\common\System.Sensors.pasTSensorManagerType = class of TSensorManager;
.
rtl\common\System.Win.ComObj.pasTAutoClass = class of TAutoObject;
rtl\common\System.Win.ComObj.pasTComClass = class of TComObject;
rtl\common\System.Win.ComObj.pasTTypedComClass = class of TTypedComObject;
.
rtl\common\System.Win.VCLCom.pasTComponentProtectedAccessClass = class of TComponentProtectedAccess;
.
rtl\net\System.Beacon.pasTBeaconManufacturerDataParserClass = class of TBeaconManufacturerDataParser;
.
rtl\net\System.Bluetooth.pasTBluetoothManagerClass = class of TBluetoothLEManager;
rtl\net\System.Bluetooth.pasTBluetoothManagerClass = class of TBluetoothManager;
.
rtl\net\System.Net.URLClient.pasTURLClientClass = class of TURLClient;
.
rtl\sys\System.pasTClass = class of TObject;
rtl\sys\System.pasTClassHelperBaseClass = class of TClassHelperBase;
rtl\sys\System.pasTInterfacedClass = class of TInterfacedObject;
.
rtl\sys\System.SysUtils.pasExceptClass = class of Exception;
.
rtl\sys\System.Types.pasTMultiWaitEventClass = class of TMultiWaitEvent;
.
rtl\sys\System.Variants.pasTCustomVariantTypeClass = class of TCustomVariantType;
.
soap\Soap.InvokeRegistry.pasERemotableExceptionClass = class of ERemotableException;
soap\Soap.InvokeRegistry.pasTInvokableClassClass = class of TInvokableClass;
soap\Soap.InvokeRegistry.pasTRemotableClass = class of TRemotable;
soap\Soap.InvokeRegistry.pasTRemotableXSClass = class of TRemotableXS;
soap\Soap.InvokeRegistry.pasTSOAPAttachmentClass = class of TSOAPAttachment;
soap\Soap.InvokeRegistry.pasTSOAPHeaderClass = class of TSOAPHeader;
.
soap\wsdlimporter\WSDLCppWriter.pasTWSDLCppWriterClass = class of TWSDLCppWriter;
.
soap\wsdlimporter\WSDLImpWriter.pasTWSDLWriterClass = class of TWSDLWriter;
.
soap\wsdlimporter\WSDLpasWriter.pasTWSDLpasWriterClass = class of TWSDLpasWriter;
.
tethering\System.Tether.Manager.pasTTetheringAdapterClass = class of TTetheringAdapter;
tethering\System.Tether.Manager.pasTTetheringProfileClass = class of TTetheringProfile;
tethering\System.Tether.Manager.pasTTetheringProtocolClass = class of TTetheringProtocol;
.
ToolsAPI\ActionEditors.pasTIDEActionsClass = class of TIDEActions;
.
ToolsAPI\DataExplorerAPI.pasTFrameClass = class of TFrame;
.
ToolsAPI\DesignIntf.pasTComponentEditorClass = class of TBaseComponentEditor;
ToolsAPI\DesignIntf.pasTComponentGuidelinesClass = class of TBaseComponentGuidelines;
ToolsAPI\DesignIntf.pasTCustomModuleClass = class of TBaseCustomModule;
ToolsAPI\DesignIntf.pasTDragTargetClass = class of TDragTarget;
ToolsAPI\DesignIntf.pasTPropertyEditorClass = class of TBasePropertyEditor;
ToolsAPI\DesignIntf.pasTSelectionEditorClass = class of TBaseSelectionEditor;
.
ToolsAPI\PropertyCategories.pasTPropertyCategoryClass = class of TPropertyCategory;
.
ToolsAPI\TreeIntf.pasTComponentSprigClass = class of TComponentSprig;
ToolsAPI\TreeIntf.pasTRootSprigClass = class of TRootSprig;
ToolsAPI\TreeIntf.pasTSprigClass = class of TSprig;
.
vcl\CtlPanel.pasTAppletModuleClass = class of TAppletModule;
vcl\CtlPanel.pasTCPLAppletClass = class of TAppletModule;
vcl\CtlPanel.pasTDataModuleClass = class of TDataModule;
.
vcl\Vcl.ActnCtrls.pasTCustomToolScrollBtnClass = class of TCustomToolScrollBtn;
.
vcl\Vcl.ActnList.pasTActionLinkClass = class of TActionLink;
.
vcl\Vcl.ActnMan.pasTActionBarItemClass = class of TActionBarItem;
vcl\Vcl.ActnMan.pasTActionBarsClass = class of TActionBars;
vcl\Vcl.ActnMan.pasTActionClientClass = class of TActionClient;
vcl\Vcl.ActnMan.pasTActionClientItemClass = class of TActionClientItem;
vcl\Vcl.ActnMan.pasTActionClientLinkClass = class of TActionClientLink;
vcl\Vcl.ActnMan.pasTActionClientsClass = class of TActionClients;
vcl\Vcl.ActnMan.pasTCommandPropertiesClass = class of TCommandProperties;
vcl\Vcl.ActnMan.pasTCustomActionBarClass = class of TCustomActionBar;
vcl\Vcl.ActnMan.pasTCustomActionClass = class of TCustomAction;
vcl\Vcl.ActnMan.pasTCustomActionControlClass = class of TCustomActionControl;
vcl\Vcl.ActnMan.pasTCustomColorMapClass = class of TCustomActionBarColorMap;
.
vcl\Vcl.ActnMenus.pasTAddRemoveItemClass = class of TCustomAddRemoveItem;
vcl\Vcl.ActnMenus.pasTCustomAddRemoveItemClass = class of TCustomAddRemoveItem;
vcl\Vcl.ActnMenus.pasTCustomizeActionToolBarClass = class of TCustomizeActionToolBar;
vcl\Vcl.ActnMenus.pasTCustomMenuExpandBtnClass = class of TCustomMenuExpandBtn;
vcl\Vcl.ActnMenus.pasTCustomPopupClass = class of TCustomActionPopupMenu;
vcl\Vcl.ActnMenus.pasTMenuButtonControlClass = class of TCustomMenuButton;
vcl\Vcl.ActnMenus.pasTMenuItemControlClass = class of TCustomMenuItem;
.
vcl\Vcl.AxCtrls.pasTActiveFormClass = class of TActiveForm;
vcl\Vcl.AxCtrls.pasTActiveXControlClass = class of TActiveXControl;
vcl\Vcl.AxCtrls.pasTPropertyPageClass = class of TPropertyPage;
.
vcl\Vcl.ButtonGroup.pasTGrpButtonItemClass = class of TGrpButtonItem;
vcl\Vcl.ButtonGroup.pasTGrpButtonItemsClass = class of TGrpButtonItems;
.
vcl\Vcl.CaptionedDockTree.pasTCaptionedDockTreeClass = class of TCaptionedDockTree;
vcl\Vcl.CaptionedDockTree.pasTDockCaptionDrawerClass = class of TDockCaptionDrawer;
.
vcl\Vcl.CategoryButtons.pasTBaseButtonItemClass = class of TBaseButtonItem;
vcl\Vcl.CategoryButtons.pasTButtonCategoriesClass = class of TButtonCategories;
vcl\Vcl.CategoryButtons.pasTButtonCategoryClass = class of TButtonCategory;
vcl\Vcl.CategoryButtons.pasTButtonItemActionLinkClass = class of TButtonItemActionLink;
vcl\Vcl.CategoryButtons.pasTButtonItemClass = class of TButtonItem;
.
vcl\Vcl.ComCtrls.pasECommonCalExceptClass = class of Exception;
vcl\Vcl.ComCtrls.pasTComboBoxExStringsClass = class of TComboBoxExStrings;
vcl\Vcl.ComCtrls.pasTComboExItemClass = class of TComboExItem;
vcl\Vcl.ComCtrls.pasTComboExItemsClass = class of TComboExItems;
vcl\Vcl.ComCtrls.pasTConversionClass = class of TConversion;
vcl\Vcl.ComCtrls.pasTHeaderSectionClass = class of THeaderSection;
vcl\Vcl.ComCtrls.pasTListColumnClass = class of TListColumn;
vcl\Vcl.ComCtrls.pasTListColumnsClass = class of TListColumns;
vcl\Vcl.ComCtrls.pasTListItemClass = class of TListItem;
vcl\Vcl.ComCtrls.pasTStatusPanelClass = class of TStatusPanel;
vcl\Vcl.ComCtrls.pasTToolButtonActionLinkClass = class of TToolButtonActionLink;
vcl\Vcl.ComCtrls.pasTTreeNodeClass = class of TTreeNode;
.
vcl\Vcl.Controls.pasTControlActionLinkClass = class of TControlActionLink;
vcl\Vcl.Controls.pasTControlClass = class of TControl;
vcl\Vcl.Controls.pasTDockTreeClass = class of TDockTree;
vcl\Vcl.Controls.pasTDragObjectClass = class of TDragObject;
vcl\Vcl.Controls.pasTHintWindowClass = class of THintWindow;
vcl\Vcl.Controls.pasTPanningWindowClass = class of TCustomPanningWindow;
vcl\Vcl.Controls.pasTWinControlActionLinkClass = class of TWinControlActionLink;
vcl\Vcl.Controls.pasTWinControlClass = class of TWinControl;
.
vcl\Vcl.DockTabSet.pasTTabDockPanelClass = class of TTabDockPanel;
.
vcl\Vcl.ExtCtrls.pasTCategoryPanelClass = class of TCustomCategoryPanel;
vcl\Vcl.ExtCtrls.pasTCategoryPanelSurfaceClass = class of TCategoryPanelSurface;
vcl\Vcl.ExtCtrls.pasTEditButtonClass = class of TEditButton;
.
vcl\Vcl.Forms.pasTCustomFormClass = class of TCustomForm;
vcl\Vcl.Forms.pasTCustomFrameClass = class of TCustomFrame;
vcl\Vcl.Forms.pasTFormClass = class of TForm;
.
vcl\Vcl.Graphics.pasTGraphicClass = class of TGraphic;
.
vcl\Vcl.Imaging.GIFImg.pasTColorLookupClass = class of TColorLookup;
vcl\Vcl.Imaging.GIFImg.pasTGIFAppExtensionClass = class of TGIFApplicationExtension;
vcl\Vcl.Imaging.GIFImg.pasTGIFExtensionClass = class of TGIFExtension;
.
vcl\Vcl.Imaging.pngimage.pasExceptClass = class of Exception;
vcl\Vcl.Imaging.pngimage.pasTChunkClass = class of TChunk;
.
vcl\Vcl.ListActns.pasTListControlItemClass = class of TListControlItem;
.
vcl\Vcl.Menus.pasTMenuActionLinkClass = class of TMenuActionLink;
.
vcl\Vcl.OleAuto.pasTAutoClass = class of TAutoObject;
.
vcl\Vcl.ScreenTips.pasTScreenTipsWindowClass = class of TScreenTipsWindow;
.
vcl\Vcl.StdActns.pasTCommonDialogClass = class of TCommonDialog;
.
vcl\Vcl.StdCtrls.pasTButtonActionLinkClass = class of TButtonActionLink;
vcl\Vcl.StdCtrls.pasTCustomComboBoxStringsClass = class of TCustomComboBoxStrings;
vcl\Vcl.StdCtrls.pasTPushButtonActionLinkClass = class of TPushButtonActionLink;
.
vcl\Vcl.SvcMgr.pasTServiceClass = class of TService;
.
vcl\Vcl.Themes.pasTCustomElementServicesClass = class of TCustomElementServices;
vcl\Vcl.Themes.pasTCustomStyleEngineClass = class of TCustomStyleEngine;
vcl\Vcl.Themes.pasTCustomStyleServicesClass = class of TCustomStyleServices;
vcl\Vcl.Themes.pasTStyleHookClass = class of TStyleHook;
vcl\Vcl.Themes.pasTSysStyleHookClass = class of TSysStyleHook;
.
vcl\Vcl.Touch.GestureMgr.pasTGestureStreamDataClass = class of TGestureStreamData;
.
vcl\Vcl.Touch.Gestures.pasTCustomGestureRecognizerClass = class of TCustomGestureRecognizer;
vcl\Vcl.Touch.Gestures.pasTGestureEngineClass = class of TCustomGestureEngine;
.
vcl\Vcl.Touch.Keyboard.pasTCustomKeyboardButtonClass = class of TCustomKeyboardButton;
.
vcl\Vcl.WinXCtrls.pasTToggleSwitchActionLinkClass = class of TToggleSwitchActionLink;
.
vcl\Vcl.WinXPanels.pasTCardClass = class of TCard;
.
vcl\Vcl.WinXPickers.pasTPickerColumnClass = class of TPickerColumn;
.
xml\Xml.adomxmldom.pasTox4DOMNodeClass = class of Tox4DOMNode;
.
xml\Xml.Internal.AdomCore_4_3.pasTXmlSignalClass = class of TXmlSignal;
.
xml\Xml.Internal.CodecUtilsWin32.pasTUnicodeCodecClass = class of TUnicodeCodec;
.
xml\Xml.Internal.EncodingUtils.pasTEncodingInfoClass = class of TEncodingInfo;
.
xml\Xml.Internal.OmniXML.pasOmniTXMLAttrClass = class of OmniTXMLAttr;
xml\Xml.Internal.OmniXML.pasOmniTXMLCDATASectionClass = class of OmniTXMLCDATASection;
xml\Xml.Internal.OmniXML.pasOmniTXMLCommentClass = class of OmniTXMLComment;
xml\Xml.Internal.OmniXML.pasOmniTXMLDocumentTypeClass = class of OmniTXMLDocumentType;
xml\Xml.Internal.OmniXML.pasOmniTXMLElementClass = class of OmniTXMLElement;
xml\Xml.Internal.OmniXML.pasOmniTXMLProcessingInstructionClass = class of OmniTXMLProcessingInstruction;
xml\Xml.Internal.OmniXML.pasOmniTXMLTextClass = class of OmniTXMLText;
.
xml\Xml.omnixmldom.pasTODOMNodeClass = class of TODOMNode;
.
xml\Xml.Win.msxmldom.pasTMSDOMNodeClass = class of TMSDOMNode;
xml\Xml.Win.msxmldom.pasTMSXMLDOMDocumentFactoryClass = class of TMSXMLDOMDocumentFactory;
.
xml\Xml.XMLDoc.pasTXMLNodeClass = class of TXMLNode;
xml\Xml.XMLDoc.pasTXMLNodeCollectionClass = class of TXMLNodeCollection;
.
xml\Xml.XMLSchema.pasTSchemaTranslatorClass = class of TXMLSchemaTranslator;


How to create a "Cheat Sheet" of your favorite @Lynda courses

$
0
0
I finally purchased Camtasia a couple weeks ago so I could create videos in support the upgrade I'm doing of my Zilch Standard software. This is just an interim upgrade that I'm doing using an old copy of Delphi 5 with source code from 2006, even though I have Delphi 10.3 😎

I love lynda.com. There are so many courses available on many different subjects. The best part is my free access through the Boston Public Library (bpl.org). I was paying $40 a month until a co-worker told me I could get access for free using my BPL e-card. Check and see if your local library offers free access to @Lynda.

It's a shame that there are no Delphi or Object Pascal courses on @Lynda. There's a story there but you'll have to ask me in person about this one.

Anyway, had forgotten what the instructor said was the best screen resolution to use. So, I logged back into my account and pulled up my course history. Did a search for what I was looking for and then discovered a hidden gem inside all the courses.

"I discovered a hidden gem inside all the courses"


Notes. I knew you could take notes but I didn't know you could turn those notes into a permanent "cheat sheet". So, I decided to just make a video on how to take notes. It's good practice for me and hopefully you find it beneficial.


Please like this video and subscribe to my YouTube channel.

These are the two courses I took on Lynda:
Camtasia 2019 Essential Training: The Basics (2hr 5min)

Camtasia 2019 Essential Training: Advanced Techniques (2hr 21min)

For voice recording I used my Microsoft LifeChat LX-6000 headphones with built in microphone which I purchased in March 2014.

Enjoy,
Semper Fi
Gunny Mike


The question "What do you want to be when you grow up?" is not just for kids.

$
0
0

  1. Stop your busy life for just three-and-a-half minutes. 
  2. Watch this video.
  3. Answer this question, "What do you want to be when you grow up?" in the comments.



How I use my "Like Buckets"

When I grow up, I want to tell stories. Not just any kind of stories. Stories that inspire people. People just like you. To help me get there, I joined Toastmasters two years ago. Right now, I have three priorities all competing for my spare time. One of those priorities is getting an updated copy of my software out there. The sooner this is done, the less competition there is for my spare time. 😃

What do you want to be when you grow up?

( Answer in the comments below )

Semper Fi
Gunny Mike
https://zilchworks.com


Applications Built With Delphi ( Add yours with 3 clicks )

$
0
0
Here is a link to a web page I found that lists applications built with Delphi.

https://delphi.fandom.com/wiki/Good_Quality_Applications_Built_With_Delphi

The best part about this site... you can add your own software if it's not listed. It comes with a built in editor:


When you click on "Edit" and edit window pops up. Scroll down until you find the appropriate spot to add your software. Click on the left cell and it becomes highlighted.


Click on the little arrow to the left and you will be prompted to insert a row above or below the highlighted cell.


Then simply add your software product and give it a link to your product page. Then add your software description in the right cell.

If you know of a website or web page that maintains a list of applications created with Delphi please add it using the comments. I will keep this post updated with all the links provided.

Cheers,
Gunny Mike
https://zilchworks.com

30 Year Old Delphi/Turbo Pascal App Still Relevant

$
0
0

Once upon a time there was an enthusiastic, ambitious, twenty-seven year old man who spent sixty dollars on a two-inch thick book with a funny looking floppy-disk measuring exactly five-and-a-quarter inches tucked in the back. To him, it was just another book he bought at the college book store. A book he needed as a requirement for a class on learning how to program a computer using a compiler. This was the next step. Beforehand, it was some introductory class on programing using BASIC. Yup 1986 was going to be a good year.

That young man was me. I was a Sergeant in the United States Marine Corps. I spent a year or so taking college classes at night working my way toward a degree. T'was was a pretty good deal. The Marine Corps was paying eighty percent of my tuition as long as I passed my classes. No problem.

Until, the admissions office said, "Mr. Riley, you have taken too many electives. You won't be able to enroll in any more classes until you complete English 101". No. Not  English 101. Not Again!

"Not English 101. Not Again"

I had taken the English Composition with Essay CLEP test six months ago and missed a passing grade by one point. One lousy, miserable, stinking point. It felt like Mr. Potter from It's a Wonderful Life had snatched my dream. It was at that moment my college career ended. Me and English 101, let's just say we don't get along.

I turned on my computer and said "let's see what this Turbo Pascal can do". I decided to create a flash card program for my young daughter. I didn't want her to read the screen, type an answer, and hit enter. Oh no, that would be too much work. I wanted her to use the arrow keys and move around and pick the right answer. No data entry for my little girl. 

With the help of two books by Ray Duncan I learned how to trap the keyboard using Interrupt 21 function calls. I also learned how to use Interrupt 10 to save and restore screen memory creating nice, fancy popup windows. Later I added true shadows to my popup windows by disabling the blinking bit which gave me a whopping 16 background colors. 

This was all fine and dandy. It kept me busy. Never really made a worthwhile "flash card" program. But I learned a lot. And it sure beat trying to master English 101.


Thus began my pursuit to create an exciting, functional working computer program. But where do I begin. I decided to modernize an old BASIC program I created back in 1986. After a year of nights and weekends that involved typing, cursing, compiling, retyping, more cursing, recompiling, printing, adjusting, readjusting, more typing and recompiling I had a program. I called it Zilch. The year was 1991. And there were two things I didn't know:

  1. Would anyone purchase this software?
  2. How long would it last?

Zilch v1.19 from 1995

Well it's been 30 years and tens of thousands of people have purchased this software. And it's still selling 30 years later.

Learn to tell a story

The past 30 years have been fraught with many hardships and many more wonderous moments. I would like to share a few insights I have gained over the years. Hopefully you'll find some of them useful.

  • Do not fall in love with your product. My software was "my baby". I spent a lot of time creating it  and I loved it. I nurtured it and most of all, told the world how lovely it was. When people tried to tell me what they thought about my software, I didn't listen. They would say "I'd buy your software if it did [ fill in the blank]".  I quickly jumped in, cut them off and said stuff like "No, you don't understand how my software works. It does this and you need to...". People were to kind to hang up mid sentence. They just didn't buy. 
  • 3,000 Views = 100 Downloads = 1 Sale. It's hard to get people to part with their money. Make sure you value every customer. 
  • Beware of copycats. From day one when you release your software competitors will find you. Better mousetraps are always being invented based off of similar designs. If you have a pioneering idea you will be copied. 
  •  Preserve screenshots and early drawings. At the start you never know how long your software will last or how many changes it will go through. Take pictures and preserve the memories from your early efforts. Someday you will want to look back at how it all started. Trust me.
  • Learn to tell a story. This is where I am at, right now after 30 years. It's taken me a long time. The story I have been telling for 30 years was a "transactional" story. It's been the story of what my software does, how it works, a litany of the features. Learn how to connect "emotionally" with your customers. People don't care about you or your software. They care about themselves, and how you can make their lives better.

I wish I played with my kids more


Do I have any regrets over the past 30 years. You betcha. Here's a few:
  • I wish I played with my kids more when they interrupted my keyboard time. Instead of saying "No, not now I'm busy". I should have spent 15-20 minutes playing catch or dolls. 
  • I failed to keep in touch with people who helped me along the way. I failed to keep in touch with Steve Payne from Craven Community College. He was my first in-person computer programming instructor. Hey Steve if you see this contact me..
  • Not listening to potential customers. (See Do not fall in love with product )
  • Having a Microsoft Windows only product.
Zilch v29.7 from 2020


Cheers,
Gunny Mike


Viewing all 139 articles
Browse latest View live