Simple code sharing on macOS

For anyone not using version control perhaps the easiest way to share code between various projects is to use external items. However, not everything cam be made external. Modules that contain classes & other modules cant be shared via external items.

So how can you share these ?

On macOS the answer is … ALIASES !!!!!

The trick is how you set things up when you want to share such a thing between various projects. But this only works for TEXT projects because text projects save everything in separate files. For binary & XML projects you really dont have much in the way of choices when it comes to sharing Modules with other classes & modules in them.

And, if you ARE using TEXT projects then its also very likely you ARE using version control. So this tip is really for a very narrow set of use cases.

Basically what we need to do is set up an aliases that refers to the code we want to share instead of the code in our project.

Lets walk through a really simple example.

First create a new folder on your desktop – I called mine Code Sharing Example.

Now lets create a new project and add a module that we’re going to share between this new project and others.

And we’ll save this in a new folder we create inside the new folder we created earlier. I named the folder this is saved in Originator since we’re going to have the original source in this project and others will use it.Make sure you save the project as a TEXT, or Xojo Project. I also named the project Originator.xojo_project

This forms the basis of what we want to share.

Start a new desktop project. In this example I happen to use a desktop project but it could be a web or console app. iOS is harder to do this with because it uses the new, now deprecated Xojo framework so data type compatibility may be an issue. However, if you are using the new framework in desktop or other apps then you can use the same technique.

In this new app we are going to add a PROXY module that we will fix up manually later. Its just a placeholder. But it does make life simpler if you name this the same as the one we wanted to bring in from the other project. So we’ll name it ToBeShared like in the Originator project.

Now save this project in a new folder inside the Code Sharing Folder. I named mine Second Project and the project also named Second Project.

And now the manual work to make it so we can share the module.

A module, when saved as a text project, is a file. If you look in the folder that holds the Originator Project you will see a file named ToBeShared.xojo_code as well as a directory named ToBeShared.

In order to get Second Project to read the Originator Projects module and contained classes we need to replace the Second Projects file on disk with an alias named exactly the same – extension and everything. And we also need to make sure we have an alias to the directory with the ToBeShared name so the IDE realizes that this is a module with contents.

Since we added the proxy in the Second Project with the exact name we do not need to do anything to the project manifest.

First navigate to Second Project and remove ToBeShared.xojo_code

Then navigate to Originator Project and select ToBeShared.xojo_code and the ToBeShared directory. And press Cmd + Ctrl + A (or right click and select make alias)

Move these two aliases to the Second Project directory.

We’re almost done.

Navigate to Second Project.

Change ToBeShared alias so its names is only ToBeShared without the alias as part of the name. And ToBeShared.xojo_code alias to ToBeShared.xojo_code again with the alias part removed.

Open the Originator project & add a method to the Shareable class and the ToBeShared module. Save.

Now open the Second Project and you should see those just added items.

And here is where norm realizes he missed a step 😛

Thats ok though.

I’ll show you how I fixed it and what I messed up earlier.

To explain the problem. We should have added not just a proxy that we’d fix for the module but the Shareable class inside it as well. This would have made it so we did not need to manually fix the manifest and creating the aliases would have been sufficient. If you were to go back to that step & add the class then everything would just work now.

But since I missed that step I’ll manually fix the manifest. If you opened the Second project what was missing is the class that we added inside the ToBeSharedModule. The reason is that the manifest does not know there is such a class.

In the Originator project you would see something like

Module=ToBeShared;ToBeShared.xojo_code;&h000000000F00AFFF;&h0000000000000000;false
Class=ShareableClass;ToBeShared/ShareableClass.xojo_code;&h00000000580AC7FF;&h000000000F00AFFF;false

But when you open the Second Project manifest, because we did not add the class to the module, you only see something like

Module=ToBeShared;ToBeShared.xojo_code;&h0000000042EA77FF;&h0000000000000000;false

and no entry for the class. And thats the problem. The manifest lists everything a project is composed of and since there is no entry for the class the class contained by the module doesnt show. And the fix is simply to copy the lines for the module & class in the manifest from the Originator project and replace the one line for the module in the Second Project.*

And once you copy that across & save the manifest and reopen the project there’s the code from the Originator project. And if you make chnages to this code in the second project and reopen the originator project the changes will exist there as well.

