DataGrid Calculated列不显示任何数据

时间:2014-07-28 13:27:56

标签: c# wpf

让我们有一个例子来清楚地理解我的问题:

我有三个TextBox控件,如下所示:

<TextBox x:Name="Quantity" />
<TextBox x:Name="Rate" />
<TextBox x:Name="Amount" />

所以,我想在数量或费率变化时计算金额。 金额=数量*费率

同样,如果用户打算更改金额,我想更改费率。所以,Rate的公式将是 费率=金额/数量

实际问题:

我有一个包含三个列的DataGrid:

<DataGrid ItemsSource="{Binding OpeningBalances}">

    <DataGrid.Columns>

        <DataGridTemplateColumn Header="Quantity" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Quantity}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Quantity}" Loaded="TextBox_Loaded"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="Rate" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Rate}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Rate}" Loaded="TextBox_Loaded"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>

        <DataGridTemplateColumn Header="Amount" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock>
                        <TextBlock.Text>
                            <MultiBinding Converter="{StaticResource amountFromQuantityAndRateConverter}">
                                <Binding Path="Quantity" />
                                <Binding Path="Rate" />
                            </MultiBinding>
                        </TextBlock.Text>
                    </TextBlock>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
            <DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <TextBox Loaded="TextBox_Loaded">
                        <TextBox.Text>
                            <MultiBinding Converter="{StaticResource amountFromQuantityAndRateConverter}">
                                <Binding Path="Quantity" />
                                <Binding Path="Rate" />
                            </MultiBinding>
                        </TextBox.Text>
                    </TextBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellEditingTemplate>
        </DataGridTemplateColumn>

    </DataGrid.Columns>

</DataGrid>

ViewModel中的属性声明:

public MainWindowViewModel()
{
    OpeningBalances = new ObservableCollection<ItemOpeningBalanceRow>();
}

private ObservableCollection<ItemOpeningBalanceRow> _openingBalances;
public ObservableCollection<ItemOpeningBalanceRow> OpeningBalances
{
    get
    {
        return _openingBalances;
    }
    set
    {
        _openingBalances = value;
        OnPropertyChanged("OpeningBalances");
    }
}

ItemOpeningBalnceRow.cs:

public class ItemOpeningBalanceRow 
{
    public double Quantity { get; set; }
    public double Rate { get; set; }
    public double Amount { get; set; }
}

转换器 - AmountFromQuantityAndRateConverter.cs

public class AmountFromQuantityAndRateConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int _quantity = 0;
        int _rate = 0;

        if (int.TryParse(values[0].ToString(), out _quantity) && int.TryParse(values[1].ToString(), out _rate))
        {
            return _quantity * _rate;
        }

        return 0;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

我使用了上面提到的多值转换器但仍然是Amount列始终为null。我的意思是它不显示任何数据。

更新

@ sky-dev给出的答案是最好的答案,并且工作正常。但我想使用转换器获得相同的方法。

我尝试了什么?:

我创建了两个转换器,如下所示:

RateFromAmountAndQuantityConverter.cs

public class RateFromAmountAndQuantityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int _quantity = 0;
        int _amount = 0;
        int _rate = 0;

        if (int.TryParse(values[0].ToString(), out _quantity) && int.TryParse(values[1].ToString(), out _amount) && int.TryParse(values[2].ToString(), out _rate))
        {
            if (_quantity != 0 && _amount != 0)
            {
                if (_rate == _amount / _quantity)
                    return _rate.ToString();
                else
                    return (_amount / _quantity).ToString();
            }
        }

        return "0";
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

其他是AmountFromQuantityAndRateConverter.cs

public class AmountFromQuantityAndRateConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int _quantity = 0;
        int _rate = 0;
        int _amount = 0;

        if (int.TryParse(values[0].ToString(), out _quantity) && int.TryParse(values[1].ToString(), out _rate) && int.TryParse(values[2].ToString(), out _amount))
        {
            if (_rate != 0)
            {
                if (_amount == _quantity * _rate)
                    return _amount.ToString();
                else
                    return (_quantity * _rate).ToString();
            }
        }

        return "0";
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

我称他们如下:

<DataGridTemplateColumn Header="Quantity" >
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged}" Loaded="TextBox_Loaded"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Rate" >
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock>
                <TextBlock.Text>
                    <MultiBinding Converter="{StaticResource rateFromAmountAndQuantityConverter}">
                        <Binding Path="Quantity" UpdateSourceTrigger="PropertyChanged"/>
                        <Binding Path="Amount" UpdateSourceTrigger="PropertyChanged"/>
                        <Binding Path="Rate" UpdateSourceTrigger="PropertyChanged" />
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <TextBox Loaded="TextBox_Loaded">
                <TextBox.Text>
                    <MultiBinding Converter="{StaticResource rateFromAmountAndQuantityConverter}" >
                        <Binding Path="Quantity" UpdateSourceTrigger="PropertyChanged"/>
                        <Binding Path="Amount" UpdateSourceTrigger="PropertyChanged"/>
                        <Binding Path="Rate" UpdateSourceTrigger="PropertyChanged" />
                    </MultiBinding>
                </TextBox.Text>
            </TextBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

<DataGridTemplateColumn Header="Amount" >
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock>
                <TextBlock.Text>
                    <MultiBinding Converter="{StaticResource amountFromQuantityAndRateConverter}">
                        <Binding Path="Quantity" UpdateSourceTrigger="PropertyChanged"/>
                        <Binding Path="Rate" UpdateSourceTrigger="PropertyChanged"/>
                        <Binding Path="Amount" UpdateSourceTrigger="PropertyChanged" />
                    </MultiBinding>
                </TextBlock.Text>
            </TextBlock>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <TextBox Loaded="TextBox_Loaded">
                <TextBox.Text>
                    <MultiBinding Converter="{StaticResource amountFromQuantityAndRateConverter}">
                        <Binding Path="Quantity" UpdateSourceTrigger="PropertyChanged"/>
                        <Binding Path="Rate" UpdateSourceTrigger="PropertyChanged"/>
                        <Binding Path="Amount" UpdateSourceTrigger="PropertyChanged" />
                    </MultiBinding>
                </TextBox.Text>
            </TextBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

使用以上两个转换器的问题:

他们引用彼此,如循环引用,但我没有得到stackOverflow的例外。事实上,我没有得到任何错误,但无论我设定什么价格或金额都保持为0.我也知道为什么他们仍然是0.但不知道解决方案。

2 个答案:

答案 0 :(得分:1)

