xaf-custom-editors

XAF custom property and list editors - BlazorPropertyEditorBase with ComponentModelBase and Razor component, WinForms WinPropertyEditor/DXPropertyEditor with CreateControlCore/OnControlValueChanged/BreakLinksToControl, custom ListEditor implementation with all required members, attribute-based and manual EditorDescriptorsFactory registration, accessing editors from controllers. Use when building custom controls, third-party component wrappers, or specialized editors in DevExpress XAF Blazor or WinForms.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "xaf-custom-editors" with this command: npx skills add kashiash/xaf-skills/kashiash-xaf-skills-xaf-custom-editors

XAF: Custom Property Editors & List Editors

When to Build a Custom Editor

  • Wrap a third-party control not natively supported by XAF
  • Display data in a specialized way (color picker, rating stars, progress ring)
  • Combine multiple properties into one visual component
  • Use a platform-specific control (Blazor component library, WinForms control)

Blazor Custom Property Editor

Base Class

Inherit from BlazorPropertyEditorBase (namespace: DevExpress.ExpressApp.Blazor.Editors).

using DevExpress.ExpressApp.Blazor.Editors;
using DevExpress.ExpressApp.Model;
using DevExpress.ExpressApp.Editors;

[PropertyEditor(typeof(int), "RatingPropertyEditor", false)]
public class RatingPropertyEditor : BlazorPropertyEditorBase {
    public RatingPropertyEditor(Type objectType, IModelMemberViewItem model)
        : base(objectType, model) { }

    protected override IComponentModel CreateComponentAdapter() {
        return new RatingComponentModel();
    }

    protected override IComponentModel CreateViewerComponentAdapter() {
        return new RatingComponentModel { ReadOnly = true };
    }

    protected override object GetControlValueCore() {
        return ((RatingComponentModel)ComponentModel).Value;
    }

    protected override void ReadValueToControl(object value) {
        if (ComponentModel is RatingComponentModel model)
            model.Value = value is int v ? v : 0;
    }
}

ComponentModel (Blazor bridge)

using DevExpress.ExpressApp.Blazor.Editors.Adapters;

public class RatingComponentModel : ComponentModelBase {
    private int value;
    private bool readOnly;

    public int Value {
        get => value;
        set {
            SetProperty(ref this.value, value);
            ValueChanged?.Invoke(this, EventArgs.Empty);
        }
    }

    public bool ReadOnly {
        get => readOnly;
        set => SetProperty(ref readOnly, value);
    }

    public event EventHandler ValueChanged;
    public override Type ComponentType => typeof(RatingEditorComponent);
}

Razor Component

@* RatingEditorComponent.razor *@
<div class="rating-editor">
    @for (int i = 1; i <= 5; i++) {
        int star = i;
        <span class="@(star <= Model.Value ? "star filled" : "star")"
              @onclick="() => OnStarClick(star)">★</span>
    }
</div>

@code {
    [Parameter]
    public RatingComponentModel Model { get; set; }

    private void OnStarClick(int star) {
        if (!Model.ReadOnly) Model.Value = star;
    }
}

Registration via [PropertyEditor] Attribute

// [PropertyEditor(typeof(PropertyType), "AliasString", isDefault)]
[PropertyEditor(typeof(int), "RatingPropertyEditor", false)]
// isDefault: true  → auto-applied to ALL int properties
// isDefault: false → must assign via [EditorAlias] or Application Model

Assign to a specific property:

[EditorAlias("RatingPropertyEditor")]
public virtual int StarRating { get; set; }

WinForms Custom Property Editor

WinPropertyEditor (standard WinForms controls)

using DevExpress.ExpressApp.Win.Editors;
using DevExpress.ExpressApp.Editors;
using DevExpress.ExpressApp.Model;

[PropertyEditor(typeof(int), "TrackBarPropertyEditor", false)]
public class TrackBarPropertyEditor : WinPropertyEditor {
    public TrackBarPropertyEditor(Type objectType, IModelMemberViewItem model)
        : base(objectType, model) { }

    protected override object CreateControlCore() {
        var trackBar = new TrackBar {
            Minimum = 0,
            Maximum = 100,
            TickFrequency = 10
        };
        trackBar.Scroll += TrackBar_Scroll;
        ControlBindingProperty = nameof(TrackBar.Value); // auto data binding
        return trackBar;
    }

    private void TrackBar_Scroll(object sender, EventArgs e) {
        OnControlValueChanged(); // notify XAF that value changed
    }

    protected override void BreakLinksToControl(bool unwireEventsOnly) {
        if (Control is TrackBar trackBar)
            trackBar.Scroll -= TrackBar_Scroll;
        base.BreakLinksToControl(unwireEventsOnly);
    }

    public new TrackBar Control => (TrackBar)base.Control;
}

DXPropertyEditor (DevExpress controls with RepositoryItem)

