【问题标题】:show on a datagrid in red changed text in a datatable WPF在数据表 WPF 中以红色更改文本显示在数据网格上
【发布时间】:2016-01-16 23:06:06
【问题描述】:

当数据表中单元格的值与另一个数据表中的同一单元格不同时,如何以红色文本显示该单元格的值?在最终的应用程序中,表格将通过更改 csv 文件生成。所以我必须替换自动生成的列。我知道您需要一个 DataGridTemplateColumn ,将 CellTemplate 设置为资源。然而,这个被替换的列不是可视树的一部分,因此绑定不起作用。

文章How to bind to data when the DataContext is not inherited 表明实现Freezable 对象的转换器应该可以解决这个问题。为了弄清楚这个解决方案,我做了下一个简化的例子。但是它显示了发生变化的列的所有单元格中单元格的最后一个值,并且全部显示为红色:

视图模型:

using System;
using System.ComponentModel;
using System.Data;

namespace WpfApplication1
{
    class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        //private Model _Model; //for clarity left out
        private DataTable _propDataTable;
        public DataTable propDataTable
        {
            get { return _propDataTable; }
            set
            {
                _propDataTable = value;
                NotifyPropertyChanged("propDataTable");
            }
        }
        private DataTable propCopyDataTable;
        private string _sB;
        public string sB
        {
            get { return _sB; }
            set
            {
                _sB = value;
                NotifyPropertyChanged("sB");
            }
        }
        private bool _bB = false;
        public bool bB
        {
            get { return _bB; }
            set
            {
                _bB = value;
                NotifyPropertyChanged("bB");
            }
        }
        public ViewModel()
        {
            propDataTable = new DataTable();

            propDataTable.Columns.Add("A", typeof(string));
            propDataTable.Columns.Add("B", typeof(string));
            DataRow row0 = propDataTable.NewRow();
            DataRow row1 = propDataTable.NewRow();
            row0[0] = "A0";
            row0[1] = "B0";
            row1[0] = "A1";
            row1[1] = "B1";
            propDataTable.Rows.Add(row0);
            propDataTable.Rows.Add(row1);

            propCopyDataTable = propDataTable.Copy();
            //now set a different value in propCopyDataTable
            propCopyDataTable.Rows[1][1] = "Changed";
            //find out which cells in column B are different
            //try to show in red text which cell changed
            for (int i = 0; i < propDataTable.Rows.Count; i++)
            {
                DataRow dr = propDataTable.Rows[i];
                DataRow drc = propCopyDataTable.Rows[i];
                sB = (string) dr["B"];
                if (dr["B"].ToString().Equals(drc["B"].ToString()))
                {
                    bB = true;
                }
                else
                {
                    bB = false;
                }
            }
        }
    }
}

ObjectToForegroundConverter:

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;

namespace WpfApplication1
{
    [ValueConversion(typeof(object), typeof(SolidColorBrush))]
    public class ObjectToForegroundConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            SolidColorBrush b = new SolidColorBrush(Colors.Black);
            try
            {
                bool changedValue = (bool)value;
                if (changedValue)
                {
                    b = Brushes.Red;
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(string.Format("Error: {0}", e));//instance not set to a etc.
            }

            return b;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

BindingProxy 转换器:

using System.Windows;

namespace WpfApplication1
{
    public class BindingProxy : Freezable
    {  
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }
        public object Data
        {
            get { return (object)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
}

XAML 控件:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:myViewModel="clr-namespace:WpfApplication1" 
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <myViewModel:ViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <myViewModel:ObjectToForegroundConverter x:Key="MyObjectToForegroundConverter"/>
        <myViewModel:BindingProxy x:Key="proxy" Data="{Binding}" />
        <DataTemplate x:Key="changedBColumn" >
            <TextBlock 
            Text="{Binding Data.sB,Source={StaticResource proxy},Mode=OneWay}" 
            Foreground="{Binding Data.bB,Converter={StaticResource MyObjectToForegroundConverter},Source={StaticResource proxy},Mode=OneWay}"
            />
        </DataTemplate>

    </Window.Resources>
    <Grid>
        <DataGrid x:Name="myXAMLtable" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn"
                  ItemsSource="{Binding propDataTable}">            
        </DataGrid>
    </Grid>
</Window>

背后的代码:

using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
            switch (e.Column.Header.ToString())
            {
                case "B":
                    {
                        DataGridTemplateColumn BTemplateColumn = new DataGridTemplateColumn();
                        BTemplateColumn.Header = "B";
                        BTemplateColumn.CellTemplate = (DataTemplate)Resources["changedBColumn"];
                        e.Column = BTemplateColumn;
                        break;
                    }
            }
        }
    }
}

【问题讨论】:

    标签: c# wpf xaml datagrid datatable


    【解决方案1】:

    这里的问题是每个单元格都需要一个布尔值来指示变化的状态。但是您的视图模型中的列只有 1 个属性。这意味着同一列中的所有单元格将根据该属性具有相同的状态。这就解释了为什么B 列中的所有单元格都是红色的,因为属性bB 设置为true 并绑定到B 列中的所有单元格。

    您需要一个项目类(而不仅仅是简单的字符串值)来保存状态,如下所示:

    public class Item {
        public string Value {get;set;}
        public Item(string value){
            Value = value;
        }
        public bool IsChanged { get; private set;}
        public void SetChanged(){
           IsChanged = true;
        }
        public override string ToString(){
           return Value;
        }
        public override bool Equals(object other){
            var item = other as Item;
            if(item == null) return false;
            return item.Value == Value;
        }
        public override int GetHashCode(){
            if(Value == null) return base.GetHashCode();
            return Value.GetHashCode();
        }
    }
    

    现在你的DataTable 应该是这样创建的:

    propDataTable = new DataTable();
    
    propDataTable.Columns.Add("A", typeof(Item));
    propDataTable.Columns.Add("B", typeof(Item));
    DataRow row0 = propDataTable.NewRow();
    DataRow row1 = propDataTable.NewRow();
    row0[0] = new Item("A0");
    row0[1] = new Item("B0");
    row1[0] = new Item("A1");
    row1[1] = new Item("B1");
    propDataTable.Rows.Add(row0);
    propDataTable.Rows.Add(row1);
    
    propCopyDataTable = propDataTable.Copy();
    //now set a different value in propCopyDataTable
    propCopyDataTable.Rows[1][1] = new Item("Changed");
    //find out which cells in column B are different
    //try to show in red text which cell changed
    for (int i = 0; i < propDataTable.Rows.Count; i++) {
         DataRow dr = propDataTable.Rows[i];
         DataRow drc = propCopyDataTable.Rows[i];
         if (!object.Equals(dr["B"], drc["B"])) {
             (dr["B"] as Item).SetChanged();
         }         
    }
    

    现在你还需要像这样修改你的转换器:

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        SolidColorBrush b = new SolidColorBrush(Colors.Black);            
        var item = (Item)value;
        if (item.IsChanged) {
            b = Brushes.Red;
        }        
        return b;
    }
    

    XAML 也应该这样修改:

    <DataTemplate x:Key="changedBColumn" >
            <TextBlock Text="{Binding [B], Mode=OneWay}" 
            Foreground="{Binding [B],Converter={StaticResource MyObjectToForegroundConverter}, 
                         Mode=OneWay}"
            />
    </DataTemplate>
    

    注意:您不需要使用代理除非您想将 DataTemplate 中的控件绑定到主视图模型。但实际上这是错误的。实际上,您需要将控件绑定到这里的是每个数据行。在每个单元格模板中,隐含的DataContext 实际上是一个DataRowView。所以你不需要明确设置 Source 。正常绑定就好了。在上面的 XAML 中,我使用了路径 [B],这意味着直接传入带有字符串键 B 的索引器 [] - 它相当于调用 someDataRowView["B"]。此外,转换器将传入值作为Item(不是布尔值)。每次更改单元格的值时,都需要将其设置为新的Item,不要简单地更改Item.Value 属性,因为它不支持INotifyPropertyChanged。虽然在这种情况下看起来你只是想测试它。

    如果您想测试代理技术,请尝试创建另一个示例。事实上,当它成功地将所有单元格绑定到通过代理传入的主视图模型的 bB 属性时,该技术确实在您的原始代码中工作.

    【讨论】:

    • 感谢您的通俗解释。效果很好!
    猜你喜欢
    • 2012-10-26
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 2018-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-07
    相关资源
    最近更新 更多