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

syncfusion flutter datagrid Auto Focus on Startup and Up Down on Arrow Keys. #2134

Open
abdulrehmananwar opened this issue Oct 16, 2024 · 6 comments
Labels
data grid Data grid component waiting for customer response Cannot make further progress until the customer responds.

Comments

@abdulrehmananwar
Copy link

Use case

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

class TextFieldAndDataGrid extends StatefulWidget {
@OverRide
_TextFieldAndDataGridState createState() => _TextFieldAndDataGridState();
}

class _TextFieldAndDataGridState extends State {
final TextEditingController _textController = TextEditingController();
final FocusNode _dataGridFocusNode = FocusNode(); // FocusNode for DataGrid
final DataGridController _dataGridController = DataGridController();
List _employees = [];
int _currentSelectedIndex = 0;

@OverRide
void initState() {
super.initState();

// Initialize employees data with 30 rows
_employees = getEmployees();

// Set default selected index and focus on the DataGrid
WidgetsBinding.instance.addPostFrameCallback((_) {
  _dataGridController.selectedIndex = _currentSelectedIndex;
  _dataGridController.scrollToRow(_currentSelectedIndex.toDouble());
  _dataGridFocusNode.requestFocus();  // Focus on DataGrid when screen loads
});

}

@OverRide
void dispose() {
_textController.dispose();
_dataGridFocusNode.dispose();
_dataGridController.dispose();
super.dispose();
}

@OverRide
Widget build(BuildContext context) {
return Scaffold(
body: RawKeyboardListener(
focusNode: _dataGridFocusNode, // Focus on DataGrid for keyboard events
onKey: (RawKeyEvent event) {
if (event is RawKeyDownEvent) {
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
// Move to the next row if possible
if (_currentSelectedIndex < _employees.length - 1) {
_currentSelectedIndex++;
_dataGridController.selectedIndex = _currentSelectedIndex;
_dataGridController.scrollToRow(_currentSelectedIndex.toDouble());
_updateTextField('ArrowDown'); // Reflect keypress in TextField
}
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
// Move to the previous row if possible
if (_currentSelectedIndex > 0) {
_currentSelectedIndex--;
_dataGridController.selectedIndex = _currentSelectedIndex;
_dataGridController.scrollToRow(_currentSelectedIndex.toDouble());
_updateTextField('ArrowUp'); // Reflect keypress in TextField
}
} else {
// For any other key pressed, reflect the key in the TextField
_updateTextField(event.logicalKey.keyLabel);
}
}
},
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _textController,
decoration: InputDecoration(labelText: 'Keypresses will show here'),
readOnly: true, // Make the TextField read-only so we control input
),
),
Expanded(
child: SfDataGrid(
source: EmployeeDataSource(_employees),
controller: _dataGridController,
selectionMode: SelectionMode.single,
columns: [
GridColumn(
columnName: 'id',
label: Container(
padding: EdgeInsets.all(8.0),
alignment: Alignment.center,
child: Text('ID'),
),
),
GridColumn(
columnName: 'name',
label: Container(
padding: EdgeInsets.all(8.0),
alignment: Alignment.center,
child: Text('Name'),
),
),
GridColumn(
columnName: 'designation',
label: Container(
padding: EdgeInsets.all(8.0),
alignment: Alignment.center,
child: Text('Designation'),
),
),
],
),
),
],
),
),
);
}

// Update the TextField with the key pressed
void _updateTextField(String key) {
setState(() {
_textController.text = key;
});
}
}

// Data model for the Employee
class Employee {
Employee(this.id, this.name, this.designation);
final int id;
final String name;
final String designation;
}

// DataSource class for DataGrid
class EmployeeDataSource extends DataGridSource {
EmployeeDataSource(this.employees) {
dataGridRows = employees
.map((e) => DataGridRow(cells: [
DataGridCell(columnName: 'id', value: e.id),
DataGridCell(columnName: 'name', value: e.name),
DataGridCell(columnName: 'designation', value: e.designation),
]))
.toList();
}

List dataGridRows = [];
List employees = [];

@OverRide
List get rows => dataGridRows;

@OverRide
DataGridRowAdapter buildRow(DataGridRow row) {
return DataGridRowAdapter(
cells: row.getCells().map((dataGridCell) {
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(8.0),
child: Text(dataGridCell.value.toString()),
);
}).toList(),
);
}
}

// Sample data for employees (30 rows)
List getEmployees() {
return List.generate(30, (index) => Employee(index + 1, 'Employee $index', 'Designation $index'));
}

Proposal

Project.2.mp4

i want basically same pattren like when dialog is open there is one textfield and one gridview what is want is
1- when dialog open default datagridview first row selected. also which key i press it inputing the data into textfield
2- when i click arrow up or arrow down same time my datagridview row focus is moving
3- it also scrolling if arrow is going doing and same time if i press any keyboard keyword same time its also typing and filtering.

