DataGrid вычисляет разницу между значениями в двух ячейках данных - PullRequest
4 голосов
/ 14 февраля 2011

В моем небольшом приложении у меня есть DataGrid (см. Скриншот), который привязан к списку объектов Measurement. Измерение - это просто контейнер данных с двумя свойствами: Date и CounterGas (float). Каждый объект измерений представляет мое потребление газа на определенную дату.

enter image description here

Список измерений привязан к DataGrid следующим образом:

    <DataGrid ItemsSource="{Binding Path=Measurements}" AutoGenerateColumns="False">
        <DataGrid.Columns>
             <DataGridTextColumn Header="Date" Binding="{Binding Path=Date, StringFormat={}{0:dd.MM.yyyy}}" />
             <DataGridTextColumn Header="Counter Gas" Binding="{Binding Path=ValueGas, StringFormat={}{0:F3}}" />
        </DataGrid.Columns>
    </DataGrid>

Ну, а теперь мой вопрос :) Я бы хотел, чтобы рядом со столбцом «Счетчик газа» был еще один столбец, в котором показана разница между фактическим значением счетчика и последним значением счетчика.

например. этот дополнительный столбец должен вычислять разницу между значением 13 февраля и 6 февраля => 199,789 - 187,115 = 15,674

Каков наилучший способ достичь этого? Я хотел бы избежать любых вычислений в классе Measurement, которые должны просто хранить данные. Я бы предпочел, чтобы DataGrid обрабатывал вычисления. Так есть ли способ добавить еще один столбец, который просто вычисляет разницу между значениями? Может быть, использовать какой-то конвертер и экстремальные привязки? ; D

P.S .: Может быть, кто-то с лучшей репутацией мог бы вставить скриншот. Спасибо:)

1 Ответ

4 голосов
/ 15 февраля 2011

Экстремальный переплет?Нет проблем.

<Window.Resources>
    <local:ItemsDifferenceConverter x:Key="ItemsDifferenceConverter"/>
</Window.Resources>
<DataGrid ItemsSource="{Binding Path=Measurements}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Date" Binding="{Binding Path=Date, StringFormat={}{0:dd.MM.yyyy}}" />
        <DataGridTextColumn Header="Counter Gas" Binding="{Binding Path=ValueGas, StringFormat={}{0:F3}}" />
        <DataGridTextColumn Header="Difference">
            <DataGridTextColumn.Binding>
                <MultiBinding Converter="{StaticResource ItemsDifferenceConverter}" Mode="OneWay">
                    <Binding Path="."/>
                    <Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" Path="ItemsSource"/>
                </MultiBinding>
            </DataGridTextColumn.Binding>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

Какой-то конвертер

class ItemsDifferenceConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
                      object parameter, CultureInfo culture)
    {
        if (values.Length != 2)
            return null;

        var item = values[0] as Measurement;
        var collection = values[1] as IEnumerable<Measurement>;
        if (item == null || collection == null)
            return null;

        var list = collection.OrderBy(v => v.Date).ToList(); //it will be easier to find a previous date
        var itemIndex = list.IndexOf(item);
        if (itemIndex == 0) //First item
            return null;

        var diff = item.ValueGas - list[itemIndex - 1].ValueGas;
        return (diff > 0 ? "+" : "") + diff.ToString();
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new Exception("The method or operation is not implemented.");
    }
}

Но этот пример не работает с удалением / обновлением элементов базовой коллекции.В этом случае промежуточный ViewModel является лучшим выбором.

Вот мой способ сделать это.Работает с обновлением, удалением и добавлением элементов.

/// <summary>
/// Main ViewModel, contains items for DataGrid
/// </summary>
public class MeasurementListViewModel
{
    public MeasurementListViewModel(IEnumerable<Measurement> measurements)
    {
        this.Items = new ObservableCollection<MeasurementViewModel>(measurements.Select(m=>new MeasurementViewModel(m)));
        this.Measurements = (ListCollectionView)CollectionViewSource.GetDefaultView(this.Items);

        this.Items.CollectionChanged += new NotifyCollectionChangedEventHandler(Items_CollectionChanged);
        foreach(var m in this.Items)
            m.PropertyChanged += new PropertyChangedEventHandler(Item_PropertyChanged);

    }

    //Date or Value were changed
    void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        //Update the collection view if refresh isn't possible
        if (this.Measurements.IsEditingItem)
            this.Measurements.CommitEdit();
        if (this.Measurements.IsAddingNew)
            this.Measurements.CommitNew();

        this.Measurements.Refresh();
    }

    //Items were added or removed
    void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        //Attach the observer for the properties
        if (e.NewItems != null)
            foreach (var vm in e.NewItems.OfType<MeasurementViewModel>())
                vm.PropertyChanged += Item_PropertyChanged;

        //Refresh when it is possible
        if(!this.Measurements.IsAddingNew && !this.Measurements.IsEditingItem)
            this.Measurements.Refresh();
    }

    private ObservableCollection<MeasurementViewModel> Items { get; set; }

    public ListCollectionView Measurements { get; set; }
}

/// <summary>
/// Wraps Measurement class and provide notification of changes
/// </summary>
public class MeasurementViewModel
{
    public MeasurementViewModel()
    {
        this.Model = new Measurement();
    }

    public MeasurementViewModel(Measurement m)
    {
        this.Model = m;
    }

    public Measurement Model { get; private set; }

    public DateTime Date
    {
        get { return this.Model.Date; }
        set
        {
            this.Model.Date = value;
            OnPropertyChanged("Date");
        }
    }

    public double ValueGas
    {
        get { return this.Model.ValueGas; }
        set
        {
            this.Model.ValueGas = value;
            OnPropertyChanged("ValueGas");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Конвертер немного отличается:

class ItemsDifferenceConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType,
                      object parameter, CultureInfo culture)
    {
        var item = values[0] as MeasurementViewModel;
        var view = values[1] as ICollectionView;
        if (item == null || view == null)
            return null;

        var list = view.SourceCollection.OfType<MeasurementViewModel>().OrderBy(v => v.Date).ToList(); //it will be easier to find a previous date
        var itemIndex = list.IndexOf(item);

        if (itemIndex == 0) //First item
            return null;

        var diff = item.ValueGas - list[itemIndex - 1].ValueGas;
        return (diff > 0 ? "+" : "") + diff.ToString();
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new Exception("The method or operation is not implemented.");
    }
}

И DataGrid:

<DataGrid ItemsSource="{Binding Path=Measurements}"  AutoGenerateColumns="False"
          CanUserAddRows="True">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Date" Binding="{Binding Path=Date, StringFormat={}{0:dd.MM.yyyy}}" />
        <DataGridTextColumn Header="Counter Gas" Binding="{Binding Path=ValueGas, StringFormat={}{0:F3}}" />
        <DataGridTextColumn Header="Difference">
            <DataGridTextColumn.Binding>
                <MultiBinding Converter="{StaticResource ItemsDifferenceConverter}" Mode="OneWay">
                    <Binding Path="."/>
                    <Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGrid}}" Path="ItemsSource"/>
                </MultiBinding>
            </DataGridTextColumn.Binding>
        </DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...