目录
介绍
Avalonia是一个很棒的新的多平台.NET框架,用于构建UI桌面应用程序。它与WPF非常相似,更强大且错误更少,并且不仅可以在Windows上运行,还可以在Linux和Mac上运行。可以称之为多平台WPF++。最近,可以在浏览器中运行Avalonia并且我知道Avalonia的移动版本即将推出。
Avalonia直到最近才准备好生产,因此,主要的第三方组件提供商,如Telerik、DevExpress或Infragistics仍然没有发布Avalonia的组件。
根据我的经验,像Telerik这样的大型供应商只需要两个组件——一个窗口停靠功能和一个数据网格。其他一切——自定义按钮、框等都可以(并且应该)由团队使用WPF或Avalonia原语轻松构建,以满足用户体验要求。
我的UniDock包弥补了Avalonia中对接框架的不足。
DataGrid已经存在于Avalonia。很大程度上,它是内置WPF DataGrid的一个端口。不幸的是,就像它的WPF对应物一样,Avalonia DataGrid缺少一些重要功能,包括:
- 过滤
- 更改网格列的可见性
- 保存/恢复布局
- 分组
DataGrid除了分组(在我的列表中并且应该很快添加)之外,我已将上述所有功能添加到内置的Avaloni中。所有这些都添加到我的NP.Avalonia.Visuals库/包中。
本文提供了一个示例,演示如何使用这些高级功能。
如果您是初学者,可以从以下文章开始:
- 在Easy Samples中使用AvaloniaUI进行多平台UI编码——第1部分——AvaloniaUI构建块
- 多平台Avalonia .NET Framework简单示例中的XAML基础知识
- 简单示例中的多平台Avalonia .NET Framework编程基本概念
- 简单示例中的多平台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.Visuals的“ThemeStyles.axaml”样式文件应该在应用程序中可见(这是通过我们添加行来实现的):
<StyleInclude Source="avares://NP.Avalonia.Visuals/Themes/ThemeStyles.axaml"/>
到App.asaml文件)。
DataGrid标记的行np:DataGridCollectionViewBehavior.ItemsSource="{StaticResource TheDemoProducts}",将网格的集合源(简单来说就是TheDemoProducts资源)更改为一个DataGridCollectionView对象,该对象实际分配给DataGrid的Items属性。
DataGridCollectionView类本质上是一个集合,其中内置了一些有用的功能,允许过滤、分组和排序。
DataGrid标记的行np:DataGridFilteringBehavior.RowDataType="{x:Type local:Product}",将行类型设置为Product类型的对象。这有助于创建基于属性名称的快速预编译过滤器。
行np:DataGridFilteringBehavior.DataGridFilterTextBoxClasses="DataGridFilterTextBox"允许指定类来设置过滤的样式TextBox,例如,可以使用它来更改过滤的TextBox背景、大小、Font等。
现在让我们描述列的特定属性。
Binding属性只允许DataGrid将列值绑定到行的属性——这就是基本的DataGrid(NP.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")
相反的。
CallAction是NP.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