📄 OutGridTree.Window/MainWindow.axaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Converters;

namespace OutGridTree.Window;

internal sealed partial class MainWindow : Avalonia.Controls.Window
{
    private readonly RpcService rpcService;
    private readonly ObservableCollection<object?> records = [];

    public MainWindow(RpcService rpcService)
    {
        this.rpcService = rpcService;
        rpcService.TitleReceived += OnTitleReceived;
        rpcService.HeadersReceived += OnHeadersReceived;
        rpcService.RecordReceived += OnRecordReceived;

        InitializeComponent();

        Table.ItemsSource = records;
    }

    private void OnTitleReceived(string title)
    {
        Dispatcher.Post(() => Title = title);
    }

    private void OnHeadersReceived(IEnumerable<string> headers)
    {
        Dispatcher.Post(() => SetPsObjectHeaders(headers));
    }

    private void OnRecordReceived(object? record)
    {
        Dispatcher.Post(() => AddRecord(record));
    }

    private void AddRecord(object? record)
    {
        if (record is not null && Table.Columns.Count is 0)
        {
            if (record is PSObject psObject)
            {
                SetPsObjectHeaders(psObject.Properties.Select(p => p.Name));
            }
            else
            {
                SetPrimitiveHeader(record.GetType());
            }
        }

        records.Add(record);
    }

    private void SetPsObjectHeaders(IEnumerable<string> headers)
    {
        foreach (var header in headers)
        {
            Table.Columns.Add(
                new DataGridTextColumn()
                {
                    Header = header,
                    Binding = CompiledBinding.Create<object, object>(
                        o => o,
                        converter: new FuncValueConverter<object, string?>(o =>
                            (o as PSObject)?.Properties[header].Value?.ToString()
                        )
                    ),
                }
            );
        }
    }

    private void SetPrimitiveHeader(Type type)
    {
        var header = "Primitive";
        if (type == typeof(bool))
        {
            header = "Boolean";
        }
        else if (
            type == typeof(byte)
            || type == typeof(sbyte)
            || type == typeof(short)
            || type == typeof(ushort)
            || type == typeof(int)
            || type == typeof(uint)
            || type == typeof(long)
            || type == typeof(ulong)
        )
        {
            header = "Integer";
        }
        else if (type == typeof(float) || type == typeof(double) || type == typeof(decimal))
        {
            header = "Number";
        }
        else if (type == typeof(char))
        {
            header = "Character";
        }
        else if (type == typeof(string))
        {
            header = "String";
        }

        Table.Columns.Add(
            new DataGridTextColumn()
            {
                Header = header,
                Binding = CompiledBinding.Create<object, object>(
                    o => o,
                    converter: new FuncValueConverter<object, string?>(o => o?.ToString())
                ),
            }
        );
    }

    protected override void OnClosed(EventArgs e)
    {
        rpcService.HeadersReceived -= OnHeadersReceived;
        rpcService.RecordReceived -= OnRecordReceived;

        base.OnClosed(e);
    }
}