Back to Devexpress

WPF Best Practices

xpo-403706-best-practices-wpf-best-practices.md

latest7.5 KB
Original Source

WPF Best Practices

  • Jan 19, 2022
  • 4 minutes to read

Create a Data Model for WPF Applications

Recommendation

When you create data model classes for WPF applications, inherit XPO classes from the PersistentBase class.

It is not recommended to use the following classes as base classes:

Detailed Explanation

Classes that inherit XPObject, XPLiteObject, XPCustomObject or XPBaseObject have the following limitations:

  • The TwoWay binding mode does not work with nullable properties: a user cannot clear the editor’s value. Editors with empty values display a validation error. Binding to nullable properties works correctly if a persistent class inherits PersistentBase.
  • You should use Virtual Properties ( PropertyName! and PropertyName!Key ) to bind a collection to a property that references another XPO class. See the following article for more information: How to: Bind an XPCollection to a LookUp.

If you change a base class to PersistentBase, you can lose some functionaly. Follow the recommendations below:

The code sample below shows how to implement a custom base class that enables only the required features:

csharp
using System.Runtime.CompilerServices;

using DevExpress.Data.Filtering;
using DevExpress.Data.Filtering.Helpers;
using DevExpress.Xpo;
using DevExpress.Xpo.Metadata;
//...
    [NonPersistent]
    [DeferredDeletion]
    [OptimisticLocking]
    public class BaseObject : PersistentBase {
        public BaseObject(Session session) : base(session) { }

        [Key(true)]
        [Persistent("OID")]
        private int fOid;
        [PersistentAlias("fOid")]
        public int Oid {
            get => fOid;
        }
        public object EvaluateAlias([CallerMemberName] string memberName = null) {
            XPMemberInfo mi = ClassInfo.GetMember(memberName);
            PersistentAliasAttribute aa = (PersistentAliasAttribute)mi.GetAttributeInfo(typeof(PersistentAliasAttribute));
            EvaluatorContextDescriptor descriptor = ClassInfo.GetEvaluatorContextDescriptor();
            CriteriaOperator criteria = CriteriaOperator.Parse(aa.AliasExpression);
            ExpressionEvaluator evaluator = new ExpressionEvaluator(descriptor, criteria, Session.CaseSensitive, Session.Dictionary.CustomFunctionOperators, Session.Dictionary.CustomAggregates);
            return evaluator.Evaluate(this);

        }
    }
vb
Imports System.Runtime.CompilerServices

Imports DevExpress.Data.Filtering
Imports DevExpress.Data.Filtering.Helpers
Imports DevExpress.Xpo
Imports DevExpress.Xpo.Metadata
' ...
    <NonPersistent, DeferredDeletion, OptimisticLocking> _
    Public Class BaseObject
        Inherits PersistentBase
        Public Sub New(ByVal session As Session)
            MyBase.New(session)
        End Sub

        <Key(True), Persistent("OID")> _
        Private fOid As Integer
        <PersistentAlias("fOid")> _
        Public ReadOnly Property Oid() As Integer
            Function(get) fOid
        End Property
        Public Function EvaluateAlias(Optional <CallerMemberName> ByVal memberName As String = Nothing) As Object
            Dim mi As XPMemberInfo = ClassInfo.GetMember(memberName)
            Dim aa As PersistentAliasAttribute = CType(mi.GetAttributeInfo(GetType(PersistentAliasAttribute)), PersistentAliasAttribute)
            Dim descriptor As EvaluatorContextDescriptor = ClassInfo.GetEvaluatorContextDescriptor()
            Dim criteria As CriteriaOperator = CriteriaOperator.Parse(aa.AliasExpression)
            Dim evaluator As New ExpressionEvaluator(descriptor, criteria, Session.CaseSensitive, Session.Dictionary.CustomFunctionOperators, Session.Dictionary.CustomAggregates)
            Return evaluator.Evaluate(Me)

        End Function
    End Class

Query Data in WPF Applications

Recommendation

To bind a data-aware control to a collection of objects, use the ObservableCollection<T> class instead of XPCollection or XPView.

Use LINQ to XPO to populate ObservableCollection<T> with data.

csharp
// a helper method to support anonymous types
static ObservableCollection<T> ToObservableCollection<T> (this IEnumerable<T> en) { 
    return new ObservableCollection<T>(en); 
}

// examples
var orders = session.Query<Order>().ToObservableCollection();
var customerNames = session.Query<Customer>()
    .Select(c => string.Concat(c.FirstName, " ", c.LastName))
    .ToObservableCollection();
vb
Option Infer On
' a helper method to support anonymous types
 _
Function ToObservableCollection(Of T)(ByVal en As IEnumerable(Of T)) As ObservableCollection(Of T)
    Return New ObservableCollection(Of T)(en)
End Function

' examples
Private orders = session.Query(Of Order)().ToObservableCollection()
Private customerNames = session.Query(Of Customer)() _
    .Select(Function(c) String.Concat(c.FirstName, " ", c.LastName)) _
    .ToObservableCollection()

Detailed Explanation

XPCollection does not raise the ListChanged event when a user changes an editor value if a persistent class is inherited from XPObject, XPLiteObject, XPCustomObject or XPBaseObject. As a result, other editors or controls bound to the modified property may display the previous value until the IEditableObject.EndEdit method is called.

XPView contains ViewRecords instead of XPO objects. This feature is not useful in MVVM applications where the business logic in a ViewModel requires a Model instance (an XPO object).