C# WPF DataGrid 更改值 PropertyChange 的行格式(例如背景)

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

我的目标是当底层值属性更改时更改 DataGrid 的行/单元格样式。它与值无关,DataGrid 应该只显示它已更改。我有一个 Tournament.cs 对象列表,它会触发属性更改事件。我添加了一个方法,该方法可以侦听 MainWindow.cs 中的这些更改,并且应该在 DataGrid 中找到该对象并更改相关行/单元格的格式。不幸的是它对我不起作用。该方法被调用,我能够找到锦标赛的“DataGridRow”对象,但更改它的格式,例如背景没有任何影响。我怎样才能做到这一点?在下面找到我的代码片段。

这是数据网格。每行代表一个锦标赛对象,每个单元格一个属性,可以在外部更新。目标是突出更新的价值。

来自 MainWindow.cs 的片段:

    namespace TurnierChecker
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml.
        /// </summary>
        public partial class MainWindow
        {
            private readonly ICollectionView sortedTournamentList;
    
            /// <summary>
            /// Initializes a new instance of the <see cref="MainWindow"/> class.
            /// </summary>
            public MainWindow()
            {
                // intialization step
                this.InitializeComponent();
    
                // Initial loading of data.
                SortableBindingList<Tournament>? tournaments = JSONSerializer.DeserializeFromFile<SortableBindingList<Tournament>>("TournamentData.json");
                this.TournamentList = tournaments ?? new SortableBindingList<Tournament>();
                this.TournamentList.ListChanged += this.TournamentListChanged;
    
                // Sorting
                this.sortedTournamentList = CollectionViewSource.GetDefaultView(this.TournamentList);
                this.sortedTournamentList.SortDescriptions.Add(new SortDescription("Date", ListSortDirection.Ascending));
                this.sortedTournamentList.Refresh();
    
                // tie View with ViewModel
                this.TournamentGrid.DataContext = this.TournamentList;
            }
    
            /// <summary>
            /// Gets the tournament list.
            /// </summary>
            /// <value>
            /// The tournament list.
            /// </value>
            public SortableBindingList<Tournament> TournamentList { get; private set; }
    
            public void TournamentListChanged(object? sender, ListChangedEventArgs e)
            {
    
                // Property of tournament change.
                if (e.ListChangedType == ListChangedType.ItemChanged)
                {
    
                    Tournament? tmnt = (sender as SortableBindingList<Tournament>)?.ElementAt(e.NewIndex);
    
                    DataGridRow? row = this.TournamentGrid.ItemContainerGenerator.ContainerFromItem(tmnt) as DataGridRow;
    
                    LoggerUI.Instance.Error(Logging.LogType.System, $"Row: {row}  -  Turnier: {tmnt}");
    
                    switch (e.PropertyDescriptor?.Name)
                    {
                        case nameof(Tournament.Date):
                            break;
                        case nameof(Tournament.Series):

                            row.Background = Brushes.Green;
                            break;
    
                        // Default property change.
                        default:
                            break;
                    }
                }
            }
        }
    }

MainWindow.xaml 中的片段:

        <Window x:Class="TurnierChecker.MainWindow"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:TurnierChecker"
                xmlns:system="clr-namespace:System;assembly=mscorlib"
                Title="TurnierSpion" Height="850" Width="1240" Icon="Resources/Logo.ico">
            <DockPanel>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" MinHeight="100"/>
                        <RowDefinition Height="5" MinHeight="5"/>
                        <RowDefinition Height="22" MinHeight="22"/>
                        <RowDefinition Height="250" MinHeight="100"/>
                        <RowDefinition Height="30" MinHeight="30"/>
                    </Grid.RowDefinitions>
                    <!--Tournament DataGrid-->
                    <DataGrid x:Name="TournamentGrid" ItemsSource="{Binding}" AlternatingRowBackground="Moccasin" AlternationCount="2" IsReadOnly="True" HeadersVisibility="Column" 
                              AutoGenerateColumns="False" VerticalScrollBarVisibility="Auto" RowHeight="28" SelectionUnit="FullRow">
                        <!-- This is required to handle CTRL + C when something is selected in the DataGrid -->
                        <DataGrid.CommandBindings>
                            <CommandBinding Command="Copy" Executed="CopyCommand" />
                        </DataGrid.CommandBindings>
                        <!-- This is required to handle CTRL + C when something is selected in the DataGrid -->
                        <DataGrid.InputBindings>
                            <KeyBinding Key="C" Modifiers="Control" Command="Copy" />
                        </DataGrid.InputBindings>
                        <DataGrid.Columns>
                            <!--Tournament Date-->
                            <DataGridTextColumn Binding="{Binding Date, StringFormat=\{0: ddd dd.MM.yy HH:mm U\\hr\}, ConverterCulture=de-DE}" Header="Datum" Width="160" MinWidth="170" SortDirection="Ascending"/>
                            <!--Tournament Catgory-->
                            <DataGridTextColumn Binding="{Binding Category}" Header="Kategorie" Width="180" MinWidth="100"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </Grid>
            </DockPanel>
        </Window>

