Window Groups

Window groups allow you to manipulate a group of windows as a single entity. By setting group properties, you can lock the relative positions of windows in a group, so that moving one window moves the others. You can group windows so that they activate or deactivate together, or so that they collapse together. You can group windows so that they behaves as a single layer, and you can fix the ordering of windows in a group.

The Window Manager has a section of functions devoted to window groups. We'll implement a core subset here; implementing others as you need should then be no problem. The design of the window group interface is that of Core Foundation, in its use of opaque types and reference counting for memory management, and I refer you to that chapter for discussion.

WindowGroupRef is the main datatype for working with window groups; it is a pointer to an opaque struct and thus is declared in REALbasic as type Integer. WindowGroupAttributes is a bitfield type used to specify group attributes.

typedef UInt32 WindowGroupAttributes; enum { kWindowGroupAttrSelectAsLayer = 1 kWindowGroupAttrMoveTogether = 2, kWindowGroupAttrLayerTogether = 4 kWindowGroupAttrSharedActivation = 8 kWindowGroupAttrHideOnCollapse = 16 };

Memory Management

Window groups are reference-counted objects. By creating a WindowGroup class, we can use REALbasic's automatic memory management to handle the reference counting. The Window Manager functions we will need are the following.

OSStatus ReleaseWindowGroup ( WindowGroupRef inGroup );

OSStatus RetainWindowGroup ( WindowGroupRef inGroup );

The header file MacWindows.h states that these functions are available in MacOS 10.0, and CarbonLib 1.4 or later. On the off chance that this code would be used in a Carbon application running in OS 9 with an old version of CarbonLib, we use soft declares.

We must balance RetainWindowGroup calls with ReleaseWindowGroup calls. The scope of such double-entry bookkeeping should usually be either a single method, or the life of a REALbasic object. So we begin with the following constructor and destructor.

Sub Constructor(theRef as Integer) If theRef = 0 then Return //or raise an exception End if If NOT System.IsFunctionAvailable("RetainWindowGroup", CarbonLib) then Return End if Soft Declare Function RetainWindowGroup Lib CarbonLib (inGroup as Integer) as Integer dim OSError as Integer = RetainWindowGroup(theRef) If OSError = 0 then me.GroupRef = theRef Else me.GroupRef = 0 End if End Sub

Sub Destructor() If Not System.IsFunctionAvailable("ReleaseWindowGroup", CarbonLib) Then Return End if Soft Declare Function ReleaseWindowGroup Lib CarbonLib (inGroup as Integer) as Integer If me.GroupRef <> 0 then dim OSError as Integer = ReleaseWindowGroup(me.GroupRef) End if End Sub

Because WindowGroupRef is a pointer type, we know that it shouldn't have the value 0; thus I have included a guard clause in the constructor that checks the parameter. As it turns out, passing 0 to RetainWindowGroup will result in error -5616, windowGroupInvalidErr. But the use of the separate guard clause allows the object to explicitly document that 0 is an invalid parameter, and to catch such an error as early as possible,

Even if the constructor fails, the destructor will still be called when the object is destroyed. Thus we need to test the property me.GroupRef to make certain that it is nonzero.

Creating a New Group

Creating a new window group is simple, using the following function.

OSStatus CreateWindowGroup ( WindowGroupAttributes inAttributes, WindowGroupRef * outGroup );

We now write a second constructor. In this constructor we create a new window group, then pass the WindowGroupRef to the constructor written above. CreateWindowGroup returns a WindowGroupRef with a reference count of 1 --this is not explicitly documented, but this is consistent with the design of Core Foundation -- so we release it in the same method. The default constructor from above is responsible for incrementing the reference count of the WindowGroupRef. This scheme for using REALbasic memory management to handle Carbon reference counting is one that you will encounter throughout the book.

Sub Constructor() If Not System.IsFunctionAvailable("CreateWindowGroup", CarbonLib) then Return End if Soft Declare Function CreateWindowGroup Lib CarbonLib (inAttributes as Integer, ByRef outGroup as Integer) as Integer If Not System.IsFunctionAvailable("ReleaseWindowGroup", CarbonLib) then Return End if Soft Declare Function ReleaseWindowGroup Lib CarbonLib (inGroup as Integer) as Integer dim theRef as Integer dim OSError as Integer = CreateWindowGroup(0, theRef) If OSError = 0 then me.Constructor theRef End if If theRef <> 0 then OSError = ReleaseWindowGroup(theRef) End if End Sub

Attributes

Next, we add properties corresponding to the window group attributes. Here we'll just do one; the others are similar. First we'll need the Window Manager functions for getting and setting attributes.

