Telerik:RadPropertyGrid 似乎无法在集合 Editor/Picker 中呈现嵌套 类

Telerik: RadPropertyGrid does not seem to render nested classes in Collection Editor/Picker

我的问题是二级集合不呈现内部(嵌套)classes 的成员。

二级意味着在 Collections Add/Remove 编辑器中(CollectionEditorPicker)。

我希望在图 2 中显示 class 'powerVoltageDefinition' 的所有内部成员,就像我们在图 1.

有什么诀窍?我如何在这个默认的 Collection Add/Remove 编辑器中显示这样的内部 classes(所谓的 'CollectionEditorPicker')?

图 1 - 正确呈现的嵌套 class 成员

图 2 - 未呈现嵌套 class 成员

XAML 此 RadPropertyGrid 的片段

<telerik:RadPropertyGrid x:Name="SelectedProperty" 

    AutoGenerateBindingPaths="True"
    AutoGeneratePropertyDefinitions ="True" 
    AutoGeneratingPropertyDefinition="RadPropertyGrid_AutoGeneratingPropertyDefinition"
    CanUserResizeDescriptionPanel="True"
    NestedPropertiesVisibility="Visible"
    DescriptionPanelVisibility="Visible"
        SearchInNestedProperties="True"                                                                                                      
        PropertySetMode="Union"                                                                                                  
        RenderMode="Hierarchical"
        EditMode="Default"
        EditEnded="CellEditEnded"
        ToolTip="ToDo: Tool Tips">

</telerik:RadPropertyGrid>

我已经调查过:

2. And this solution does not cover my nested class powerVoltageDefinition

我的解决方案会导致这些吗?:

1. PropertyGridIndentPresenter Class

2. PropertyGridIndentPresenter.IndentLevelProperty

由于 Telerik 似乎不支持将其原生 CollectionEditor/Picker 附加到 RadPropertyGrid 的情况,我们的第二个最佳选择是(恕我直言)创建自己的 WPF 控件。

这是我对这种 "generic" 控制的建议:

UniCollectionPropertyGrid.XAML:

<UserControl x:Class="TrackDataEditor.XAML.UniCollectionPropertyGrid"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:TrackDataEditor.XAML"
         xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
         xmlns:telerikDocking="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Docking"                     
         mc:Ignorable="d" 
         d:DesignHeight="400" d:DesignWidth="700">
<Grid>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <!-- The Items Pane  -->
    <!-- <telerikDocking:RadPane x:Name="TemplatesPane" Header="Templates"> -->
    <Grid Grid.Column="0" Grid.Row="0">

        <!-- Icons to control template instances-->
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- #### +/- Buttons #### -->
        <DockPanel x:Name="Buttons" Grid.Row="0" HorizontalAlignment="Stretch">
            <Button x:Name="AddItemButton" Foreground="Green" HorizontalAlignment="Stretch" VerticalAlignment="Top" Width="30" ToolTip="Add an element" Click="AddItem">{+}</Button>
            <Button x:Name="RemoveItemButton" Foreground="Red" HorizontalAlignment="Left" VerticalAlignment="Top" Width="30" ToolTip="Remove the selected element" Click="RemoveItem">{-}</Button>
            <Button x:Name="RedrawPropGridButton" HorizontalAlignment="Left" VerticalAlignment="Top" Width="30" ToolTip="Redraw actualy selected element" Click="RedrawPropGrid">{R}</Button>
        </DockPanel>

        <!-- #### Lister of items #### -->
        <telerik:RadGridView Grid.Row="1"
                             x:Name="TheListOfItems" ItemsSource="{Binding}" CanUserReorderColumns="True"
                             SelectionChanged="ListOfItems_SelectionChanged"
                             Loaded="ListOfItems_Loaded"
                             CanUserInsertRows="True" CanUserDeleteRows="True"
                             AutoGenerateColumns="True"
                             RowIndicatorVisibility="Collapsed"
                             ShowGroupPanel="False">
        </telerik:RadGridView>

    </Grid>

    <!-- #### Properties of selected item #### -->                
    <telerik:RadPropertyGrid Grid.Column="1" Grid.Row="1"
                            x:Name="ThePropertyGrid" 
                            AutoGenerateBindingPaths="True"
                            AutoGeneratePropertyDefinitions ="True"
                            AutoGeneratingPropertyDefinition="PropertyGrid_AutoGeneratingPropertyDefinition"

                            DescriptionPanelVisibility="Collapsed" 
                            CanUserResizeDescriptionPanel="True"                               

                            OverridesDefaultStyle="True"
                            SearchBoxVisibility="Collapsed"

                            SearchInNestedProperties="True"                                 
                            NestedPropertiesVisibility="Visible"
                            RenderMode="Hierarchical"

                            PropertySetMode="Intersection"                                                                                                                                 

                            EditMode="Default"
                            EditEnded="PropertyGrid_CellEditEnded"
                            SelectionChanged="PropertyGrid_SelectionChanged"

                            FieldIndicatorVisibility ="Collapsed"
                            EnableEditorCaching ="True"
                            EnableCustomFiltering  ="False"

                            ToolTip="ToDo: Tool Tips">
    </telerik:RadPropertyGrid>
    <!-- EditEnded="CellEditEnded" -->

