diff --git a/src/EventArgs/TableViewStateChangedEventArgs.cs b/src/EventArgs/TableViewStateChangedEventArgs.cs
new file mode 100644
index 00000000..7c442b1e
--- /dev/null
+++ b/src/EventArgs/TableViewStateChangedEventArgs.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace WinUI.TableView;
+
+///
+/// Provides data for the event.
+///
+public class TableViewStateChangedEventArgs : EventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The kind of change that triggered the event.
+ public TableViewStateChangedEventArgs(TableViewStateChangedKind kind)
+ {
+ Kind = kind;
+ }
+
+ ///
+ /// Gets the kind of change that triggered the event.
+ ///
+ public TableViewStateChangedKind Kind { get; }
+}
diff --git a/src/EventArgs/TableViewStateChangedKind.cs b/src/EventArgs/TableViewStateChangedKind.cs
new file mode 100644
index 00000000..7c9c5410
--- /dev/null
+++ b/src/EventArgs/TableViewStateChangedKind.cs
@@ -0,0 +1,22 @@
+namespace WinUI.TableView;
+
+///
+/// Identifies the kind of change that raised a event.
+///
+public enum TableViewStateChangedKind
+{
+ ///
+ /// One or more sort descriptions were added, removed, or cleared.
+ ///
+ Sort,
+
+ ///
+ /// One or more filter descriptions were added, removed, or cleared.
+ ///
+ Filter,
+
+ ///
+ /// A column was reordered, or its width or visibility changed.
+ ///
+ Column,
+}
diff --git a/src/Helpers/TableViewStateHelper.cs b/src/Helpers/TableViewStateHelper.cs
new file mode 100644
index 00000000..cb832dae
--- /dev/null
+++ b/src/Helpers/TableViewStateHelper.cs
@@ -0,0 +1,317 @@
+using Microsoft.UI.Xaml;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace WinUI.TableView.Helpers;
+
+///
+/// Provides methods to capture and apply snapshots,
+/// enabling persistence of a 's sort, filter, and column layout state.
+///
+///
+/// This helper operates exclusively on state that can be round-tripped through serialization.
+/// Runtime-only constructs (such as ) are intentionally
+/// excluded — see the filter-related methods for details. Serialization itself (e.g. JSON) is
+/// the responsibility of the consuming application.
+///
+internal static class TableViewStateHelper
+{
+ ///
+ /// Captures the current sort, filter, and column layout state of
+ /// into a new instance.
+ ///
+ /// The table view whose state should be captured.
+ /// A representing the current state.
+ internal static TableViewState Capture(TableView tableView)
+ {
+ ArgumentNullException.ThrowIfNull(tableView);
+
+ var state = new TableViewState();
+ CaptureSort(tableView, state);
+ CaptureFilter(tableView, state);
+ CaptureColumns(tableView, state);
+ return state;
+ }
+
+ ///
+ /// Applies a previously captured to ,
+ /// restoring its sort, filter, and column layout state.
+ /// Columns are restored first so that ordering is correct before sort and filter are applied.
+ /// Unrecognised column keys are silently skipped; a
+ /// is a no-op.
+ ///
+ /// The target table view.
+ /// The state to apply, or to skip.
+ internal static void Apply(TableView tableView, TableViewState? state)
+ {
+ ArgumentNullException.ThrowIfNull(tableView);
+
+ if (state is null)
+ {
+ return;
+ }
+
+ ApplyColumns(tableView, state.Columns);
+ ApplySort(tableView, state.SortDescriptions);
+ ApplyFilter(tableView, state.FilterDescriptions);
+ }
+
+ // ── Sort ──────────────────────────────────────────────────────────────────
+
+ private static void CaptureSort(TableView tableView, TableViewState state)
+ {
+ foreach (var sd in tableView.SortDescriptions)
+ {
+ state.SortDescriptions.Add(new TableViewSortDescriptionState
+ {
+ PropertyName = sd.PropertyName,
+ Direction = sd.Direction,
+ });
+ }
+ }
+
+ private static void ApplySort(TableView tableView, IEnumerable sortDescriptions)
+ {
+ tableView.SortDescriptions.Clear();
+
+ foreach (var sd in sortDescriptions)
+ {
+ if (string.IsNullOrWhiteSpace(sd.PropertyName))
+ {
+ continue;
+ }
+
+ tableView.SortDescriptions.Add(new SortDescription(sd.PropertyName, sd.Direction));
+ }
+ }
+
+ // ── Filter ────────────────────────────────────────────────────────────────
+ //
+ // FilterDescription.Predicate is a runtime-only lambda and cannot be serialized.
+ // Only the user-selected values from FilterHandler.SelectedValues are captured (as strings).
+ // On restore, the predicate is reconstructed by calling FilterHandler.ApplyFilter,
+ // which rebuilds it from the restored SelectedValues.
+
+ private static void CaptureFilter(TableView tableView, TableViewState state)
+ {
+ foreach (var fd in tableView.FilterDescriptions)
+ {
+ // ColumnFilterDescription (internal) carries a direct column reference; use it
+ // when available to avoid the slower property-name lookup.
+ var column = fd is ColumnFilterDescription cfd
+ ? cfd.Column
+ : FindColumnByPropertyName(tableView, fd.PropertyName);
+
+ if (column is null)
+ {
+ continue;
+ }
+
+ var filterState = new TableViewFilterDescriptionState
+ {
+ ColumnKey = GetColumnKey(column),
+ };
+
+ if (tableView.FilterHandler.SelectedValues.TryGetValue(column, out var selectedValues))
+ {
+ foreach (var value in selectedValues)
+ {
+ filterState.SelectedValues.Add(value?.ToString());
+ }
+ }
+
+ state.FilterDescriptions.Add(filterState);
+ }
+ }
+
+ private static void ApplyFilter(TableView tableView, IEnumerable filterDescriptions)
+ {
+ // Clear all existing column filters before restoring.
+ tableView.FilterHandler?.ClearFilter(null);
+
+ foreach (var filterState in filterDescriptions)
+ {
+ if (string.IsNullOrWhiteSpace(filterState.ColumnKey) || filterState.SelectedValues.Count == 0)
+ {
+ continue;
+ }
+
+ var column = FindColumnByKey(tableView, filterState.ColumnKey);
+ if (column is null)
+ {
+ continue;
+ }
+
+ var targetType = FindColumnValueType(tableView, column);
+ var selectedValues = filterState.SelectedValues
+ .Select(v => ConvertFromString(v, targetType))
+ .ToList