Xojo 2019r2 & API 2.0

There’s a lot of changes in R2

There are some that are very minor. Like making VAR a synonym for DIM. You can use it or not. It functions no differently.

Some of them are very welcome like updates to URLConnection for one. The JSON Parse and Generate methods seem to work a LOT faster now. And there are a pile of useful extensions on various types. And a lot of fixes in the IDE to make it faster.

The navigator draws a little differently but seems a bit snappier.

Folderitem was updated significantly on macOS to use much more modern API’s. The newer APIs used should make folderitems operate a lot faster on newer versions of macOS. They no longer use the slow code path that Apple had in place that could make APSF volumes really slow with older versions.

A number of things that have been deprecated for a long time are removed so you will have to update code to fix those if you’ve avoided them until now. If you still need to use this code in an older version you can use an #if XojoVersion conditional compilation directive around the old code and it will compile without issue.

That’s the easy stuff.

There are a couple changes that may cause problems as they are silently going to happen just by opening your project in 2019r2.

If you placed TCPSockets, UDPSockets or instances of any of their subclasses on layouts at design time then be aware that the Error event will get a new parameters when you open in 2019r2. This _should_ not have any effect or cause any issues.

If you create folderitems using URL’s check all your inputs to the constructors. I’d strongly recommend you analyze your project and look for deprecated folderitem constructors. If you happened to write code that uses an URL Path with a query portion to get a folder item this behaviour has changed between older versions and 2019r2. The old and new constructors both get this changed behaviour. If you had code that did something like

Dim file As FolderItem = SpecialFolder.Desktop.child("Hello World")
Dim urlpath As String = normFile.URLPath + "?key=value"
Dim resultfile As folderitem = GetFolderItem(urlpath,FolderItem.PathTypeURL)

You’ll find that the result you get in 2019r1.1 and earlier is quite different that what you get in 2019r2. Xojo chose not to fix this behaviour change or introduce a new FileSystemItem class to segregate the old and new behaviour like they did with Date and DateTime. While I understand their issue with updating the entire framework to use this new FileSystemItem or duplicating every function to have old functions returning folderitems and new ones returning FileSystemItems its still disappointing that this reasonably silent behaviour change has happened. I’d be surprised if a lot of people used URL paths this way. I do know its more than zero that did and they’ll have to update their code to deal with this difference.

Another thing that may make updating difficult is that a fairly large number of events and properties got renamed. When you open your project in 2019r2 the renamed events are not automatically updated to their new names so both the old and new event names exist. This may not affect you. The issue I’ve experienced is that if you happened to subclass controls and added events to your subclasses the new names Xojo is using might conflict with the ones you defined previously.

The really hard part is that to fix this you have a hard time doing it 100% accurately in a large projects in 2019r2. Xojo can’t help you because the old event name is still legal to use and instances can implement it. Its just not the event you defined previously in your subclass. Its now the one Xojo defined. If you find you’re in this position make sure you do whatever updating and renaming of your events in 2019r1.1 as the 2019r2 compiler can’t help you find them.

For example if in 2019r1.1 you created the following

Class CustomTextField
Inherits TextField
		Sub TextChange() Handles Event
		  
		End Sub

		EventDef TextChanged()
End Class

and then put an instance of this class on a window layout and Run everything is fine. Save this project and reopen it in 2019R2. You should get a compile error

CustomTextField.TextChanged Declaration
 This property has the same name as an event. You must resolve this conflict
 Event TextChanged()

Xojo added a new TextChanged event to TextField. And now the event definition you had in your subclass now conflicts with the Xojo one. If you alter your TextChanged event definition to be DoTextChanged and run everything seem to work. Your project compiles and runs but TextChanged in the instance on the layout is NOT going to be the DoTextChanged event that you defined before. Nor will it get raised when you expect.

Now the TextChanged event is the implementation of the framework event because the name was taken over. And, since R2 takes this name over, you have a hard time being 100% sure you have fixed all your implementations and all the code that raised that event. It all still compiles without error. So if you have this situation fix these errors in 2019r1.1 which WILL complain if you miss one or don’t fix the code that raises your custom event.

