# 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 IntelliSence driven way to craft layouts in code.

By way of a quick reminder the illustration below shows what the final layout should look like;

Person Result Layout

# Registration

As before the first task is to tell XAF to use the LayoutBuilders.
To do this it is necessary to 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();
        }
    }
}
1
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();
        }
    }
}
1
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
            {

            };
        }
    }
}
1
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 m and e are used, where m is short for Member and e for Editor.
This is regarded as best practice convention 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)
                    )
                )
            };
        }
    }
}
1
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. 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 guildines.

# Inherit from LayoutBuilder<T>

Thus far the LayoutBuilder<T> has been used as an instance utilising 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.
To do this it is necessary to inherit from the LayoutBuilder<T> class and change the registration as illustrated 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)
                    )
                )
            };
        }
    }
}
1
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