Grid Swiping with Stream as DataSource

Hi support,

I have a bunch of stopwatches in a SFDataGrid.

Updating the values is made through a stream of items attached to the grid datasource.

This works perfect and is fast enough.

Now I want to use swipe actions to change a row, delete it etc.

But refreshing the grid also finishes the swiping, so the user can not swipe.

What can I do?

Regards,

Stephan


11 Replies

SD Sethupathy Devarajan Syncfusion Team May 5, 2026 11:50 AM UTC

Hi Stephan Schrade,

 

Swiping state can not be maintained for column and source collection changes and some other property changes. It's expected behavior in Flutter DataGrid.

 

Could you please disclose why you need to perform swipe action when cell values are updating in time interval?

So that we could provide you with an alternative solution knowing about your expectation if possible.

Regards,
Sethupathy D.



SS Stephan Schrade May 5, 2026 01:08 PM UTC

Hi Sethupathy,

many thanks for your quick answer.

Perhaps I have to give you some more background infos.

About 2 years ago I have developed this app with NetMaui and your SFGrid which worked very well and was super fast. I think this was due to using the Observable Objects-Pattern provided by NetMaui.

See the screenshot netMaui_app_start_dns.png

Swiping is used here to set DNS = did not start

Due to the fact the Microsoft has abandoned VisualStudio from MacOS I do now rewrite the whole app with flutter.

To refresh the stopwatches I use a Stream to populate the grid.

But it looks like the whole grid is rendered every time the Stream provides new values although only the value of the stopwatch does change (but not the other parts of the row). The refresh rate is 100ms which worked well with NetMaui.

I don't know how to achieve a partial rendering of the grid (as seemed was possible with NetMaui)

As I said before, I do use a stream for the source of the grid and provider for state management.

Regards,

Stephan


Attachment: netMaui_app_start_dns_4803d702.png


GR Gowtham Ravi Syncfusion Team May 6, 2026 05:49 AM UTC

Hi Stephan,
Thank you for sharing the details. We understand that you would like to update a specific row or cell value instead of refreshing the entire DataGrid. This can be achieved by using the notifyDataSourceListeners method.
For more information and an example demonstrating how to use this method, please refer to the Knowledge Base article linked below.
We hope this explanation addresses your query. If you require further assistance or if the issue persists, please provide additional details or steps to help us reproduce the problem. Additionally, if possible, share a video recording demonstrating the issue for better clarity.
Providing a simple, reproducible sample along with detailed information will help us investigate the issue more thoroughly and offer a more accurate resolution.
We look forward to your response.
Regards,
Gowtham R


SS Stephan Schrade May 6, 2026 07:59 AM UTC

Hi,

many thanks for this hint.

I think this is best the way to go.

But in your example the refresh is made after a user interaction.

What to do if the source itsself changes automatically?

Like through a periodic api call or because of a periodic timed changing of the source (like in my case).

At the first call I do need the complete content of my view model to draw the whole grid.

Afterwards I only need to redraw on specific column in all of my rows.

Right now I do bind the source of the grid to my stream provider which causes a complete redraw of the grid.

@override
Widget build(BuildContext context) {
TimingProvider timingProvider = Provider.of<TimingProvider>(context);
// var stopwatchesRace = timingProvider.stopwatchesRace;

return Scaffold(
appBar: AppBar(title: Text('Stopwatches')),
body:
Column(
children: <Widget>[
StreamProvider<List<Stopwatchitem>>(
create: (_) => timingProvider.stopwatchesRaceStream,
initialData: [], // Wichtig für den ersten Build
child: StopwatchGrid(),
),
Consumer<AuthProvider>(builder: (context, controller, _) {
return TextButton(
child: const Text("logout"),
onPressed: () {
controller.logoutPressed(context);
}
);
})
]
)
);
}


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

