Sign in Join
Wiki copytemporary

Cocoa Internals/Widgetset: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Handles
22
33THANDLE - an opaque reference value that's being generated during the .CreateHandle() call of a particular Widgetset class. THANDLE is the value that's used to interface with a control. Using the value a widgetset recognizes a particular control.
44
55Most of the Widget interface methods accept TWinControl as a parameter. It's assumed that the Handle property of the control would be used to back reference the actual control. The property stores the value received from the CreateHandle() call made earlier. The HandleAllocated property can be checked in order to know if handle has been previously created or not. If handle has not been created it's expected that the method can fail (returning the proper failure information. Exceptions are not expected?).
66
77Overall LCL Handles refer to complex controls. For example, HANDLE can refer to a Memo control that's capable of editing AND scrolling text as needed.
88
99Cocoa's composite engine operates controls on a lower level scope. With the Memo example, there's not a single control that does both: text editing and scrolling. Instead there are two separate controls: NSTextView and NSScrollView (both are descendants of NSView). But LCL expects only one value to be returned as a handle.
1010
1111The common rule is the following: the outermost bounds (aka container) control is returned as the handle. Thus in case of Memo, two NSViews are allocated: NSTextView and NSScrollView. But the outermost NSScrollView is returned as theHANDLE. All TCustomMemoWSControl methods are aware of that and treat the handle as such. For example:
1212
1313- if paste action is required, the handle would be used as **NSScrollView** and then it's **documentview** would be used to get **NSTextView to call for**paste'''.
1414- if bounds changes are required (moving, resizing) then the sizes are changed directly for the NSView referenced by HANDLE. (It's assumed that the view would automatically resize its controls. In case of TMemo, NSScrollView would adjust size of NSTextView, if wordwrapping is off).
1515- Forms (TForm), that generally correspond to NSWindow, also return their handle as NSView. That view represents the root NSView of a form. However, CocoaWS API's know that and call the necessary NSWindow methods where applicable.
1616
1717HandleViews are stored within Callback object that's being created for each CreateHandle() method.
1818
1919### Handles Reference
2020
2121- Window handle (HWND) is always NSView.
2222 - TCustomWSForm it is content NSView.
2323 - Any control that has scroll bars (i.e. TCustomWSList) the it its the embedding NSScrollView (TCocoaScrollView)
2424
2525The table below gives a reference of LCL controls and their implementing classes. Note that the LCL handle returned is not always the same as the implementing class. This applies to controls that have to embedded in a scrollbox.
2626
2727**LCL Control**
2828
2929**WS Part**
3030
3131**Cocoa Class (NS class)**
3232
3333**View Returned as LCL Handle**
3434
3535**Notes**
3636
3737TScrollBar
3838
3939Std
4040
4141TCocoaScrollBar (NSScrollBar)
4242
4343*self*
4444
4545TGroupBox
4646
4747Std
4848
4949TCocoaGroupBox (NSBox)
5050
5151*self*
5252
5353There's also a content view created to hold all the child controls within the box
5454
5555TComboBox
5656
5757Std
5858
5959TCocoaComboBox (NSComboBox)
6060
6161or
6262
6363TCocoaReadOnlyComboBox (NSPopUpButton)
6464
6565*self*
6666
6767The actual type is defined by the style of LCL combobox (readonly vs editable)
6868
6969TListBox
7070
7171Std
7272
7373TCocoaTableView (NSTableView)
7474
7575NSScrollView
7676
7777The behavior of TListbox is defined by the callback object TLCLListBoxCallback assigned to TCocoaTableView.
7878
7979TEdit
8080
8181Std
8282
8383TCocoaTextField (NSTextField)
8484
8585*self*
8686
8787TCocoaTextField itself is readonly static field. The editing capabilities are implemented by TCocoaFieldEditor, which is driven by AppKit "editing" schema
8888
8989TMemo
9090
9191Std
9292
9393TCocoaTextView (NSTextView)
9494
9595NSScrollView
9696
9797Lines property is implemented via TCocoaMemoStrings
9898
9999TButton
100100
101101Std
102102
103103TCocoaButton (NSButton)
104104
105105*self*
106106
107107TCheckBox
108108
109109Std
110110
111111TCocoaButton (NSButton)
112112
113113*self*
114114
115115Checkbox is still a button but with a different Bezel
116116
117117TToggleBox
118118
119119Std
120120
121121TCocoaButton (NSButton)
122122
123123*self*
124124
125125TRadioButton
126126
127127Std
128128
129129TCocoaButton (NSButton)
130130
131131*self*
132132
133133Radiobutton is still a button but with a different Bezel
134134
135135TStaticText
136136
137137Std
138138
139139TCocoaTextField (NSTextField)
140140
141141*self*
142142
143143This field doesn't allow editor to be enabled
144144
145145[[TTrackBar]]
146146
147147Com
148148
149149[[Cocoa Internals/Buttons|TCocoaSlider]] (NSSlider)
150150
151151*self*
152152
153153## Focusing
154154
155155That has important impact of FOCUS-ing. Cocoa doesn't operate the term "focused" instead it's using terms "firstRepsonder" and "key" view/window. Generally focused control corresponds to Cocoa's "firstResponder". The NSResponder object (typically NSView) is the first object that would handle key or mouse events. However, this is not the case for composed controls. TMemo is a combination of NSScrollView and NSTextField. NSScrollView is used as a handle. If, NSTextField would be the firstRepsonder, CocoaWS needs to return NSScrollView as focused HANDLE.
156156
157157The same approach is applicable in reverse. If SetFocus is made for the HANDLE that represents NSScrollView with NSTextField inside. NSTextField should actually become Cocoa's firstResponder.
158158
159159The expected order of events is:
160160
161161- LM_KILLFOCUS
162162- LM_SETFOCUS
163163
164164Note that unlike WinAPI WM_KILLFOCUS, LM_KILLFOCUS doesn't provide the information about "the next suggested" control to be focused. For controls that have some sort of editors (i.e. TTreeView with a node editor (TEdit) active) such approach is causing to switch focus to TForm (the hosting form), for the next focus control selection.
165165
166166The focus switch notification is handled in TCocoaWindow makeFirstResponder call. Due to problems with the recursive calls of changing the focus, the current order of events is:
167167
168168- LM_SETFOCUS
169169- LM_KILLFOCUS
170170
171171(for Cocoa WS)
172172
173173#### Need of Window
174174
175175The "firstResponder" is a property of a window. Thus a view can become a "firstResponder" only if it's within NSWindow's view hierarchy. This is important for complex controls, such as a TabControl. It manages multiple child views (each view for a tab). Only one view is visible at a time. All over views are actually DETACHED from NSWindow hierarchy. (while from LCL perspective they are still children of TForm). When switching between tabs a notification about a successful switch should only be delivered when a new view is in the Window hierarchy.
176176
177177#### Ignoring System Setting
178178
179179LCL basic logic is to focus every control that is a tab stoppable (which is every TWinControl by default) user. Where in macOS there's a system setting of **Full Keyboard Access** (which is off by default). If the setting is off then macOS acts similar to what LCL does. If it's on only "keyboard input" controls are "tab-reachable" text fields (including editable combo-boxes) and lists. Buttons, scrollers are skipped from tabbing. LCL doesn't currently support limited tabbing.
180180
181181#### Focus Ring
182182
183183Focus Ring is not drawn for controls with **BorderStyle** set to **bsNone**. (that allows to visually "hide" controls)
184184
185185If global variable **CocoaHideFocusNoBorder** is set to false, then the focus remains unchanged.
186186
187187## Cursors
188188
189189Widgetset cursors are based on **NSCursor** class. Which very nice and convenient to use. Cocoa however, doesn't provide some of the cursors that do exist in windows/LCL. For example: diagonal resize. Diagonal cursors are generated by rotation of the horizontal cursors.
190190
191191## User Prompts and Modal Dialogs
192192
193193Most of the dialogs are based on **NSModal** class.
194194
195195With the switch of rendering engine to layered drawing (effective macOS 10.14), an attempt to show a modal dialog during rendering (or any other stage of animation. I.e. "Activation") would cause an exception:
196196
197-`Application Specific Information:`\
198-`*** Terminating app due to uncaught exception 'NSGenericException', reason: '-[NSAlert runModal]`\
199-`may not be  invoked inside of transaction begin/commit pair, or inside of transaction commit `\
200-`(usually this means it was invoked inside of a view's -drawRect: method.)'`
197+``` pascal
198+Application Specific Information:
199+*** Terminating app due to uncaught exception 'NSGenericException', reason: '-[NSAlert runModal]
200+may not be invoked inside of transaction begin/commit pair, or inside of transaction commit
201+(usually this means it was invoked inside of a view's -drawRect: method.)'
202+```
201203
202204However, if the error dialog is shown as a "sheet" then the error doesn't occur.
203205
204206## See Also
205207
206208- [[Cocoa Internals]]
207209
208210---
209211*From the Free Pascal wiki, page "Cocoa Internals/Widgetset", as it stood on 2020-10-05 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
210212Category: Cocoa
211213Category: macOS

Back to history