WPF ListView 中的自动生成列
一个 WPF ListView,
引言
这是我系列文章中的又一篇,直接源于实际编码,并展示了标准 WPF ListView
的一个有用变体。我不会在这里探讨理论或最佳实践,而是分享我为实现特定目的而编写的代码的最终结果,这些代码足够通用,可以被其他人使用。所以,请坐稳,享受这段旅程。
我组织了这篇文章,以适应注意力广度递减的读者。首先是必要的介绍文本,然后是代码使用方法,最后是对代码的相当详细的讨论。这应该能满足几乎所有人,所以不会容忍任何抱怨。
本文附带的下载内容同时提供了 .Net Framework 和 .Net 5.0 (Core) 项目集。它们有清晰的标记,并且运行方式相同。由于解决方案包含两个 .Net 5 项目,因此您必须使用 Visual Studio 2019 才能编译它,或者将这两个 .Net 5 项目转换为使用 .Net Core 3.1。

我们为什么在这里
在我工作的地方,我最近不得不开发一个工具,用于测试在涉及超过五十万成员的数据库刷新过程中执行的计算。这个工具将包含大约十几个不同的窗口,每个窗口都将包含一个 ListView
,并且每个 ListView
都将绑定到结构迥异的项目集合。正如您可能猜到的,每个实体集合通常都需要为每个 ListView
进行广泛的列定义工作,而我真的不想处理这些。理想的解决方案是创建一个 ListView
,该 ListView
可以根据实体的内容自动生成其列。我一点也不惊讶地发现,标准的 WPF ListView
默认不提供这种功能。这意味着我必须“自己动手”,结果证明这并非一项过于艰巨的任务。我还因此添加了一些自定义功能,例如可选的属性和资源。
用法
以下是实现 AutomaticListView
的最小步骤。下方提供的示例代码仅用于说明需要执行的操作,并且是示例代码的简化版本。
Window
/UserControl
XAML 文件中添加命名空间条目<Window x:Class="AuoGenListView_Example.MainWindow"
...
xmlns:aglv="clr-namespace:AutoGenListView;assembly=AutoGenListView"
...>
1)将 AutomaticListView
控件添加到您的 Window
(或 UserControl
)中<Grid >
<aglv:AutomaticListView x:Name="lvData" SelectionMode="Single"
ItemsSource="{Binding Path=SampleData}" />
</Grid>
2)创建您的 ViewModel 集合实体public class VMSampleItem : Notifiable
{
[ColVisible]
public int Identity { get; set; }
[ColVisible]
public int Ident2 { get; set; }
[ColVisible]
public string Name { get; set; }
[ColVisible]
public string Address { get; set; }
[ColVisible]
public string Info { get; set; }
// This property won't be displayed because it's not decorated
// with the ColVisible attribute
public string MoreInfo { get; set; }
}
public class VMSampleItemList : ObservableCollection<VMSampleItem>
{
public VMSampleItemList()
{
this.Add(new VMSampleItem() {...} );
this.Add(new VMSampleItem() {...} );
this.Add(new VMSampleItem() {...} );
this.Add(new VMSampleItem() {...} );
}
}
基本上就是这样。您的 ListView
将显示所有带有 ColVisible
属性的列。列宽是根据属性名称(也用作标题文本)的测量宽度自动计算的。虽然这很有趣,但我们还有很多事情可以做。
其他可选的视觉样式
我更喜欢看到我的列表带有灰色条纹外观,即每隔一行有一个稍深一点的背景颜色(在本篇文章顶部的截图中有说明)。为了实现这一点,您可以将以下 XAML 添加到您的 App.xaml 文件或适当的资源字典中。
<!-- this style allows us to see gray-bar rows (alternating white/gray rows
in the <code>ListView</code>), as well as make background changes for specific columns use
the entire width of the column. -->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<!-- the three following properties reduce the gap between rows-->
<Setter Property="Margin" Value="0,-1,0,0" />
<Setter Property="Padding" Value="0" />
<Setter Property="BorderThickness" Value="0" />
<!-- triggers for gray bar, selected, and mouse hover-->
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="White" />
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="#eeeeee" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="LightSkyBlue" />
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightBlue" />
</Trigger>
</Style.Triggers>
</Style>
要启用灰色条纹着色,您还需要更改 Window
/UserControl
中 ListView
的 XAML(这不限于 AutomaticListView
,在您应用中的任何 ListView
中都可以工作)。
<aglv:AutomaticListView x:Name="lvData" SelectionMode="Single" Margin="0,3,0,0"
AlternationCount="2"
ItemsSource="{Binding Path=SampleData}"/>
ColWidth 属性
虽然自动计算列宽很方便,但在现实世界中用处不大,因为列中的数据通常比列标题文本(宽得多)。如果您有这样的数据,可以使用 ColWidth
属性来指定所需的列宽。列宽仍然根据测量到的标题文本宽度进行计算,但实际使用的宽度将是计算宽度和指定宽度中的较大者。如下所示,示例应用程序中的几列都指定了列宽。
public class VMSampleItem : Notifiable
{
[ColVisible]
public int Identity { get; set; }
[ColVisible]
public int Ident2 { get; set; }
[ColVisible][ColWidth(200)]
public string Name { get; set; }
[ColVisible][ColWidth(200)]
public string Address { get; set; }
[ColVisible][ColWidth(325)]
public string Info { get; set; }
public string MoreInfo { get; set; }
}
ColSort 属性
默认情况下,所有列都可以排序,但有时您可能不希望/不需要一个或多个列可排序。如果您有符合此描述的列,可以使用 ColSort
属性来阻止排序
public class VMSampleItem : Notifiable
{
[ColVisible]
public int Identity { get; set; }
[ColVisible]
public int Ident2 { get; set; }
[ColVisible][ColWidth(200)]
public string Name { get; set; }
[ColVisible][ColWidth(200)]
public string Address { get; set; }
[ColVisible][ColWidth(325)][ColSort(false)]
public string Info { get; set; }
public string MoreInfo { get; set; }
}
当然,设置 ColSort(true)
将允许列排序,但由于排序会自动为每列启用,因此这是不必要的,仅出于完整性的考虑而包含。
要实际启用排序,您必须为单击列标题添加一个事件处理程序。在 XAML 中,它看起来是这样的
<aglv:AutomaticListView x:Name="lvData" SelectionMode="Single" Margin="0,3,0,0"
AlternationCount="2"
ItemsSource="{Binding Path=SampleData}"
GridViewColumnHeader.Click="GridViewColumnHeader_Click" />
在代码隐藏中,关联的代码如下所示
private void GridViewColumnHeader_Click(object sender, RoutedEventArgs e)
{
AutomaticListView lvData = sender as AutomaticListView;
lvData.ProcessSortRequest(e);
}
我之所以倾向于在父容器中处理事件,是因为有时我想在排序发生之前或之后进行一些额外的处理。
ColCellTemplate 属性
有时,您可能希望根据给定的一组要求对特定列进行样式设置。在我这里,我想让某一列使用非比例字体,因为数据总是由六个字母数字字符组成,用比例字体显示会使眼睛从数据上移开,实际上更难扫描该列。我最初的想法是简单地允许更改字体系列和大小,但我想到了“既然我在做这件事”,我可以毫不费力地添加一些更合理的样式功能
public class VMSampleItem : Notifiable
{
[ColVisible]
public int Identity { get; set; }
[ColVisible][DisplayName("2nd Identity")]
public int Ident2 { get; set; }
[ColVisible][ColWidth(200)]
public string Name { get; set; }
[ColVisible][ColWidth(200)]
public string Address { get; set; }
[ColVisible][ColWidth(325)][ColSort(false)]
[ColCellTemplate("Consolas", "18", "Italic", "Black", "Red", "Yellow", "Right", null)]
public string Info { get; set; }
public string MoreInfo { get; set; }
}
您可以指定字体系列、大小、样式和粗细,以及前景色和背景色,以及水平和垂直对齐方式。
另一个样式选项 - 排序箭头颜色
这个选项非常可选,几乎有点好笑。我希望排序箭头不仅仅是普通的黑色,所以我想出了一种轻松改变单个向上/向下箭头颜色的方法。您只需要将以下资源添加到您的 App.xaml 或适当的资源字典中。如果代码找不到它们,箭头将是黑色的(请注意键名 - 这些是代码查找的资源名称 - 并且它们是区分大小写的
<!-- Brush defintions for the sort adorner arrows - if you don't specify
these, the arrows default to Black. -->
<SolidColorBrush x:Key="SortArrowUpColor" Color="Red" />
<SolidColorBrush x:Key="SortArrowDownColor" Color="Blue" />
代码
正如您可能猜到的,最有趣的代码位于 DLL 中,其中实现了 AutromaticListView
、排序修饰符和属性。
属性
以下属性通常不那么重要,只是为了完整性而包含。
//=================================================
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
public class ColSortAttribute : Attribute
{
public bool Sort { get; set; }
public ColSortAttribute()
{
Sort = true;
}
public ColSortAttribute(bool sort)
{
Sort = sort;
}
}
//=================================================
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
public class ColVisibleAttribute : Attribute
{
public bool Visible { get; set; }
public ColVisibleAttribute()
{
this.Visible = true;
}
public ColVisibleAttribute(bool visible)
{
this.Visible = visible;
}
}
//=================================================
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
public class ColWidthAttribute : Attribute
{
public double Width { get; set; }
public ColWidthAttribute()
{
this.Width = 150;
}
public ColWidthAttribute(double width)
{
this.Width = Math.Max(0, width);
}
}
AutomaticListView
还识别 DataAnnotations.DisplayName
属性,因此您可以更改显示列的标题文本。我曾经尝试编写自己的属性类,以便 a) 所有属性类的名称都遵循相同的命名约定,以及 b) 我不必引用另一个 .Net 程序集,但我变得懒惰和厌倦,就这样吧。
重头戏是 ColCellTemplateAttribute 类。由于其属性的数量和类型,更重要的是,因为一些程序员在编写代码时根本不使用常识,我觉得有必要进行验证,以防止出现问题。
首先,我想定义一些枚举,以便更容易地编写验证代码
private enum FieldID { Name, Size, Style, Weight, Foreground,
Background, HorzAlign, VertAlign}
private enum AttribHorzAlignments { Center, Left, Right, Stretch }
private enum AttribVertAlignments { Bottom, Center, Stretch, Top }
private enum AttribFontWeights { Black, Bold, DemiBold,
ExtraBlack, ExtraBold, ExtraLight,
Heavy, Light, Medium, Normal,
Regular, SemiBold, Thin,
UltraBlack, UltraBold, UltraLight }
private enum AttribFontStyles { Italic, Normal, Oblique }
接下来,我想创建一个已安装字体集合。它是一个静态字段,因为我不想为该属性的每个实例分配空间。
private static List<string> installedFonts = null;
为了使验证代码更轻松(打字更少),我创建了一个字符串常量,用于在使用 string.Format()
调用时作为基础
private const string _DEFAULT_FORMAT_ = "{{Binding RelativeSource={{RelativeSource FindAncestor,AncestorType=ListViewItem}}, Path={0}}}";
然后是属性。我将所有内容定义为字符串,以便可以在 AutomaticListView
类中轻松使用它们。
public string FontName { get; set; }
public string FontSize { get; set; }
public string FontStyle { get; set; }
public string FontWeight { get; set; }
public string Foreground { get; set; }
public string Background { get; set; }
public string HorzAlign { get; set; }
public string VertAlign { get; set; }
所有其他属性都提供一个不接受任何参数的默认构造函数。这允许您使用匿名属性来设置值。您也可以使用接受要设置的值的标准化构造函数来设置属性。对于 ColCellTemplate
属性,这并不真正实用,因为我想确保程序员不会做任何愚蠢的事情。
public ColCellTemplateAttribute(string name, string size, string style, string weight,
string fore, string back, string horz, string vert)
{
this.FontName = (!string.IsNullOrEmpty(name)) ? this.Validate(FieldID.Name, name )
: string.Format(_DEFAULT_FORMAT_, "FontFamily");
this.FontSize = (!string.IsNullOrEmpty(size)) ? this.Validate(FieldID.Size, size )
: string.Format(_DEFAULT_FORMAT_, "FontSize");
this.FontStyle = (!string.IsNullOrEmpty(style)) ? this.Validate(FieldID.Style, style )
: string.Format(_DEFAULT_FORMAT_, "FontStyle");
this.FontWeight = (!string.IsNullOrEmpty(weight)) ? this.Validate(FieldID.Weight, weight)
: string.Format(_DEFAULT_FORMAT_, "FontWeight");
this.Foreground = (!string.IsNullOrEmpty(fore)) ? this.Validate(FieldID.Foreground, fore )
: string.Format(_DEFAULT_FORMAT_, "Foreground");
this.Background = (!string.IsNullOrEmpty(back)) ? this.Validate(FieldID.Background, back )
: string.Format(_DEFAULT_FORMAT_, "Background");
this.HorzAlign = (!string.IsNullOrEmpty(horz)) ? this.Validate(FieldID.HorzAlign, horz )
: "Left";
this.VertAlign = (!string.IsNullOrEmpty(vert)) ? this.Validate(FieldID.VertAlign, vert )
: "Center";
}
验证方法使用 case 语句来决定它正在验证什么。本质上,该方法试图理解程序员指定的意图,如果出现问题,则使用默认设置。这意味着不会有中断 ListView
使用的明显失败迹象,除了指定列的外观可能与您预期的不符。
private string Validate (FieldID id, string value)
{
string result = value;
switch (id)
{
case FieldID.Background :
{
try
{
Color color = (Color)(ColorConverter.ConvertFromString(value));
result = value;
}
catch(Exception)
{
result = string.Format(_DEFAULT_FORMAT_, "Background");
}
}
break;
case FieldID.Foreground :
{
try
{
Color color = (Color)(System.Windows.Media.ColorConverter.ConvertFromString(value));
result = value;
}
catch(Exception)
{
result = string.Format(_DEFAULT_FORMAT_, "Foreground");
}
}
break;
case FieldID.HorzAlign :
{
AttribHorzAlignments align;
if (!Enum.TryParse(value, out align))
{
result = AttribHorzAlignments.Left.ToString();
}
}
break;
case FieldID.Name :
{
if (installedFonts == null)
{
using (InstalledFontCollection fontsCollection = new InstalledFontCollection())
{
installedFonts = (from x in fontsCollection.Families select x.Name).ToList();
}
}
if (!installedFonts.Contains(value))
{
result = string.Format(_DEFAULT_FORMAT_, "FontFamily");
}
}
break;
case FieldID.Size :
{
double dbl;
if (!double.TryParse(value, out dbl))
{
result = string.Format(_DEFAULT_FORMAT_, "FontSize");
}
}
break;
case FieldID.Style :
{
AttribFontWeights enumVal;
if (!Enum.TryParse(value, out enumVal))
{
result = string.Format(_DEFAULT_FORMAT_, "FontStyle");
}
}
break;
case FieldID.VertAlign :
{
AttribVertAlignments align;
if (!Enum.TryParse(value, out align))
{
result = AttribVertAlignments.Center.ToString();
}
}
break;
case FieldID.Weight :
{
AttribFontWeights weight;
if (!Enum.TryParse(value, out weight))
{
result = string.Format(_DEFAULT_FORMAT_, "FontWeight");
}
}
break;
}
return result;
}
排序修饰符
SortAdorner 类负责绘制适当的箭头以指示排序方向。它唯一可配置的部分是箭头的颜色。默认情况下,向上和向下箭头都是黑色的,但如果找到了预期的画笔资源,颜色将反映在发现的资源中设置的颜色。属性是静态的,所以我们不必为类的每个实例设置它们,并且我们在箭头实际渲染时查找资源。再次,请注意资源的(区分大小写的)名称。
public class SortAdorner : Adorner
{
private static SolidColorBrush UpArrowColor { get; set; }
private static SolidColorBrush DownArrowColor { get; set; }
...
protected override void OnRender(DrawingContext drawingContext)
{
// see if we have specially defined arrow colors
if (UpArrowColor == null)
{
// Use TryFindResource instead of FindResource to avoid exception being thrown if the
// resources weren't found
SolidColorBrush up = (SolidColorBrush)TryFindResource("SortArrowUpColor");
UpArrowColor = (up != null) ? up : Brushes.Black;
SolidColorBrush down = (SolidColorBrush)TryFindResource("SortArrowDownColor");
DownArrowColor = (down != null) ? down : Brushes.Black;
}
...
}
}
AutomaticListView - 渲染列
此类负责渲染可见列并提供排序功能。渲染列涉及 RenderColumns 方法以及一些辅助方法。我决定不提供代码和旁白,而是提供带有注释的代码。
// this method renders the columns
protected void CreateColumns(AutomaticListView lv)
{
// get the collection item type
Type dataType = lv.ItemsSource.GetType().GetMethod("get_Item").ReturnType;
// create the gridview in which we will populate the columns
GridView gridView = new GridView()
{
AllowsColumnReorder = true
};
// get all of the properties that are decorated with the ColVisible attribute
PropertyInfo[] properties = dataType.GetProperties()
.Where(x=>x.GetCustomAttributes(true)
.FirstOrDefault(y=>y is ColVisibleAttribute) != null)
.ToArray();
// For each appropriately decorated property in the item "type", make a column
// and bind the property to it
foreach(PropertyInfo info in properties)
{
// If the property is being renamed with the DisplayName attribute,
// use the new name. Otherwise use the property's actual name
DisplayNameAttribute dna = (DisplayNameAttribute)(info.GetCustomAttributes(true).FirstOrDefault(x=>x is DisplayNameAttribute));
string displayName = (dna == null) ? info.Name : dna.DisplayName;
// Build the cell template if necessary
DataTemplate cellTemplate = this.BuildCellTemplateFromAttribute(info);
// determine the column width
double width = this.GetWidthFromAttribute(info, displayName);
// if the cellTemplate is null, create a typical binding object for display
// member binding
Binding binding = (cellTemplate != null) ? null : new Binding() { Path = new PropertyPath(info.Name), Mode = BindingMode.OneWay };
// Create the column, and add it to the gridview. In WPF, you can only specify
// binding in one of two places, either the DisplayMemberBinding, or the
// CellTemplate. Whichever is used, the other must be null. By the time we get
// here, that decision tree has already been processed, and just ONE of the
// two binding methods will not be null.
GridViewColumn column = new GridViewColumn()
{
Header = displayName,
DisplayMemberBinding = binding,
CellTemplate = cellTemplate,
Width = width,
};
gridView.Columns.Add(column);
}
// set the list view's gridview
lv.View = gridView;
}
辅助方法(创建目的是为了让主方法中的代码量保持在一个合理的范围内)
/// <summary>
/// Determine the width of the column, using the largest of either the calculated width,
/// or the decorated width (using ColWidth attribute).
/// </summary>
private double GetWidthFromAttribute(PropertyInfo property, string displayName)
{
// Get the decorated width (if specified)
ColWidthAttribute widthAttrib = (ColWidthAttribute)(property.GetCustomAttributes(true).FirstOrDefault(x=>x is ColWidthAttribute));
double width = (widthAttrib != null) ? widthAttrib.Width : 0d;
// calc the actual width, and use the larger of the decorated/calculated widths
width = Math.Max(this.CalcTextWidth(this, displayName, this.FontFamily, this.FontSize)+35, width);
return width;
}
// This string represents the actual template xaml that will be populated with
// properties from the specified ColCellTemplates attribute.
private readonly string _CELL_TEMPLATE_ = string.Concat( "<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" ",
"xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" ",
"x:Key=\"nonPropTemplate\">",
"<Grid>",
"<Rectangle Fill=\"{6}\" Opacity=\"0.6\"/>",
"<TextBlock ",
"FontFamily=\"{1}\" ",
"FontSize=\"{2}\" ",
"FontWeight=\"{3}\" ",
"FontStyle=\"{4}\" ",
"Foreground=\"{5}\" ",
"HorizontalAlignment=\"{7}\" ",
"VerticalAlignment=\"{8}\" ",
">",
"<TextBlock.Text><Binding Path=\"{0}\" Mode=\"OneWay\" /></TextBlock.Text> ",
"</TextBlock>",
"</Grid>",
"</DataTemplate>");
/// <summary>
/// Build the CellTemplate based on the specified ColCelltemplate attribute.
private DataTemplate BuildCellTemplateFromAttribute(PropertyInfo property)
{
// the attriubte is validated when it's defined, so we don't have to worry about it
// by the time we get here. Or do we?
DataTemplate cellTemplate = null;
ColCellTemplateAttribute cell = (ColCellTemplateAttribute)(property.GetCustomAttributes(true).FirstOrDefault(x=>x is ColCellTemplateAttribute));
if (cell != null)
{
string xaml = string.Format(_CELL_TEMPLATE_, property.Name,
cell.FontName, cell.FontSize, cell.FontWeight, cell.FontStyle,
cell.Foreground, cell.Background,
cell.HorzAlign, cell.VertAlign);
cellTemplate = (DataTemplate)this.XamlReaderLoad(xaml);
}
return cellTemplate;
}
/// <summary>
/// Calculates the width of the specified text base on the framework element and the
/// specified font family/size.
/// </summary>
private double CalcTextWidth(FrameworkElement fe, string text, FontFamily family, double fontSize)
{
FormattedText formattedText = new FormattedText(text,
CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(family.Source),
fontSize,
Brushes.Black,
VisualTreeHelper.GetDpi(fe).PixelsPerDip);
return formattedText.WidthIncludingTrailingWhitespace;
}
/// <summary>
/// Loads the specified XAML string
/// </summary>
/// <param name="xaml"></param>
/// <returns></returns>
private object XamlReaderLoad(string xaml)
{
var xamlObj = XamlReader.Load(new MemoryStream(Encoding.ASCII.GetBytes(xaml)));
return xamlObj;
}
AutomaticListView - 排序列
排序操作完全包含在 AutomaticListView
类中。排序时需要考虑两个主要问题,这些问题在 DetermineSortCriteria
方法中进行处理。同样,我将提供带注释的源代码,而不是带有单独旁白的源代码。
/// <summary>
/// Determines if the "sortBy" property name is renamed, and if so, gets the actual
/// property name that was decorated with the DisplayName attribute. Called by the
/// ListViewSortColumn() method in this class.
/// </summary>
/// <param name="lv"></param>
/// <param name="sortBy">The displayed header column text</param>
/// <param name="canSort">Whether or not the column can be sorted</param>
/// <returns></returns>
private void DetermineSortCriteria(ref string sortBy, ref bool canSort)
{
// Determine the item type represented by the collection
Type collectionType = this.GetBoundCollectionItemType();
Type[] genericArgs = collectionType.GetGenericArguments();
if (genericArgs != null && genericArgs.Count() > 0)
{
// this is the type of item in the collection
Type itemType = genericArgs.First();
// find the specified property name
PropertyInfo property = itemType.GetProperty(sortBy);
// if the property wasn't found, it was probably renamed
if (property == null)
{
// get all of the properties that are visible
PropertyInfo[] properties = itemType.GetProperties()
.Where(x=>x.GetCustomAttributes(true)
.FirstOrDefault(y=>y is ColVisibleAttribute) != null)
.ToArray();
foreach(PropertyInfo prop in properties)
{
var dna = (DisplayNameAttribute)(prop.GetCustomAttributes(true).FirstOrDefault(x=>x is DisplayNameAttribute));
// if the column is renamed
if (dna != null)
{
// change the sortby value
sortBy = (dna.DisplayName == sortBy) ? prop.Name : sortBy;
// and set the property
property = itemType.GetProperty(sortBy);
}
}
}
if (property != null)
{
var csa = (ColSortAttribute)(property.GetCustomAttributes(true).FirstOrDefault(x=>x is ColSortAttribute));
canSort = (csa == null || csa.Sort);
}
else
{
// looks like we can't sort
canSort = false;
}
}
}
关注点
对于 .Net Core 版本,我不得不添加一个 nuget 包 - Microsoft.Windows.Compatibility - 才能访问系统上安装的字体。当我编译代码时,我得到了 642 个文件,52 个文件夹,占用了 170MB 的磁盘空间。这太荒谬了。没有办法选择要包含在编译后的应用程序中的程序集 - 您只会得到全部。
因此,我卸载了该包,并安装了 System.Drawing.Common,所有东西都可以正常编译,而且不会带来一堆文件。这仍然使 DLL 成为 Windows 特定,但至少您不会免费获得微软提供的巨大占用空间。
如果您打算重新编译以用于跨平台使用,则必须放弃 ColCellTemplateAttribute
类中的字体系列验证,但这很容易做到。只需在 Visual Studio 中打开解决方案,编辑 AutoGenListView.NetCore 项目的属性,删除 __WINDOWS_TARGET__
编译器定义(在“生成”选项卡上),然后重新生成解决方案。如果您想查看执行此操作时所做的更改,请查看 AutoGenListView.NetCore/Attributes/ColCellTemplateAttribute.cs 文件。
结语
总的来说,这对我来说是一个相当有用的副项目。我不得不反复回到实体并重新排列东西,调整列大小,或者在列表视图中添加/删除列。我真的不太喜欢在 XAML 中乱搞,并且会付出近乎英雄般的努力来避免这样做。在编写这段代码的过程中,我再次体会到了 WPF 的奇特之处,以及它在绑定和资源方面的严格性。当然,缺点是,以我这个年纪,我可能不得不在下一个项目中重新学习这些破烂。
历史
- 2021.02.19 - 修复了一些拼写和标记上的小问题。
- 2021.02.18 - 初次发布。这篇文章可能充斥着拼写错误,但时间已经很晚了,如果需要,我以后会编辑。