自己写一个ObjectDataProvider类

ObjectDataProvider.cs代码

using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Xml.Linq;
using System.Xml.XPath;
using Avalonia.Platform;
using System.Diagnostics;
using System.Linq;

namespace Shares.Avalonia
{
    public partial class ObjectDataProvider : ObservableObject
    {
        [ObservableProperty]
        private Type? objectType;

        [ObservableProperty]
        private string? methodName;

        [ObservableProperty]
        private string? source;

        [ObservableProperty]
        private string? xPath;

        [ObservableProperty]
        private object? data;

        private bool isRefreshing;

        public ObjectDataProvider()
        {
            PropertyChanged += (_, e) =>
            {
                if (e.PropertyName is nameof(ObjectType)
                    or nameof(MethodName)
                    or nameof(Source)
                    or nameof(XPath))
                {
                    Refresh();
                }
            };
        }

        public void Refresh()
        {
            if (isRefreshing)
                return;

            isRefreshing = true;

            try
            {
                if (ObjectType != null && !string.IsNullOrWhiteSpace(MethodName))
                {
                    Data = CallObjectMethod();
                    return;
                }

                if (!string.IsNullOrWhiteSpace(Source) && !string.IsNullOrWhiteSpace(XPath))
                {
                    Data = LoadFromXml();
                    return;
                }

                Data = null;
            }
            finally
            {
                isRefreshing = false;
            }
        }

        private object? CallObjectMethod()
        {
            try
            {
                var instance = CreateInstanceEvenWithoutDefaultCtor(ObjectType!);
                var method = ObjectType!.GetMethod(MethodName!);
                return method?.Invoke(instance, null);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[ObjectDataProvider] Method invoke error: {ex}");
                return null;
            }
        }

        private object? LoadFromXml()
        {
            try
            {
                var uri = new Uri(Source!, UriKind.RelativeOrAbsolute);
                using var stream = AssetLoader.Open(uri);
                var doc = XDocument.Load(stream);

                var nodes = doc.XPathSelectElements(XPath!);

                // 只在 ObjectType 为空时推断,且避免循环触发
                if (ObjectType == null)
                {
                    var nodeName = GetLastNodeNameFromXPath(XPath!);
                    var inferred = FindTypeByName(nodeName);

                    if (inferred != null && inferred != ObjectType)
                        ObjectType = inferred;

                    // 若仍然找不到类型 → 返回 XElement 列表
                    if (ObjectType == null)
                        return new List<XElement>(nodes);
                }

                var listType = typeof(List<>).MakeGenericType(ObjectType!);
                IList list = (IList)Activator.CreateInstance(listType)!;

                foreach (var node in nodes)
                {
                    var obj = CreateObjectFromXml(node);
                    if (obj != null)
                        list.Add(obj);
                }

                return list;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[ObjectDataProvider] XML load error: {ex}");
                return null;
            }
        }

        private object? CreateObjectFromXml(XElement node)
        {
            try
            {
                var instance = CreateInstanceEvenWithoutDefaultCtor(ObjectType!);
                if (instance == null)
                    return null;

                foreach (var prop in ObjectType!.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    // 先查找 Element
                    var el = node.Element(prop.Name);
                    string? xmlValue = el?.Value;

                    // 查找 Attribute
                    if (xmlValue == null)
                    {
                        var attr = node.Attribute(prop.Name);
                        xmlValue = attr?.Value;
                    }

                    if (xmlValue == null)
                        continue;

                    try
                    {
                        object? converted = ConvertXmlValue(xmlValue, prop.PropertyType);
                        if (converted != null || prop.PropertyType.IsClass)
                            prop.SetValue(instance, converted);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine($"[ObjectDataProvider] Convert error on {prop.Name}: {ex}");
                    }
                }

                return instance;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[ObjectDataProvider] Object create error: {ex}");
                return null;
            }
        }

        // 支持 Enum、Nullable<T>、基本类型
        private static object? ConvertXmlValue(string value, Type targetType)
        {
            try
            {
                // Nullable<T>
                var underlying = Nullable.GetUnderlyingType(targetType);
                if (underlying != null)
                    return Convert.ChangeType(value, underlying, CultureInfo.InvariantCulture);

                // Enum
                if (targetType.IsEnum)
                    return Enum.Parse(targetType, value, ignoreCase: true);

                // 默认行为
                return Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
            }
            catch
            {
                return null;
            }
        }