干得好,包括所有细节。真有帮助!有一些麻烦点。

  1. 我在后面的MainWindow代码和行对象中实现了INotifyPropertyChanged
  2. MainWindow需要DataContext = this才能正确绑定数据
  3. 在键入数字而不是焦点更改时需要进行行更新,因此&#34; UpdateSourceTrigger = PropertyChanged&#34;
  4. 转换器需要返回(_quantity * _rate).ToString()以正确绑定到TextBlock.Text字段。这也让我感到惊讶。
  5. 试试这个。

    <Grid>
        <Grid.Resources>
            <converters:AmountFromQuantityAndRateConverter x:Key="amountFromQuantityAndRateConverter" />
        </Grid.Resources>
    
        <DataGrid ItemsSource="{Binding OpeningBalances}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Quantity" >
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
    
                <DataGridTemplateColumn Header="Rate" >
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Rate, UpdateSourceTrigger=PropertyChanged}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Rate, UpdateSourceTrigger=PropertyChanged}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
    
                <DataGridTemplateColumn Header="Amount" >
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock>
                                <TextBlock.Text>
                                    <MultiBinding Converter="{StaticResource amountFromQuantityAndRateConverter}">
                                        <Binding Path="Quantity" UpdateSourceTrigger="PropertyChanged" />
                                        <Binding Path="Rate" UpdateSourceTrigger="PropertyChanged"  />
                                    </MultiBinding>
                                </TextBlock.Text>
                            </TextBlock>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid> 
    
    
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            OpeningBalances = new ObservableCollection<ItemOpeningBalanceRow>(new [] { new ItemOpeningBalanceRow()});
            DataContext = this;
        }
    
        private ObservableCollection<ItemOpeningBalanceRow> _openingBalances;
        public ObservableCollection<ItemOpeningBalanceRow> OpeningBalances
        {
            get
            {
                return _openingBalances;
            }
            set
            {
                _openingBalances = value;
                OnPropertyChanged();
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    public class ItemOpeningBalanceRow : INotifyPropertyChanged
    {
        private double _quantity;
        private double _rate;
    
        public double Quantity
        {
            get { return _quantity; }
            set { _quantity = value;  OnPropertyChanged(); }
        }
        public double Rate {
            get { return _rate; }
            set { _rate = value; OnPropertyChanged(); }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    public class AmountFromQuantityAndRateConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            int _quantity = 0;
            int _rate = 0;
    
            if (int.TryParse(values[0].ToString(), out _quantity) && int.TryParse(values[1].ToString(), out _rate))
            {
                return (_quantity * _rate).ToString();
            }
    
            return 0;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    上面的代码应该有用,但我会推荐另一种方法。只需抛弃转换器并在ViewModel(ItemOpeningBalanceRow)中执行此操作。更简单。这是什么样的:

    <DataGrid ItemsSource="{Binding OpeningBalances}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Quantity" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
    
            <DataGridTemplateColumn Header="Rate" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Rate, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Rate, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
    
            <DataGridTemplateColumn Header="Amount" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Amount}">
                        </TextBlock>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
    
    public class ItemOpeningBalanceRow : INotifyPropertyChanged
    {
        private double _quantity;
        private double _rate;
    
        public double Quantity
        {
            get { return _quantity; }
            set { _quantity = value; OnPropertyChanged(); OnPropertyChanged("Amount"); }
        }
        public double Rate {
            get { return _rate; }
            set { _rate = value; OnPropertyChanged(); OnPropertyChanged("Amount"); }
        }
    
        public double Amount
        {
            get { return Quantity*Rate; }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

    *更新*

    我已经计算了金额的费率。

    <DataGrid ItemsSource="{Binding OpeningBalances}" AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="Quantity" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
    
            <DataGridTemplateColumn Header="Rate" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Rate, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Rate, UpdateSourceTrigger=PropertyChanged}"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
    
            <DataGridTemplateColumn Header="Amount" >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                            <TextBlock Text="{Binding Amount, UpdateSourceTrigger=PropertyChanged}">
                        </TextBlock>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <TextBox Text="{Binding Amount, UpdateSourceTrigger=PropertyChanged}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
    
    public class ItemOpeningBalanceRow : INotifyPropertyChanged
    {
        private double _quantity;
        private double _rate;
        private double _amount;
    
        public double Quantity
        {
            get { return _quantity; }
            set 
            { 
                _quantity = value; 
                OnPropertyChanged();
                Amount = Quantity*Rate;
            }
        }
        public double Rate {
            get { return _rate; }
            set
            {
                if (!(Math.Abs(_rate - value) > 0.0001)) return;
                _rate = value;
                OnPropertyChanged();
                Amount = Quantity * Rate;
            }
        }
    
        public double Amount
        {
            get { return Quantity*Rate; }
            set
            {
                if (!(Math.Abs(_amount - value) > 0.0001)) return;
                _amount = value;
                OnPropertyChanged();
                Rate = _amount / Quantity;
            }
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

答案 1 :(得分:1)

要使用转换器,您只能使用1个转换器。您需要使转换器中数量字段的范围处于类级别,以便它可以保留数量值以计算ConvertBack调用的新速率。我的模型现在没有Amount属性,但如果您需要任何业务逻辑,那么您只需要一个返回Quantity * Rate的getter,因为Amount将始终等于该结果。

AmountConverter - 我使用了小数,但您可以更改为int并处理舍入,但是您决定。

public class AmountConverter : IMultiValueConverter
{
    decimal quantity = 0m;
    decimal rate = 0m;

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (decimal.TryParse(values[0].ToString(), out quantity) && decimal.TryParse(values[1].ToString(), out rate))
        {
            return (quantity * rate).ToString();
        }

        return "0";
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        decimal amount = 0m;
        object[] values = new object[2];
        values[0] = quantity;
        values[1] = rate;

        if (decimal.TryParse(value.ToString(), out amount))
        {
            if (quantity != 0m)
                values[1] = amount / quantity;
        }

        return values;
    }
}

XAML - 我简化了使用一些TextBlock来演示功能。如果数量为0,则输入新的金额会将金额重置为0,因为您不能除以0。

<StackPanel DataContext="{StaticResource MainViewModel}">
    <TextBox Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged}" 
             Margin="4" />
    <TextBox Text="{Binding Rate, UpdateSourceTrigger=PropertyChanged}"
             Margin="4" />
    <TextBox Margin="4">
        <TextBox.Text>
            <MultiBinding Converter="{StaticResource AmountConverter}"
                          UpdateSourceTrigger="PropertyChanged">
                <Binding Path="Quantity" />
                <Binding Path="Rate" />
            </MultiBinding>
        </TextBox.Text>
    </TextBox>
</StackPanel>