Skip to content

Commit 47185ab

Browse files
committed
Code-behind fragment and widget access fixes
Fragment handling code partially taken from dotnet#1302 Fragments are treated as normal widget, with the difference that they have to be found using `FragmentManager.FindFragmentById` instead of the usual `Activity.FindViewById` method. Each generated class also gets a property named `Widget` which returns the actual Android widget as found in the layout file. This allows us to keep hierarchical nature of the XML code (thus handling nested widgets with duplicate ids gracefully) while being able to access the parent widget itself. The commit introduces a new attribute called `tools:managedType` which is used to specify the element associated property's type. We cannot use `tools:class` for this purpose since the layout root element already uses it to specify the name of the generated code-behind class and if the element had `android:id` on it, we would end up using the activity's type for the root element's property in the generated code, instead of its actual type.
1 parent 351ca70 commit 47185ab

2 files changed

Lines changed: 273 additions & 46 deletions

File tree

Documentation/guides/LayoutCodeBehind.md

Lines changed: 142 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,45 @@ dateupdated: 2018-01-29
1111
Xamarin.Android supports the auto generation of "Code Behind" classes. These classes
1212
can reduce the amount code a developer writes. You can end up replacing code like
1313

14-
SetContentView (Resource.Layout.Main);
15-
var button = FindViewById<Button> (Resource.Id.myButton);
16-
button.Click += delegate {
17-
};
14+
```csharp
15+
SetContentView (Resource.Layout.Main);
16+
var button = FindViewById<Button> (Resource.Id.myButton);
17+
button.Click += delegate {
18+
};
19+
```
1820

1921
with
2022

21-
InitializeContentView ();
22-
myButton.Click += delegate {
23-
};
23+
```csharp
24+
InitializeContentView ();
25+
myButton.Click += delegate {
26+
};
27+
```
2428

29+
or, with nested layouts:
30+
31+
```csharp
32+
InitializeContentView ();
33+
myParentLayout.myButton.Widget.Click += delegate {
34+
};
35+
```
2536

2637
<a name="" class="injected"/></a>
2738

2839
# Preparing to use Code Behind
2940

3041
In order to make use of this new feature there are a few changes which are required.
31-
An axml/xml file that you want to associate with an activity needs to be modified to
32-
include a few extra xml attributes on the root layout element.
42+
An ``axml/xml`` file that you want to associate with an activity needs to be modified to
43+
include a few extra xml attributes on the root layout element.
44+
45+
Additionally, **only*** elements which have the `android:id` attribute will be accessible via
46+
the generated code.
47+
3348

34-
xmlns:tools="http://schemas.xamarin.com/android/tools"
35-
tools:class="$(Namespace).$(ClassName)"
49+
```xml
50+
xmlns:tools="http://schemas.xamarin.com/android/tools"
51+
tools:class="$(Namespace).$(ClassName)"
52+
```
3653

3754
The `class` attribute defines the Namespace and ClassName of the code which will be
3855
generated. For example if you have a layout for your `MainActivity` you would set
@@ -42,35 +59,46 @@ qualified name, not just the class name on its own.
4259
The next thing we need to do is to make the `MainActivity` a `partial` class. This
4360
allows the genereted code to extend the current class which you have written.
4461
So
45-
public class MainActivity : Activity {
46-
}
62+
63+
```csharp
64+
public class MainActivity : Activity {
65+
}
66+
```
4767

4868
will become
4969

50-
public partial class MainActivity : Activity {
51-
}
70+
```csharp
71+
public partial class MainActivity : Activity {
72+
}
73+
```
5274

5375
You then need to make sure you initialize the layout properties by calling
5476
`InitializeContentView ()` in the `OnCreate()` method of your activity.
5577

56-
protected override void OnCreate (Bundle bundle)
57-
{
58-
base.OnCreate (bundle);
59-
InitializeContentView ();
60-
}
78+
```csharp
79+
protected override void OnCreate (Bundle bundle)
80+
{
81+
base.OnCreate (bundle);
82+
InitializeContentView ();
83+
}
84+
```
6185

6286
For those of you familiar with System.Windows.Forms this is akin
6387
to `InitializeComponent`. Once this has been done you can now access
6488
your layout items via the properties.
6589

66-
myButton.Click += delegate {
67-
};
90+
```csharp
91+
myButton.Click += delegate {
92+
};
93+
```
6894