So what CAN you do about this if you do not want to do a LOT of work right now but want to get the fixes for other items ?

That will have to be a whole new column on dealing with the differences as it definitely depends on your circumstances and whether you need to support backwards compatibility with oder versions of Xojo.

R2 deprecates a LOT of methods. Things like Mid have been renamed to Middle. The renaming is fairly innocuous. However an analyze of one of my clients projects resulted in more than 15000 deprecation warnings. Theres just no easy way to deal with that many.

Be careful about just updating with the suggested replacement without examining your code. The reason is that in many cases the old method might have used 1 based indexes and the new one uses 0 based indexes. So you not only have to replace the name you also have to examine the code to adjust for the different indexing or you will cause a lot of off by one errors.

And, as soon as you do update to use the new methods, you are going to need to handle exceptions they may raise. API 2.0 is switching everything to using exceptions rather than error codes. We’ll have to see what the net effect of this is but exceptions are slower than error codes when you have to deal with a lot of them.

There are pros and cons to this approach. Runtime errors of any kind can’t be “ignored” by failing to check an error code. You can ignore the exceptions but then your application will just crash or quit with an unhandled exception. Now you MUST write code to deliberately catch, and maybe ignore, whatever exceptions that get raised. Hopefully these all get well documented so you dont just use

Try
   // some new API 2.0 code
Catch exc as RuntimeException
End Try

I’d advice against a “catch all errors” kind of usage. You should never catch more than the errors you CAN actually deal with and let the rest bubble on up.

2019r2 is, for me, a mixed bag. Some welcome stuff. Some serious pain points. And some stuff that I have to just not use if I want to make code that still compiles in older IDE’s (which for some clients has to). There are things to like in 2019r2, and things that are going to make for a LOT of rework. For me there just arent enough nice things to warrant moving to it for my day in day out work at this time.

Your mileage may vary.

Sort like Finder

Sometimes you want to be able to sort things in the same fashion as the Finder. This method when used as the comparison function for the Arry.Sort( delegate) form of sorting will do that

Public Function CompareLikeFinder(firstString as string, secondString as String) as integer
  #If targetMacOS
    // // 
    // // typedef NS_CLOSED_ENUM(NSInteger, NSComparisonResult) {
    // // NSOrderedAscending = -1L,
    // // NSOrderedSame,
    // // NSOrderedDescending
    // // };
    // 
    // // this gives us "Finder like" comparisons
    
    // -[NSString localizedStandardCompare:].
    Declare Function localizedStandardCompare Lib "Foundation" selector "localizedStandardCompare:" (string1 As CFStringRef, string2 As CFStringRef) As Integer
    Return localizedStandardCompare(firstString, secondString)
    
    
  #Else
    
    Return StrComp(firstString, secondString, REALbasic.StrCompLexical)
    
  #EndIf
  
  
  
End Function

So with code like

Dim strings() As String
strings.append "file10"
strings.append "file1"
strings.append "file2"
strings.append "file11"

Strings.sort( AddressOf CompareLikeFinder )

You will get

file1
file2
file10
file11

instead of a strictly lexically ordered list

Is full keyboard access on ?

Sometimes its useful to be able to be able to tell your user if they need to enable full keyboard access

You can tell if its enabled with this method

Public Function FullKeyboardAccessEnabled() as Boolean
  #If TargetMacOS
    Try
      Declare Function NSClassFromString Lib "Cocoa" (name As CFStringRef) As Ptr
      Declare Function GetSharedApplication Lib "AppKit" Selector "sharedApplication" (target As Ptr) As Ptr
      Declare Function IsFullKeyboardAccessEnabled Lib "AppKit" Selector "isFullKeyboardAccessEnabled" (target As Ptr) As Boolean
      
      Dim AppClass As Ptr = NSClassFromString("NSApplication")
      If AppClass <> Nil Then
        Dim AppObject As Ptr = GetSharedApplication(AppClass)
        If AppObject <> Nil Then
          Return IsFullKeyboardAccessEnabled(AppObject)
        End If
      End If
    Catch Err As RuntimeException
      Return False
    End Try
  #endif
  
  
  // windows & linux can tab to EVERY control anyway
  return true