using DevExpress.XtraEditors;
using DevExpress.XtraEditors.Repository;
using DevExpress.ExpressApp.Win.Editors;

[PropertyEditor(typeof(int), "SpinEditPropertyEditor", false)]
public class SpinEditPropertyEditor : DXPropertyEditor {
    public SpinEditPropertyEditor(Type objectType, IModelMemberViewItem model)
        : base(objectType, model) { }

    protected override void SetupRepositoryItem(RepositoryItem item) {
        base.SetupRepositoryItem(item);
        if (item is RepositoryItemSpinEdit spinItem) {
            spinItem.MinValue = 0;
            spinItem.MaxValue = 999;
            spinItem.IsFloatValue = false;
        }
    }

    protected override object CreateControlCore() {
        return new SpinEdit();
    }
}

Required Override Summary (WinForms)

MemberPurpose
CreateControlCore()Instantiate and configure control; return it
OnControlValueChanged()Call from control's change event
ControlBindingPropertyNames control property for auto data binding
BreakLinksToControl(bool)Unsubscribe events; base handles disposal

Custom List Editor

using DevExpress.ExpressApp.Editors;
using DevExpress.ExpressApp.Model;

[ListEditor(typeof(object), isDefault: false)]
public class CardListEditor : ListEditor {
    private CardListControl control;

    public CardListEditor(IModelListView info) : base(info) { }

    protected override object CreateControlsCore() {
        control = new CardListControl();
        control.SelectionChanged += (s, e) => OnSelectionChanged();
        control.ItemDoubleClick += (s, e) => OnProcessSelectedItem();
        AssignDataSourceToControl(DataSource);
        return control;
    }

    protected override void AssignDataSourceToControl(IEnumerable dataSource) {
        if (control != null)
            control.DataSource = dataSource;
    }

    public override void Refresh() => control?.Refresh();

    public override IList GetSelectedObjects() =>
        control?.SelectedItems ?? new List<object>();

    public override SelectionType SelectionType => SelectionType.MultipleSelection;

    public override string[] RequiredProperties => Array.Empty<string>();

    public override object FocusedObject {
        get => control?.FocusedItem;
        set { if (control != null) control.FocusedItem = value; }
    }

    public override void Dispose() {
        control?.Dispose();
        control = null;
        base.Dispose();
    }
}

Key ListEditor Members

MemberRequiredPurpose
CreateControlsCore()YesCreate and return the list control
AssignDataSourceToControl(IEnumerable)YesBind data to control
Refresh()YesReload/repaint control data
GetSelectedObjects()YesReturn currently selected objects
SelectionTypeYesNone, SingleObject, MultipleSelection
RequiredPropertiesYesProperty names the control needs
FocusedObjectYesGet/set the focused (current) object
OnSelectionChanged()Call when selection changesFires SelectionChanged event
OnProcessSelectedItem()Call on double-clickOpens Detail View

Manual Registration via EditorDescriptorsFactory

Override in your module (preferred for large apps — avoids attribute scanning):

protected override void RegisterEditorDescriptors(EditorDescriptorsFactory factory) {
    base.RegisterEditorDescriptors(factory);

    factory.RegisterPropertyEditor(
        "RatingPropertyEditor",
        typeof(int),
        typeof(RatingPropertyEditor),
        isDefaultEditor: false);

    factory.RegisterListEditor(
        typeof(MyObject),
        typeof(CardListEditor),
        isDefaultEditor: true);
}

Accessing Editors in Controllers

// Property editor in DetailView
public class MyDetailViewController : ObjectViewController<DetailView, MyObject> {
    protected override void OnViewControlsCreated() {
        base.OnViewControlsCreated();
        var editor = View.FindItem("StarRating") as RatingPropertyEditor;
        if (editor?.ComponentModel is RatingComponentModel model) {
            // customize
        }
    }
}

// List editor in ListView
public class MyListViewController : ObjectViewController<ListView, MyObject> {
    protected override void OnViewControlsCreated() {
        base.OnViewControlsCreated();
        if (View.Editor is CardListEditor listEditor) {
            // access custom list editor members
        }
    }
}

v24.2 vs v25.1 Notes

No breaking changes to the custom editor API between v24.2 and v25.1.

  • Assembly version suffix changes (v24.2.dllv25.1.dll)
  • DxGridListEditor in Blazor: enhanced column API in v25.1
  • .NET target: v24.2 supports .NET 8; v25.1 supports .NET 8 and .NET 9

Source Links

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

xaf-winforms-ui

No summary provided by upstream source.

Repository SourceNeeds Review
General

xaf-office

No summary provided by upstream source.

Repository SourceNeeds Review
General

xaf-editors

No summary provided by upstream source.

Repository SourceNeeds Review
General

xaf-reports

No summary provided by upstream source.

Repository SourceNeeds Review