Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flutter Copy From Excel Clipboard and pate into each cell loop column wise and row wise #2090

Closed
abdulrehmananwar opened this issue Sep 22, 2024 · 9 comments
Labels
data grid Data grid component workaround available Workaround available to overcome the query

Comments

@abdulrehmananwar
Copy link

Bug description

i want to Copy excel Clipboard and paste into Grid with auto add new Row.
Note it will also validate cell when paste and if there any issue on cansubmit event then brake the paste function

Steps to reproduce

1.while Paste into first column value it doing same rest other column values are fine
2. before pasting into new cell editing cursor is not visible. each cell need to show edit cursor when it being paste the data.
3. while pasting into 3 columns it showing much cell focused.

Code sample

Code sample
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:flutter/services.dart';

class CustomSelectionManager extends RowSelectionManager {
  CustomSelectionManager({
    required this.isDialogOpen,
    required this.dataGridController,
    required this.dataGridSource,
    required this.context,
    required this.lastColumnIndex,
  });

  final bool isDialogOpen;
  final DataGridController dataGridController;
  final DataGridSource dataGridSource;
  final int lastColumnIndex;
  final BuildContext context;

  @override
  Future<void> handleKeyEvent(KeyEvent keyEvent) async {
    if (keyEvent.logicalKey == LogicalKeyboardKey.tab ||
        keyEvent.logicalKey == LogicalKeyboardKey.enter) {
      if (!isDialogOpen) {
        await super.handleKeyEvent(keyEvent).then(
              (value) => WidgetsBinding.instance!.addPostFrameCallback((_) async {
            await dataGridController.beginEdit(dataGridController.currentCell);
          }),
        );
      }
    }
  }
}

class GridEntryDataSource extends DataGridSource {
  GridEntryDataSource(this.dataGridController, this.fieldNames) {
    _initializeDataList();
    updateDataGridRows();
  }

  final DataGridController dataGridController;
  final List<String> fieldNames;
  List<DataGridRow> dataGridRows = [];
  List<Map<String, String>> dataList = [];

  void _initializeDataList() {
    dataList.add(Map.fromIterable(fieldNames, key: (v) => v, value: (v) => ''));
  }

  @override
  List<DataGridRow> get rows => dataGridRows;

  void updateDataGridRows() {
    dataGridRows = dataList.map<DataGridRow>((data) => _createDataRow(data)).toList();
    notifyListeners();
  }

  DataGridRow _createDataRow(Map<String, String> data) {
    return DataGridRow(
      cells: fieldNames.map((field) => DataGridCell<String>(columnName: field, value: data[field])).toList(),
    );
  }

  Future<void> handlePasteAndNavigate(int startRowIndex, int startColumnIndex) async {
    final clipboardData = await Clipboard.getData('text/plain');

    if (clipboardData != null) {
      List<String> rows = clipboardData.text!.split('\n');

      for (int i = 0; i < rows.length - 1; i++) {
        List<String> columns = rows[i].split('\t');

        // Ensure we have enough rows in the dataList
        while (dataList.length <= startRowIndex + i) {
          dataList.add(Map.fromIterable(fieldNames, key: (v) => v, value: (v) => ''));
        }

        for (int j = 0; j < columns.length; j++) {
          if (startColumnIndex + j < fieldNames.length) {
            // Use the updated row index safely
            int rowIndex = startRowIndex + i;
            int colIndex = startColumnIndex+j;
            RowColumnIndex _rowColumnIndex = RowColumnIndex(rowIndex, colIndex);
            await dataGridController.beginEdit(_rowColumnIndex);
            await Future.delayed(Duration(milliseconds: 500)); // Wait before submitting
            await _onCellSubmitted(dataGridRows[rowIndex], fieldNames[colIndex], columns[j]);
            print('Field Name is ${ fieldNames[colIndex]} value is ${columns[j]} Row Number is $rowIndex Column Index is $colIndex');
//            dataList[rowIndex][fieldNames[colIndex]] = columns[j];
            print('data List Value is $dataList');
            dataGridController.moveCurrentCellTo(_rowColumnIndex);
          }
        }
      }
      updateDataGridRows();
    }
  }

