This help topic describes how to allow end users to select a property value of a simple type (string, integer, enumeration, and so on) in a lookup editor.
Populate the Lookup Editor with Values from Another Property
This scenario displays user-friendly strings instead of simple type values in a lookup editor.
Scenario
Suppose you have the following class and want to display user-friendly strings for the Position integer property:
File: MySolution.Module\BusinessObjects\DemoClass.cs.
using DevExpress.Persistent.Base;
using System.ComponentModel.DataAnnotations;
//...
[DefaultClassOptions]
public class DemoClass : BaseObject {
public virtual int Position { get; set; }
}
// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Xpo;
//...
[DefaultClassOptions]
public class DemoClass : BaseObject {
public DemoClass(Session session) : base(session) { }
int _position;
public int Position {
get { return _position; }
set { SetPropertyValue(nameof(Position), ref _position, value); }
}
}
Imports DevExpress.Persistent.Base
Imports DevExpress.Persistent.BaseImpl
Imports DevExpress.Xpo
'...
<DefaultClassOptions>
Public Class DemoClass
Inherits BaseObject
Public Sub New(ByVal session As Session)
MyBase.New(session)
End Sub
Private _position As Integer
Public Property Position() As Integer
Get
Return _position
End Get
Set(ByVal value As Integer)
SetPropertyValue(NameOf(Position), _position, value)
End Set
End Property
End Class

The UI with a numeric editor (before customization)

