# DetailViewLayoutBuilders - LayoutBuilder<T> Syntax
The last section demonstrated that layouts are essentially domain specific language which can, through the power of C#, produce in code, pixel perfect results which are both refactor safe and have a consistent look and feel.
Whilst the basic layout building blocks can be verbose Xenial.Framework does provide a more robust and IntelliSense driven way to craft layouts in code.
By way of a quick reminder the illustration below shows what the final layout should look like;

# Registration
As before the first task is to tell XAF to use the DetailViewLayoutBuilders.
Override the AddGeneratorUpdaters in the platform agnostic module and call the updaters.UseDetailViewLayoutBuilders() extension method.
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Model.Core;
namespace MyApplication.Module
{
public sealed partial class MyApplicationModule : ModuleBase
{
public override void AddGeneratorUpdaters(ModelNodesGeneratorUpdaters updaters)
{
base.AddGeneratorUpdaters(updaters);
updaters.UseDetailViewLayoutBuilders();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Defining the builder method
Once again declare a public static method in the business object class for which the layout is to be created called BuildLayout that returns a Xenial.Framework.Layouts.Items.Base.Layout instance and decorate it with the DetailViewLayoutBuilderAttribute.
The DetailViewLayoutBuilderAttribute defines the method and type that is responsible for building the DetailView.
using DevExpress.Persistent.Base;
using DevExpress.Xpo;
using Xenial.Framework.Layouts;
using Xenial.Framework.Layouts.Items.Base;
namespace MainDemo.Module.BusinessObjects
{
[Persistent]
[DefaultClassOptions]
[DetailViewLayoutBuilder]
public class Person : XPObject
{
public static Layout BuildLayout()
{
return new Layout();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Using the LayoutBuilder<T> instance
At this stage the workflow moves from using the initializer syntax to hand craft the object graph to using the LayoutBuilder<T> class as illustrated below;
using DevExpress.Persistent.Base;
using DevExpress.Xpo;
using Xenial.Framework.Layouts;
using Xenial.Framework.Layouts.Items;
using Xenial.Framework.Layouts.Items.Base;
using Xenial.Framework.Layouts.Items.LeafNodes;
namespace MainDemo.Module.BusinessObjects
{
[Persistent]
[DefaultClassOptions]
[DetailViewLayoutBuilder]
public class Person : XPObject
{
public static Layout BuildLayout()
{
var b = new LayoutBuilder<Person>();
return new Layout
{
};
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
The layout builder has a number of methods and overloads that accept some basic parameters like Caption and ImageName first, followed by a params LayoutItemNode[] nodes, as well as a callback method Action<TNodeType>.
These allow the use of a more compact syntax, without loosing any functionality over the traditional initializer syntax.
In the code sample below the parameters
mandeare used, wheremis short forMemberandeforEditor.
This convention is regarded as best practice but in reality any naming convention could be used.
using System;
using Xenial.Framework.Layouts;
using Xenial.Framework.Layouts.Items.Base;
namespace MainDemo.Module.BusinessObjects
{
[Persistent]
[DefaultClassOptions]
[DetailViewLayoutBuilder]
public class Person : XPObject
{
public static Layout BuildLayout()
{
var b = new LayoutBuilder<Person>();
return new Layout
{
b.HorizontalGroup(g =>
{
g.Caption = "Person";
g.ShowCaption = true;
g.RelativeSize = 25;
},
b.PropertyEditor(m => m.Image, editor =>
{
editor.ShowCaption = false;
editor.RelativeSize = 10;
}),
b.VerticalGroup(
b.PropertyEditor(m => m.FullName),
b.HorizontalGroup(
b.PropertyEditor(m => m.FirstName),
b.PropertyEditor(m => m.LastName)
),
b.HorizontalGroup(
b.PropertyEditor(m => m.Email),
b.PropertyEditor(m => m.Phone)
),
b.EmptySpaceItem()
)
),
b.TabbedGroup(
b.Tab("Primary Address", FlowDirection.Horizontal,
b.VerticalGroup(
b.PropertyEditor(m => m.Address1.Street, e => e.CaptionLocation = Locations.Top),
b.HorizontalGroup(
b.PropertyEditor(m => m.Address1.City, e => e.CaptionLocation = Locations.Top),
b.PropertyEditor(m => m.Address1.ZipPostal, e => e.CaptionLocation = Locations.Top)
),
b.PropertyEditor(m => m.Address1.StateProvince, e => e.CaptionLocation = Locations.Top),
b.PropertyEditor(m => m.Address1.Country, e => e.CaptionLocation = Locations.Top),
b.EmptySpaceItem()
),
b.EmptySpaceItem()
),
b.Tab("Secondary Address", FlowDirection.Horizontal,
b.VerticalGroup(
b.PropertyEditor(m => m.Address2.Street, e => e.CaptionLocation = Locations.Top),
b.HorizontalGroup(
b.PropertyEditor(m => m.Address2.City, e => e.CaptionLocation = Locations.Top),
b.PropertyEditor(m => m.Address2.ZipPostal, e => e.CaptionLocation = Locations.Top)
),
b.PropertyEditor(m => m.Address2.StateProvince, e => e.CaptionLocation = Locations.Top),
b.PropertyEditor(m => m.Address2.Country, e => e.CaptionLocation = Locations.Top),
b.EmptySpaceItem()
),
b.EmptySpaceItem()
),
b.Tab("Additional Addresses",
b.PropertyEditor(m => m.Addresses)
)
)
};
}
}
} 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
The benefit of this approach is that IntelliSense is available to guide the layout building process obviating the need to remember all the type names, however it is a much denser syntax which may not be to all tastes.
TIP
It is perfectly acceptable to mix both initializer and functional style to suit personal preference or team guidelines.
# Inherit from LayoutBuilder<T>
Thus far the LayoutBuilder<T> has been used as an instance utilizing the convention based registration pattern for the builder.
By inheriting from LayoutBuilder<T> and using the typed overload of the DetailViewLayoutBuilderAttribute it is possible to reduce additional noise from the syntax. This is achieved by inheriting from the LayoutBuilder<T> class and changing the registration as shown below.
using System;
using Xenial.Framework.Layouts;
using Xenial.Framework.Layouts.Items.Base;
namespace MainDemo.Module.BusinessObjects
{
[Persistent]
[DefaultClassOptions]
[DetailViewLayoutBuilder(typeof(PersonLayoutBuilder))]
public class Person : XPObject {}
public sealed class PersonLayoutBuilder : LayoutBuilder<Person>
{
public Layout BuildLayout()
{
return new Layout
{
HorizontalGroup(g =>
{
g.Caption = "Person";
g.ShowCaption = true;
g.RelativeSize = 25;
},
PropertyEditor(m => m.Image, editor =>
{
editor.ShowCaption = false;
editor.RelativeSize = 10;
}),
VerticalGroup(
PropertyEditor(m => m.FullName),
HorizontalGroup(
PropertyEditor(m => m.FirstName),
PropertyEditor(m => m.LastName)
),
HorizontalGroup(
PropertyEditor(m => m.Email),
PropertyEditor(m => m.Phone)
),
EmptySpaceItem()
)
),
TabbedGroup(
Tab("Primary Address", FlowDirection.Horizontal,
VerticalGroup(
PropertyEditor(m => m.Address1.Street, e => e.CaptionLocation = Locations.Top),
HorizontalGroup(
PropertyEditor(m => m.Address1.City, e => e.CaptionLocation = Locations.Top),
PropertyEditor(m => m.Address1.ZipPostal, e => e.CaptionLocation = Locations.Top)
),
PropertyEditor(m => m.Address1.StateProvince, e => e.CaptionLocation = Locations.Top),
PropertyEditor(m => m.Address1.Country, e => e.CaptionLocation = Locations.Top),
EmptySpaceItem()
),
EmptySpaceItem()
),
Tab("Secondary Address", FlowDirection.Horizontal,
VerticalGroup(
PropertyEditor(m => m.Address2.Street, e => e.CaptionLocation = Locations.Top),
HorizontalGroup(
PropertyEditor(m => m.Address2.City, e => e.CaptionLocation = Locations.Top),
PropertyEditor(m => m.Address2.ZipPostal, e => e.CaptionLocation = Locations.Top)
),
PropertyEditor(m => m.Address2.StateProvince, e => e.CaptionLocation = Locations.Top),
PropertyEditor(m => m.Address2.Country, e => e.CaptionLocation = Locations.Top),
EmptySpaceItem()
),
EmptySpaceItem()
),
Tab("Additional Addresses",
PropertyEditor(m => m.Addresses)
)
)
};
}
}
} 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75