@override
Widget build(BuildContext context) {
const smallTextStyle = TextStyle(fontSize: 14, color: Colors.white);

final data = context.watch<List<Stopwatchitem>>();
if (data.isEmpty) {
return const Center(child: CircularProgressIndicator(color: Color(0xffff6700)));
} else {
final List<Stopwatchitem> stopwatches = data;
StopwatchesDataSource stopwatchesDataSource = StopwatchesDataSource(stopwatches: stopwatches);
return SfDataGrid(
source: stopwatchesDataSource,
verticalScrollPhysics: const ScrollPhysics(), // Scrolling aktivieren
// horizontalScrollPhysics: const NeverScrollableScrollPhysics(),
shrinkWrapRows: true, // alle verfügbaren Rows anzeigen
allowSwiping: true,
swipeMaxOffset: 100.0,
selectionMode: SelectionMode.single,
columnWidthMode: ColumnWidthMode.auto,
headerGridLinesVisibility: GridLinesVisibility.horizontal,
gridLinesVisibility: GridLinesVisibility.horizontal,


So I do need a solution where I use the complete view model for the first call of the grid.

After that I only one column in every needs the update.

Perhaps a stream as the source is not the correct solution, but this attempt makes it easy to update the view model.


This is the first part of the stream

Stream<List<Stopwatchitem>> get stopwatchesRaceStream async* {
SecureStorageService storage = SecureStorageService();
TimecalcHelper timecalcHelper = TimecalcHelper();
timecalcHelper.stationHashStart = await storage.getKey(Constants.TIMING_STATIONHASH_START) ?? '';
timecalcHelper.stationHashFinish = await storage.getKey(Constants.TIMING_STATIONHASH_FINISH) ?? '';
yield* Stream.periodic(Duration(milliseconds: 100), (_) async {
List<ParticipantsTblData> participants = await db.fetchAllParticipants();
List<Stopwatchitem> stopwatches = <Stopwatchitem>[];

for (int i=0; i<participants.length; i++) {
String racegroup = participants[i].race;


Can you please give more detailed specifics for this kind of problem?

additional remarks:

I don't need to stick to provider as the state management pattern.

If there is another approach I'm fine with that.

Regards,

Stephan



GR Gowtham Ravi Syncfusion Team May 6, 2026 10:32 AM UTC

Hi Stephan,
Thank you again for your detailed description. As a runnable sample was not provided, we created a simple example based on the shared details, where only a specific column is updated periodically.
Upon reviewing your code, we observed that the issue mainly stemmed from how the data stream was integrated with the SfDataGrid. In your implementation, the grid was bound using a StreamProvider<List<Stopwatchitem>>, and context.watch<List<Stopwatchitem>>() was used to access the data. This caused the entire widget, including the grid, to rebuild whenever the stream emitted new data.
Since the stream updates frequently (e.g., every 100 milliseconds), this led to continuous full rebuilds of the grid. Additionally, each rebuild created a new instance of StopwatchesDataSource, causing the grid to treat the data as fully replaced rather than incrementally updated, which affected rendering performance.
To address this, we modified the approach by initializing the DataGridSource only once during the initial data load and maintaining it throughout the widget’s lifecycle. Instead of using StreamProvider for UI updates, we used a StreamSubscription. On the first emission, the grid is populated with the full dataset. For subsequent updates, only the required fields in existing rows are updated. After updating, calling notifyListeners() on the data source ensures that only the affected cells are refreshed.
This approach avoids full grid rebuilds and enables efficient incremental updates, resulting in improved performance and smoother UI behavior, even with frequent data changes.

We have included a sample and a video demo for your reference. Kindly refer to them for further guidance. If the issue still persists, could you please modify the attached sample to demonstrate the issue?
Regards,
Gowtham R

Attachment: SfDataGrid_4d8134b0.zip


SS Stephan Schrade May 12, 2026 02:38 PM UTC

Hi,

many thanks for the detailed answer!

I managed to change my code.

Here ist my data source:

class TimingService {

/*
final List<Stopwatchitem> _data = List.generate(
2,
(i) => Stopwatchitem(
timingpartid: 1,
timingid: 0,
raceorder: 0,
race: '',
racegroup: '',
raceheatindex: '',
heat: 0,
subheat: 0,
lane: 0,
startno: '',
startgroup: '',
club: '',
crew: '',
targetDuration: Duration.zero,
targetDurationString: '',
targetPercent: 0.0,
raceTime: Duration.zero,
raceTimeString: '',
// raceTimeString: '${now.minute}:${now.second},${now.millisecond}',
stopwatchState: 0,
raceEndAbsolute: DateTime.now(),
raceEndTimingtimestampid: 0,
place: 0
),
);

Future<List<Stopwatchitem>> get stopwatchesRaceInitial async {
SecureStorageService storage = SecureStorageService();
TimecalcHelper timecalcHelper = TimecalcHelper();
timecalcHelper.stationHashStart = await storage.getKey(Constants.TIMING_STATIONHASH_START) ?? '';
timecalcHelper.stationHashFinish = await storage.getKey(Constants.TIMING_STATIONHASH_FINISH) ?? '';
List<ParticipantsTblData> participants = await db.fetchAllParticipants();
List<Stopwatchitem> stopwatches = <Stopwatchitem>[];

for (int i=0; i<participants.length; i++) {
String racegroup = participants[i].race;
racegroup += (participants[i].heat != 0) ? ' - heat ${participants[i].heat}' : "";
Duration targetDuration = TimecalcHelper.iso8601ToDuration(participants[i].targetdurationiso8601);
TimeCalcResult timeCalcResult = await timecalcHelper.getRaceTimeOfParticipant(participants[i].timingpartid);
// DateTime now = DateTime.now().toUtc();
stopwatches.add(Stopwatchitem(
timingpartid: participants[i].timingpartid,
timingid: participants[i].timingid,
raceorder: participants[i].raceorder,
race: participants[i].race,
racegroup: racegroup,
raceheatindex: '${participants[i].race}%%${participants[i].heat}',
heat: participants[i].heat,
subheat: participants[i].subheat,
lane: participants[i].lane,
startno: participants[i].startno,
startgroup: participants[i].startgroup,
club: participants[i].club,
crew: participants[i].crew,
targetDuration: targetDuration,
targetDurationString: TimecalcHelper.getDurationAsString(targetDuration),
targetPercent: timeCalcResult.raceTargetPercent,
raceTime: timeCalcResult.raceTime,
raceTimeString: timeCalcResult.raceTimeString,
// raceTimeString: '${now.minute}:${now.second},${now.millisecond}',
stopwatchState: timeCalcResult.raceState,
raceEndAbsolute: timeCalcResult.raceEndAbsolute,
raceEndTimingtimestampid: timeCalcResult.raceEndTimingtimestampid,
place: 0
));
}
return stopwatches;
}

*/

Stream<List<Stopwatchitem>> get stopwatchesRaceStream async* {
SecureStorageService storage = SecureStorageService();
TimecalcHelper timecalcHelper = TimecalcHelper();
timecalcHelper.stationHashStart = await storage.getKey(Constants.TIMING_STATIONHASH_START) ?? '';
timecalcHelper.stationHashFinish = await storage.getKey(Constants.TIMING_STATIONHASH_FINISH) ?? '';
yield* Stream.periodic(Duration(milliseconds: 100), (_) async {
List<ParticipantsTblData> participants = await db.fetchAllParticipants();
List<Stopwatchitem> stopwatches = <Stopwatchitem>[];

for (int i=0; i<participants.length; i++) {
String racegroup = participants[i].race;
racegroup += (participants[i].heat != 0) ? ' - heat ${participants[i].heat}' : "";
Duration targetDuration = TimecalcHelper.iso8601ToDuration(participants[i].targetdurationiso8601);
TimeCalcResult timeCalcResult = await timecalcHelper.getRaceTimeOfParticipant(participants[i].timingpartid);
// DateTime now = DateTime.now().toUtc();
stopwatches.add(Stopwatchitem(
timingpartid: participants[i].timingpartid,
timingid: participants[i].timingid,
raceorder: participants[i].raceorder,
race: participants[i].race,
racegroup: racegroup,
raceheatindex: '${participants[i].race}%%${participants[i].heat}',
heat: participants[i].heat,
subheat: participants[i].subheat,
lane: participants[i].lane,
startno: participants[i].startno,
startgroup: participants[i].startgroup,
club: participants[i].club,
crew: participants[i].crew,
targetDuration: targetDuration,
targetDurationString: TimecalcHelper.getDurationAsString(targetDuration),
targetPercent: timeCalcResult.raceTargetPercent,
raceTime: timeCalcResult.raceTime,
raceTimeString: timeCalcResult.raceTimeString,
// raceTimeString: '${now.minute}:${now.second},${now.millisecond}',
stopwatchState: timeCalcResult.raceState,
raceEndAbsolute: timeCalcResult.raceEndAbsolute,
raceEndTimingtimestampid: timeCalcResult.raceEndTimingtimestampid,
place: 0
));
}
return stopwatches;
}).asyncMap((event) => event);
}
}

New data comes from the TimecalcHelper which gets the values from the internal database.


And this is the page:

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

@override
State<StopwatchTimetrialStartPage> createState() => _StopwatchTimetrialStartPage();
}

class _StopwatchTimetrialStartPage extends State<StopwatchTimetrialStartPage> {
final TimingService service = TimingService();

StopwatchesDataSource? _dataSource;
StreamSubscription? _sub;

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

_sub = service.stopwatchesRaceStream.listen((data) {
if (_dataSource == null) {
/// ✅ FIRST LOAD → full grid build
_dataSource = StopwatchesDataSource(data);
setState(() {});
} else {
/// ✅ SUBSEQUENT UPDATES → only update column
_dataSource!.updateData(data);
}
});
}

@override
void dispose() {
_sub?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
const smallTextStyle = TextStyle(fontSize: 14, color: Colors.white);

if (_dataSource == null) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
return Scaffold(
appBar: AppBar(title: Text('Stopwatches')),
body:
Column(
children: <Widget>[
Container(
height: 250, // to test Grid scrolling
child:
SfDataGrid(
source: _dataSource!,
verticalScrollPhysics: const ScrollPhysics(), // Scrolling aktivieren
// horizontalScrollPhysics: const NeverScrollableScrollPhysics(),
shrinkWrapRows: true, // show all available rows
allowSwiping: true,
swipeMaxOffset: 100.0,
selectionMode: SelectionMode.single,
columnWidthMode: ColumnWidthMode.auto,
headerGridLinesVisibility: GridLinesVisibility.horizontal,
gridLinesVisibility: GridLinesVisibility.horizontal,
headerRowHeight: 0,
rowHeight: 86,

columns: <GridColumn>[
GridColumn(
columnName: 'timingpartid',
label: Text(''),
visible: false,
),
GridColumn(
columnName: 'stopwatchstate',
label: Text(''),
visible: false,
),
GridColumn(
columnName: 'startno',
label: Text(''),
),
GridColumn(
columnName: 'partdata',
width: 140,
label: Text(''),
),
GridColumn(
columnName: 'racetime',
width: 170,
label: Text(''),
),
],

placeholder: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.thumb_down_alt_outlined, size: 30),
SizedBox(height: 8),
Text('keine Daten vorhanden', style: TextStyle(fontSize: 16),),
],
),
),

onCellTap: (details) async {
debugPrint(details.rowColumnIndex.columnIndex.toString());
if (details.rowColumnIndex.rowIndex != 0) {
DataGridRow row = _dataSource!.effectiveRows[details.rowColumnIndex.rowIndex - 1];
debugPrint(row.getCells()[0].value.toString());
final int timingpartId = int.parse(row.getCells()[0].value.toString());
final int stopwatchState = int.parse(row.getCells()[1].value.toString());
if (stopwatchState == Constants.STOPWATCH_STATE_WAITING_FOR_START) {
await startStopwatch(timingpartId);
}
}
},

onSwipeStart: (details) {
if (details.swipeDirection == DataGridRowSwipeDirection.startToEnd) {
details.setSwipeMaxOffset(0);
} else if (details.swipeDirection == DataGridRowSwipeDirection.endToStart) {
details.setSwipeMaxOffset(100.0);
}
return true;
},

endSwipeActionsBuilder:
(BuildContext context, DataGridRow row, int rowIndex) {
return GestureDetector(
onTap: () async {
debugPrint(rowIndex.toString());
debugPrint(_dataSource!.rows[rowIndex].getCells().elementAt(0).value.toString());
await setStopwatchToDNS(int.parse(_dataSource!.rows[rowIndex].getCells().elementAt(0).value.toString()));
},
child: const ColoredBox(
color: Colors.redAccent,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.person_off, size: 40, color:Colors.white),
Text('DNS', style: smallTextStyle),
],
)
)
);
}
),
),
Consumer<AuthProvider>(builder: (context, controller, _) {
return TextButton(
child: const Text("logout"),
onPressed: () {
controller.logoutPressed(context);
}
);
})
]
)
);
}
}
class StopwatchesDataSource extends DataGridSource {
final dataTextStyle = const TextStyle(fontSize: 17, color: Colors.white, fontWeight: FontWeight.w400);
final disciplineTextStyle = const TextStyle(fontSize: 17, color: Color(0xffff6700), fontWeight: FontWeight.w700);
final nameTextStyle = TextStyle(fontSize: 14, color: const Color.fromARGB(255, 75, 40, 40));
final followerstatusTextStyle = TextStyle(fontSize: 16, color: Colors.white70, fontWeight: FontWeight.w600);
final amountdataTextStyle = TextStyle(fontSize: 16, color: Colors.white70, fontWeight: FontWeight.w600);
final activityTextStyle = TextStyle(fontSize: 16, color: Colors.white70, fontWeight: FontWeight.w600);
final buttonTextStyle = TextStyle(fontSize: 14, color: Color(0xffff6700), fontWeight: FontWeight.w600);
final startnoTextStyle = TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.w800);
final caption1TextStyle = TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w800);
final caption2TextStyle = TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.w400);
final raceTextStyle = TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.w400);
final targetdurationTextStyle = TextStyle(fontSize: 14, color: Colors.white, fontWeight: FontWeight.w400);
final infoTextStyle = TextStyle(fontSize: 20, color: Colors.white70, fontFamily: 'Courier', fontWeight: FontWeight.w800);
final racetimeStyle = TextStyle(fontSize: 48, color: Color(0xffff6700), fontFamily: 'HelveticaNeue-Thin', fontWeight: FontWeight.w400);

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

