当前位置:首页 > 技术 > 正文内容

Avalonia数据绑定:共享模型类示例

访客 技术 2026年10月25日 1

本示例提供了用于 Avalonia 数据绑定的共享模型类,包括 Category、Product 以及数据访问类 StoreDb1 和 StoreDbDataSet。此外,还包含了用于加载数据的 store.xml 和 store.xsd 文件。

Category.cs


using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;

namespace AvaloniaUI.Demos.Book._19.StoreDatabase
{
    public partial class Category : ObservableObject
    {
        [ObservableProperty]
        private string? categoryName;

        [ObservableProperty]
        private int categoryId;

        [ObservableProperty]
        private ObservableCollection<Product> products = new();

        public Category(string categoryName, ObservableCollection<Product> products)
        {
            CategoryName = categoryName;
            Products = products;
        }

        public Category(string categoryName, int categoryId)
        {
            CategoryName = categoryName;
            CategoryId = categoryId;
        }
    }
}
    

Product.cs

包含 Product 类以及两个用于数据转换的辅助类:PriceToBackgroundConverter 和 ImagePathToBitmapConverter。


using Avalonia.Data.Converters;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;

namespace AvaloniaUI.Demos.Book._19.StoreDatabase
{
    public class PriceToBackgroundConverter : IValueConverter
    {
        public IBrush? HighlightBrush { get; set; }
        public decimal MinPrice { get; set; } = 0;

        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
        {
            if (value is Product product)
            {
                return product.UnitCost >= MinPrice ? HighlightBrush : Brushes.Transparent;
            }
            return Brushes.Transparent;
        }

        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
        {
            throw new NotSupportedException("This converter does not support conversion back.");
        }
    }

    public class ImagePathToBitmapConverter : IValueConverter
    {
        private const string ResourcePrefix = "avares://AvaloniaUI/Resources/Images/";
        public static readonly ImagePathToBitmapConverter Instance = new();

        public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
        {
            if (value is not string fileName || string.IsNullOrWhiteSpace(fileName))
                return null;

            try
            {
                var uri = new Uri(ResourcePrefix + fileName);
                using (var stream = AssetLoader.Open(uri))
                {
                    return new Bitmap(stream);
                }
            }
            catch (Exception)
            {
                // Log exception if needed
                return null;
            }
        }

        public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
            => throw new NotSupportedException("This converter does not support conversion back.");
    }

    public partial class Product : ObservableValidator
    {
        [ObservableProperty]
        [NotifyDataErrorInfo]
        [Required(ErrorMessage = "Model number is required.")]
        private string? modelNumber;

        [ObservableProperty]
        [NotifyDataErrorInfo]
        [Required(ErrorMessage = "Model name is required.")]
        [MinLength(3, ErrorMessage = "Model name must be at least 3 characters.")]
        private string? modelName;

        [ObservableProperty]
        [NotifyDataErrorInfo]
        [Range(0.01, 999.99, ErrorMessage = "Unit cost must be between 0.01 and 999.99.")]
        private decimal unitCost;

        [ObservableProperty]
        private string? description;

        [ObservableProperty]
        private string? categoryName;

        [ObservableProperty]
        private int categoryId;

        [ObservableProperty]
        private string? productImagePath;

        [ObservableProperty]
        private DateTime dateAdded = DateTime.Today;

        public bool IsTravel => CategoryName == "Travel";

        public Product(string modelNumber, string modelName, decimal unitCost, string description)
        {
            ModelNumber = modelNumber;
            ModelName = modelName;
            UnitCost = unitCost;
            Description = description;
        }

        public Product(string modelNumber, string modelName, decimal unitCost, string description, string productImagePath)
            : this(modelNumber, modelName, unitCost, description)
        {
            ProductImagePath = productImagePath;
        }

        public Product(string modelNumber, string modelName, decimal unitCost, string description, int categoryId, string categoryName, string productImagePath)
            : this(modelNumber, modelName, unitCost, description)
        {
            CategoryId = categoryId;
            CategoryName = categoryName;
            ProductImagePath = productImagePath;
        }

