使用第一列中的值对 wpf 数据网格动态生成的列进行样式化

问题描述 投票:0回答:1

我有这个数据网格,我使用以下代码动态生成除第一列之外的列。

var viewModel = (SnapshotViewModel)DataContext;

foreach (var date in OverviewCompViewModel.DateColumns)
{
    
    var column = new DataGridTextColumn
    {
        Header = date,
        Binding = new Binding($"DateValues[{date}]"),
    };
    HistoricDataGrid.Columns.Add(column);
}

其财务数据反映了典型的财务报表 Metric Yearx、yeary、yearz 等。

我的问题是如何根据指标选项卡中给定行的值格式化行。例如,我想这样做,如果指标包含增长,则它是一个百分比,如果该值> 0,则颜色为绿色并且< 0 red etc is this even possible?

这是我的xaml

<DataGrid Grid.Row="1" x:Name="HistoricDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding FinancialMetrics}" CanUserAddRows="False" IsReadOnly="True">
 
    <DataGrid.Columns>
        <DataGridTextColumn Header="Metric" Binding="{Binding Metric}" />            
    </DataGrid.Columns>
</DataGrid>

enter image description here

c# .net wpf
1个回答
0
投票

您的规范适用于 dynamic 列,这使事情变得更有趣。满足您的规格的一种方法是使用

DataGridTemplateColumn
而不是
DataGridTextColumn
。这就是简短的答案(其余的是细节)。

var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));
textBlockFactory.SetBinding(DataContextProperty, new Binding($"[{e.Key}]"));
textBlockFactory.SetBinding(TextBlock.TextProperty, new Binding("Text"));
textBlockFactory.SetBinding(TextBlock.ForegroundProperty, new Binding("ForeColor"));
textBlockFactory.SetBinding(TextBlock.BackgroundProperty, new Binding("BackColor"));
textBlockFactory.SetValue(TextBlock.PaddingProperty, new Thickness(5, 0, 5, 0));

var template = new DataTemplate
{
    VisualTree = textBlockFactory,
};

HistoricDataGrid.Columns.Add(new DataGridTemplateColumn
{
    Header = e.Key,
    CellTemplate = template
});

将绑定粘合在一起需要几个步骤,在我浪费时间阅读下面我解释的内容之前,您可能需要克隆并运行我的工作示例来验证这是否是您所要的行为正在寻找。


示例
FinancialMetric
类(表示绑定到网格行的行项目)

上面的代码片段关于绑定到给定行的

FinancialMetric
对象的说法是,需要有一个提供数据上下文的索引器。将有一个 key,动态指定的列名,我们需要能够检索一个可以提供
ForeColor
BackColor
和(格式化)
Text
的对象。

作为前向参考,我们将制作自己的

FormattableObject
类,它可以包装任何
object
,同时还提供特定于单元格的格式信息。我们知道
FinancialMetric
类必须为
FinancialMetic
提供一个索引器,可以设置或获取这些对象之一,并订阅一个事件,该事件将给予
FinancialObject
对给定单元格值的格式的“最终决定权”,基于包含对象的运行时计算。

class FinancialMetric
{
    // The indexer for new Binding($"[{e.Key}]")
    public FormattableObject? this[string key]
    {
        get => _columns.TryGetValue(key, out var value) ? value : null;
        set
        {
            if (value is null)
            {
                if (_columns.ContainsKey(key))
                {
                    _columns.Remove(key);
                    ColumnChanged?.Invoke(this, new ColumnChangeEventArgs(key));
                }
            }
            else
            {
                _columns[key] = value;

                // This is the DYNAMIC GLUE that allows this specific
                // FinancialMetric to respond to this specific FormattableObject.
                value.PropertyRequestedFromParent -= ProvidePropertyValue;
                value.PropertyRequestedFromParent += ProvidePropertyValue;

                // This is an event, declared static, that informs the grid that
                // this value goes in a column named {key} which needs to be created
                // if it doesn't already exist.
                ColumnChanged?.Invoke(this, new ColumnChangeEventArgs(key, value));
            }
            OnPropertyChanged(key);
        }
    }
    private readonly Dictionary<string, FormattableObject> _columns = new();
    .
    .
    .
}

