如何创建含混合列内容的DataGrid?附Calc类数据绑定需求
Got it, let's build this out step by step. I'll use WPF for the implementation (since you mentioned ObservableCollection and DataGrid, that's the standard framework here). We'll cover the data model, calculation logic, viewmodel, and finally the DataGrid setup with editable input parameters.
1. Define the Parameter Entry Model
First, we need a class to represent each parameter (input or output) along with its values from the two calculations. This will bind directly to our DataGrid:
using System.ComponentModel; public class ParameterEntry : INotifyPropertyChanged { private string _parameterName; private object _firstCalculationValue; private object _secondCalculationValue; private bool _isEditable; public string ParameterName { get => _parameterName; set { _parameterName = value; OnPropertyChanged(); } } public object FirstCalculationValue { get => _firstCalculationValue; set { _firstCalculationValue = value; OnPropertyChanged(); } } public object SecondCalculationValue { get => _secondCalculationValue; set { _secondCalculationValue = value; OnPropertyChanged(); } } public bool IsEditable { get => _isEditable; set { _isEditable = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
This class holds the parameter name, both calculation values, and a flag to control if the parameter is editable (only input parameters will be editable).
2. Implement the Calc Class
Next, our calculation logic - we'll add a guard clause for division by zero to prevent runtime errors:
public class Calc { public (int d, double e, bool f) Calculate(int a, int b, bool c) { int d = a + b; double e = b != 0 ? (double)(a * a) / (b * b) : double.NaN; // Handle division by zero bool f = !c; return (d, e, f); } }
3. Build the ViewModel
The viewmodel will manage our ObservableCollection of parameters, handle user input, and trigger calculations to update output values:
using System.Collections.ObjectModel; using System.Windows.Input; public class CalcViewModel : INotifyPropertyChanged { private readonly Calc _calc = new Calc(); public ObservableCollection<ParameterEntry> Parameters { get; } public ICommand CalculateCommand { get; } public CalcViewModel() { Parameters = new ObservableCollection<ParameterEntry> { // Input parameters (editable) new ParameterEntry { ParameterName = "a (int)", IsEditable = true, FirstCalculationValue = 0, SecondCalculationValue = 0 }, new ParameterEntry { ParameterName = "b (int)", IsEditable = true, FirstCalculationValue = 1, SecondCalculationValue = 2 }, new ParameterEntry { ParameterName = "c (bool)", IsEditable = true, FirstCalculationValue = false, SecondCalculationValue = true }, // Output parameters (non-editable) new ParameterEntry { ParameterName = "d = a + b", IsEditable = false }, new ParameterEntry { ParameterName = "e = a²/b²", IsEditable = false }, new ParameterEntry { ParameterName = "f = !c", IsEditable = false } }; CalculateCommand = new RelayCommand(ExecuteCalculate); // Initial calculation to populate output values ExecuteCalculate(); } private void ExecuteCalculate() { // Get input values from the first 3 entries if (int.TryParse(Parameters[0].FirstCalculationValue?.ToString(), out int a1) && int.TryParse(Parameters[1].FirstCalculationValue?.ToString(), out int b1) && bool.TryParse(Parameters[2].FirstCalculationValue?.ToString(), out bool c1)) { var result1 = _calc.Calculate(a1, b1, c1); Parameters[3].FirstCalculationValue = result1.d; Parameters[4].FirstCalculationValue = result1.e.ToString("F2"); // Format to 2 decimal places Parameters[5].FirstCalculationValue = result1.f; } if (int.TryParse(Parameters[0].SecondCalculationValue?.ToString(), out int a2) && int.TryParse(Parameters[1].SecondCalculationValue?.ToString(), out int b2) && bool.TryParse(Parameters[2].SecondCalculationValue?.ToString(), out bool c2)) { var result2 = _calc.Calculate(a2, b2, c2); Parameters[3].SecondCalculationValue = result2.d; Parameters[4].SecondCalculationValue = result2.e.ToString("F2"); Parameters[5].SecondCalculationValue = result2.f; } } // Simple RelayCommand implementation (you can also use Prism/MvvmLight commands) public class RelayCommand : ICommand { private readonly Action _execute; public event EventHandler CanExecuteChanged; public RelayCommand(Action execute) => _execute = execute; public bool CanExecute(object parameter) => true; public void Execute(object parameter) => _execute(); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
The viewmodel initializes the parameter collection with default values, sets up a command to trigger calculations, and handles parsing input values safely.
4. Design the DataGrid in XAML
Finally, the DataGrid with mixed editable/non-editable columns, and proper data type handling:
<Window x:Class="CalcApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CalcApp" Title="Calc DataGrid" Height="450" Width="600"> <Window.DataContext> <local:CalcViewModel /> </Window.DataContext> <Grid Margin="20"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Button Content="Run Calculations" Command="{Binding CalculateCommand}" Margin="0 0 0 10" Padding="10 5"/> <DataGrid Grid.Row="1" ItemsSource="{Binding Parameters}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False"> <DataGrid.Columns> <!-- Parameter Name Column --> <DataGridTextColumn Header="Parameter Name" Binding="{Binding ParameterName}" IsReadOnly="True" Width="200"/> <!-- First Calculation Column --> <DataGridTemplateColumn Header="Calculation 1"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding FirstCalculationValue}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <TextBox Text="{Binding FirstCalculationValue}" /> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> <DataGridTemplateColumn.CellStyle> <Style TargetType="DataGridCell"> <Setter Property="IsEditing" Value="{Binding IsEditable}" /> </Style> </DataGridTemplateColumn.CellStyle> </DataGridTemplateColumn> <!-- Second Calculation Column --> <DataGridTemplateColumn Header="Calculation 2"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding SecondCalculationValue}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <TextBox Text="{Binding SecondCalculationValue}" /> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> <DataGridTemplateColumn.CellStyle> <Style TargetType="DataGridCell"> <Setter Property="IsEditing" Value="{Binding IsEditable}" /> </Style> </DataGridTemplateColumn.CellStyle> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </Grid> </Window>
Key Features Explained:
- Editable Inputs: The first three rows (input parameters) are editable - users can type new values directly in the cells.
- Read-only Outputs: Output rows are non-editable, showing the calculated results.
- Type Handling: The code safely parses input values and formats the double output to 2 decimal places for readability.
- ObservableCollection: Automatically updates the DataGrid whenever parameters change (after running calculations).
内容的提问来源于stack exchange,提问作者Andrey Shirshov