来自 Tournament.cs 的片段:

    namespace TurnierChecker.Models
    {
        using System;
        using System.Collections.Generic;
        using System.ComponentModel;
        using System.Runtime.CompilerServices;
        using System.Text;
    
        /// <summary>
        /// The tournament model.
        /// </summary>
        public class Tournament : INotifyPropertyChanged, IEquatable<Tournament>
        {
            /// <summary>
            /// The date and time of the tournament.
            /// </summary>
            private DateTime? date;
    
            /// <summary>
            /// The series  of the tournament.
            /// </summary>
            private string? series;
    
            
            /// <summary>
            /// Initializes a new instance of the <see cref="Tournament"/> class.
            /// </summary>
            public Tournament()
            {
            }
    
            /// <summary>
            /// Tritt ein, wenn sich ein Eigenschaftswert ändert.
            /// </summary>
            public event PropertyChangedEventHandler? PropertyChanged;
    
            /// <summary>
            /// Gets or sets the date and time of the tournament.
            /// </summary>
            /// <value>
            /// The date and time.
            /// </value>
            public DateTime? Date
            {
                get => this.date;
                set => this.SetField(ref this.date, value);
            }
    
            /// <summary>
            /// Gets or sets the series of the tournament.
            /// </summary>
            /// <value>
            /// The series.
            /// </value>
            public string? Series
            {
                get => this.series;
                set => this.SetField(ref this.series, value);
            }
    
            /// <summary>
            /// Called when [property changed].
            /// </summary>
            /// <param name="propertyName">Name of the property.</param>
            private void OnPropertyChanged(string? propertyName) => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
            /// <summary>
            /// Sets the field.
            /// </summary>
            /// <typeparam name="T">The property type.</typeparam>
            /// <param name="field">The field.</param>
            /// <param name="value">The value.</param>
            /// <param name="propertyName">Name of the property.</param>
            /// <returns>Returns true if the property was changed.</returns>
            private bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
            {
                if (EqualityComparer<T>.Default.Equals(field, value))
                {
                    return false;
                }
    
                field = value;
                this.OnPropertyChanged(propertyName);
                return true;
            }
        }
    }
c# wpf datagrid
1个回答
0
投票

好的,我可以给你一些东西。它有点离谱,并且不包含在 XAML 中,但我仍然会尝试。

这是我的数据网格行的片段

<DataGrid.Columns>
    <DataGridTextColumn x:Name="Id" Header="EmployeeId" Binding="{Binding EmployeeId}" IsReadOnly="True"/>
    <DataGridTextColumn x:Name="Name" Header="Name" Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
    <DataGridCheckBoxColumn x:Name="HasLeased" Header="HasLeasedCar" Binding="{Binding HasLeasedCar, UpdateSourceTrigger=PropertyChanged}" />
    <DataGridTextColumn x:Name="RFID" Header="RFID" Binding="{Binding Rfid, UpdateSourceTrigger=PropertyChanged}"/>
    <DataGridTextColumn x:Name="RFIDMapped" Header="RFIDMapped" Binding="{Binding RfidMapped, UpdateSourceTrigger=PropertyChanged}"/>
    <DataGridTextColumn x:Name="Created" Header="CreatedDate" Binding="{Binding CreatedDate}" IsReadOnly="True"/>
    <DataGridTextColumn x:Name="Modified" Header="ModifiedDate" Binding="{Binding ModifiedDate}" IsReadOnly="True" Width="*"/>
</DataGrid.Columns>

现在我的 Employee 对象对于每个可编辑属性都有一个 HasNew 属性。 例如,我有 RFID 和 HasNewRFID,在这种情况下,列标题与 HasNewAttributes 具有相同的名称也很重要。

private bool _nameInit;
private string _oldName = null!;
private string _name = null!;

public string Name
{
    get => _name;
    set
    {
        if (!_nameInit)
        {
            _oldName = value;
            _name = value;
            _nameInit = true;
        }
        else
        {
            if (value.Trim() == _name && value.Trim() != _oldName)
            {
                return;
            }

            _name = value.Trim();
            OnPropertyChanged();
            HasNewName = _name != _oldName;
        }
    }
}

private bool _hasNewName;