One thing to take care with – do NOT open both projects at the same time and edit in both. You are sure to cause yourself problems.

If you add new classes to either Originator or Second Project you will have to do many of the same manifest file changes over again so each project knows about the added items.

*When manually editing a manifest there are some things to be careful about. One is the two hexadecimal entries on the line. The first is the items id and the second the id of the item that contains this one. These items need to be unique within a project. Take care to make sure this is true otherwise you run the very real risk of messing your project manifest up to the point your project won’t load.

Code for safety II

Back when I wrote Code for Safety I gave one example of something that I’m sure we’ve all done from time to time. Saving the state of some flag and then returning and having possibly multiple places to reset the flag we saved.

There’s another thats is more common with the advent of HiDPI support for Windows; drawing in a listbox event like CellBackGroundPaint Saving the graphics state, disabling antialiasing, and then needing to restore that state.

This is especially true on Windows where you may need to disable anti-aliasing, change the foreground color & fill the background to achieve some custom effect. If you dont disable anti-aliasing you can end up with odd effects as noted in the documentation and this post.

Having a way to save the existing state and consisntenly and reliably restore it to what it was when the method was called is useful since you dont know if the graphics object is passed on to other events like CellTextPaint. Not restoring the state may lead to unwanted side effects.

So how to make it so you can simply save the state and never worry about it being restored properly ? As I mentioned before ; a class that has almost nothing to it but a constructor & a destructor.

My implementation is called GraphicsStatePreserver.

The constructor is simply

Sub Constructor(g as graphics)
  mG = g
  
  Me.mAntiAlias = g.AntiAlias
  Me.mAntiAliasMode = g.AntiAliasMode
  Me.mBold = g.Bold
  Me.mCharacterSpacing = g.CharacterSpacing
  Me.mForeColor = g.ForeColor
  Me.mItalic = g.Italic
  Me.mLastPage = g.LastPage
  Me.mPenHeight = g.PenHeight
  Me.mPenWidth = g.PenWidth
  Me.mScaleX = g.ScaleX
  Me.mScaleY = g.ScaleY
  Me.mTextFont = g.TextFont
  Me.mTextSize = g.TextSize
  me.mtextunit = g.TextUnit
  Me.mTransparency = g.Transparency
  Me.mUnderline = g.Underline
  
End Sub

We save the current state of the various writeable properties of the graphics into locals, and a hard reference to the graphics object. We keep a hard reference to make sur it cannot go out of scope before we have done our thing to restore the various settings.

The destructor is equally simple

Sub Destructor()
  mg.AntiAlias = Me.mAntiAlias 
  mg.AntiAliasMode = Me.mAntiAliasMode 
  mg.Bold = Me.mBold 
  mg.CharacterSpacing = Me.mCharacterSpacing 
  mg.ForeColor = Me.mForeColor 
  mg.Italic = Me.mItalic 
  mg.PenHeight = Me.mPenHeight 
  mg.PenWidth = Me.mPenWidth 
  mg.ScaleX = Me.mScaleX 
  mg.ScaleY = Me.mScaleY 
  mg.TextFont = Me.mTextFont 
  mg.TextSize = Me.mTextSize 
  mg.textunit = Me.mTextUnit
  mg.Transparency = Me.mTransparency 
  mg.Underline = Me.mUnderline 
  
End Sub

When this class is destroyed it will put back all the properties on the graphics just as they were when the instance was created.

And since an instance is used like

Dim statepreserver As New GraphicsStatePreserver(g)

#If targetwindows
  g.AntiAlias = False
#EndIf

If row < 0 Then 
  Return True
End If
If column < 0 Then 
  Return True
End If

// whatever othe r code you need in cell background paint

Return True

This instance will ONLY go out of scope, and be destroyed, at the points where the event is being exited. And that happens to be the exact points we want to restore the graphics state.

Here’s the entire implementation