@VijayakumarMariappan VijayakumarMariappan added data grid Data grid component open Open labels Oct 17, 2024
@abineshPalanisamy
Copy link

Hi @abdulrehmananwar ,

Based on the details provided, we have modified the sample to meet your expectations. In the updated sample, when you press the up or down arrow keys, the focus remains on the DataGrid, and scrolling occurs according to the current selection. Additionally, the TextField has been implemented to facilitate filtering within the DataGrid.

We’ve included a modified sample 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 18, 2024
@abdulrehmananwar
Copy link
Author

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

void main() {
runApp(const MaterialApp(
home: MyHomePage(),
));
}

class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});

@OverRide
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dialog with DataGrid'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return const DataGridDialog();
},
);
},
child: const Text('Open Dialog'),
),
),
);
}
}

class DataGridDialog extends StatefulWidget {
const DataGridDialog({super.key});

@OverRide
_DataGridDialogState createState() => _DataGridDialogState();
}

class _DataGridDialogState extends State {
final TextEditingController _textController = TextEditingController();
final FocusNode _dataGridFocusNode = FocusNode();
final FocusNode _textFieldFocusNode = FocusNode();
final DataGridController _dataGridController = DataGridController();
late EmployeeDataSource employeeDataSource;
List _employees = [];
int _currentSelectedIndex = 0;

@OverRide
void initState() {
super.initState();
_employees = getEmployees();
employeeDataSource = EmployeeDataSource(_employees);

// Set default selected index and focus on the DataGrid and TextField when dialog opens.
WidgetsBinding.instance.addPostFrameCallback((_) {
  _dataGridController.selectedIndex = _currentSelectedIndex;
  _dataGridController.scrollToRow(_currentSelectedIndex.toDouble());
  _textFieldFocusNode.requestFocus(); // Focus on the TextField
});

}

@OverRide
void dispose() {
_textController.dispose();
_dataGridFocusNode.dispose();
_textFieldFocusNode.dispose();
_dataGridController.dispose();
super.dispose();
}

void _handleKeyEvent(RawKeyEvent event) {
if (event is RawKeyDownEvent) {
// Only intercept arrow keys while TextField is focused
if (_textFieldFocusNode.hasFocus) {
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
_moveSelection(1);
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
// Move the cursor to the end of the TextField when up arrow is pressed
print('going cursor to last');
// Move the cursor to the end of the TextField when up arrow is pressed
Future.delayed(Duration(milliseconds: 5), () {
_textController.selection = TextSelection.fromPosition(
TextPosition(offset: _textController.text.length),
);
});
_moveSelection(-1);
}
}
}
}

void _moveSelection(int offset) {
setState(() {
_currentSelectedIndex += offset;
_currentSelectedIndex = _currentSelectedIndex.clamp(0, _employees.length - 1);
_dataGridController.selectedIndex = _currentSelectedIndex;
_dataGridController.scrollToRow(_currentSelectedIndex.toDouble());
});
}

@OverRide
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('DataGrid Dialog'),
content: SizedBox(
width: 600,
height: 400,
child: RawKeyboardListener(
focusNode: _dataGridFocusNode,
onKey: _handleKeyEvent,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _textController,
focusNode: _textFieldFocusNode,
decoration: const InputDecoration(labelText: 'Type to filter...'),
onChanged: (value) {
// Apply filtering to the name column.
employeeDataSource.clearFilters(columnName: 'name');
if (value.isNotEmpty) {
employeeDataSource.addFilter(
'name',
FilterCondition(
type: FilterType.contains,
filterBehavior: FilterBehavior.stringDataType,
value: value));
}
// To refresh the DataGrid.
employeeDataSource.updateDataGriDataSource();
// Clear selection.
_dataGridController.selectedIndex = -1;
// Reset to the first filtered row.
_currentSelectedIndex = 0;
_dataGridController.selectedIndex = _currentSelectedIndex;
_dataGridController.scrollToRow(_currentSelectedIndex.toDouble());
},
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp("[\n\r]")),
],
),
),
Expanded(
child: SfDataGrid(
source: employeeDataSource,
controller: _dataGridController,
selectionMode: SelectionMode.single,
columnWidthMode: ColumnWidthMode.fill,
columns: [
GridColumn(
columnName: 'id',
label: Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: const Text('ID'),
),
),
GridColumn(
columnName: 'name',
label: Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: const Text('Name'),
),
),
GridColumn(
columnName: 'designation',
label: Container(
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: const Text('Designation'),
),
),
],
),
),
],
),
),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('Close'),
),
],
);
}
}