6995
There is a partial method available which can be implemented to handle
7096
situations where the View is not found. The method is
7197

72-
void OnLayoutViewNotFound<T> (int resourceId, ref T type)
73-
where T : global::Android.Views.View;
98+
```csharp
99+
void OnLayoutViewNotFound<T> (int resourceId, ref T type)
100+
where T : global::Android.Views.View;
101+
```
74102

75103
If `FindViewById` returns `null` then the `OnLayoutViewNotFound` method
76104
will be called (if it is implemented). This is done BEFORE we throw the
@@ -79,6 +107,96 @@ situation in a manner which fits the app they are writing. For example
79107
you might want to switch to a backup view, or just log some additional
80108
diagnostic information.
81109

110+
Another partial method exists to handle fragments:
111+
112+
```csharp
113+
void OnLayoutFragmentNotFound<T> (int resourceId, ref T type)
114+
where T : global::Android.App.Fragment;
115+
```
116+
117+
It works in exactly the same way as `OnLayoutViewNotFound` above, just for fragments.
118+
119+
## Generated code structure
120+
121+
The generated code-behind is laid out in a hierarchical fashion, reflecting the parent-child
122+
relationship found in the layout file. The way it is done is that each element which has any
123+
child elements **with** the `android:id` attribute (that is, ones which will also have code
124+
generated for them) will have a nested class generated for it which will have a property for
125+
each child element as well as the `Widget` property which refers to this element's actual
126+
Android widget/view. Each element which does **not** have any child elements with the
127+
`android:id` attribute, however, will become a *leaf node* and will have an associated property
128+
in its parent widget's class directly typed to the actual Android type (e.g. `TextView`) instead
129+
of the class described before. For instance, given this layout:
130+
131+
```xml
132+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.xamarin.com/android/tools"
133+
tools:class="MyActivity">
134+
<ScrollView android:id="@+id/myScrollView">
135+
<TextView android:id="@+id/myTextView"/>
136+
</ScrollView>
137+
</LinearLayout>
138+
```
139+
140+
The code-behind will have this rough structure (class names are different to keep the documentation clear):
141+
142+
```csharp
143+
myScrollView_Class myScrollView {
144+
get { return new myScrollView_Class (this); }
145+
}
146+
147+
class myScrollView_Class
148+
{
149+
public ScrollView Widget {
150+
get { /* ... */ }
151+
}
152+
153+
public TextView myTextView {
154+
get { /* ... */ }
155+
}
156+
157+
public myScrollView_Class (MyActivity parent) {}
158+
}
159+
```
160+
161+
So in order to access the widgets you'd use code similar to:
162+
163+
```csharp
164+
InitializeContentView ();
165+
myScrollView.Widget.Fling (100);
166+
myScrollView.myTextView.AutoSizeMaxTextSize = 40;
167+
```
168+
169+
## Managed types
170+
171+
By default each element for which we generate code-behind has its managed type set to
172+
its local name, for instance
173+
174+
```xml
175+
<TextView android:id=""@+id/textView" />
176+
```
177+
178+
Will generate a property named `textView` of type `TextView`. It works fine in most cases
179+
but sometimes you might find code which either refers to a custom widget using the package
180+
name or a fragment which uses the case-insensitive `android:name` attribute syntax, for
181+
instance:
182+
183+
```xml
184+
<fragment
185+
android:name="commonsamplelibrary.LogFragment"
186+
android:id="@+id/log_fragment" />
187+
```
188+
189+
In this case the generated property would have the managed type `commonsamplelibrary.LogFragment`,
190+
however the actual managed type fully qualified name is `CommonSampleLibrary.LogFragment` and thus
191+
the generated code would fail to compile. The solution is to add the `tools:managedType` attribute
192+
which specifies the element's (all elements support this attribute) managed type.
193+
194+
One may wonder why we didn't reuse the `tools:class` attribute to specify the managed type? It is
195+
because that attribute is used to specify the code-behind partial class name on the root element of
196+
the layout and should the element had the `android:id` attribute present we'd end up with generated
197+
code that would use the **activity** type for the element's associated property instead of its
198+
actual type and the code wouldn't build.
199+
82200
# How it works
83201
84202
There are a couple of new MSBuild Tasks which generate the code behind.

0 commit comments

Comments
 (0)