wxWidgets/docs/cocoa/coding_patterns.txt
David Elliott b1c702af6f Added preliminary documentation about how to write new wxCocoa controls.
Basically just a brain dump but it's better than nothing.


git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@47516 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2007-07-16 23:09:56 +00:00

103 lines
7.6 KiB
Plaintext

=== wxCocoa coding patterns ===
Any language or library tends to have a particular set of coding patterns that serve to make the code easier to read
by making it look consistent across the project. Objective-C makes particularly heavy use of patterns as does wxWidgets.
It is not the intention of this document to repeat Cocoa or wxWidgets documentation except for clarity.
--- Class design ---
wxCocoa takes a rather unique approach by decoupling interaction between C++ and Objective-C from the wxWidgets classes.
For any given Objective-C class you wish to override messages from or receive action messages from (e.g. as a delegate
or notification observer) you should implement a C++ wxCocoa##ObjcClass class and one or more Objective-C classes.
The C++ class goes in a file include/wx/cocoa/ObjcClass.h (where ObjcClass is the Objective-C class name) and the
Objective-C classes can either be declared in the implementation file (src/cocoa/ObjcClass.h) or separated into an
include/wx/cocoa/objc/ObjcClass.h file.
Take NSButton as an example. The include/wx/cocoa/NSButton.h declares a wxCocoaNSButton class. Classes such as
wxButton, wxCheckBox, and wxRadioButton all multiply inherit from this (protected). These classes can almost
be thought of as an interface whereby the inheriting class is essentially declaring that it is able to respond
to the various Cocoa_ methods that will be called. It is not quite a pure interface as it actually contains the
logic for this as well, but it can be thought of from a design perspective as such.
Because we do not wish to subclass Objective-C classes except when absolutely necessary we use a hash map so
that the wxCocoaObjcClass instance can be retrieved knowing only the ObjcClass instance. This is acheived by
the sm_cocoaHash static member and the GetFromCocoa method. These are provided by the HASHMAP series of macros
in the include/wx/cocoa/ObjcAssociate.h header.
In addition to the GetFromCocoa method, the pattern also provides for a pair of Associate##ObjcClass and
Disassociate##ObjcClass methods. These non-virtual methods if implemented by the macro merely insert and
remove the Objective-C/C++ pair from the hash map. More often than not they require more than just associating
using the hash map but also require setTarget: and setAction: to be called. This is a leftover of the original
design where it was expected that the classes would be subclasses already containing the code to call the
C++ virtual methods. Later design decisions changed this to use target/action and delegates whenever possible
which is more often the case than not.
To implement a response to an action message, one should simply create a singleton instance of a controller class
that can be used for all instances of the given Objective-C class. For NSButton there is the wxNSButtonTarget
class which implements the (arbitrarily named) wxNSButtonAction: method. The wxCocoaNSButton::AssociateNSButton
method is implemented to setTarget:sm_cocoaTarget (the singleton wxNSButtonTarget) and
setAction:@selector(wxNSButtonAction:). When the button is clicked, the NSButton will send a wxNSButtonAction:
message to its target (the singleton wxNSButtonTarget) with itself as the sender. The implementation of
that message simply looks up the wxCocoaNSButton in the hash map and calls the Cocoa_wxNSButtonAction method.
The wxWidgets class (e.g. wxButton or wxCheckBox) implements that method as it sees fit. For example, to
simply send the corresponding wxWidgets wxEvent.
It should be noted that a better design might have used a generic target/action handler since target/action isn't
actually specific to buttons. This might be a future design change.
Of note, wxCocoaNSButton does not inherit from anything, particularly from wxCocoaNSControl. This is because
of the C++ non-virtual base class problem. Instead, wxControl inherits from wxControlBase and wxCocoaNSControl.
wxButtonBase in turn inherits from wxControl and wxButton in turn inherits from wxButtonBase and wxCocoaNSButton.
One may be wondering how NSControl events (if any) make their way to the wxControl. The answer is in the way
the Associate* methods are called. This is where the Set* methods come in.
Within the wxWidgets class (e.g. wxButton) there is a SetNSButton(NSButton*) method. This method calls
AssociateNSButton and DisassociateNSButton appropriately and also calls the base class SetNSControl implemented
by the wxControl class (note: not the wxCocoaNSControl class). SetNSControl does a similar thing but then
calls its base class SetNSView method. All of these are implemented using the same macro except for SetNSView
which is implemented to do proper retain/release and set the m_cocoaNSView instance variable in wxWindow.
In addition to the Set* set of methods, there is also a Get* set. These are implemented (inline) to cast
the root class pointer type to the desired type. For instance, GetNSButton merely returns
(NSButton*)m_cocoaNSView. These are a convenience for coding the library itself and are also public such that
users of wxCocoa wishing to make Cocoa-specific calls can easily get at a properly-typed instance.
This works well for the common case like a button or checkbox where one Cocoa class clearly represents one
wxWidgets class. For more complex cases involving a Cocoa view hierarchy one may need to implement these
methods in a different manner.
--- The view hierarchy ---
Because the Cocoa view hierarchy isn't a perfect match with the wxWidgets hierarchy, there are some conventions
used to resolve this conflict. The first is that m_cocoaNSView is defined to be the view which most-closely
represents the wxWidgets view. For instance, a wxButton has an NSButton instance and a wxStaticBox has an NSBox
instance. Unfortunately, wxWidgets defines some behavior that Cocoa cannot directly implement. This is primarily
window scrolling (e.g. without using a wxScrolledWindow) and window hiding.
Scrolling is implemented in a separate class known as wxWindowCocoaScrollView. This class does not fit into
the wxWidgets class hierarchy but instead implements the wxCocoaNSView interface itself, including listening for
the Cocoa_FrameChanged notification. This is a good example of why the Objective-C to C++ shim code is
unrelated to the wxWidgets class hierarchy. As you can clearly see, it allows the shim code to be used for
classes that aren't part of the wxWidgets hierarchy.
Hiding is implemented in another class known as wxWindowCocoaHider in a similar manner to wxWindowCocoaScrollView.
This is an artifact of the pre-Panther days of Cocoa where there was no method for hiding a view.
What these classes do is provide a Cocoa view that sits between the wxWidget's parent window's view and the
m_cocoaNSView provided by the window. The wxWindow class has a GetNSViewForSuperview() method that returns either
the m_cocoaNSView (if the window does not need scrolling behavior and is not hidden) or returns the scroll view
for the case of scrolling or the dummy view in the case of hiding. As the name suggests, the method is used
from the parent wxWindow (the superview) when it sends something like an addSubview: message. The method is under
no circumstances intended to be used as the receiver of an addSubview message. In fact, not even the GetNSView()
method should be used for this as in [m_parent->GetNSView() addSubview:GetNSViewForSuperView()] because this
functionality is provided by the CocoaAddChild method.
Note that there is a small hole in the API here because classes other than wxWindow wishing to implement a view
hierarchy will not be able to correctly do this since CocoaAddChild is not virtual and there is no virtual
GetNSViewForSubviews() method.