Class GraphicsStatePreserver
	Sub Constructor(g as graphics)
	  mG = g
  
	  Me.mAntiAlias = g.AntiAlias
	  Me.mAntiAliasMode = g.AntiAliasMode
	  Me.mBold = g.Bold
	  Me.mCharacterSpacing = g.CharacterSpacing
	  Me.mForeColor = g.ForeColor
	  Me.mItalic = g.Italic
	  Me.mLastPage = g.LastPage
	  Me.mPenHeight = g.PenHeight
	  Me.mPenWidth = g.PenWidth
	  Me.mScaleX = g.ScaleX
	  Me.mScaleY = g.ScaleY
	  Me.mTextFont = g.TextFont
	  Me.mTextSize = g.TextSize
	  me.mtextunit = g.TextUnit
	  Me.mTransparency = g.Transparency
	  Me.mUnderline = g.Underline
  
	End Sub

	Sub Destructor()
	  mg.AntiAlias = Me.mAntiAlias 
	  mg.AntiAliasMode = Me.mAntiAliasMode 
	  mg.Bold = Me.mBold 
	  mg.CharacterSpacing = Me.mCharacterSpacing 
	  mg.ForeColor = Me.mForeColor 
	  mg.Italic = Me.mItalic 
	  mg.PenHeight = Me.mPenHeight 
	  mg.PenWidth = Me.mPenWidth 
	  mg.ScaleX = Me.mScaleX 
	  mg.ScaleY = Me.mScaleY 
	  mg.TextFont = Me.mTextFont 
	  mg.TextSize = Me.mTextSize 
	  mg.textunit = Me.mTextUnit
	  mg.Transparency = Me.mTransparency 
	  mg.Underline = Me.mUnderline 
  
	End Sub

	Private Property mAntiAlias As boolean
	Private Property mAntiAliasMode As Graphics.AntiAliasModes
	Private Property mBold As boolean
	Private Property mCharacterSpacing As Integer
	Private Property mForeColor As color
	Private Property mG As graphics
	Private Property mItalic As boolean
	Private Property mLastPage As Integer
	Private Property mPenHeight As double
	Private Property mPenWidth As double
	Private Property mScaleX As double
	Private Property mScaleY As double
	Private Property mTextFont As string
	Private Property mTextSize As Single
	Private Property mtextunit As FontUnits
	Private Property mTransparency As double
	Private Property mUnderline As boolean

End Class

How do you raiseevent ?

When you create a custom control from a canvas, container control or some other base class its not unusual to use raise event to create new events like “BeforeButtonPressed”, “AfterButtonPressed” or to just make it so you;r control can implement normal events like Open, Close, etc and pass then along.

You’ll see code using raiseevent to cause these events to be raised on any instances.

But, you can use raiseevent for much more than this and use it in places you might not expect.

For instance, you might want an event to return a boolean as well as some parameter byref much like the SelectColor method does.

Dim c As Color

If SelectColor(c) Then
  // use the color selected
Else
  // c is still "default"
End If

This isnt so bad and it easy to imagine writing the SelectColor method to do exactly this. But what if SelectColor is an event in a custom control you’re writing ? You might be tempted to write

Dim c As Color

Dim b As Boolean = SelectColor(c)

If b Then
  // use the color selected
Else
  // c is still "default"
End If

But in a way this hides the fact that SelectColor IS an event your code is calling for an instance to implement. Believe it or not you CAN write

Dim c As Color

Dim b As Boolean = RaiseEvent SelectColor(c)

If b Then
  // use the color selected
Else
  // c is still "default"
End If

And this then is more obvious that SelectColor IS an event that an instance might have implemented. But, since you can write that why not

Dim c As Color = &cffffff

If RaiseEvent SelectColor(c) Then
  // use the color selected
Else
  // c is still "default"
End If

And once again this works just as we expect. And it helps make your code clearer that you ARE calling an event that an instance may have implemented. And, code clarity is a good thing.

I think if you try you might be surprised at the spots where putting RaiseEvent works exactly as expected and helps improve code clarity.

Dynamic menu handling

A lot of times when you need to create a menu at runtime you don’t know the number of items and those items may require some additional data to be handled correctly like a folder item, window reference etc. And in cases like this a subclass of MenuItem makes sense so you can add properties to the subclass and implement the action event to handle that extra data. This is a common pattern for things like the Window menu, Font menu and Recent items.

But its not the only way to handle a dynamic menu.

Suppose you want to add a menu at runtime that is well known before hand and depending on which user is using your application the menu has different entries ?

In such a case you can define everything ahead of time and it will all behave just like if you had defined it in the IDE menu editor.

The trick is to know that the “AutoEnable” setting for menus is dependent on the NAME of the menu item, not the text it contains.

Lets see how we can take advantage of this fact.

