# 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 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();
        }
    }
}
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 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)
                    )
                )
            };
        }
    }
}
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, 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 guildines.

# 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)
                    )
                )
            };
        }
    }
}
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