The UI with a lookup editor (after customization)
Solution
Apply the Browsable attribute to the Position property to hide its editor from the UI:
using System.ComponentModel;
//...
public class DemoClass : BaseObject {
//...
[Browsable(false)]
public virtual int Position { get; set; }
}
// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
using System.ComponentModel;
//...
public class DemoClass {
//...
[Browsable(false)]
public int Position {
//...
}
}
Imports System.ComponentModel
'...
Public Class DemoClass
'...
<Browsable(False)>
Public Property Position As Integer
'...
End Property
End Class
Create an additional PositionPropertyWrapper class that is a wrapper for the DemoClass.Position property. Apply the DomainComponent attribute to this class to make it non-persistent. In PositionPropertyWrapper, the Key property stores the original integer values, and the DisplayName property returns their user-friendly string representations.
File: MySolution.Module\BusinessObjects\PositionPropertyWrapper.cs.
using DevExpress.ExpressApp.DC;
//...
[DomainComponent, XafDefaultProperty(nameof(PositionName))]
public class PositionPropertyWrapper {
private int _key;
private string _positionName;
public PositionPropertyWrapper(string positionName, int key) {
this._key = key;
this._positionName = positionName;
}
[DevExpress.ExpressApp.Data.Key]
public int Key { get { return _key; } }
public string PositionName { get { return _positionName; } }
}
Imports DevExpress.ExpressApp.DC
'...
<DomainComponent, XafDefaultProperty(nameof(PositionName))>
Public Class PositionPropertyWrapper
Private _key As Integer
Private _positionName As String
Public Sub New(ByVal positionName As String, ByVal key As Integer)
Me._key = key
Me._positionName = positionName
End Sub
<DevExpress.ExpressApp.Data.Key>
Public ReadOnly Property Key() As Integer
Get
Return _key
End Get
End Property
Public ReadOnly Property PositionName() As String
Get
Return _positionName
End Get
End Property
End Class
Important
Use the Key attribute from the DevExpress.ExpressApp.Data namespace only (not from the System.ComponentModel.DataAnnotations or DevExpress.Xpo namespaces). This attribute is required for XAF ASP.NET Web Forms and ASP.NET Core Blazor applications.
Extend DemoClass with the PositionDataSource property that stores the collection of non-persistent PositionPropertyWrapper objects. The Position lookup editor displays these objects. Apply the Browsable attribute to the PositionDataSource property to hide its editor from the UI:
File: MySolution.Module\BusinessObjects\DemoClass.cs.
using System.ComponentModel;
using System.ComponentModel.Annotations;
//...
public class DemoClass : BaseObject {
//...
private BindingList<PositionPropertyWrapper> _positionDataSource;
[NotMapped, Browsable(false)]
public BindingList<PositionPropertyWrapper> PositionDataSource {
get {
if (_positionDataSource == null) {
_positionDataSource = new BindingList<PositionPropertyWrapper>();
for (int i = 0; i < 5; i++) {
_positionDataSource.Add(new PositionPropertyWrapper("Position" + i.ToString(), i));
}
}
return _positionDataSource;
}
}
}
// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
using System.ComponentModel;
//...
public class DemoClass {
//...
private BindingList<PositionPropertyWrapper> _positionDataSource;
[Browsable(false)]
public BindingList<PositionPropertyWrapper> PositionDataSource {
get {
if (_positionDataSource == null) {
_positionDataSource = new BindingList<PositionPropertyWrapper>();
for (int i = 0; i < 5; i++) {
_positionDataSource.Add(new PositionPropertyWrapper("Position" + i.ToString(), i));
}
}
return _positionDataSource;
}
}
}
Imports System.ComponentModel
'...
Public Class DemoClass
'...
Private dataSource As BindingList(Of PositionPropertyWrapper)
<Browsable(False)>
Public ReadOnly Property PositionsDataSource() As BindingList(Of PositionPropertyWrapper)
Get
If dataSource Is Nothing Then
dataSource = New BindingList(Of PositionPropertyWrapper)()
For i As Integer = 0 To 4
dataSource.Add(New PositionPropertyWrapper("Position" & i.ToString(), i))
Next i
End If
Return dataSource
End Get
End Property
End Class
Extend DemoClass with the non-persistent PositionWrapper property of the PositionPropertyWrapper type to update the persistent Position property. Apply the DataSourceProperty attribute to PositionWrapper to populate the lookup editor data source:
using System.Linq;
using System.ComponentModel.Annotations;
// ...
public class DemoClass : BaseObject {
// ...
private PositionPropertyWrapper _positionPropertyWrapper;
[NotMapped, XafDisplayName("Position")]
[DataSourceProperty(nameof(PositionDataSource))]
public PositionPropertyWrapper PositionWrapper {
get {
if (_positionPropertyWrapper == null || _positionPropertyWrapper.Key != Position) {
_positionPropertyWrapper = PositionDataSource.FirstOrDefault(i => i.Key == Position);
}
return _positionPropertyWrapper;
}
set {
_positionPropertyWrapper = value;
Position = value.Key;
}
}
}
// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
using System.Linq;
// ...
public class DemoClass {
// ...
private PositionPropertyWrapper _positionPropertyWrapper;
[NonPersistent, XafDisplayName("Position")]
[DataSourceProperty(nameof(PositionDataSource))]
public PositionPropertyWrapper PositionWrapper {
get {
if (_positionPropertyWrapper == null || _positionPropertyWrapper.Key != Position) {
_positionPropertyWrapper = PositionDataSource.FirstOrDefault(i => i.Key == Position);
}
return _positionPropertyWrapper;
}
set {
SetPropertyValue(nameof(PositionWrapper), ref _positionPropertyWrapper, value);
if (!IsLoading && !IsSaving) {
Position = value.Key;
}
}
}
}
Imports System.Linq
' ...
Public Class DemoClass
' ...
Private _positionPropertyWrapper As PositionPropertyWrapper
<NonPersistent, XafDisplayName("Position"), DataSourceProperty(nameof(PositionDataSource))>
Public Property PositionWrapper() As PositionPropertyWrapper
Get
If _positionPropertyWrapper Is Nothing OrElse _positionPropertyWrapper.Key <> Position Then
_positionPropertyWrapper = PositionDataSource.FirstOrDefault(Function(i) i.Key = Position)
End If
Return _positionPropertyWrapper
End Get
Set(ByVal value As PositionPropertyWrapper)
SetPropertyValue(NameOf(PositionWrapper), _positionPropertyWrapper, value)
If Not IsLoading AndAlso Not IsSaving Then
Position = value.Key
End If
End Set
End Property
End Class
Optional. You can also hide the lookup editor’s Clear button for a non-nullable PositionWrapper property. To do this, set its AllowClear property to false in the Model Editor or apply ModelDefaultAttribute to PositionWrapper:
using DevExpress.ExpressApp.Model;
// ...
public class DemoClass : BaseObject {
// ...
[ModelDefault("AllowClear", "false")]
public PositionPropertyWrapper PositionWrapper {
// ...
}
}
Imports DevExpress.ExpressApp.Model
' ...
Public Class DemoClass
' ...
<ModelDefault("AllowClear", "false")>
Public Property PositionWrapper() As PositionPropertyWrapper
' ...
End Property
End Class
Populate the Lookup Editor with Persistent Business Objects
This scenario displays a lookup editor with persistent objects instead of a simple type editor. Use this technique if you have legacy databases and cannot modify their schemas to create associations between tables.
Scenario
Suppose you have the following classes and want to display the Position.Title lookup editor instead of the DemoClass.PositionTitle string editor.
using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExoress.Persistent.BaseImpl.EF;
using System.ComponentModel.DataAnnotations;
//...
[DefaultClassOptions]
public class DemoClass : BaseObject {
public virtual string PositionTitle { get; set; }
}
[DefaultClassOptions, XafDefaultProperty(nameof(Title))]
public class Position : BaseObject {
public virtual string Title { get; set; }
}
// Make sure that you use options.UseChangeTrackingProxies() in your DbContext settings.
using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Xpo;
//...
[DefaultClassOptions]
public class DemoClass : BaseObject {
public DemoClass(Session session) : base(session) { }
string positionTitle;
public string PositionTitle {
get { return positionTitle; }
set { SetPropertyValue(nameof(PositionTitle), ref positionTitle, value); }
}
}
[DefaultClassOptions, XafDefaultProperty(nameof(Title))]
public class Position : BaseObject {
public Position(Session session) : base(session) { }
string _title;
[Size(SizeAttribute.DefaultStringMappingFieldSize)]
public string Title {
get {
return _title;
}
set {
SetPropertyValue(nameof(Title), ref _title, value);
}
}
}
Imports DevExpress.ExpressApp.DC
Imports DevExpress.Persistent.Base
Imports DevExpress.Persistent.BaseImpl
Imports DevExpress.Xpo
'...
<DefaultClassOptions>
Public Class DemoClass
Inherits BaseObject
Public Sub New(ByVal session As Session)
MyBase.New(session)
End Sub
Private _positionTitle As String
Public Property PositionTitle As String
Get
Return _positionTitle
End Get
Set(ByVal value As String)
SetPropertyValue(NameOf(PositionTitle), _positionTitle, value)
End Set
End Property
End Class
<DefaultClassOptions, XafDefaultProperty(NameOf(Position.Title))>
Public Class Position
Inherits BaseObject
Public Sub New(ByVal session As Session)
MyBase.New(session)
End Sub
Private _title As String
<Size(SizeAttribute.DefaultStringMappingFieldSize)>
Public Property Title As String
Get
Return _title
End Get
Set(ByVal value As String)
SetPropertyValue(NameOf(Title), _title, value)
End Set
End Property
End Class