  void resetGrid() {
    dataList.clear();
    _initializeDataList();
    updateDataGridRows();
  }

  void changeColumns(List<String> newColumns) {
    fieldNames.clear();
    fieldNames.addAll(newColumns);
    resetGrid();
  }

  Future<void> _onCellSubmitted(DataGridRow row, String columnName, String value) async {
    final rowIndex = dataGridController.currentCell.rowIndex;

    // Validate rowIndex and dataList length
    if (rowIndex >= 0 && rowIndex < dataList.length) {
      dataList[rowIndex][columnName] = value;

      // Add a new row if the last one is filled
      if (rowIndex == dataList.length - 1 && dataList[rowIndex].values.any((v) => v.isNotEmpty)) {
        dataList.add(Map.fromIterable(fieldNames, key: (v) => v, value: (v) => ''));
      }
      updateDataGridRows();
    } else {
      print("Invalid row index during submission: $rowIndex");
    }
  }


  @override
  Future<void> onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) async {
    print('onCellSubmit${rowColumnIndex}');
    final value = dataList[rowColumnIndex.rowIndex][column.columnName];
    await _onCellSubmitted(dataGridRow, column.columnName, value!);
    return super.onCellSubmit(dataGridRow, rowColumnIndex, column);
  }

  @override
  Future<bool> canSubmitCell(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column) {
    print('can submit event ${rowColumnIndex}');
    // TODO: implement canSubmitCell
    return super.canSubmitCell(dataGridRow, rowColumnIndex, column);
  }


  @override
  DataGridRowAdapter buildRow(DataGridRow row) {
    return DataGridRowAdapter(
      cells: row.getCells().map<Widget>((cell) {
        return Container(
          alignment: Alignment.center,
          child: Text(cell.value?.toString() ?? ''),
        );
      }).toList(),
    );
  }



  @override
  Widget? buildEditWidget(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, GridColumn column, CellSubmit submitCell) {
    final TextEditingController editingController = TextEditingController(
      text: dataList[rowColumnIndex.rowIndex][column.columnName]?.toString() ?? '',
    );

    return Focus(
      onKeyEvent: (node, keyEvent) {
        if (keyEvent.logicalKey == LogicalKeyboardKey.keyV && HardwareKeyboard.instance.isControlPressed) {
          handlePasteAndNavigate(rowColumnIndex.rowIndex, rowColumnIndex.columnIndex);
          return KeyEventResult.handled;
        }
        return KeyEventResult.ignored;
      },
      child: TextField(
        autofocus: true,
        controller: editingController,
        onSubmitted: (value) {
//          _onCellSubmitted(dataGridRow, column.columnName, value);
  //        submitCell();
        },
      ),
    );
  }
}

class DataGridExample extends StatefulWidget {
  @override
  _DataGridExampleState createState() => _DataGridExampleState();
}

class _DataGridExampleState extends State<DataGridExample> {
  List<String> fieldNames = ['EmployeeName', 'Age'];
  late DataGridController dataGridController;
  late GridEntryDataSource dataSource;

