有没有办法在 WPF 中求和或平均属性?
Posted
技术标签:
【中文标题】有没有办法在 WPF 中求和或平均属性?【英文标题】:Is there a way to sum or average properties in WPF? 【发布时间】:2021-10-17 05:53:37 【问题描述】:我有一个数据网格控件,我用它来显示很多值,例如价格、金额、每日变化百分比等。因此每个单元格都显示适当的值。
这些都是基础数据的绑定属性。
但有些列标题我想显示所有这些属性的总和或平均值。
例如对于价格、平均(价格)等。
现在我必须创建一个单独的类来为整个集合执行此操作并使用它们绑定到列标题,但它确实创建了更多代码和额外的层来维护。
这是否可以更优雅地完成,最好是在 XAML 中?所以我不必为聚合数据跟踪和引发更改的事件。
这是一列的代码:
<DataGridTemplateColumn Width="50" SortMemberPath="PriceChangeMonthly.InPercent" local:AttachedClass.ColName="PriceChangeMonthly">
<DataGridTemplateColumn.Header>
<TextBlock Text="Binding Path=(local:MarketData.PriceChangeMonthlyDisplay)"/>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock x:Name="ValueAvailable" Text="Binding PriceChangeMonthly.Display" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Ellipse Width="6" Height="6" x:Name="ValueNotAvailable" Visibility="Hidden" Fill="#5a5a5a"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="Binding PriceChangeMonthly.Display" Value="-">
<Setter TargetName="ValueAvailable" Property="Visibility" Value="Hidden"/>
<Setter TargetName="ValueNotAvailable" Property="Visibility" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
PriceChangeMonthly.Display 跟踪每件商品的价格变化。
MarketData.PriceChangeMonthlyDisplay 对整个集合的这些值求和然后取平均值。
public static decimal PriceChangeMonthly
get return MarketData.CopiedCoins?.Where ( c => c.HasBalance ).Select ( c => c.PriceChangeMonthly.InPercent ).DefaultIfEmpty ( ).Average ( ) ?? 0;
public static string PriceChangeMonthlyDisplay
get
decimal change = MarketData.PriceChangeMonthly;
return String.Format ( "01:n1%m", ( change >= 0 ) ? "+" : String.Empty, change );
【问题讨论】:
我不太明白你的解释。您能否举一个数据计算的示例和/或输出 DataGrid 表的屏幕截图。首先,我感兴趣的是计算是只依赖于这个集合元素的属性,还是对其他元素有依赖。 是的,它看起来像这样:imgur.com/a/KHgHlYW 你可以使用所有这些值,我只是将它们平均并在列标题中显示该平均值。它们不依赖于其他任何东西。 如果我理解正确,那么您需要在其标题中获取列元素的平均值吗?如果是这样,那么决定关键取决于是否需要在指定集合时一次性计算这个值,或者这个集合是否可以动态变化并且需要实时计算平均值。请说明这个细微差别。 是的,基本上就是你所说的,它必须随着值的变化而更新,所以标题应该始终显示其列元素的平均值。 【参考方案1】:我给出第二个答案,因为我最初误解了这个问题。
实现了一个代理来监视集合元素的指定属性。 如果它们的值发生变化,或者集合发生变化,则所有新值都将传递给 Values 属性。
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace Converters
[ValueConversion(typeof(object), typeof(object[]))]
public class ToArrayConverter : IValueConverter, IMultiValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> new object[] value ;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
return values?.Clone();
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
throw new NotImplementedException();
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
throw new NotImplementedException();
public static ToArrayConverter Instance get; = new ToArrayConverter();
public class ToArrayConverterExtension : MarkupExtension
public override object ProvideValue(IServiceProvider serviceProvider)
=> ToArrayConverter.Instance;
using Converters;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace Proxy
public class ObservingСollectionItemsProxy : Freezable
/// <summary>
/// Indexed collection. Items in this collection will be observabled.
/// </summary>
public IList List
get return (IList)GetValue(ListProperty);
set SetValue(ListProperty, value);
/// <summary><see cref="DependencyProperty"/> for property <see cref="List"/>.</summary>
public static readonly DependencyProperty ListProperty =
DependencyProperty.Register(nameof(List), typeof(IList), typeof(ObservingСollectionItemsProxy), new PropertyMetadata(null));
/// <summary>
/// The path to the property of the element, the value of which will be passed to the array of values.
/// </summary>
public string ValuePath
get return (string)GetValue(ValuePathProperty);
set SetValue(ValuePathProperty, value);
/// <summary><see cref="DependencyProperty"/> for property <see cref="ValuePath"/>.</summary>
public static readonly DependencyProperty ValuePathProperty =
DependencyProperty.Register(nameof(ValuePath), typeof(string), typeof(ObservingСollectionItemsProxy), new PropertyMetadata(null, PathChanged));
private string privatePath;
private static void PathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
ObservingСollectionItemsProxy proxy = (ObservingСollectionItemsProxy)d;
proxy.privatePath = (string)e.NewValue;
proxy.PathOrCountChanged();
/// <summary>
/// An array of observed values.
/// </summary>
public IReadOnlyList<object> Values
get return (IReadOnlyList<object>)GetValue(ValuesProperty);
private set SetValue(ValuesPropertyKey, value);
private static readonly IReadOnlyList<object> emptyArray = new object[0];
private static readonly DependencyPropertyKey ValuesPropertyKey =
DependencyProperty.RegisterReadOnly(nameof(Values), typeof(IReadOnlyList<object>), typeof(ObservingСollectionItemsProxy), new PropertyMetadata(emptyArray));
/// <summary><see cref="DependencyProperty"/> for property <see cref="Values"/>.</summary>
public static readonly DependencyProperty ValuesProperty = ValuesPropertyKey.DependencyProperty;
/// <summary>Private property for creating binding to collection items.</summary>
private static readonly DependencyProperty PrivateArrayProperty =
DependencyProperty.Register("1", typeof(object[]), typeof(ObservingСollectionItemsProxy), new PropertyMetadata(null, ArrayChanged));
private static void ArrayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
((ObservingСollectionItemsProxy)d).Values = (!(e.NewValue is object[] array) || array.Length == 0)
? emptyArray
: Array.AsReadOnly(array);
/// <summary>A private property to monitor the number of items in the collection.</summary>
private static readonly DependencyProperty PrivateCountProperty =
DependencyProperty.Register("2", typeof(int?), typeof(ObservingСollectionItemsProxy), new PropertyMetadata(null, CountChanged));
private int? privateCount;
private static void CountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
ObservingСollectionItemsProxy proxy = (ObservingСollectionItemsProxy)d;
proxy.privateCount = (int?)e.NewValue;
proxy.PathOrCountChanged();
private void PathOrCountChanged()
MultiBinding multiBinding = new MultiBinding() Converter = ToArrayConverter.Instance ;
if (privateCount != null && privateCount > 0)
string path = string.IsNullOrWhiteSpace(privatePath)
? string.Empty
: $".privatePath.Trim()";
for (int i = 0; i < privateCount.Value; i++)
Binding binding = new Binding($"nameof(List)[i]path")
Source = this
;
multiBinding.Bindings.Add(binding);
BindingOperations.SetBinding(this, PrivateArrayProperty, multiBinding);
public ObservingСollectionItemsProxy()
Binding binding = new Binding($"nameof(List).nameof(List.Count)")
Source = this
;
_ = BindingOperations.SetBinding(this, PrivateCountProperty, binding);
protected override Freezable CreateInstanceCore()
throw new NotImplementedException();
要处理生成的值集合,您可以使用转换器
在处理值的函数的参数中接收委托的转换器示例。
对于此任务,为了让转换器处理值,委托参数必须与 IReadOnlyList<object>
兼容。
也就是说,可以有以下类型中的一种:IReadOnlyCollection<object>
、IEnumerable<object>
、IEnumerable
。
using System;
using System.Globalization;
using System.Reflection;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace Converters
public class FuncForValueConverter : IValueConverter
public static FuncForValueConverter Instance get; = new FuncForValueConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
if (parameter is Delegate func)
MethodInfo method = func.Method;
Type retType = method.ReturnType;
if (retType != null && retType != typeof(void))
ParameterInfo[] parameters = method.GetParameters();
if (parameters.Length == 1)
try
return func.DynamicInvoke(new object[] value );
catch (Exception)
return DependencyProperty.UnsetValue;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
throw new NotImplementedException();
public class FuncForValueConverterExtension : MarkupExtension
public override object ProvideValue(IServiceProvider serviceProvider)
=> FuncForValueConverter.Instance;
用法示例。
Helper 类:集合项类型、集合类型、函数集。
using Simplified;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace ItemsProxyTest
public class DoubleItem : BaseInpc
private double _number;
public double Number get => _number; set => Set(ref _number, value);
public static class Function
public static readonly Func<IReadOnlyList<object>, double> Average
= (arr) => arr
.OfType<double>()
.Average();
public static readonly Func<IReadOnlyList<object>, double> Sum
= (arr) => arr
.OfType<double>()
.Sum();
public class DoubleItemCollection : ObservableCollection<DoubleItem>
XAML:
<Window x:Class="ItemsProxyTest.ItemsProxyTestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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"
xmlns:local="clr-namespace:ItemsProxyTest"
xmlns:proxy="clr-namespace:Proxy;assembly=Common"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:cnv="clr-namespace:Converters;assembly=Common"
mc:Ignorable="d"
Title="ItemsProxyTestWindow" Height="450" Width="800">
<FrameworkElement.Resources>
<local:DoubleItemCollection x:Key="list">
<local:DoubleItem Number="123"/>
<local:DoubleItem Number="456"/>
<local:DoubleItem Number="789"/>
</local:DoubleItemCollection>
</FrameworkElement.Resources>
<Grid>
<DataGrid ItemsSource="DynamicResource list"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="Binding Number">
<DataGridColumn.HeaderTemplate>
<DataTemplate>
<StackPanel>
<FrameworkElement.Resources>
<proxy:ObservingСollectionItemsProxy
x:Key="proxy"
List="DynamicResource list"
ValuePath="Number"/>
<sys:String x:Key="str">121321</sys:String>
</FrameworkElement.Resources>
<TextBlock Text="Binding Values,
Source=StaticResource proxy,
Converter=cnv:FuncForValueConverter,
ConverterParameter=x:Static local:Function.Average"/>
<TextBlock Text="Binding Values,
Source=StaticResource proxy,
Converter=cnv:FuncForValueConverter,
ConverterParameter=x:Static local:Function.Sum"/>
</StackPanel>
</DataTemplate>
</DataGridColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
【讨论】:
【参考方案2】:我创建了一个通用转换器来评估任何表达式。 我原来的主题:ExpressionConverter. 源代码链接:WpfMvvm/WpfMvvm.Converters NuGet 链接:WpfMvvm.Converters
原文翻译:
ExpressionConverter - 计算简单算术表达式的转换器。 要从转换器中的绑定中获取字符串表达式,请使用 Format(String, Object[]) method。 对于复合格式字符串,使用转换器参数或第一个绑定的值。 要计算字符串中的结果表达式,请使用 DataTable.Compute(String, String) method。 第一个值是接收到的带有表达式的字符串,第二个是空字符串。
转换器可以用作一个值的普通转换器:
<TextBlock Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center">
<Run Text="Half window height:"/>
<Run Text="Binding ActualHeight,
ElementName=window,
Mode=OneWay,
Converter=cnvs:ExpressionConverter,
ConverterParameter='0 / 2.0'"/>
</TextBlock>
它也可以用作多个值的多重转换器:
<FrameworkElement.Resources>
<x:Array Type="sys:String" x:Key="operators">
<sys:String>+</sys:String>
<sys:String>-</sys:String>
<sys:String>/</sys:String>
<sys:String>*</sys:String>
</x:Array>
</FrameworkElement.Resources>
<UniformGrid Columns="1">
<UniformGrid Columns="5" VerticalAlignment="Center">
<TextBox x:Name="tb1" Text="1.2" Margin="5" HorizontalContentAlignment="Center"/>
<ComboBox x:Name="cBox" Margin="5" HorizontalContentAlignment="Center"
ItemsSource="DynamicResource operators"
SelectedIndex="0"/>
<TextBox x:Name="tb2" Text="3.4" Margin="5" HorizontalContentAlignment="Center"/>
<TextBlock Text="=" TextAlignment="Center" Margin="5"/>
<TextBlock TextAlignment="Center" Margin="5">
<TextBlock.Text>
<MultiBinding Converter="cnvs:ExpressionConverter"
ConverterParameter="(0) 1 (2)">
<Binding Path="Text" ElementName="tb1"/>
<Binding Path="SelectedItem" ElementName="cBox"/>
<Binding Path="Text" ElementName="tb2"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</UniformGrid>
P.S.如果您对类似方法感兴趣,如果您提供有关您的任务的更多详细信息,我可以向您展示如何将其用于您的任务。
【讨论】:
CalcBinding 不是这里发明的以上是关于有没有办法在 WPF 中求和或平均属性?的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法在 wpf WebBrowser 控件之上呈现 WPF 控件?