Wednesday, April 10, 2019

A custom property editor for enumeration dropdown filtering through DataSourceProperty and DataSourceCriteria

    • Dear DX,
      For a while now, I will need to be able to filter out certain enumeration values for enum-edits in a XAF application. So far I managed by either manipulating individual controls through individual BO controllers (just for the sake of), or in the rare case, package the enum in a BO.
      The following links show that this is not an uncommon scenario:
      I may be jumping the gun a bit, but I can not accept the work-around where one has to define a BO around an enumeration just to be able to filter an edit's drop-down list. The only exception to this is when the BO method legitimately adds extra functionality to the enum. In our business cases, this is when a user is allowed to extend the BO enumeration with own 'values' where the actual enumeration field will be set to NULL for those user enums. When not NULL, the BO means it is a system enumeration rather than a user added enum.
      In any other case, I will not subject to package every enum into a BO that needs to have a limited values list. After all, this defeats the purpose of having enum data types at all in the current scenario where they are (enum properties) editable by the user; not to mention all the extra overhead of dealing with a BO rather than a very simple value type.
      Hence, I would like to ask/suggest you to seriously look at providing a generic mechanism to provide filtering for enumeration editors. You keep asking about 'use-cases' which I really do not understand. All of the usecases, (outlined in the linked issued) share a common view:
      -The editor must be able to display the localized enum value for its bound property irregardless of whether the value is present in the editor's visible items list.
      -Some enum values are system controlled (think state-machine), while only a sub-set of the values are user-selectable.
      This doesn't require a concrete, chewed out use case, it is very common business case and I have several of these scenarios in our application where some enum values are a system status (not directly selectable by the user) and other values of the same enum are user-selectable.
      My question is; will I have to write a custom control myself in order to create me this functionality (current editvalue independant of available values to choose from) or will it be worth waiting for a solid solution from your end..?
      Thanks,
      Arno.
