This is a migrated thread and some comments may be shown as answers.

How to edit CollectionEditor in PropertyGrid to customize control used and format text

17 Answers 900 Views
PropertyGrid
This is a migrated thread and some comments may be shown as answers.
Auticus
Top achievements
Rank 1
Auticus asked on 30 Jun 2017, 03:19 PM

I am using a PropertyGrid.  The grid itself uses a converter to select the proper control to display based on type as such:

<UserControl.Resources>
    <converters:PropertyGridDataTemplateSelector x:Key="dataTemplateSelector">
        <converters:PropertyGridDataTemplateSelector.NumericPropertyDataTemplate>
            <DataTemplate>
                <telerik:RadNumericUpDown propertyGrid:AutoBindBehavior.UpdateBindingOnElementLoaded="Value"/>
            </DataTemplate>
        </converters:PropertyGridDataTemplateSelector.NumericPropertyDataTemplate>
        <converters:PropertyGridDataTemplateSelector.IntegerPropertyDataTemplate>
            <DataTemplate>
                <telerik:RadNumericUpDown propertyGrid:AutoBindBehavior.UpdateBindingOnElementLoaded="Value" NumberDecimalDigits="0"/>
            </DataTemplate>
        </converters:PropertyGridDataTemplateSelector.IntegerPropertyDataTemplate>
    </converters:PropertyGridDataTemplateSelector>
</UserControl.Resources>

 

I then assign the EditorTemplateSelector of the RadPropertyGrid

<telerik:RadPropertyGrid EditorTemplateSelector="{StaticResource dataTemplateSelector}">

 

There are other properties set as well such rendermode flat, data context, etc... but the above is how I am hooking to my converter for the property grid.

The primary property grid works great.  I have a couple of ObservableCollection<myObject> that allow for a drop down and those show a CollectionEditor.  This also works great with two exceptions:

#1 - I have no ability to assign a control to a property because the converter being used is not carried over into the CollectionEditor.  So for example I have a Rate on the object contained in the ObservableCollection.

The value $25.10 shows up as "25.1".  

What I would like is to be able to assign to that property the RadNumericUpDown like the properties in the primary property grid and then just say "NumberDecimalDigits="2" like I can do with the converter. 

However I cannot see a way to do this.

I found an article on Editor Attributes that you can use on the model, but our Data Model that contains our models is not allowed to reference View-Specific control assemblies.  The other attributes you can decorate properties with (like Browsable, Description, etc...) work fine because they are part of the .NET Framework and we have access, but the Editor Attributes you all provide cannot be accessed from the Data Model.

Is there a way to edit the CollectionEditor within a RadPropertyGrid to know to use certain controls like you can do with converters on a RadPropertyGrid itself?  Is there another way to accomplish what I am after?

17 Answers, 1 is accepted

Sort by
0
Auticus
Top achievements
Rank 1
answered on 30 Jun 2017, 03:21 PM

I wrote 2 issues... to clarify

Issue #1 - I need to specify decimal points in my rate property so that it doesn't just auto cut off the 0s.  I want rate to show up as 25.10 instead of 25.1.

Issue #2 - I want that field to be a RadNumericUpDown like it would be on the primary property grid.

0
Dilyan Traykov
Telerik team
answered on 05 Jul 2017, 10:39 AM
Hello Auticus,

You can get ahold of the child RadPropertyGrid through the CollectionEditor property of the CollectionEditorPicker and set its EditorTemplateSelector like so:

private void RadPropertyGrid_Loaded(object sender, RoutedEventArgs e)
{
    var editorPicker = rpg.ChildrenOfType<CollectionEditorPicker>().First();
    var collectionEditor = editorPicker.CollectionEditor;
    collectionEditor.Loaded += CollectionEditor_Loaded;
}
 
private void CollectionEditor_Loaded(object sender, RoutedEventArgs e)
{
    var collectionEditor = sender as CollectionEditor;
    var propertyGrid = collectionEditor.ChildrenOfType<RadPropertyGrid>().First();
    propertyGrid.EditorTemplateSelector = App.Current.Resources["dataTemplateSelector"] as DataTemplateSelector;
}