        public override string ToString()
        {
            return $"{ModelName} ({ModelNumber})";
        }
    }
}
    

StoreDb1.cs

提供了从 DataSet 加载产品和分类数据的方法。


using System;
using System.Collections.ObjectModel;
using System.Data;
using System.Linq;

namespace AvaloniaUI.Demos.Book._19.StoreDatabase
{
    public class StoreDb1
    {
        public Product? GetProductById(int id)
        {
            DataSet? ds = StoreDbDataSet.LoadData();
            DataTable? productsTable = ds?.Tables["Products"];

            if (productsTable == null) return null;

            DataRow? row = productsTable.AsEnumerable()
                                       .FirstOrDefault(r => r.Field<int>("ProductID") == id);

            return row != null ? CreateProductFromRow(row) : null;
        }

        public ObservableCollection<Product> GetAllProducts()
        {
            DataSet? ds = StoreDbDataSet.LoadData();
            DataTable? productsTable = ds?.Tables["Products"];
            var products = new ObservableCollection<Product>();

            if (productsTable == null) return products;

            foreach (DataRow row in productsTable.Rows)
            {
                products.Add(CreateProductFromRow(row));
            }
            return products;
        }

        public ObservableCollection<Category> GetCategoriesWithProducts()
        {
            DataSet? ds = StoreDbDataSet.LoadData();
            DataTable? categoriesTable = ds?.Tables["Categories"];
            DataTable? productsTable = ds?.Tables["Products"];
            var categories = new ObservableCollection<Category>();

            if (categoriesTable == null || productsTable == null) return categories;

            DataRelation? relation = ds?.Relations["CategoryProduct"]; // Assuming the relation name is 'CategoryProduct'

            foreach (DataRow categoryRow in categoriesTable.Rows)
            {
                var categoryProducts = new ObservableCollection<Product>();
                if (relation != null)
                {
                    foreach (DataRow productRow in categoryRow.GetChildRows(relation))
                    {
                        categoryProducts.Add(CreateProductFromRow(productRow));
                    }
                }
                string? name = categoryRow["CategoryName"]?.ToString();
                categories.Add(new Category(name ?? "", categoryProducts));
            }
            return categories;
        }

        public ObservableCollection<Product> GetProductsAboveCost(decimal minCost)
        {
            var allProducts = GetAllProducts();
            var filteredProducts = allProducts.Where(p => p.UnitCost >= minCost).ToList();
            return new ObservableCollection<Product>(filteredProducts);
        }

        public ObservableCollection<Category> GetCategories()
        {
            DataSet? ds = StoreDbDataSet.LoadData();
            DataTable? table = ds?.Tables["Categories"];
            var categories = new ObservableCollection<Category>();

            if (table == null) return categories;

            foreach (DataRow row in table.Rows)
            {
                string? name = row["CategoryName"]?.ToString();
                int id = row.Field<int>("CategoryID");
                categories.Add(new Category(name ?? "", id));
            }
            return categories;
        }

        private Product CreateProductFromRow(DataRow row)
        {
            return new Product(
                row["ModelNumber"]?.ToString() ?? "",
                row["ModelName"]?.ToString() ?? "",
                row.Field<decimal>("UnitCost"),
                row["Description"]?.ToString() ?? "",
                row.Field<int>("CategoryID"),
                row["CategoryName"]?.ToString() ?? "",
                row["ProductImage"]?.ToString() ?? ""
            );
        }
    }
}
    

StoreDbDataSet.cs

负责加载 store.xml 和 store.xsd 文件,并返回一个 DataSet 对象。


using Avalonia.Platform;
using System;
using System.Data;

namespace AvaloniaUI.Demos.Book._19.StoreDatabase
{
    public static class StoreDbDataSet
    {
        private static DataSet? _dataSet;