</Grid>

背后的C#代码: UniCollectionPropertyGrid.XAML.cs

/// <summary>
/// Interaction logic for UniCollectionPropertyGrid.xaml
/// </summary>
public partial class UniCollectionPropertyGrid : UserControl
{
    private static Logger logger = LogManager.GetCurrentClassLogger();

    // C-tor
    public UniCollectionPropertyGrid()
    {
        InitializeComponent();
    }

    /// <summary>
    /// Called when user clicks add item button: {+}
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void AddItem(object sender, RoutedEventArgs e)
    {
        if (this.DataContext is SomeDataContextClass)
        {
            SomeDataContextClass dataCtx = (this.DataContext as SomeDataContextClass);

            dynamic newItem = null;

            // What is being currenlty edited (this was unfortunatelly "impossible?" to pass dynamically. 
            // (Why ? Because by this time, Telerik & WPF neither seem to provide no mechanism to distinguish generic controls like this one, generated by editor template (factory) ...)
            switch (MainWindow.EditedPropertyName)
            {
                case "Property1":
                    newItem = new Property1Type();
                    dataCtx.Property1_List.Add(newItem as Property1Type);
                    TheListOfItems.ItemsSource = dataCtx.Property1_List;
                    break;
                case "Property2":
                    newItem = new Property2Type();
                    dataCtx.Property2_List.Add(newItem as Property2Type);
                    TheListOfItems.ItemsSource = dataCtx.Property2_List;
                    break;
            }

            // Set the newly added item as selected
            TheListOfItems.SelectedItem = newItem;
            // SetBinding it to property grid to be rendered
            ThePropertyGrid.Item = newItem;                
        }
    }

    /// <summary>
    /// Called when user clicks remove item button: {-}
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void RemoveItem(object sender, RoutedEventArgs e)
    {
        // Leave right away if no item is selected
        if (TheListOfItems.SelectedItem == null) return;

        if (this.DataContext is SomeDataContextClass)
        {
            SomeDataContextClass dataCtx = (this.DataContext as SomeDataContextClass);
            switch (MainWindow.EditedPropertyName)
            {
                case "Property1":
                    dataCtx.Property1_List.Remove(TheListOfItems.SelectedItem as Property1Type);
                    break;
                case "Property2":
                    dataCtx.Property2_List.Remove(TheListOfItems.SelectedItem as Property2Type);
                    break;
            }
        }

        // Nothing to show on property grid
        ThePropertyGrid.Item = null;
    }


    /// <summary>
    /// Called when user clicks Redraw property grid button: {R}
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void RedrawPropGrid(object sender, RoutedEventArgs e)
    {
        // This redraws, however closes all opened elements in property grid
        ThePropertyGrid.Item = null;
        ThePropertyGrid.Item = TheListOfItems.SelectedItem;
    }

    // ------------------------------------------------------------------------------------------------

    #region LIST_OF_ITEMS_EVENT_HANDLERS

