Saturday, September 7, 2019

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

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

Sunday, August 11, 2019

Delphi Tip of the Day - Rounding (Again!)

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

Monday, July 29, 2019

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

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

Sunday, July 28, 2019

Delphi Tip of the Day - TODO or not TODO

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
https://zilchworks.com

Saturday, July 27, 2019

Delphi Makes Updating Old Code Very Easy (Hidden Gem)

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

Sunday, June 9, 2019

Say what you mean and mean what you say!

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.

Saturday, March 9, 2019

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

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