Ticket T921103
Visible to All Users
Duplicate

Removing IModelColumns from a IModelListView takes too long

created 5 years ago (modified 5 years ago)

Hello support,

I my business objects I have some properties, that are used for administration and I don't want to offer them to potential users - so they would not be visible in a column chooser form, but in the ObjectModel view, they would be simply unchecked, so that user could add them if they would like to. I cannot use Browsable(false) attribute because it would hide the property for all users.

Basically I have to delete some nodes in IModelListView's Columns collection. At first I used custom model generator, found here: ModelViewsNodesGenerator Class, but I found that it works with application model on default level, which means any model differences, for example changed editor type or cloned views are not present here, so this approach is not usable for me.

Then I found out that in this scenario I can catch Application.SetupComplete event, found in a following ticket: How to change the default Application Model value globally or for multiple nodes, and at this point model is fully updated with exception of user model differences, which is exactly what I needed. My logic is that I get all model list views from application model and filter them according to my needs. Then I get collection of distinct types, which are present amongst the views and for each type I get the list of properties I'd like to hide.

The main problem for me is that the operation of removing columns takes too long - about 8 to 10 seconds for about 800 views. I'm currently using Parallel.ForEach, but when I tried it with sequential foreach, it took more than 1 minute. Sadly even 8 more seconds of a startup of a win application is really long time.

Here is sample of my code:

C#
private void Application_SetupComplete(object sender, EventArgs e) { // get list of model views to process filteredListViews = Application.Model.Views.Where(...).ToList(); Parallel.ForEach(filteredListViews, (viewNode) => { var view = viewNode as IModelView; if (view is IModelListView modelListView) { var propertiesToHide = // get properties to hide according to a model class of a model view foreach (var property in propertiesToHide) { var column = modelListView.Columns[property.Name] as IModelColumn; if (column != null) { column.Remove(); } } } }); }

I'd like to ask if this is the right way of customizing application model before application start. Or if removing nodes is really a longer procedure, then if you could recommend different approach.

Thank you.

Answers approved by DevExpress Support

created 2 months ago

Hello,

XAF v24.2 includes a new HideInUI attribute for use within business classes. This is a simple declarative way to hide fields from ListView and DetailView, their customization forms and many other UI contexts such as the Field List of the Filter, Report and Dashboard Editor/Designer. You no longer need manual Controller-based solutions or multiple non-flexible VisibleInXXX and Browsable attributes.

24-2-xaf-control-filed-visibility-for-various-ui-contexts-declaratively@2x.png

We also unified ways to disable runtime layout customization completely - you can now use 2 CustomizationFormEnabled options (global and View-specific) instead of the former 4 options.

Refer to the following breaking change article for more information: EnableColumnChooser and CustomizationEnabled options are replaced by a common cross-plarform option CustomizationFormEnabled.

Best regards,
Herman

    created 5 years ago

    Hello Lukáš,

    The approach you are using is slow because your code forces node generators to create nodes for all views at startup. Normally, node generators are invoked on demand, when a node is accessed for the first time. Also, removing nodes is not a fast operation because it involves the modification of related nodes.

    I recommend that you create a Nodes Generator Updater for ModelListViewColumnsNodesGenerator and remove unwanted columns. This updater will be executed before a View is shown for the first time, which will redistribute the startup delay over the application's lifetime. You can also improve the performance by enabling model caching (XafApplication.EnableModelCache Property). It stores shared model customizations made before the SetupComplete event.

    Since node generators are invoked before model differences are applied, unwanted columns in manually created views should be removed manually.

    See also:
    T271022 - How to change the default Application Model value globally or for multiple nodes

      Show previous comments (1)

        Hi Lukas,

        I had a similar need, so here's what I did:

        An extended VisibleInListViewAttribute:

        C#
        [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)] public sealed class MyVisibleInListViewAttribute : VisibleInListViewAttribute { public MyVisibleInListViewAttribute(ListViewColumnVisibility visibility) : base(visibility == ListViewColumnVisibility.Visible || visibility == ListViewColumnVisibility.Always) { Visibility = visibility; } public ListViewColumnVisibility Visibility { get; } }

        A ListViewColumnVisibility enum that you could extend to your needs. For example add 'Admin' type.

        C#
        public enum ListViewColumnVisibility { /// <summary> /// Column is visible by default and it can be hidden at runtime /// </summary> /// <remarks>The equivalent of VisibleInListViewAttribute(true)</remarks> Visible = 0, /// <summary> /// Column is hidden by default and it can be shown at runtime /// </summary> /// <remarks>The equivalent of VisibleInListViewAttribute(false)</remarks> Hidden = 1, /// <summary> /// Column is always visible and cannot be hidden at runtime /// </summary> Always = 2, /// <summary> /// Column is not visible and cannot be enabled at runtime /// </summary> Never = 3, }

        A ListViewController for the special column handling…

        C#
        public sealed class MyListViewController : ViewController<ListView> { protected override void OnActivated() { base.OnActivated(); if (View.Editor is GridListEditor gridListEditor && !(Frame.Template is ILookupPopupFrameTemplate)) { gridListEditor.CustomizeGridColumn += OnListViewCustomizeGridColumn; } } protected override void OnViewControlsCreated() { base.OnViewControlsCreated(); if (View.Editor is GridListEditor gridListEditor && gridListEditor.GridView is GridView gridView) { try { gridView.BeginUpdate(); foreach (IModelColumn modelColumn in View.Model.Columns) { if (gridListEditor.TryGetGridColumn(modelColumn, out GridColumn gridColumn)) { MyVisibleInListViewAttribute visibleInListViewAttribute = modelColumn.ModelMember?.MemberInfo?.FindAttribute<MyVisibleInListViewAttribute>(); if (visibleInListViewAttribute != null && visibleInListViewAttribute.Visibility == ListViewColumnVisibility.Always) { // Properties decorated with [MyVisibleInListView(ListViewColumnVisibility.Always)] cannot be hidden gridColumn.OptionsColumn.AllowShowHide = false; } } } } finally { gridView.EndUpdate(); } } } private static void OnListViewCustomizeGridColumn(object sender, CustomizeGridColumnEventArgs e) { MyVisibleInListViewAttribute visibleInListView = e.GridColumnInfo.MemberInfo?.FindAttribute<MyVisibleInListViewAttribute>(); if (visibleInListView != null && visibleInListView.Visibility == ListViewColumnVisibility.Never) { // This ensures those columns can never be added at runtime using the grid customization form. e.GridColumn.OptionsColumn.ShowInCustomizationForm = false; } } }

        Then I decorate my properties with the desired visibility:

        C#
        public sealed class MyBusinessObject : BaseObject { [MyVisibleInListView(ListViewColumnVisibility.Hidden)] public string PropertyName { get => propertyName; set => SetPropertyValue(nameof(PropertyName), ref propertyName, value); } private string propertyName; }

        HTH

        Alex

        LL LL
        Lukáš Lindovský 5 years ago

          Hello Alex,

          Thank you for your suggestion, using a custom attribute to decorate properties and then customizing columns in a ViewController is exactly what I had in mind.

          DevExpress Support Team 5 years ago

            Lukáš,

            Node generators and updaters run before any model customizations are applied. If you need to automatically process customized views, modify view controls in a view controller. The solution suggested by Alex is fine.

            Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

            Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.