    /// <summary>
    /// Fired, when user clicks on any item of the ListOfItems table (RadGridView).
    /// Is used to set selected item to the property grid
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ListOfItems_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangeEventArgs e)
    {
        // Try to redraw - wonder if this works
        // this.Arrange(new Rect(RenderSize));
        // this.UpdateLayout();

        ThePropertyGrid.Item = this.TheListOfItems.SelectedItem;
        // Prevent further traveling down the GUI hierarchy
        e.Handled = true;
    }


    /// <summary>
    /// Fired, when ListOfItem is started (loaded)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void ListOfItems_Loaded(object sender, RoutedEventArgs e)
    {
        if (this.DataContext is SomeDataContextClass)
        {
            SomeDataContextClass dataCtx = (this.DataContext as SomeDataContextClass);
            switch (MainWindow.EditedPropertyName)
            {
                case "Property1":
                    TheListOfItems.ItemsSource = dataCtx.Property1_List;
                    break;
                case "Property2":
                    TheListOfItems.ItemsSource = dataCtx.Property2_List;
                    break;
            }

        }
    }

    #endregion LIST_OF_ITEMS_EVENT_HANDLERS

    // ------------------------------------------------------------------------------------------------

    #region PROPERTY_GRID_EVENT_HANDLERS

    /// <summary>
    /// Here you can hide whatever is unwanted, or otherwise control look of properties
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void PropertyGrid_AutoGeneratingPropertyDefinition(object sender, Telerik.Windows.Controls.Data.PropertyGrid.AutoGeneratingPropertyDefinitionEventArgs e)
    {
        try
        {
            string dispName = e.PropertyDefinition.DisplayName;

            dynamic parentValue = e.PropertyDefinition.ParentProperty?.Value;

            // Unfortunatelly e.PropertyDefinition.Value is null, so we can't switch by object type
            switch (dispName)
            {
                case "Item":
                case "ItemElementName":
                    e.PropertyDefinition.Visibility = Visibility.Collapsed; // Filter out Item
                    break;

                case "SomeOtherProperty":
                    e.PropertyDefinition.Visibility = ((parentValue?.RangeType as ItemChoiceType?) == ItemChoiceType.choiceOne) ? Visibility.Visible : Visibility.Collapsed;
                    break;                   
            }
        }
        catch (Exception ex)
        {
            logger.Error("Problem occured when generating properties of the parent value: {1}  \nReason: {2}", e.PropertyDefinition.ParentProperty?.Value, ex.Message);
        }

    }


    /// <summary>
    /// Fired, when user clicks on any RadPropertyGrid element.
    /// Is used to define specific actions based on which property is selected
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void PropertyGrid_SelectionChanged(object sender, Telerik.Windows.Controls.SelectionChangeEventArgs e)
    {

        // Do something, like RedrawWholeControl();

        // Prevent further traveling down the GUI hierarchy
        e.Handled = true;
    }

    /// <summary>
    /// Event handler when (Segment, Template, or Node) Rad Property Grid cell was edited and just commited to be stored in element property.
    /// Enables different handling of various edited elements (cells)
    /// So far as example a segment length cell is filtered and handled (invoked setting of graphical rail widget length)
    /// This can be used to perform validation of input values (for example)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void PropertyGrid_CellEditEnded(object sender, Telerik.Windows.Controls.Data.PropertyGrid.PropertyGridEditEndedEventArgs e)
    {
        string editedPropertyName = null;

        if (sender is Telerik.Windows.Controls.RadPropertyGrid)
        {
            // Find out what property was actually edited
            Telerik.Windows.Controls.RadPropertyGrid rpg = (sender as Telerik.Windows.Controls.RadPropertyGrid);
            editedPropertyName = rpg.SelectedPropertyDefinition.DisplayName;

            // Prevent further traveling down the GUI hierarchy
            e.Handled = true;
        }

        // This redraws, however closes all opened elements in property grid
        ThePropertyGrid.Item = null;
        ThePropertyGrid.Item = TheListOfItems.SelectedItem;

    }

    #endregion PROPERTY_GRID_EVENT_HANDLERS

}

那你就用这个uni-control作为客户端定义的数据模板class XAML:

        <!-- Universal template for rendering any sort of nested class structure -->
    <DataTemplate x:Key="UniversalTemplatePropertyGrid">
        <local:UniCollectionPropertyGrid DataContext="{Binding}"/>
    </DataTemplate>

并且在后面的客户端 class 代码中,您指定在部署指定名称的 collection 时打开此控件:

    private void RadPropertyGrid_AutoGeneratingPropertyDefinition(object sender, Telerik.Windows.Controls.Data.PropertyGrid.AutoGeneratingPropertyDefinitionEventArgs e)
    {
        string dispName = e.PropertyDefinition.DisplayName;

        // Unfortunatelly e.PropertyDefinition.Value is null, so we can't switch by object type
        switch (dispName)
        {
            case "Property1_List":
            case "Property2_List":
                e.PropertyDefinition.EditorTemplate = this.Resources["UniversalTemplatePropertyGrid"] as DataTemplate;
                break;

            default:
                break;
        }
    }

下面是这种通用控件的外观 (powerVoltageChangesList):

优点: 1.这样的控件不局限于复杂(嵌套)classes里面 collection.

  1. 你可以完全控制这个 class,因为你有源代码。

  2. 作为奖励,在递归模式的情况下:嵌套 Collection->ComplexClass->Collection->ComplexClass->Collection-> .. .. 您可以通过重复调用完全相同的模板来解决问题:您刚刚创建的 UniversalTemplatePropertyGrid(即 UniCollectionPropertyGrid class)。