List<DataGridRow> _rows = [];

StopwatchesDataSource(List<Stopwatchitem> data) {
_rows = _buildRows(data);
}

List<DataGridRow> _buildRows(List<Stopwatchitem> data) {
return data.map<DataGridRow>(
(dataGridRow) => DataGridRow(
cells: [
DataGridCell<int>(columnName: 'timingpartid', value: dataGridRow.timingpartid),
DataGridCell<int>(columnName: 'stopwatchstate', value: dataGridRow.stopwatchState),
DataGridCell<String>(columnName: 'startno', value: dataGridRow.startno),
DataGridCell<Map<String, String>>(columnName: 'partdata', value: {
'club': dataGridRow.club,
'crew': dataGridRow.crew,
'race': dataGridRow.race,
'targetduration': dataGridRow.targetDurationString,
}),
DataGridCell<String>(columnName: 'racetime', value: dataGridRow.raceTimeString),
]))
.toList();
}

/// ✅ ONLY update time column
void updateData(List<Stopwatchitem> newData) {
for (int i = 0; i < newData.length; i++) {
_rows[i].getCells()[4] = DataGridCell<String>(
columnName: 'racetime',
value: newData[i].raceTimeString,
);
}

notifyListeners(); // ✅ partial refresh
}

@override
DataGridRowAdapter? buildRow(DataGridRow row) {
Color getRowBackgroundColor() {
final int index = effectiveRows.indexOf(row);
if (index % 2 != 0) {
return const Color.fromARGB(255, 0, 47, 108);
}
return const Color(0xff001e46);
}

// Radius only at full left and full right
BorderRadius getBorderRadius(String columnName) {
if (columnName == 'startno') {
return const BorderRadius.only(
topLeft: Radius.circular(15),
bottomLeft: Radius.circular(15),
);
}
if (columnName == 'racetime') {
return const BorderRadius.only(
topRight: Radius.circular(15),
bottomRight: Radius.circular(15),
);
}
return BorderRadius.zero;
}

return DataGridRowAdapter(
cells: row.getCells().map<Widget>((cellData) {
switch (cellData.columnName) {
case 'startno':
return DecoratedBox(
decoration : BoxDecoration(
borderRadius: getBorderRadius(cellData.columnName),
color: getRowBackgroundColor(),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
CircleAvatar(radius: 30, backgroundColor: Colors.grey, child: Text(cellData.value, style: startnoTextStyle,), ),
],
),
);
case 'partdata':
final Map<String, String> values = cellData.value as Map<String, String>;
String caption1 = '';
String caption2 = '';
if (values['club'] != '') {
caption1 = values['club']!;
caption2 = values['crew']!;
} else {
caption1 = values['crew']!;
caption2 = '';
}
return DecoratedBox(
decoration : BoxDecoration(
borderRadius: getBorderRadius(cellData.columnName),
color: getRowBackgroundColor(),
),
child: Padding(
padding: const EdgeInsets.only(top: 12, bottom: 16, left: 4, right: 1),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Row(children: [
Expanded(child: Text(caption1, style: caption1TextStyle, overflow: TextOverflow.ellipsis)),
],),
if (caption2 != '') Row(children: [
Expanded(child: Text(caption2, style: caption2TextStyle, overflow: TextOverflow.ellipsis)),
],),
Row(children: [
Text(values['race']!, style: raceTextStyle),
Text(values['targetduration']!, style: targetdurationTextStyle),
],),
],
),
),
);
case 'racetime':
return DecoratedBox(
decoration : BoxDecoration(
borderRadius: getBorderRadius(cellData.columnName),
color: getRowBackgroundColor(),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(cellData.value, style: racetimeStyle),
],
),
);
default:
return Text('-');
}
}).toList());
}

}