End Function

Opting in or out of Windows AutoTabbing

macOS adopted an interesting behaviour some time ago where when you opened a new window in an application. It would default to opening a new tab in the window instead of opening a new window.

If your application could not deal with this and the user had the preference in System Preferences > Dock > Prefer Tabs when opening documents set to Always then it could cause your app issues.

You can opt out of this behaviour in your app’s open event.

Public Sub AppAutoTabbing(optIn as boolean)
  #if not TargetMacOS
    #pragma unused optIn
  #endif
  
  #if TargetMacOS
    
    Declare Function NSClassFromString Lib "Cocoa" (name As CFStringRef) As Ptr
Dim nsWindowClass As ptr = NSClassFromString( "NSWindow" ) 

if nsWindowClass = nil then
  // msgbox CurrentMethodName + " nsWindowClass is Nil"
  break
  return
end if

declare sub EnableTabGrouping lib "AppKit"selector "setAllowsAutomaticWindowTabbing:" ( classPtr as Ptr , enableDisable as Boolean ) 

EnableTabGrouping( nsWindowClass, optIn )
    
  #endif
  
End Sub

Switch the window type at runtime

I’ve seen this question come up a couple times recently. And while it would be nice to do you cant.

The frame type is required to be set when the window is constructed. And there’s no constructor on a window that allows you to do something like

dim w as new Window(frametype)

So how can you have something like a document window on macOS and a movable modal on Windows ?

With a little inventiveness and some work.

First – make a container control that will be the windows content. All your common ui elements go in here. This is the common UI.

Then create a new window for macOS and set its frame type to whatever you need on macOS.

And one for Windows and Linux if needed.

One each embed, at design time, an instance of the container control you just created. Any non-common ui can be added directly to the windows themselves.

Now at runtime when you need an instance of this window you can use conditional compilation to make sure you get the right kind of window.

I’ve put together a small sample

Showing a file in Windows Explorer

No. Not IE.

// there are issues with this style
//  1. you can only select one file
//  2. If folder is already opened, it doesn't select the file
Declare Function ShellExecuteW lib "shell32" (hwnd as Integer, lpOperation as WString, lpFile as WString, lpParameters as WString, lpDirectory as Integer, nShowCmnd as Integer) as Integer

Dim err as Integer
Dim param As String