Please let me know whether this works for you.

Regards,
Dilyan Traykov
Progress Telerik
Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which you to write beautiful native mobile apps using a single shared C# codebase.
0
Auticus
Top achievements
Rank 1
answered on 07 Jul 2017, 03:01 PM

Dilyan,

Implementing the code you have displayed, the RadPropertyGrid loaded event runs.  However CollectionEditor.Loaded never fires off and the results are the same (no formatting).

0
Auticus
Top achievements
Rank 1
answered on 07 Jul 2017, 03:10 PM

Further delving... in FacilityPropertyGrid_OnLoaded, the line

var editorPicker = FacilityPropertyGrid.ChildrenOfType<CollectionEditorPicker>().FirstOrDefault();

returns nothing.  My property grid that I am using apparently has no CollectionEditorPickers within it that I can access.

0
Auticus
Top achievements
Rank 1
answered on 07 Jul 2017, 03:26 PM
(and to answer the followup:  yes I'm sure there are two within that grid)
0
Auticus
Top achievements
Rank 1
answered on 07 Jul 2017, 04:28 PM
<telerik:RadPropertyGrid x:Name="FacilityPropertyGrid"
                         Item="{Binding ActiveFacility}"
                         AutoGeneratePropertyDefinitions="True"
                         SearchInNestedProperties="True"
                         NestedPropertiesVisibility="Visible"
                         RenderMode="Flat"
                         SelectionMode="Single"
                         Loaded="FacilityPropertyGrid_OnLoaded"
                         LabelColumnWidth="300"
                         EditorTemplateSelector="{StaticResource dataTemplateSelector}"
                         DataContext="{x:Static models:EnterpriseManagerModelView.Instance}">
</telerik:RadPropertyGrid>

 

Here is the property grid.  I've noticed from other forum posts that certain properties may cause issues, so perhaps one of these things would prevent the collection editor from being present?

0
Dilyan Traykov
Telerik team
answered on 10 Jul 2017, 11:36 AM
Hello Auticus,

I'm attaching a sample project where I've tried to replicate the setup you've described. The Loaded event is fired as expected upon opening the CollectionEditorPicker.

Could you please have a look and let me know how my project differs from the one you have at your end? It would be of help if you could describe the properties of your bound object.

Thank you in advance for your cooperation on the matter.

Regards,
Dilyan Traykov
Progress Telerik
Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which you to write beautiful native mobile apps using a single shared C# codebase.
0
Auticus
Top achievements
Rank 1
answered on 10 Jul 2017, 12:01 PM

Some differences:

*  You set the DataContext at the Grid level.  I set my data context at the PropertyGrid level.  I also use an instantiated Instance whereas you are just stating {StaticResource MyViewModel}

*  I am catching "AutoGeneratingPropertyDefinition" event, and your example is not in my Property Grid.
*  I have EditorTemplateSelector set to {StaticResource dataTemplateSelector} - you are not setting this on your end on the Property Grid.

Everything else in the xaml is the same.

View Model
My view model implements INotifyPropertyChanged as yours does

The PropertyGrid is bound to the following:

private Facility _activeFacility;
 
       public Facility ActiveFacility
       {
           get { return _activeFacility; }
           set
           {
               _activeFacility = value;
               OnPropertyChanged("ActiveFacility");
           }
       }

The facility model has quite a bit of properties attached to it.  These are all showing up just fine on the Property Grid w

        private ObservableCollection<LaborRate> _laborRates;
 
        //populated external via injection code, not dapper, will be used to copy itself within settings for the various rate drop downs
        //they cannot reference the same collection on the Property Pages grid
        //the user should be able to add labor rates straight into this grid
        [Browsable(false)]
        public ObservableCollection<LaborRate> LaborRates
        {
            get{return _laborRates;}
            set
            {
                _laborRates = value;
                OnPropertyChanged("LaborRates");
 
                if (Settings != null)
                {
                    Settings.LaborRateCollectionChanged();
                }
            }
        } //list of all associated Labor Rates
 
/// <summary>
        /// Settings that only deal with Facility. 
        /// </summary>
        [Description("Facility Specific Settings")]
        [Category("Facility")]
        [DisplayName("Settings")]
        [ReadOnly(true)]
        public FacilitySettings Settings { get { return _settings; } set { _settings = value; OnPropertyChanged("Settings"); } }

 

The Below is the part of the settings object which houses the problem object 
 
 [Description("")]
       [Category("Facility")]
       [DisplayName("Default Replenishment Transfer Labor Rate")]
       public ObservableCollection<LaborRate> DefaultReplenishmentTransferLaborRates
       {
           get { return ParentFacility.LaborRates; }
           set
           {
               ParentFacility.LaborRates = value;
               OnPropertyChanged("DefaultReplenishmentTransferLaborRates");
           }
       }
 
private int _defaultReplenishmentTransferLaborRateID;
 
       [DataCrudParameter(DataOperation.Insert, "DefaultReplenishmentTransferLaborRateID")]
       [Browsable(false)]
       public int DefaultReplenishmentTransferLaborRateID
       {
           get  { return _defaultReplenishmentTransferLaborRateID;}
           set
           {
               _defaultReplenishmentTransferLaborRateID = value;
               if (ParentFacility != null && ParentFacility.LaborRates.Any())
               {
                   //when this object first gets populated via dapper, LaborRates will not have been set yet
                   var laborRate =
                       ParentFacility.LaborRates.FirstOrDefault(lr => lr.LaborRateID == _defaultReplenishmentTransferLaborRateID);
                   if (laborRate != null)
                   {
                       if (_defaultReplenishmentTransferLaborRateID != SelectedDefaultReplenishmentTransferLaborRate.LaborRateID)
                           SelectedDefaultReplenishmentTransferLaborRate = laborRate;
                   }
                   else
                       throw new InvalidOperationException(
                           "DefaultReplenishmentTransferLaborRateID being passed cannot be found within the Labor Rates collection for this facility");
               }
               OnPropertyChanged("DefaultReplenishmentTransferLaborRateID");
           }
       }
0
Auticus
Top achievements
Rank 1
answered on 10 Jul 2017, 12:04 PM

Then we have the Labor Rate object (the object that I need formatting that is failing)

 public sealed class LaborRate : DataModel
    {
        /// <summary>
        /// Dapper Constructor
        /// </summary>
        public LaborRate()
        {

        }

        [Browsable(false)]
        public int LaborRateID { get; set; } //PK

        [DataCrudParameter(DataOperation.Insert, "FacilityID")]
        [Browsable(false)]
        public int FacilityID { get; set; }

        [DataCrudParameter(DataOperation.Insert, "Description")]
        public string Description { get; set; }

        [DataCrudParameter(DataOperation.Insert, "RateDollarsPerHour")]
        [DisplayName("Rate (Dollars Per Hour)")]
        
        public double RateDollarsPerHour { get; set; }

        public override string ToString()
        {
            return Description;
        }
    }

 

So what is happening is that when the drop down for the labor rate appears, the sub form drops down successfully but RateDollarsPerHour is not formatted properly as noted in original post.

When running, the code behind for the xaml OnLoaded DOES fire.  However the second line (var editorPicker...) comes out null.  There is no collection editor picker in the FacilityPropertyGrid.  (as such I put the null check underneath it to prevent app from crashing)

private void FacilityPropertyGrid_OnLoaded(object sender, RoutedEventArgs e)
{
    FacilityPropertyGrid.PreparedEditor += FacilityPropertyGridOnPreparedEditor;
    var editorPicker = FacilityPropertyGrid.ChildrenOfType<CollectionEditorPicker>().FirstOrDefault();
 
    if (editorPicker == null)
        return;
 
    var collectionEditor = editorPicker.CollectionEditor;
    collectionEditor.Loaded += CollectionEditor_Loaded;
}
 
private void FacilityPropertyGridOnPreparedEditor(object sender, PropertyGridPreparedEditorEventArgs propertyGridPreparedEditorEventArgs)
{
     
}
 
private void CollectionEditor_Loaded(object sender, RoutedEventArgs e)
{
    var collectionEditor = sender as CollectionEditor;
    var propertyGrid = collectionEditor.ChildrenOfType<RadPropertyGrid>().First();
    propertyGrid.EditorTemplateSelector = Application.Current.Resources["dataTemplateSelector"] as DataTemplateSelector;
}
0
Auticus
Top achievements
Rank 1
answered on 10 Jul 2017, 12:11 PM
I also noted the Laborrate model did not fire OnPropertyChanged, so changed that and that still did nothing.  The root issue is that your solution is not successful on my end because it is not finding the property collection editor on my property grid (it always comes out null).
0
Dilyan Traykov
Telerik team
answered on 12 Jul 2017, 10:22 AM
Hello Auticus,

I'm afraid that this information is still insufficient for me to isolate the issue at my end.

May I kindly ask you to open a new support ticket and send over a sample project that demonstrates the setup at your end so that I can further assist you with this?

I'm looking forward to your reply.

Regards,
Dilyan Traykov
Progress Telerik
Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
0
Auticus
Top achievements
Rank 1
answered on 12 Jul 2017, 12:40 PM

To get around the issue, any of my objects that have numeric decimal properties I simply don't use a property grid for and just develop a form that displays the object data in bound controls.  (time constraints, I have to get this project done soon)
The simplest way to generate the issue:

* Create WPF Project

* Create a model object.  In my project its called a LaborRate.  LaborRate has a Description (string) and a Rate (double).  The Rate needs to be displayed to the user as a currency value.

* Create another model object.  I call it settings.  Settings has a variety of settings that all work fine, but will have an ObservableCollection of LaborRate.  The XAML Property Grid will render this as a drop down and when clicking the drop down, it will give you the drop down form that has the Add/Remove button and ability to create Labor Rates.  The Labor Rates will have a Description and a Rate, and the Rate will be in the form of a double which will display 25.10 as 25.1.  This is what needs to be formatted.

* Create a model view.  Place the Settings object on the Model View.  Make sure Labor Rates has a couple Labor Rate objects in the observable collection.

* Bind XAML Property Grid to the Settings object.  

* Implement your Loaded event exactly as you have it.

var editorPicker = rpg.ChildrenOfType<CollectionEditorPicker>().First() will throw an exception because the rpg has no children within it of type CollectionEditorPicker.  This is the root of the problem.  Your loaded event DOES fire off every time, but the RadPropertyGrid NEVER has anything of type CollectionEditorPicker in this Loaded event and thus the rest of your code is never reached.

Example project would therefore be:
LaborRate.cs - the model for the labor rate that simply has string Description and double Rate.

Settings.cs - the model that contains several other properties (doesn't matter what you put here, as long as you can see them bound successfully to the grid) and an observableCollection of LaborRate.

ExampleModelView.cs - the model view that contains the Settings object - creates several labor rate objects to insert into the Settings.LaborRates observable collection

Example.xaml - the primary form with the RadPropertyGrid bound to the ExampleModelView data context Settings object

Example.xaml.cs - the code-behind with your code implementation of the loaded events.

This example will not include any of my converters, but the primary issue is that Example.xaml.cs property grid loaded event is not finding any child object within it as you have described and that simple structure above will generate the issue.  

0
Dilyan Traykov
Telerik team
answered on 14 Jul 2017, 01:56 PM
Hello Auticus,

Thank you very much for the detailed explanation.

Based on it I prepared a sample project which seems to work as expected and the ChildrenOfType<CollectionEditorPicker>().First() code correctly gets the CollectionEditorPicker at my end. Thus, the EditorTemplateSelector can correctly pick the template for the property definitions. I'm attaching the project to my reply for your reference.

May I kindly ask you to have a look and let me know if I'm missing something of importance. It may also be of help if you could specify which version and type (standard or NoXaml) of the assemblies you're using.

I'm looking forward to your reply.

Regards,
Dilyan Traykov
Progress Telerik
Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which you to write beautiful native mobile apps using a single shared C# codebase.
0
Auticus
Top achievements
Rank 1
answered on 18 Jul 2017, 06:10 PM

I wasn't aware that there were two different versions.  The project I am working on has no xaml versions anywhere of the control.  If that means we are running NoXaml then that would be what we are using.

Is there another way to tell?

0
Auticus
Top achievements
Rank 1
answered on 18 Jul 2017, 06:52 PM

Your code when I load it into a project works as expected.  My code is nearly identical, which baffles me.

A big difference is that I extensively use decorators and your models are empty. 

Ex:

[DataCrudInsertProcedure("someInsertProc")]
    [DataCrudSelectProcedure("someSelectProc")]
    [DataCrudUpdateProcedure(("someUpdateProc"))]
    public sealed class LaborRate : DataModel
    {
        /// <summary>
        /// Dapper Constructor
        /// </summary>
        public LaborRate()
        {
 
        }
 
        private int _laborRateID;
        [Browsable(false)]
        [DataCrudParameter(DataOperation.Update, "LaborRateID")]
        public int LaborRateID { get { return _laborRateID; } set { _laborRateID = value; OnPropertyChanged("LaborRateID"); } } //PK
 
        private int _facilityID;
        [DataCrudParameter(DataOperation.Insert | DataOperation.Update, "FacilityID")]
        [Browsable(false)]
        public int FacilityID { get { return _facilityID; } set { _facilityID = value; OnPropertyChanged("FacilityID"); } }
 
        private string _description;
 
        [DataCrudParameter(DataOperation.Insert | DataOperation.Update, "Description")]
        public string Description
        {
            get
            { return _description; }
            set
            {
                _description = string.IsNullOrEmpty(value) ? "<unnamed labor rate>" : value;
 
                OnPropertyChanged("Description");
                OnPropertyChanged("Display");
            }
        }
 
        private double _rateDollarsPerHour;
 
        [DataCrudParameter(DataOperation.Insert | DataOperation.Update, "RateDollarsPerHour")]
        [DisplayName("Rate (Dollars Per Hour)")]
        public double RateDollarsPerHour
        {
            get {return _rateDollarsPerHour;}
            set
            {
                _rateDollarsPerHour = value;
                OnPropertyChanged("RateDollarsPerHour");
                OnPropertyChanged("Display");
            }
        }
 
        [Browsable(false)]
        public string Display => Description + " - {" + $"{RateDollarsPerHour:C}" + "}";
 
        public override string ToString()
        {
            return Description;
        }
    }

The data crud attributes are ours.  The DisplayName, Browsable etc are windows attributes that the property grid uses to either not display on the grid or change the display name, etc...

The Facility Settings model also does the same thing.

Additionally we fire OnPropertyChanged event in the setters, and the model examples you gave are empty Get;Set; blocks.

0
Auticus
Top achievements
Rank 1
answered on 18 Jul 2017, 06:55 PM

Regardless, your example shows that it *can* be done.  But there is something with how I'm using DataModels or my ModelView that blocks the control somehow and prevents it from loading the editor until after the fact.  I'll have to play around with that on my own.

Cheers.

0
Dilyan Traykov
Telerik team
answered on 20 Jul 2017, 12:22 PM
Hello Auticus,

Do let me know if you manage to reproduce the issue with the sample project so that I can further assist you.

Regards,
Dilyan Traykov
Progress Telerik
Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
Tags
PropertyGrid
Asked by
Auticus
Top achievements
Rank 1
Answers by
Auticus
Top achievements
Rank 1
Dilyan Traykov
Telerik team
Share this question
or