Avalonia DataGrid——来自NP.Avalonia.Visuals包的高级功能

目录

介绍

演示代码

演示代码

实现说明

过滤功能的实现

更改列可见性

保存/恢复DataGrid布局

结论


介绍

Avalonia是一个很棒的新的多平台.NET框架,用于构建UI桌面应用程序。它与WPF非常相似,更强大且错误更少,并且不仅可以在Windows上运行,还可以在LinuxMac上运行。可以称之为多平台WPF++。最近,可以在浏览器中运行Avalonia并且我知道Avalonia的移动版本即将推出。

Avalonia直到最近才准备好生产,因此,主要的第三方组件提供商,如TelerikDevExpressInfragistics仍然没有发布Avalonia的组件。

根据我的经验,像Telerik这样的大型供应商只需要两个组件——一个窗口停靠功能和一个数据网格。其他一切——自定义按钮、框等都可以(并且应该)由团队使用WPFAvalonia原语轻松构建,以满足用户体验要求。

我的UniDock包弥补了Avalonia中对接框架的不足。

DataGrid已经存在于Avalonia。很大程度上,它是内置WPF DataGrid的一个端口。不幸的是,就像它的WPF对应物一样,Avalonia DataGrid缺少一些重要功能,包括:

  1. 过滤
  2. 更改网格列的可见性
  3. 保存/恢复布局
  4. 分组

DataGrid除了分组(在我的列表中并且应该很快添加)之外,我已将上述所有功能添加到内置的Avaloni中。所有这些都添加到我的NP.Avalonia.Visuals/包中。

本文提供了一个示例,演示如何使用这些高级功能。

 如果您是初学者,可以从以下文章开始: 

  1. 在Easy Samples中使用AvaloniaUI进行多平台UI编码——第1部分——AvaloniaUI构建块
  2. 多平台Avalonia .NET Framework简单示例中的XAML基础知识
  3. 简单示例中的多平台Avalonia .NET Framework编程基本概念
  4. 简单示例中的多平台Avalonia .NET Framework编程高级概念

演示代码

演示代码位于NP.Demos.VisualSamples/NP.Demos.AdvancedDataGridDemo项目下的NP.Avalonia.Demos存储库中。

要获取该项目,请克隆存储库NP.Demos.VisualSamples/NP.Demos.AdvancedDataGridDemo文件夹并打开NP.Demos.AdvancedDataGridDemo.sln解决方案(您需要为此使用Visual Studion 2022)。

构建解决方案,确保Visual Studio已下载所有需要的nuget包。

如果您打开解决方案中唯一项目的依赖包区域,您将只看到两个项目:

其余的Avalonia引用都来自NP.Avalonia.Visuals依赖项。

运行解决方案;这是要弹出的窗口:

红色矩形曲线包含文本过滤器。最后一列Cost的过滤器被禁用。

尝试在过滤器的文本框中输入一些string内容。您将看到仅显示包含过滤器文本的行——其余的将变得不可见:

上图是产品名称中包含b” 产品描述中包含nic的记录。

现在右键单击其中一列(例如Manufacturer)并选择Remove Column菜单项:

单击后,列(Manufacturer)将变为不可见:

现在单击顶部的列可见性设置器按钮,在打开的下拉列表中单击制造商列旁边的checkbutton使其再次可见:

现在更改各个列的宽度并通过将某些列拖动到其他位置来更改它们的顺序,例如:

保存网格布局按钮保存网格布局。

重新启动应用程序并按下按钮恢复网格布局。保存的布局将被恢复。

演示代码

在演示中,我们定义了一个简单的类Product

public class Product
{
    public string? Name { get; }

    public string? Description { get; }

    public string? Manufacturer { get; }

    public double? Cost { get; }


    public Product(string? name, string? description, 
                   string? manufacturer, double? cost)
    {
        Name = name;
        Description = description;
        Manufacturer = manufacturer;
        Cost = cost;
    }
}  

和预定义的产品集合——DemoProducts

public class DemoProducts : ObservableCollection<product>
{
    private void AddProduct(string? name, string? description, 
                            string? manufacturer, double? cost)
    {
        this.Add(new Product(name, description, manufacturer, cost));
    }