Hide comments

  • Krzysztof Krzyzsłof7 years ago
      Amazing! +1 for Arno.
      I send an up-to-date sample:

    2 Solutions

    Creation DateImportanceSort by
    Here is a Win/Web solution.
    I have implemented a Web version of enumeration filtering. So the final solution has:
    - filtering function of enumerable types
    - win & web property editors that cooperate with DataSourceProperty and/or DataSourceCriteria attributes
    - they work with nullable enum types

    The solution (targetted to .NET Framework 4.0 ):

    Example of use:
    [C#]Open in popup window
    [DefaultClassOptions] public class BOWithAnEnum : BaseObject { private StatusColor? _status; private bool _someSwitch; public BOWithAnEnum(Session session) : base(session) { } //[DataSourceProperty("StaticStatusDataSource")] [DataSourceProperty("InstanceStatusDataSource")] //[DataSourceCriteria("Status = Green")] // specify values as properties. Any binary operator can be used. //[DataSourceCriteria("Status = 'Blue'")] // specify values as string values. //[DataSourceCriteria("Status < 'White'")] // Integer comparison //[DataSourceCriteria("Status in (Red, Blue)")] // IN operator, useful for creating model-defined datasource //[DataSourceCriteria("not (Status in ('Red', 'Blue'))")] // NOT IN operator public StatusColor? Status { get { return _status; } set { SetPropertyValue("Status", ref _status, value); } } [ImmediatePostData] public bool SomeSwitch { get { return _someSwitch; } set { SetPropertyValue("SomeSwitch", ref _someSwitch, value); OnChanged("InstanceStatusDataSource"); } } [Browsable(false)] public static IList<StatusColor?> StaticStatusDataSource { get { return new StatusColor?[] { StatusColor.Red, StatusColor.Green }; } } [Browsable(false)] public IList<StatusColor?> InstanceStatusDataSource { get { return SomeSwitch ? new StatusColor?[] { StatusColor.Red, StatusColor.Green } : new StatusColor?[] { StatusColor.White, StatusColor.Blue }; } } }

    Helper class (add it to your platform-independent module, e.g., Application.Module):
    [C#]Open in popup window
    using System; using System.Collections.Generic; using System.ComponentModel; using DevExpress.Data.Filtering; using DevExpress.Persistent.Base; namespace EnumEditSample.Module.Infrastructure { /* Enum criteria parser. Converts enum values specified as fieldnames (OperandProperty) or * specified as string value (OperandValue) into OperandValue instances containing real enum * values. */ public class EnumCriteriaParser : CriteriaProcessorBase { /* Private */ private Dictionary<string, OperandValue> _values = new Dictionary<string, OperandValue>(); /* Internal */ private static void UnsupportedCriteria() { throw new InvalidEnumArgumentException("Unsupported criteria."); } private void UpdatePropertyName(CriteriaOperator operand) { var operandProperty = operand as OperandProperty; if ((ReferenceEquals(operandProperty, null)) || (!operandProperty.PropertyName.Equals(PropertyName))) UnsupportedCriteria(); operandProperty.PropertyName = "Value"; } private void ToValue(IList<CriteriaOperator> operands) { CriteriaOperator operandValue; for (int i = 0; i < operands.Count; i++) if (ToValue(operands[i], out operandValue)) operands[i] = operandValue; } private bool ToValue(CriteriaOperator operand, out CriteriaOperator operandValue) { operandValue = null; string valueName; if (operand is OperandProperty) valueName = ((OperandProperty)operand).PropertyName; else if (operand is OperandValue && ((OperandValue)operand).Value is string) valueName = (string)((OperandValue)operand).Value; else return false; operandValue = _values[valueName]; return true; } /* Constructor */ public EnumCriteriaParser(string propertyName, Type enumType) { PropertyName = propertyName; if (enumType.IsGenericType) { var types = enumType.GetGenericArguments(); if (types.Length == 1 && typeof(Nullable<>).MakeGenericType(types[0]).Equals(enumType)) enumType = types[0]; } EnumType = enumType; foreach (object value in Enum.GetValues(enumType)) _values.Add(value.ToString(), new OperandValue(value)); } public object Visit(InOperator theOperator) { UpdatePropertyName(theOperator.LeftOperand); ToValue(theOperator.Operands); return theOperator; } protected override void Process(InOperator theOperator) { UpdatePropertyName(theOperator.LeftOperand); ToValue(theOperator.Operands); base.Process(theOperator); } protected override void Process(UnaryOperator theOperator) { switch (theOperator.OperatorType) { case UnaryOperatorType.IsNull: UpdatePropertyName(theOperator.Operand); break; case UnaryOperatorType.Not: theOperator.Operand.Accept(this); break; } base.Process(theOperator); } protected override void Process(BinaryOperator theOperator) { UpdatePropertyName(theOperator.LeftOperand); CriteriaOperator operandValue; if (ToValue(theOperator.RightOperand, out operandValue)) theOperator.RightOperand = operandValue; else theOperator.RightOperand.Accept(this); base.Process(theOperator); } protected override void Process(BetweenOperator theOperator) { UpdatePropertyName(theOperator.TestExpression); CriteriaOperator operandValue; if (ToValue((OperandProperty)theOperator.BeginExpression, out operandValue)) theOperator.BeginExpression = operandValue; else theOperator.BeginExpression.Accept(this); if (ToValue((OperandProperty)theOperator.EndExpression, out operandValue)) theOperator.EndExpression = operandValue; else theOperator.EndExpression.Accept(this); base.Process(theOperator); } /* Meta */ public string PropertyName { get; private set; } public Type EnumType { get; private set; } } }

    WIN property editor for enum filtering (add it to your WinForms module, e.g., Application.Module.Win):
    [C#]Open in popup window
    using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Text; using System.Windows.Forms; using DevExpress.Data.Filtering; using DevExpress.Data.Filtering.Helpers; using DevExpress.ExpressApp; using DevExpress.ExpressApp.DC; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Model; using DevExpress.ExpressApp.Win.Core; using DevExpress.ExpressApp.Win.Editors; using DevExpress.XtraEditors; using DevExpress.XtraEditors.Controls; using DevExpress.XtraEditors.Drawing; using DevExpress.XtraEditors.Popup; using DevExpress.XtraEditors.Registrator; using DevExpress.XtraEditors.Repository; using DevExpress.XtraEditors.ViewInfo; using EnumEditSample.Module.Infrastructure; namespace Kraan.ExpressApp.Win.Framework.Editors.Xaf { [PropertyEditor(typeof(Enum), EditorAliases.EnumPropertyEditor)] // this becomes a default editor. If you want to make it non-default, just add a "false" flag to this PropertyEditor attribute. public class FilterableEnumPropertyEditor : DXPropertyEditor, IComplexViewItem { private void UpdateControlWithCurrentObject() { var control = Control as IGridInplaceEdit; if (control != null) control.GridEditingObject = CurrentObject; } protected override object CreateControlCore() { return new XafEnumEdit(); } protected override RepositoryItem CreateRepositoryItem() { return new RepositoryItemXafEnumEdit(); } protected override void SetupRepositoryItem(RepositoryItem item) { base.SetupRepositoryItem(item); var enumEditRepositoryItem = item as RepositoryItemXafEnumEdit; enumEditRepositoryItem.Setup(Application, ObjectSpace, Model); UpdateControlWithCurrentObject(); } protected override void OnCurrentObjectChanged() { base.OnCurrentObjectChanged(); UpdateControlWithCurrentObject(); } public FilterableEnumPropertyEditor(Type objectType, IModelMemberViewItem model) : base(objectType, model) { ImmediatePostData = model.ImmediatePostData; } public new ComboBoxEdit Control { get { return (ComboBoxEdit)base.Control; } } public void Setup(IObjectSpace objectSpace, XafApplication application) { Application = application; ObjectSpace = objectSpace; } public XafApplication Application { get; private set; } public IObjectSpace ObjectSpace { get; private set; } } [System.ComponentModel.DesignerCategory("")] [System.ComponentModel.ToolboxItem(false)] public class RepositoryItemXafEnumEdit : RepositoryItemImageComboBox { private XafApplication _application; private IObjectSpace _objectSpace; private IModelMemberViewItem _model; private IMemberInfo _propertyMemberInfo; private IMemberInfo _dataSourceMemberInfo; private ITypeInfo GetObjectTypeInfo(IModelMemberViewItem model) { var objectView = model.ParentView as IModelObjectView; return objectView != null ? objectView.ModelClass.TypeInfo : null; } internal const string EditorName = "XafEnumEdit"; internal static void Register() { if (!EditorRegistrationInfo.Default.Editors.Contains(EditorName)) { EditorRegistrationInfo.Default.Editors.Add(new EditorClassInfo(EditorName, typeof(XafEnumEdit), typeof(RepositoryItemXafEnumEdit), typeof(ImageComboBoxEditViewInfo), new ImageComboBoxEditPainter(), true, EditImageIndexes.ImageComboBoxEdit, typeof(DevExpress.Accessibility.PopupEditAccessible))); } } protected override RepositoryItem CreateRepositoryItem() { return new RepositoryItemXafEnumEdit(); } static RepositoryItemXafEnumEdit() { RepositoryItemXafEnumEdit.Register(); } public RepositoryItemXafEnumEdit() { ReadOnly = true; TextEditStyle = TextEditStyles.Standard; ShowDropDown = ShowDropDown.SingleClick; } public override void Assign(RepositoryItem item) { if (item is RepositoryItemXafEnumEdit) { var source = item as RepositoryItemXafEnumEdit; if (source != null) { _application = source._application; _objectSpace = source._objectSpace; _model = source._model; _propertyMemberInfo = source._propertyMemberInfo; _dataSourceMemberInfo = source._dataSourceMemberInfo; } } base.Assign(item); } public override BaseEdit CreateEditor() { return base.CreateEditor() as XafEnumEdit; } public void Init(Type type) { EnumImagesLoader loader = new EnumImagesLoader(type); Items.AddRange(loader.GetImageComboBoxItems()); if (loader.Images.Images.Count > 0) { SmallImages = loader.Images; } } public void Setup(XafApplication application, IObjectSpace objectSpace, IModelMemberViewItem model) { /* Applicatie / Objectspace. */ this._application = application; this._objectSpace = objectSpace; this._model = model; /* Reset member infos. */ _propertyMemberInfo = null; _dataSourceMemberInfo = null; /* Get TypeInfo for view object. If not present, nothing to do. */ var typeInfo = GetObjectTypeInfo(model); if (typeInfo == null) return; /* Get member info for property associated to the editor. */ _propertyMemberInfo = typeInfo.FindMember(model.PropertyName); /* Analyse datasource property. */ if (!String.IsNullOrEmpty(model.DataSourceProperty)) { /* Create appropriate datasource property path relative to editor property. * This makes the datasource work with nested properties (xxxx.yyyy.EnunPropertyField) * as well. */ StringBuilder builder = new StringBuilder(model.DataSourceProperty); var path = _propertyMemberInfo.GetPath(); for (int index = path.Count - 2; index >= 0; index--) // property path delimiter builder.Insert(0, ".").Insert(0, path[index].Name); /* Get the member. */ _dataSourceMemberInfo = typeInfo.FindMember(builder.ToString()); } /* default initialisation */ Init(_propertyMemberInfo.MemberType); } public override string EditorTypeName { get { return EditorName; } } public XafApplication Application { get { return _application; } } public IObjectSpace ObjectSpace { get { return _objectSpace; } } public IModelMemberViewItem Model { get { return _model; } } public IMemberInfo PropertyMemberInfo { get { return _propertyMemberInfo; } } public IMemberInfo DataSourceMemberInfo { get { return _dataSourceMemberInfo; } } } [System.ComponentModel.DesignerCategory("")] [System.ComponentModel.ToolboxItem(false)] public partial class XafEnumEdit : ImageComboBoxEdit, IGridInplaceEdit { private static PropertyDescriptorCollection _imageComboBoxItemProperties; private object _gridEditingObject; private IObjectSpace _objectSpace; internal IList GetDataSource(object forObject) { /* Out initialisation. */ CriteriaOperator criteria = null; /* D'r moeten properties zijn. */ if (Properties == null) return null; /* Attempt to get the property datasource if defined. */ IList propertyDataSource = (Properties.DataSourceMemberInfo != null) ? Properties.DataSourceMemberInfo.GetValue(forObject) as IList : null; /* Create datasource containing ImageComboBox items. */ IList dataSource = new List<ImageComboBoxItem>(); if (propertyDataSource == null) /* If no propertydatasource is present, add items. */ for (int i = 0; i < Properties.Items.Count; i++) dataSource.Add((ImageComboBoxItem)Properties.Items[i]); else /* Otherwise only add those whose value exist in the property datasource. */ for (int i = 0; i < Properties.Items.Count; i++) { var item = (ImageComboBoxItem)Properties.Items[i]; if (propertyDataSource.Contains(item.Value)) dataSource.Add(item); } /* Determine criteria presence and convert criteria operators as needed. */ string criteriaString = Properties.Model.DataSourceCriteria; if (!String.IsNullOrEmpty(criteriaString)) criteria = CriteriaOperator.Parse(criteriaString); if (!ReferenceEquals(criteria, null)) { /* Replace OperandProperty objects by OperandValue objects with actual enum values */ criteria.Accept(new EnumCriteriaParser( Properties.PropertyMemberInfo.Name, Properties.PropertyMemberInfo.MemberType)); /* Filter the datasource using the ExpressionEvaluator. */ var filteredDataSource = new ExpressionEvaluator(ImageComboBoxItemProperties, criteria, true).Filter(dataSource); /* Clear the datasource and readd filtered items. */ dataSource.Clear(); foreach (ImageComboBoxItem item in filteredDataSource) dataSource.Add(item); } /* Return... */ return dataSource; } private void ObjectSpaceObjectChanged(object sender, ObjectChangedEventArgs e) { if (e.Object == GridEditingObject && ( String.IsNullOrEmpty(e.PropertyName) || ( Properties.DataSourceMemberInfo != null && Properties.DataSourceMemberInfo.Name.Equals(e.PropertyName)))) { /* Datasource property changed. Nothing to do right now as the windows editor * obtains the datasource on each dropdown. */ } } protected override void OnKeyDown(KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { ClosePopup(); } base.OnKeyDown(e); } protected override void OnPropertiesChanged() { base.OnPropertiesChanged(); if (Properties != null) ObjectSpace = Properties.ObjectSpace; } protected override void Dispose(bool disposing) { if (disposing) { ObjectSpace = null; } base.Dispose(disposing); } protected override PopupBaseForm CreatePopupForm() { return new XafEnumEditPopupForm(this); } protected static PropertyDescriptorCollection ImageComboBoxItemProperties { get { if (_imageComboBoxItemProperties == null) _imageComboBoxItemProperties = TypeDescriptor.GetProperties(typeof(ImageComboBoxItem)); return _imageComboBoxItemProperties; } } static XafEnumEdit() { RepositoryItemXafEnumEdit.Register(); } public XafEnumEdit() { Properties.TextEditStyle = TextEditStyles.Standard; Properties.ReadOnly = true; Height = WinPropertyEditor.TextControlHeight; } public override string EditorTypeName { get { return RepositoryItemXafEnumEdit.EditorName; } } public object EditingObject { get { return BindingHelper.GetEditingObject(this); } } public new RepositoryItemXafEnumEdit Properties { get { return (RepositoryItemXafEnumEdit)base.Properties; } } public object GridEditingObject { get { return _gridEditingObject; } set { if (_gridEditingObject == value) return; _gridEditingObject = value; } } public IObjectSpace ObjectSpace { get { return _objectSpace; } set { if (_objectSpace != null) _objectSpace.ObjectChanged -= ObjectSpaceObjectChanged; _objectSpace = value; if (_objectSpace != null) _objectSpace.ObjectChanged += ObjectSpaceObjectChanged; } } public new XafEnumEditPopupForm PopupForm { get { return (XafEnumEditPopupForm)base.PopupForm; } } } public partial class XafEnumEdit { } public class XafEnumEditPopupForm : PopupListBoxForm { /* Overrides */ protected override void OnBeforeShowPopup() { /* Update datasource. */ UpdateDataSource(); /* Base. */ base.OnBeforeShowPopup(); } protected override void SetupListBoxOnShow() { /* Base. */ base.SetupListBoxOnShow(); /* Gather meta to determine whether and which item to select. */ var visibleItems = ListBox.DataSource as IList; var currentItem = (ImageComboBoxItem)OwnerEdit.SelectedItem; var currentItemInVisibleItems = visibleItems == null || visibleItems.Contains(currentItem); var selectedItem = (ImageComboBoxItem)ListBox.SelectedItem; /* When the listbox selected item differs from the edit value of the editor, or when * the current item isn't present in the listbox, reset the selected item. */ if (selectedItem != currentItem || !currentItemInVisibleItems) selectedItem = null; /* Only when there's not a selected item while the current item exists in the list, * make the current item the selected item. */ if (selectedItem == null && currentItemInVisibleItems) selectedItem = currentItem; /* Set the selected item. */ ListBox.SelectedIndex = -1; // Bug work-around, setting null doesn't always reset SelectedItem to null. ListBox.SelectedItem = selectedItem; } /* Constructor */ public XafEnumEditPopupForm(XafEnumEdit ownerEdit) : base(ownerEdit) { } /* Datasource */ public void UpdateDataSource() { if (Properties == null) return; var dataSource = OwnerEdit.GetDataSource(OwnerEdit.EditingObject) as IList; ListBox.DataSource = dataSource != null ? (object)dataSource : (object)Properties.Items; } /* Infrastructure */ public new XafEnumEdit OwnerEdit { get { return (XafEnumEdit)base.OwnerEdit; } } public new RepositoryItemXafEnumEdit Properties { get { return (RepositoryItemXafEnumEdit)base.Properties; } } } }

    WEB property editor for enum filtering (add it to your ASP.NET module, e.g., Application.Module.Web):
    [C#]Open in popup window
    using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Reflection; using DevExpress.Data.Filtering; using DevExpress.Data.Filtering.Helpers; using DevExpress.ExpressApp; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Model; using DevExpress.ExpressApp.Utils; using DevExpress.ExpressApp.Web.Editors.ASPx; using DevExpress.Persistent.Base; using DevExpress.Web; using EnumEditSample.Module.Infrastructure; namespace Krzysztof.ExpressApp.Web.Framework.Editors.Xaf { [PropertyEditor(typeof(Enum), EditorAliases.EnumPropertyEditor)] // this becomes a default editor. If you want to make it non-default, just add a "false" flag to this PropertyEditor attribute. public class WebFilterableEnumPropertyEditor : ASPxEnumPropertyEditor, IComplexViewItem { XafApplication application; IObjectSpace objectSpace; PropertyInfo dataSourceProperty; DataSourcePropertyIsNullMode isNullMode = DataSourcePropertyIsNullMode.SelectAll; string isNullCriteria; Type propertyType; public void Setup(IObjectSpace objectSpace, XafApplication application) { this.application = application; if (this.objectSpace != null) this.objectSpace.ObjectChanged -= ObjectSpace_ObjectChanged; this.objectSpace = objectSpace; this.objectSpace.ObjectChanged += ObjectSpace_ObjectChanged; } public WebFilterableEnumPropertyEditor(Type objectType, IModelMemberViewItem model) : base(objectType, model) { PropertyInfo propertyInfo = this.ObjectType.GetProperty(this.PropertyName); if (propertyInfo != null) { propertyType = propertyInfo.PropertyType; foreach (var item in propertyInfo.GetCustomAttributes(false)) { DataSourcePropertyAttribute propAttr = item as DataSourcePropertyAttribute; if (propAttr != null && !string.IsNullOrEmpty(propAttr.DataSourceProperty)) { PropertyInfo dataSourceProperty = this.ObjectType.GetProperty(propAttr.DataSourceProperty); isNullMode = propAttr.DataSourcePropertyIsNullMode; isNullCriteria = propAttr.DataSourcePropertyIsNullCriteria; if (dataSourceProperty != null) { if (typeof(IEnumerable).IsAssignableFrom(dataSourceProperty.PropertyType) && dataSourceProperty.PropertyType.IsGenericType && dataSourceProperty.PropertyType.GetGenericArguments()[0].IsAssignableFrom(propertyInfo.PropertyType)) this.dataSourceProperty = dataSourceProperty; } } DataSourceCriteriaAttribute criteriaAttr = item as DataSourceCriteriaAttribute; if (criteriaAttr != null) isNullCriteria = criteriaAttr.DataSourceCriteria; } } } protected override void SetupControl(System.Web.UI.WebControls.WebControl control) { //base.SetupControl(control); if (control is ASPxComboBox) { ASPxComboBox editor = (ASPxComboBox)control; editor.ShowImageInEditBox = true; editor.SelectedIndexChanged += EditValueChangedHandler; FillEditor(editor); } } private void FillEditor(ASPxComboBox editor) { editor.Items.Clear(); editor.ValueType = GetComboBoxValueType(); IEnumerable dataSource = GetDataSource(); if (this.propertyType.IsGenericType && this.propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { editor.Items.Add(CaptionHelper.NullValueText, null); } if (dataSource != null) foreach (object value in dataSource) { if (value is ListEditItem) editor.Items.Add(value as ListEditItem); else editor.Items.Add(CreateEditItem(value)); } } private IEnumerable GetDataSource() { IEnumerable dataSource = null; if (dataSourceProperty != null) { dataSource = dataSourceProperty.GetValue(this.CurrentObject, null) as IEnumerable; if (dataSource != null) { bool hasItems = (dataSource).GetEnumerator().MoveNext(); if (!hasItems) dataSource = null; } } if (dataSource == null) { if (string.IsNullOrEmpty(isNullCriteria)) { if (isNullMode == DataSourcePropertyIsNullMode.SelectAll) return descriptor.Values; else if (isNullMode == DataSourcePropertyIsNullMode.SelectNothing) return null; } else { CriteriaOperator criteriaOperator = CriteriaOperator.Parse(isNullCriteria); if (!ReferenceEquals(criteriaOperator, null)) { dataSource = new List<object>(); foreach (var item in descriptor.Values) ((IList)dataSource).Add(CreateEditItem(item)); /* Replace OperandProperty objects by OperandValue objects with actual enum values */ criteriaOperator.Accept(new EnumCriteriaParser( this.PropertyName, this.propertyType)); /* Filter the datasource using the ExpressionEvaluator. */ var filteredDataSource = new ExpressionEvaluator(ListEditItemProperties, criteriaOperator, true).Filter(dataSource); /* Clear the datasource and readd filtered items. */ ((IList)dataSource).Clear(); foreach (ListEditItem item in filteredDataSource) ((IList)dataSource).Add(item); } } } return dataSource; } private IList WrapToList(IEnumerable dataSource) { if (dataSource == null) return null; ArrayList res = new ArrayList(); foreach (var item in dataSource) res.Add(item); return res; } void ObjectSpace_ObjectChanged(object sender, ObjectChangedEventArgs e) { if (e.NewValue != e.OldValue && dataSourceProperty != null && e.PropertyName == dataSourceProperty.Name) { FillEditor(this.Editor); } } private ListEditItem CreateEditItem(object enumValue) { object value = ConvertEnumValueForComboBox(enumValue); ImageInfo imageInfo = GetImageInfo(enumValue); if (imageInfo.IsUrlEmpty) { return new ListEditItem(descriptor.GetCaption(enumValue), value); } else { return new ListEditItem(descriptor.GetCaption(enumValue), value, imageInfo.ImageUrl); } } protected override object GetControlValueCore() { try { return base.GetControlValueCore(); } catch { if (Editor != null) Editor.SelectedItem = null; return null; } } private static PropertyDescriptorCollection listEditItemProperties; protected static PropertyDescriptorCollection ListEditItemProperties { get { if (listEditItemProperties == null) listEditItemProperties = TypeDescriptor.GetProperties(typeof(ListEditItem)); return listEditItemProperties; } } } }


    Sample project for Win and Web solution:
    There is a much simpler solution if the list of enum values to be excluded is static. Say, you have this enum:
    [C#]Open in popup window
    public enum Gender { None, // We don't want this entry to show up. Male, Female }

    And you want to exclude the “None” entry. Under Localization > Enums, prepend a "-" in front of the texts of the unwanted enum values (e.g. "-None") and use this customized property editor which excludes all values starting with "-": (solution for WinForms only)

    [C#]Open in popup window
    [PropertyEditorAttribute(typeof(Enum), true)] public class FilteringEnumPropertyEditor : EnumPropertyEditor { public FilteringEnumPropertyEditor(Type objectType, IModelMemberViewItem model) : base(objectType, model) { this.CustomSetupRepositoryItem += FilteringEnumPropertyEditor_CustomSetupRepositoryItem; } void FilteringEnumPropertyEditor_CustomSetupRepositoryItem(object sender, CustomSetupRepositoryItemEventArgs e) { var combo = (ComboBoxEdit)Control; for (int i = combo.Properties.Items.Count - 1; i >= 0 ; i--) { if (combo.Properties.Items[i].ToString().StartsWith("-")) { combo.Properties.Items.RemoveAt(i); } } } }


    • Joseph Kellerer 23 years ago
        I extended Olivier's solution to be attribute-based.
        Only for WinForms. I don't know how to do this for Web UI.
        [C#]Open in popup window
        using System; using System.ComponentModel; using System.Reflection; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Model; using DevExpress.ExpressApp.Win.Editors; using DevExpress.XtraEditors; using DevExpress.XtraEditors.Controls; namespace Your.Solution.Module.Win.Editors { /// /// This special EnumPropertyEditor hides enum values marked with Browsable(false) attribute. ///
      /// /// https://www.devexpress.com/Support/Center/Question/Details/Q394379 /// [PropertyEditorAttribute(typeof(Enum), true)] public class FilteringEnumPropertyEditor : EnumPropertyEditor { public FilteringEnumPropertyEditor(Type objectType, IModelMemberViewItem model) : base(objectType, model) { } protected override void OnControlCreated() { base.OnControlCreated(); var combo = (ComboBoxEdit)Control; if (combo.Properties.Items.Count == 0) { return; } ComboBoxItem firstComboBoxItem = combo.Properties.Items[0] as ComboBoxItem; if (firstComboBoxItem == null) { return; } Type type = firstComboBoxItem.Value.GetType(); for (int i = combo.Properties.Items.Count - 1; i >= 0; i--) { ComboBoxItem comboBoxItem = combo.Properties.Items[i] as ComboBoxItem; var memberInfo = type.GetMember(comboBoxItem.Value.ToString()); if (memberInfo.Length == 0) continue; var browsableAttribute = memberInfo[0].GetCustomAttribute<BrowsableAttribute>(); if (browsableAttribute != null && !browsableAttribute.Browsable) { combo.Properties.Items.RemoveAt(i); } } } } }Just add the Browsable(false) attribute to the enum values which shall not be shown in UI.
      Example:
      [C#]Open in popup window
      public enum MyEnum { Option1ShownInUi, Option2ShownInUi, [Browsable(false)] Option3HiddenInUi }


    • Dennis (DevExpress Support)3 years ago
        @Joseph: Thank you for sharing your approach with the XAF community.
      • Alex Miller2 months ago
          Thank you all, for sharing your excellent solutions! Inspired by them, I created a variation for my needs: I couldn’t decorate the enum values and it needed to be compatible with BO inheritance.  Here’s what I came up with:
          A class attribute to decorate BOs:
          [C#]Open in popup window
          [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public sealed class EnumFilterAttribute : Attribute { public EnumFilterAttribute(string propertyName, FilterMode mode, params object[] values) { PropertyName = propertyName; Mode = mode; Values = values; } public string PropertyName { get; } public FilterMode Mode { get; } public object[] Values { get; } }
          A basic FilterMode enum (Yes for now it could be replaced by a bool, but it is easier on code readability and we could add new modes like RemoveAllAfter, etc..)
          [C#]Open in popup window
          public enum FilterMode { AllowOnly = 0, Remove = 1, }
          And a derived EnumPropertyEditor:
          [C#]Open in popup window
          [PropertyEditor(typeof(Enum), true)] public class FilterEnumPropertyEditor:EnumPropertyEditor { public FilterEnumPropertyEditor(Type objectType, IModelMemberViewItem model) : base(objectType, model) { } protected override void SetupRepositoryItem(RepositoryItem item) { base.SetupRepositoryItem(item); if (item is RepositoryItemEnumEdit repositoryItemEnumEdit) { foreach (EnumFilterAttribute enumFilterAttribute in ObjectTypeInfo.FindAttributes<EnumFilterAttribute>()) { if (enumFilterAttribute.PropertyName == PropertyName) { switch (enumFilterAttribute.Mode) { case FilterMode.AllowOnly: for (int i = repositoryItemEnumEdit.Items.Count - 1; i >= 0; i--) { ImageComboBoxItem comboBoxItem = repositoryItemEnumEdit.Items[i]; if (Array.IndexOf(enumFilterAttribute.Values,comboBoxItem.Value) == -1) { repositoryItemEnumEdit.Items.RemoveAt(i); } } break; case FilterMode.Remove: foreach (object enumValue in enumFilterAttribute.Values) { ImageComboBoxItem comboBoxItem = repositoryItemEnumEdit.Items.GetItem(enumValue); if (comboBoxItem != null) { repositoryItemEnumEdit.Items.Remove(comboBoxItem); } } break; } return; } } } } }
          Intended usage:
          [C#]Open in popup window
          public abstract class LogBase : BaseObject { private TraceLevel level; protected LogBase(Session session) : base(session) { } public TraceLevel Level { get => level; set => SetPropertyValue(nameof(Level), ref level, value); } } [DefaultClassOptions] public class Log : LogBase { //Would show a combo with all values public Log(Session session) : base(session) { } } [DefaultClassOptions] [EnumFilter(nameof(Level), FilterMode.Remove, TraceLevel.Verbose)] public class NoVerboseLog : LogBase { //Would show a combo with all values except Verbose public NoVerboseLog(Session session) : base(session) { } } [DefaultClassOptions] [EnumFilter(nameof(Level), FilterMode.AllowOnly, TraceLevel.Error, TraceLevel.Warning)] public class CriticalLog : LogBase { //Would show a combo with only Error and Warning public CriticalLog(Session session) : base(session) { } }
        • Dennis (DevExpress Support)2 months ago
            @Alex Miller: Thank you for sharing!
          • No comments: