Avalonia.PropertyGrid

repository·main·Indexed 43 days ago

https://github.com/bodong1987/avalonia.propertygrid

A PropertyGrid implementation for Avalonia applications featuring automatic class property analysis, simultaneous multi-object editing, and extensive customization. It supports data validation via Data Annotations, dynamic property visibility using PropertyVisibilityConditionAttribute, and custom cell editors via AbstractCellEditFactory.

Tokens
8.9K
Snippets
16
Records
31
Agent score
54%

What's inside Avalonia.PropertyGrid

  1. Overview of Avalonia.PropertyGrid features

    main

    Avalonia.PropertyGrid is a comprehensive property editor implementation for Avalonia applications. It provides a high-level interface for inspecting and editing object properties, similar to DevExpress's PropertyGridControl.

    Key capabilities include:

    • Automatic Property Analysis: Automatically discovers and displays class properties for editing.
    • Multi-Object Editing: Edit multiple objects simultaneously within a single control.
    • Customization: Supports custom property labels, custom cell editors, and custom operation areas.
    • Advanced Data Handling: Supports ICustomTypeDescriptor and TypeDescriptionProvider for custom types, collection editing (add/insert/delete/clear), and data validation.
    • User Experience: Includes built-in Undo/Redo, global ReadOnly settings, path picking, and automatic sub-object expansion.
    • Display & Filtering: Offers category-based, alphabetical, and built-in sorting modes, along with text and regex filtering.
    • Dynamic UI: Supports automatic property visibility based on conditions, real-time localization, and resizable columns via title dragging.
  2. Use PropertyGrid for common containers

    main

    Version 11.3.0.21 introduced display and editing support for common containers.

    Note: Containers that implement observable interfaces have limitations; they will not support dynamic operations such as adding, deleting, or clearing items within the grid interface.

  3. Configure property behavior with Attributes

    main

    You can control how properties are displayed and edited in the PropertyGrid by applying attributes to your class properties. The library supports standard System.ComponentModel attributes as well as specialized attributes from PropertyModels.ComponentModel and PropertyModels.ComponentModel.DataAnnotations.

    Standard Attributes

    • System.ComponentModel.CategoryAttribute: Set the property category.
    • System.ComponentModel.BrowsableAttribute: Hide a property.
    • System.ComponentModel.ReadOnlyAttribute: Make a property read-only.
    • System.ComponentModel.DisplayNameAttribute: Set a user-friendly name.
    • System.ComponentModel.DescriptionAttribute: Set a description that appears as a tooltip.
    • System.ComponentModel.PasswordPropertyTextAttribute: Mark a text property as a password.
    • System.ComponentModel.DataAnnotations.EditableAttribute: Enable add/remove/clear for list properties.
    • System.ComponentModel.DataAnnotations.RangeAttribute: Set numeric ranges.
    • System.Runtime.Serialization.IgnoreDataMemberAttribute: Hide a property.
  4. Configure properties using Attributes

    main

    To customize how properties appear and behave in the PropertyGrid, decorate your class properties with various attributes. The PropertyGrid automatically analyzes these to select the appropriate CellEdit (editor) and display settings.

    Standard System Attributes

    • System.ComponentModel.CategoryAttribute: Groups properties into categories.
    • System.ComponentModel.BrowsableAttribute: Hides properties from the grid.
    • System.ComponentModel.ReadOnlyAttribute: Makes a property read-only.
    • System.ComponentModel.DisplayNameAttribute: Sets a user-friendly name.
    • System.ComponentModel.DescriptionAttribute: Sets a description that appears as a Tooltip when hovering over the title.
    • System.ComponentModel.PasswordPropertyTextAttribute: Marks text properties as password fields.
    • System.ComponentModel.DataAnnotations.EditableAttribute: Enables adding, deleting, or clearing elements in list properties.
    • System.ComponentModel.DataAnnotations.RangeAttribute: Sets numeric ranges.
  5. Implement dynamic property visibility

    main

    You can make properties appear or disappear based on the values of other properties using PropertyVisibilityConditionAttribute and ConditionTarget.

    Requirements:

    1. Any property used as a condition for another property must be marked with [ConditionTarget] so the PropertyGrid knows to refresh visibility when it changes.
    2. Use [PropertyVisibilityCondition(nameof(DependencyProperty), expectedValue)] on the target property.
    3. Use [DependsOnProperty(nameof(Dep1), nameof(Dep2))] to declare complex dependencies.

    Example:

    public class DynamicVisibilityObject : ReactiveObject
    {
        [ConditionTarget]
        public bool IsShowPath { get; set; } = true;
    
        [PropertyVisibilityCondition(nameof(IsShowPath), true)]
        public string Path { get; set; } = "";
    
        [ConditionTarget]
        public PlatformID Platform { get; set; } = PlatformID.Win32NT;
    
        [PropertyVisibilityCondition(nameof(Platform), PlatformID.Unix)]
        [ConditionTarget]
        public string UnixVersion { get; set; } = "";
    
        [Browsable(false)]
        [DependsOnProperty(nameof(IsShowPath), nameof(Platform), nameof(UnixVersion))]
        [ConditionTarget]
        public bool IsShowUnixLoginInfo => IsShowPath && Platform == PlatformID.Unix && UnixVersion.IsNotNullOrEmpty();
    }
  6. Use Avalonia.PropertyGrid in your application

    main

    To use the PropertyGrid, add the control to your XAML and bind the object you wish to display and edit to its DataContext property.

    • For a single object: Bind the specific instance to DataContext.
    • For multiple objects: Bind an IEnumerable<T> directly to DataContext to enable simultaneous editing of multiple objects.

    Ensure you include the correct XML namespace for the control: xmlns:apc="clr-namespace:Avalonia.PropertyGrid.Controls;assembly=Avalonia.PropertyGrid".

    <!-- MainView.axaml -->
    <UserControl xmlns="https://github.com/avaloniaui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:apc="clr-namespace:Avalonia.PropertyGrid.Controls;assembly=Avalonia.PropertyGrid"
                 x:Class="AvaloniaApplication19.Views.MainView">
    
        <!-- bind your target object to DataContext -->
        <apc:PropertyGrid DataContext="{Binding Target}"></apc:PropertyGrid>
    </UserControl>
  7. Manage PropertyModels dependency

    main

    The data modeling module was extracted into a separate project called PropertyModels to support the separation of data and presentation.

    • For v11.0.4.1+: You may need to install two NuGet packages: Avalonia.PropertyGrid and PropertyModels.
    • For v11.1.1.1+: Avalonia.PropertyGrid automatically includes PropertyModels as a dependency.

    Important: If you are referencing PropertyModels directly in your own custom data model project, ensure its version matches the version used by the Avalonia.PropertyGrid package in your solution to avoid compatibility issues.

  8. Customize Property Operations (Buttons and Menus)

    main

    You can customize the operation area (buttons/menus) by configuring PropertyOperationVisibility and handling CustomPropertyOperationControl or CustomPropertyOperationMenuOpening events.

    Important: When modifying properties within these events, always use the Factory provided in the PropertyCellContext (e.g., e.Context.Factory!.SetPropertyValue(...)). This ensures changes are integrated into the framework's command pattern for undo/redo support.

    private void OnCustomPropertyOperationMenuOpening(object? sender, CustomPropertyDefaultOperationEventArgs e)
    {
        if (e.Context.Property.Name == nameof(TestExtendsObject.CustomOperationMenuNumber))
        {
            e.Menu.Items.Clear();
            var minMenuItem = new MenuItem { Header = "Min" };
            minMenuItem.Click += (s, args) => 
            {
                // Use the factory to ensure command pattern integration
                e.Context.Factory!.SetPropertyValue(e.Context, 0);
            };
            e.Menu.Items.Add(minMenuItem);
        }
    }
    private void OnCustomPropertyOperationMenuOpening(object? sender, CustomPropertyDefaultOperationEventArgs e)
    {
        if (!e.Context.Property.IsDefined<PropertyOperationVisibilityAttribute>())
        {
            return;
        }
    
        if (e.Context.Property.Name == nameof(TestExtendsObject.CustomOperationMenuNumber))
        {
            if (e.StageType == PropertyDefaultOperationStageType.Init)
            {
                e.DefaultButton.SetLocalizeBinding(Button.ContentProperty, "Operation");    
            }
            else if (e.StageType == PropertyDefaultOperationStageType.MenuOpening)
            {
                // If you don't want to create the menu every time, you can move it to Init, so you don't have to Clear it here.
                e.Menu.Items.Clear();
                    
                var minMenuItem = new MenuItem();
                minMenuItem.SetLocalizeBinding(HeaderedSelectingItemsControl.HeaderProperty, "Min");
                minMenuItem.Click += (s, args) => 
                {
                    e.Context.Factory!.SetPropertyValue(e.Context, 0);
                };
    
                var maxMenuItem = new MenuItem();
                maxMenuItem.SetLocalizeBinding(HeaderedSelectingItemsControl.HeaderProperty, "Max");
                maxMenuItem.Click += (s, args) => 
                {
                    e.Context.Factory!.SetPropertyValue(e.Context, 1024); 
                };
            
                var errorMenuItem = new MenuItem();
                errorMenuItem.SetLocalizeBinding(HeaderedSelectingItemsControl.HeaderProperty, "GenError");
                errorMenuItem.Click += (s, args) => 
                {
                    e.Context.Factory!.SetPropertyValue(e.Context, 1024000);
                };
    
                e.Menu.Items.Add(minMenuItem);
                e.Menu.Items.Add(maxMenuItem);
                e.Menu.Items.Add(errorMenuItem);
            }
        }
    }
  9. Implement data reloading and synchronization

    main

    To ensure the PropertyGrid updates when your data changes, implement System.ComponentModel.INotifyPropertyChanged and trigger the PropertyChanged event.

    For better integration:

    • Inherit from PropertyModels.ComponentModel.ReactiveObject to gain dynamic visibility refresh support.
    • Inherit from PropertyModels.ComponentModel.MiniReactiveObject for basic data change notification.
    • If using ReactiveUI.ReactiveObject directly, you must manually monitor properties and trigger change events; dynamic visibility support is not included automatically.
  10. Install Avalonia.PropertyGrid via NuGet

    main

    You can integrate Avalonia.PropertyGrid into your Avalonia application by installing the NuGet package. This allows you to use the PropertyGrid control to automatically analyze, display, and edit class properties.

    https://www.nuget.org/packages/bodong.Avalonia.PropertyGrid