    public DemoProducts()
    {
        AddProduct("Batmobile", "Nice and comfortable tank that 
                    can jump between rooftops", "Wayne Enterprises", 10000000);
        AddProduct("Instant Tunnel", "Allows a cartoon character to escape", 
                   "ACME Corp", 20000);
        AddProduct("Brains for Scarecrow", "Provides any bright scarecrow 
                    with intellectual confidence", "OZ Production", 50);
        AddProduct("UniDock", "Multiplatform Window Docking Package for Avalonia", 
                   "Nick Polyak Enterprises", 0);
    }
}

其余大部分代码在MainWindow.axaml文件中定义,只有对样式文件的引用在App.axaml中定义。

DemoProducts类型的对象被定义为带有MainWindow.asaml文件的资源:

<Window.Resources>
    <local:DemoProducts x:Key="TheDemoProducts"/>
</Window.Resources>  

MainWindow.axaml文件最重要的部分是DataGrid本身:

<DataGrid x:Name="TheDataGrid"
          Classes="WithColumnFilters"
          CanUserReorderColumns="True"
          CanUserResizeColumns="True"
          HorizontalAlignment="Left"
          Grid.Row="1"
          np:DataGridFilteringBehavior.DataGridFilterTextBoxClasses=
                                                 "DataGridFilterTextBox"
          np:DataGridFilteringBehavior.RowDataType="{x:Type local:Product}"
          np:DataGridCollectionViewBehavior.ItemsSource=
                                            "{StaticResource TheDemoProducts}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Product Name"
                            np:DataGridColumnManipulationBehavior.
                                                CanRemoveColumn="False"
                            np:DataGridFilteringBehavior.FilterPropName="Name"
                            Binding="{Binding Path=Name}"/>
        <DataGridTextColumn Header="Product Description"
                            np:DataGridFilteringBehavior.FilterPropName="Description"
                            Binding="{Binding Path=Description}"/>
        <DataGridTextColumn Header="Manufacturer"
                            np:DataGridFilteringBehavior.FilterPropName="Manufacturer"
                            Binding="{Binding Path=Manufacturer}"/>
        <DataGridTextColumn Header="Cost"
                            Binding="{Binding Path=Cost, 
                                      StringFormat='$\{0:#,##0.00\}'}"/>
    </DataGrid.Columns>
</DataGrid>  

为了显示过滤textbox和列删除菜单,需要在DataGrid类中使用WithColumnFilters类,而来自NP.Avalonia.VisualsThemeStyles.axaml样式文件应该在应用程序中可见(这是通过我们添加行来实现的):

<StyleInclude Source="avares://NP.Avalonia.Visuals/Themes/ThemeStyles.axaml"/> 

App.asaml文件)。

DataGrid标记的行np:DataGridCollectionViewBehavior.ItemsSource="{StaticResource TheDemoProducts}",将网格的集合源(简单来说就是TheDemoProducts资源)更改为一个DataGridCollectionView对象,该对象实际分配给DataGridItems属性。

DataGridCollectionView类本质上是一个集合,其中内置了一些有用的功能,允许过滤、分组和排序。

DataGrid标记的行np:DataGridFilteringBehavior.RowDataType="{x:Type local:Product}",将行类型设置为Product类型的对象。这有助于创建基于属性名称的快速预编译过滤器。

np:DataGridFilteringBehavior.DataGridFilterTextBoxClasses="DataGridFilterTextBox"允许指定类来设置过滤的样式TextBox,例如,可以使用它来更改过滤的TextBox背景、大小、Font等。

现在让我们描述列的特定属性。

Binding属性只允许DataGrid将列值绑定到行的属性——这就是基本的DataGridNP.Avalonia.Visuals没有做任何改进)的工作方式。

如果要显示已启用且正在工作的过滤器TextBox,则需要将附加属性np:DataGridFilteringBehavior.FilterPropName设置为行对象上的某个属性名称,例如:

np:DataGridFilteringBehavior.FilterPropName="Description"

Cost没有指定这样的属性名称,因此,它的过滤TextBox被禁用。

如果您不希望列可移动,则必须将附加属性np:DataGridFilteringBehavior.DataGridFilterTextBoxClasses设置为false,就像在Product Name列上完成的那样:

np:DataGridColumnManipulationBehavior.CanRemoveColumn="False"  

默认情况下,列是可移动的。

删除一列后,我们需要提供一种重新添加它的方法。这就是顶部Column Visibility Setter”按钮的用途。以下是相关代码:

<Button Content="Column Visibility Setter"
        Margin="0,2"
        HorizontalAlignment="Left"
        VerticalAlignment="Center">
    <Button.Flyout>
        <Flyout Placement="Bottom">
            <ContentPresenter Content="{Binding #TheDataGrid.Columns}"
             ContentTemplate="{StaticResource DataGridColumnsVisibilityDataTemplate}"/>
        </Flyout>
    </Button.Flyout>
</Button>  

该按钮只是打开一个Flyout(一种菜单弹出窗口),其中包含一个内容控件,显示每一个DataGrid列的实例。这是由在NP.Avalonia.Visuals项目中的文件之一中定义的名为DataGridColumnsVisibilityDataTemplate的静态资源提供的DataTemplate

最后,看看底部的布局保存/恢复按钮代码:

<StackPanel HorizontalAlignment="Right"
            Orientation="Horizontal"
            Margin="0,10,0,0"
            Grid.Row="2">
    <Button Content="Save Grid Layout"
            np:CallAction.TheEvent="{x:Static Button.ClickEvent}"
            np:CallAction.TargetObject="{Binding #TheDataGrid}"
            np:CallAction.StaticType="{x:Type np:DataGridColumnManipulationBehavior}"
            np:CallAction.MethodName="SaveDataGridLayoutToFile"
            np:CallAction.HasArg="True"
            np:CallAction.Arg1="MyGridLayoutFile.xml"/>
    <Button Content="Restore Grid Layout"
            Margin="10,0,0,0"
            np:CallAction.TheEvent="{x:Static Button.ClickEvent}"
            np:CallAction.TargetObject="{Binding #TheDataGrid}"
            np:CallAction.StaticType="{x:Type np:DataGridColumnManipulationBehavior}"
            np:CallAction.MethodName="RestoreDataGridLayoutFromFile"
            np:CallAction.HasArg="True"
            np:CallAction.Arg1="MyGridLayoutFile.xml"/>
</StackPanel>  

我们使用NP.AvaloniaVisuals包中定义的CallAction行为来调用static方法:

DataGridColumnManipulationBehavior.SaveDataGridLayoutToFile
                                   (dataGrid, "MyGridLayoutFile.xml")

NP.Avalonia Visuals包中定义,以便将DataGrid(我们作为第一个参数传递给静态方法的)的布局保存到我们作为第二个参数传递的文件MyGridLayoutFile.xml中。

恢复布局时,我们采用方法:

DataGridColumnManipulationBehavior.RestoreDataGridLayoutFromFile
                                   (dataGrid, "MyGridLayoutFile.xml")

相反的。

CallActionNP.Avalonia.Visuals包中定义的一个非常重要和有用的行为,此包将在别处详细解释。

实现说明

对于那些对如何创建过滤、列可见性和布局保存恢复功能感到好奇的人,我将在下面提供简要说明。

过滤功能的实现

对于过滤器和列删除功能,我提供了一个带有WithFilter类的DataGrid列标题Style(请参阅项目NP.Avalonia.Visuals中的ThemeStyles.axaml文件)。它更改了在通常标题下插入过滤TextBox的列标题:

<TextBox x:Name="FilterTextBox"
         HorizontalAlignment="Stretch"
         Grid.Row="1"
         Margin="3,1"
         Padding="2,1"
         IsVisible="{Binding $parent[DataGrid].HasFilters}"
         np:ClassesBehavior.TheClasses=
         "{Binding $parent[DataGrid].DataGridFilterTextBoxClasses}"
         IsEnabled="{Binding !!$parent[DataGridColumnHeader].Column.FilterPropName}"
         Text="{Binding $parent[DataGridColumnHeader].ColumnFilterText,
                        Mode=TwoWay}"/>  

然后,我使用各种附加属性和行为来连接过滤行为、定义文本框外观和感觉的类以及TextBox是否可见或启用。

然后,我使用AddClassesToDataGridColumnHeaderBehavior附加行为为DataGrid中的每一列注入WithFilter DataGridColumnHeader类。

更改列可见性

更改列可见性的菜单也内置在DataGridColumnHeader.WithFilter样式中,作为标题主网格的上下文弹出菜单:

<Grid.ContextFlyout>
	<MenuFlyout>
		<MenuItem Header="Remove Column"
					IsEnabled="{Binding $parent[DataGridColumnHeader].
                                Column.CanRemoveColumn}"
					np:CallAction.TheEvent="{x:Static MenuItem.ClickEvent}"
					np:CallAction.StaticType=
                    "{x:Type np:DataGridColumnManipulationBehavior}"
					np:CallAction.TargetObject=
                    "{Binding $parent[DataGridColumnHeader].Column}"
					np:CallAction.MethodName="RemoveColumn">
			<MenuItem.Icon>
				<Path Data="{StaticResource CloseIcon}"
						Stretch="Uniform"
						Fill="Red"
						Width="9"
						VerticalAlignment="Center"
						HorizontalAlignment="Center"/>
			</MenuItem.Icon>
		</MenuItem>
	</MenuFlyout>
</Grid.ContextFlyout>  

CallAction行为被连接到调用static方法:

DataGridColumnManipulationBehavior.RemoveColumn(DataGridColumn column)

单击菜单项时。方法simple将列的IsVisible属性更改为false

用于恢复网格列的弹出窗口由DataGridResources.axaml文件中定义的DataGridColumnsVisibilityDataTemplate DataTemplate提供:

<DataTemplate x:Key="DataGridColumnsVisibilityDataTemplate">
    <ItemsControl Items="{Binding}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <np:NpToggleButton IsChecked="{Binding Path=IsVisible, Mode=TwoWay}"
                                       HorizontalAlignment="Center"
                                       VerticalAlignment="Center"
                                       Margin="3"
                                       IsEnabled="{Binding CanRemoveColumn}"/>
                    <TextBlock Text="{Binding Header}"
                               HorizontalAlignment="Center"
                               VerticalAlignment="Center"
                               Margin="5,0,0,0"/>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</DataTemplate>  

对于每一列,它显示一个CheckBox,后跟列的名称,允许用户切换checkbox,使列可见或不可见。

保存/恢复DataGrid布局

用于保存和恢复数据网格布局的SaveDataGridLayoutToFile(...)RestoreDataGridLayoutFromFile(...)方法分别定义在static DataGridColumnManipulationBehavior static类中。

这是保存方法的实现:

public static void SaveDataGridLayoutToFile(this DataGrid dataGrid, string fileName)
{
    var colSerializationData =
        dataGrid
            .Columns
                .OrderBy(col => col.DisplayIndex)
                .Select
                (col => new ColumnSerializationData
                        {
                            IsVisible = col.IsVisible,
                            WidthStr = TheDataGridLengthConverter.ConvertToString
                                       (col.Width),
                            HeaderId = col.Header?.ToStr()
                        }).ToArray();

    XmlSerializationUtils.SerializeToFile(colSerializationData, fileName);
}  

我们将列集合转换为ColumnSerializableData对象集合,然后使用XmlSerializationUtils.SerializeToFile(...)方法将其保存到文件中。

在恢复方法中——我们做相反的事情——我们从文件中恢复ColumnSerializableData对象的集合,然后将它们的值应用于当前网格:

public static void RestoreDataGridLayoutFromFile(this DataGrid dataGrid, string fileName)
{
    ColumnSerializationData[] colSerializationData =
        XmlSerializationUtils.DeserializeFromFile<ColumnSerializationData[]>(fileName);

    colSerializationData
        .DoForEach
        (
            (col, idx) =>
            {
                DataGridColumn gridCol =
                    dataGrid.Columns.Single(dataGridCol => 
                             dataGridCol.Header?.ToString() == col.HeaderId);

                gridCol.IsVisible = col.IsVisible;
                gridCol.DisplayIndex = idx;
                gridCol.Width = (DataGridLength)TheDataGridLengthConverter.
                                 ConvertFromString(col.WidthStr);
            });
}  

这是ColumnSerializableData类的实现:

public class ColumnSerializationData
{
    [XmlAttribute]
    public string? HeaderId { get; set; }

    [XmlAttribute]
    public bool IsVisible { get; set; }

    [XmlAttribute]
    public string? WidthStr { get; set; }
}  

结论

在本文中,我解释了如何将重要的缺失功能添加到Avalonia DataGrid,包括过滤、布局保存/恢复和控制列可见性。这些额外的功能来自我的开源NP.Avalonia.Visuals库是免费的。

我计划写更多关于这个库的内容,包括详细描述它最重要的附加行为。

在不久的将来,我还计划在Avalonia DataGrid中添加分组。

https://www.codeproject.com/Articles/5329865/Avalonia-DataGrid-Advanced-Features-coming-from-NP

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值