OSStatus GetWindowGroupAttributes ( WindowGroupRef inGroup, WindowGroupAttributes * outAttributes );

OSStatus ChangeWindowGroupAttributes ( WindowGroupRef inGroup, WindowGroupAttributes setTheseAttributes, WindowGroupAttributes clearTheseAttributes );

Then we wrap the functions into WindowGroup methods that add a property MoveTogether as Boolean.

Sub MoveTogether(Assigns theValue as Boolean) If me.GroupRef = 0 then Return End if If Not System.IsFunctionAvailable("ChangeWindowGroupAttributes", CarbonLib) then Return End if Soft Declare Function ChangeWindowGroupAttributes Lib CarbonLib (inGroup as Integer, setTheseAttributes as Integer, clearTheseAttributes as Integer) as Integer Const kWindowGroupAttrMoveTogether = 2 dim OSError as Integer If theValue then OSError = ChangeWindowGroupAttributes(me.GroupRef, kWindowGroupAttrMoveTogether , 0) Else OSError = ChangeWindowGroupAttributes(me.GroupRef, 0, kWindowGroupAttrMoveTogether) End if End Sub

Function MoveTogether() as Boolean If me.GroupRef = 0 then Return false End if If Not System.IsFunctionAvailable("GetWindowGroupAttributes", CarbonLib) then Return false End if Declare Function GetWindowGroupAttributes Lib CarbonLib (inGroup as Integer, ByRef outAttributes as Integer) as Integer Const kWindowGroupAttrMoveTogether = 2 dim theAttributes as Integer dim OSError as Integer = GetWindowGroupAttributes(me.GroupRef, theAttributes) Return (Bitwise.BitAnd(theAttributes, kWindowGroupAttrMoveTogether) = kWindowGroupAttrMoveTogether) End Function

Now we'll add a few more methods to gain experience with window groups. SetWindowGroup adds a window to an existing group and removes the window from the group to which it previously belonged.

OSStatus SetWindowGroup ( WindowRef inWindow, WindowGroupRef inNewGroup );

Then it's easy to add an Add method to our WindowGroup class.

Sub Add(w as Window) If w Is Nil then Return End if If me.GroupRef = 0 then Return End if If Not System.IsFunctionAvailable("SetWindowGroup", CarbonLib) then Return End if Soft Declare Function SetWindowGroup Lib CarbonLib (inWindow as WindowPtr, inNewGroup as Integer) as Integer dim OSError as Integer = SetWindowGroup(w, me.GroupRef) End Sub

A certain amount of fiddling may be required to get window groups to work as you expect. Consider, for instance, the following code, which should put two windows side by side and group them so that they move together.

aWindowGroup.MoveTogether = true dim w as new Window1 w.top = 100 w.Left = 100 aWindowGroup.Add w dim w2 = new Window1 w2.top = 100 w2.Left = 400 aWindowGroup.Add w2

This code does not work as you expect; the windows do not appear in the positions you specified. The reason appears to be that the repositioning of the windows does not occur at the instant the top or left properties are assigned. So the windows are assigned to the group before the repositioning occurs, but at that point they are bound to move together.

To get the code to work as you want, you need to force an update, so that the windows are repositioned.

aWindowGroup.MoveTogether = true dim w as new Window1 w.top = 100 w.Left = 100 w.Show aWindowGroup.Add w dim w2 as new Window1 w2.top = 100 w2.Left = 400 w2.Show aWindowGroup.Add w2

A window group has a parent group, except for the root group, and so there are methods for getting and setting the parent group.

WindowGroupRef GetWindowGroupParent ( WindowGroupRef inGroup );

OSStatus SetWindowGroupParent ( WindowGroupRef inGroup, WindowGroupRef inNewGroup );

We implement the get function, as it illustrates a point concerning reference counts.

Function Parent() as WindowGroup If me.GroupRef = 0 then Return nil End if If Not System.IsFunctionAvailable("GetWindowGroupParent", CarbonLib) then Return nil End if Soft Declare Function GetWindowGroupParent Lib CarbonLib (inGroup as Integer) as Integer dim theRef as Integer = GetWindowGroupParent(me.GroupRef) If theRef <> 0 then Return new WindowGroup(theRef) Else Return Nil End if End Function

In this function, we don't release the WindowGroupRef returned by GetWindowGroupParent because it returns a reference to an existing object, instead of creating one. Thus it is the caller's responsibility to increment the reference count. This is documented only in the header file MacWindows.h. The WindowGroup constructor increments the reference count.

There is more window group functionality than is discussed here, including the ability to control the order of layering. The examples we have covered should make it possible to implement any other functionality you want.