Create a new desktop application. In Window1’s open event we’re going to add a menu, and some items to that menu.

Add the following code to the Open event


Dim SelfMenu As New MenuItem("Self Service")
Self.MenuBar.Append SelfMenu
SelfMenu.Enabled = True

Dim fMenu As MenuItem

fMenu = New MenuItem("selfchgpswd")
fMenu.Name = "selfchgpswd"
fMenu.Text = "Change Password"
fMenu.AutoEnable = True
SelfMenu.Append(fMenu)

Note that we have set the name. THIS is crucial. And each item added should have a unique name otherwise you can end up with odd problems.

If you run at this point you should see the menu is added but the items are not enabled. But note that we DID set the AutoEnable property. The Xojo runtime will automatically enable this item IF there is a handler for it on our Window.

So lets add a menu handler for selfchgpswd (menu handlers use the NAME to match the menu item with the menu handler to be enabled & executed)

Right click on Window1 in the navigator and select “Add to Window1” and add a menu handler. In the editor field for the menu handler name put in selfchgpswd. You CAN define a menu handler for menu items that do not exist.

In the menu handler simply add

break

And now run.

First you will see that the menu is added AND the item is now enabled ! And if you select that item from the menu you should stop at the break statement – the Code in the menu handler IS being called exactly as we expected.

At the end of the day this gives you a second way to handle menu items that you add and remove at runtime and, in this case, you know beforehand and can add the code just like you would have if you had defined the menu in the Menu editor.

Enjoy !

Much ado about bevel buttons

One of the things that transitioning from the old Classic framework, which was based on Carbon and HIToolbox, to Cocoa brought about was changes to lots of controls. Bevel buttons fall into that boat and now with the support of Dark Mode they look awful – because bevel buttons are based on an old style control that has no modern equivalent in 64 bit Cocoa.

And even worse you have no way to force them to be dark mode compliant – again because the control they are based on doesn’t have a modern equivalent. I’m working on a project that used them in various ways and updating everything to be dark mode compliant was darned hard when you have such controls in use.

Now its easy to say “Oh well Xojo should just update that to whatever is the modern equivalent”. And there’s the problem. There isn’t one that matches all the things that bevel button can do. So Xojo could update to some modern style on macOS but that would silently break code. Or, as they have done so far, do nothing and encourage people to use some other control. That’s not always practical advice either. For this project I’m working on we’ve taken a different path and actually made our own replacement. It supports dark mode. And menus and icons and most everything the existing bevel button does. We’ve left out things that arent used but they would not be hard to add.

And now the entire project is moving along, is looking great in macOS dark mode, and we have a nice cross platform bevel button that we can continue to use.

With that said here’s the replacement we made. It also happens to include some code for Semantic Colors. The project uses the MBS plugins based code so the non-MBS based code may, or may not be, suitable for your uses.

If you ever need to crash

No. Not at my place 😛

Sometimes you NEED to test your app and whatever remediation you have put in place and need a reliable way to make your app crash

Here it is

Dim p As ptr
p.Byte(0) = 123

Those two lines. Insert them anywhere you might put normal code and book your app will crash.

Let the compiler help you

An interesting question posed by a relatively new user came up on the forums. It had to do with how the Array() function worked and how he could force it to return an array of variants to call his method.

His method was defined like

BuildMenu( base as menuitem, params() as variant)

And he would call it like

Result = BuildMenu (base, Array ("Run", False, "Edit", "Rename", "Delete") )

and this would build up a menu from the base item and append menu items to it. Those new menu items were configured by the array of variants

‘False’ means the previous menu item is disabled (‘True’ means it is enabled, but is optional and assumed)

Because the boolean elements are optional, it is possible to make a call using only string elements, such as:

Result = BuildMenu (base, Array (“Run”, “Edit”, “Rename”, “Delete”) )

But when I do this, I get the following compile error:

So he’s trying to figure out how to force Array to return variants ALL the time. Which is the wrong question to ask in many ways.

Using an array of variants means the compiler cannot help you out and tell you when you are trying to assign the wrong kind of value to another.

Normally if you did something like

dim foo as integer
foo = "123"

the compiler will warn you about this. But if you change this simple code to :

dim foo as variant
foo = "123

you will no longer get a compile error. And depending on how you use “foo” you may, or may not, get some kind of runtime error.