用法:使用动态列值创建 5 个不同的行项目。
DataContext.FinancialMetrics.Add(new FinancialMetric(Metric.Growth)
{
    {"2022", new FormattableObject{Target= "-4.0%" } },
    {"2023", new FormattableObject{Target= " 1.2%" } },
    {"2024", new FormattableObject{Target= "11.9%" } },
});
DataContext.FinancialMetrics.Add(new FinancialMetric(Metric.EBIT));
DataContext.FinancialMetrics.Add(new FinancialMetric(Metric.ROI));
DataContext.FinancialMetrics.Add(new FinancialMetric(Metric.Revenue)
{
    {"2023", new FormattableObject{Target= 999999.00, ForeColor = Brushes.Blue } },
});
DataContext.FinancialMetrics.Add(new FinancialMetric(Metric.StockPrice)
{
    {"2023", new FormattableObject{Target= 66.22, ForeColor = Brushes.Maroon } },
    {"2024", new FormattableObject{Target= 11.8, ForeColor = Brushes.Red } },
});

screenshot


FinancialMetric
拥有格式化的最终决定权。

如果我们有一个

FinancialMetric
实例,我们可以使用表达式
var formattedObject = lineItem["2023"]
。这正是
DataGridTemplateColumn
绑定正在做的事情。由于网格将出现
get
使用此
FormattableObject
绘制单元格的值,因此技巧将首先事件化行项目。

class FormattableObject : INotifyPropertyChanged
{
    public object? Target
    {
        get => _target;
        set
        {
            if (!Equals(_target, value))
            {
                _target = value;
                OnPropertyChanged();
            }
        }
    }
    object? _target = default;

    private T? RequestFromParent<T>(T value, [CallerMemberName] string? propertyName = null)
    {
        var e = new RequestFromParentEventArgs<T>(propertyName ?? string.Empty);
        PropertyRequestedFromParent?.Invoke(this, e);
        return e.NewValue ?? value;
    }

    // Requesting the formatted value of ForeColor from parent.
    public Brush? ForeColor
    {
        get => RequestFromParent(_foreColor);
        set
        {
            if (_foreColor != value)
            {
                _foreColor = value;
                OnPropertyChanged();
            }
        }
    }

    // Requesting the formatted value of the Text from parent.
    public string? Text => RequestFromParent(Target?.ToString());
    
    public event PropertyChangedEventHandler? PropertyChanged;
    public static event EventHandler<ColumnChangeEventArgs>? ColumnChanged;
    .
    .
    .
}

父级处理格式请求的示例

class FinancialMetric : INotifyPropertyChanged, IEnumerable<KeyValuePair<string, FormattableObject>>
{
    .
    .
    .
    private void ProvidePropertyValue(object? sender, PropertyChangedEventArgs e)
    {
        dynamic generic = e;
        if (sender is FormattableObject formattable)
        {
            var formatTarget = formattable.Target as IFormattable;
            switch (e.PropertyName)
            {
                case nameof(FormattableObject.Text):
                    // T E X T    S A M P L E S
                    switch (Metric)
                    {
                        case Metric.Revenue:
                            if (formatTarget is null)
                            {
                                generic.NewValue = formattable.Target?.ToString();
                            }
                            else
                            {
                                generic.NewValue = formatTarget.ToString("C0", CultureInfo.CurrentCulture);
                            }
                            break;
                        case Metric.StockPrice:
                            if (formatTarget is null)
                            {
                                generic.NewValue = formattable.Target?.ToString();
                            }
                            else
                            {
                                generic.NewValue = formatTarget.ToString("C2", CultureInfo.CurrentCulture);
                            }
                            break;
                    }
                    break;
                case nameof(FormattableObject.ForeColor):
                    // C O L O R    S A M P L E S
                    switch (Metric)
                    {
                        case Metric.Growth:
                            generic.NewValue = $"{formattable.Target}".Contains("-") ?
                                Brushes.Red : Brushes.Green;
                            break;
                    }
                    break;
                case nameof(FormattableObject.BackColor):
                    break;
            }
        }
    }
    .
    .
    .
}
© www.soinside.com 2019 - 2024. All rights reserved.