  final Map<String, List<String>> columnOptions = {
    'AgeFields': ['EmployeeName', 'Age'],
    'InvoiceFields': ['ItemName', 'Quantity', 'Price', 'Amount'],
    'WeekSummary': ['ItemName', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
  };

  String selectedOption = 'AgeFields';

  @override
  void initState() {
    super.initState();
    dataGridController = DataGridController();
    dataSource = GridEntryDataSource(dataGridController, fieldNames);
  }

  void changeColumns(String option) {
    setState(() {
      selectedOption = option;
      dataSource.changeColumns(columnOptions[option]!);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Editable DataGrid Example'),
        actions: [
          IconButton(
            icon: Icon(Icons.paste),
            onPressed: () async {
              final focusedCell = dataSource.rows.first;
              int rowIndex = dataSource.rows.indexOf(focusedCell);
              await dataSource.handlePasteAndNavigate(rowIndex, 0);
            },
          ),
          IconButton(
            icon: Icon(Icons.refresh),
            onPressed: () {
              dataSource.resetGrid();
            },
          ),
        ],
      ),
      body: Column(
        children: [
          DropdownButton<String>(
            value: selectedOption,
            onChanged: (String? newValue) {
              if (newValue != null) {
                changeColumns(newValue);
              }
            },
            items: columnOptions.keys.map<DropdownMenuItem<String>>((String value) {
              return DropdownMenuItem<String>(
                value: value,
                child: Text(value),
              );
            }).toList(),
          ),
          Expanded(
            child: GestureDetector(
              onTap: () {
                FocusScope.of(context).unfocus();
              },
              child: SfDataGrid(
                allowColumnsResizing: true,
                selectionMode: SelectionMode.single,
                navigationMode: GridNavigationMode.cell,
                editingGestureType: EditingGestureType.tap,
                allowEditing: true,
                source: dataSource,
                controller: dataGridController,
                selectionManager: CustomSelectionManager(
                  isDialogOpen: false,
                  dataGridController: dataGridController,
                  dataGridSource: dataSource,
                  context: context,
                  lastColumnIndex: fieldNames.length - 1,
                ),
                columns: fieldNames.map((fieldName) {
                  return GridColumn(
                    columnName: fieldName,
                    label: Container(
                      alignment: Alignment.center,
                      child: Text(fieldName, style: TextStyle(fontWeight: FontWeight.bold)),
                    ),
                  );
                }).toList(),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(home: DataGridExample()));
}

Screenshots or Video

Screenshots / Video demonstration

[Upload media here]
current flutter
https://github.com/user-attachments/assets/5b859745-7fa1-427e-bba7-5118b3707485

required as

22.09.2024_19.03.22_REC.mp4

Stack Traces

Stack Traces
[Add the Stack Traces here]

On which target platforms have you observed this bug?

Web, Windows

Flutter Doctor output

Doctor output
[Add your output here]
@VijayakumarMariappan VijayakumarMariappan added data grid Data grid component open Open labels Sep 23, 2024
@abineshPalanisamy
Copy link

abineshPalanisamy commented Sep 23, 2024

Hi @abdulrehmananwar

We thoroughly analyzed the provided sample. After a detailed review, we found that in the _onCellSubmitted method, you are creating new DataGridRows for every value update in the Map collection, instead of editing the existing ones. This results in improper behavior with the current cell focus and causes the cursor to become invisible.

We have modified the sample so that instead of rebuilding the rows for each value update in the _onCellSubmitted method, we now update the respective cell values within the rows during DataGridSource.onCellSubmit. In the onCellSubmit method, we fetch the relevant value from the Map collection and update it in the appropriate cell. Additionally, we build an extra row based on specific conditions within the _onCellSubmitted method.

We have attached the modified sample for your reference. Please review it for further details.

Regards,
Abinesh P

@abdulrehmananwar
Copy link
Author

its almost perfect only Few Issues .

  1. when i paste new data on existing data it still showing existing not new one.
  2. when i suddenly change the grid type you can see cell showing focus into multiple cells.
  3. when i suddenly change the grid multiple types before completion of coping. then grid hands. as you see in last of video. and see exception into code.
23.09.2024_21.26.34_REC.mp4

@abineshPalanisamy
Copy link

Hi @abdulrehmananwar

 

Query Response
when i paste new data on existing data it still showing existing not new one. The issue arises from the logic added to newValue in the onCellSubmit method. When a cell enters edit mode, the text inside the cell is assigned to newValue. This results in improper pasting of new data over the existing data. To resolve this issue, we need to adjust newValue in the handlePasteAndNavigate method when pasting.
when i suddenly change the grid multiple types before completion of coping. then grid hands. as you see in last of video. and see exception into code. When pasting is in progress and we switch the table, we encounter an exception in _onCellSubmitted because the for loop that assigns values to cells in handlePasteAndNavigate is still in progress. When we switch the grid, the dataGridRows are initialized a new, causing the row index in the for loop to not get updated. To avoid this exception, we have used the IgnorePointer widget as the parent of the DropdownButton and utilized a ValueNotifier _isPasting to restrict the dropdown from opening while pasting is in progress. With this approach, after completing the paste operation, we can access the dropdown to switch the grid.
when i suddenly change the grid type you can see cell showing focus into multiple cells. After restricting the switch to the new DataGrid before completing the pasting process, we did not encounter the improper focus on the cells as you mentioned. If you are still experiencing the same problem, we kindly request that you provide clear additional details and reproducible steps. This will greatly assist us in investigating the issue further and providing an appropriate solution as quickly as possible.

 

We have attached the modified sample for your reference. Please review it for further details.

 

Regards,
Abinesh P

@abdulrehmananwar
Copy link
Author

Still Facing Error.
1- on Second Time Company last cell value not update.
2- after coping complete some times if i click on other click it not clickable. as showing into video.
3- while coping if user click on any other gridview cell then gridview cell some are not clickable.
4. some time

24.09.2024_16.54.18_REC.mp4

after copy completed then i select different grid-view from option button then cell focus still on same cell as previous grid.

@abineshPalanisamy
Copy link

Hi @abdulrehmananwar,

Based on the provided details and the mentioned issue, we have conducted a review on our end.

Query Response
on Second Time Company last cell value not update. The issue occurs due to the newValue variable. We have added logic in buildEditWidget using _isPasting.value. When we tap the cell, displayText is added to newValue. However, if you are using copy-pasting, newValue is set to null
after coping complete sometimes if i click on other click it not clickable. as showing into video. After copying is complete, sometimes if I click on another cell, it becomes unclickable. This issue arises from calling notifyListeners in onCellSubmit and the logic added in canSubmitCell. This improper behavior occurs when tapping a cell to enter edit mode. We have modified the logic in canSubmitCell and added notifyListeners in _onCellSubmitted only when creating a new row.
while coping if user click on any other gridview cell then gridview cell some are not clickable.   after copy completed then i select different grid-view from option button then cell focus still on same cell as previous grid. This issue arises when switching to a new grid. Therefore, we call endEdit when transitioning to the new grid and use WidgetsBinding.instance.addPostFrameCallback in changeColumns(newValue) to ensure that the endEdit completes properly. Additionally, when switching to the new grid, we need to reinitialize the controller and data source.

 

We have attached the modified sample and video for your reference. Please review it for further details.

 

Regards,

Abinesh P 

@ashok-kuvaraja ashok-kuvaraja added waiting for customer response Cannot make further progress until the customer responds. and removed open Open labels Oct 3, 2024
@ashok-kuvaraja
Copy link
Collaborator

Hi @abdulrehmananwar,

We suspect that the reported issue has been resolved at your end. Hence, we are closing this issue. If you need any further assistance, please reopen this. We are always happy to help.

Regards,
Ashok K

@ashok-kuvaraja ashok-kuvaraja added workaround available Workaround available to overcome the query and removed waiting for customer response Cannot make further progress until the customer responds. labels Oct 15, 2024
@MalikSamiAwan
Copy link

i tried this solution its working fine on windows but not on macos

@MalikSamiAwan
Copy link

@ashok-kuvaraja is there any workaround for mac as well because same copy paste demo provided is working for windows but not for mac

@abineshPalanisamy
Copy link

Hi @MalikSamiAwan

The sample of copy-paste behavior mentioned above was achieved through a custom implementation by the customer. Currently, we do not have direct support for copying and pasting cell content in the DataGrid.

At present, the DataGrid does not support copying and pasting cell content. However, we have already noted your request as a potential feature, and we plan to consider it for future releases. During the planning stage for each release cycle, we review all open feature requests and prioritize them for implementation based on various factors, including product vision, technological feasibility, and customer demand.

We appreciate your patience and understanding, and you can refer to the feedback link below for further updates.

Feedback link: 37701

Regards,
Abinesh P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
data grid Data grid component workaround available Workaround available to overcome the query
Projects
None yet
Development

No branches or pull requests

5 participants