public bool HasNewName
{
    get => _hasNewName;
    set
    {
        _hasNewName = value;
        OnPropertyChanged();
    }
}

public bool HasNew => HasNewName || HasNewRFID || etc

现在我的主要想法是,我可以为每个实例编写一个样式,但我想自动化。

#region Style Support
public void CreateSources()
{
    ILog log = _logger.Log().Start();

    try
    {
        DataGridColumn[] editableColumns = DatGrdEmployee.Columns.Where(c => !c.IsReadOnly).ToArray();

        string[] na = editableColumns.Select(c => "HasNew" + (c.Header as string)).ToArray();

        Dictionary<string, Style> styles = CreateSource(na);

        foreach (DataGridColumn c in editableColumns)
        {
            var style = styles.Where(s => s.Key == (c.Header as string)).ToArray()[0].Value;

            c.CellStyle = style;
        }
    }
    catch (Exception ex)
    {
        log.Error(ex);
    }

    log.End();
}

public Dictionary<string, Style> CreateSource(string[] pathNames)
{
    // ReSharper disable once CoVariantArrayConversion
    ILog log = _logger.Log().Start(pathNames);

    Dictionary<string, Style> styles = new();

    try
    {
        Setter orangeBackground = new(BackgroundProperty, new SolidColorBrush(Colors.Orange));
        Setter transparentBackground = new(BackgroundProperty, new SolidColorBrush(Colors.Transparent));

        foreach (string pathName in pathNames)
        {
            Style style = new();
            style.Setters.Add(transparentBackground);

            DataTrigger dataTrigger = new()
            {
                Binding = new Binding
                {
                    Path = ((PropertyPath)(TypeDescriptor.GetConverter(typeof(PropertyPath)).ConvertFromInvariantString(pathName))!)
                },
                Value = true,
                Setters = { orangeBackground }
            };

            style.Triggers.Add(dataTrigger);
            string newPathName = pathName.Replace("HasNew", "");

            styles.Add(newPathName, style);
        }
    }
    catch (Exception ex)
    {
        log.Error(ex);
    }

    return log.End(styles);
}

#endregion

因此,如果我们一步一步进行:一开始我只是收集所有我感兴趣/应该对值变化做出反应的列。

然后在 na 中,我将所有标头与 HasNew 组合起来,这样我就有一个充满 HasNewName、HasNewRFID 等的数组。

然后在 CreateSource() 中我创建一个包含字符串和样式的字典。最终目标是用标题名称和样式填充它。

然后我们创建两个 setter,一个用于更改值,一个用于正常状态。

我们进入 foreach 循环,首先创建样式并添加或普通设置器。

然后我们创建一个DataTrigger,它将创建一个Binding,看起来很复杂,但最后是这样的

Binding = new Binding
{
    Path = ((PropertyPath)(TypeDescriptor.GetConverter(typeof(PropertyPath)).ConvertFromInvariantString(pathName))!)
}

Only 真正意味着 Binding ="{Binding Path= HasNew[Property]}" 完整的 DataTrigger 代码翻译为

<DataTrigger Binding="{Binding Path=HasNewName}" Value="True">
    <Setter Property="Background" Value="Orange"/>
</DataTrigger>

然后我们将该触发器添加到样式中。然后,我们从字符串中删除 HasNew,以便取回标头,然后将该标头名称和样式添加到字典中。现在冲洗并重复,您就明白了。

现在样式看起来像这样

<Style>
    <Setter Property="Background" Value="Transparent"/>
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=HasNewName}" Value="True">
            <Setter Property="Background" Value="Orange"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

简单解释一下Style中的DataTrigger。它基本上只是意味着当 X 条件满足时,将 Y 属性设置为值 Z,如果未满足,则值 Y 将为其默认值。

一旦我们拥有了所有样式,我们就会返回字典并迭代我们的列。我们从标题具有适当名称的字典中获取样式,并将列单元格样式设置为该样式,导致该列中的每个单元格现在都具有该样式。现在不用担心每个单元格仍然绑定到其行的员工。就是这样。现在,如果您真的不想使用 C# 代码,则必须为每一列单独执行每种样式。

但是现在假设您想要更简单的东西,只需检查:是否有更改并重新着色整行。 现在这相当简单

<DataGrid.RowStyle>
    <Style TargetType="DataGridRow">
        <Style.Triggers>
            <DataTrigger Binding="{Binding HasNew}" Value="True">
                <Setter Property="Background" Value="Orange"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.RowStyle>

只需将其直接添加到 DataGridColoumns 下方即可完成。 This这对我来说就是细胞变化的样子。我审查了很多行,因为其他条目都是真实的人,我不会破坏公司数据

© www.soinside.com 2019 - 2024. All rights reserved.