This is why some of the long time users of Xojo suggest avoiding variants unless you absolutely cannot do something any other way.

Variants make it possible to bypass and avoid type checking by the compiler. What this means is that by using a variant you are possibly only finding out about errors at runtime because the compiler cannot do its normal helpful type checking to make sure you catch errors early on.

So in this particular thread on the forums the right question isn’t “how to force the array() function to always return a variant array” but “how to design this particular API so variants are not required”. And that is certainly possible since Xojo supports variable argument lists, optional parameters and other features that retain strong type checking at compile time AND still allow for a good degree of flexibility in designing an API.

And I made a suggestion on that thread :

Result = BuildMenu (base, BuildItem( “Run”, false), BuildItem(“edit”), BuildItem(“Rename”), BuildItem(“Delete”))
with buildmenu defined as

Sub BuildMenu(base as menuitem, paramarray items as menuitem)
// just append the items passed to the base
for i as integer = 0 to items.ubound
base.append items(i)
next
end sub

and BuildItem as

Function BuildItem(name as string, enabled as boolean = false)
dim item as new menuitem(name)
item.enabled = enabled
return item
end function

Avoid variants where you can and design an API that permits the compiler to help you out at compile time. The earlier you catch possible bugs the easier, and less costly, they are to fix.

The compiler can help you IF you let it.

Speed tip with popupmenus

In a different forum a user reminded me of a speed tip that had cropped up once or twice over the years. It has to do with popupmenus.

Basically on older versions of macOS if you did


PopupMenu1.ListIndex = -1
For i As Integer = 0 To 1000
  PopupMenu1.AddRow Str(i)
Next

This behaved not so badly although there is a slightly faster variation

But on Mojave this is noticeably slow. This simple loop could take as much as a second on Mojave in dark mode.

To speed it up it was discovered that if you added an item in position(0), set the listindex to 0 then added all the items and removed that extraneous item at position 0 then the speed was hardly impacted at all.

PopupMenu2.AddRow ""
PopupMenu2.ListIndex = 0
For i As Integer = 0 To 1000
  PopupMenu2.AddRow Str(i)
Next
PopupMenu2.RemoveRow 0

My testing on 10.12.6 and 10.14.5, in light and dark modes, using 2019r1.1 with the following code shows the differences in the debugger

Sub Action()
  Dim startTime1 As Double 
  Dim endTime1 As Double
  
  Dim startTime2 As Double 
  Dim endTime2 As Double
  
  
  startTime1 = Microseconds
  PopupMenu1.ListIndex = -1
  For i As Integer = 0 To 1000
    PopupMenu1.AddRow Str(i)
  Next
  endTime1 = Microseconds
  
  
  startTime2 = Microseconds
  PopupMenu2.AddRow ""
  PopupMenu2.ListIndex = 0
  For i As Integer = 0 To 1000
    PopupMenu2.AddRow Str(i)
  Next
  PopupMenu2.RemoveRow 0
  endTime2 = Microseconds
  
  
  Dim total1 As Double = (endtime1 - startTime1) / 1000000
  Dim total2 As Double = (endtime2 - startTime2) / 1000000
  
  label1.text = str(total1, "###.00000")
  label2.text = str(total2, "###.00000")

End Sub

And the results are surprising and consistent between running in the debugger & a built application

Debug              Total1 (slow)       Total2 (fast)
 10.12.6            0.037 sec            0.012 sec
 10.14.5 (light)    1.152 sec            0.019 sec
 10.14.5 (dark)     1.158 sec            0.019 sec 
 Built x86_64       Total1 (slow)       Total2 (fast)
 10.12.6            0.036 sec            0.010 sec
 10.14.5 (light)    1.174 sec            0.017 sec
 10.14.5 (dark)     1.174 sec            0.028 sec

Whatever changed in Mojave Popupmenus got very slow.

Heads up.

Thanks to Jim McKay for reporting this

A new take on listbox resizing

Normally a listbox doesnt support much in the way of live resizing of rows or columns if you dont have a header row.

Turns out that for something else I’m working on I needed to be able to resize things and NOT have a header row – and also make it possible to resize rows as well by clicking & dragging.

And the result so far is this

Live resizing by clicking & moving the grids themselves !

There is no header on this listbox either.

I’ll see if I can release this as a stand alone item.