        public static DataSet? LoadData()
        {
            if (_dataSet != null)
            {
                return _dataSet;
            }

            try
            {
                _dataSet = new DataSet();
                var xsdUri = new Uri("avares://AvaloniaUI/Resources/Datas/store.xsd");
                var xmlUri = new Uri("avares://AvaloniaUI/Resources/Datas/store.xml");

                using (var xsdStream = AssetLoader.Open(xsdUri))
                using (var xmlStream = AssetLoader.Open(xmlUri))
                {
                    _dataSet.ReadXmlSchema(xsdStream);
                    _dataSet.ReadXml(xmlStream);
                }

                if (_dataSet.Tables.Count == 0)
                {
                    throw new InvalidOperationException("Failed to load any tables from the data source.");
                }
                return _dataSet;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error loading data: {ex.Message}");
                _dataSet = null; // Reset on error
                return null;
            }
        }
    }
}
    

store.xml

包含产品和分类的示例数据。


<?xml version="1.0" standalone="yes"?>
<NewDataSet>
  <Products>
    <ProductID>355</ProductID>
    <CategoryID>16</CategoryID>
    <ModelNumber>RU007</ModelNumber>
    <ModelName>Rain Racer 2000</ModelName>
    <ProductImage>360.gif</ProductImage>
    <UnitCost>1499.9900</UnitCost>
    <Description>Looks like an ordinary bumbershoot, but don't be fooled! ...</Description>
    <CategoryID1>16</CategoryID1>
    <CategoryName>Travel</CategoryName>
  </Products>
  <Products>
    <ProductID>356</ProductID>
    <CategoryID>20</CategoryID>
    <ModelNumber>STKY1</ModelNumber>
    <ModelName>Edible Tape</ModelName>
    <ProductImage>image.gif</ProductImage>
    <UnitCost>3.9900</UnitCost>
    <Description>The latest in personal survival gear, the STKY1 looks like a roll of ordinary office tape...</Description>
    <CategoryID1>20</CategoryID1>
    <CategoryName>General</CategoryName>
  </Products>
  <!-- ... more product entries ... -->
  <Categories>
    <CategoryID>14</CategoryID>
    <CategoryName>Communications</CategoryName>
  </Categories>
  <Categories>
    <CategoryID>15</CategoryID>
    <CategoryName>Deception</CategoryName>
  </Categories>
  <!-- ... more category entries ... -->
</NewDataSet>
    

store.xsd

定义了 store.xml 的数据结构和约束。


<?xml version="1.0" standalone="yes"?>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="Products">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ProductID" msdata:ReadOnly="true" msdata:AutoIncrement="true" type="xs:int" />
              <xs:element name="CategoryID" type="xs:int" />
              <xs:element name="ModelNumber" minOccurs="0">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="50" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
              <!-- ... other product elements ... -->
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Categories">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CategoryID" msdata:ReadOnly="true" msdata:AutoIncrement="true" type="xs:int" />
              <xs:element name="CategoryName" minOccurs="0">
                <xs:simpleType>
                  <xs:restriction base="xs:string">
                    <xs:maxLength value="50" />
                  </xs:restriction>
                </xs:simpleType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
    <xs:unique name="Constraint1" msdata:PrimaryKey="true">
      <xs:selector xpath=".//Products" />
      <xs:field xpath="ProductID" />
      <xs:field xpath="CategoryID1" />
    </xs:unique>
    <xs:unique name="Categories_Constraint1" msdata:ConstraintName="Constraint1" msdata:PrimaryKey="true">
      <xs:selector xpath=".//Categories" />
      <xs:field xpath="CategoryID" />
    </xs:unique>
    <xs:keyref name="CategoryProduct" refer="Categories_Constraint1">
      <xs:selector xpath=".//Products" />
      <xs:field xpath="CategoryID" />
    </xs:keyref>
  </xs:element>
</xs:schema>
    

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

linux screen 用法详情 (nohup 的替代方案)

一、screen 是什么?能干嘛?screen 是一个终端复用器,可以:在一个 SSH 会话中开多个“虚拟终端”SSH 断线后,程序仍然在后台运行随时重新连接到原来的会话特别适合:nohup 的替代方案跑脚本 / 爬虫 / 训练模型运维、远程开发二、安装 screen# CentOS / Rocky / Almayum install -y screen# Debian / Ubuntuapt i...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。