// Data model for the Employee
class Employee {
Employee(this.id, this.name, this.designation);
final int id;
final String name;
final String designation;
}

// DataSource class for DataGrid
class EmployeeDataSource extends DataGridSource {
EmployeeDataSource(this.employees) {
dataGridRows = employees
.map((e) => DataGridRow(cells: [
DataGridCell(columnName: 'id', value: e.id),
DataGridCell(columnName: 'name', value: e.name),
DataGridCell(columnName: 'designation', value: e.designation),
]))
.toList();
}

List dataGridRows = [];
List employees;

@OverRide
List get rows => dataGridRows;

@OverRide
DataGridRowAdapter buildRow(DataGridRow row) {
return DataGridRowAdapter(
cells: row.getCells().map((dataGridCell) {
return Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: Text(dataGridCell.value.toString()),
);
}).toList(),
);
}

void updateDataGriDataSource() {
notifyListeners();
}
}

// Sample data for employees (30 rows)
List getEmployees() {
return [
Employee(10001, 'James', 'Project Lead'),
Employee(10002, 'Kathryn', 'Manager'),
Employee(10003, 'Liam', 'Software Engineer'),
Employee(10004, 'Michael', 'Designer'),
Employee(10005, 'Martin', 'Developer'),
Employee(10006, 'Newberry', 'Developer'),
Employee(10007, 'Evelyn', 'Quality Assurance'),
Employee(10008, 'Perry', 'Developer'),
Employee(10009, 'Gable', 'Developer'),
Employee(10010, 'Grimes', 'Developer'),
Employee(10011, 'Sophia', 'Data Analyst'),
Employee(10012, 'Isabella', 'UI/UX Designer'),
Employee(10013, 'Jacob', 'Backend Developer'),
Employee(10014, 'Mason', 'Frontend Developer'),
Employee(10015, 'Olivia', 'Scrum Master'),
Employee(10016, 'Emma', 'Project Coordinator'),
Employee(10017, 'Ava', 'Business Analyst'),
Employee(10018, 'William', 'DevOps Engineer'),
Employee(10019, 'Jackson', 'Product Owner'),
Employee(10020, 'Amelia', 'HR Specialist'),
Employee(10021, 'Ethan', 'Database Administrator'),
Employee(10022, 'Lucas', 'Security Analyst'),
Employee(10023, 'Mia', 'Cloud Engineer'),
Employee(10024, 'Elijah', 'Infrastructure Specialist'),
Employee(10025, 'Harper', 'Technical Writer'),
Employee(10026, 'Evelyn', 'Quality Assurance'),
Employee(10027, 'Benjamin', 'Architect'),
Employee(10028, 'Alexander', 'Developer'),
Employee(10029, 'Sebastian', 'Junior Developer'),
Employee(10030, 'Gabriel', 'Intern'),
];
}

I have made significant improvements to the code you provided. However, there’s one remaining issue: when I press the down arrow key, the grid scrolls each time, causing the previous row to become invisible. The scrolling should only occur when the selection reaches the last row, similar to how a standard focused GridView behaves.

@abineshPalanisamy
Copy link

Hi @abdulrehmananwar ,

Based on the information provided, we have modified the sample to meet your requirements. In the SfDataGrid, you can obtain the starting and ending indices of the visible rows using the DataGridController.getVisibleRowStartIndex and DataGridController.getVisibleRowEndIndex methods, respectively, by specifying the required RowRegion. We have restricted scrolling when the down arrow key is pressed, so scrolling will only occur when the selection reaches the last visible row. We have included a sample for your reference. Please review it for further details.

Regards,
Abinesh P

@abdulrehmananwar
Copy link
Author

Thanks for sharing the sample code. However, I'm using this in a Windows app, and when I press the up or down key once, it navigates twice instead of once. Can you help me fix this?

@abdulrehmananwar
Copy link
Author

22.10.2024_17.11.41_REC.mp4

I noticed an issue: when I press the arrow key once, it behaves as if I've pressed it twice.

@abineshPalanisamy
Copy link

Hi @abdulrehmananwar ,

The issue you're encountering, where pressing the down arrow key triggers two actions instead of one, may be due to the KeyboardListener responding to both the key down and key up events. In Flutter, a single key press can generate multiple events: one when the key is pressed down and another when it's released. To address this, modify the event handler to check the type of key event being received, and ensure that you only respond to the key down event.

However, note that the KeyDownEvent does not fire continuously when a key is held down. Flutter triggers the KeyDownEvent only once for a single press. To detect continuous key presses, you also need to handle both KeyDownEvent and KeyRepeatEvent. We have included a sample for your reference. Please review it for further details.

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 waiting for customer response Cannot make further progress until the customer responds.
Projects
None yet
Development

No branches or pull requests

4 participants