Unfortunately swiping still does not work.

Is there any way to get this working or is swiping not available with this refresh rate?

Or ist there still an error in my code?

Regards,

Stephan



GR Gowtham Ravi Syncfusion Team May 13, 2026 07:17 AM UTC

Hi Stephan,
Thank you for sharing the details. Upon reviewing your code, we observed that the continuous use of notifyListeners() to update the data is causing the swipe gesture to break. By default, the Flutter SfDataGrid automatically closes a swiped row whenever the grid is refreshed. Since your grid refreshes every 100 ms, the swiped row immediately resets to its original position.

To meet your requirement of keeping a row open after a swipe until it is manually reset, we have implemented a workaround by temporarily stopping updates during the swipe action. This is achieved by introducing a Boolean property to track whether a swipe is currently active.
Based on the information you provided, we have created a simple sample and included a video demonstration for your reference. Kindly review them for further guidance.
Please let us know if you need any additional assistance.
Regards,
Gowtham R

Attachment: SfDataGrid_1204bbc2.zip


SS Stephan Schrade May 23, 2026 08:23 PM UTC

Hi support,

many thanks for your solution.

This works!

Is there a way to detect if a user swiped accidentially and did not press the DNS button and swipes back again?

Then "pauseUpdates" should be reset to false.

I tried several combinations with onSwipeUpdate and onSwipeEnd but did not mange to get this working.

Many Thanks,

Stephan



GR Gowtham Ravi Syncfusion Team May 25, 2026 09:23 AM UTC

Hi Stephan,
Thank you for sharing the details. We have analyzed your query. Your requirement to set pauseUpdates to false when the swiped row returns to its original position can be achieved at the sample level itself.
As you mentioned, the Flutter SfDataGrid provides the onSwipeUpdate and onSwipeEnd callbacks. These can be used to obtain the swipeOffset and swipeDirection of the currently swiped row. Using this information, you can implement the required logic within these callbacks to meet your scenario.
 
For more details on swiping, please refer to the User Guide link below:
For your reference, we have included a sample and a video demo. Kindly review them for further guidance.
Regards,
Gowtham R

Attachment: SfDataGrid_b8ef6988.zip


SS Stephan Schrade May 26, 2026 09:47 AM UTC

Hi support,

many thanks!

Your solution now works in my app without problems.

Regards,

Stephan



GR Gowtham Ravi Syncfusion Team May 27, 2026 10:39 AM UTC

Hi Stephan,

We are glad to know that the reported problem has been resolved at your end. Please let us know if you have any further queries. We are happy to help.

Regards,
Gowtham R

Loader.
Up arrow icon