# 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
m
ande
are used, wherem
is short forMember
ande
forEditor
.
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