        private static object? CreateInstanceEvenWithoutDefaultCtor(Type type)
        {
            try
            {
                var defaultCtor = type.GetConstructor(Type.EmptyTypes);
                if (defaultCtor != null)
                    return defaultCtor.Invoke(null);

                // 使用“参数最少构造器”
                var ctor = type.GetConstructors()
                               .OrderBy(c => c.GetParameters().Length)
                               .First();

                var parameters = ctor.GetParameters();
                var args = new object?[parameters.Length];

                for (int i = 0; i < parameters.Length; i++)
                    args[i] = GetDefaultValue(parameters[i].ParameterType);

                return ctor.Invoke(args);
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[ObjectDataProvider] Create instance error: {ex}");
                return null;
            }
        }

        private static object? GetDefaultValue(Type t)
        {
            if (t.IsValueType)
                return Activator.CreateInstance(t);
            return null;
        }

        private static string GetLastNodeNameFromXPath(string xpath)
        {
            var p = xpath.Trim().Split('/', StringSplitOptions.RemoveEmptyEntries);
            return p.Length > 0 ? p[^1] : "";
        }

        private static Type? FindTypeByName(string name)
        {
            try
            {
                foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
                {
                    foreach (var t in asm.GetTypes())
                    {
                        if (t.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
                            return t;
                    }
                }
                return null;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"[ObjectDataProvider] Find type error: {ex}");
                return null;
            }
        }
    }
}

BindToObjectDataProvider.axaml代码

<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Height="550" Width="400"
        x:Class="AvaloniaUI.BindToObjectDataProvider"
        xmlns:data="using:AvaloniaUI.Demos.Book._19.StoreDatabase"

        Title="BindToObjectDataProvider">
    <Window.Resources>
        <!--
        <ObjectDataProvider x:Key="products"
                    ObjectType="data:StoreDb1"
                    MethodName="GetProducts"/>
        -->

        <ObjectDataProvider x:Key="products"
                    Source="avares://AvaloniaUI/Resources/Datas/store.xml"
                    XPath="/NewDataSet/Products"
                    ObjectType="data:Product"/>
</Window.Resources>
    
    <Grid RowDefinitions="*,auto,*">
        <Grid RowDefinitions="auto,*">
            <TextBlock Margin="5">ObjectDataProvider Example...</TextBlock>

            <ListBox Grid.Row="1"
                     x:Name="lstProducts"
                     Margin="5"
                     ItemsSource="{Binding Source={StaticResource products}, Path=Data}">
                <ListBox.ItemTemplate>
                    <DataTemplate x:DataType="data:Product">
                        <TextBlock Text="{Binding ModelName}" />
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>

        <GridSplitter Grid.Row="1"
                      Height="5"
                      HorizontalAlignment="Stretch"
                      VerticalAlignment="Bottom"
                      ResizeDirection="Rows"
                      ResizeBehavior="PreviousAndNext" />
        
        <Border Grid.Row="2"
                Padding="7"
                Margin="7"
                Background="LightSteelBlue">

            <Grid DataContext="{Binding #lstProducts.SelectedItem}" x:DataType="data:Product"
                  RowDefinitions="auto,auto,auto,auto,*"
                  ColumnDefinitions="auto,*">

                <TextBlock Margin="7">Model Number:</TextBlock>
                <TextBox Margin="5"
                         Grid.Column="1"
                         Text="{Binding ModelNumber}" />

                <TextBlock Margin="7"
                           Grid.Row="1">Model Name:</TextBlock>
                <TextBox Margin="5"
                         Grid.Row="1"
                         Grid.Column="1"
                         Text="{Binding ModelName}" />

                <TextBlock Margin="7"
                           Grid.Row="2">Unit Cost:</TextBlock>
                <TextBox Margin="5"
                         Grid.Row="2"
                         Grid.Column="1"
                         Text="{Binding UnitCost}" />

                <TextBlock Margin="7,7,7,0"
                           Grid.Row="3">Description:</TextBlock>
                <ScrollViewer Grid.Row="4"
                         Grid.Column="0"
                         Grid.ColumnSpan="2">
                    <TextBox Margin="7"
                         AcceptsReturn="True"
                         TextWrapping="Wrap"
                         Text="{Binding Description}" />
                </ScrollViewer>
            </Grid>
        </Border>
        
    </Grid>
</Window>

BindToObjectDataProvider.axaml.cs代码

using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace AvaloniaUI;

public partial class BindToObjectDataProvider : Window
{
    public BindToObjectDataProvider()
    {
        InitializeComponent();
    }
}

运行效果

image

 

posted on 2026-01-02 08:00  dalgleish  阅读(0)  评论(0)    收藏  举报