The UI with a string editor (before customization)

The UI with a lookup editor (after customization)
Solution
Apply the Browsable attribute to the PositionTitle property to hide its editor from the UI:
using System.ComponentModel;
//...
public class DemoClass : BaseObject {
//...
[Browsable(false)]
public string PositionTitle {
//...
}
}
Imports System.ComponentModel
'...
Public Class DemoClass
'...
<Browsable(False)>
Public Property PositionTitle As String
'...
End Property
End Class
Create a non-persistent wrapper property (LookupPropertyForDisplay) to fetch records from the Position data table:
using DevExpress.Data.Filtering;
using DevExpress.ExpressApp.DC;
//...
public class DemoClass : BaseObject {
//...
private Position _LookupPropertyForDisplay;
[NotMapped, XafDisplayName("Position")]
public virtual Position LookupPropertyForDisplay {
get {
if ((_LookupPropertyForDisplay == null && !string.IsNullOrEmpty(positionTitle)) ||
(_LookupPropertyForDisplay != null && _LookupPropertyForDisplay.Title != PositionTitle)) {
_LookupPropertyForDisplay =
ObjectSpace.GetObjectsQuery<Position>().FirstOrDefault(t => t.Title == positionTitle);
}
return _LookupPropertyForDisplay;
}
set {
_LookupPropertyForDisplay = value;
PositionTitle = value != null ? value.Title : string.Empty;
}
}
}
// Make sure that you use options.UseChangeTrackingProxies() and options.UseObjectSpaceLinkProxies() in your DbContext settings.
using DevExpress.Data.Filtering;
using DevExpress.ExpressApp.DC;
using DevExpress.Xpo;
//...
public class DemoClass : BaseObject {
//...
private Position _LookupPropertyForDisplay;
[NonPersistent, XafDisplayName("Position")]
public Position LookupPropertyForDisplay {
get {
if ((_LookupPropertyForDisplay == null && !string.IsNullOrEmpty(positionTitle)) ||
(_LookupPropertyForDisplay != null && _LookupPropertyForDisplay.Title != PositionTitle)) {
_LookupPropertyForDisplay =
Session.FindObject<Position>(new BinaryOperator("Title", positionTitle));
}
return _LookupPropertyForDisplay;
}
set {
SetPropertyValue<Position>(nameof(LookupPropertyForDisplay),
ref _LookupPropertyForDisplay, value);
if (!IsLoading && !IsSaving) {
PositionTitle = value != null ? value.Title : string.Empty;
}
}
}
}
Imports DevExpress.Data.Filtering
Imports DevExpress.ExpressApp.DC
Imports DevExpress.Xpo
'...
Public Class DemoClass
'...
Private _LookupPropertyForDisplay As Position
<NonPersistent, XafDisplayName("Position")>
Public Property LookupPropertyForDisplay() As Position
Get
If (_LookupPropertyForDisplay Is Nothing AndAlso Not String.IsNullOrEmpty(positionTitle)) OrElse
(_LookupPropertyForDisplay IsNot Nothing AndAlso _LookupPropertyForDisplay.Title <> PositionTitle) Then
_LookupPropertyForDisplay =
Session.FindObject(Of Position)(New BinaryOperator("Title", positionTitle))
End If
Return _LookupPropertyForDisplay
End Get
Set(ByVal value As Position)
SetPropertyValue(Of Position)(NameOf(LookupPropertyForDisplay),
_LookupPropertyForDisplay, value)
If Not IsLoading AndAlso Not IsSaving Then
PositionTitle = If(value IsNot Nothing, value.Title, String.Empty)
End If
End Set
End Property
End Class
Notes