param = "/select, """ + f.AbsolutePath + """"

err = ShellExecuteW(Window(0).WinHWND, "Open", "explorer", param, 0, 1)

EDIT – a better version has been written by Julian Samphire

He’s given me permission to post it here. Note there are two methods. One takes an array of file paths. The second overloads things to accept paramarry that then calls into the version that takes an array

Public Function ShowSelectedInExplorer(path As String, files() As String) as Int32
  Declare Function CoInitialize Lib "Ole32.dll" Alias "CoInitialize" (pvReserved As Integer) As Int32
  Declare Function ILCreateFromPathW Lib "Shell32.dll" Alias "ILCreateFromPathW" (pszPath As WString) As Ptr
  Declare Function SHOpenFolderAndSelectItems Lib "Shell32.dll" Alias "SHOpenFolderAndSelectItems" (pidlFolder As Ptr, cidl As UInt32, apidl As Ptr, dwFlags As UInt32) As Int32
  Declare Sub ILFree Lib "Shell32.dll" Alias "ILFree" (pidl As Ptr)
  
  Call CoInitialize(0)
  
  Dim pidl As Ptr = ILCreateFromPathW(path)
  If pidl = Nil Then 
    Return 0 'If path wasn't found then return
  End If
  
  Dim mb As New MemoryBlock(COM.SIZEOF_PTR * (files.Ubound + 1))
  
  'Get a pidl for each file and store it
  For i As Integer = 0 To files.Ubound
    mb.Ptr(i * COM.SIZEOF_PTR) = ILCreateFromPathW(files(i))
  Next
  
  Dim ok As Int32 = SHOpenFolderAndSelectItems(pidl, files.Ubound + 1, mb, 0)
  
  ILFree(pidl)
  
  Return ok
End Function


Public Function ShowSelectedInExplorer(path As String, ParamArray files As String) as Int32
  'Build an array from the param array and pass it back to ShowSelectedInExplorer so we can support both methods
  Dim arrayOfFiles() As String
  For Each f As String In files
    arrayOfFiles.Append(f)
  Next
  Return ShowSelectedInExplorer(path, arrayOfFiles)
End Function

Showing a file in the Finder

This has probably been posted in the forums. Its just not always easy to find.

Declare Function NSClassFromString Lib "Cocoa" (name As CFStringRef) As Ptr
Declare Function sharedWorkspace Lib "AppKit" selector "sharedWorkspace" ( obj As ptr ) As ptr
Declare Function selectFile Lib "AppKit" selector "selectFile:inFileViewerRootedAtPath:" ( obj As ptr, fPath As CFStringRef, rootFullPath As CFStringRef ) As Boolean
Dim workspace As ptr = sharedWorkspace( NSClassFromString( "NSWorkspace" ) )

Call selectFile( workspace, f.NativePath, "")

Debugging tip

Sometime when you’re debugging your application you run into a situation where you get funky behaviour.

You might do something like Javier mentioned in his recent blog post on Xojo’s blog :

Dim ASource() As Integer = Array(1,2,3,4,5,6,7,8)
Dim ATarget() As Integer
ATarget = ASource

And, as he noted, you cant figure out why when you change one array you also appear to alter the other. This can also happen with other reference types as I noted in other posts.

For instance I’ve seen

Dim d As New Date
Dim d1 As date
d1 = d

d1.month = 2

and then the question of “why did d change?” arises

Again this has been covered before and it has to do with both arrays and dates, as well as many other type, being reference types.

One way to see that in fact these are the same object is not well documented in the Xojo documentation. Its buried in the Debugging pane of the preferences – Show Object IDs in variable lists.

Once you enable this setting what you see in the debugger pane makes it much easier to see when you have two references to the same object.

When viewing the following code

Dim ASource() As Integer = Array(1,2,3,4,5,6,7,8)
Dim ATarget() As Integer
ATarget = ASource

You can clearly see that ATarget and ASource have the same objectID. In Xojo’s runtime this means they are the same object – objectsID’s are unique to every instance and the only way you get two objectID’s that are the same is when two references refer to the same object.

I’d recommend always turning this setting on when debugging.

What scope should this be ?

This question isnt asked that often. But I think it should be.

When you create classes, modules, layouts or anything else in Xojo you should make everything as private as it needs to be. And no more. And as public as it needs to be. And no more.

But exactly what does that mean ?

I would say that you should normally start with most things in classes being protected. This means that any subclasses can access the items in the class. But code outside the class cant access it. This gives you a decent way to sort out what API you want the class to expose and grow that exposed API over time rather than just making everything publicly accessible all the time.

Code and controls on layouts I would treat similarly and make them all private since you only have private or public to chose from.

For modules public is also a good default for many of the same reasons. But in the case of a method or property in a module that is public it CAN be accessed by code outside the module it just has to be fully qualified so there’s no ambiguity when you are using such a thing.

In my time as a product developer at Xojo I fixed a lot of bugs and many times the issue turned out to be code outside some class, module or layout that was accessing and changing or calling a method that wasnt protected or private and it was doing so at an inopportune time and so it appeared to be a bug in the Xojo framework.

Properly encapsulate your code and expose only as much as needs to be exposed whether thats by methods, events or properties. And by doing this you will prevent all kinds of weird coding bugs in your own projects.