├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── cd-build.yml │ └── ci-build.yml ├── .gitignore ├── LICENSE.md ├── README.md └── src ├── ColumnFilterHandler.cs ├── Columns ├── TableViewBoundColumn.cs ├── TableViewCheckBoxColumn.cs ├── TableViewColumn.cs ├── TableViewComboBoxColumn.cs ├── TableViewDateColumn.cs ├── TableViewNumberColumn.cs ├── TableViewTemplateColumn.cs ├── TableViewTextColumn.cs ├── TableViewTimeColumn.cs └── TableViewToggleSwitchColumn.cs ├── Controls ├── TableViewDatePicker.cs └── TableViewTimePicker.cs ├── Converters └── BoolToVisibilityConverter.cs ├── EventArgs ├── TableViewAutoGeneratingColumnEventArgs.cs ├── TableViewCellContextFlyoutEventArgs.cs ├── TableViewCellSelectionChangedEvenArgs.cs ├── TableViewClearSortingEventArgs.cs ├── TableViewColumnPropertyChangedEventArgs.cs ├── TableViewCopyToClipboardEventArgs.cs ├── TableViewExportContentEventArgs.cs ├── TableViewRowContextFlyoutEventArgs.cs └── TableViewSortingEventArgs.cs ├── Extensions ├── CollectionExtensions.cs ├── DateTimeExtensions.cs ├── ItemIndexRangeExtensions.cs ├── ObjectExtensions.cs ├── TableViewCellSlotExtensions.cs └── TypeExtensions.cs ├── Helpers ├── CollectionChangedListener.cs ├── DateTimeFormatHelper.cs └── KeyboardHelper.cs ├── IColumnFilterHandler.cs ├── ItemsSource ├── CollectionView.Deffer.cs ├── CollectionView.Events.cs ├── CollectionView.Properties.cs ├── CollectionView.cs ├── ColumnFilterDescription.cs ├── ColumnSortDescription.cs ├── FilterDescription.cs ├── SortDescription.cs ├── SortDirection.cs └── VectorChangedEventArgs.cs ├── Strings ├── de-DE │ └── WinUI.TableView.resw ├── en-US │ └── WinUI.TableView.resw ├── es-ES │ └── WinUI.TableView.resw └── ru-RU │ └── WinUI.TableView.resw ├── TableView.Properties.cs ├── TableView.cs ├── TableViewCell.cs ├── TableViewCellSlot.cs ├── TableViewCellsPresenter.cs ├── TableViewColumnHeader.FilterItem.cs ├── TableViewColumnHeader.OptionsFlyoutViewModel.cs ├── TableViewColumnHeader.cs ├── TableViewColumnsCollection.cs ├── TableViewCornerButtonMode.cs ├── TableViewGridLinesVisibility.cs ├── TableViewHeaderRow.OptionsFlyoutViewModel.cs ├── TableViewHeaderRow.cs ├── TableViewLocalizedStrings.cs ├── TableViewRow.cs ├── TableViewSelectionUnit.cs ├── Tableview.Uno.cs ├── Themes ├── Generic.xaml ├── Resources.xaml ├── TableView.xaml ├── TableViewCell.xaml ├── TableViewCellsPresenter.xaml ├── TableViewColumnHeader.xaml ├── TableViewHeaderRow.xaml ├── TableViewRow.xaml └── TableViewTimePicker.xaml ├── VisualStates.cs ├── WinUI.TableView.csproj ├── WinUI.TableView.sln └── global.json /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Description: 11 | A clear and concise description of what the bug is. 12 | 13 | ### Steps to Reproduce: 14 | Steps to reproduce the behavior. 15 | 16 | ### Expected behavior: 17 | A clear and concise description of what you expected to happen. 18 | 19 | ### Screenshots: 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | ### Environment: 23 | - Package Version: 24 | - WinAppSDK Version: 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/cd-build.yml: -------------------------------------------------------------------------------- 1 | name: cd-build 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: windows-2022 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4.1.4 14 | 15 | - name: Setup MSBuild.exe 16 | uses: microsoft/setup-msbuild@v2 17 | 18 | - name: 'Get Version' 19 | id: version 20 | uses: battila7/get-version-action@v2.2.1 21 | 22 | - name: Build 23 | run: | 24 | msbuild /restore ` 25 | /t:Build,Pack src/WinUI.TableView.csproj ` 26 | /p:Configuration=Release ` 27 | /p:Version=${{ steps.version.outputs.version-without-v }} ` 28 | /p:PackageReleaseNotes="https://github.com/w-ahmad/WinUI.TableView/releases/tag/v${{ steps.version.outputs.version-without-v }}" 29 | 30 | - name: Upload artifacts 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: NuGet Packages 34 | path: artifacts/NuGet/Release 35 | 36 | - name: Push to NuGet 37 | run: | 38 | dotnet nuget push artifacts\NuGet\Release\*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate 39 | dotnet nuget push artifacts\NuGet\Release\*.snupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate 40 | -------------------------------------------------------------------------------- /.github/workflows/ci-build.yml: -------------------------------------------------------------------------------- 1 | name: ci-build 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | branches: main 8 | 9 | jobs: 10 | build: 11 | runs-on: windows-2022 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4.1.4 15 | 16 | - name: Setup MSBuild.exe 17 | uses: microsoft/setup-msbuild@v2 18 | 19 | - name: Build 20 | run: | 21 | msbuild /restore ` 22 | /t:Build,Pack src/WinUI.TableView.csproj ` 23 | /p:Configuration=Release ` 24 | /p:Version=1.0.${{ github.run_number }} 25 | 26 | - name: Upload artifacts 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: NuGet Packages 30 | path: artifacts/NuGet/Release 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### MIT License 2 | 3 | Copyright (c) [2024] [w-ahmad https://github.com/w-ahmad] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # WinUI.TableView 3 | 4 | **WinUI.TableView** is a lightweight and fast data grid control made for [WinUI](https://learn.microsoft.com/en-us/windows/apps/winui/winui3) apps and now supports the [Uno Platform](https://platform.uno/docs/index.html). It is easy to use, and capable of handling large numbers of items with a focus on performance. It's derived from `ListView` so you will experience fluent look and feel in your project. It comes with all the essential features you need, plus extras like an Excel like column filter, options buttons (for columns and the TableView) and easy data export. 5 | 6 | [![ci-build](https://github.com/w-ahmad/WinUI.TableView/actions/workflows/ci-build.yml/badge.svg)](https://github.com/w-ahmad/WinUI.TableView/actions/workflows/ci-build.yml) 7 | [![cd-build](https://github.com/w-ahmad/WinUI.TableView/actions/workflows/cd-build.yml/badge.svg)](https://github.com/w-ahmad/WinUI.TableView/actions/workflows/cd-build.yml) 8 | [![nuget](https://img.shields.io/nuget/v/WinUI.TableView)](https://www.nuget.org/packages/WinUI.TableView/) 9 | [![nuget](https://img.shields.io/nuget/dt/WinUI.TableView)](https://www.nuget.org/packages/WinUI.TableView/) 10 | 11 | ### [SampleApp](https://github.com/w-ahmad/WinUI.TableView.SampleApp) 12 | 13 | ![WinUI TableView SampleApp](https://raw.githubusercontent.com/w-ahmad/WinUI.TableView.SampleApp/main/WinUI.TableView%20SampleApp.gif) 14 | 15 | ## Features 16 | 17 | - **Auto-generating Columns**: Automatically generate columns based on the data source. 18 | - **Individual cell selection**: You can select any cell for the ease of access and better editing experience. 19 | - **Copy row or cell content**: TableView allows you to copy rows or cells content, with the option to include or exclude column headers. 20 | - **Editing**: Modify cell content directly within the TableView by double tapping on a cell. 21 | - **Sorting**: Offers built in column sorting. 22 | - **Excel-like Column Filter**: TableView allows you to filter data within columns with an excel like flyout to enhance data exploration and analysis. 23 | - **Export functionality**: Built-in export functionality to export data to CSV format. This feature can be enabled by setting the `ShowExportOptions = true`. 24 | - **Grid Lines**: Display grid lines to improve data visibility and organization. 25 | - **Localization**: Support for multiple languages to enhance usability for global audiences. 26 | - **Alternate Row Colors**: Apply alternate row coloring for better readability and aesthetics. 27 | - **Cell & Row Context Flyout**: Provides context flyouts for cells and rows to allow quick actions. *(not available on uno)* 28 | 29 | ## Uno Platform Support 30 | 31 | `WinUI.TableView` is compatible with the Uno Platform(WASM, Desktop), enabling you to use the control across multiple platforms. 32 | 33 | ## Getting Started 34 | 35 | ### 1. Create a New WinUI3 or Uno Project 36 | 37 | If you don't already have a WinUI 3 project or Uno prject, create one in Visual Studio. 38 | 39 | ### 2: Install NuGet Package 40 | Inatall `WinUI.TableView` NuGet package to your app with your preferred method. Here is the one using NuGet Package Manager: 41 | 42 | ```bash 43 | Install-Package WinUI.TableView 44 | ``` 45 | ### 3. Add `WinUI.TableView` to Your XAML 46 | 47 | In your `MainWindow.xaml`, add the `WinUI.TableView` control: 48 | 49 | ```xaml 50 | 59 | 60 | 61 | 63 | 64 | 65 | ``` 66 | 67 | ### 4. Bind Data to `TableView` 68 | 69 | In your `MainPage.xaml.cs`, set up the data context and bind data to the `TableView`: 70 | 71 | ```csharp 72 | public sealed partial class MainWindow : Window 73 | { 74 | public MainViewModel ViewModel { get; } = new MainViewModel(); 75 | 76 | public MainWindow() 77 | { 78 | this.InitializeComponent(); 79 | } 80 | } 81 | ``` 82 | 83 | Create a simple `MainViewModel` with a collection of items to display: 84 | 85 | ```csharp 86 | public class MainViewModel 87 | { 88 | public ObservableCollection Items { get; set; } 89 | 90 | public MainViewModel() 91 | { 92 | Items = new ObservableCollection 93 | { 94 | new Item { Name = "Item 1", Price = 10.0, Quantity = 1 }, 95 | new Item { Name = "Item 2", Price = 15.0, Quantity = 2 }, 96 | new Item { Name = "Item 3", Price = 20.0, Quantity = 3 }, 97 | // Add more items here 98 | }; 99 | } 100 | } 101 | 102 | public class Item : INotifyPropertyChanged 103 | { 104 | private string _name; 105 | private double _price; 106 | private int _quantity; 107 | 108 | public string Name 109 | { 110 | get => _name; 111 | set 112 | { 113 | _name = value; 114 | OnPropertyChanged(nameof(Name)); 115 | } 116 | } 117 | public double Price 118 | { 119 | get => _price; 120 | set 121 | { 122 | _price = value; 123 | OnPropertyChanged(nameof(Price)); 124 | } 125 | } 126 | public int Quantity 127 | { 128 | get => _quantity; 129 | set 130 | { 131 | _quantity = value; 132 | OnPropertyChanged(nameof(Quantity)); 133 | } 134 | } 135 | 136 | private void OnPropertyChanged(string propertyName) 137 | { 138 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 139 | } 140 | 141 | public event PropertyChangedEventHandler PropertyChanged; 142 | } 143 | ``` 144 | 145 | ### 5. Run Your Application 146 | 147 | Build and run your application. You should see the `TableView` populated with the rows and cells from your `ViewModel`. 148 | 149 | ## Customization 150 | 151 | You can customize the appearance and behavior of the `TableView` by modifying its properties, templates, and styles. For example: 152 | 153 | - **Column Customization**: Define custom columns based on data types. 154 | - **Is ReadOnly**: You can make any column or the TableView itself read only. 155 | - **Sorting and Filtering**: Enable sorting and filtering on specific columns or for the all columns. 156 | - **Corner Button Mode**: Use the `CornerButtonMode` property to configure the corner button's behavior. You can select from: 157 | - `None`: No corner button. 158 | - `SelectAll`: Displays a "Select All" button. 159 | - `Options`: Displays an options menu. 160 | - **Column Header and Cell Styles**: Customize the styles for column headers and cells to match your application's theme or specific design requirements. 161 | 162 | ```xml 163 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | ``` 185 | 186 | ### Available Column Types 187 | 1. TableViewTextColumn 188 | 2. TableViewCheckBoxColumn 189 | 3. TableViewComboBoxColumn 190 | 4. TableViewNumberColumn 191 | 5. TableViewToggleSwitchColumn 192 | 6. TableViewTemplateColumn 193 | 7. TableViewTimeColumn 194 | 8. TableViewDateColumn 195 | 196 | ## Contributing 197 | 198 | Contributions are welcome from the community! If you find any issues or have suggestions for improvements, please submit them through the GitHub issue tracker or consider making a pull request. 199 | 200 | #### Contributors 201 | [![ci-build](https://contrib.rocks/image?repo=w-ahmad/WinUI.TableView)](https://github.com/w-ahmad/WinUI.TableView/graphs/contributors) 202 | 203 | 204 | ## License 205 | 206 | This project is licensed under the [MIT License](https://github.com/w-ahmad/WinUI.TableView?tab=MIT-1-ov-file). 207 | -------------------------------------------------------------------------------- /src/ColumnFilterHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using WinUI.TableView.Extensions; 5 | 6 | namespace WinUI.TableView; 7 | 8 | /// 9 | /// Default implementation of the IColumnFilterHandler interface. 10 | /// 11 | public class ColumnFilterHandler : IColumnFilterHandler 12 | { 13 | private readonly TableView _tableView; 14 | 15 | /// 16 | /// Initializes a new instance of the ColumnFilterHandler class. 17 | /// 18 | public ColumnFilterHandler(TableView tableView) 19 | { 20 | _tableView = tableView; 21 | } 22 | 23 | public virtual IList GetFilterItems(TableViewColumn column, string? searchText = default) 24 | { 25 | if (column is { TableView.ItemsSource: { } }) 26 | { 27 | var collectionView = new CollectionView(column.TableView.ItemsSource); 28 | collectionView.FilterDescriptions.AddRange( 29 | column.TableView.FilterDescriptions.Where( 30 | x => x is not ColumnFilterDescription columnFilter || columnFilter.Column != column)); 31 | 32 | var filterValues = new SortedSet(); 33 | 34 | foreach (var item in collectionView) 35 | { 36 | var value = column.GetCellContent(item); 37 | filterValues.Add(value); 38 | } 39 | 40 | return filterValues.Select(value => 41 | { 42 | value = string.IsNullOrWhiteSpace(value?.ToString()) ? TableViewLocalizedStrings.BlankFilterValue : value; 43 | var isSelected = !column.IsFiltered || !string.IsNullOrEmpty(searchText) || 44 | (column.IsFiltered && SelectedValues[column].Contains(value)); 45 | 46 | return string.IsNullOrEmpty(searchText) 47 | || value?.ToString()?.Contains(searchText, StringComparison.OrdinalIgnoreCase) == true 48 | ? new TableViewFilterItem(isSelected, value) 49 | : null; 50 | 51 | }).OfType() 52 | .ToList(); 53 | } 54 | 55 | return []; 56 | } 57 | 58 | public virtual void ApplyFilter(TableViewColumn column) 59 | { 60 | if (column is { TableView: { } }) 61 | { 62 | column.TableView.DeselectAll(); 63 | 64 | if (column.IsFiltered) 65 | { 66 | column.TableView.RefreshFilter(); 67 | } 68 | else 69 | { 70 | var boundColumn = column as TableViewBoundColumn; 71 | 72 | column.IsFiltered = true; 73 | column.TableView.FilterDescriptions.Add(new ColumnFilterDescription( 74 | column, 75 | boundColumn?.PropertyPath, 76 | (o) => Filter(column, o))); 77 | } 78 | column.TableView.RefreshFilter(); 79 | column.TableView.EnsureAlternateRowColors(); 80 | } 81 | } 82 | 83 | public virtual void ClearFilter(TableViewColumn? column) 84 | { 85 | if (column is { TableView: { } }) 86 | { 87 | column.IsFiltered = false; 88 | column.TableView.FilterDescriptions.RemoveWhere(x => x is ColumnFilterDescription columnFilter && columnFilter.Column == column); 89 | SelectedValues.RemoveWhere(x => x.Key == column); 90 | column.TableView.RefreshFilter(); 91 | } 92 | else 93 | { 94 | SelectedValues.Clear(); 95 | _tableView.FilterDescriptions.Clear(); 96 | 97 | foreach (var col in _tableView.Columns) 98 | { 99 | if (col is not null) 100 | { 101 | col.IsFiltered = false; 102 | } 103 | } 104 | } 105 | } 106 | 107 | public virtual bool Filter(TableViewColumn column, object? item) 108 | { 109 | var value = column.GetCellContent(item); 110 | value = string.IsNullOrWhiteSpace(value?.ToString()) ? TableViewLocalizedStrings.BlankFilterValue : value; 111 | return SelectedValues[column].Contains(value); 112 | } 113 | 114 | public IDictionary> SelectedValues { get; } = new Dictionary>(); 115 | } 116 | -------------------------------------------------------------------------------- /src/Columns/TableViewBoundColumn.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Data; 3 | using System; 4 | using System.Reflection; 5 | using WinUI.TableView.Extensions; 6 | 7 | namespace WinUI.TableView; 8 | 9 | /// 10 | /// Represents a column in a TableView that is bound to a data source. 11 | /// 12 | public abstract class TableViewBoundColumn : TableViewColumn 13 | { 14 | private Type? _listType; 15 | private string? _propertyPath; 16 | private Binding _binding = new(); 17 | private (PropertyInfo, object?)[]? _propertyInfo; 18 | 19 | public override object? GetCellContent(object? dataItem) 20 | { 21 | if (dataItem is null) return null; 22 | 23 | if (_propertyInfo is null || dataItem.GetType() != _listType) 24 | { 25 | _listType = dataItem.GetType(); 26 | dataItem = dataItem.GetValue(_listType, PropertyPath, out _propertyInfo); 27 | } 28 | else 29 | { 30 | dataItem = dataItem.GetValue(_propertyInfo); 31 | } 32 | 33 | if (Binding?.Converter is not null) 34 | { 35 | dataItem = Binding.Converter.Convert( 36 | dataItem, 37 | typeof(object), 38 | Binding.ConverterParameter, 39 | Binding.ConverterLanguage); 40 | } 41 | 42 | return dataItem; 43 | } 44 | 45 | /// 46 | /// Gets the property path for the binding. 47 | /// 48 | internal string? PropertyPath 49 | { 50 | get 51 | { 52 | _propertyPath ??= Binding?.Path?.Path; 53 | return _propertyPath; 54 | } 55 | } 56 | 57 | /// 58 | /// Gets or sets the binding for the column. 59 | /// 60 | public virtual Binding Binding 61 | { 62 | get => _binding; 63 | set 64 | { 65 | _binding = value; 66 | if (_binding is not null) 67 | { 68 | _binding.Mode = BindingMode.TwoWay; 69 | } 70 | } 71 | } 72 | 73 | /// 74 | /// Handles changes to the ElementStyle property. 75 | /// 76 | private static void OnElementStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 77 | { 78 | if (d is TableViewColumn column && column.OwningCollection is { }) 79 | { 80 | column.OwningCollection.HandleColumnPropertyChanged(column, nameof(ElementStyle)); 81 | } 82 | } 83 | 84 | /// 85 | /// Handles changes to the EditingElementStyle property. 86 | /// 87 | private static void OnEditingElementStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 88 | { 89 | if (d is TableViewColumn column && column.OwningCollection is { }) 90 | { 91 | column.OwningCollection.HandleColumnPropertyChanged(column, nameof(EditingElementStyle)); 92 | } 93 | } 94 | 95 | /// 96 | /// Gets or sets the style that is used when rendering the element that the column 97 | /// displays for a cell that is not in editing mode. 98 | /// 99 | public Style ElementStyle 100 | { 101 | get => (Style)GetValue(ElementStyleProperty); 102 | set => SetValue(ElementStyleProperty, value); 103 | } 104 | 105 | /// 106 | /// Gets or sets the style that is used when rendering the element that the column 107 | /// displays for a cell in editing mode. 108 | /// 109 | public Style EditingElementStyle 110 | { 111 | get => (Style)GetValue(EditingElementStyleProperty); 112 | set => SetValue(EditingElementStyleProperty, value); 113 | } 114 | 115 | /// 116 | /// Identifies the dependency property. 117 | /// 118 | public static readonly DependencyProperty ElementStyleProperty = DependencyProperty.Register(nameof(ElementStyle), typeof(Style), typeof(TableViewBoundColumn), new PropertyMetadata(null, OnElementStyleChanged)); 119 | 120 | /// 121 | /// Identifies the dependency property. 122 | /// 123 | public static readonly DependencyProperty EditingElementStyleProperty = DependencyProperty.Register(nameof(EditingElementStyle), typeof(Style), typeof(TableViewBoundColumn), new PropertyMetadata(null, OnEditingElementStyleChanged)); 124 | } 125 | -------------------------------------------------------------------------------- /src/Columns/TableViewCheckBoxColumn.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Controls.Primitives; 4 | using System; 5 | 6 | namespace WinUI.TableView; 7 | 8 | /// 9 | /// Represents a column in a TableView that displays a CheckBox. 10 | /// 11 | [StyleTypedProperty(Property = nameof(ElementStyle), StyleTargetType = typeof(CheckBox))] 12 | public class TableViewCheckBoxColumn : TableViewBoundColumn 13 | { 14 | /// 15 | /// Initializes a new instance of the TableViewCheckBoxColumn class. 16 | /// 17 | public TableViewCheckBoxColumn() 18 | { 19 | UseSingleElement = true; 20 | } 21 | 22 | /// 23 | /// Generates a CheckBox element for the cell. 24 | /// 25 | /// The cell for which the element is generated. 26 | /// The data item associated with the cell. 27 | /// A CheckBox element. 28 | public override FrameworkElement GenerateElement(TableViewCell cell, object? dataItem) 29 | { 30 | var checkBox = new CheckBox 31 | { 32 | MinWidth = 20, 33 | MaxWidth = 20, 34 | Margin = new Thickness(12, 0, 12, 0), 35 | HorizontalAlignment = HorizontalAlignment.Center, 36 | UseSystemFocusVisuals = false, 37 | }; 38 | 39 | checkBox.SetBinding(ToggleButton.IsCheckedProperty, Binding); 40 | UpdateCheckBoxState(checkBox); 41 | 42 | return checkBox; 43 | } 44 | 45 | public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) 46 | { 47 | throw new NotImplementedException(); 48 | } 49 | 50 | public override void UpdateElementState(TableViewCell cell, object? dataItem) 51 | { 52 | if (cell?.Content is CheckBox checkBox) 53 | { 54 | UpdateCheckBoxState(checkBox); 55 | } 56 | } 57 | 58 | /// 59 | /// Updates the state of the CheckBox element. 60 | /// 61 | /// The CheckBox element to update. 62 | private void UpdateCheckBoxState(CheckBox checkBox) 63 | { 64 | checkBox.IsHitTestVisible = TableView?.IsReadOnly is false && !IsReadOnly; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Columns/TableViewComboBoxColumn.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Controls.Primitives; 4 | using Microsoft.UI.Xaml.Data; 5 | 6 | namespace WinUI.TableView; 7 | 8 | /// 9 | /// Represents a column in a TableView that displays a ComboBox. 10 | /// 11 | [StyleTypedProperty(Property = nameof(ElementStyle), StyleTargetType = typeof(TextBlock))] 12 | [StyleTypedProperty(Property = nameof(EditingElementStyle), StyleTargetType = typeof(ComboBox))] 13 | public class TableViewComboBoxColumn : TableViewBoundColumn 14 | { 15 | private Binding? _textBinding; 16 | private Binding? _selectedValueBinding; 17 | 18 | /// 19 | /// Generates a TextBlock element for the cell. 20 | /// 21 | /// The cell for which the element is generated. 22 | /// The data item associated with the cell. 23 | /// A TextBlock element. 24 | public override FrameworkElement GenerateElement(TableViewCell cell, object? dataItem) 25 | { 26 | var textBlock = new TextBlock 27 | { 28 | Margin = new Thickness(12, 0, 12, 0), 29 | }; 30 | textBlock.SetBinding(TextBlock.TextProperty, Binding); 31 | return textBlock; 32 | } 33 | 34 | /// 35 | /// Generates a ComboBox element for editing the cell. 36 | /// 37 | /// The cell for which the editing element is generated. 38 | /// The data item associated with the cell. 39 | /// A ComboBox element. 40 | public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) 41 | { 42 | var comboBox = new ComboBox { HorizontalAlignment = HorizontalAlignment.Stretch }; 43 | comboBox.SetBinding(ItemsControl.ItemsSourceProperty, new Binding { Source = this, Path = new PropertyPath(nameof(ItemsSource)) }); 44 | comboBox.SetBinding(Selector.SelectedValuePathProperty, new Binding { Source = this, Path = new PropertyPath(nameof(SelectedValuePath)) }); 45 | comboBox.SetBinding(ItemsControl.DisplayMemberPathProperty, new Binding { Source = this, Path = new PropertyPath(nameof(DisplayMemberPath)) }); 46 | comboBox.SetBinding(Selector.SelectedItemProperty, Binding); 47 | comboBox.SetBinding(ComboBox.IsEditableProperty, new Binding { Source = this, Path = new PropertyPath(nameof(IsEditable)) }); 48 | 49 | if (TextBinding is not null) 50 | { 51 | comboBox.SetBinding(ComboBox.TextProperty, TextBinding); 52 | } 53 | 54 | if (SelectedValueBinding is not null) 55 | { 56 | comboBox.SetBinding(Selector.SelectedValueProperty, SelectedValueBinding); 57 | } 58 | 59 | return comboBox; 60 | } 61 | 62 | /// 63 | /// Gets or sets the items source for the ComboBox. 64 | /// 65 | public object? ItemsSource 66 | { 67 | get => GetValue(ItemsSourceProperty); 68 | set => SetValue(ItemsSourceProperty, value); 69 | } 70 | 71 | /// 72 | /// Gets or sets the path to the display member for the ComboBox. 73 | /// 74 | public string? DisplayMemberPath 75 | { 76 | get => (string?)GetValue(DisplayMemberPathProperty); 77 | set => SetValue(DisplayMemberPathProperty, value); 78 | } 79 | 80 | /// 81 | /// Gets or sets the path to the selected value for the ComboBox. 82 | /// 83 | public string? SelectedValuePath 84 | { 85 | get => (string?)GetValue(SelectedValuePathProperty); 86 | set => SetValue(SelectedValuePathProperty, value); 87 | } 88 | 89 | /// 90 | /// Gets or sets a value indicating whether the ComboBox is editable. 91 | /// 92 | public bool IsEditable 93 | { 94 | get => (bool)GetValue(IsEditableProperty); 95 | set => SetValue(IsEditableProperty, value); 96 | } 97 | 98 | /// 99 | /// Gets or sets the binding for the text property of the ComboBox. 100 | /// 101 | public virtual Binding TextBinding 102 | { 103 | get => _textBinding!; 104 | set 105 | { 106 | _textBinding = value; 107 | if (_textBinding is not null) 108 | { 109 | _textBinding.Mode = BindingMode.TwoWay; 110 | } 111 | } 112 | } 113 | 114 | /// 115 | /// Gets or sets the binding for the selected value property of the ComboBox. 116 | /// 117 | public virtual Binding SelectedValueBinding 118 | { 119 | get => _selectedValueBinding!; 120 | set 121 | { 122 | _selectedValueBinding = value; 123 | if (_selectedValueBinding is not null) 124 | { 125 | _selectedValueBinding.Mode = BindingMode.TwoWay; 126 | } 127 | } 128 | } 129 | 130 | /// 131 | /// Identifies the SelectedValuePath dependency property. 132 | /// 133 | public static readonly DependencyProperty SelectedValuePathProperty = DependencyProperty.Register(nameof(SelectedValuePath), typeof(string), typeof(TableViewComboBoxColumn), new PropertyMetadata(default)); 134 | 135 | /// 136 | /// Identifies the DisplayMemberPath dependency property. 137 | /// 138 | public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register(nameof(DisplayMemberPath), typeof(string), typeof(TableViewComboBoxColumn), new PropertyMetadata(default)); 139 | 140 | /// 141 | /// Identifies the ItemsSource dependency property. 142 | /// 143 | public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(TableViewComboBoxColumn), new PropertyMetadata(default)); 144 | 145 | /// 146 | /// Identifies the IsEditable dependency property. 147 | /// 148 | public static readonly DependencyProperty IsEditableProperty = DependencyProperty.Register(nameof(IsEditable), typeof(bool), typeof(TableViewComboBoxColumn), new PropertyMetadata(false)); 149 | } -------------------------------------------------------------------------------- /src/Columns/TableViewNumberColumn.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | 4 | namespace WinUI.TableView; 5 | 6 | /// 7 | /// Represents a column in a TableView that displays a number. 8 | /// 9 | [StyleTypedProperty(Property = nameof(ElementStyle), StyleTargetType = typeof(TextBlock))] 10 | [StyleTypedProperty(Property = nameof(EditingElementStyle), StyleTargetType = typeof(NumberBox))] 11 | public class TableViewNumberColumn : TableViewBoundColumn 12 | { 13 | /// 14 | /// Generates a TextBlock element for the cell. 15 | /// 16 | /// The cell for which the element is generated. 17 | /// The data item associated with the cell. 18 | /// A TextBlock element. 19 | public override FrameworkElement GenerateElement(TableViewCell cell, object? dataItem) 20 | { 21 | var textBlock = new TextBlock 22 | { 23 | TextAlignment = TextAlignment.Right, 24 | Margin = new Thickness(12, 0, 12, 0), 25 | }; 26 | textBlock.SetBinding(TextBlock.TextProperty, Binding); 27 | 28 | return textBlock; 29 | } 30 | 31 | /// 32 | /// Generates a NumberBox element for editing the cell. 33 | /// 34 | /// The cell for which the editing element is generated. 35 | /// The data item associated with the cell. 36 | /// A NumberBox element. 37 | public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) 38 | { 39 | var numberBox = new NumberBox(); 40 | numberBox.SetBinding(NumberBox.ValueProperty, Binding); 41 | 42 | return numberBox; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Columns/TableViewTemplateColumn.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | 4 | namespace WinUI.TableView; 5 | 6 | /// 7 | /// Represents a column in a TableView that uses a DataTemplate for its content. 8 | /// 9 | public class TableViewTemplateColumn : TableViewColumn 10 | { 11 | public TableViewTemplateColumn() 12 | { 13 | CanSort = false; 14 | CanFilter = false; 15 | } 16 | 17 | /// 18 | /// Generates a ContentControl for the cell based on CellTemplate and CellTemplateSelector. 19 | /// 20 | /// The cell for which the element is generated. 21 | /// The data item associated with the cell. 22 | /// A ContentControl element. 23 | public override FrameworkElement GenerateElement(TableViewCell cell, object? dataItem) 24 | { 25 | var template = CellTemplateSelector?.SelectTemplate(dataItem) ?? CellTemplate; 26 | return (template?.LoadContent() as FrameworkElement)!; 27 | } 28 | 29 | /// 30 | /// Generates a ContentControl for editing the cell based on EditingTemplate and EditingTemplateSelector. 31 | /// If EditingTemplate or EditingTemplateSelector is not set, GenerateElement is used instead to generate the editing element. 32 | /// 33 | /// The cell for which the editing element is generated. 34 | /// The data item associated with the cell. 35 | /// A ContentControl element. 36 | public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) 37 | { 38 | if (EditingTemplate is not null || EditingTemplateSelector is not null) 39 | { 40 | var template = EditingTemplateSelector?.SelectTemplate(dataItem) ?? EditingTemplate; 41 | return (template?.LoadContent() as FrameworkElement)!; 42 | } 43 | 44 | return GenerateElement(cell, dataItem); 45 | } 46 | 47 | public override void RefreshElement(TableViewCell cell, object? dataItem) 48 | { 49 | cell.Content = GenerateElement(cell, dataItem); 50 | } 51 | 52 | /// 53 | /// Gets or sets the DataTemplate for the cell content. 54 | /// 55 | public DataTemplate? CellTemplate 56 | { 57 | get => (DataTemplate?)GetValue(CellTemplateProperty); 58 | set => SetValue(CellTemplateProperty, value); 59 | } 60 | 61 | /// 62 | /// Gets or sets the DataTemplateSelector for the cell content. 63 | /// 64 | public DataTemplateSelector? CellTemplateSelector 65 | { 66 | get => (DataTemplateSelector?)GetValue(CellTemplateSelectorProperty); 67 | set => SetValue(CellTemplateSelectorProperty, value); 68 | } 69 | 70 | /// 71 | /// Gets or sets the DataTemplate for the editing cell content. 72 | /// 73 | public DataTemplate? EditingTemplate 74 | { 75 | get => (DataTemplate?)GetValue(EditingTemplateProperty); 76 | set => SetValue(EditingTemplateProperty, value); 77 | } 78 | 79 | /// 80 | /// Gets or sets the DataTemplateSelector for the editing cell content. 81 | /// 82 | public DataTemplateSelector? EditingTemplateSelector 83 | { 84 | get => (DataTemplateSelector?)GetValue(EditingTemplateSelectorProperty); 85 | set => SetValue(EditingTemplateSelectorProperty, value); 86 | } 87 | 88 | /// 89 | /// Identifies the CellTemplate dependency property. 90 | /// 91 | public static readonly DependencyProperty CellTemplateProperty = DependencyProperty.Register(nameof(CellTemplate), typeof(DataTemplate), typeof(TableViewTemplateColumn), new PropertyMetadata(default)); 92 | 93 | /// 94 | /// Identifies the CellTemplateSelector dependency property. 95 | /// 96 | public static readonly DependencyProperty CellTemplateSelectorProperty = DependencyProperty.Register(nameof(CellTemplateSelector), typeof(DataTemplateSelector), typeof(TableViewTemplateColumn), new PropertyMetadata(default)); 97 | 98 | /// 99 | /// Identifies the EditingTemplate dependency property. 100 | /// 101 | public static readonly DependencyProperty EditingTemplateProperty = DependencyProperty.Register(nameof(EditingTemplate), typeof(DataTemplate), typeof(TableViewTemplateColumn), new PropertyMetadata(default)); 102 | 103 | /// 104 | /// Identifies the EditingTemplateSelector dependency property. 105 | /// 106 | public static readonly DependencyProperty EditingTemplateSelectorProperty = DependencyProperty.Register(nameof(EditingTemplateSelector), typeof(DataTemplateSelector), typeof(TableViewTemplateColumn), new PropertyMetadata(default)); 107 | } 108 | -------------------------------------------------------------------------------- /src/Columns/TableViewTextColumn.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | 4 | namespace WinUI.TableView; 5 | 6 | /// 7 | /// Represents a column in a TableView that displays text. 8 | /// 9 | [StyleTypedProperty(Property = nameof(ElementStyle), StyleTargetType = typeof(TextBlock))] 10 | [StyleTypedProperty(Property = nameof(EditingElementStyle), StyleTargetType = typeof(TextBox))] 11 | public class TableViewTextColumn : TableViewBoundColumn 12 | { 13 | /// 14 | /// Generates a TextBlock element for the cell. 15 | /// 16 | /// The cell for which the element is generated. 17 | /// The data item associated with the cell. 18 | /// A TextBlock element. 19 | public override FrameworkElement GenerateElement(TableViewCell cell, object? dataItem) 20 | { 21 | var textBlock = new TextBlock 22 | { 23 | Margin = new Thickness(12, 0, 12, 0), 24 | }; 25 | textBlock.SetBinding(TextBlock.TextProperty, Binding); 26 | return textBlock; 27 | } 28 | 29 | /// 30 | /// Generates a TextBox element for editing the cell. 31 | /// 32 | /// The cell for which the editing element is generated. 33 | /// The data item associated with the cell. 34 | /// A TextBox element. 35 | public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) 36 | { 37 | var textBox = new TextBox(); 38 | textBox.SetBinding(TextBox.TextProperty, Binding); 39 | 40 | return textBox; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Columns/TableViewTimeColumn.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Data; 4 | using System; 5 | using Windows.Globalization.DateTimeFormatting; 6 | using WinUI.TableView.Controls; 7 | using WinUI.TableView.Extensions; 8 | using WinUI.TableView.Helpers; 9 | 10 | namespace WinUI.TableView; 11 | 12 | /// 13 | /// Represents a column in a TableView that displays time. 14 | /// 15 | [StyleTypedProperty(Property = nameof(ElementStyle), StyleTargetType = typeof(TextBlock))] 16 | [StyleTypedProperty(Property = nameof(EditingElementStyle), StyleTargetType = typeof(TableViewTimePicker))] 17 | public partial class TableViewTimeColumn : TableViewBoundColumn 18 | { 19 | /// 20 | /// Initializes a new instance of the TableViewTimeColumn class. 21 | /// 22 | public TableViewTimeColumn() 23 | { 24 | ClockIdentifier = DateTimeFormatter.LongTime.Clock; 25 | } 26 | 27 | /// 28 | /// Generates a TextBlock element for the cell. 29 | /// 30 | /// The cell for which the element is generated. 31 | /// The data item associated with the cell. 32 | /// A TextBlock element. 33 | public override FrameworkElement GenerateElement(TableViewCell cell, object? dataItem) 34 | { 35 | var textBlock = new TextBlock 36 | { 37 | Margin = new Thickness(12, 0, 12, 0), 38 | }; 39 | 40 | textBlock.SetBinding(DateTimeFormatHelper.ValueProperty, Binding); 41 | textBlock.SetBinding(DateTimeFormatHelper.FormatProperty, new Binding 42 | { 43 | Path = new PropertyPath(nameof(ClockIdentifier)), 44 | Source = this 45 | }); 46 | 47 | return textBlock; 48 | } 49 | 50 | /// 51 | /// Generates a TableViewTimePicker element for editing the cell. 52 | /// 53 | /// The cell for which the editing element is generated. 54 | /// The data item associated with the cell. 55 | /// A TableViewTimePicker element. 56 | public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) 57 | { 58 | var timePicker = new TableViewTimePicker 59 | { 60 | ClockIdentifier = ClockIdentifier, 61 | MinuteIncrement = MinuteIncrement, 62 | PlaceholderText = PlaceholderText ?? TableViewLocalizedStrings.TimePickerPlaceholder, 63 | SourceType = GetSourcePropertyType(dataItem), 64 | VerticalAlignment = VerticalAlignment.Stretch, 65 | HorizontalAlignment = HorizontalAlignment.Stretch, 66 | }; 67 | 68 | timePicker.SetBinding(TableViewTimePicker.SelectedTimeProperty, Binding); 69 | 70 | return timePicker; 71 | } 72 | 73 | /// 74 | /// Gets the type of the source property. 75 | /// 76 | /// The data item associated with the cell. 77 | /// The type of the source property. 78 | private Type? GetSourcePropertyType(object? dataItem) 79 | { 80 | if (Binding is not null && dataItem is not null) 81 | { 82 | var type = dataItem.GetType(); 83 | var propertyPath = Binding.Path?.Path; 84 | 85 | if (!string.IsNullOrEmpty(propertyPath)) 86 | { 87 | var propertyInfo = type.GetProperty(propertyPath); 88 | if (propertyInfo is not null) 89 | { 90 | type = propertyInfo.PropertyType; 91 | } 92 | } 93 | 94 | if (type.IsTimeSpan() || type.IsTimeOnly() || type.IsDateTime() || type.IsDateTimeOffset()) 95 | { 96 | return type; 97 | } 98 | } 99 | 100 | return typeof(TimeSpan); 101 | } 102 | 103 | /// 104 | /// Gets or sets the clock identifier for the time picker. 105 | /// 106 | public string ClockIdentifier 107 | { 108 | get => (string)GetValue(ClockIdentifierProperty); 109 | set => SetValue(ClockIdentifierProperty, value); 110 | } 111 | 112 | /// 113 | /// Gets or sets the minute increment for the time picker. 114 | /// 115 | public int MinuteIncrement 116 | { 117 | get => (int)GetValue(MinuteIncrementProperty); 118 | set => SetValue(MinuteIncrementProperty, value); 119 | } 120 | 121 | /// 122 | /// Gets or sets the placeholder text for the time picker. 123 | /// 124 | public string? PlaceholderText 125 | { 126 | get => (string?)GetValue(PlaceholderTextProperty); 127 | set => SetValue(PlaceholderTextProperty, value); 128 | } 129 | 130 | /// 131 | /// Identifies the MinuteIncrement dependency property. 132 | /// 133 | public static readonly DependencyProperty MinuteIncrementProperty = DependencyProperty.Register(nameof(MinuteIncrement), typeof(int), typeof(TableViewTimeColumn), new PropertyMetadata(1)); 134 | 135 | /// 136 | /// Identifies the ClockIdentifier dependency property. 137 | /// 138 | public static readonly DependencyProperty ClockIdentifierProperty = DependencyProperty.Register(nameof(ClockIdentifier), typeof(string), typeof(TableViewTimeColumn), new PropertyMetadata(default)); 139 | 140 | /// 141 | /// Identifies the PlaceholderText dependency property. 142 | /// 143 | public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(nameof(PlaceholderText), typeof(string), typeof(TableViewTimeColumn), new PropertyMetadata(null)); 144 | 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/Columns/TableViewToggleSwitchColumn.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using System; 4 | 5 | namespace WinUI.TableView; 6 | 7 | /// 8 | /// Represents a column in a TableView that displays a ToggleSwitch. 9 | /// 10 | [StyleTypedProperty(Property = nameof(ElementStyle), StyleTargetType = typeof(ToggleSwitch))] 11 | public class TableViewToggleSwitchColumn : TableViewBoundColumn 12 | { 13 | /// 14 | /// Initializes a new instance of the TableViewToggleSwitchColumn class. 15 | /// 16 | public TableViewToggleSwitchColumn() 17 | { 18 | UseSingleElement = true; 19 | } 20 | 21 | /// 22 | /// Generates a ToggleSwitch element for the cell. 23 | /// 24 | /// The cell for which the element is generated. 25 | /// The data item associated with the cell. 26 | /// A ToggleSwitch element. 27 | public override FrameworkElement GenerateElement(TableViewCell cell, object? dataItem) 28 | { 29 | var toggleSwitch = new ToggleSwitch 30 | { 31 | OnContent = OnContent, 32 | OffContent = OffContent, 33 | UseSystemFocusVisuals = false, 34 | Margin = new Thickness(12, 0, 12, 0) 35 | }; 36 | 37 | toggleSwitch.SetBinding(ToggleSwitch.IsOnProperty, Binding); 38 | UpdateToggleButtonState(toggleSwitch); 39 | 40 | return toggleSwitch; 41 | } 42 | 43 | public override FrameworkElement GenerateEditingElement(TableViewCell cell, object? dataItem) 44 | { 45 | throw new NotImplementedException(); 46 | } 47 | 48 | public override void UpdateElementState(TableViewCell cell, object? dataItem) 49 | { 50 | if (cell?.Content is ToggleSwitch toggleSwitch) 51 | { 52 | UpdateToggleButtonState(toggleSwitch); 53 | } 54 | } 55 | 56 | /// 57 | /// Updates the state of the ToggleSwitch element. 58 | /// 59 | /// The ToggleSwitch element to update. 60 | private void UpdateToggleButtonState(ToggleSwitch toggleSwitch) 61 | { 62 | toggleSwitch.IsHitTestVisible = TableView?.IsReadOnly is false && !IsReadOnly; 63 | } 64 | 65 | /// 66 | /// Gets or sets the content to display when the ToggleSwitch is on. 67 | /// 68 | public object? OnContent 69 | { 70 | get => GetValue(OnContentProperty); 71 | set => SetValue(OnContentProperty, value); 72 | } 73 | 74 | /// 75 | /// Gets or sets the content to display when the ToggleSwitch is off. 76 | /// 77 | public object? OffContent 78 | { 79 | get => GetValue(OffContentProperty); 80 | set => SetValue(OffContentProperty, value); 81 | } 82 | 83 | /// 84 | /// Identifies the OnContent dependency property. 85 | /// 86 | public static readonly DependencyProperty OnContentProperty = DependencyProperty.Register(nameof(OnContent), typeof(object), typeof(TableViewToggleSwitchColumn), new PropertyMetadata(null)); 87 | 88 | /// 89 | /// Identifies the OffContent dependency property. 90 | /// 91 | public static readonly DependencyProperty OffContentProperty = DependencyProperty.Register(nameof(OffContent), typeof(object), typeof(TableViewToggleSwitchColumn), new PropertyMetadata(null)); 92 | } 93 | -------------------------------------------------------------------------------- /src/Controls/TableViewDatePicker.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using System; 4 | using WinUI.TableView.Extensions; 5 | 6 | namespace WinUI.TableView.Controls; 7 | 8 | /// 9 | /// Represents a date editing element for the TableViewDateColumn. 10 | /// 11 | public partial class TableViewDatePicker : CalendarDatePicker 12 | { 13 | private bool _deferUpdate; 14 | 15 | /// 16 | /// Initializes a new instance of the TableViewDatePicker class. 17 | /// 18 | public TableViewDatePicker() 19 | { 20 | DateChanged += OnDateChanged; 21 | } 22 | 23 | /// 24 | /// Handles the DateChanged event. 25 | /// 26 | private void OnDateChanged(CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs args) 27 | { 28 | if (_deferUpdate) return; 29 | 30 | _deferUpdate = true; 31 | 32 | if (Date is null) 33 | { 34 | SelectedDate = null; 35 | } 36 | else if (SourceType.IsDateOnly()) 37 | { 38 | SelectedDate = DateOnly.FromDateTime(Date.Value.DateTime); 39 | } 40 | else if (SourceType.IsDateTime()) 41 | { 42 | var newDate = Date.Value.DateTime; 43 | var selectedDate = (DateTime?)SelectedDate ?? DateTime.Now; 44 | SelectedDate = new DateTime(newDate.Year, newDate.Month, newDate.Day, 45 | selectedDate.Hour, selectedDate.Minute, selectedDate.Second); 46 | } 47 | else if (SourceType.IsDateTimeOffset()) 48 | { 49 | var selectedDate = (DateTimeOffset?)SelectedDate ?? DateTimeOffset.Now; 50 | var newDate = Date.Value; 51 | SelectedDate = new DateTimeOffset(newDate.Year, newDate.Month, newDate.Day, 52 | selectedDate.Hour, selectedDate.Minute, selectedDate.Second, selectedDate.Offset); 53 | } 54 | 55 | _deferUpdate = false; 56 | } 57 | 58 | /// 59 | /// Handles changes to the SelectedDate property. 60 | /// 61 | private static void OnSelectedDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 62 | { 63 | if (d is TableViewDatePicker datePicker && !datePicker._deferUpdate) 64 | { 65 | datePicker._deferUpdate = true; 66 | datePicker.Date = e.NewValue switch 67 | { 68 | DateOnly dateOnly => dateOnly.ToDateTimeOffset(), 69 | DateTime dateTime => dateTime.ToDateTimeOffset(), 70 | DateTimeOffset dateTimeOffset => dateTimeOffset, 71 | _ => throw new FormatException() 72 | }; 73 | datePicker.SourceType ??= e.NewValue?.GetType(); 74 | datePicker._deferUpdate = false; 75 | } 76 | } 77 | 78 | /// 79 | /// Gets or sets the source type of the date picker. 80 | /// This value could be DateOnly, DateTime, or DateTimeOffset. 81 | /// 82 | internal Type? SourceType { get; set; } 83 | 84 | /// 85 | /// Gets or sets the selected date. 86 | /// 87 | public object? SelectedDate 88 | { 89 | get => GetValue(SelectedDateProperty); 90 | set => SetValue(SelectedDateProperty, value); 91 | } 92 | 93 | /// 94 | /// Identifies the SelectedDate dependency property. 95 | /// 96 | public static readonly DependencyProperty SelectedDateProperty = DependencyProperty.Register(nameof(SelectedDate), typeof(object), typeof(TableViewDatePicker), new PropertyMetadata(default, OnSelectedDateChanged)); 97 | } 98 | -------------------------------------------------------------------------------- /src/Controls/TableViewTimePicker.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Controls.Primitives; 4 | using Microsoft.UI.Xaml.Input; 5 | using System; 6 | using Windows.System; 7 | using WinUI.TableView.Extensions; 8 | using WinUI.TableView.Helpers; 9 | 10 | namespace WinUI.TableView.Controls; 11 | 12 | /// 13 | /// Represents a time editing element for the TableViewTimeColumn. 14 | /// 15 | public partial class TableViewTimePicker : Control 16 | { 17 | private TextBlock? _timeText; 18 | private readonly TimePickerFlyout _flyout; 19 | 20 | /// 21 | /// Initializes a new instance of the TableViewTimePicker class. 22 | /// 23 | public TableViewTimePicker() 24 | { 25 | DefaultStyleKey = typeof(TableViewTimePicker); 26 | 27 | _flyout = new() { Placement = FlyoutPlacementMode.Bottom }; 28 | _flyout.TimePicked += OnTimePicked; 29 | 30 | ClockIdentifier = _flyout.ClockIdentifier; 31 | } 32 | 33 | protected override void OnApplyTemplate() 34 | { 35 | base.OnApplyTemplate(); 36 | 37 | _timeText = GetTemplateChild("TimeText") as TextBlock; 38 | 39 | UpdateTimeText(); 40 | } 41 | 42 | protected override void OnPointerPressed(PointerRoutedEventArgs e) 43 | { 44 | base.OnPointerPressed(e); 45 | 46 | ShowFlyout(); 47 | } 48 | 49 | protected override void OnKeyDown(KeyRoutedEventArgs e) 50 | { 51 | base.OnKeyDown(e); 52 | 53 | if (e.Key is VirtualKey.Space) 54 | { 55 | ShowFlyout(); 56 | } 57 | } 58 | 59 | /// 60 | /// Shows the time picker flyout. 61 | /// 62 | private void ShowFlyout() 63 | { 64 | _flyout.Time = SelectedTime switch 65 | { 66 | TimeSpan timeSpan => timeSpan, 67 | TimeOnly timeOnly => timeOnly.ToTimeSpan(), 68 | DateTime dateTime => dateTime.TimeOfDay, 69 | DateTimeOffset dateTimeOffset => dateTimeOffset.TimeOfDay, 70 | _ => _flyout.Time 71 | }; 72 | 73 | _flyout.ClockIdentifier = ClockIdentifier; 74 | _flyout.MinuteIncrement = MinuteIncrement; 75 | _flyout.ShowAt(this); 76 | } 77 | 78 | /// 79 | /// Handles the TimePicked event of the flyout. 80 | /// 81 | private void OnTimePicked(TimePickerFlyout sender, TimePickedEventArgs args) 82 | { 83 | var oldTime = SelectedTime is null ? TimeSpan.Zero : args.OldTime; 84 | 85 | if (SourceType.IsTimeSpan()) 86 | { 87 | SelectedTime = args.NewTime; 88 | } 89 | else if (SourceType.IsTimeOnly()) 90 | { 91 | SelectedTime = TimeOnly.FromTimeSpan(args.NewTime); 92 | } 93 | else if (SourceType.IsDateTime()) 94 | { 95 | var dateTime = (DateTime?)SelectedTime ?? DateTime.Today; 96 | SelectedTime = dateTime.Subtract(oldTime).Add(args.NewTime); 97 | } 98 | else if (SourceType.IsDateTimeOffset()) 99 | { 100 | var offset = TimeZoneInfo.Local.GetUtcOffset(DateTime.Today); 101 | var dateTimeOffset = (DateTimeOffset?)SelectedTime ?? new DateTimeOffset(DateTime.Today, offset); 102 | SelectedTime = dateTimeOffset.Subtract(oldTime).Add(args.NewTime); 103 | } 104 | } 105 | 106 | /// 107 | /// Updates the text displayed in the time picker. 108 | /// 109 | private void UpdateTimeText() 110 | { 111 | if (_timeText is null) return; 112 | 113 | var formatter = DateTimeFormatHelper.GetDateTimeFormatter("shorttime", ClockIdentifier); 114 | 115 | _timeText.Text = SelectedTime switch 116 | { 117 | TimeSpan timeSpan => formatter.Format(timeSpan.ToDateTimeOffset()), 118 | TimeOnly timeOnly => formatter.Format(timeOnly.ToDateTimeOffset()), 119 | DateTime dateTime => formatter.Format(dateTime.ToDateTimeOffset()), 120 | DateTimeOffset dateTimeOffset => formatter.Format(dateTimeOffset), 121 | null => PlaceholderText, 122 | _ => throw new FormatException() 123 | }; 124 | } 125 | 126 | /// 127 | /// Handles changes to the SelectedTime property. 128 | /// 129 | private static void OnSelectedTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 130 | { 131 | if (d is TableViewTimePicker timePicker) 132 | { 133 | timePicker.UpdateTimeText(); 134 | timePicker.SourceType ??= e.NewValue?.GetType(); 135 | } 136 | } 137 | 138 | /// 139 | /// Handles changes to the PlaceholderText property. 140 | /// 141 | private static void OnPlaceHolderTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 142 | { 143 | if (d is TableViewTimePicker timePicker) 144 | { 145 | timePicker.UpdateTimeText(); 146 | } 147 | } 148 | 149 | /// 150 | /// Handles changes to the ClockIdentifier property. 151 | /// 152 | private static void OnClockIdentifierChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 153 | { 154 | if (d is TableViewTimePicker timePicker) 155 | { 156 | timePicker.UpdateTimeText(); 157 | } 158 | } 159 | 160 | /// 161 | /// Gets or sets the source type of the time picker. 162 | /// The value could be TimeSpan, TimeOnly, DateTime, or DateTimeOffset. 163 | /// 164 | internal Type? SourceType { get; set; } 165 | 166 | /// 167 | /// Gets or sets the selected time. 168 | /// 169 | public object? SelectedTime 170 | { 171 | get => GetValue(SelectedTimeProperty); 172 | set => SetValue(SelectedTimeProperty, value); 173 | } 174 | 175 | /// 176 | /// Gets or sets the placeholder text for the time picker. 177 | /// 178 | public string? PlaceholderText 179 | { 180 | get => (string?)GetValue(PlaceholderTextProperty); 181 | set => SetValue(PlaceholderTextProperty, value); 182 | } 183 | 184 | /// 185 | /// Gets or sets the clock identifier for the time picker. 186 | /// 187 | public string ClockIdentifier 188 | { 189 | get => (string)GetValue(ClockIdentifierProperty); 190 | set => SetValue(ClockIdentifierProperty, value); 191 | } 192 | 193 | /// 194 | /// Gets or sets the minute increment for the time picker. 195 | /// 196 | public int MinuteIncrement 197 | { 198 | get => (int)GetValue(MinuteIncrementProperty); 199 | set => SetValue(MinuteIncrementProperty, value); 200 | } 201 | 202 | /// 203 | /// Identifies the MinuteIncrement dependency property. 204 | /// 205 | public static readonly DependencyProperty MinuteIncrementProperty = DependencyProperty.Register(nameof(MinuteIncrement), typeof(int), typeof(TableViewTimePicker), new PropertyMetadata(1)); 206 | 207 | /// 208 | /// Identifies the SelectedTime dependency property. 209 | /// 210 | public static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register(nameof(SelectedTime), typeof(object), typeof(TableViewTimePicker), new PropertyMetadata(default, OnSelectedTimeChanged)); 211 | 212 | /// 213 | /// Identifies the PlaceholderText dependency property. 214 | /// 215 | public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(nameof(PlaceholderText), typeof(string), typeof(TableViewTimePicker), new PropertyMetadata("pick a time", OnPlaceHolderTextChanged)); 216 | 217 | /// 218 | /// Identifies the ClockIdentifier dependency property. 219 | /// 220 | public static readonly DependencyProperty ClockIdentifierProperty = DependencyProperty.Register(nameof(ClockIdentifier), typeof(string), typeof(TableViewTimePicker), new PropertyMetadata(default, OnClockIdentifierChanged)); 221 | } 222 | -------------------------------------------------------------------------------- /src/Converters/BoolToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Data; 3 | using System; 4 | 5 | namespace WinUI.TableView.Converters; 6 | 7 | internal partial class BoolToVisibilityConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, string language) 10 | { 11 | return value is bool b && b ? Visibility.Visible : Visibility.Collapsed; 12 | } 13 | 14 | public object ConvertBack(object value, Type targetType, object parameter, string language) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/EventArgs/TableViewAutoGeneratingColumnEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace WinUI.TableView; 5 | 6 | /// 7 | /// Provides data for the AutoGeneratingColumn event. 8 | /// 9 | public class TableViewAutoGeneratingColumnEventArgs : CancelEventArgs 10 | { 11 | /// 12 | /// Initializes a new instance of the TableViewAutoGeneratingColumnEventArgs class. 13 | /// 14 | /// The name of the property for which the column is being generated. 15 | /// The type of the property for which the column is being generated. 16 | /// The column that is being generated. 17 | public TableViewAutoGeneratingColumnEventArgs(string propertyName, Type propertyType, TableViewColumn column) 18 | { 19 | PropertyName = propertyName; 20 | PropertyType = propertyType; 21 | Column = column; 22 | } 23 | 24 | /// 25 | /// Gets the name of the property for which the column is being generated. 26 | /// 27 | public string PropertyName { get; } 28 | 29 | /// 30 | /// Gets the type of the property for which the column is being generated. 31 | /// 32 | public Type PropertyType { get; } 33 | 34 | /// 35 | /// Gets or sets the column that is being generated. 36 | /// 37 | public TableViewColumn Column { get; set; } 38 | } 39 | -------------------------------------------------------------------------------- /src/EventArgs/TableViewCellContextFlyoutEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls.Primitives; 2 | using System.ComponentModel; 3 | 4 | namespace WinUI.TableView; 5 | 6 | /// 7 | /// Provides data for the CellContextFlyout event. 8 | /// 9 | public partial class TableViewCellContextFlyoutEventArgs : HandledEventArgs 10 | { 11 | /// 12 | /// Initializes a new instance of the TableViewCellContextFlyoutEventArgs class. 13 | /// 14 | /// The slot of the cell for which the context flyout is being shown. 15 | /// The cell for which the context flyout is being shown. 16 | /// The item associated with the cell. 17 | /// The context flyout to be shown. 18 | public TableViewCellContextFlyoutEventArgs(TableViewCellSlot slot, TableViewCell cell, object item, FlyoutBase? flyout) 19 | { 20 | Slot = slot; 21 | Cell = cell; 22 | Item = item; 23 | Flyout = flyout; 24 | } 25 | 26 | /// 27 | /// Gets the slot of the cell for which the context flyout is being shown. 28 | /// 29 | public TableViewCellSlot Slot { get; } 30 | 31 | /// 32 | /// Gets the cell for which the context flyout is being shown. 33 | /// 34 | public TableViewCell Cell { get; } 35 | 36 | /// 37 | /// Gets the item associated with the cell. 38 | /// 39 | public object Item { get; } 40 | 41 | /// 42 | /// Gets the context flyout to be shown. 43 | /// 44 | public FlyoutBase? Flyout { get; } 45 | } 46 | -------------------------------------------------------------------------------- /src/EventArgs/TableViewCellSelectionChangedEvenArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WinUI.TableView; 5 | 6 | /// 7 | /// Provides data for the cell selection changed event. 8 | /// 9 | public class TableViewCellSelectionChangedEventArgs : EventArgs 10 | { 11 | /// 12 | /// Initializes a new instance of the TableViewCellSelectionChangedEventArgs class. 13 | /// 14 | /// The list that contains the cells that were unselected. 15 | /// The list that contains the cells that were selected. 16 | public TableViewCellSelectionChangedEventArgs(IList removedCells, 17 | IList addedCells) 18 | { 19 | RemovedCells = removedCells; 20 | AddedCells = addedCells; 21 | } 22 | 23 | /// 24 | /// Gets a list that contains the cells that were unselected. 25 | /// 26 | public IList RemovedCells { get; } 27 | 28 | /// 29 | /// Gets a list that contains the cells that were selected. 30 | /// 31 | public IList AddedCells { get; } 32 | } 33 | -------------------------------------------------------------------------------- /src/EventArgs/TableViewClearSortingEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Provides data for the event that is raised when sorting is cleared from TableView. 7 | /// 8 | public partial class TableViewClearSortingEventArgs : HandledEventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The column from which the sorting is being cleared. 14 | public TableViewClearSortingEventArgs(TableViewColumn? column = default) 15 | { 16 | Column = column; 17 | } 18 | 19 | /// 20 | /// Gets the column from which the sorting is being cleared. 21 | /// 22 | public TableViewColumn? Column { get; } 23 | } -------------------------------------------------------------------------------- /src/EventArgs/TableViewColumnPropertyChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Provides data for the ColumnPropertyChanged event. 7 | /// 8 | internal class TableViewColumnPropertyChangedEventArgs : EventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the TableViewColumnPropertyChanged class. 12 | /// 13 | /// The column that changed. 14 | /// The name of the property that changed. 15 | /// The index of the column in the collection. 16 | public TableViewColumnPropertyChangedEventArgs(TableViewColumn column, string propertyName, int index) 17 | { 18 | Column = column; 19 | PropertyName = propertyName; 20 | Index = index; 21 | } 22 | 23 | /// 24 | /// Gets the column that changed. 25 | /// 26 | public TableViewColumn Column { get; } 27 | 28 | /// 29 | /// Gets the name of the property that changed. 30 | /// 31 | public string PropertyName { get; } 32 | 33 | /// 34 | /// Gets the index of the column in the collection. 35 | /// 36 | public int Index { get; } 37 | } 38 | -------------------------------------------------------------------------------- /src/EventArgs/TableViewCopyToClipboardEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Provides data for the CopyToClipboard event. 7 | /// 8 | public class TableViewCopyToClipboardEventArgs : HandledEventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the TableViewCopyToClipboardEventArgs class. 12 | /// 13 | /// A value indicating whether to include headers in the copied content. 14 | public TableViewCopyToClipboardEventArgs(bool includeHeaders) 15 | { 16 | IncludeHeaders = includeHeaders; 17 | } 18 | 19 | /// 20 | /// Gets a value indicating whether to include headers in the copied content. 21 | /// 22 | public bool IncludeHeaders { get; } 23 | } 24 | -------------------------------------------------------------------------------- /src/EventArgs/TableViewExportContentEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Provides data for the ExportContent event. 7 | /// 8 | public class TableViewExportContentEventArgs : HandledEventArgs { } 9 | -------------------------------------------------------------------------------- /src/EventArgs/TableViewRowContextFlyoutEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls.Primitives; 2 | using System.ComponentModel; 3 | 4 | namespace WinUI.TableView; 5 | 6 | /// 7 | /// Provides data for the RowContextFlyout event. 8 | /// 9 | public partial class TableViewRowContextFlyoutEventArgs : HandledEventArgs 10 | { 11 | /// 12 | /// Initializes a new instance of the TableViewRowContextFlyoutEventArgs class. 13 | /// 14 | /// The index of the row. 15 | /// The TableViewRow associated with the event. 16 | /// The item associated with the row. 17 | /// The flyout to be shown. 18 | public TableViewRowContextFlyoutEventArgs(int index, TableViewRow row, object item, FlyoutBase? flyout) 19 | { 20 | Index = index; 21 | Row = row; 22 | Item = item; 23 | Flyout = flyout; 24 | } 25 | 26 | /// 27 | /// Gets the index of the row. 28 | /// 29 | public int Index { get; } 30 | 31 | /// 32 | /// Gets the TableViewRow associated with the event. 33 | /// 34 | public TableViewRow Row { get; } 35 | 36 | /// 37 | /// Gets the item associated with the row. 38 | /// 39 | public object Item { get; } 40 | 41 | /// 42 | /// Gets the flyout to be shown. 43 | /// 44 | public FlyoutBase? Flyout { get; } 45 | } 46 | -------------------------------------------------------------------------------- /src/EventArgs/TableViewSortingEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Provides data for the event that is raised when a column is being sorted in a TableView. 7 | /// 8 | public partial class TableViewSortingEventArgs : HandledEventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The column that is being sorted. 14 | public TableViewSortingEventArgs(TableViewColumn column) 15 | { 16 | Column = column; 17 | } 18 | 19 | /// 20 | /// Gets the column that is being sorted. 21 | /// 22 | public TableViewColumn Column { get; } 23 | } 24 | -------------------------------------------------------------------------------- /src/Extensions/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace WinUI.TableView.Extensions; 6 | 7 | /// 8 | /// Provides extension methods for collections. 9 | /// 10 | internal static class CollectionExtensions 11 | { 12 | /// 13 | /// Adds a range of items to the collection. 14 | /// 15 | /// The type of elements in the collection. 16 | /// The collection to add items to. 17 | /// The items to add to the collection. 18 | public static void AddRange(this IList collection, IEnumerable items) 19 | { 20 | foreach (var item in items) 21 | { 22 | collection.Add(item); 23 | } 24 | } 25 | 26 | /// 27 | /// Removes items from the collection that match the specified predicate. 28 | /// 29 | /// The type of elements in the collection. 30 | /// The collection to remove items from. 31 | /// The predicate to determine which items to remove. 32 | public static void RemoveWhere(this ICollection collection, Predicate predicate) 33 | { 34 | var itemsToRemove = collection.Where(x => predicate(x)).ToList(); 35 | 36 | foreach (var item in itemsToRemove) 37 | { 38 | collection.Remove(item); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Extensions/DateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WinUI.TableView.Extensions; 4 | 5 | /// 6 | /// Provides extension methods for Date and Time types. 7 | /// 8 | internal static class DateTimeExtensions 9 | { 10 | /// 11 | /// Converts a DateTime to a DateTimeOffset using the local time zone. 12 | /// 13 | /// The DateTime to convert. 14 | /// A DateTimeOffset representing the same point in time as the DateTime. 15 | public static DateTimeOffset ToDateTimeOffset(this DateTime dateTime) 16 | { 17 | return new DateTimeOffset(dateTime, TimeZoneInfo.Local.GetUtcOffset(dateTime)); 18 | } 19 | 20 | /// 21 | /// Converts a TimeSpan to a DateTimeOffset using the current date. 22 | /// 23 | /// The TimeSpan to convert. 24 | /// A DateTimeOffset representing the same time of day as the TimeSpan on the current date. 25 | public static DateTimeOffset ToDateTimeOffset(this TimeSpan timeSpan) 26 | { 27 | return DateTime.Today.Add(timeSpan).ToDateTimeOffset(); 28 | } 29 | 30 | /// 31 | /// Converts a DateOnly to a DateTimeOffset using the minimum time of day. 32 | /// 33 | /// The DateOnly to convert. 34 | /// A DateTimeOffset representing the same date as the DateOnly with the minimum time of day. 35 | public static DateTimeOffset ToDateTimeOffset(this DateOnly dateOnly) 36 | { 37 | return dateOnly.ToDateTime(TimeOnly.MinValue).ToDateTimeOffset(); 38 | } 39 | 40 | /// 41 | /// Converts a TimeOnly to a DateTimeOffset using the current date. 42 | /// 43 | /// The TimeOnly to convert. 44 | /// A DateTimeOffset representing the same time of day as the TimeOnly on the current date. 45 | public static DateTimeOffset ToDateTimeOffset(this TimeOnly timeOnly) 46 | { 47 | return timeOnly.ToTimeSpan().ToDateTimeOffset(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Extensions/ItemIndexRangeExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Data; 2 | 3 | namespace WinUI.TableView.Extensions; 4 | 5 | /// 6 | /// Provides extension methods for the ItemIndexRange type. 7 | /// 8 | public static class ItemIndexRangeExtensions 9 | { 10 | /// 11 | /// Determines whether a specified index is within the range. 12 | /// 13 | /// The ItemIndexRange to check. 14 | /// The index to check. 15 | /// True if the index is within the range; otherwise, false. 16 | public static bool IsInRange(this ItemIndexRange range, int index) 17 | { 18 | return index >= range.FirstIndex && index <= range.LastIndex; 19 | } 20 | 21 | /// 22 | /// Determines whether the given item index range is valid within the TableView. 23 | /// 24 | /// The ItemIndexRange to check. 25 | /// The TableView to check against. 26 | /// True if the item index range of TableView is valid; otherwise, false. 27 | public static bool IsValid(this ItemIndexRange itemIndexRange, TableView tableView) 28 | { 29 | return itemIndexRange.FirstIndex >= 0 && itemIndexRange.LastIndex < tableView?.Items.Count; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace WinUI.TableView.Extensions; 5 | 6 | /// 7 | /// Provides extension methods for object types. 8 | /// 9 | internal static class ObjectExtensions 10 | { 11 | /// 12 | /// Gets the value of a property from an object using a sequence of property info and index pairs. 13 | /// 14 | /// The object from which to get the value. 15 | /// An array of property info and index pairs. 16 | /// The value of the property, or null if the object is null. 17 | internal static object? GetValue(this object? obj, (PropertyInfo pi, object? index)[] pis) 18 | { 19 | foreach (var pi in pis) 20 | { 21 | if (obj is null) 22 | { 23 | break; 24 | } 25 | 26 | obj = pi.index is not null ? pi.pi.GetValue(obj, [pi.index]) : pi.pi.GetValue(obj); 27 | } 28 | 29 | return obj; 30 | } 31 | 32 | /// 33 | /// Gets the value of a property from an object using a type and a property path. 34 | /// 35 | /// The object from which to get the value. 36 | /// The type of the object. 37 | /// The property path. 38 | /// An array of property info and index pairs. 39 | /// The value of the property, or null if the object is null. 40 | internal static object? GetValue(this object? obj, Type? type, string? path, out (PropertyInfo pi, object? index)[] pis) 41 | { 42 | var parts = path?.Split('.'); 43 | 44 | if (parts is null) 45 | { 46 | pis = []; 47 | return obj; 48 | } 49 | 50 | pis = new (PropertyInfo, object?)[parts.Length]; 51 | 52 | for (var i = 0; i < parts.Length; i++) 53 | { 54 | var part = parts[i]; 55 | var index = default(object?); 56 | if (part.StartsWith('[') && part.EndsWith(']')) 57 | { 58 | index = int.TryParse(part[1..^1], out var ind) ? ind : index; 59 | part = "Item"; 60 | } 61 | 62 | var pi = type?.GetProperty(part); 63 | if (pi is not null) 64 | { 65 | pis[i] = (pi, index); 66 | obj = index is not null ? pi?.GetValue(obj, [index]) : pi?.GetValue(obj); 67 | type = obj?.GetType(); 68 | } 69 | else 70 | { 71 | pis = null!; 72 | return null; 73 | } 74 | } 75 | 76 | return obj; 77 | } 78 | 79 | /// 80 | /// Determines whether the specified object is numeric. 81 | /// 82 | /// 83 | /// 84 | public static bool IsNumeric(this object obj) 85 | { 86 | return obj is byte 87 | or sbyte 88 | or short 89 | or ushort 90 | or int 91 | or uint 92 | or long 93 | or ulong 94 | or float 95 | or double 96 | or decimal; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Extensions/TableViewCellSlotExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace WinUI.TableView.Extensions; 2 | 3 | /// 4 | /// Provides extension methods for the TableViewCellSlot type. 5 | /// 6 | internal static class TableViewCellSlotExtensions 7 | { 8 | /// 9 | /// Determines whether the row index of the slot is valid within the TableView. 10 | /// 11 | /// The TableViewCellSlot to check. 12 | /// The TableView to check against. 13 | /// True if the row index of TableView is valid; otherwise, false. 14 | public static bool IsValidRow(this TableViewCellSlot slot, TableView tableView) 15 | { 16 | return slot.Row >= 0 && slot.Row < tableView?.Items.Count; 17 | } 18 | 19 | /// 20 | /// Determines whether the column index of the slot is valid within the TableView. 21 | /// 22 | /// The TableViewCellSlot to check. 23 | /// The TableView to check against. 24 | /// True if the column index TableView is valid; otherwise, false. 25 | public static bool IsValidColumn(this TableViewCellSlot slot, TableView tableView) 26 | { 27 | return slot.Column >= 0 && slot.Column < tableView?.Columns.VisibleColumns.Count; 28 | } 29 | 30 | /// 31 | /// Determines whether both the row and column indices of the slot are valid within the TableView. 32 | /// 33 | /// The TableViewCellSlot to check. 34 | /// The TableView to check against. 35 | /// True if both the row and column indices of TableView are valid; otherwise, false. 36 | public static bool IsValid(this TableViewCellSlot slot, TableView tableView) 37 | { 38 | return slot.IsValidRow(tableView) && slot.IsValidColumn(tableView); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace WinUI.TableView.Extensions; 6 | 7 | /// 8 | /// Provides extension methods for the Type class. 9 | /// 10 | internal static class TypeExtensions 11 | { 12 | /// 13 | /// Determines whether the specified type is a Boolean. 14 | /// 15 | /// The type to check. 16 | /// True if the type is Boolean; otherwise, false. 17 | public static bool IsBoolean(this Type type) 18 | { 19 | return type == typeof(bool) || type == typeof(bool?); 20 | } 21 | 22 | /// 23 | /// Determines whether the specified type is a numeric. 24 | /// 25 | /// The type to check. 26 | /// True if the type is numeric; otherwise, false. 27 | public static bool IsNumeric(this Type type) 28 | { 29 | return type == typeof(byte) || type == typeof(byte?) || 30 | type == typeof(sbyte) || type == typeof(sbyte?) || 31 | type == typeof(short) || type == typeof(short?) || 32 | type == typeof(ushort) || type == typeof(ushort?) || 33 | type == typeof(int) || type == typeof(int?) || 34 | type == typeof(uint) || type == typeof(uint?) || 35 | type == typeof(long) || type == typeof(long?) || 36 | type == typeof(ulong) || type == typeof(ulong?) || 37 | type == typeof(float) || type == typeof(float?) || 38 | type == typeof(double) || type == typeof(double?) || 39 | type == typeof(decimal) || type == typeof(decimal?); 40 | } 41 | 42 | /// 43 | /// Determines whether the specified type is a TimeSpan or nullable TimeSpan. 44 | /// 45 | /// The type to check. 46 | /// True if the type is TimeSpan or nullable TimeSpan; otherwise, false. 47 | public static bool IsTimeSpan(this Type? type) 48 | { 49 | return type == typeof(TimeSpan) || type == typeof(TimeSpan?); 50 | } 51 | 52 | /// 53 | /// Determines whether the specified type is a TimeOnly or nullable TimeOnly. 54 | /// 55 | /// The type to check. 56 | /// True if the type is TimeOnly or nullable TimeOnly; otherwise, false. 57 | public static bool IsTimeOnly(this Type? type) 58 | { 59 | return type == typeof(TimeOnly) || type == typeof(TimeOnly?); 60 | } 61 | 62 | /// 63 | /// Determines whether the specified type is a DateOnly or nullable DateOnly. 64 | /// 65 | /// The type to check. 66 | /// True if the type is DateOnly or nullable DateOnly; otherwise, false. 67 | public static bool IsDateOnly(this Type? type) 68 | { 69 | return type == typeof(DateOnly) || type == typeof(DateOnly?); 70 | } 71 | 72 | /// 73 | /// Determines whether the specified type is a DateTime or nullable DateTime. 74 | /// 75 | /// The type to check. 76 | /// True if the type is DateTime or nullable DateTime; otherwise, false. 77 | public static bool IsDateTime(this Type? type) 78 | { 79 | return type == typeof(DateTime) || type == typeof(DateTime?); 80 | } 81 | 82 | /// 83 | /// Determines whether the specified type is a DateTimeOffset or nullable DateTimeOffset. 84 | /// 85 | /// The type to check. 86 | /// True if the type is DateTimeOffset or nullable DateTimeOffset; otherwise, false. 87 | public static bool IsDateTimeOffset(this Type? type) 88 | { 89 | return type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?); 90 | } 91 | 92 | /// 93 | /// Determines whether the specified type is a nullable type. 94 | /// 95 | public static bool IsNullableType(this Type? type) 96 | { 97 | return type is not null && type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); 98 | } 99 | 100 | /// 101 | /// Gets the underlying type argument of a nullable type. 102 | /// 103 | public static Type GetNonNullableType(this Type type) 104 | { 105 | return type.IsNullableType() ? Nullable.GetUnderlyingType(type)! : type; 106 | } 107 | 108 | /// 109 | /// Determines whether the specified type is a primitive type. 110 | /// 111 | public static bool IsPrimitive(this Type? dataType) 112 | { 113 | return dataType is not null && 114 | (dataType.GetTypeInfo().IsPrimitive || 115 | dataType == typeof(string) || 116 | dataType == typeof(decimal) || 117 | dataType == typeof(DateTime)); 118 | } 119 | 120 | /// 121 | /// Determines whether the specified type is inherited from . 122 | /// 123 | public static bool IsInheritedFromIComparable(this Type type) 124 | { 125 | return type.GetInterfaces().Any(i => i == typeof(IComparable)); 126 | } 127 | } -------------------------------------------------------------------------------- /src/Helpers/CollectionChangedListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Specialized; 3 | 4 | namespace WinUI.TableView.Helpers; 5 | 6 | /// 7 | /// Listens for collection changed events using a weak reference to the source. 8 | /// 9 | /// The type of the source object. 10 | internal class CollectionChangedListener where TSource : class 11 | { 12 | private readonly WeakReference _source; 13 | private readonly INotifyCollectionChanged _notifyCollection; 14 | private readonly Action? _onEventAction; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The source object to listen for changes. 20 | /// The collection that notifies about changes. 21 | /// The action to perform when the collection changes. 22 | public CollectionChangedListener(TSource source, 23 | INotifyCollectionChanged notifyCollection, 24 | Action onEventAction) 25 | { 26 | ArgumentNullException.ThrowIfNull(source); 27 | ArgumentNullException.ThrowIfNull(notifyCollection); 28 | ArgumentNullException.ThrowIfNull(onEventAction); 29 | 30 | _source = new WeakReference(source); 31 | _notifyCollection = notifyCollection; 32 | _onEventAction = onEventAction; 33 | _notifyCollection.CollectionChanged += OnCollectionChanged; 34 | } 35 | 36 | /// 37 | /// Handles the collection changed event. 38 | /// 39 | /// The event sender. 40 | /// The event arguments. 41 | private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) 42 | { 43 | if (_source.TryGetTarget(out var target)) 44 | { 45 | _onEventAction?.Invoke(sender, e); // Call registered action 46 | } 47 | else 48 | { 49 | Detach(); // Detach from event 50 | } 51 | } 52 | 53 | /// 54 | /// Detaches the listener from the collection changed event. 55 | /// 56 | public void Detach() 57 | { 58 | _notifyCollection.CollectionChanged -= OnCollectionChanged; 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/Helpers/DateTimeFormatHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using Windows.Globalization.DateTimeFormatting; 8 | using Windows.System.UserProfile; 9 | using WinUI.TableView.Extensions; 10 | 11 | namespace WinUI.TableView.Helpers; 12 | 13 | /// 14 | /// Provides helper methods for formatting Date and Time values. 15 | /// 16 | internal static class DateTimeFormatHelper 17 | { 18 | private const string _12HourClock = "12HourClock"; 19 | private const string _24HourClock = "24HourClock"; 20 | private static readonly Dictionary<(string Format, string? Clock), DateTimeFormatter> _formatters = []; 21 | 22 | /// 23 | /// Sets the formatted text for a TextBlock based on its value and format. 24 | /// 25 | /// The TextBlock to set the formatted text for. 26 | private static void SetFormattedText(TextBlock textBlock) 27 | { 28 | var value = GetValue(textBlock); 29 | var format = GetFormat(textBlock); 30 | 31 | try 32 | { 33 | if (value is not null && format is _12HourClock or _24HourClock) 34 | { 35 | var formatter = GetDateTimeFormatter("shorttime", format); 36 | var dateTimeOffset = value switch 37 | { 38 | TimeSpan timeSpan => timeSpan.ToDateTimeOffset(), 39 | TimeOnly timeOnly => timeOnly.ToDateTimeOffset(), 40 | DateTime dateTime => dateTime.ToDateTimeOffset(), 41 | DateTimeOffset => (DateTimeOffset)value, 42 | _ => throw new FormatException() 43 | }; 44 | 45 | textBlock.Text = formatter.Format(dateTimeOffset); 46 | } 47 | else if (value is not null) 48 | { 49 | var formatter = GetDateTimeFormatter(format); 50 | 51 | var dateTimeOffset = value switch 52 | { 53 | DateOnly dateOnly => dateOnly.ToDateTimeOffset(), 54 | DateTime dateTime => dateTime.ToDateTimeOffset(), 55 | DateTimeOffset => (DateTimeOffset)value, 56 | _ => throw new FormatException() 57 | }; 58 | 59 | textBlock.Text = formatter.Format(dateTimeOffset); 60 | } 61 | else 62 | { 63 | textBlock.Text = value?.ToString(); 64 | } 65 | } 66 | catch (Exception ex) 67 | { 68 | Debug.WriteLine($"Unable to format value. Error: {ex.Message}"); 69 | throw; 70 | } 71 | } 72 | 73 | /// 74 | /// Gets a DateTimeFormatter for the specified format and clock. 75 | /// 76 | internal static DateTimeFormatter GetDateTimeFormatter(string strFormat, string? strClock = null) 77 | { 78 | if (_formatters.TryGetValue((strFormat, strClock), out var cacheFormatter)) 79 | { 80 | return cacheFormatter; 81 | } 82 | 83 | var formatter = new DateTimeFormatter(strFormat); 84 | var result = new DateTimeFormatter( 85 | strFormat, 86 | formatter.Languages, 87 | formatter.GeographicRegion, 88 | formatter.Calendar, 89 | strClock ?? formatter.Clock); 90 | 91 | _formatters[(strFormat, strClock)] = result; 92 | 93 | return result; 94 | } 95 | 96 | /// 97 | /// Handles changes to the Value attached property. 98 | /// 99 | private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 100 | { 101 | if (d is TextBlock textBlock) 102 | { 103 | SetFormattedText(textBlock); 104 | } 105 | } 106 | 107 | /// 108 | /// Handles changes to the Format attached property. 109 | /// 110 | private static void OnFormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 111 | { 112 | if (d is TextBlock textBlock) 113 | { 114 | SetFormattedText(textBlock); 115 | } 116 | } 117 | 118 | /// 119 | /// Gets the value of the Value attached property. 120 | /// 121 | public static object GetValue(DependencyObject obj) 122 | { 123 | return obj.GetValue(ValueProperty); 124 | } 125 | 126 | /// 127 | /// Sets the value of the Value attached property. 128 | /// 129 | public static void SetValue(DependencyObject obj, object value) 130 | { 131 | obj.SetValue(ValueProperty, value); 132 | } 133 | 134 | /// 135 | /// Gets the value of the Format attached property. 136 | /// 137 | public static string GetFormat(DependencyObject obj) 138 | { 139 | return (string)obj.GetValue(FormatProperty); 140 | } 141 | 142 | /// 143 | /// Sets the value of the Format attached property. 144 | /// 145 | public static void SetFormat(DependencyObject obj, string value) 146 | { 147 | obj.SetValue(FormatProperty, value); 148 | } 149 | 150 | /// 151 | /// Identifies the Value attached property. 152 | /// 153 | public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached("Value", typeof(object), typeof(DateTimeFormatHelper), new PropertyMetadata(default, OnValueChanged)); 154 | 155 | /// 156 | /// Identifies the Format attached property. 157 | /// 158 | public static readonly DependencyProperty FormatProperty = DependencyProperty.RegisterAttached("Format", typeof(string), typeof(DateTimeFormatHelper), new PropertyMetadata(default, OnFormatChanged)); 159 | } 160 | -------------------------------------------------------------------------------- /src/Helpers/KeyboardHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Input; 2 | using Windows.System; 3 | using Windows.UI.Core; 4 | 5 | namespace WinUI.TableView.Helpers; 6 | 7 | /// 8 | /// Provides helper methods for keyboard keys state checks. 9 | /// 10 | internal static class KeyboardHelper 11 | { 12 | /// 13 | /// Determines whether the Shift key is currently pressed. 14 | /// 15 | /// True if the Shift key is down; otherwise, false. 16 | public static bool IsShiftKeyDown() 17 | { 18 | var shiftKey = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift); 19 | return shiftKey is CoreVirtualKeyStates.Down or (CoreVirtualKeyStates.Down | CoreVirtualKeyStates.Locked); 20 | } 21 | 22 | /// 23 | /// Determines whether the Ctrl key is currently pressed. 24 | /// 25 | /// True if the Ctrl key is down; otherwise, false. 26 | public static bool IsCtrlKeyDown() 27 | { 28 | var ctrlKey = InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Control); 29 | return ctrlKey is CoreVirtualKeyStates.Down or (CoreVirtualKeyStates.Down | CoreVirtualKeyStates.Locked); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/IColumnFilterHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Interface for handling column filtering in a TableView. 7 | /// 8 | public interface IColumnFilterHandler 9 | { 10 | /// 11 | /// Gets or sets the selected values for the filter per column. 12 | /// 13 | IDictionary> SelectedValues { get; } 14 | 15 | /// 16 | /// Get the filter items for the specified column. 17 | /// 18 | /// The column for which to prepare filter items. 19 | /// The search text to filter the items. 20 | IList GetFilterItems(TableViewColumn column, string? searchText); 21 | 22 | /// 23 | /// Applies the filter to the specified column. 24 | /// 25 | /// The column to which the filter is applied. 26 | void ApplyFilter(TableViewColumn column); 27 | 28 | /// 29 | /// Clears the filter from the specified column. 30 | /// 31 | /// The column from which the filter is cleared. 32 | void ClearFilter(TableViewColumn? column); 33 | 34 | /// 35 | /// Determines whether the specified item passes the filter for the specified column. 36 | /// 37 | /// The column for which the filter is applied. 38 | /// The item to check. 39 | /// True if the item passes the filter; otherwise, false. 40 | bool Filter(TableViewColumn column, object? item); 41 | } 42 | -------------------------------------------------------------------------------- /src/ItemsSource/CollectionView.Deffer.cs: -------------------------------------------------------------------------------- 1 | using Windows.Foundation; 2 | 3 | namespace WinUI.TableView; 4 | 5 | partial class CollectionView 6 | { 7 | private uint _deferCounter = 0; 8 | 9 | /// 10 | /// Creates a deferral that prevents the view from listening 11 | /// to changes until it completes. 12 | /// 13 | /// This only defers collection changes raised on 14 | /// . When all deferrals 15 | /// complete, the collection is fully refreshed, and a 16 | /// vector change is invoked using the reset action. 17 | /// 18 | public Deferral DeferRefresh() 19 | { 20 | _deferCounter++; 21 | 22 | return new Deferral(OnDeferralCompleted); 23 | } 24 | 25 | private void OnDeferralCompleted() 26 | { 27 | _deferCounter--; 28 | if (_deferCounter == 0) 29 | HandleSourceChanged(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ItemsSource/CollectionView.Events.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Data; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | using Windows.Foundation.Collections; 6 | 7 | namespace WinUI.TableView; 8 | 9 | partial class CollectionView 10 | { 11 | /// 12 | /// Currently selected item changing event 13 | /// 14 | /// event args 15 | private void OnCurrentChanging(CurrentChangingEventArgs e) 16 | { 17 | if (_deferCounter > 0) 18 | { 19 | return; 20 | } 21 | 22 | CurrentChanging?.Invoke(this, e); 23 | } 24 | 25 | /// 26 | /// Currently selected item changed event 27 | /// 28 | private void OnCurrentChanged() 29 | { 30 | if (_deferCounter > 0) 31 | { 32 | return; 33 | } 34 | 35 | CurrentChanged?.Invoke(this, null!); 36 | 37 | // ReSharper disable once ExplicitCallerInfoArgument 38 | OnPropertyChanged(nameof(CurrentItem)); 39 | } 40 | 41 | /// 42 | /// Vector changed event 43 | /// 44 | /// event args 45 | private void OnVectorChanged(IVectorChangedEventArgs e) 46 | { 47 | if (_deferCounter > 0) 48 | { 49 | return; 50 | } 51 | 52 | VectorChanged?.Invoke(this, e); 53 | 54 | // ReSharper disable once ExplicitCallerInfoArgument 55 | OnPropertyChanged(nameof(Count)); 56 | } 57 | 58 | /// 59 | /// Property changed event invoker 60 | /// 61 | /// name of the property that changed 62 | private void OnPropertyChanged([CallerMemberName] string propertyName = null!) 63 | { 64 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 65 | } 66 | 67 | /// 68 | /// Occurs when the current item has changed. 69 | /// 70 | public event EventHandler? CurrentChanged; 71 | 72 | /// 73 | /// Occurs when the current item is changing. 74 | /// 75 | public event CurrentChangingEventHandler? CurrentChanging; 76 | 77 | /// 78 | /// Occurs when the vector has changed. 79 | /// 80 | public event VectorChangedEventHandler? VectorChanged; 81 | 82 | /// 83 | /// Occurs when a property value changes. 84 | /// 85 | public event PropertyChangedEventHandler? PropertyChanged; 86 | } 87 | -------------------------------------------------------------------------------- /src/ItemsSource/CollectionView.Properties.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Data; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Collections.Specialized; 5 | using Windows.Foundation.Collections; 6 | 7 | namespace WinUI.TableView; 8 | 9 | partial class CollectionView 10 | { 11 | /// 12 | /// Gets or sets the source collection. 13 | /// 14 | public IList Source 15 | { 16 | get => _source; 17 | set 18 | { 19 | if (_source == value) return; 20 | 21 | if (_source is not null) DetachPropertyChangedHandlers(_source); 22 | 23 | _source = value; 24 | AttachPropertyChangedHandlers(_source); 25 | 26 | _collectionChangedListener?.Detach(); 27 | 28 | if (_source is INotifyCollectionChanged sourceNcc) 29 | { 30 | _collectionChangedListener = new(this, sourceNcc, OnSourceCollectionChanged); 31 | } 32 | 33 | HandleSourceChanged(); 34 | OnPropertyChanged(); 35 | } 36 | } 37 | 38 | /// 39 | /// Gets a value indicating whether this CollectionView can filter its items. 40 | /// 41 | public bool CanFilter => FilterDescriptions.Count > 0; 42 | 43 | /// 44 | /// Gets the collection of filter descriptions. 45 | /// 46 | public IList FilterDescriptions => _filterDescriptions; 47 | 48 | /// 49 | /// Gets the collection of sort descriptions. 50 | /// 51 | public IList SortDescriptions => _sortDescriptions; 52 | 53 | /// 54 | /// Gets or sets the item at the specified index. 55 | /// 56 | /// The zero-based index of the item to get or set. 57 | /// The item at the specified index. 58 | public object this[int index] 59 | { 60 | get => _view[index]; 61 | set => _view[index] = value; 62 | } 63 | 64 | /// 65 | /// Gets the collection groups. 66 | /// 67 | public IObservableVector? CollectionGroups { get; } = null; 68 | 69 | /// 70 | /// Gets or sets the current item in the view. 71 | /// 72 | public object CurrentItem 73 | { 74 | get => CurrentPosition > -1 && CurrentPosition < _view.Count ? _view[CurrentPosition] : null!; 75 | set => MoveCurrentTo(value); 76 | } 77 | 78 | /// 79 | /// Gets the current position of the item in the view. 80 | /// 81 | public int CurrentPosition { get; private set; } 82 | 83 | /// 84 | /// Gets a value indicating whether there are more items to load. 85 | /// 86 | public bool HasMoreItems => (_source as ISupportIncrementalLoading)?.HasMoreItems ?? false; 87 | 88 | /// 89 | /// Gets a value indicating whether the current item is after the last item in the view. 90 | /// 91 | public bool IsCurrentAfterLast => CurrentPosition >= _view.Count; 92 | 93 | /// 94 | /// Gets a value indicating whether the current item is before the first item in the view. 95 | /// 96 | public bool IsCurrentBeforeFirst => CurrentPosition < 0; 97 | 98 | /// 99 | /// Gets the number of items in the view. 100 | /// 101 | public int Count => _view.Count; 102 | 103 | /// 104 | /// Gets a value indicating whether the collection is read-only. 105 | /// 106 | public bool IsReadOnly => _source == null || _source.IsReadOnly; 107 | 108 | /// 109 | /// Gets or sets a value indicating whether live shaping is enabled. 110 | /// 111 | public bool AllowLiveShaping 112 | { 113 | get => _allowLiveShaping; 114 | set 115 | { 116 | if (_allowLiveShaping == value) return; 117 | 118 | _allowLiveShaping = value; 119 | if (_allowLiveShaping) 120 | AttachPropertyChangedHandlers(_source); 121 | else 122 | DetachPropertyChangedHandlers(_source); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/ItemsSource/ColumnFilterDescription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Represents a filter description for a specific column in TableView. 7 | /// 8 | internal class ColumnFilterDescription : FilterDescription 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The column to filter. 14 | /// The name of the property to filter by. 15 | /// The predicate to apply for filtering. 16 | public ColumnFilterDescription(TableViewColumn column, 17 | string? propertyName, 18 | Predicate predicate) 19 | : base(propertyName!, predicate) 20 | { 21 | Column = column; 22 | } 23 | 24 | /// 25 | /// Gets the column associated with this filter description. 26 | /// 27 | public TableViewColumn Column { get; } 28 | } 29 | -------------------------------------------------------------------------------- /src/ItemsSource/ColumnSortDescription.cs: -------------------------------------------------------------------------------- 1 | namespace WinUI.TableView; 2 | 3 | /// 4 | /// Represents a sort description for a specific column in TableView. 5 | /// 6 | internal class ColumnSortDescription : SortDescription 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | /// The column to sort. 12 | /// The name of the property to sort by. 13 | /// The direction of the sort. 14 | public ColumnSortDescription(TableViewColumn column, 15 | string? propertyName, 16 | SortDirection direction) 17 | : base(propertyName!, direction, null, null) 18 | { 19 | Column = column; 20 | } 21 | 22 | public override object? GetPropertyValue(object? item) 23 | { 24 | return Column.GetCellContent(item); 25 | } 26 | 27 | /// 28 | /// Gets the column associated with this sort description. 29 | /// 30 | public TableViewColumn Column { get; } 31 | } 32 | -------------------------------------------------------------------------------- /src/ItemsSource/FilterDescription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Describes a filter operation applied to TableView items. 7 | /// 8 | public class FilterDescription 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The name of the property to filter by. 14 | /// The predicate to apply for filtering. 15 | public FilterDescription(string? propertyName, 16 | Predicate predicate) 17 | { 18 | PropertyName = propertyName; 19 | Predicate = predicate; 20 | } 21 | 22 | /// 23 | /// Gets the name of the property to filter by. 24 | /// 25 | public string? PropertyName { get; } 26 | 27 | /// 28 | /// Gets the predicate to apply for filtering. 29 | /// 30 | public Predicate Predicate { get; } 31 | } 32 | -------------------------------------------------------------------------------- /src/ItemsSource/SortDescription.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace WinUI.TableView; 6 | 7 | /// 8 | /// Describes a sort operation applied to TableView items. 9 | /// 10 | public class SortDescription 11 | { 12 | /// 13 | /// Initializes a new instance of the class that describes 14 | /// a sort on the object itself 15 | /// 16 | /// The name of the property to sort by. 17 | /// The direction of the sort. 18 | /// An optional comparer to use for sorting. 19 | /// An optional delegate to extract the value to sort by. 20 | public SortDescription(string? propertyName, 21 | SortDirection direction, 22 | IComparer? comparer = null, 23 | Func? valueDelegate = null) 24 | { 25 | PropertyName = propertyName; 26 | Direction = direction; 27 | Comparer = comparer; 28 | ValueDelegate = valueDelegate; 29 | } 30 | 31 | /// 32 | /// Gets the value of the specified property from the given item. 33 | /// 34 | /// The item to get the property value from. 35 | /// The value of the property. 36 | public virtual object? GetPropertyValue(object? item) 37 | { 38 | if (ValueDelegate is not null) 39 | { 40 | return ValueDelegate(item); 41 | } 42 | else if (PropertyName is not null) 43 | { 44 | return item?.GetType() 45 | .GetProperty(PropertyName)? 46 | .GetValue(item); 47 | } 48 | else 49 | { 50 | return default!; 51 | } 52 | } 53 | 54 | /// 55 | /// Compares two objects based on the sort description. 56 | /// 57 | /// The first object to compare. 58 | /// The second object to compare. 59 | /// An integer that indicates the relative order of the objects being compared. 60 | public int Compare(object? x, object? y) 61 | { 62 | return (Comparer ?? Comparer.Default).Compare(x, y); 63 | } 64 | 65 | /// 66 | /// Gets the name of the property to sort by. 67 | /// 68 | public string? PropertyName { get; } 69 | 70 | /// 71 | /// Gets the direction of the sort. 72 | /// 73 | public SortDirection Direction { get; } 74 | 75 | /// 76 | /// Gets the comparer to use for sorting. 77 | /// 78 | public IComparer? Comparer { get; } 79 | 80 | /// 81 | /// Gets the delegate to extract the value to sort by. 82 | /// 83 | public Func? ValueDelegate { get; } 84 | } 85 | -------------------------------------------------------------------------------- /src/ItemsSource/SortDirection.cs: -------------------------------------------------------------------------------- 1 | namespace WinUI.TableView; 2 | 3 | /// 4 | /// Sort direction enum 5 | /// 6 | public enum SortDirection 7 | { 8 | /// 9 | /// Ascending order (eg. abc...) 10 | /// 11 | Ascending = 0, 12 | 13 | /// 14 | /// Descending order (eg. zyx...) 15 | /// 16 | Descending = 1 17 | } -------------------------------------------------------------------------------- /src/ItemsSource/VectorChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Windows.Foundation.Collections; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Vector changed EventArgs 7 | /// 8 | /// 9 | internal partial class VectorChangedEventArgs : IVectorChangedEventArgs 10 | { 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// collection change type 14 | /// index of item changed 15 | /// item changed 16 | public VectorChangedEventArgs(CollectionChange cc, int index = -1, object? item = null!) 17 | { 18 | CollectionChange = cc; 19 | Index = (uint)index; 20 | Item = item; 21 | } 22 | 23 | /// 24 | /// Gets the type of change that occurred in the vector. 25 | /// 26 | /// 27 | /// The type of change in the vector. 28 | /// 29 | public CollectionChange CollectionChange { get; } 30 | 31 | /// 32 | /// Gets the changed item. 33 | /// 34 | public object? Item { get; } 35 | 36 | /// 37 | /// Gets the position where the change occurred in the vector. 38 | /// 39 | /// 40 | /// The zero-based position where the change occurred in the vector, if applicable. 41 | /// 42 | public uint Index { get; } 43 | } -------------------------------------------------------------------------------- /src/Strings/de-DE/WinUI.TableView.resw: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Datum auswählen... 122 | 123 | 124 | (Leer) 125 | 126 | 127 | Abbruch 128 | 129 | 130 | Filter löschen 131 | 132 | 133 | Ok 134 | 135 | 136 | (Alles auswählen) 137 | 138 | 139 | aufsteigend sortieren 140 | 141 | 142 | Sortierung aufheben 143 | 144 | 145 | absteigend sortieren 146 | 147 | 148 | Zeit auswählen... 149 | 150 | 151 | Ausgewählte Zeilen in di Zwischenablage kopieren 152 | 153 | 154 | Kopieren mit Überschriften 155 | 156 | 157 | Mit Überschriften in die Zwischenablage kopieren 158 | 159 | 160 | Auswahl aufheben 161 | 162 | 163 | Auswahl aufheben 164 | 165 | 166 | Alles nach CSV exportieren 167 | 168 | 169 | Ausgewählte Zeilen nach CSV exportieren 170 | 171 | 172 | Alle Zeilen auswählen 173 | 174 | 175 | Alles auswählen 176 | 177 | 178 | Suchen... 179 | 180 | 181 | Kopieren 182 | 183 | -------------------------------------------------------------------------------- /src/Strings/en-US/WinUI.TableView.resw: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Pick a date 122 | 123 | 124 | (Blank) 125 | 126 | 127 | Cancel 128 | 129 | 130 | Clear Filter 131 | 132 | 133 | Ok 134 | 135 | 136 | (Select All) 137 | 138 | 139 | Sort Ascending 140 | 141 | 142 | Clear Sorting 143 | 144 | 145 | Sort Descending 146 | 147 | 148 | Pick a time 149 | 150 | 151 | Copy the selected row's content to clipboard. 152 | 153 | 154 | Copy with Headers 155 | 156 | 157 | Copy the selected row's content including column headers to clipboard. 158 | 159 | 160 | Deselect All 161 | 162 | 163 | Deselect all rows. 164 | 165 | 166 | Export All to CSV 167 | 168 | 169 | Export Selected to CSV 170 | 171 | 172 | Select all rows. 173 | 174 | 175 | Select All 176 | 177 | 178 | Search... 179 | 180 | 181 | Copy 182 | 183 | -------------------------------------------------------------------------------- /src/Strings/es-ES/WinUI.TableView.resw: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Seleccionar una fecha 122 | 123 | 124 | (Vacías) 125 | 126 | 127 | Cancelar 128 | 129 | 130 | Borrar filtro 131 | 132 | 133 | (Seleccionar todo) 134 | 135 | 136 | Borrar ordenación 137 | 138 | 139 | Orden ascendente 140 | 141 | 142 | Orden descendente 143 | 144 | 145 | Copiar las filas seleccionadas al portapapeles. 146 | 147 | 148 | Copiar incluyendo los encabezados 149 | 150 | 151 | Copiar las filas seleccionadas incluyendo los encabezadso al portapapeles. 152 | 153 | 154 | Copiar 155 | 156 | 157 | Buscar... 158 | 159 | 160 | Seleccionar todas las filas. 161 | 162 | 163 | Seleccionar todo 164 | 165 | 166 | Exportar tabla a CSV 167 | 168 | 169 | Exportar selección a CSV 170 | 171 | 172 | Eliminar selección 173 | 174 | 175 | Deseleccionar todas las filas. 176 | 177 | 178 | Aceptar 179 | 180 | 181 | Seleccionar una hora 182 | 183 | -------------------------------------------------------------------------------- /src/Strings/ru-RU/WinUI.TableView.resw: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Выберите дату 122 | 123 | 124 | (Пусто) 125 | 126 | 127 | Отменить 128 | 129 | 130 | Очистить фильтр 131 | 132 | 133 | Ok 134 | 135 | 136 | (Выбрать всё) 137 | 138 | 139 | Сорт. по возрастанию 140 | 141 | 142 | Убрать сорт. 143 | 144 | 145 | Сорт. по убыванию 146 | 147 | 148 | Выберите время 149 | 150 | 151 | Copy the selected row's content to clipboard. 152 | 153 | 154 | Copy with Headers 155 | 156 | 157 | Скопируйте содержимое выбранных строк в буфер обмена. 158 | 159 | 160 | Отменить выбор всех 161 | 162 | 163 | Отменить выбор всех строк. 164 | 165 | 166 | Export All to CSV 167 | 168 | 169 | Экспортировать всё в CSV 170 | 171 | 172 | Выбрать все строки. 173 | 174 | 175 | Выбрать всё 176 | 177 | 178 | Искать... 179 | 180 | 181 | Копировать 182 | 183 | -------------------------------------------------------------------------------- /src/TableViewCellSlot.cs: -------------------------------------------------------------------------------- 1 | namespace WinUI.TableView; 2 | 3 | /// 4 | /// Represents a slot of a TableView cell, identified by its row and column indices. 5 | /// 6 | public readonly record struct TableViewCellSlot(int Row, int Column); 7 | -------------------------------------------------------------------------------- /src/TableViewCellsPresenter.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.WinUI; 2 | using Microsoft.UI; 3 | using Microsoft.UI.Xaml; 4 | using Microsoft.UI.Xaml.Controls; 5 | using Microsoft.UI.Xaml.Media; 6 | using Microsoft.UI.Xaml.Shapes; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | namespace WinUI.TableView; 11 | 12 | /// 13 | /// Represents a presenter for the cells in a TableView row. 14 | /// 15 | public partial class TableViewCellsPresenter : Control 16 | { 17 | private StackPanel? _stackPanel; 18 | private Rectangle? _v_gridLine; 19 | private Rectangle? _h_gridLine; 20 | 21 | /// 22 | /// Initializes a new instance of the TableViewCellsPresenter class. 23 | /// 24 | public TableViewCellsPresenter() 25 | { 26 | DefaultStyleKey = typeof(TableViewCellsPresenter); 27 | } 28 | 29 | protected override void OnApplyTemplate() 30 | { 31 | base.OnApplyTemplate(); 32 | 33 | _stackPanel = GetTemplateChild("StackPanel") as StackPanel; 34 | _v_gridLine = GetTemplateChild("VerticalGridLine") as Rectangle; 35 | _h_gridLine = GetTemplateChild("HorizontalGridLine") as Rectangle; 36 | 37 | TableViewRow = this.FindAscendant(); 38 | TableView = TableViewRow?.TableView; 39 | 40 | #if !WINDOWS 41 | TableView?.EnsureCells(); 42 | #else 43 | TableViewRow?.EnsureCells(); 44 | #endif 45 | EnsureGridLines(); 46 | } 47 | 48 | /// 49 | /// Ensures grid lines are applied to the cells. 50 | /// 51 | internal void EnsureGridLines() 52 | { 53 | if (TableView is null) return; 54 | 55 | if (_h_gridLine is not null) 56 | { 57 | _h_gridLine.Fill = TableView.HorizontalGridLinesStroke; 58 | _h_gridLine.Height = TableView.HorizontalGridLinesStrokeThickness; 59 | _h_gridLine.Visibility = TableView.GridLinesVisibility is TableViewGridLinesVisibility.All or TableViewGridLinesVisibility.Horizontal 60 | ? Visibility.Visible : Visibility.Collapsed; 61 | 62 | if (_v_gridLine is not null) 63 | { 64 | _v_gridLine.Fill = TableView.GridLinesVisibility is TableViewGridLinesVisibility.All or TableViewGridLinesVisibility.Vertical 65 | ? TableView.VerticalGridLinesStroke : new SolidColorBrush(Colors.Transparent); 66 | _v_gridLine.Width = TableView.VerticalGridLinesStrokeThickness; 67 | _v_gridLine.Visibility = TableView.HeaderGridLinesVisibility is TableViewGridLinesVisibility.All or TableViewGridLinesVisibility.Vertical 68 | || TableView.GridLinesVisibility is TableViewGridLinesVisibility.All or TableViewGridLinesVisibility.Vertical 69 | ? Visibility.Visible : Visibility.Collapsed; 70 | } 71 | } 72 | 73 | foreach (var cell in Cells) 74 | { 75 | cell.EnsureGridLines(); 76 | } 77 | } 78 | 79 | /// 80 | /// Gets the collection of child elements. 81 | /// 82 | internal UIElementCollection Children => _stackPanel?.Children!; 83 | 84 | /// 85 | /// Gets the list of cells in the presenter. 86 | /// 87 | public IList Cells => _stackPanel?.Children.OfType().ToList()!; 88 | 89 | /// 90 | /// Gets or sets the TableViewRow associated with the presenter. 91 | /// 92 | public TableViewRow? TableViewRow { get; private set; } 93 | 94 | /// 95 | /// Gets or sets the TableView associated with the presenter. 96 | /// 97 | public TableView? TableView { get; private set; } 98 | } 99 | -------------------------------------------------------------------------------- /src/TableViewColumnHeader.FilterItem.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace WinUI.TableView; 4 | 5 | /// 6 | /// Represents a filter item used in the options flyout of a TableViewColumnHeader. 7 | /// 8 | public partial class TableViewFilterItem : INotifyPropertyChanged 9 | { 10 | public event PropertyChangedEventHandler? PropertyChanged; 11 | 12 | private bool _isSelected; 13 | 14 | /// 15 | /// Initializes a new instance of the FilterItem class. 16 | /// 17 | /// Indicates whether the filter item is selected. 18 | /// The value of the filter item. 19 | public TableViewFilterItem(bool isSelected, object value) 20 | { 21 | IsSelected = isSelected; 22 | Value = value; 23 | } 24 | 25 | /// 26 | /// Gets or sets a value indicating whether the filter item is selected. 27 | /// 28 | public bool IsSelected 29 | { 30 | get => _isSelected; 31 | set 32 | { 33 | _isSelected = value; 34 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected))); 35 | } 36 | } 37 | 38 | /// 39 | /// Gets the value of the filter item. 40 | /// 41 | public object Value { get; } 42 | } 43 | -------------------------------------------------------------------------------- /src/TableViewColumnHeader.OptionsFlyoutViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Input; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using SD = WinUI.TableView.SortDirection; 7 | 8 | namespace WinUI.TableView; 9 | 10 | public partial class TableViewColumnHeader 11 | { 12 | /// 13 | /// ViewModel for the options flyout in the TableViewColumnHeader. 14 | /// 15 | private partial class OptionsFlyoutViewModel : INotifyPropertyChanged 16 | { 17 | public event PropertyChangedEventHandler? PropertyChanged; 18 | private string? _filterText; 19 | private bool _canFilter = true; 20 | private IList _filterItems = []; 21 | private bool _canSetState = true; 22 | 23 | /// 24 | /// Initializes a new instance of the OptionsFlyoutViewModel class. 25 | /// 26 | /// The TableView associated with the ViewModel. 27 | /// The TableViewColumnHeader associated with the ViewModel. 28 | public OptionsFlyoutViewModel(TableView tableView, TableViewColumnHeader columnHeader) 29 | { 30 | TableView = tableView; 31 | ColumnHeader = columnHeader; 32 | InitializeCommands(); 33 | } 34 | 35 | /// 36 | /// Initializes the commands for the ViewModel. 37 | /// 38 | private void InitializeCommands() 39 | { 40 | SortAscendingCommand.ExecuteRequested += delegate { ColumnHeader.DoSort(SD.Ascending); }; 41 | SortAscendingCommand.CanExecuteRequested += (_, e) => e.CanExecute = ColumnHeader.CanSort && ColumnHeader.Column?.SortDirection != SD.Ascending; 42 | 43 | SortDescendingCommand.ExecuteRequested += delegate { ColumnHeader.DoSort(SD.Descending); }; 44 | SortDescendingCommand.CanExecuteRequested += (_, e) => e.CanExecute = ColumnHeader.CanSort && ColumnHeader.Column?.SortDirection != SD.Descending; 45 | 46 | ClearSortingCommand.ExecuteRequested += delegate { ColumnHeader.ClearSortingWithEvent(); }; 47 | ClearSortingCommand.CanExecuteRequested += (_, e) => e.CanExecute = ColumnHeader.Column?.SortDirection is not null; 48 | 49 | ClearFilterCommand.ExecuteRequested += delegate { ColumnHeader.ClearFilter(); }; 50 | ClearFilterCommand.CanExecuteRequested += (_, e) => e.CanExecute = ColumnHeader.Column?.IsFiltered is true; 51 | 52 | OkCommand.ExecuteRequested += delegate 53 | { 54 | ColumnHeader.HideFlyout(); 55 | ColumnHeader.ApplyFilter(); 56 | }; 57 | 58 | CancelCommand.ExecuteRequested += delegate { ColumnHeader.HideFlyout(); }; 59 | } 60 | 61 | /// 62 | /// Attaches property changed handlers to the filter items. 63 | /// 64 | private void AttachPropertyChangedHandlers() 65 | { 66 | if (_filterItems?.Count > 0) 67 | { 68 | foreach (var item in _filterItems) 69 | { 70 | item.PropertyChanged += OnFilterItemPropertyChanged; 71 | } 72 | } 73 | } 74 | 75 | /// 76 | /// Detaches property changed handlers from the filter items. 77 | /// 78 | private void DetachPropertyChangedHandlers() 79 | { 80 | if (_filterItems?.Count > 0) 81 | { 82 | foreach (var item in _filterItems) 83 | { 84 | item.PropertyChanged -= OnFilterItemPropertyChanged; 85 | } 86 | } 87 | } 88 | 89 | /// 90 | /// Handles the PropertyChanged event for filter items. 91 | /// 92 | private void OnFilterItemPropertyChanged(object? sender, PropertyChangedEventArgs e) 93 | { 94 | SetSelectAllCheckBoxState(); 95 | } 96 | 97 | /// 98 | /// Gets the TableView associated with the ViewModel. 99 | /// 100 | public TableView TableView { get; } 101 | 102 | /// 103 | /// Gets the TableViewColumnHeader associated with the ViewModel. 104 | /// 105 | public TableViewColumnHeader ColumnHeader { get; } 106 | 107 | /// 108 | /// Gets or sets the filter items. 109 | /// 110 | public IList FilterItems 111 | { 112 | get => _filterItems; 113 | set 114 | { 115 | if (_filterItems == value) return; 116 | 117 | DetachPropertyChangedHandlers(); 118 | _filterItems = value; 119 | AttachPropertyChangedHandlers(); 120 | SetSelectAllCheckBoxState(); 121 | OnPropertyChanged(); 122 | } 123 | } 124 | 125 | /// 126 | /// Gets the selected values for the filter. 127 | /// 128 | public List SelectedValues { get; set; } = []; 129 | 130 | /// 131 | /// Sets the state of the select all checkbox. 132 | /// 133 | private void SetSelectAllCheckBoxState() 134 | { 135 | if (ColumnHeader._selectAllCheckBox is null || !_canSetState) 136 | { 137 | return; 138 | } 139 | 140 | ColumnHeader._selectAllCheckBox.IsChecked = _filterItems.All(x => x.IsSelected) 141 | ? true 142 | : _filterItems.All(x => !x.IsSelected) 143 | ? false 144 | : null; 145 | } 146 | 147 | /// 148 | /// Sets the state of the filter items. 149 | /// 150 | /// The state to set. 151 | internal void SetFilterItemsState(bool isSelected) 152 | { 153 | _canSetState = false; 154 | 155 | foreach (var item in FilterItems) 156 | { 157 | item.IsSelected = isSelected; 158 | } 159 | 160 | _canSetState = true; 161 | } 162 | 163 | /// 164 | /// Raises the PropertyChanged event. 165 | /// 166 | /// The name of the property that changed. 167 | private void OnPropertyChanged([CallerMemberName] string? propertyName = default) 168 | { 169 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 170 | } 171 | 172 | /// 173 | /// Gets the command to sort in ascending order. 174 | /// 175 | public StandardUICommand SortAscendingCommand { get; } = new() { Label = TableViewLocalizedStrings.SortAscending }; 176 | 177 | /// 178 | /// Gets the command to sort in descending order. 179 | /// 180 | public StandardUICommand SortDescendingCommand { get; } = new() { Label = TableViewLocalizedStrings.SortDescending }; 181 | 182 | /// 183 | /// Gets the command to clear sorting. 184 | /// 185 | public StandardUICommand ClearSortingCommand { get; } = new() { Label = TableViewLocalizedStrings.ClearSorting }; 186 | 187 | /// 188 | /// Gets the command to clear the filter. 189 | /// 190 | public StandardUICommand ClearFilterCommand { get; } = new() { Label = TableViewLocalizedStrings.ClearFilter }; 191 | 192 | /// 193 | /// Gets the command to confirm the filter. 194 | /// 195 | public StandardUICommand OkCommand { get; } = new() { Label = TableViewLocalizedStrings.Ok }; 196 | 197 | /// 198 | /// Gets the command to cancel the filter. 199 | /// 200 | public StandardUICommand CancelCommand { get; } = new() { Label = TableViewLocalizedStrings.Cancel }; 201 | } 202 | } -------------------------------------------------------------------------------- /src/TableViewColumnsCollection.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Collections.Specialized; 6 | using System.ComponentModel; 7 | using System.Linq; 8 | 9 | namespace WinUI.TableView; 10 | 11 | /// 12 | /// Represents a collection of columns in a TableView. 13 | /// 14 | public partial class TableViewColumnsCollection : ObservableCollection 15 | { 16 | /// 17 | /// Occurs when a property of a column in the collection changes. 18 | /// 19 | internal event EventHandler? ColumnPropertyChanged; 20 | 21 | /// 22 | /// Gets the list of visible columns in the collection. 23 | /// 24 | internal IList VisibleColumns => Items.Where(x => x.Visibility == Visibility.Visible).ToList(); 25 | 26 | protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 27 | { 28 | base.OnCollectionChanged(e); 29 | 30 | if (e.NewItems != null) 31 | { 32 | foreach (var column in e.NewItems.OfType()) 33 | { 34 | column.SetOwningCollection(this); 35 | column.SetOwningTableView(TableView!); 36 | } 37 | } 38 | 39 | if (e.OldItems != null) 40 | { 41 | foreach (var column in e.OldItems.OfType()) 42 | { 43 | column.SetOwningCollection(null!); 44 | column.SetOwningTableView(null!); 45 | } 46 | } 47 | } 48 | 49 | /// 50 | /// Handles the property changed event for a column. 51 | /// 52 | /// The column that changed. 53 | /// The name of the property that changed. 54 | internal void HandleColumnPropertyChanged(TableViewColumn column, string propertyName) 55 | { 56 | if (Items.Contains(column)) 57 | { 58 | var index = IndexOf(column); 59 | ColumnPropertyChanged?.Invoke(this, new TableViewColumnPropertyChangedEventArgs(column, propertyName, index)); 60 | } 61 | } 62 | 63 | /// 64 | /// Gets or sets the TableView associated with the collection. 65 | /// 66 | public TableView? TableView { get; internal set; } 67 | } 68 | -------------------------------------------------------------------------------- /src/TableViewCornerButtonMode.cs: -------------------------------------------------------------------------------- 1 | namespace WinUI.TableView; 2 | 3 | /// 4 | /// Specifies the mode of the corner button in a TableView. 5 | /// 6 | public enum TableViewCornerButtonMode 7 | { 8 | /// 9 | /// No button. 10 | /// 11 | None, 12 | 13 | /// 14 | /// Show Select All button. 15 | /// 16 | SelectAll, 17 | 18 | /// 19 | /// Show Options button. 20 | /// 21 | Options 22 | } 23 | -------------------------------------------------------------------------------- /src/TableViewGridLinesVisibility.cs: -------------------------------------------------------------------------------- 1 | namespace WinUI.TableView; 2 | 3 | /// 4 | /// Specifies the visibility of grid lines in a TableView. 5 | /// 6 | public enum TableViewGridLinesVisibility 7 | { 8 | /// 9 | /// Both horizontal and vertical grid lines are visible. 10 | /// 11 | All, 12 | 13 | /// 14 | /// Only horizontal grid lines are visible. 15 | /// 16 | Horizontal, 17 | 18 | /// 19 | /// No grid lines are visible. 20 | /// 21 | None, 22 | 23 | /// 24 | /// Only vertical grid lines are visible. 25 | /// 26 | Vertical 27 | } 28 | -------------------------------------------------------------------------------- /src/TableViewHeaderRow.OptionsFlyoutViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml; 2 | using Microsoft.UI.Xaml.Controls; 3 | using Microsoft.UI.Xaml.Input; 4 | using System; 5 | 6 | namespace WinUI.TableView; 7 | 8 | public partial class TableViewHeaderRow 9 | { 10 | /// 11 | /// ViewModel for the options flyout in the TableViewHeaderRow. 12 | /// 13 | private class OptionsFlyoutViewModel 14 | { 15 | /// 16 | /// Initializes a new instance of the OptionsFlyoutViewModel class. 17 | /// 18 | /// The TableView associated with the ViewModel. 19 | public OptionsFlyoutViewModel(TableView _tableView) 20 | { 21 | InitializeCommands(); 22 | TableView = _tableView; 23 | } 24 | 25 | /// 26 | /// Initializes the commands for the ViewModel. 27 | /// 28 | private void InitializeCommands() 29 | { 30 | SelectAllCommand.Description = TableViewLocalizedStrings.SelectAllCommandDescription; 31 | SelectAllCommand.ExecuteRequested += delegate { TableView.SelectAll(); }; 32 | SelectAllCommand.CanExecuteRequested += CanExecuteSelectAllCommand; 33 | 34 | DeselectAllCommand.Description = TableViewLocalizedStrings.DeselectAllCommandDescription; 35 | DeselectAllCommand.ExecuteRequested += delegate { TableView.DeselectAll(); }; 36 | DeselectAllCommand.CanExecuteRequested += CanExecuteDeselectAllCommand; 37 | 38 | CopyCommand.Description = TableViewLocalizedStrings.CopyCommandDescription; 39 | CopyCommand.ExecuteRequested += ExecuteCopyCommand; 40 | CopyCommand.CanExecuteRequested += CanExecuteCopyCommand; 41 | 42 | CopyWithHeadersCommand.Description = TableViewLocalizedStrings.CopyWithHeadersCommandDescription; 43 | CopyWithHeadersCommand.ExecuteRequested += delegate { TableView.CopyToClipboardInternal(true); }; 44 | CopyWithHeadersCommand.CanExecuteRequested += CanExecuteCopyWithHeadersCommand; 45 | 46 | ClearSortingCommand.ExecuteRequested += delegate { TableView.ClearAllSortingWithEvent(); }; 47 | ClearSortingCommand.CanExecuteRequested += CanExecuteClearSortingCommand; 48 | 49 | ClearFilterCommand.ExecuteRequested += delegate { TableView.FilterHandler.ClearFilter(default); }; 50 | ClearFilterCommand.CanExecuteRequested += CanExecuteClearFilterCommand; 51 | 52 | ExportAllToCSVCommand.ExecuteRequested += delegate { TableView.ExportAllToCSV(); }; 53 | 54 | ExportSelectedToCSVCommand.ExecuteRequested += delegate { TableView.ExportSelectedToCSV(); }; 55 | ExportSelectedToCSVCommand.CanExecuteRequested += CanExecuteExportSelectedToCSVCommand; 56 | } 57 | 58 | private void CanExecuteSelectAllCommand(XamlUICommand sender, CanExecuteRequestedEventArgs e) 59 | { 60 | e.CanExecute = !TableView.IsEditing && TableView.SelectionMode is ListViewSelectionMode.Multiple or ListViewSelectionMode.Extended; 61 | } 62 | 63 | private void CanExecuteDeselectAllCommand(XamlUICommand sender, CanExecuteRequestedEventArgs e) 64 | { 65 | e.CanExecute = !TableView.IsEditing && (TableView.SelectedItems.Count > 0 || TableView.SelectedCells.Count > 0); 66 | } 67 | 68 | private void ExecuteCopyCommand(XamlUICommand sender, ExecuteRequestedEventArgs e) 69 | { 70 | #if WINDOWS 71 | var focusedElement = FocusManager.GetFocusedElement(TableView.XamlRoot); 72 | #else 73 | var focusedElement = FocusManager.GetFocusedElement(); 74 | #endif 75 | if (focusedElement is FrameworkElement { Parent: TableViewCell }) 76 | { 77 | return; 78 | } 79 | 80 | TableView.CopyToClipboardInternal(false); 81 | } 82 | 83 | private void CanExecuteCopyCommand(XamlUICommand sender, CanExecuteRequestedEventArgs e) 84 | { 85 | e.CanExecute = TableView.SelectedItems.Count > 0 || TableView.SelectedCells.Count > 0 || TableView.CurrentCellSlot.HasValue; 86 | } 87 | 88 | private void CanExecuteCopyWithHeadersCommand(XamlUICommand sender, CanExecuteRequestedEventArgs e) 89 | { 90 | e.CanExecute = TableView.SelectedItems.Count > 0 || TableView.SelectedCells.Count > 0 || TableView.CurrentCellSlot.HasValue; 91 | } 92 | 93 | private void CanExecuteClearSortingCommand(XamlUICommand sender, CanExecuteRequestedEventArgs e) 94 | { 95 | e.CanExecute = !TableView.IsEditing && TableView.IsSorted; 96 | } 97 | 98 | private void CanExecuteClearFilterCommand(XamlUICommand sender, CanExecuteRequestedEventArgs e) 99 | { 100 | e.CanExecute = !TableView.IsEditing && TableView.IsFiltered; 101 | } 102 | 103 | private void CanExecuteExportSelectedToCSVCommand(XamlUICommand sender, CanExecuteRequestedEventArgs e) 104 | { 105 | e.CanExecute = !TableView.IsEditing && (TableView.SelectedItems.Count > 0 || TableView.SelectedCells.Count > 0 || TableView.CurrentCellSlot.HasValue); 106 | } 107 | 108 | /// 109 | /// Gets the command to select all rows. 110 | /// 111 | public StandardUICommand SelectAllCommand { get; } = new(StandardUICommandKind.SelectAll) { Label = TableViewLocalizedStrings.SelectAll }; 112 | 113 | /// 114 | /// Gets the command to deselect all rows. 115 | /// 116 | public StandardUICommand DeselectAllCommand { get; } = new() { Label = TableViewLocalizedStrings.DeselectAll }; 117 | 118 | /// 119 | /// Gets the command to copy the selected row's content to the clipboard. 120 | /// 121 | public StandardUICommand CopyCommand { get; } = new(StandardUICommandKind.Copy) { Label = TableViewLocalizedStrings.Copy }; 122 | 123 | /// 124 | /// Gets the command to copy the selected row's content including column headers to the clipboard. 125 | /// 126 | public StandardUICommand CopyWithHeadersCommand { get; } = new() { Label = TableViewLocalizedStrings.CopyWithHeaders }; 127 | 128 | /// 129 | /// Gets the command to clear sorting. 130 | /// 131 | public StandardUICommand ClearSortingCommand { get; } = new() { Label = TableViewLocalizedStrings.ClearSorting }; 132 | 133 | /// 134 | /// Gets the command to clear filters. 135 | /// 136 | public StandardUICommand ClearFilterCommand { get; } = new() { Label = TableViewLocalizedStrings.ClearFilter }; 137 | 138 | /// 139 | /// Gets the command to export all content to a CSV file. 140 | /// 141 | public StandardUICommand ExportAllToCSVCommand { get; } = new() { Label = TableViewLocalizedStrings.ExportAll }; 142 | 143 | /// 144 | /// Gets the command to export selected content to a CSV file. 145 | /// 146 | public StandardUICommand ExportSelectedToCSVCommand { get; } = new() { Label = TableViewLocalizedStrings.ExportSelected }; 147 | 148 | /// 149 | /// Gets the TableView associated with the ViewModel. 150 | /// 151 | public TableView TableView { get; } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/TableViewLocalizedStrings.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Windows.ApplicationModel.Resources; 2 | using System; 3 | 4 | namespace WinUI.TableView; 5 | 6 | /// 7 | /// Provides localized string resources for the TableView. 8 | /// 9 | internal partial class TableViewLocalizedStrings 10 | { 11 | private const string WinUI_TableView = "WinUI.TableView"; 12 | #if WINDOWS 13 | private static readonly ResourceManager _resourceManager = new(); 14 | #else 15 | private static readonly ResourceLoader _appResourceLoader = new(WinUI_TableView); 16 | private static readonly ResourceLoader _defaultResourceLoader = new($"{WinUI_TableView}/{WinUI_TableView}"); 17 | #endif 18 | 19 | static TableViewLocalizedStrings() 20 | { 21 | BlankFilterValue = GetValue(nameof(BlankFilterValue)); 22 | Cancel = GetValue(nameof(Cancel)); 23 | ClearFilter = GetValue(nameof(ClearFilter)); 24 | ClearSorting = GetValue(nameof(ClearSorting)); 25 | Copy = GetValue(nameof(Copy)); 26 | CopyCommandDescription = GetValue(nameof(CopyCommandDescription)); 27 | CopyWithHeaders = GetValue(nameof(CopyWithHeaders)); 28 | CopyWithHeadersCommandDescription = GetValue(nameof(CopyWithHeadersCommandDescription)); 29 | DatePickerPlaceholder = GetValue(nameof(DatePickerPlaceholder)); 30 | DeselectAll = GetValue(nameof(DeselectAll)); 31 | DeselectAllCommandDescription = GetValue(nameof(DeselectAllCommandDescription)); 32 | ExportAll = GetValue(nameof(ExportAll)); 33 | ExportSelected = GetValue(nameof(ExportSelected)); 34 | Ok = GetValue(nameof(Ok)); 35 | SearchBoxPlaceholder = GetValue(nameof(SearchBoxPlaceholder)); 36 | SelectAll = GetValue(nameof(SelectAll)); 37 | SelectAllCommandDescription = GetValue(nameof(SelectAllCommandDescription)); 38 | SelectAllParenthesized = GetValue(nameof(SelectAllParenthesized)); 39 | SortAscending = GetValue(nameof(SortAscending)); 40 | SortDescending = GetValue(nameof(SortDescending)); 41 | TimePickerPlaceholder = GetValue(nameof(TimePickerPlaceholder)); 42 | } 43 | 44 | private static string GetValue(string name) 45 | { 46 | #if WINDOWS 47 | var value = _resourceManager.MainResourceMap.TryGetValue($"{WinUI_TableView}/{name}"); 48 | value ??= _resourceManager.MainResourceMap.GetValue($"{WinUI_TableView}/{WinUI_TableView}/{name}"); 49 | 50 | return value.ValueAsString; 51 | #else 52 | if (_appResourceLoader.GetString(name) is { Length: > 0 } appValue) 53 | { 54 | return appValue; 55 | } 56 | else if (_defaultResourceLoader.GetString(name) is { Length: > 0 } defaultValue) 57 | { 58 | return defaultValue; 59 | } 60 | else 61 | { 62 | throw new InvalidOperationException("Should not happen."); 63 | } 64 | #endif 65 | } 66 | 67 | public static string BlankFilterValue { get; set; } 68 | public static string Cancel { get; set; } 69 | public static string ClearFilter { get; set; } 70 | public static string ClearSorting { get; set; } 71 | public static string Copy { get; set; } 72 | public static string CopyCommandDescription { get; set; } 73 | public static string CopyWithHeaders { get; set; } 74 | public static string CopyWithHeadersCommandDescription { get; set; } 75 | public static string DatePickerPlaceholder { get; set; } 76 | public static string DeselectAll { get; set; } 77 | public static string DeselectAllCommandDescription { get; set; } 78 | public static string ExportAll { get; set; } 79 | public static string ExportSelected { get; set; } 80 | public static string Ok { get; set; } 81 | public static string SearchBoxPlaceholder { get; set; } 82 | public static string SelectAll { get; set; } 83 | public static string SelectAllCommandDescription { get; set; } 84 | public static string SelectAllParenthesized { get; set; } 85 | public static string SortAscending { get; set; } 86 | public static string SortDescending { get; set; } 87 | public static string TimePickerPlaceholder { get; set; } 88 | } 89 | -------------------------------------------------------------------------------- /src/TableViewSelectionUnit.cs: -------------------------------------------------------------------------------- 1 | namespace WinUI.TableView; 2 | 3 | /// 4 | /// Specifies the selection unit for a TableView. 5 | /// 6 | public enum TableViewSelectionUnit 7 | { 8 | /// 9 | /// Selection can be either a cell or a row. 10 | /// 11 | CellOrRow, 12 | 13 | /// 14 | /// Selection is limited to cells. 15 | /// 16 | Cell, 17 | 18 | /// 19 | /// Selection is limited to rows. 20 | /// 21 | Row, 22 | } 23 | -------------------------------------------------------------------------------- /src/Tableview.Uno.cs: -------------------------------------------------------------------------------- 1 | #if !WINDOWS 2 | using Microsoft.UI.Xaml.Controls.Primitives; 3 | using Microsoft.UI.Xaml.Data; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using WinUI.TableView.Extensions; 9 | 10 | namespace WinUI.TableView; 11 | 12 | /// 13 | /// Partial class for TableView that contains Uno stuff. 14 | /// 15 | partial class TableView 16 | { 17 | private const BindingFlags BindingAttr = BindingFlags.NonPublic | BindingFlags.Instance; 18 | private PropertyInfo? _disableRaiseSelectionChangedPropertyInfo; 19 | private MethodInfo? _invokeSelectionChangedMethodInfo; 20 | 21 | private void SetDisableRaiseSelectionChanged(bool value) 22 | { 23 | _disableRaiseSelectionChangedPropertyInfo ??= typeof(Selector).GetProperty("DisableRaiseSelectionChanged", BindingAttr); 24 | _disableRaiseSelectionChangedPropertyInfo?.SetValue(this, value); 25 | } 26 | 27 | private void InvokeSelectionChanged(object[] removedItems, object[] addedItems) 28 | { 29 | _invokeSelectionChangedMethodInfo ??= typeof(Selector).GetMethod("InvokeSelectionChanged", BindingAttr); 30 | _invokeSelectionChangedMethodInfo?.Invoke(this, [removedItems, addedItems]); 31 | } 32 | 33 | private new void DeselectRange(ItemIndexRange itemIndexRange) 34 | { 35 | var removedItems = new List(); 36 | 37 | SetDisableRaiseSelectionChanged(true); 38 | { 39 | if (!itemIndexRange.IsValid(this)) 40 | { 41 | throw new IndexOutOfRangeException("The given item index range bounds are not valid."); 42 | } 43 | 44 | for (var index = itemIndexRange.FirstIndex; index <= itemIndexRange.LastIndex; index++) 45 | { 46 | var item = Items[index]; 47 | if (SelectedItems.Contains(item)) 48 | { 49 | removedItems.Add(item); 50 | SelectedItems.Remove(item); 51 | } 52 | } 53 | 54 | AdjustSelectedRanges(); 55 | } 56 | SetDisableRaiseSelectionChanged(false); 57 | 58 | InvokeSelectionChanged([.. removedItems], []); 59 | } 60 | 61 | private void AdjustSelectedRanges() 62 | { 63 | SelectedRanges.Clear(); 64 | 65 | if (SelectedItems.Count == 0) return; 66 | 67 | var selectedIndexes = SelectedItems.Select(Items.IndexOf).Order(); 68 | var start = selectedIndexes.First(); 69 | var prev = start; 70 | 71 | foreach (var index in selectedIndexes) 72 | { 73 | if (index != prev + 1) 74 | { 75 | var length = (uint)(prev - start + 1); 76 | SelectedRanges.Add(new ItemIndexRange(start, length)); 77 | start = index; 78 | } 79 | prev = index; 80 | } 81 | 82 | var finalLength = (uint)(prev - start + 1); 83 | SelectedRanges.Add(new ItemIndexRange(start, finalLength)); 84 | } 85 | 86 | private new void SelectRange(ItemIndexRange itemIndexRange) 87 | { 88 | var addedItems = new List(); 89 | 90 | SetDisableRaiseSelectionChanged(true); 91 | { 92 | if (!itemIndexRange.IsValid(this)) 93 | { 94 | throw new IndexOutOfRangeException("The given item index range bounds are not valid."); 95 | } 96 | 97 | for (var index = itemIndexRange.FirstIndex; index <= itemIndexRange.LastIndex; index++) 98 | { 99 | var item = Items[index]; 100 | if (!SelectedItems.Contains(item)) 101 | { 102 | addedItems.Add(item); 103 | SelectedItems.Add(item); 104 | } 105 | } 106 | 107 | AdjustSelectedRanges(); 108 | } 109 | SetDisableRaiseSelectionChanged(false); 110 | 111 | InvokeSelectionChanged([], [.. addedItems]); 112 | } 113 | 114 | private new IList SelectedRanges { get; } = []; 115 | } 116 | #endif -------------------------------------------------------------------------------- /src/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/Themes/TableViewCell.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/Themes/TableViewCellsPresenter.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 51 | -------------------------------------------------------------------------------- /src/Themes/TableViewRow.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/Themes/TableViewTimePicker.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 50 | 51 | -------------------------------------------------------------------------------- /src/WinUI.TableView.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0-desktop; 5 | net8.0-maccatalyst; 6 | net8.0-windows10.0.22621.0; 7 | net8.0-browserwasm; 8 | 9 | true 10 | true 11 | Library 12 | 13 | true 14 | enable 15 | true 16 | 17 | 18 | 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | WinUI.TableView 36 | true 37 | snupkg 38 | true 39 | TableView/DataGrid control for WinUI. 40 | WinUI, WinAppSDK, Windows, UNO, XAML, TableView, DataGrid 41 | WinUI TableView Control 42 | https://github.com/w-ahmad/WinUI.TableView 43 | https://github.com/w-ahmad/WinUI.TableView 44 | README.md 45 | MIT 46 | $(MSBuildThisFileDirectory)..\artifacts\NuGet\$(Configuration)\ 47 | w-ahmad - https://github.com/w-ahmad 48 | 49 | 50 | 51 | 52 | all 53 | runtime; build; native; contentfiles; analyzers 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/WinUI.TableView.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34408.163 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinUI.TableView", "WinUI.TableView.csproj", "{D8CCAC00-5BB4-40A9-AB9D-C5FD1888B1B7}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D8CCAC00-5BB4-40A9-AB9D-C5FD1888B1B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {D8CCAC00-5BB4-40A9-AB9D-C5FD1888B1B7}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {D8CCAC00-5BB4-40A9-AB9D-C5FD1888B1B7}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {D8CCAC00-5BB4-40A9-AB9D-C5FD1888B1B7}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {4272F7F4-4F05-444E-8852-B595F685371A} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "msbuild-sdks": { 3 | "Uno.Sdk": "5.6.51" 4 | } 5 | } 6 | --------------------------------------------------------------------------------