syncfusion / flutter-widgets

Syncfusion Flutter widgets libraries include high quality UI widgets and file-format packages to help you create rich, high-quality applications for iOS, Android, and web from a single code base.
1.6k stars 783 forks source link

Column Resizing or Columns Dynamic (column insertion/deletion) only one works? #2143

Closed MalikSamiAwan closed 1 month ago

MalikSamiAwan commented 1 month ago

In my syncfusion grid datasource if declare columns like this: List get columns => [] ; means as a getter than column resizing works fine

but in my use case i want column resizing and i also have feature to add/remove columns but if i try to do that than that does not work i have to make columns in variable like this List columns = [];

in what way i can achieve both means i want column resizing as well as columns insertion/deletion

CODE:

`

SyncfusionHomePage( initialData: [ Employee(id: 1, name: 'John Doe', salary: 60000, jobTitle: 'Manager', joiningDate: '2022-01-15'), Employee(id: 2, name: 'Jane Doe', salary: 55000, jobTitle: 'Developer', joiningDate: '2021-06-10'), Employee(id: 3, name: 'Mark Smith', salary: 75000, jobTitle: 'Director', joiningDate: '2020-03-25'), Employee(id: 4, name: 'Sara Williams', salary: 50000, jobTitle: 'Designer', joiningDate: '2019-08-05'), Employee(id: 5, name: 'Paul Brown', salary: 45000, jobTitle: 'QA', joiningDate: '2018-11-30'), ],),

import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import 'package:syncfusion_flutter_datagrid/datagrid.dart';

class SyncfusionHomePage extends StatefulWidget { final List initialData;

SyncfusionHomePage({Key? key, required this.initialData}) : super(key: key);

@override _SyncfusionHomePageState createState() => _SyncfusionHomePageState(); }

class _SyncfusionHomePageState extends State { late CustomDataSource _dataSource; Color currentColor = Colors.amber;

String searchQuery = '';

@override void initState() { super.initState(); _dataSource = CustomDataSource(initialData: widget.initialData); }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('DataGrid with Dynamic Rows and Columns'), ), body: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Expanded( child: SfDataGrid( source: _dataSource, columnWidthMode: ColumnWidthMode.fill, allowEditing: true, allowColumnsResizing: true, columns: _dataSource.columns, columnResizeMode: ColumnResizeMode.onResizeEnd, onColumnResizeUpdate: (ColumnResizeUpdateDetails details) { print(details.width); setState(() {

              });
              return true;
            },
            onCellLongPress: (DataGridCellLongPressDetails details) {
              if (details.rowColumnIndex.rowIndex != -1 &&
                  details.rowColumnIndex.columnIndex != -1) {
                _showContextMenu(details.globalPosition,
                    details.rowColumnIndex.rowIndex - 1, details.rowColumnIndex.columnIndex);
              }
            },
          ),
        ),
      ],
    ),
  ),
);

}

void _showContextMenu(Offset globalPosition, int rowIndex, int columnIndex) { final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;

showMenu<int>(
  context: context,
  position: RelativeRect.fromRect(
    globalPosition & const Size(40, 40),
    Offset.zero & overlay.size,
  ),
  items: [
    PopupMenuItem<int>(
      value: 1,
      child: Text('Add Row Above'),
    ),
    PopupMenuItem<int>(
      value: 2,
      child: Text('Add Row Below'),
    ),
    PopupMenuItem<int>(
      value: 3,
      child: Text('Add Column Before'),
    ),
    PopupMenuItem<int>(
      value: 4,
      child: Text('Add Column After'),
    ),
    const PopupMenuDivider(),
    PopupMenuItem<int>(
      value: 5,
      child: Text('Delete Row'),
    ),
    PopupMenuItem<int>(
      value: 6,
      child: Text('Delete Column'),
    ),
    const PopupMenuDivider(),
    PopupMenuItem<int>(
      value: 11,
      child: Text('Change Cell Color'),
    ),
    PopupMenuItem<int>(
      value: 12,
      child: Text('Change Cell Text Style'),
    ),
    PopupMenuItem<int>(
      value: 13,
      child: Text('Change Cell Font Size'),
    ),
  ],
).then((value) {
  if (value != null) {
    setState(() {
      switch (value) {
        case 1:
          _dataSource.insertRow(rowIndex);
          break;
        case 2:
          _dataSource.insertRow(rowIndex + 1);
          break;
        case 3:
          _dataSource.insertColumn(columnIndex);
          break;
        case 4:
          _dataSource.insertColumn(columnIndex + 1);
          break;
        case 5:
          _dataSource.deleteRowAt(rowIndex);
          break;
        case 6:
          _dataSource.deleteColumnAt(columnIndex);
          break;
        case 11:
          _selectCellColor(rowIndex, columnIndex);
          break;
        case 12:
          _selectCellTextStyle(rowIndex, columnIndex);
          break;
        case 13:
          _selectCellFontSize(rowIndex, columnIndex);
          break;
      }
    });
  }
});

}

void _selectCellColor(int rowIndex, int columnIndex) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Select Cell Color"), content: SingleChildScrollView( child: BlockPicker( pickerColor: currentColor, onColorChanged: (Color color) { setState(() { currentColor = color; _dataSource.updateCellStyle(rowIndex, columnIndex, backgroundColor: currentColor); }); }, availableColors: [ Colors.red, Colors.green, Colors.blue, Colors.yellow, Colors.purple, Colors.orange, ], ), ), actions: [ TextButton( child: const Text("Close"), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); }

void _selectCellTextStyle(int rowIndex, int columnIndex) { String selectedFontFamily = 'Roboto'; showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Select Font Style"), content: DropdownButton( value: selectedFontFamily, onChanged: (String? newFont) { setState(() { selectedFontFamily = newFont!; _dataSource.updateCellStyle(rowIndex, columnIndex, fontFamily: selectedFontFamily); }); }, items: ['Roboto', 'Arial', 'Courier', 'Times New Roman', 'Verdana'] .map<DropdownMenuItem>((String value) { return DropdownMenuItem( value: value, child: Text(value), ); }).toList(), ), actions: [ TextButton( child: const Text("Close"), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); }

void _selectCellFontSize(int rowIndex, int columnIndex) { double selectedFontSize = _dataSource.getCellStyle(rowIndex, columnIndex).fontSize; showDialog( context: context, builder: (BuildContext context) { return AlertDialog( title: const Text("Select Cell Font Size"), content: DropdownButton( value: selectedFontSize, onChanged: (double? newSize) { setState(() { selectedFontSize = newSize!; _dataSource.updateCellStyle(rowIndex, columnIndex, fontSize: selectedFontSize); }); }, items: [12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0] .map<DropdownMenuItem>((double value) { return DropdownMenuItem( value: value, child: Text(value.toString()), ); }).toList(), ), actions: [ TextButton( child: const Text("Close"), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); } }

class CustomDataSource extends DataGridSource { List _rows = []; Map<String, CellStyle> _cellStyles = {}; // Store cell styles

List get columns => [ GridColumn( columnName: 'id', label: Container( padding: EdgeInsets.all(8), alignment: Alignment.center, child: Text('ID'), ), ), GridColumn( columnName: 'name', label: Container( padding: EdgeInsets.all(8), alignment: Alignment.center, child: Text('Name'), ), ), GridColumn( columnName: 'salary', label: Container( padding: EdgeInsets.all(8), alignment: Alignment.center, child: Text('Salary'), ), ), GridColumn( columnName: 'jobTitle', label: Container( padding: EdgeInsets.all(8), alignment: Alignment.center, child: Text('Job Title'), ), ), GridColumn( columnName: 'joiningDate', label: Container( padding: EdgeInsets.all(8), alignment: Alignment.center, child: Text('Joining Date'), ), ), ];

CustomDataSource({required List initialData}) { _rows = initialData.map((employee) { return DataGridRow(cells: [ DataGridCell(columnName: 'id', value: employee.id), DataGridCell(columnName: 'name', value: employee.name), DataGridCell(columnName: 'salary', value: employee.salary), DataGridCell(columnName: 'jobTitle', value: employee.jobTitle), DataGridCell(columnName: 'joiningDate', value: employee.joiningDate), ]); }).toList(); }

@override List get rows => _rows;

@override DataGridRowAdapter buildRow(DataGridRow row) { return DataGridRowAdapter( cells: row.getCells().asMap().entries.map((entry) { int index = entry.key; DataGridCell cell = entry.value;

    String cellKey = '${_rows.indexOf(row)}_$index';
    CellStyle style = _cellStyles[cellKey] ?? CellStyle();

    if (cell.columnName == 'name') {
      return Container(
        alignment: Alignment.center,
        padding: EdgeInsets.all(8.0),
        color: style.backgroundColor,
        child: TextField(
          style: TextStyle(fontSize: style.fontSize, fontWeight: style.fontWeight, fontFamily: style.fontFamily),
          controller: TextEditingController(text: cell.value.toString()),
          onSubmitted: (value) {
            final int index = _rows.indexOf(row);
            row.getCells()[1] = DataGridCell<String>(columnName: 'name', value: value);
            updateDataSource(index, row);
          },
        ),
      );
    } else {
      return Container(
        alignment: Alignment.center,
        padding: EdgeInsets.all(8.0),
        color: style.backgroundColor,
        child: Text(cell.value.toString(),
            style: TextStyle(fontSize: style.fontSize, fontWeight: style.fontWeight, fontFamily: style.fontFamily)),
      );
    }
  }).toList(),
);

}

void updateCellStyle(int rowIndex, int columnIndex, {Color? backgroundColor, double? fontSize, FontWeight? fontWeight, String? fontFamily}) { String key = '${rowIndex}_$columnIndex'; final currentStyle = _cellStyles[key] ?? CellStyle(); _cellStyles[key] = currentStyle.copyWith( backgroundColor: backgroundColor ?? currentStyle.backgroundColor, fontSize: fontSize ?? currentStyle.fontSize, fontWeight: fontWeight ?? currentStyle.fontWeight, fontFamily: fontFamily ?? currentStyle.fontFamily, ); notifyListeners(); }

CellStyle getCellStyle(int rowIndex, int columnIndex) { String key = '${rowIndex}_$columnIndex'; return _cellStyles[key] ?? CellStyle(); }

void insertRow(int index) { _rows.insert( index, DataGridRow(cells: [ DataGridCell(columnName: 'id', value: 0), DataGridCell(columnName: 'name', value: ''), DataGridCell(columnName: 'salary', value: 0), DataGridCell(columnName: 'jobTitle', value: ''), DataGridCell(columnName: 'joiningDate', value: ''), ]), ); notifyListeners(); }

void deleteRowAt(int index) { if (index >= 0 && index < _rows.length) { _rows.removeAt(index); notifyListeners(); } }

void insertColumn(int index) { final columnName = 'NewColumn${columns.length}'; columns.insert( index, GridColumn( columnName: columnName, label: Container( padding: EdgeInsets.all(8), alignment: Alignment.center, child: Text(columnName), ), ), );

for (var row in _rows) {
  row.getCells().insert(index, DataGridCell<String>(
      columnName: columnName, value: 'Value-${index}'));
}
notifyListeners();

}

void deleteColumnAt(int index) { if (index >= 0 && index < columns.length && columns.length > 2) { // Ensuring minimum columns String columnNameToBeRemoved = columns[index].columnName; columns.removeAt(index); for (var row in _rows) { row.getCells().removeWhere( (cell) => cell.columnName == columnNameToBeRemoved); } notifyListeners(); } }

void updateDataSource(int index, DataGridRow row) { _rows[index] = row; notifyListeners(); } }

class CellStyle { final Color backgroundColor; final double fontSize; final FontWeight fontWeight; final String fontFamily;

CellStyle({ this.backgroundColor = Colors.white, this.fontSize = 14.0, this.fontWeight = FontWeight.normal, this.fontFamily = 'Roboto', });

CellStyle copyWith({ Color? backgroundColor, double? fontSize, FontWeight? fontWeight, String? fontFamily, }) { return CellStyle( backgroundColor: backgroundColor ?? this.backgroundColor, fontSize: fontSize ?? this.fontSize, fontWeight: fontWeight ?? this.fontWeight, fontFamily: fontFamily ?? this.fontFamily, ); } }

class Employee { final int id; final String name; final int salary; final String jobTitle; final String joiningDate;

Employee({ required this.id, required this.name, required this.salary, required this.jobTitle, required this.joiningDate, }); }

`

abineshPalanisamy commented 1 month ago

Hi @MalikSamiAwan ,

As requested, we have provided a sample demonstrating the column insertion and deletion functionality, along with the column resizing feature. The attached sample serves as a reference for your review. In this example, the solution allows for both column resizing and the dynamic addition and removal of columns while preserving the current column widths. This approach avoids unnecessarily rebuilding the entire data grid and ensures that the resizing feature works seamlessly with dynamic column operations. Please review sample for further details and adapt it to suit your specific requirements.

Regards, Abinesh P

MalikSamiAwan commented 1 month ago

Thankyou very much issue is resolved