├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ └── build.yml ├── .gitignore ├── .nuke ├── build.schema.json └── parameters.json ├── DataBox.sln ├── LICENSE.TXT ├── README.md ├── azure-pipelines.yml ├── build.cmd ├── build.ps1 ├── build.sh ├── build ├── Avalonia.Desktop.props ├── Avalonia.Diagnostics.props ├── Avalonia.ReactiveUI.props ├── Avalonia.Themes.Fluent.props ├── Avalonia.props ├── Base.props ├── ReferenceAssemblies.props ├── SignAssembly.props ├── SourceLink.props ├── XUnit.props ├── build │ ├── Build.cs │ └── _build.csproj └── databox.public.snk ├── global.json ├── nuget.config ├── samples ├── DataBoxDataVirtualizationDemo │ ├── App.axaml │ ├── App.axaml.cs │ ├── DataBoxDataVirtualizationDemo.csproj │ ├── MainWindow.axaml │ ├── MainWindow.axaml.cs │ ├── Program.cs │ └── ViewModels │ │ ├── ItemProvider.cs │ │ ├── ItemViewModel.cs │ │ ├── MainWindowViewModel.cs │ │ └── ViewModelBase.cs ├── DataBoxDemo │ ├── App.axaml │ ├── App.axaml.cs │ ├── DataBoxDemo.csproj │ ├── MainWindow.axaml │ ├── MainWindow.axaml.cs │ ├── Program.cs │ └── ViewModels │ │ ├── ItemViewModel.cs │ │ ├── MainWindowViewModel.cs │ │ └── ViewModelBase.cs ├── VirtualPanelDemo.NetCore │ ├── Assets │ │ └── avalonia-logo.ico │ ├── Program.cs │ └── VirtualPanelDemo.NetCore.csproj └── VirtualPanelDemo │ ├── App.axaml │ ├── App.axaml.cs │ ├── MainView.axaml │ ├── MainView.axaml.cs │ ├── MainWindow.axaml │ ├── MainWindow.axaml.cs │ ├── ViewModels │ ├── ItemProvider.cs │ └── MainWindowViewModel.cs │ └── VirtualPanelDemo.csproj ├── src ├── DataBox.DataVirtualization │ ├── AsyncVirtualizingCollection.cs │ ├── DataBox.DataVirtualization.csproj │ ├── DataPage.cs │ ├── DataWrapper.cs │ ├── IItemsProvider.cs │ ├── LICENSE │ ├── README.txt │ └── VirtualizingCollection.cs ├── DataBox │ ├── Automation │ │ └── Peers │ │ │ ├── DataBoxAutomationPeer.cs │ │ │ ├── DataBoxCellAutomationPeer.cs │ │ │ ├── DataBoxColumnHeaderAutomationPeer.cs │ │ │ ├── DataBoxColumnHeadersPresenterAutomationPeer.cs │ │ │ └── DataBoxRowAutomationPeer.cs │ ├── Columns │ │ ├── DataBoxAutoCompleteColumn.cs │ │ ├── DataBoxBoundColumn.cs │ │ ├── DataBoxButtonColumn.cs │ │ ├── DataBoxCheckBoxColumn.cs │ │ ├── DataBoxContentColumn.cs │ │ ├── DataBoxDateColumn.cs │ │ ├── DataBoxEntryColumn.cs │ │ ├── DataBoxItemsColumn.cs │ │ ├── DataBoxLabelColumn.cs │ │ ├── DataBoxNumericColumn.cs │ │ ├── DataBoxProgressColumn.cs │ │ ├── DataBoxRangeColumn.cs │ │ ├── DataBoxSliderColumn.cs │ │ ├── DataBoxSwitchColumn.cs │ │ ├── DataBoxTemplateColumn.cs │ │ ├── DataBoxTextColumn.cs │ │ ├── DataBoxTimeColumn.cs │ │ └── DataBoxToggleColumn.cs │ ├── Controls │ │ └── DataBoxPanel.cs │ ├── DataBox.cs │ ├── DataBox.csproj │ ├── DataBoxCell.cs │ ├── DataBoxColumn.cs │ ├── DataBoxColumnHeader.cs │ ├── DataBoxGridLinesVisibility.cs │ ├── DataBoxRow.cs │ ├── Primitives │ │ ├── ActionObserver.cs │ │ ├── DataBoxCellsPresenter.cs │ │ ├── DataBoxColumnHeadersPresenter.cs │ │ ├── DataBoxRowsPresenter.cs │ │ └── Layout │ │ │ ├── DataBoxCellsLayout.cs │ │ │ └── DataBoxRowsLayout.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── Themes │ │ ├── Accents │ │ └── Fluent.axaml │ │ ├── Controls │ │ ├── DataBox.axaml │ │ ├── DataBoxCell.axaml │ │ ├── DataBoxCellsPresenter.axaml │ │ ├── DataBoxColumnHeader.axaml │ │ ├── DataBoxColumnHeadersPresenter.axaml │ │ ├── DataBoxPanel.axaml │ │ ├── DataBoxRow.axaml │ │ └── DataBoxRowsPresenter.axaml │ │ ├── DataBoxFluentTheme.axaml │ │ └── DataBoxFluentTheme.axaml.cs └── VirtualPanel │ ├── Properties │ └── AssemblyInfo.cs │ ├── VirtualPanel.cs │ └── VirtualPanel.csproj └── tests └── DataBox.UnitTests ├── DataBox.UnitTests.csproj └── Properties └── AssemblyInfo.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Default settings: 7 | # A newline ending every file 8 | # Use 4 spaces as indentation 9 | [*] 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # C# files 15 | [*.cs] 16 | # New line preferences 17 | csharp_new_line_before_open_brace = all 18 | csharp_new_line_before_else = true 19 | csharp_new_line_before_catch = true 20 | csharp_new_line_before_finally = true 21 | csharp_new_line_before_members_in_object_initializers = true 22 | csharp_new_line_before_members_in_anonymous_types = true 23 | csharp_new_line_between_query_expression_clauses = true 24 | 25 | # Indentation preferences 26 | csharp_indent_block_contents = true 27 | csharp_indent_braces = false 28 | csharp_indent_case_contents = true 29 | csharp_indent_switch_labels = true 30 | csharp_indent_labels = one_less_than_current 31 | 32 | # avoid this. unless absolutely necessary 33 | dotnet_style_qualification_for_field = false:suggestion 34 | dotnet_style_qualification_for_property = false:suggestion 35 | dotnet_style_qualification_for_method = false:suggestion 36 | dotnet_style_qualification_for_event = false:suggestion 37 | 38 | # prefer var 39 | csharp_style_var_for_built_in_types = true 40 | csharp_style_var_when_type_is_apparent = true 41 | csharp_style_var_elsewhere = true:suggestion 42 | 43 | # use language keywords instead of BCL types 44 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 45 | dotnet_style_predefined_type_for_member_access = true:suggestion 46 | 47 | # name all constant fields using PascalCase 48 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 49 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 50 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 51 | 52 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 53 | dotnet_naming_symbols.constant_fields.required_modifiers = const 54 | 55 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 56 | 57 | # static fields should have s_ prefix 58 | dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion 59 | dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields 60 | dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style 61 | 62 | dotnet_naming_symbols.static_fields.applicable_kinds = field 63 | dotnet_naming_symbols.static_fields.required_modifiers = static 64 | 65 | dotnet_naming_style.static_prefix_style.required_prefix = s_ 66 | dotnet_naming_style.static_prefix_style.capitalization = camel_case 67 | 68 | # internal and private fields should be _camelCase 69 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion 70 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 71 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 72 | 73 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 74 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal 75 | 76 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 77 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 78 | 79 | # use accessibility modifiers 80 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 81 | 82 | # Code style defaults 83 | dotnet_sort_system_directives_first = true 84 | csharp_preserve_single_line_blocks = true 85 | csharp_preserve_single_line_statements = false 86 | 87 | # Expression-level preferences 88 | dotnet_style_object_initializer = true:suggestion 89 | dotnet_style_collection_initializer = true:suggestion 90 | dotnet_style_explicit_tuple_names = true:suggestion 91 | dotnet_style_coalesce_expression = true:suggestion 92 | dotnet_style_null_propagation = true:suggestion 93 | 94 | # Expression-bodied members 95 | csharp_style_expression_bodied_methods = false:none 96 | csharp_style_expression_bodied_constructors = false:none 97 | csharp_style_expression_bodied_operators = false:none 98 | csharp_style_expression_bodied_properties = true:none 99 | csharp_style_expression_bodied_indexers = true:none 100 | csharp_style_expression_bodied_accessors = true:none 101 | 102 | # Pattern matching 103 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 104 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 105 | csharp_style_inlined_variable_declaration = true:suggestion 106 | 107 | # Null checking preferences 108 | csharp_style_throw_expression = true:suggestion 109 | csharp_style_conditional_delegate_call = true:suggestion 110 | 111 | # Space preferences 112 | csharp_space_after_cast = false 113 | csharp_space_after_colon_in_inheritance_clause = true 114 | csharp_space_after_comma = true 115 | csharp_space_after_dot = false 116 | csharp_space_after_keywords_in_control_flow_statements = true 117 | csharp_space_after_semicolon_in_for_statement = true 118 | csharp_space_around_binary_operators = before_and_after 119 | csharp_space_around_declaration_statements = do_not_ignore 120 | csharp_space_before_colon_in_inheritance_clause = true 121 | csharp_space_before_comma = false 122 | csharp_space_before_dot = false 123 | csharp_space_before_open_square_brackets = false 124 | csharp_space_before_semicolon_in_for_statement = false 125 | csharp_space_between_empty_square_brackets = false 126 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 127 | csharp_space_between_method_call_name_and_opening_parenthesis = false 128 | csharp_space_between_method_call_parameter_list_parentheses = false 129 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 130 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 131 | csharp_space_between_method_declaration_parameter_list_parentheses = false 132 | csharp_space_between_parentheses = false 133 | csharp_space_between_square_brackets = false 134 | 135 | # Xaml files 136 | [*.{xaml,axaml}] 137 | indent_style = space 138 | indent_size = 2 139 | 140 | # Xml project files 141 | [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] 142 | indent_size = 2 143 | 144 | # Xml build files 145 | [*.builds] 146 | indent_size = 2 147 | 148 | # Xml files 149 | [*.{xml,stylecop,resx,ruleset}] 150 | indent_size = 2 151 | 152 | # Xml config files 153 | [*.{props,targets,config,nuspec}] 154 | indent_size = 2 155 | 156 | # Shell scripts 157 | [*.sh] 158 | end_of_line = lf 159 | [*.{cmd, bat}] 160 | end_of_line = crlf -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [wieslawsoltes] 2 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # https://probot.github.io/apps/stale/ 2 | 3 | # Number of days of inactivity before an issue becomes stale 4 | daysUntilStale: 60 5 | 6 | # Number of days of inactivity before a stale issue is closed 7 | daysUntilClose: 7 8 | 9 | # Issues with these labels will never be considered stale 10 | exemptLabels: 11 | - pinned 12 | - security 13 | 14 | # Label to use when marking an issue as stale 15 | staleLabel: stale 16 | 17 | # Comment to post when marking an issue as stale. Set to `false` to disable 18 | markComment: > 19 | This issue has been automatically marked as stale because it has not had 20 | recent activity. It will be closed if no further activity occurs. Thank you 21 | for your contributions. 22 | 23 | # Comment to post when closing a stale issue. Set to `false` to disable 24 | closeComment: true 25 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release/* 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macos-latest] 17 | name: Build ${{ matrix.os }} 18 | runs-on: ${{ matrix.os }} 19 | 20 | steps: 21 | - uses: actions/checkout@v1 22 | - name: Setup .NET Core 23 | uses: actions/setup-dotnet@v1 24 | - name: Install wasm-tools 25 | run: dotnet workload install wasm-tools 26 | - name: Build Release 27 | run: dotnet build --configuration Release 28 | - name: Test Release 29 | run: dotnet test --configuration Release 30 | -------------------------------------------------------------------------------- /.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 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /.nuke/build.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Build Schema", 4 | "$ref": "#/definitions/build", 5 | "definitions": { 6 | "build": { 7 | "type": "object", 8 | "properties": { 9 | "Configuration": { 10 | "type": "string", 11 | "description": "configuration" 12 | }, 13 | "Continue": { 14 | "type": "boolean", 15 | "description": "Indicates to continue a previously failed build attempt" 16 | }, 17 | "Help": { 18 | "type": "boolean", 19 | "description": "Shows the help text for this build assembly" 20 | }, 21 | "Host": { 22 | "type": "string", 23 | "description": "Host for execution. Default is 'automatic'", 24 | "enum": [ 25 | "AppVeyor", 26 | "AzurePipelines", 27 | "Bamboo", 28 | "Bitrise", 29 | "GitHubActions", 30 | "GitLab", 31 | "Jenkins", 32 | "Rider", 33 | "SpaceAutomation", 34 | "TeamCity", 35 | "Terminal", 36 | "TravisCI", 37 | "VisualStudio", 38 | "VSCode" 39 | ] 40 | }, 41 | "NoLogo": { 42 | "type": "boolean", 43 | "description": "Disables displaying the NUKE logo" 44 | }, 45 | "Partition": { 46 | "type": "string", 47 | "description": "Partition to use on CI" 48 | }, 49 | "Plan": { 50 | "type": "boolean", 51 | "description": "Shows the execution plan (HTML)" 52 | }, 53 | "Profile": { 54 | "type": "array", 55 | "description": "Defines the profiles to load", 56 | "items": { 57 | "type": "string" 58 | } 59 | }, 60 | "PublishFramework": { 61 | "type": "string", 62 | "description": "publish-framework" 63 | }, 64 | "PublishProject": { 65 | "type": "string", 66 | "description": "publish-project" 67 | }, 68 | "PublishRuntime": { 69 | "type": "string", 70 | "description": "publish-runtime" 71 | }, 72 | "PublishSelfContained": { 73 | "type": "boolean", 74 | "description": "publish-self-contained" 75 | }, 76 | "Root": { 77 | "type": "string", 78 | "description": "Root directory during build execution" 79 | }, 80 | "Skip": { 81 | "type": "array", 82 | "description": "List of targets to be skipped. Empty list skips all dependencies", 83 | "items": { 84 | "type": "string", 85 | "enum": [ 86 | "Clean", 87 | "Compile", 88 | "Pack", 89 | "Publish", 90 | "Restore", 91 | "Test" 92 | ] 93 | } 94 | }, 95 | "Solution": { 96 | "type": "string", 97 | "description": "Path to a solution file that is automatically loaded" 98 | }, 99 | "Target": { 100 | "type": "array", 101 | "description": "List of targets to be invoked. Default is '{default_target}'", 102 | "items": { 103 | "type": "string", 104 | "enum": [ 105 | "Clean", 106 | "Compile", 107 | "Pack", 108 | "Publish", 109 | "Restore", 110 | "Test" 111 | ] 112 | } 113 | }, 114 | "Verbosity": { 115 | "type": "string", 116 | "description": "Logging verbosity during build execution. Default is 'Normal'", 117 | "enum": [ 118 | "Minimal", 119 | "Normal", 120 | "Quiet", 121 | "Verbose" 122 | ] 123 | }, 124 | "VersionSuffix": { 125 | "type": "string", 126 | "description": "version-suffix" 127 | } 128 | } 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /.nuke/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./build.schema.json", 3 | "Solution": "DataBox.sln" 4 | } -------------------------------------------------------------------------------- /DataBox.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataBoxDemo", "samples\DataBoxDemo\DataBoxDemo.csproj", "{D9D964C6-72D8-4009-910D-6DA99BA9D30F}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataBox", "src\DataBox\DataBox.csproj", "{00120A66-65E2-4D26-A117-51BA40C2F844}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{BFAAFDC6-9A28-4F70-9C21-F6DF50786C70}" 8 | ProjectSection(SolutionItems) = preProject 9 | .editorconfig = .editorconfig 10 | azure-pipelines.yml = azure-pipelines.yml 11 | build.ps1 = build.ps1 12 | build.sh = build.sh 13 | global.json = global.json 14 | build\databox.public.snk = build\databox.public.snk 15 | EndProjectSection 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "git", "git", "{17E6165A-4614-435A-8BBE-55A8C850F21B}" 18 | ProjectSection(SolutionItems) = preProject 19 | .gitattributes = .gitattributes 20 | .gitignore = .gitignore 21 | EndProjectSection 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{94B631C1-E35B-462E-8D52-90BF04F72ECB}" 24 | ProjectSection(SolutionItems) = preProject 25 | nuget.config = nuget.config 26 | EndProjectSection 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "props", "props", "{BFAD5779-6E04-463F-9A38-6D78CEFC6E09}" 29 | ProjectSection(SolutionItems) = preProject 30 | build\Avalonia.Desktop.props = build\Avalonia.Desktop.props 31 | build\Avalonia.Diagnostics.props = build\Avalonia.Diagnostics.props 32 | build\Avalonia.props = build\Avalonia.props 33 | build\Avalonia.ReactiveUI.props = build\Avalonia.ReactiveUI.props 34 | build\Base.props = build\Base.props 35 | build\ReferenceAssemblies.props = build\ReferenceAssemblies.props 36 | build\SignAssembly.props = build\SignAssembly.props 37 | build\SourceLink.props = build\SourceLink.props 38 | build\XUnit.props = build\XUnit.props 39 | build\Avalonia.Themes.Fluent.props = build\Avalonia.Themes.Fluent.props 40 | EndProjectSection 41 | EndProject 42 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{4E19FF9C-17EE-4AFA-9317-7C949FB3C66A}" 43 | ProjectSection(SolutionItems) = preProject 44 | LICENSE.TXT = LICENSE.TXT 45 | README.md = README.md 46 | EndProjectSection 47 | EndProject 48 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{3B564AD4-C507-43DC-8711-38320FB3B679}" 49 | EndProject 50 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D7275F04-303E-4385-B806-08726C572D6D}" 51 | EndProject 52 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{491007E3-8E11-4236-A724-ED7AC78FF726}" 53 | EndProject 54 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataBox.UnitTests", "tests\DataBox.UnitTests\DataBox.UnitTests.csproj", "{85FA5761-009D-4072-9C86-7CCFE1B14CE3}" 55 | EndProject 56 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_build", "build\build\_build.csproj", "{185AA19E-8CAC-4539-BD28-8199E348AC40}" 57 | EndProject 58 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataBox.DataVirtualization", "src\DataBox.DataVirtualization\DataBox.DataVirtualization.csproj", "{8AD8D915-14C1-49E5-AB33-792C63DF2379}" 59 | EndProject 60 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataBoxDataVirtualizationDemo", "samples\DataBoxDataVirtualizationDemo\DataBoxDataVirtualizationDemo.csproj", "{51696845-5450-4A14-9368-E1063B6B14B5}" 61 | EndProject 62 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualPanel", "src\VirtualPanel\VirtualPanel.csproj", "{2C2535BC-38DB-4FFF-BD30-ABAED465E7D7}" 63 | EndProject 64 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualPanelDemo.NetCore", "samples\VirtualPanelDemo.NetCore\VirtualPanelDemo.NetCore.csproj", "{7396717D-036A-4F90-9120-4755F44C6310}" 65 | EndProject 66 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualPanelDemo", "samples\VirtualPanelDemo\VirtualPanelDemo.csproj", "{CBA4D84C-45CC-4E37-9E16-F95E1CEF5DC4}" 67 | EndProject 68 | Global 69 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 70 | Debug|Any CPU = Debug|Any CPU 71 | Release|Any CPU = Release|Any CPU 72 | EndGlobalSection 73 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 74 | {D9D964C6-72D8-4009-910D-6DA99BA9D30F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {D9D964C6-72D8-4009-910D-6DA99BA9D30F}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {D9D964C6-72D8-4009-910D-6DA99BA9D30F}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {D9D964C6-72D8-4009-910D-6DA99BA9D30F}.Release|Any CPU.Build.0 = Release|Any CPU 78 | {00120A66-65E2-4D26-A117-51BA40C2F844}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {00120A66-65E2-4D26-A117-51BA40C2F844}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {00120A66-65E2-4D26-A117-51BA40C2F844}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {00120A66-65E2-4D26-A117-51BA40C2F844}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {85FA5761-009D-4072-9C86-7CCFE1B14CE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 83 | {85FA5761-009D-4072-9C86-7CCFE1B14CE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 84 | {85FA5761-009D-4072-9C86-7CCFE1B14CE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 85 | {85FA5761-009D-4072-9C86-7CCFE1B14CE3}.Release|Any CPU.Build.0 = Release|Any CPU 86 | {185AA19E-8CAC-4539-BD28-8199E348AC40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 87 | {185AA19E-8CAC-4539-BD28-8199E348AC40}.Debug|Any CPU.Build.0 = Debug|Any CPU 88 | {185AA19E-8CAC-4539-BD28-8199E348AC40}.Release|Any CPU.ActiveCfg = Release|Any CPU 89 | {185AA19E-8CAC-4539-BD28-8199E348AC40}.Release|Any CPU.Build.0 = Release|Any CPU 90 | {8AD8D915-14C1-49E5-AB33-792C63DF2379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 91 | {8AD8D915-14C1-49E5-AB33-792C63DF2379}.Debug|Any CPU.Build.0 = Debug|Any CPU 92 | {8AD8D915-14C1-49E5-AB33-792C63DF2379}.Release|Any CPU.ActiveCfg = Release|Any CPU 93 | {8AD8D915-14C1-49E5-AB33-792C63DF2379}.Release|Any CPU.Build.0 = Release|Any CPU 94 | {51696845-5450-4A14-9368-E1063B6B14B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 95 | {51696845-5450-4A14-9368-E1063B6B14B5}.Debug|Any CPU.Build.0 = Debug|Any CPU 96 | {51696845-5450-4A14-9368-E1063B6B14B5}.Release|Any CPU.ActiveCfg = Release|Any CPU 97 | {51696845-5450-4A14-9368-E1063B6B14B5}.Release|Any CPU.Build.0 = Release|Any CPU 98 | {2C2535BC-38DB-4FFF-BD30-ABAED465E7D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 99 | {2C2535BC-38DB-4FFF-BD30-ABAED465E7D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 100 | {2C2535BC-38DB-4FFF-BD30-ABAED465E7D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 101 | {2C2535BC-38DB-4FFF-BD30-ABAED465E7D7}.Release|Any CPU.Build.0 = Release|Any CPU 102 | {7396717D-036A-4F90-9120-4755F44C6310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 103 | {7396717D-036A-4F90-9120-4755F44C6310}.Debug|Any CPU.Build.0 = Debug|Any CPU 104 | {7396717D-036A-4F90-9120-4755F44C6310}.Release|Any CPU.ActiveCfg = Release|Any CPU 105 | {7396717D-036A-4F90-9120-4755F44C6310}.Release|Any CPU.Build.0 = Release|Any CPU 106 | {CBA4D84C-45CC-4E37-9E16-F95E1CEF5DC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 107 | {CBA4D84C-45CC-4E37-9E16-F95E1CEF5DC4}.Debug|Any CPU.Build.0 = Debug|Any CPU 108 | {CBA4D84C-45CC-4E37-9E16-F95E1CEF5DC4}.Release|Any CPU.ActiveCfg = Release|Any CPU 109 | {CBA4D84C-45CC-4E37-9E16-F95E1CEF5DC4}.Release|Any CPU.Build.0 = Release|Any CPU 110 | EndGlobalSection 111 | GlobalSection(NestedProjects) = preSolution 112 | {17E6165A-4614-435A-8BBE-55A8C850F21B} = {BFAAFDC6-9A28-4F70-9C21-F6DF50786C70} 113 | {94B631C1-E35B-462E-8D52-90BF04F72ECB} = {BFAAFDC6-9A28-4F70-9C21-F6DF50786C70} 114 | {BFAD5779-6E04-463F-9A38-6D78CEFC6E09} = {BFAAFDC6-9A28-4F70-9C21-F6DF50786C70} 115 | {D9D964C6-72D8-4009-910D-6DA99BA9D30F} = {3B564AD4-C507-43DC-8711-38320FB3B679} 116 | {00120A66-65E2-4D26-A117-51BA40C2F844} = {D7275F04-303E-4385-B806-08726C572D6D} 117 | {85FA5761-009D-4072-9C86-7CCFE1B14CE3} = {491007E3-8E11-4236-A724-ED7AC78FF726} 118 | {185AA19E-8CAC-4539-BD28-8199E348AC40} = {BFAAFDC6-9A28-4F70-9C21-F6DF50786C70} 119 | {8AD8D915-14C1-49E5-AB33-792C63DF2379} = {D7275F04-303E-4385-B806-08726C572D6D} 120 | {51696845-5450-4A14-9368-E1063B6B14B5} = {3B564AD4-C507-43DC-8711-38320FB3B679} 121 | {2C2535BC-38DB-4FFF-BD30-ABAED465E7D7} = {D7275F04-303E-4385-B806-08726C572D6D} 122 | {7396717D-036A-4F90-9120-4755F44C6310} = {3B564AD4-C507-43DC-8711-38320FB3B679} 123 | {CBA4D84C-45CC-4E37-9E16-F95E1CEF5DC4} = {3B564AD4-C507-43DC-8711-38320FB3B679} 124 | EndGlobalSection 125 | EndGlobal 126 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Wiesław Šoltés 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 | # DataBox 2 | 3 | [![Build Status](https://dev.azure.com/wieslawsoltes/GitHub/_apis/build/status/wieslawsoltes.DataBox?repoName=wieslawsoltes%2FDataBox&branchName=main)](https://dev.azure.com/wieslawsoltes/GitHub/_build/latest?definitionId=103&repoName=wieslawsoltes%2FDataBox&branchName=main) 4 | [![CI](https://github.com/wieslawsoltes/DataBox/actions/workflows/build.yml/badge.svg)](https://github.com/wieslawsoltes/DataBox/actions/workflows/build.yml) 5 | 6 | [![NuGet](https://img.shields.io/nuget/v/DataBox.svg)](https://www.nuget.org/packages/DataBox) 7 | [![NuGet](https://img.shields.io/nuget/dt/DataBox.svg)](https://www.nuget.org/packages/DataBox) 8 | 9 | A DataGrid control based on ListBox control. 10 | 11 | ![image](https://user-images.githubusercontent.com/2297442/138347025-4b19b0b6-ff4e-4d68-8ec7-6e265b62e396.png) 12 | 13 | ## Building DataBox 14 | 15 | First, clone the repository or download the latest zip. 16 | ``` 17 | git clone https://github.com/wieslawsoltes/DataBox.git 18 | ``` 19 | 20 | ### Build on Windows using script 21 | 22 | * [.NET Core](https://www.microsoft.com/net/download?initial-os=windows). 23 | 24 | Open up a command-prompt and execute the commands: 25 | ``` 26 | .\build.ps1 27 | ``` 28 | 29 | ### Build on Linux using script 30 | 31 | * [.NET Core](https://www.microsoft.com/net/download?initial-os=linux). 32 | 33 | Open up a terminal prompt and execute the commands: 34 | ``` 35 | ./build.sh 36 | ``` 37 | 38 | ### Build on OSX using script 39 | 40 | * [.NET Core](https://www.microsoft.com/net/download?initial-os=macos). 41 | 42 | Open up a terminal prompt and execute the commands: 43 | ``` 44 | ./build.sh 45 | ``` 46 | 47 | ## NuGet 48 | 49 | DataBox is delivered as a NuGet package. 50 | 51 | You can find the packages here [NuGet](https://www.nuget.org/packages/DataBox/) and install the package like this: 52 | 53 | `Install-Package DataBox` 54 | 55 | ### Package Sources 56 | 57 | * https://api.nuget.org/v3/index.json 58 | * https://www.myget.org/F/avalonia-ci/api/v2 59 | 60 | ## Resources 61 | 62 | * [GitHub source code repository.](https://github.com/wieslawsoltes/DataBox) 63 | 64 | ## License 65 | 66 | DataBox is licensed under the [MIT license](LICENSE.TXT). 67 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: $(date:yyyyMMdd)$(rev:-rr) 2 | 3 | resources: 4 | repositories: 5 | - repository: templates 6 | endpoint: wieslawsoltes 7 | type: github 8 | name: wieslawsoltes/BuildTemplates 9 | ref: refs/tags/v2.0.0 10 | 11 | variables: 12 | BuildConfiguration: 'Release' 13 | BuildPlatform: 'Any CPU' 14 | PublishFramework: 'net9.0' 15 | PublishProject: 'DataBoxDemo' 16 | PublishRuntime: '' 17 | 18 | jobs: 19 | - template: Test-PowerShell.yml@templates 20 | parameters: 21 | name: 'Test_Windows' 22 | vmImage: 'windows-2022' 23 | BuildConfiguration: ${{ variables.BuildConfiguration }} 24 | 25 | - template: Test-Bash.yml@templates 26 | parameters: 27 | name: 'Test_Linux' 28 | vmImage: 'ubuntu-20.04' 29 | BuildConfiguration: ${{ variables.BuildConfiguration }} 30 | 31 | - template: Test-Bash.yml@templates 32 | parameters: 33 | name: 'Test_macOS' 34 | vmImage: 'macOS-14' 35 | BuildConfiguration: ${{ variables.BuildConfiguration }} 36 | 37 | - template: Pack-MyGet.yml@templates 38 | parameters: 39 | name: 'Pack_MyGet' 40 | vmImage: 'windows-2022' 41 | BuildConfiguration: ${{ variables.BuildConfiguration }} 42 | 43 | - template: Pack-NuGet.yml@templates 44 | parameters: 45 | name: 'Pack_NuGet' 46 | vmImage: 'windows-2022' 47 | BuildConfiguration: ${{ variables.BuildConfiguration }} 48 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | :; set -eo pipefail 2 | :; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 3 | :; ${SCRIPT_DIR}/build.sh "$@" 4 | :; exit $? 5 | 6 | @ECHO OFF 7 | powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %* 8 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 4 | [string[]]$BuildArguments 5 | ) 6 | 7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" 8 | 9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } 10 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 11 | 12 | ########################################################################### 13 | # CONFIGURATION 14 | ########################################################################### 15 | 16 | $BuildProjectFile = "$PSScriptRoot\build\build\_build.csproj" 17 | $TempDirectory = "$PSScriptRoot\\.nuke\temp" 18 | 19 | $DotNetGlobalFile = "$PSScriptRoot\\global.json" 20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" 21 | $DotNetChannel = "Current" 22 | 23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0 26 | 27 | ########################################################################### 28 | # EXECUTION 29 | ########################################################################### 30 | 31 | function ExecSafe([scriptblock] $cmd) { 32 | & $cmd 33 | if ($LASTEXITCODE) { exit $LASTEXITCODE } 34 | } 35 | 36 | # If dotnet CLI is installed globally and it matches requested version, use for execution 37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` 38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) { 39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path 40 | } 41 | else { 42 | # Download install script 43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" 44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null 45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) 47 | 48 | # If global.json exists, load expected version 49 | if (Test-Path $DotNetGlobalFile) { 50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) 51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { 52 | $DotNetVersion = $DotNetGlobal.sdk.version 53 | } 54 | } 55 | 56 | # Install by channel or version 57 | $DotNetDirectory = "$TempDirectory\dotnet-win" 58 | if (!(Test-Path variable:DotNetVersion)) { 59 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } 60 | } else { 61 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } 62 | } 63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" 64 | } 65 | 66 | Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" 67 | 68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } 69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } 70 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | bash --version 2>&1 | head -n 1 4 | 5 | set -eo pipefail 6 | SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) 7 | 8 | ########################################################################### 9 | # CONFIGURATION 10 | ########################################################################### 11 | 12 | BUILD_PROJECT_FILE="$SCRIPT_DIR/build/build/_build.csproj" 13 | TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp" 14 | 15 | DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json" 16 | DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh" 17 | DOTNET_CHANNEL="Current" 18 | 19 | export DOTNET_CLI_TELEMETRY_OPTOUT=1 20 | export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 21 | export DOTNET_MULTILEVEL_LOOKUP=0 22 | 23 | ########################################################################### 24 | # EXECUTION 25 | ########################################################################### 26 | 27 | function FirstJsonValue { 28 | perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}" 29 | } 30 | 31 | # If dotnet CLI is installed globally and it matches requested version, use for execution 32 | if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then 33 | export DOTNET_EXE="$(command -v dotnet)" 34 | else 35 | # Download install script 36 | DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh" 37 | mkdir -p "$TEMP_DIRECTORY" 38 | curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL" 39 | chmod +x "$DOTNET_INSTALL_FILE" 40 | 41 | # If global.json exists, load expected version 42 | if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then 43 | DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")") 44 | if [[ "$DOTNET_VERSION" == "" ]]; then 45 | unset DOTNET_VERSION 46 | fi 47 | fi 48 | 49 | # Install by channel or version 50 | DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix" 51 | if [[ -z ${DOTNET_VERSION+x} ]]; then 52 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path 53 | else 54 | "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path 55 | fi 56 | export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet" 57 | fi 58 | 59 | echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)" 60 | 61 | "$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet 62 | "$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@" 63 | -------------------------------------------------------------------------------- /build/Avalonia.Desktop.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /build/Avalonia.Diagnostics.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /build/Avalonia.ReactiveUI.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /build/Avalonia.Themes.Fluent.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /build/Avalonia.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /build/Base.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11.2.0 4 | 5 | Wiesław Šoltés 6 | Wiesław Šoltés 7 | Copyright © Wiesław Šoltés 2024 8 | MIT 9 | https://github.com/wieslawsoltes/DataBox 10 | 11 | 12 | latest 13 | preview 14 | 15 | 16 | -------------------------------------------------------------------------------- /build/ReferenceAssemblies.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /build/SignAssembly.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | True 5 | $(MSBuildThisFileDirectory)\databox.public.snk 6 | false 7 | true 8 | 9 | 10 | -------------------------------------------------------------------------------- /build/SourceLink.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | false 5 | true 6 | embedded 7 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 8 | 9 | 10 | true 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /build/XUnit.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build/build/Build.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Nuke.Common; 3 | using Nuke.Common.Git; 4 | using Nuke.Common.ProjectModel; 5 | using Nuke.Common.Tools.DotNet; 6 | using Nuke.Common.IO; 7 | using static Nuke.Common.IO.FileSystemTasks; 8 | using static Nuke.Common.IO.PathConstruction; 9 | using static Nuke.Common.Tools.DotNet.DotNetTasks; 10 | 11 | class Build : NukeBuild 12 | { 13 | public static int Main() => Execute(x => x.Compile); 14 | 15 | [Solution] 16 | readonly Solution Solution; 17 | 18 | [GitRepository] 19 | readonly GitRepository GitRepository; 20 | 21 | [Parameter("configuration")] 22 | public string Configuration { get; set; } 23 | 24 | [Parameter("version-suffix")] 25 | public string VersionSuffix { get; set; } 26 | 27 | [Parameter("publish-framework")] 28 | public string PublishFramework { get; set; } 29 | 30 | [Parameter("publish-runtime")] 31 | public string PublishRuntime { get; set; } 32 | 33 | [Parameter("publish-project")] 34 | public string PublishProject { get; set; } 35 | 36 | [Parameter("publish-self-contained")] 37 | public bool PublishSelfContained { get; set; } = true; 38 | 39 | AbsolutePath SourceDirectory => RootDirectory / "src"; 40 | 41 | AbsolutePath TestsDirectory => RootDirectory / "tests"; 42 | 43 | AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; 44 | 45 | protected override void OnBuildInitialized() 46 | { 47 | Configuration = Configuration ?? "Release"; 48 | VersionSuffix = VersionSuffix ?? ""; 49 | } 50 | 51 | private void DeleteDirectories(IReadOnlyCollection directories) 52 | { 53 | foreach (var directory in directories) 54 | { 55 | DeleteDirectory(directory); 56 | } 57 | } 58 | 59 | Target Clean => _ => _ 60 | .Executes(() => 61 | { 62 | DeleteDirectories(GlobDirectories(SourceDirectory, "**/bin", "**/obj")); 63 | DeleteDirectories(GlobDirectories(TestsDirectory, "**/bin", "**/obj")); 64 | EnsureCleanDirectory(ArtifactsDirectory); 65 | }); 66 | 67 | Target Restore => _ => _ 68 | .DependsOn(Clean) 69 | .Executes(() => 70 | { 71 | DotNetRestore(s => s 72 | .SetProjectFile(Solution)); 73 | }); 74 | 75 | Target Compile => _ => _ 76 | .DependsOn(Restore) 77 | .Executes(() => 78 | { 79 | DotNetBuild(s => s 80 | .SetProjectFile(Solution) 81 | .SetConfiguration(Configuration) 82 | .SetVersionSuffix(VersionSuffix) 83 | .EnableNoRestore()); 84 | }); 85 | 86 | Target Test => _ => _ 87 | .DependsOn(Compile) 88 | .Executes(() => 89 | { 90 | DotNetTest(s => s 91 | .SetProjectFile(Solution) 92 | .SetConfiguration(Configuration) 93 | .SetLoggers("trx") 94 | .SetResultsDirectory(ArtifactsDirectory / "TestResults") 95 | .EnableNoBuild() 96 | .EnableNoRestore()); 97 | }); 98 | 99 | Target Pack => _ => _ 100 | .DependsOn(Test) 101 | .Executes(() => 102 | { 103 | DotNetPack(s => s 104 | .SetProject(Solution) 105 | .SetConfiguration(Configuration) 106 | .SetVersionSuffix(VersionSuffix) 107 | .SetOutputDirectory(ArtifactsDirectory / "NuGet") 108 | .EnableNoBuild() 109 | .EnableNoRestore()); 110 | }); 111 | 112 | Target Publish => _ => _ 113 | .DependsOn(Test) 114 | .Requires(() => PublishRuntime) 115 | .Requires(() => PublishFramework) 116 | .Requires(() => PublishProject) 117 | .Executes(() => 118 | { 119 | DotNetPublish(s => s 120 | .SetProject(Solution.GetProject(PublishProject)) 121 | .SetConfiguration(Configuration) 122 | .SetVersionSuffix(VersionSuffix) 123 | .SetFramework(PublishFramework) 124 | .SetRuntime(PublishRuntime) 125 | .SetSelfContained(PublishSelfContained) 126 | .SetOutput(ArtifactsDirectory / "Publish" / PublishProject + "-" + PublishFramework + "-" + PublishRuntime)); 127 | }); 128 | } 129 | -------------------------------------------------------------------------------- /build/build/_build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | false 7 | False 8 | CS0649;CS0169 9 | 1 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /build/databox.public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wieslawsoltes/DataBox/316c5de320064ebf429002def9b05e389cb97aed/build/databox.public.snk -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.100", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/App.axaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | using DataBoxDataVirtualizationDemo.ViewModels; 5 | 6 | namespace DataBoxDataVirtualizationDemo; 7 | 8 | public class App : Application 9 | { 10 | public override void Initialize() 11 | { 12 | AvaloniaXamlLoader.Load(this); 13 | } 14 | 15 | public override void OnFrameworkInitializationCompleted() 16 | { 17 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 18 | { 19 | desktop.MainWindow = new MainWindow 20 | { 21 | DataContext = new MainWindowViewModel(2_000_000, 100) 22 | }; 23 | } 24 | 25 | base.OnFrameworkInitializationCompleted(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/DataBoxDataVirtualizationDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | 5 | 6 | WinExe 7 | 8 | 9 | net9.0 10 | False 11 | enable 12 | Debug;Release 13 | AnyCPU;x64 14 | 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | all 40 | runtime; build; native; contentfiles; analyzers; buildtransitive 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/MainWindow.axaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 63 | 66 | 67 | 68 | 69 | 71 | 72 | 73 | 74 | 75 | 77 | 80 | 81 | 82 | 83 | 85 | 86 | 87 | 88 | 89 | 91 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/MainWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Markup.Xaml; 4 | using Avalonia.Rendering; 5 | 6 | namespace DataBoxDataVirtualizationDemo; 7 | 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | #if DEBUG 14 | this.AttachDevTools(); 15 | #endif 16 | RendererDiagnostics.DebugOverlays = RendererDebugOverlays.Fps; 17 | } 18 | 19 | private void InitializeComponent() 20 | { 21 | AvaloniaXamlLoader.Load(this); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.ReactiveUI; 4 | 5 | namespace DataBoxDataVirtualizationDemo; 6 | 7 | class Program 8 | { 9 | [STAThread] 10 | public static void Main(string[] args) => BuildAvaloniaApp() 11 | .StartWithClassicDesktopLifetime(args); 12 | 13 | public static AppBuilder BuildAvaloniaApp() 14 | => AppBuilder.Configure() 15 | .UsePlatformDetect() 16 | .LogToTrace() 17 | .UseReactiveUI(); 18 | } 19 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/ViewModels/ItemProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using DataVirtualization; 4 | 5 | namespace DataBoxDataVirtualizationDemo.ViewModels; 6 | 7 | public class ItemProvider : IItemsProvider 8 | { 9 | private readonly int _count; 10 | 11 | public ItemProvider(int count) 12 | { 13 | _count = count; 14 | } 15 | 16 | public int FetchCount() 17 | { 18 | return _count; 19 | } 20 | 21 | private ItemViewModel CreateItem(int index) 22 | { 23 | return new ItemViewModel( 24 | $"Item {index}-1", 25 | $"Item {index}-2", 26 | $"Item {index}-3", 27 | Random.Shared.NextDouble() > 0.5, 28 | index, 29 | //Random.Shared.Next(24, 100)); 30 | double.NaN); 31 | } 32 | 33 | public IList FetchRange(int startIndex, int pageCount, out int overallCount) 34 | { 35 | var result = new List(); 36 | var endIndex = startIndex + pageCount; 37 | 38 | overallCount = _count; 39 | 40 | for (var i = startIndex; i < endIndex; i++) 41 | { 42 | result.Add(CreateItem(i)); 43 | } 44 | 45 | return result; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/ViewModels/ItemViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace DataBoxDataVirtualizationDemo.ViewModels; 2 | 3 | public partial class ItemViewModel : ViewModelBase 4 | { 5 | [Reactive] 6 | public partial string Column1 { get; set; } 7 | 8 | [Reactive] 9 | public partial string Column2 { get; set; } 10 | 11 | [Reactive] 12 | public partial string Column3 { get; set; } 13 | 14 | [Reactive] 15 | public partial bool Column4 { get; set; } 16 | 17 | [Reactive] 18 | public partial int Column5 { get; set; } 19 | 20 | [Reactive] 21 | public partial double Height { get; set; } 22 | 23 | public ItemViewModel(string column1, string column2, string column3, bool column4, int column5, double height) 24 | { 25 | _column1 = column1; 26 | _column2 = column2; 27 | _column3 = column3; 28 | _column4 = column4; 29 | _column5 = column5; 30 | _height = height; 31 | } 32 | 33 | public override string ToString() 34 | { 35 | return $"{_column5}"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using DataVirtualization; 2 | 3 | namespace DataBoxDataVirtualizationDemo.ViewModels; 4 | 5 | public partial class MainWindowViewModel : ViewModelBase 6 | { 7 | [Reactive] 8 | public partial AsyncVirtualizingCollection? Items { get; set; } 9 | 10 | public MainWindowViewModel(int count, int pageSize) 11 | { 12 | Items = new AsyncVirtualizingCollection( 13 | new ItemProvider(count), 14 | pageSize, 15 | 5000); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/DataBoxDataVirtualizationDemo/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | 3 | namespace DataBoxDataVirtualizationDemo.ViewModels; 4 | 5 | public class ViewModelBase : ReactiveObject 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /samples/DataBoxDemo/App.axaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/DataBoxDemo/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | using DataBoxDemo.ViewModels; 5 | 6 | namespace DataBoxDemo; 7 | 8 | public class App : Application 9 | { 10 | public override void Initialize() 11 | { 12 | AvaloniaXamlLoader.Load(this); 13 | } 14 | 15 | public override void OnFrameworkInitializationCompleted() 16 | { 17 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 18 | { 19 | desktop.MainWindow = new MainWindow 20 | { 21 | DataContext = new MainWindowViewModel(), 22 | }; 23 | } 24 | 25 | base.OnFrameworkInitializationCompleted(); 26 | } 27 | } -------------------------------------------------------------------------------- /samples/DataBoxDemo/DataBoxDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | 5 | 6 | WinExe 7 | 8 | 9 | net9.0 10 | False 11 | enable 12 | Debug;Release 13 | AnyCPU;x64 14 | 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | all 39 | runtime; build; native; contentfiles; analyzers; buildtransitive 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /samples/DataBoxDemo/MainWindow.axaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 63 | 64 | 65 | 68 | 69 | 70 | 71 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 94 | 95 | 96 | 97 | 105 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /samples/DataBoxDemo/MainWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Markup.Xaml; 4 | using Avalonia.Rendering; 5 | 6 | namespace DataBoxDemo; 7 | 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | #if DEBUG 14 | this.AttachDevTools(); 15 | #endif 16 | RendererDiagnostics.DebugOverlays = RendererDebugOverlays.Fps; 17 | } 18 | 19 | private void InitializeComponent() 20 | { 21 | AvaloniaXamlLoader.Load(this); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/DataBoxDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.ReactiveUI; 4 | 5 | namespace DataBoxDemo; 6 | 7 | class Program 8 | { 9 | [STAThread] 10 | public static void Main(string[] args) => BuildAvaloniaApp() 11 | .StartWithClassicDesktopLifetime(args); 12 | 13 | public static AppBuilder BuildAvaloniaApp() 14 | => AppBuilder.Configure() 15 | .UsePlatformDetect() 16 | .LogToTrace() 17 | .UseReactiveUI(); 18 | } -------------------------------------------------------------------------------- /samples/DataBoxDemo/ViewModels/ItemViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace DataBoxDemo.ViewModels; 2 | 3 | public partial class ItemViewModel : ViewModelBase 4 | { 5 | [Reactive] 6 | public partial string Column1 { get; set; } 7 | 8 | [Reactive] 9 | public partial string Column2 { get; set; } 10 | 11 | [Reactive] 12 | public partial string Column3 { get; set; } 13 | 14 | [Reactive] 15 | public partial bool Column4 { get; set; } 16 | 17 | [Reactive] 18 | public partial int Column5 { get; set; } 19 | 20 | [Reactive] 21 | public partial double Height { get; set; } 22 | 23 | public ItemViewModel(string column1, string column2, string column3, bool column4, int column5, double height) 24 | { 25 | _column1 = column1; 26 | _column2 = column2; 27 | _column3 = column3; 28 | _column4 = column4; 29 | _column5 = column5; 30 | _height = height; 31 | } 32 | 33 | public override string ToString() 34 | { 35 | return $"{_column5}"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/DataBoxDemo/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Reactive.Linq; 7 | using System.Reactive.Subjects; 8 | using System.Threading.Tasks; 9 | using System.Windows.Input; 10 | using DynamicData; 11 | using DynamicData.Binding; 12 | using ReactiveUI; 13 | 14 | namespace DataBoxDemo.ViewModels; 15 | 16 | public partial class MainWindowViewModel : ViewModelBase 17 | { 18 | private readonly SourceList _itemsSourceList; 19 | private ReadOnlyObservableCollection? _items; 20 | private readonly Subject> _comparerSubject; 21 | private IDisposable? _subscription; 22 | private bool _isSortingEnabled; 23 | 24 | public ReadOnlyObservableCollection? Items => _items; 25 | 26 | [Reactive] 27 | public partial ItemViewModel? SelectedItem { get; set; } 28 | 29 | [Reactive] 30 | public partial ListSortDirection? SortingStateColumn1 { get; set; } 31 | 32 | [Reactive] 33 | public partial ListSortDirection? SortingStateColumn2 { get; set; } 34 | 35 | [Reactive] 36 | public partial ListSortDirection? SortingStateColumn3 { get; set; } 37 | 38 | [Reactive] 39 | public partial ListSortDirection? SortingStateColumn4 { get; set; } 40 | 41 | [Reactive] 42 | public partial ListSortDirection? SortingStateColumn5 { get; set; } 43 | 44 | public ICommand SortCommand { get; } 45 | 46 | public ICommand AddItemCommand { get; } 47 | 48 | public ICommand InsertItemCommand { get; } 49 | 50 | public ICommand RemoveItemCommand { get; } 51 | 52 | public ICommand SelectFirstItemCommand { get; } 53 | 54 | public MainWindowViewModel() 55 | { 56 | var totalItems = 10_000; 57 | var rand = new Random(); 58 | var items = new List(); 59 | 60 | for (var i = 0; i < totalItems; i++) 61 | { 62 | items.Add(CreateItem(i)); 63 | } 64 | 65 | ItemViewModel CreateItem(int index) 66 | { 67 | return new ItemViewModel( 68 | $"Item {index}-1", 69 | $"Item {index}-2", 70 | $"Item {index}-3", 71 | rand.NextDouble() > 0.5, 72 | index, 73 | //rand.Next(24, 100)); 74 | double.NaN); 75 | } 76 | 77 | _itemsSourceList = new SourceList(); 78 | _itemsSourceList.AddRange(items); 79 | 80 | _comparerSubject = new Subject>(); 81 | _isSortingEnabled = false; 82 | SortingStateColumn5 = ListSortDirection.Ascending; 83 | EnableSort(x => x.Column5, SortingStateColumn5); 84 | 85 | SortCommand = ReactiveCommand.CreateFromTask(async sortMemberPath => 86 | { 87 | await Task.Run(() => 88 | { 89 | Sort(sortMemberPath); 90 | }); 91 | }); 92 | 93 | InsertItemCommand = ReactiveCommand.Create(() => 94 | { 95 | if (_items is null) 96 | { 97 | return; 98 | } 99 | var index = _items.Count; 100 | var item = CreateItem(index); 101 | _itemsSourceList.Insert(0, item); 102 | }); 103 | 104 | AddItemCommand = ReactiveCommand.Create(() => 105 | { 106 | if (_items is null) 107 | { 108 | return; 109 | } 110 | var index = _items.Count; 111 | var item = CreateItem(index); 112 | _itemsSourceList.Add(item); 113 | }); 114 | 115 | RemoveItemCommand = ReactiveCommand.Create((item) => 116 | { 117 | if (item is not null) 118 | { 119 | _itemsSourceList.Remove(item); 120 | } 121 | }); 122 | 123 | SelectFirstItemCommand = ReactiveCommand.Create(() => 124 | { 125 | if (_items is null) 126 | { 127 | return; 128 | } 129 | SelectedItem = _items.FirstOrDefault(); 130 | }); 131 | } 132 | 133 | private IObservable> GetSortObservable(IComparer comparer) 134 | { 135 | return _itemsSourceList! 136 | .Connect() 137 | .ObserveOn(RxApp.MainThreadScheduler) 138 | .Sort(comparer, comparerChanged: _comparerSubject) 139 | .Bind(out _items); 140 | } 141 | 142 | private IObservable> GetDefaultObservable() 143 | { 144 | return _itemsSourceList! 145 | .Connect() 146 | .ObserveOn(RxApp.MainThreadScheduler) 147 | .Bind(out _items); 148 | } 149 | 150 | private void EnableSort(Func expression, ListSortDirection? listSortDirection) 151 | { 152 | var sortExpressionComparer = listSortDirection == ListSortDirection.Ascending 153 | ? SortExpressionComparer.Ascending(expression) 154 | : SortExpressionComparer.Descending(expression); 155 | 156 | if (!_isSortingEnabled) 157 | { 158 | _subscription?.Dispose(); 159 | _subscription = GetSortObservable(sortExpressionComparer).Subscribe(); 160 | _isSortingEnabled = true; 161 | this.RaisePropertyChanged(nameof(Items)); 162 | } 163 | else 164 | { 165 | _comparerSubject.OnNext(sortExpressionComparer); 166 | } 167 | } 168 | 169 | private void DisableSort() 170 | { 171 | if (_isSortingEnabled) 172 | { 173 | _subscription?.Dispose(); 174 | _subscription = GetDefaultObservable().Subscribe(); 175 | _isSortingEnabled = false; 176 | this.RaisePropertyChanged(nameof(Items)); 177 | } 178 | } 179 | 180 | private void Sort(string? sortMemberPath) 181 | { 182 | switch (sortMemberPath) 183 | { 184 | case null: 185 | DisableSort(); 186 | break; 187 | case "Column1": 188 | EnableSort(x => x.Column1, SortingStateColumn1); 189 | break; 190 | case "Column2": 191 | EnableSort(x => x.Column2, SortingStateColumn2); 192 | break; 193 | case "Column3": 194 | EnableSort(x => x.Column3, SortingStateColumn3); 195 | break; 196 | case "Column4": 197 | EnableSort(x => x.Column4, SortingStateColumn4); 198 | break; 199 | case "Column5": 200 | EnableSort(x => x.Column5, SortingStateColumn5); 201 | break; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /samples/DataBoxDemo/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using ReactiveUI; 2 | 3 | namespace DataBoxDemo.ViewModels; 4 | 5 | public class ViewModelBase : ReactiveObject 6 | { 7 | } -------------------------------------------------------------------------------- /samples/VirtualPanelDemo.NetCore/Assets/avalonia-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wieslawsoltes/DataBox/316c5de320064ebf429002def9b05e389cb97aed/samples/VirtualPanelDemo.NetCore/Assets/avalonia-logo.ico -------------------------------------------------------------------------------- /samples/VirtualPanelDemo.NetCore/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Controls.ApplicationLifetimes; 4 | using Avalonia.ReactiveUI; 5 | 6 | namespace VirtualPanelDemo.NetCore 7 | { 8 | class Program 9 | { 10 | // Initialization code. Don't use any Avalonia, third-party APIs or any 11 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 12 | // yet and stuff might break. 13 | [STAThread] 14 | public static void Main(string[] args) => BuildAvaloniaApp() 15 | .StartWithClassicDesktopLifetime(args); 16 | 17 | // Avalonia configuration, don't remove; also used by visual designer. 18 | public static AppBuilder BuildAvaloniaApp() 19 | => AppBuilder.Configure() 20 | .UsePlatformDetect() 21 | .LogToTrace() 22 | .UseReactiveUI(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo.NetCore/VirtualPanelDemo.NetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | 5 | 6 | WinExe 7 | 8 | 9 | net9.0 10 | False 11 | enable 12 | Debug;Release 13 | AnyCPU;x64 14 | preview 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo/App.axaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo/App.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | 5 | namespace VirtualPanelDemo; 6 | 7 | public class App : Application 8 | { 9 | public override void Initialize() 10 | { 11 | AvaloniaXamlLoader.Load(this); 12 | } 13 | 14 | public override void OnFrameworkInitializationCompleted() 15 | { 16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 17 | { 18 | desktop.MainWindow = new MainWindow(); 19 | } 20 | else if (ApplicationLifetime is ISingleViewApplicationLifetime single) 21 | { 22 | single.MainView = new MainView(); 23 | } 24 | 25 | base.OnFrameworkInitializationCompleted(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo/MainView.axaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 35 | 36 | 37 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo/MainView.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Markup.Xaml; 3 | using VirtualPanelDemo.ViewModels; 4 | 5 | namespace VirtualPanelDemo; 6 | 7 | public partial class MainView : UserControl 8 | { 9 | public MainView() 10 | { 11 | InitializeComponent(); 12 | 13 | var vm = new MainWindowViewModel(2_000_000_000, 100, 100, 100); 14 | 15 | DataContext = vm; 16 | 17 | var vp = this.FindControl("VirtualPanel"); 18 | 19 | if (vm.Items is { }) 20 | { 21 | vm.Items.CollectionChanged += (_, _) => 22 | { 23 | vp?.InvalidateMeasure(); 24 | }; 25 | } 26 | } 27 | 28 | private void InitializeComponent() 29 | { 30 | AvaloniaXamlLoader.Load(this); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo/MainWindow.axaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo/MainWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Markup.Xaml; 4 | using Avalonia.Rendering; 5 | using VirtualPanelDemo.ViewModels; 6 | 7 | namespace VirtualPanelDemo; 8 | 9 | public partial class MainWindow : Window 10 | { 11 | public MainWindow() 12 | { 13 | InitializeComponent(); 14 | #if DEBUG 15 | this.AttachDevTools(); 16 | #endif 17 | //RendererDiagnostics.DebugOverlays = RendererDebugOverlays.Fps | RendererDebugOverlays.LayoutTimeGraph | RendererDebugOverlays.RenderTimeGraph; 18 | RendererDiagnostics.DebugOverlays = RendererDebugOverlays.Fps; 19 | } 20 | 21 | private void InitializeComponent() 22 | { 23 | AvaloniaXamlLoader.Load(this); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo/ViewModels/ItemProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DataVirtualization; 3 | 4 | namespace VirtualPanelDemo.ViewModels 5 | { 6 | public class ItemProvider(int count) : IItemsProvider 7 | { 8 | public int FetchCount() 9 | { 10 | return count; 11 | } 12 | 13 | public IList FetchRange(int startIndex, int pageCount, out int overallCount) 14 | { 15 | var result = new List(); 16 | var endIndex = startIndex + pageCount; 17 | 18 | overallCount = count; 19 | 20 | for (var i = startIndex; i < endIndex; i++) 21 | { 22 | //result.Add($"Item {i}"); 23 | result.Add($"{i}"); 24 | } 25 | 26 | return result; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using DataVirtualization; 2 | using ReactiveUI; 3 | 4 | namespace VirtualPanelDemo.ViewModels; 5 | 6 | public partial class MainWindowViewModel : ReactiveObject 7 | { 8 | [Reactive] 9 | public partial AsyncVirtualizingCollection? Items { get; set; } 10 | 11 | [Reactive] 12 | public partial double ItemHeight { get; set; } 13 | 14 | [Reactive] 15 | public partial double ItemWidth { get; set; } 16 | 17 | public int Count { get; } 18 | 19 | public MainWindowViewModel(int count, int pageSize, int itemHeight, int itemWidth) 20 | { 21 | Items = new AsyncVirtualizingCollection(new ItemProvider(count), pageSize, 5000); 22 | ItemHeight = itemHeight; 23 | ItemWidth = itemWidth; 24 | Count = count; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/VirtualPanelDemo/VirtualPanelDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net9.0 4 | Library 5 | False 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/DataBox.DataVirtualization/AsyncVirtualizingCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.Specialized; 3 | using System.ComponentModel; 4 | using System.Threading; 5 | 6 | namespace DataVirtualization 7 | { 8 | /// 9 | /// Derived VirtualizingCollection, performing loading asynchronously. 10 | /// 11 | /// The type of items in the collection 12 | public class AsyncVirtualizingCollection : VirtualizingCollection, INotifyCollectionChanged, INotifyPropertyChanged where T : class 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The items provider. 18 | /// Size of the page. 19 | /// The page timeout. 20 | public AsyncVirtualizingCollection(IItemsProvider itemsProvider, int pageSize, int pageTimeout) 21 | : base(itemsProvider, pageSize, pageTimeout) 22 | { 23 | SynchronizationContext = SynchronizationContext.Current; 24 | } 25 | 26 | #region SynchronizationContext 27 | 28 | /// 29 | /// Gets the synchronization context used for UI-related operations. This is obtained as 30 | /// the current SynchronizationContext when the AsyncVirtualizingCollection is created. 31 | /// 32 | /// The synchronization context. 33 | protected SynchronizationContext? SynchronizationContext { get; } 34 | 35 | #endregion 36 | 37 | #region INotifyCollectionChanged 38 | 39 | /// 40 | /// Occurs when the collection changes. 41 | /// 42 | public event NotifyCollectionChangedEventHandler? CollectionChanged; 43 | 44 | /// 45 | /// Raises the event. 46 | /// 47 | /// The instance containing the event data. 48 | protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 49 | { 50 | CollectionChanged?.Invoke(this, e); 51 | } 52 | 53 | /// 54 | /// Fires the collection reset event. 55 | /// 56 | private void FireCollectionReset() 57 | { 58 | var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); 59 | OnCollectionChanged(e); 60 | } 61 | 62 | #endregion 63 | 64 | #region INotifyPropertyChanged 65 | 66 | /// 67 | /// Occurs when a property value changes. 68 | /// 69 | public event PropertyChangedEventHandler? PropertyChanged; 70 | 71 | /// 72 | /// Raises the event. 73 | /// 74 | /// The instance containing the event data. 75 | protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) 76 | { 77 | PropertyChanged?.Invoke(this, e); 78 | } 79 | 80 | /// 81 | /// Fires the property changed event. 82 | /// 83 | /// Name of the property. 84 | private void FirePropertyChanged(string propertyName) 85 | { 86 | var e = new PropertyChangedEventArgs(propertyName); 87 | OnPropertyChanged(e); 88 | } 89 | 90 | #endregion 91 | 92 | #region IsLoading 93 | 94 | private bool _isLoading; 95 | 96 | /// 97 | /// Gets or sets a value indicating whether the collection is loading. 98 | /// 99 | /// 100 | /// true if this collection is loading; otherwise, false. 101 | /// 102 | public bool IsLoading 103 | { 104 | get 105 | { 106 | return _isLoading; 107 | } 108 | set 109 | { 110 | if (value != _isLoading) 111 | { 112 | _isLoading = value; 113 | FirePropertyChanged(nameof(IsLoading)); 114 | } 115 | } 116 | } 117 | 118 | private bool _isInitializing; 119 | 120 | public bool IsInitializing 121 | { 122 | get 123 | { 124 | return _isInitializing; 125 | } 126 | set 127 | { 128 | if (value != _isInitializing) 129 | { 130 | _isInitializing = value; 131 | FirePropertyChanged(nameof(IsInitializing)); 132 | } 133 | } 134 | } 135 | #endregion 136 | 137 | #region Load overrides 138 | 139 | /// 140 | /// Asynchronously loads the count of items. 141 | /// 142 | protected override void LoadCount() 143 | { 144 | if (Count == 0) 145 | { 146 | IsInitializing = true; 147 | } 148 | ThreadPool.QueueUserWorkItem(LoadCountWork); 149 | } 150 | 151 | /// 152 | /// Performed on background thread. 153 | /// 154 | /// None required. 155 | private void LoadCountWork(object? args) 156 | { 157 | var count = FetchCount(); 158 | SynchronizationContext?.Send(LoadCountCompleted, count); 159 | } 160 | 161 | /// 162 | /// Performed on UI-thread after LoadCountWork. 163 | /// 164 | /// Number of items returned. 165 | protected virtual void LoadCountCompleted(object? args) 166 | { 167 | if (args is int newCount) 168 | { 169 | TakeNewCount(newCount); 170 | } 171 | IsInitializing = false; 172 | } 173 | 174 | private void TakeNewCount(int newCount) 175 | { 176 | if (newCount != Count) 177 | { 178 | Count = newCount; 179 | EmptyCache(); 180 | FireCollectionReset(); 181 | } 182 | } 183 | 184 | /// 185 | /// Asynchronously loads the page. 186 | /// 187 | /// The page index. 188 | /// The page length. 189 | protected override void LoadPage(int pageIndex, int pageLength) 190 | { 191 | IsLoading = true; 192 | ThreadPool.QueueUserWorkItem(LoadPageWork, new[] { pageIndex, pageLength }); 193 | } 194 | 195 | /// 196 | /// Performed on background thread. 197 | /// 198 | /// Index of the page to load. 199 | private void LoadPageWork(object? state) 200 | { 201 | if (state is int[] args) 202 | { 203 | var pageIndex = args[0]; 204 | var pageLength = args[1]; 205 | var dataItems = FetchPage(pageIndex, pageLength, out var overallCount); 206 | SynchronizationContext?.Send(LoadPageCompleted, new object[] { pageIndex, dataItems, overallCount }); 207 | } 208 | } 209 | 210 | /// 211 | /// Performed on UI-thread after LoadPageWork. 212 | /// 213 | /// object[] { int pageIndex, IList(T) page } 214 | private void LoadPageCompleted(object? state) 215 | { 216 | if (state is object[] args) 217 | { 218 | var pageIndex = (int)args[0]; 219 | var dataItems = (IList)args[1]; 220 | var newCount = (int)args[2]; 221 | TakeNewCount(newCount); 222 | 223 | PopulatePage(pageIndex, dataItems); 224 | } 225 | IsLoading = false; 226 | } 227 | 228 | #endregion 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/DataBox.DataVirtualization/DataBox.DataVirtualization.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library 4 | netstandard2.0;net461;net6.0;net8.0 5 | True 6 | enable 7 | Debug;Release 8 | AnyCPU;x64 9 | DataVirtualization 10 | 11 | 12 | 13 | DataBox.DataVirtualization 14 | Specialized list implementation that provides data virtualization. 15 | data;virtualization;scrolling 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/DataBox.DataVirtualization/DataPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace DataVirtualization 6 | { 7 | public class DataPage where T : class 8 | { 9 | public DataPage(int firstIndex, int pageLength) 10 | { 11 | Items = new List>(pageLength); 12 | for (var i = 0; i < pageLength; i++) 13 | { 14 | Items.Add(new DataWrapper(firstIndex + i)); 15 | } 16 | TouchTime = DateTime.Now; 17 | } 18 | 19 | public IList> Items { get; set; } 20 | 21 | public DateTime TouchTime { get; set; } 22 | 23 | public bool IsInUse 24 | { 25 | get { return Items.Any(wrapper => wrapper.IsInUse); } 26 | } 27 | 28 | public void Populate(IList newItems) 29 | { 30 | int i; 31 | var index = 0; 32 | for (i = 0; i < newItems.Count && i < Items.Count; i++) 33 | { 34 | Items[i].Data = newItems[i]; 35 | index = Items[i].Index; 36 | } 37 | 38 | while (i < newItems.Count) 39 | { 40 | index++; 41 | Items.Add(new DataWrapper(index) { Data = newItems[i] }); 42 | i++; 43 | } 44 | 45 | while (i < Items.Count) 46 | { 47 | Items.RemoveAt(Items.Count - 1); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/DataBox.DataVirtualization/DataWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace DataVirtualization 4 | { 5 | public class DataWrapper : INotifyPropertyChanged where T : class 6 | { 7 | private readonly int _index; 8 | private T? _data; 9 | 10 | public event PropertyChangedEventHandler? PropertyChanged; 11 | 12 | public DataWrapper(int index) 13 | { 14 | _index = index; 15 | } 16 | 17 | public int Index 18 | { 19 | get { return _index; } 20 | } 21 | 22 | public int ItemNumber 23 | { 24 | get { return _index + 1; } 25 | } 26 | 27 | public bool IsLoading 28 | { 29 | get { return Data == null; } 30 | } 31 | 32 | public T? Data 33 | { 34 | get { return _data; } 35 | internal set 36 | { 37 | _data = value; 38 | OnPropertyChanged(nameof(Data)); 39 | OnPropertyChanged(nameof(IsLoading)); 40 | } 41 | } 42 | 43 | public bool IsInUse 44 | { 45 | get { return PropertyChanged != null; } 46 | } 47 | 48 | private void OnPropertyChanged(string propertyName) 49 | { 50 | System.Diagnostics.Debug.Assert(GetType().GetProperty(propertyName) != null); 51 | var handler = PropertyChanged; 52 | handler?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/DataBox.DataVirtualization/IItemsProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DataVirtualization 4 | { 5 | /// 6 | /// Represents a provider of collection details. 7 | /// 8 | /// The type of items in the collection. 9 | public interface IItemsProvider 10 | { 11 | /// 12 | /// Fetches the total number of items available. 13 | /// 14 | /// 15 | int FetchCount(); 16 | 17 | /// 18 | /// Fetches a range of items. 19 | /// 20 | /// The start index. 21 | /// The number of items to fetch. 22 | /// The overall items count. 23 | /// 24 | IList FetchRange(int startIndex, int pageCount, out int overallCount); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/DataBox.DataVirtualization/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Beatriz Stollnitz 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 | -------------------------------------------------------------------------------- /src/DataBox.DataVirtualization/README.txt: -------------------------------------------------------------------------------- 1 | https://github.com/bstollnitz/old-wpf-blog/tree/master/64-DataVirtualizationFilteringSorting -------------------------------------------------------------------------------- /src/DataBox/Automation/Peers/DataBoxAutomationPeer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Automation.Peers; 2 | using Avalonia.Automation.Provider; 3 | using Avalonia.Controls; 4 | 5 | namespace DataBox.Automation.Peers; 6 | 7 | public class DataBoxAutomationPeer : ControlAutomationPeer, IScrollProvider 8 | { 9 | private bool _searchedForScrollable; 10 | private IScrollProvider? _scroller; 11 | 12 | public DataBoxAutomationPeer(DataBox owner) 13 | : base(owner) 14 | { 15 | } 16 | 17 | public new DataBox Owner => (DataBox)base.Owner; 18 | public bool HorizontallyScrollable => _scroller?.HorizontallyScrollable ?? false; 19 | public double HorizontalScrollPercent => _scroller?.HorizontalScrollPercent ?? -1; 20 | public double HorizontalViewSize => _scroller?.HorizontalViewSize ?? 0; 21 | public bool VerticallyScrollable => _scroller?.VerticallyScrollable ?? false; 22 | public double VerticalScrollPercent => _scroller?.VerticalScrollPercent ?? -1; 23 | public double VerticalViewSize => _scroller?.VerticalViewSize ?? 0; 24 | 25 | protected virtual IScrollProvider? Scroller 26 | { 27 | get 28 | { 29 | if (!_searchedForScrollable) 30 | { 31 | if (Owner.RowsPresenter?.GetValue(ListBox.ScrollProperty) is Control scrollable) 32 | _scroller = GetOrCreate(scrollable).GetProvider(); 33 | _searchedForScrollable = true; 34 | } 35 | 36 | return _scroller; 37 | } 38 | } 39 | 40 | protected override AutomationControlType GetAutomationControlTypeCore() 41 | { 42 | return AutomationControlType.DataGrid; 43 | } 44 | 45 | public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount) 46 | { 47 | _scroller?.Scroll(horizontalAmount, verticalAmount); 48 | } 49 | 50 | public void SetScrollPercent(double horizontalPercent, double verticalPercent) 51 | { 52 | _scroller?.SetScrollPercent(horizontalPercent, verticalPercent); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/DataBox/Automation/Peers/DataBoxCellAutomationPeer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Automation.Peers; 2 | 3 | namespace DataBox.Automation.Peers; 4 | 5 | public class DataBoxCellAutomationPeer : ContentControlAutomationPeer 6 | { 7 | public DataBoxCellAutomationPeer(DataBoxCell owner) 8 | : base(owner) 9 | { 10 | } 11 | 12 | public new DataBoxCell Owner => (DataBoxCell)base.Owner; 13 | 14 | protected override AutomationControlType GetAutomationControlTypeCore() 15 | { 16 | return AutomationControlType.Custom; 17 | } 18 | 19 | protected override bool IsContentElementCore() => true; 20 | 21 | protected override bool IsControlElementCore() => true; 22 | } 23 | -------------------------------------------------------------------------------- /src/DataBox/Automation/Peers/DataBoxColumnHeaderAutomationPeer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Automation.Peers; 2 | 3 | namespace DataBox.Automation.Peers; 4 | 5 | public class DataBoxColumnHeaderAutomationPeer : ContentControlAutomationPeer 6 | { 7 | public DataBoxColumnHeaderAutomationPeer(DataBoxColumnHeader owner) 8 | : base(owner) 9 | { 10 | } 11 | 12 | public new DataBoxColumnHeader Owner => (DataBoxColumnHeader)base.Owner; 13 | 14 | protected override AutomationControlType GetAutomationControlTypeCore() 15 | { 16 | return AutomationControlType.HeaderItem; 17 | } 18 | 19 | protected override bool IsContentElementCore() => false; 20 | 21 | protected override bool IsControlElementCore() => true; 22 | } 23 | -------------------------------------------------------------------------------- /src/DataBox/Automation/Peers/DataBoxColumnHeadersPresenterAutomationPeer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Automation.Peers; 2 | using DataBox.Primitives; 3 | 4 | namespace DataBox.Automation.Peers; 5 | 6 | public class DataBoxColumnHeadersPresenterAutomationPeer : ControlAutomationPeer 7 | { 8 | public DataBoxColumnHeadersPresenterAutomationPeer(DataBoxColumnHeadersPresenter owner) 9 | : base(owner) 10 | { 11 | } 12 | 13 | public new DataBoxColumnHeadersPresenter Owner => (DataBoxColumnHeadersPresenter)base.Owner; 14 | 15 | protected override AutomationControlType GetAutomationControlTypeCore() 16 | { 17 | return AutomationControlType.Header; 18 | } 19 | 20 | protected override bool IsContentElementCore() => false; 21 | 22 | protected override bool IsControlElementCore() => true; 23 | } 24 | -------------------------------------------------------------------------------- /src/DataBox/Automation/Peers/DataBoxRowAutomationPeer.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Automation.Peers; 2 | using Avalonia.Automation.Provider; 3 | using Avalonia.Controls; 4 | 5 | namespace DataBox.Automation.Peers; 6 | 7 | public class DataBoxRowAutomationPeer : ContentControlAutomationPeer, 8 | ISelectionItemProvider 9 | { 10 | public DataBoxRowAutomationPeer(ContentControl owner) 11 | : base(owner) 12 | { 13 | } 14 | 15 | public bool IsSelected => Owner.GetValue(ListBoxItem.IsSelectedProperty); 16 | 17 | public ISelectionProvider? SelectionContainer 18 | { 19 | get 20 | { 21 | if (Owner.Parent is DataBox parent) 22 | { 23 | var parentPeer = GetOrCreate(parent); 24 | return parentPeer.GetProvider(); 25 | } 26 | 27 | return null; 28 | } 29 | } 30 | 31 | public void Select() 32 | { 33 | EnsureEnabled(); 34 | 35 | if (Owner.Parent is DataBox parent && parent.RowsPresenter is not null) 36 | { 37 | var index = parent.RowsPresenter.IndexFromContainer(Owner); 38 | 39 | if (index != -1) 40 | parent.RowsPresenter.SelectedIndex = index; 41 | } 42 | } 43 | 44 | void ISelectionItemProvider.AddToSelection() 45 | { 46 | EnsureEnabled(); 47 | 48 | if (Owner.Parent is DataBox parent 49 | && parent.RowsPresenter is not null 50 | && parent.RowsPresenter.GetValue(ListBox.SelectionProperty) is { } selectionModel) 51 | { 52 | var index = parent.RowsPresenter.IndexFromContainer(Owner); 53 | 54 | if (index != -1) 55 | selectionModel.Select(index); 56 | } 57 | } 58 | 59 | void ISelectionItemProvider.RemoveFromSelection() 60 | { 61 | EnsureEnabled(); 62 | 63 | if (Owner.Parent is DataBox parent 64 | && parent.RowsPresenter is not null 65 | && parent.GetValue(ListBox.SelectionProperty) is { } selectionModel) 66 | { 67 | var index = parent.RowsPresenter.IndexFromContainer(Owner); 68 | 69 | if (index != -1) 70 | selectionModel.Deselect(index); 71 | } 72 | } 73 | 74 | protected override AutomationControlType GetAutomationControlTypeCore() 75 | { 76 | return AutomationControlType.DataItem; 77 | } 78 | 79 | protected override bool IsContentElementCore() => true; 80 | protected override bool IsControlElementCore() => true; 81 | } 82 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxAutoCompleteColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using Avalonia.Data; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxAutoCompleteColumn : DataBoxBoundColumn 10 | { 11 | public static readonly StyledProperty ItemsProperty = 12 | AvaloniaProperty.Register(nameof(Items)); 13 | 14 | public static readonly StyledProperty SelectedItemProperty = 15 | AvaloniaProperty.Register(nameof(SelectedItem)); 16 | 17 | public static readonly StyledProperty TextProperty = 18 | AvaloniaProperty.Register(nameof(Text)); 19 | 20 | [AssignBinding] 21 | public IBinding? SelectedItem 22 | { 23 | get => GetValue(SelectedItemProperty); 24 | set => SetValue(SelectedItemProperty, value); 25 | } 26 | 27 | [AssignBinding] 28 | public IBinding? Items 29 | { 30 | get => GetValue(ItemsProperty); 31 | set => SetValue(ItemsProperty, value); 32 | } 33 | 34 | [AssignBinding] 35 | public IBinding? Text 36 | { 37 | get => GetValue(TextProperty); 38 | set => SetValue(TextProperty, value); 39 | } 40 | 41 | public DataBoxAutoCompleteColumn() 42 | { 43 | CellTemplate = new FuncDataTemplate( 44 | _ => true, 45 | (_, _) => 46 | { 47 | var autoCompleteBox = new AutoCompleteBox 48 | { 49 | VerticalAlignment = VerticalAlignment.Center 50 | }; 51 | 52 | if (Items is { }) 53 | { 54 | autoCompleteBox.Bind(AutoCompleteBox.ItemsSourceProperty, Items); 55 | } 56 | 57 | if (SelectedItem is { }) 58 | { 59 | autoCompleteBox.Bind(AutoCompleteBox.SelectedItemProperty, SelectedItem); 60 | } 61 | 62 | if (Text is { }) 63 | { 64 | autoCompleteBox.Bind(AutoCompleteBox.TextProperty, Text); 65 | } 66 | 67 | return autoCompleteBox; 68 | }, 69 | supportsRecycling: true); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxBoundColumn.cs: -------------------------------------------------------------------------------- 1 | namespace DataBox.Columns; 2 | 3 | public abstract class DataBoxBoundColumn : DataBoxColumn 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxButtonColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using Avalonia.Data; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxButtonColumn : DataBoxContentColumn 10 | { 11 | public static readonly StyledProperty CommandProperty = 12 | AvaloniaProperty.Register(nameof(Command)); 13 | 14 | public static readonly StyledProperty CommandParameterProperty = 15 | AvaloniaProperty.Register(nameof(CommandParameter)); 16 | 17 | [AssignBinding] 18 | public IBinding? Command 19 | { 20 | get => GetValue(CommandProperty); 21 | set => SetValue(CommandProperty, value); 22 | } 23 | 24 | [AssignBinding] 25 | public IBinding? CommandParameter 26 | { 27 | get => GetValue(CommandParameterProperty); 28 | set => SetValue(CommandParameterProperty, value); 29 | } 30 | 31 | public DataBoxButtonColumn() 32 | { 33 | CellTemplate = new FuncDataTemplate( 34 | _ => true, 35 | (_, _) => 36 | { 37 | var button = new Button 38 | { 39 | HorizontalAlignment = HorizontalAlignment.Stretch, 40 | HorizontalContentAlignment = HorizontalAlignment.Center, 41 | VerticalAlignment = VerticalAlignment.Stretch, 42 | VerticalContentAlignment = VerticalAlignment.Center, 43 | }; 44 | 45 | if (Content is { }) 46 | { 47 | button.Bind(ContentControl.ContentProperty, Content); 48 | } 49 | 50 | if (Command is { }) 51 | { 52 | button.Bind(Button.CommandProperty, Command); 53 | } 54 | 55 | if (CommandParameter is { }) 56 | { 57 | button.Bind(Button.CommandParameterProperty, CommandParameter); 58 | } 59 | 60 | return button; 61 | }, 62 | supportsRecycling: true); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxCheckBoxColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Primitives; 4 | using Avalonia.Controls.Templates; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxCheckBoxColumn : DataBoxToggleColumn 10 | { 11 | public DataBoxCheckBoxColumn() 12 | { 13 | CellTemplate = new FuncDataTemplate( 14 | _ => true, 15 | (_, _) => 16 | { 17 | var checkBox = new CheckBox 18 | { 19 | HorizontalAlignment = HorizontalAlignment.Stretch, 20 | HorizontalContentAlignment = HorizontalAlignment.Center, 21 | VerticalAlignment = VerticalAlignment.Center, 22 | VerticalContentAlignment = VerticalAlignment.Stretch, 23 | }; 24 | 25 | if (Content is { }) 26 | { 27 | checkBox.Bind(ContentControl.ContentProperty, Content); 28 | } 29 | 30 | if (Command is { }) 31 | { 32 | checkBox.Bind(Button.CommandProperty, Command); 33 | } 34 | 35 | if (CommandParameter is { }) 36 | { 37 | checkBox.Bind(Button.CommandParameterProperty, CommandParameter); 38 | } 39 | 40 | if (IsChecked is { }) 41 | { 42 | checkBox.Bind(ToggleButton.IsCheckedProperty, IsChecked); 43 | } 44 | 45 | return checkBox; 46 | }, 47 | supportsRecycling: true); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxContentColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using Avalonia.Data; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxContentColumn : DataBoxBoundColumn 10 | { 11 | public static readonly StyledProperty ContentProperty = 12 | AvaloniaProperty.Register(nameof(Content)); 13 | 14 | [AssignBinding] 15 | public IBinding? Content 16 | { 17 | get => GetValue(ContentProperty); 18 | set => SetValue(ContentProperty, value); 19 | } 20 | 21 | public DataBoxContentColumn() 22 | { 23 | CellTemplate = new FuncDataTemplate( 24 | _ => true, 25 | (_, _) => 26 | { 27 | var button = new ContentControl 28 | { 29 | HorizontalAlignment = HorizontalAlignment.Center, 30 | }; 31 | 32 | if (Content is { }) 33 | { 34 | button.Bind(ContentControl.ContentProperty, Content); 35 | } 36 | 37 | return button; 38 | }, 39 | supportsRecycling: true); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxDateColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using Avalonia.Data; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxDateColumn : DataBoxBoundColumn 10 | { 11 | public static readonly StyledProperty SelectedDateProperty = 12 | AvaloniaProperty.Register(nameof(SelectedDate)); 13 | 14 | [AssignBinding] 15 | public IBinding? SelectedDate 16 | { 17 | get => GetValue(SelectedDateProperty); 18 | set => SetValue(SelectedDateProperty, value); 19 | } 20 | 21 | public DataBoxDateColumn() 22 | { 23 | CellTemplate = new FuncDataTemplate( 24 | _ => true, 25 | (_, _) => 26 | { 27 | var datePicker = new DatePicker 28 | { 29 | VerticalAlignment = VerticalAlignment.Center 30 | }; 31 | 32 | if (SelectedDate is { }) 33 | { 34 | datePicker.Bind(DatePicker.SelectedDateProperty, SelectedDate); 35 | } 36 | 37 | return datePicker; 38 | }, 39 | supportsRecycling: true); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxEntryColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using Avalonia.Data; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxEntryColumn : DataBoxBoundColumn 10 | { 11 | public static readonly StyledProperty TextProperty = 12 | AvaloniaProperty.Register(nameof(Text)); 13 | 14 | [AssignBinding] 15 | public IBinding? Text 16 | { 17 | get => GetValue(TextProperty); 18 | set => SetValue(TextProperty, value); 19 | } 20 | 21 | public DataBoxEntryColumn() 22 | { 23 | CellTemplate = new FuncDataTemplate( 24 | _ => true, 25 | (_, _) => 26 | { 27 | var textBox = new TextBox 28 | { 29 | VerticalAlignment = VerticalAlignment.Center 30 | }; 31 | 32 | if (Text is { }) 33 | { 34 | textBox.Bind(TextBox.TextProperty, Text); 35 | } 36 | 37 | return textBox; 38 | }, 39 | supportsRecycling: true); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxItemsColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Primitives; 4 | using Avalonia.Controls.Templates; 5 | using Avalonia.Data; 6 | using Avalonia.Layout; 7 | 8 | namespace DataBox.Columns; 9 | 10 | public class DataBoxItemsColumn : DataBoxBoundColumn 11 | { 12 | public static readonly StyledProperty ItemsProperty = 13 | AvaloniaProperty.Register(nameof(Items)); 14 | 15 | public static readonly StyledProperty SelectedItemProperty = 16 | AvaloniaProperty.Register(nameof(SelectedItem)); 17 | 18 | [AssignBinding] 19 | public IBinding? SelectedItem 20 | { 21 | get => GetValue(SelectedItemProperty); 22 | set => SetValue(SelectedItemProperty, value); 23 | } 24 | 25 | [AssignBinding] 26 | public IBinding? Items 27 | { 28 | get => GetValue(ItemsProperty); 29 | set => SetValue(ItemsProperty, value); 30 | } 31 | 32 | public DataBoxItemsColumn() 33 | { 34 | CellTemplate = new FuncDataTemplate( 35 | _ => true, 36 | (_, _) => 37 | { 38 | var comboBox = new ComboBox 39 | { 40 | HorizontalAlignment = HorizontalAlignment.Center, 41 | VerticalAlignment = VerticalAlignment.Center, 42 | }; 43 | 44 | if (Items is { }) 45 | { 46 | comboBox.Bind(ItemsControl.ItemsSourceProperty, Items); 47 | } 48 | 49 | if (SelectedItem is { }) 50 | { 51 | comboBox.Bind(SelectingItemsControl.SelectedItemProperty, SelectedItem); 52 | } 53 | 54 | return comboBox; 55 | }, 56 | supportsRecycling: true); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxLabelColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using Avalonia.Layout; 5 | 6 | namespace DataBox.Columns; 7 | 8 | public class DataBoxLabelColumn : DataBoxContentColumn 9 | { 10 | public DataBoxLabelColumn() 11 | { 12 | CellTemplate = new FuncDataTemplate( 13 | _ => true, 14 | (_, _) => 15 | { 16 | var label = new Label 17 | { 18 | VerticalAlignment = VerticalAlignment.Center 19 | }; 20 | 21 | if (Content is { }) 22 | { 23 | label.Bind(ContentControl.ContentProperty, Content); 24 | } 25 | 26 | return label; 27 | }, 28 | supportsRecycling: true); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxNumericColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using Avalonia.Data; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxNumericColumn : DataBoxBoundColumn 10 | { 11 | public static readonly StyledProperty ValueProperty = 12 | AvaloniaProperty.Register(nameof(Value)); 13 | 14 | public static readonly StyledProperty MinimumProperty = 15 | AvaloniaProperty.Register(nameof(Minimum)); 16 | 17 | public static readonly StyledProperty MaximumProperty = 18 | AvaloniaProperty.Register(nameof(Maximum)); 19 | 20 | public static readonly StyledProperty IncrementProperty = 21 | AvaloniaProperty.Register(nameof(Increment)); 22 | 23 | [AssignBinding] 24 | public IBinding? Value 25 | { 26 | get => GetValue(ValueProperty); 27 | set => SetValue(ValueProperty, value); 28 | } 29 | 30 | [AssignBinding] 31 | public IBinding? Minimum 32 | { 33 | get => GetValue(MinimumProperty); 34 | set => SetValue(MinimumProperty, value); 35 | } 36 | 37 | [AssignBinding] 38 | public IBinding? Maximum 39 | { 40 | get => GetValue(MaximumProperty); 41 | set => SetValue(MaximumProperty, value); 42 | } 43 | 44 | [AssignBinding] 45 | public IBinding? Increment 46 | { 47 | get => GetValue(IncrementProperty); 48 | set => SetValue(IncrementProperty, value); 49 | } 50 | 51 | public DataBoxNumericColumn() 52 | { 53 | CellTemplate = new FuncDataTemplate( 54 | _ => true, 55 | (_, _) => 56 | { 57 | var numericUpDown = new NumericUpDown 58 | { 59 | VerticalAlignment = VerticalAlignment.Center 60 | }; 61 | 62 | if (Value is { }) 63 | { 64 | numericUpDown.Bind(NumericUpDown.ValueProperty, Value); 65 | } 66 | 67 | if (Minimum is { }) 68 | { 69 | numericUpDown.Bind(NumericUpDown.MinimumProperty, Minimum); 70 | } 71 | 72 | if (Maximum is { }) 73 | { 74 | numericUpDown.Bind(NumericUpDown.MaximumProperty, Maximum); 75 | } 76 | 77 | if (Increment is { }) 78 | { 79 | numericUpDown.Bind(NumericUpDown.IncrementProperty, Increment); 80 | } 81 | 82 | return numericUpDown; 83 | }, 84 | supportsRecycling: true); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxProgressColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Primitives; 4 | using Avalonia.Controls.Templates; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxProgressColumn : DataBoxRangeColumn 10 | { 11 | public DataBoxProgressColumn() 12 | { 13 | CellTemplate = new FuncDataTemplate( 14 | _ => true, 15 | (_, _) => 16 | { 17 | var progressbar = new ProgressBar 18 | { 19 | HorizontalAlignment = HorizontalAlignment.Stretch, 20 | VerticalAlignment = VerticalAlignment.Stretch, 21 | }; 22 | 23 | if (Value is { }) 24 | { 25 | progressbar.Bind(RangeBase.ValueProperty, Value); 26 | } 27 | 28 | if (Minimum is { }) 29 | { 30 | progressbar.Bind(RangeBase.MinimumProperty, Minimum); 31 | } 32 | 33 | if (Maximum is { }) 34 | { 35 | progressbar.Bind(RangeBase.MaximumProperty, Maximum); 36 | } 37 | 38 | return progressbar; 39 | }, 40 | supportsRecycling: true); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxRangeColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Data; 3 | 4 | namespace DataBox.Columns; 5 | 6 | public abstract class DataBoxRangeColumn : DataBoxBoundColumn 7 | { 8 | public static readonly StyledProperty ValueProperty = 9 | AvaloniaProperty.Register(nameof(Value)); 10 | 11 | public static readonly StyledProperty MinimumProperty = 12 | AvaloniaProperty.Register(nameof(Minimum)); 13 | 14 | public static readonly StyledProperty MaximumProperty = 15 | AvaloniaProperty.Register(nameof(Maximum)); 16 | 17 | [AssignBinding] 18 | public IBinding? Value 19 | { 20 | get => GetValue(ValueProperty); 21 | set => SetValue(ValueProperty, value); 22 | } 23 | 24 | [AssignBinding] 25 | public IBinding? Minimum 26 | { 27 | get => GetValue(MinimumProperty); 28 | set => SetValue(MinimumProperty, value); 29 | } 30 | 31 | [AssignBinding] 32 | public IBinding? Maximum 33 | { 34 | get => GetValue(MaximumProperty); 35 | set => SetValue(MaximumProperty, value); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxSliderColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Primitives; 4 | using Avalonia.Controls.Templates; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxSliderColumn : DataBoxRangeColumn 10 | { 11 | public DataBoxSliderColumn() 12 | { 13 | CellTemplate = new FuncDataTemplate( 14 | _ => true, 15 | (_, _) => 16 | { 17 | var slider = new Slider 18 | { 19 | HorizontalAlignment = HorizontalAlignment.Stretch, 20 | VerticalAlignment = VerticalAlignment.Stretch, 21 | }; 22 | 23 | if (Value is { }) 24 | { 25 | slider.Bind(RangeBase.ValueProperty, Value); 26 | } 27 | 28 | if (Minimum is { }) 29 | { 30 | slider.Bind(RangeBase.MinimumProperty, Minimum); 31 | } 32 | 33 | if (Maximum is { }) 34 | { 35 | slider.Bind(RangeBase.MaximumProperty, Maximum); 36 | } 37 | 38 | return slider; 39 | }, 40 | supportsRecycling: true); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxSwitchColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Primitives; 4 | using Avalonia.Controls.Templates; 5 | using Avalonia.Data; 6 | using Avalonia.Layout; 7 | 8 | namespace DataBox.Columns; 9 | 10 | public class DataBoxSwitchColumn : DataBoxToggleColumn 11 | { 12 | public static readonly StyledProperty OffContentProperty = 13 | AvaloniaProperty.Register(nameof(OffContent)); 14 | 15 | public static readonly StyledProperty OnContentProperty = 16 | AvaloniaProperty.Register(nameof(OnContent)); 17 | 18 | [AssignBinding] 19 | public IBinding? OffContent 20 | { 21 | get => GetValue(OffContentProperty); 22 | set => SetValue(OffContentProperty, value); 23 | } 24 | 25 | [AssignBinding] 26 | public IBinding? OnContent 27 | { 28 | get => GetValue(OnContentProperty); 29 | set => SetValue(OnContentProperty, value); 30 | } 31 | 32 | public DataBoxSwitchColumn() 33 | { 34 | CellTemplate = new FuncDataTemplate( 35 | _ => true, 36 | (_, _) => 37 | { 38 | var toggleSwitch = new ToggleSwitch 39 | { 40 | HorizontalAlignment = HorizontalAlignment.Stretch, 41 | HorizontalContentAlignment = HorizontalAlignment.Center, 42 | VerticalAlignment = VerticalAlignment.Stretch, 43 | VerticalContentAlignment = VerticalAlignment.Center, 44 | }; 45 | 46 | if (OffContent is { }) 47 | { 48 | toggleSwitch.Bind(ContentControl.ContentProperty, OffContent); 49 | } 50 | 51 | if (Command is { }) 52 | { 53 | toggleSwitch.Bind(Button.CommandProperty, Command); 54 | } 55 | 56 | if (CommandParameter is { }) 57 | { 58 | toggleSwitch.Bind(Button.CommandParameterProperty, CommandParameter); 59 | } 60 | 61 | if (IsChecked is { }) 62 | { 63 | toggleSwitch.Bind(ToggleButton.IsCheckedProperty, IsChecked); 64 | } 65 | 66 | if (OffContent is { }) 67 | { 68 | toggleSwitch.Bind(ToggleSwitch.OffContentProperty, OffContent); 69 | } 70 | 71 | if (OnContent is { }) 72 | { 73 | toggleSwitch.Bind(ToggleSwitch.OnContentProperty, OnContent); 74 | } 75 | 76 | return toggleSwitch; 77 | }, 78 | supportsRecycling: true); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxTemplateColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Data; 3 | 4 | namespace DataBox.Columns; 5 | 6 | public class DataBoxTemplateColumn : DataBoxColumn 7 | { 8 | public static readonly StyledProperty BindingProperty = 9 | AvaloniaProperty.Register(nameof(Binding)); 10 | 11 | [AssignBinding] 12 | public IBinding? Binding 13 | { 14 | get => GetValue(BindingProperty); 15 | set => SetValue(BindingProperty, value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxTextColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using Avalonia.Data; 5 | using Avalonia.Layout; 6 | using Avalonia.Markup.Xaml.MarkupExtensions; 7 | 8 | namespace DataBox.Columns; 9 | 10 | public class DataBoxTextColumn : DataBoxBoundColumn 11 | { 12 | public static readonly StyledProperty TextProperty = 13 | AvaloniaProperty.Register(nameof(Text)); 14 | 15 | [AssignBinding] 16 | public IBinding? Text 17 | { 18 | get => GetValue(TextProperty); 19 | set => SetValue(TextProperty, value); 20 | } 21 | 22 | public DataBoxTextColumn() 23 | { 24 | CellTemplate = new FuncDataTemplate( 25 | _ => true, 26 | (_, _) => 27 | { 28 | var textBlock = new TextBlock 29 | { 30 | [!Layoutable.MarginProperty] = new DynamicResourceExtension("DataGridTextColumnCellTextBlockMargin"), 31 | VerticalAlignment = VerticalAlignment.Center 32 | }; 33 | 34 | if (Text is { }) 35 | { 36 | textBlock.Bind(TextBlock.TextProperty, Text); 37 | } 38 | 39 | return textBlock; 40 | }, 41 | supportsRecycling: true); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxTimeColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Templates; 4 | using Avalonia.Data; 5 | using Avalonia.Layout; 6 | 7 | namespace DataBox.Columns; 8 | 9 | public class DataBoxTimeColumn : DataBoxBoundColumn 10 | { 11 | public static readonly StyledProperty SelectedTimeProperty = 12 | AvaloniaProperty.Register(nameof(SelectedTime)); 13 | 14 | [AssignBinding] 15 | public IBinding? SelectedTime 16 | { 17 | get => GetValue(SelectedTimeProperty); 18 | set => SetValue(SelectedTimeProperty, value); 19 | } 20 | 21 | public DataBoxTimeColumn() 22 | { 23 | CellTemplate = new FuncDataTemplate( 24 | _ => true, 25 | (_, _) => 26 | { 27 | var timePicker = new TimePicker 28 | { 29 | VerticalAlignment = VerticalAlignment.Center 30 | }; 31 | 32 | if (SelectedTime is { }) 33 | { 34 | timePicker.Bind(TimePicker.SelectedTimeProperty, SelectedTime); 35 | } 36 | 37 | return timePicker; 38 | }, 39 | supportsRecycling: true); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DataBox/Columns/DataBoxToggleColumn.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls; 3 | using Avalonia.Controls.Primitives; 4 | using Avalonia.Controls.Templates; 5 | using Avalonia.Data; 6 | using Avalonia.Layout; 7 | 8 | namespace DataBox.Columns; 9 | 10 | public class DataBoxToggleColumn : DataBoxButtonColumn 11 | { 12 | public static readonly StyledProperty IsCheckedProperty = 13 | AvaloniaProperty.Register(nameof(IsChecked)); 14 | 15 | [AssignBinding] 16 | public IBinding? IsChecked 17 | { 18 | get => GetValue(IsCheckedProperty); 19 | set => SetValue(IsCheckedProperty, value); 20 | } 21 | 22 | public DataBoxToggleColumn() 23 | { 24 | CellTemplate = new FuncDataTemplate( 25 | _ => true, 26 | (_, _) => 27 | { 28 | var toggleButton = new ToggleButton 29 | { 30 | HorizontalAlignment = HorizontalAlignment.Stretch, 31 | HorizontalContentAlignment = HorizontalAlignment.Center, 32 | VerticalAlignment = VerticalAlignment.Stretch, 33 | VerticalContentAlignment = VerticalAlignment.Center, 34 | }; 35 | 36 | if (Content is { }) 37 | { 38 | toggleButton.Bind(ContentControl.ContentProperty, Content); 39 | } 40 | 41 | if (Command is { }) 42 | { 43 | toggleButton.Bind(Button.CommandProperty, Command); 44 | } 45 | 46 | if (CommandParameter is { }) 47 | { 48 | toggleButton.Bind(Button.CommandParameterProperty, CommandParameter); 49 | } 50 | 51 | if (IsChecked is { }) 52 | { 53 | toggleButton.Bind(ToggleButton.IsCheckedProperty, IsChecked); 54 | } 55 | 56 | return toggleButton; 57 | }, 58 | supportsRecycling: true); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/DataBox/Controls/DataBoxPanel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Avalonia; 4 | using Avalonia.Controls; 5 | using Avalonia.LogicalTree; 6 | using DataBox.Primitives.Layout; 7 | 8 | namespace DataBox.Controls; 9 | 10 | public class DataBoxPanel : VirtualizingStackPanel 11 | { 12 | internal DataBox? DataBox { get; set; } 13 | 14 | protected override Type StyleKeyOverride => typeof(DataBoxPanel); 15 | 16 | public override void ApplyTemplate() 17 | { 18 | base.ApplyTemplate(); 19 | 20 | DataBox = this.GetLogicalAncestors().FirstOrDefault(x => x is DataBox) as DataBox; 21 | } 22 | 23 | protected override Size MeasureOverride(Size availableSize) 24 | { 25 | if (DataBox is null) 26 | { 27 | return availableSize; 28 | } 29 | 30 | var children = GetRealizedContainers(); 31 | 32 | if (children is null) 33 | { 34 | availableSize = base.MeasureOverride(availableSize); 35 | children = GetRealizedContainers(); 36 | } 37 | 38 | return children is { } 39 | ? DataBoxRowsLayout.Measure(availableSize, DataBox, base.MeasureOverride, base.InvalidateMeasure, children) 40 | : availableSize; 41 | } 42 | 43 | protected override Size ArrangeOverride(Size finalSize) 44 | { 45 | // TODO: 46 | // finalSize = base.ArrangeOverride(finalSize); 47 | 48 | if (DataBox is null) 49 | { 50 | return finalSize; 51 | } 52 | 53 | var children = GetRealizedContainers(); 54 | return children is { } 55 | ? DataBoxRowsLayout.Arrange(finalSize, DataBox, base.ArrangeOverride, children) 56 | : finalSize; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/DataBox/DataBox.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using Avalonia; 3 | using Avalonia.Automation.Peers; 4 | using Avalonia.Collections; 5 | using Avalonia.Controls; 6 | using Avalonia.Controls.Primitives; 7 | using Avalonia.Data; 8 | using Avalonia.Media; 9 | using Avalonia.Metadata; 10 | using DataBox.Automation.Peers; 11 | using DataBox.Primitives; 12 | 13 | namespace DataBox; 14 | 15 | public class DataBox : TemplatedControl 16 | { 17 | public static readonly DirectProperty ItemsSourceProperty = 18 | AvaloniaProperty.RegisterDirect( 19 | nameof(ItemsSource), 20 | o => o.ItemsSource, 21 | (o, v) => o.ItemsSource = v); 22 | 23 | public static readonly DirectProperty SelectedItemProperty = 24 | AvaloniaProperty.RegisterDirect( 25 | nameof(SelectedItem), 26 | o => o.SelectedItem, 27 | (o, v) => o.SelectedItem = v, 28 | defaultBindingMode: BindingMode.TwoWay); 29 | 30 | public static readonly DirectProperty> ColumnsProperty = 31 | AvaloniaProperty.RegisterDirect>( 32 | nameof(Columns), 33 | o => o.Columns); 34 | 35 | public static readonly StyledProperty CanUserSortColumnsProperty = 36 | AvaloniaProperty.Register(nameof(CanUserSortColumns), true); 37 | 38 | public static readonly StyledProperty CanUserResizeColumnsProperty = 39 | AvaloniaProperty.Register(nameof(CanUserResizeColumns)); 40 | 41 | public static readonly StyledProperty GridLinesVisibilityProperty = 42 | AvaloniaProperty.Register(nameof(GridLinesVisibility)); 43 | 44 | public static readonly StyledProperty IsReadOnlyProperty = 45 | AvaloniaProperty.Register(nameof(IsReadOnly)); 46 | 47 | public static readonly StyledProperty HorizontalGridLinesBrushProperty = 48 | AvaloniaProperty.Register(nameof(HorizontalGridLinesBrush)); 49 | 50 | public static readonly StyledProperty VerticalGridLinesBrushProperty = 51 | AvaloniaProperty.Register(nameof(VerticalGridLinesBrush)); 52 | 53 | private IEnumerable? _itemsSource = new AvaloniaList(); 54 | private object? _selectedItem; 55 | private AvaloniaList _columns; 56 | private ScrollViewer? _headersPresenterScrollViewer; 57 | private DataBoxColumnHeadersPresenter? _headersPresenter; 58 | private DataBoxRowsPresenter? _rowsPresenter; 59 | 60 | public AvaloniaList Columns 61 | { 62 | get => _columns; 63 | private set => SetAndRaise(ColumnsProperty, ref _columns, value); 64 | } 65 | 66 | [Content] 67 | public IEnumerable? ItemsSource 68 | { 69 | get { return _itemsSource; } 70 | set { SetAndRaise(ItemsSourceProperty, ref _itemsSource, value); } 71 | } 72 | 73 | public object? SelectedItem 74 | { 75 | get => _selectedItem; 76 | set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value); 77 | } 78 | 79 | public bool CanUserSortColumns 80 | { 81 | get => GetValue(CanUserSortColumnsProperty); 82 | set => SetValue(CanUserSortColumnsProperty, value); 83 | } 84 | 85 | public bool CanUserResizeColumns 86 | { 87 | get => GetValue(CanUserResizeColumnsProperty); 88 | set => SetValue(CanUserResizeColumnsProperty, value); 89 | } 90 | 91 | public DataBoxGridLinesVisibility GridLinesVisibility 92 | { 93 | get => GetValue(GridLinesVisibilityProperty); 94 | set => SetValue(GridLinesVisibilityProperty, value); 95 | } 96 | 97 | public bool IsReadOnly 98 | { 99 | get => GetValue(IsReadOnlyProperty); 100 | set => SetValue(IsReadOnlyProperty, value); 101 | } 102 | 103 | public IBrush HorizontalGridLinesBrush 104 | { 105 | get => GetValue(HorizontalGridLinesBrushProperty); 106 | set => SetValue(HorizontalGridLinesBrushProperty, value); 107 | } 108 | 109 | public IBrush VerticalGridLinesBrush 110 | { 111 | get => GetValue(VerticalGridLinesBrushProperty); 112 | set => SetValue(VerticalGridLinesBrushProperty, value); 113 | } 114 | 115 | internal double AccumulatedWidth { get; set; } 116 | 117 | internal double AvailableWidth { get; set; } 118 | 119 | internal double AvailableHeight { get; set; } 120 | 121 | internal ScrollViewer? HeadersPresenterScrollViewer => _headersPresenterScrollViewer; 122 | 123 | internal DataBoxColumnHeadersPresenter? HeadersPresenter => _headersPresenter; 124 | 125 | internal DataBoxRowsPresenter? RowsPresenter => _rowsPresenter; 126 | 127 | public DataBox() 128 | { 129 | _columns = new AvaloniaList(); 130 | } 131 | 132 | protected override AutomationPeer OnCreateAutomationPeer() 133 | { 134 | return new DataBoxAutomationPeer(this); 135 | } 136 | 137 | protected override void OnApplyTemplate(TemplateAppliedEventArgs e) 138 | { 139 | base.OnApplyTemplate(e); 140 | 141 | _headersPresenterScrollViewer = e.NameScope.Find("PART_HeadersPresenterScrollViewer"); 142 | _headersPresenter = e.NameScope.Find("PART_HeadersPresenter"); 143 | _rowsPresenter = e.NameScope.Find("PART_RowsPresenter"); 144 | 145 | Attach(); 146 | } 147 | 148 | internal void Attach() 149 | { 150 | if (_headersPresenter is { }) 151 | { 152 | _headersPresenter.DataBox = this; 153 | _headersPresenter.Detach(); 154 | _headersPresenter.Attach(); 155 | } 156 | 157 | if (_rowsPresenter is { }) 158 | { 159 | _rowsPresenter.DataBox = this; 160 | 161 | _rowsPresenter[!!ItemsControl.ItemsSourceProperty] = this[!!ItemsSourceProperty]; 162 | this[!!SelectedItemProperty] = _rowsPresenter[!!SelectingItemsControl.SelectedItemProperty]; 163 | 164 | _rowsPresenter.TemplateApplied += (_, _) => 165 | { 166 | if (_rowsPresenter.Scroll is ScrollViewer scrollViewer) 167 | { 168 | scrollViewer.ScrollChanged += (_, _) => 169 | { 170 | var (x, _) = scrollViewer.Offset; 171 | if (_headersPresenterScrollViewer is { }) 172 | { 173 | _headersPresenterScrollViewer.Offset = new Vector(x, 0); 174 | } 175 | }; 176 | } 177 | }; 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/DataBox/DataBox.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library 4 | netstandard2.0;net461;net6.0;net8.0 5 | True 6 | enable 7 | Debug;Release 8 | AnyCPU;x64 9 | 10 | 11 | 12 | DataBox 13 | A DataGrid control based on ListBox control. 14 | datagrid;panel;control;xaml;axaml;avalonia;avaloniaui 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/DataBox/DataBoxCell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Automation.Peers; 4 | using Avalonia.Controls; 5 | using Avalonia.Controls.Primitives; 6 | using Avalonia.Controls.Shapes; 7 | using DataBox.Automation.Peers; 8 | 9 | namespace DataBox; 10 | 11 | public class DataBoxCell : ContentControl 12 | { 13 | private Rectangle? _rightGridLine; 14 | 15 | internal DataBox? DataBox { get; set; } 16 | 17 | internal double MeasuredWidth { get; set; } 18 | 19 | protected override Type StyleKeyOverride => typeof(DataBoxCell); 20 | 21 | protected override AutomationPeer OnCreateAutomationPeer() 22 | { 23 | return new DataBoxCellAutomationPeer(this); 24 | } 25 | 26 | protected override void OnApplyTemplate(TemplateAppliedEventArgs e) 27 | { 28 | base.OnApplyTemplate(e); 29 | 30 | _rightGridLine = e.NameScope.Find("PART_RightGridLine"); 31 | 32 | InvalidateRightGridLine(); 33 | } 34 | 35 | protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) 36 | { 37 | base.OnAttachedToVisualTree(e); 38 | 39 | InvalidateRightGridLine(); 40 | } 41 | 42 | private void InvalidateRightGridLine() 43 | { 44 | if (_rightGridLine is null || DataBox is null) 45 | { 46 | return; 47 | } 48 | 49 | bool newVisibility = 50 | DataBox.GridLinesVisibility == DataBoxGridLinesVisibility.Vertical 51 | || DataBox.GridLinesVisibility == DataBoxGridLinesVisibility.All; 52 | 53 | if (newVisibility != _rightGridLine.IsVisible) 54 | { 55 | _rightGridLine.IsVisible = newVisibility; 56 | } 57 | 58 | _rightGridLine.Fill = DataBox.VerticalGridLinesBrush; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/DataBox/DataBoxColumn.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Input; 3 | using Avalonia; 4 | using Avalonia.Controls; 5 | using Avalonia.Controls.Templates; 6 | using Avalonia.Data; 7 | using Avalonia.Metadata; 8 | 9 | namespace DataBox; 10 | 11 | public abstract class DataBoxColumn : AvaloniaObject 12 | { 13 | public static readonly StyledProperty CellTemplateProperty = 14 | AvaloniaProperty.Register(nameof(CellTemplate)); 15 | 16 | public static readonly StyledProperty HeaderProperty = 17 | AvaloniaProperty.Register(nameof(Header)); 18 | 19 | public static readonly StyledProperty WidthProperty = 20 | AvaloniaProperty.Register(nameof(Width), new GridLength(0, GridUnitType.Auto)); 21 | 22 | public static readonly StyledProperty MinWidthProperty = 23 | AvaloniaProperty.Register(nameof(MinWidth), 0.0); 24 | 25 | public static readonly StyledProperty MaxWidthProperty = 26 | AvaloniaProperty.Register(nameof(MaxWidth), double.PositiveInfinity); 27 | 28 | public static readonly StyledProperty CanUserSortProperty = 29 | AvaloniaProperty.Register(nameof(CanUserSort), true); 30 | 31 | public static readonly StyledProperty CanUserResizeProperty = 32 | AvaloniaProperty.Register(nameof(CanUserResize)); 33 | 34 | public static readonly StyledProperty CanUserReorderProperty = 35 | AvaloniaProperty.Register(nameof(CanUserReorder)); 36 | 37 | public static readonly StyledProperty SortingStateProperty = 38 | AvaloniaProperty.Register(nameof(SortingState), null, false, BindingMode.TwoWay); 39 | 40 | public static readonly StyledProperty SortCommandProperty = 41 | AvaloniaProperty.Register(nameof(SortCommand)); 42 | 43 | public static readonly StyledProperty SortMemberPathProperty = 44 | AvaloniaProperty.Register(nameof(SortMemberPath)); 45 | 46 | internal static readonly StyledProperty MeasureWidthProperty = 47 | AvaloniaProperty.Register(nameof(MeasureWidth), double.NaN); 48 | 49 | [Content] 50 | public IDataTemplate? CellTemplate 51 | { 52 | get => GetValue(CellTemplateProperty); 53 | set => SetValue(CellTemplateProperty, value); 54 | } 55 | 56 | public object? Header 57 | { 58 | get => GetValue(HeaderProperty); 59 | set => SetValue(HeaderProperty, value); 60 | } 61 | 62 | public GridLength Width 63 | { 64 | get => GetValue(WidthProperty); 65 | set => SetValue(WidthProperty, value); 66 | } 67 | 68 | public double MinWidth 69 | { 70 | get => GetValue(MinWidthProperty); 71 | set => SetValue(MinWidthProperty, value); 72 | } 73 | 74 | public double MaxWidth 75 | { 76 | get => GetValue(MaxWidthProperty); 77 | set => SetValue(MaxWidthProperty, value); 78 | } 79 | 80 | public bool CanUserSort 81 | { 82 | get => GetValue(CanUserSortProperty); 83 | set => SetValue(CanUserSortProperty, value); 84 | } 85 | 86 | public bool CanUserResize 87 | { 88 | get => GetValue(CanUserSortProperty); 89 | set => SetValue(CanUserSortProperty, value); 90 | } 91 | 92 | public bool CanUserReorder 93 | { 94 | get => GetValue(CanUserSortProperty); 95 | set => SetValue(CanUserSortProperty, value); 96 | } 97 | 98 | public ListSortDirection? SortingState 99 | { 100 | get => GetValue(SortingStateProperty); 101 | set => SetValue(SortingStateProperty, value); 102 | } 103 | 104 | public ICommand? SortCommand 105 | { 106 | get => GetValue(SortCommandProperty); 107 | set => SetValue(SortCommandProperty, value); 108 | } 109 | 110 | public string? SortMemberPath 111 | { 112 | get => GetValue(SortMemberPathProperty); 113 | set => SetValue(SortMemberPathProperty, value); 114 | } 115 | 116 | internal double MeasureWidth 117 | { 118 | get => GetValue(MeasureWidthProperty); 119 | set => SetValue(MeasureWidthProperty, value); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/DataBox/DataBoxColumnHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using Avalonia; 6 | using Avalonia.Automation.Peers; 7 | using Avalonia.Controls; 8 | using Avalonia.Controls.Metadata; 9 | using Avalonia.Controls.Primitives; 10 | using Avalonia.Input; 11 | using Avalonia.Media; 12 | using Avalonia.VisualTree; 13 | using DataBox.Automation.Peers; 14 | 15 | namespace DataBox; 16 | 17 | [PseudoClasses(":pressed", ":sortascending", ":sortdescending")] 18 | public class DataBoxColumnHeader : ContentControl 19 | { 20 | public static readonly StyledProperty SeparatorBrushProperty = 21 | AvaloniaProperty.Register(nameof(SeparatorBrush)); 22 | 23 | public static readonly StyledProperty AreSeparatorsVisibleProperty = 24 | AvaloniaProperty.Register( 25 | nameof(AreSeparatorsVisible), 26 | defaultValue: true); 27 | 28 | internal static readonly StyledProperty IsPressedProperty = 29 | AvaloniaProperty.Register(nameof(IsPressed)); 30 | 31 | public DataBoxColumnHeader() 32 | { 33 | UpdatePseudoClassesIsPressed(IsPressed); 34 | } 35 | 36 | internal DataBox? DataBox { get; set; } 37 | 38 | public IBrush? SeparatorBrush 39 | { 40 | get => GetValue(SeparatorBrushProperty); 41 | set => SetValue(SeparatorBrushProperty, value); 42 | } 43 | 44 | public bool AreSeparatorsVisible 45 | { 46 | get => GetValue(AreSeparatorsVisibleProperty); 47 | set => SetValue(AreSeparatorsVisibleProperty, value); 48 | } 49 | 50 | protected override Type StyleKeyOverride => typeof(DataBoxColumnHeader); 51 | 52 | internal bool IsPressed 53 | { 54 | get => GetValue(IsPressedProperty); 55 | private set => SetValue(IsPressedProperty, value); 56 | } 57 | 58 | internal DataBoxColumn? Column { get; set; } 59 | 60 | internal IReadOnlyList? ColumnHeaders { get; set; } 61 | 62 | protected override AutomationPeer OnCreateAutomationPeer() 63 | { 64 | return new DataBoxColumnHeaderAutomationPeer(this); 65 | } 66 | 67 | protected override void OnApplyTemplate(TemplateAppliedEventArgs e) 68 | { 69 | base.OnApplyTemplate(e); 70 | 71 | UpdatePseudoClassesSortingState(Column?.SortingState); 72 | } 73 | 74 | protected override void OnPointerPressed(PointerPressedEventArgs e) 75 | { 76 | base.OnPointerPressed(e); 77 | 78 | if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) 79 | { 80 | IsPressed = true; 81 | e.Handled = true; 82 | } 83 | } 84 | 85 | protected override void OnPointerReleased(PointerReleasedEventArgs e) 86 | { 87 | base.OnPointerReleased(e); 88 | 89 | if (IsPressed && e.InitialPressMouseButton == MouseButton.Left) 90 | { 91 | IsPressed = false; 92 | e.Handled = true; 93 | 94 | if (this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c))) 95 | { 96 | OnClick(e.KeyModifiers); 97 | } 98 | } 99 | } 100 | 101 | protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) 102 | { 103 | IsPressed = false; 104 | } 105 | 106 | protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) 107 | { 108 | base.OnPropertyChanged(change); 109 | 110 | if (change.Property == IsPressedProperty) 111 | { 112 | UpdatePseudoClassesIsPressed(change.GetNewValue()); 113 | } 114 | } 115 | 116 | private void OnClick(KeyModifiers keyModifiers) 117 | { 118 | if (DataBox is null) 119 | { 120 | return; 121 | } 122 | 123 | if (Column is null || Column.SortCommand is null || ColumnHeaders is null) 124 | { 125 | return; 126 | } 127 | 128 | if (!Column.CanUserSort || !DataBox.CanUserSortColumns) 129 | { 130 | return; 131 | } 132 | 133 | var ctrl = (keyModifiers & KeyModifiers.Control) == KeyModifiers.Control; 134 | var shift = (keyModifiers & KeyModifiers.Shift) == KeyModifiers.Shift; 135 | 136 | if (!shift) 137 | { 138 | foreach (var columnHeader in ColumnHeaders) 139 | { 140 | if (!Equals(columnHeader, this)) 141 | { 142 | if (columnHeader.Column is { } column) 143 | { 144 | column.SortingState = null; 145 | columnHeader.UpdatePseudoClassesSortingState(column.SortingState); 146 | } 147 | } 148 | } 149 | } 150 | 151 | string? sortMemberPath = ctrl ? null : Column.SortMemberPath; 152 | ListSortDirection? sortingState = ctrl ? null : (Column.SortingState == ListSortDirection.Ascending 153 | ? ListSortDirection.Descending 154 | : ListSortDirection.Ascending); 155 | 156 | Column.SortingState = sortingState; 157 | 158 | UpdatePseudoClassesSortingState(sortingState); 159 | 160 | if (Column.SortCommand is { } command) 161 | { 162 | if (command.CanExecute(sortMemberPath)) 163 | { 164 | command.Execute(sortMemberPath); 165 | } 166 | } 167 | } 168 | 169 | private void UpdatePseudoClassesIsPressed(bool isPressed) 170 | { 171 | PseudoClasses.Set(":pressed", isPressed); 172 | } 173 | 174 | private void UpdatePseudoClassesSortingState(ListSortDirection? sortingState) 175 | { 176 | PseudoClasses.Set(":sortascending", sortingState == ListSortDirection.Ascending); 177 | PseudoClasses.Set(":sortdescending", sortingState == ListSortDirection.Descending); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/DataBox/DataBoxGridLinesVisibility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DataBox; 4 | 5 | [Flags] 6 | public enum DataBoxGridLinesVisibility 7 | { 8 | None = 0, 9 | Horizontal = 1, 10 | Vertical = 2, 11 | All = Vertical | Horizontal 12 | } -------------------------------------------------------------------------------- /src/DataBox/DataBoxRow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia.Automation.Peers; 3 | using Avalonia.Controls; 4 | using Avalonia.Controls.Primitives; 5 | using Avalonia.Controls.Shapes; 6 | using DataBox.Automation.Peers; 7 | using DataBox.Primitives; 8 | 9 | namespace DataBox; 10 | 11 | public class DataBoxRow : ListBoxItem 12 | { 13 | private Rectangle? _bottomGridLine; 14 | 15 | internal DataBox? DataBox { get; set; } 16 | 17 | internal DataBoxCellsPresenter? CellsPresenter { get; set; } 18 | 19 | protected override Type StyleKeyOverride => typeof(DataBoxRow); 20 | 21 | protected override AutomationPeer OnCreateAutomationPeer() 22 | { 23 | return new DataBoxRowAutomationPeer(this); 24 | } 25 | 26 | protected override void OnApplyTemplate(TemplateAppliedEventArgs e) 27 | { 28 | base.OnApplyTemplate(e); 29 | 30 | _bottomGridLine = e.NameScope.Find("PART_BottomGridLine"); 31 | 32 | InvalidateBottomGridLine(); 33 | 34 | CellsPresenter = e.NameScope.Find("PART_CellsPresenter"); 35 | 36 | if (CellsPresenter is { }) 37 | { 38 | CellsPresenter.DataBox = DataBox; 39 | CellsPresenter.Attach(); 40 | } 41 | } 42 | 43 | private void InvalidateBottomGridLine() 44 | { 45 | if (_bottomGridLine is { } && DataBox is { }) 46 | { 47 | bool newVisibility = 48 | DataBox.GridLinesVisibility == DataBoxGridLinesVisibility.Horizontal 49 | || DataBox.GridLinesVisibility == DataBoxGridLinesVisibility.All; 50 | 51 | if (newVisibility != _bottomGridLine.IsVisible) 52 | { 53 | _bottomGridLine.IsVisible = newVisibility; 54 | } 55 | 56 | _bottomGridLine.Fill = DataBox.HorizontalGridLinesBrush; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/DataBox/Primitives/ActionObserver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DataBox.Primitives; 4 | 5 | internal class ActionObserver : IObserver 6 | { 7 | private readonly Action _action; 8 | 9 | public ActionObserver(Action action) 10 | { 11 | _action = action; 12 | } 13 | 14 | public void OnCompleted() 15 | { 16 | } 17 | 18 | public void OnError(Exception error) 19 | { 20 | } 21 | 22 | public void OnNext(T value) 23 | { 24 | _action(value); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/DataBox/Primitives/DataBoxCellsPresenter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia; 3 | using Avalonia.Controls; 4 | using Avalonia.Layout; 5 | using DataBox.Primitives.Layout; 6 | 7 | namespace DataBox.Primitives; 8 | 9 | public class DataBoxCellsPresenter : Panel 10 | { 11 | internal DataBox? DataBox { get; set; } 12 | 13 | internal void Attach() 14 | { 15 | if (DataBox is null) 16 | { 17 | return; 18 | } 19 | 20 | foreach (var column in DataBox.Columns) 21 | { 22 | var cell = new DataBoxCell 23 | { 24 | [!ContentControl.ContentProperty] = this[!DataContextProperty], 25 | [!ContentControl.ContentTemplateProperty] = column[!DataBoxColumn.CellTemplateProperty], 26 | HorizontalAlignment = HorizontalAlignment.Stretch, 27 | VerticalAlignment = VerticalAlignment.Stretch, 28 | DataBox = DataBox 29 | }; 30 | 31 | Children.Add(cell); 32 | } 33 | } 34 | 35 | protected override Type StyleKeyOverride => typeof(DataBoxCellsPresenter); 36 | 37 | protected override Size MeasureOverride(Size availableSize) 38 | { 39 | return DataBoxCellsLayout.Measure(availableSize, DataBox, Children); 40 | } 41 | 42 | protected override Size ArrangeOverride(Size arrangeSize) 43 | { 44 | return DataBoxCellsLayout.Arrange(arrangeSize, DataBox, Children); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/DataBox/Primitives/DataBoxColumnHeadersPresenter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Avalonia; 4 | using Avalonia.Automation.Peers; 5 | using Avalonia.Controls; 6 | using Avalonia.Layout; 7 | using DataBox.Automation.Peers; 8 | using DataBox.Primitives.Layout; 9 | 10 | namespace DataBox.Primitives; 11 | 12 | public class DataBoxColumnHeadersPresenter : Panel 13 | { 14 | private List? _columnActualWidthDisposables; 15 | private List? _columnHeaders; 16 | 17 | internal DataBox? DataBox { get; set; } 18 | 19 | protected override Type StyleKeyOverride => typeof(DataBoxColumnHeadersPresenter); 20 | 21 | protected override AutomationPeer OnCreateAutomationPeer() 22 | { 23 | return new DataBoxColumnHeadersPresenterAutomationPeer(this); 24 | } 25 | 26 | protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) 27 | { 28 | base.OnDetachedFromVisualTree(e); 29 | 30 | Detach(); 31 | } 32 | 33 | internal void Attach() 34 | { 35 | if (DataBox is null) 36 | { 37 | return; 38 | } 39 | 40 | _columnHeaders = new List(); 41 | _columnActualWidthDisposables = new List(); 42 | 43 | for (var c = 0; c < DataBox.Columns.Count; c++) 44 | { 45 | var column = DataBox.Columns[c]; 46 | 47 | var columnHeader = new DataBoxColumnHeader 48 | { 49 | [!ContentControl.ContentProperty] = column[!DataBoxColumn.HeaderProperty], 50 | HorizontalAlignment = HorizontalAlignment.Stretch, 51 | VerticalAlignment = VerticalAlignment.Stretch, 52 | Column = column, 53 | ColumnHeaders = _columnHeaders, 54 | DataBox = DataBox 55 | }; 56 | 57 | Children.Add(columnHeader); 58 | _columnHeaders.Add(columnHeader); 59 | 60 | var disposable = column.GetObservable(DataBoxColumn.MeasureWidthProperty).Subscribe( 61 | new ActionObserver( 62 | _ => 63 | { 64 | InvalidateMeasure(); 65 | InvalidateVisual(); 66 | })); 67 | _columnActualWidthDisposables.Add(disposable); 68 | } 69 | } 70 | 71 | internal void Detach() 72 | { 73 | if (_columnActualWidthDisposables is { }) 74 | { 75 | foreach (var disposable in _columnActualWidthDisposables) 76 | { 77 | disposable.Dispose(); 78 | } 79 | 80 | _columnActualWidthDisposables.Clear(); 81 | _columnActualWidthDisposables = null; 82 | } 83 | 84 | _columnHeaders?.Clear(); 85 | _columnHeaders = null; 86 | } 87 | 88 | protected override Size MeasureOverride(Size availableSize) 89 | { 90 | return DataBoxCellsLayout.Measure(availableSize, DataBox, Children); 91 | } 92 | 93 | protected override Size ArrangeOverride(Size arrangeSize) 94 | { 95 | return DataBoxCellsLayout.Arrange(arrangeSize, DataBox, Children); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/DataBox/Primitives/DataBoxRowsPresenter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Avalonia.Automation.Peers; 3 | using Avalonia.Controls; 4 | 5 | namespace DataBox.Primitives; 6 | 7 | public class DataBoxRowsPresenter : ListBox 8 | { 9 | internal DataBox? DataBox { get; set; } 10 | 11 | protected override Type StyleKeyOverride => typeof(DataBoxRowsPresenter); 12 | 13 | protected override AutomationPeer OnCreateAutomationPeer() 14 | { 15 | return new NoneAutomationPeer(this); 16 | } 17 | 18 | protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) 19 | { 20 | return new DataBoxRow 21 | { 22 | DataBox = DataBox 23 | }; 24 | } 25 | 26 | protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) 27 | { 28 | return NeedsContainer(item, out recycleKey); 29 | } 30 | 31 | protected override void PrepareContainerForItemOverride(Control element, object? item, int index) 32 | { 33 | base.PrepareContainerForItemOverride(element, item, index); 34 | 35 | if (element is DataBoxRow row) 36 | { 37 | row.DataBox = DataBox; 38 | } 39 | } 40 | 41 | protected override void ClearContainerForItemOverride(Control element) 42 | { 43 | base.ClearContainerForItemOverride(element); 44 | 45 | if (element is DataBoxRow row) 46 | { 47 | row.DataBox = null; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/DataBox/Primitives/Layout/DataBoxCellsLayout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Avalonia; 4 | using Avalonia.Controls; 5 | 6 | namespace DataBox.Primitives.Layout; 7 | 8 | internal static class DataBoxCellsLayout 9 | { 10 | public static Size Measure(Size availableSize, DataBox? dataBox, IList children) 11 | where T : Control 12 | { 13 | if (dataBox is null || children.Count == 0) 14 | { 15 | return availableSize; 16 | } 17 | 18 | var parentWidth = 0.0; 19 | var parentHeight = 0.0; 20 | 21 | for (var i = 0; i < children.Count; ++i) 22 | { 23 | if (i >= dataBox.Columns.Count) 24 | { 25 | break; 26 | } 27 | 28 | var child = children[i]; 29 | var column = dataBox.Columns[i]; 30 | var width = Math.Max(0.0, double.IsNaN(column.MeasureWidth) ? 0.0 : column.MeasureWidth); 31 | 32 | width = Math.Max(column.MinWidth, width); 33 | width = Math.Min(column.MaxWidth, width); 34 | 35 | child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 36 | 37 | parentWidth += width; 38 | parentHeight = Math.Max(parentHeight, child.DesiredSize.Height); 39 | } 40 | 41 | return new Size(parentWidth, parentHeight); 42 | } 43 | 44 | public static Size Arrange(Size arrangeSize, DataBox? dataBox, IList children) 45 | where T : Control 46 | { 47 | if (dataBox is null || children.Count == 0) 48 | { 49 | return arrangeSize; 50 | } 51 | 52 | var accumulatedWidth = 0.0; 53 | var accumulatedHeight = 0.0; 54 | var maxHeight = 0.0; 55 | 56 | for (var i = 0; i < children.Count; ++i) 57 | { 58 | var child = children[i]; 59 | 60 | maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); 61 | } 62 | 63 | for (var i = 0; i < children.Count; ++i) 64 | { 65 | if (i >= dataBox.Columns.Count) 66 | { 67 | break; 68 | } 69 | 70 | var child = children[i]; 71 | var column = dataBox.Columns[i]; 72 | var width = Math.Max(0.0, double.IsNaN(column.MeasureWidth) ? 0.0 : column.MeasureWidth); 73 | var height = Math.Max(maxHeight, arrangeSize.Height); 74 | var rect = new Rect(accumulatedWidth, 0.0, width, height); 75 | 76 | child.Arrange(rect); 77 | 78 | accumulatedWidth += width; 79 | accumulatedHeight = Math.Max(accumulatedHeight, height); 80 | } 81 | 82 | return new Size(accumulatedWidth, accumulatedHeight); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/DataBox/Primitives/Layout/DataBoxRowsLayout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Avalonia; 4 | using Avalonia.Controls; 5 | 6 | namespace DataBox.Primitives.Layout; 7 | 8 | internal static class DataBoxRowsLayout 9 | { 10 | private static DataBoxCellsPresenter? GetCellsPresenter(Control? control) 11 | { 12 | if (control is DataBoxRow row) 13 | { 14 | return row.CellsPresenter; 15 | } 16 | return control as DataBoxCellsPresenter; 17 | } 18 | 19 | private static double GetAutoColumnActualWidth(IEnumerable rows, int c, double actualWidth) 20 | { 21 | foreach (var row in rows) 22 | { 23 | var cellPresenter = GetCellsPresenter(row); 24 | if (cellPresenter is null) 25 | { 26 | continue; 27 | } 28 | 29 | var cells = cellPresenter.Children; 30 | if (cells.Count > c && cells[c] is DataBoxCell cell) 31 | { 32 | var width = cell.MeasuredWidth; 33 | actualWidth = Math.Max(actualWidth, width); 34 | } 35 | } 36 | 37 | return actualWidth; 38 | } 39 | 40 | private static double GetStarColumnActualWidth(IEnumerable rows, int c, double actualWidth) 41 | { 42 | foreach (var row in rows) 43 | { 44 | var cellPresenter = GetCellsPresenter(row); 45 | if (cellPresenter is null) 46 | { 47 | continue; 48 | } 49 | 50 | var cells = cellPresenter.Children; 51 | if (cells.Count > c && cells[c] is DataBoxCell cell) 52 | { 53 | var width = cell.MeasuredWidth; 54 | actualWidth = Math.Max(actualWidth, width); 55 | } 56 | } 57 | 58 | return actualWidth; 59 | } 60 | 61 | private static double SetColumnsActualWidth(IEnumerable rows, DataBox dataBox, bool measureStarAsAuto) 62 | { 63 | var accumulatedWidth = 0.0; 64 | var actualWidths = new double[dataBox.Columns.Count]; 65 | 66 | for (var c = 0; c < dataBox.Columns.Count; c++) 67 | { 68 | var column = dataBox.Columns[c]; 69 | actualWidths[c] = double.IsNaN(column.MeasureWidth) ? 0.0 : column.MeasureWidth; 70 | } 71 | 72 | for (var c = 0; c < dataBox.Columns.Count; c++) 73 | { 74 | var column = dataBox.Columns[c]; 75 | var type = column.Width.GridUnitType; 76 | var value = column.Width.Value; 77 | 78 | if (measureStarAsAuto && type is GridUnitType.Star) 79 | { 80 | type = GridUnitType.Auto; 81 | } 82 | 83 | switch (type) 84 | { 85 | case GridUnitType.Pixel: 86 | { 87 | var actualWidth = value; 88 | 89 | actualWidth = Math.Max(column.MinWidth, actualWidth); 90 | actualWidth = Math.Min(column.MaxWidth, actualWidth); 91 | 92 | actualWidths[c] = actualWidth; 93 | accumulatedWidth += actualWidths[c]; 94 | 95 | break; 96 | } 97 | case GridUnitType.Auto: 98 | { 99 | var actualWidth = actualWidths[c]; 100 | 101 | actualWidth = GetAutoColumnActualWidth(rows, c, actualWidth); 102 | actualWidth = Math.Max(column.MinWidth, actualWidth); 103 | actualWidth = Math.Min(column.MaxWidth, actualWidth); 104 | 105 | actualWidths[c] = actualWidth; 106 | accumulatedWidth += actualWidths[c]; 107 | 108 | break; 109 | } 110 | case GridUnitType.Star: 111 | { 112 | var actualWidth = 0.0; 113 | 114 | actualWidth = GetStarColumnActualWidth(rows, c, actualWidth); 115 | actualWidth = Math.Max(column.MinWidth, actualWidth); 116 | actualWidth = Math.Min(column.MaxWidth, actualWidth); 117 | 118 | actualWidths[c] = actualWidth; 119 | 120 | break; 121 | } 122 | } 123 | } 124 | 125 | var totalWidthForStars = Math.Max(0.0, dataBox.AvailableWidth - accumulatedWidth); 126 | var totalStarValue = 0.0; 127 | 128 | for (var c = 0; c < dataBox.Columns.Count; c++) 129 | { 130 | var column = dataBox.Columns[c]; 131 | var type = column.Width.GridUnitType; 132 | 133 | if (measureStarAsAuto && type is GridUnitType.Star) 134 | { 135 | type = GridUnitType.Auto; 136 | } 137 | 138 | if (type == GridUnitType.Star) 139 | { 140 | totalStarValue += column.Width.Value; 141 | } 142 | } 143 | 144 | for (var c = 0; c < dataBox.Columns.Count; c++) 145 | { 146 | var column = dataBox.Columns[c]; 147 | var type = column.Width.GridUnitType; 148 | var value = column.Width.Value; 149 | 150 | if (measureStarAsAuto && type is GridUnitType.Star) 151 | { 152 | type = GridUnitType.Auto; 153 | } 154 | 155 | if (type == GridUnitType.Star) 156 | { 157 | var actualWidth = (value / totalStarValue) * totalWidthForStars; 158 | 159 | actualWidth = Math.Max(column.MinWidth, actualWidth); 160 | actualWidth = Math.Min(column.MaxWidth, actualWidth); 161 | 162 | totalWidthForStars -= actualWidth; 163 | totalStarValue -= value; 164 | 165 | if (actualWidths[c] > actualWidth) 166 | { 167 | actualWidth = actualWidths[c]; 168 | actualWidth = Math.Max(column.MinWidth, actualWidth); 169 | actualWidth = Math.Min(column.MaxWidth, actualWidth); 170 | } 171 | 172 | actualWidths[c] = actualWidth; 173 | accumulatedWidth += actualWidths[c]; 174 | } 175 | } 176 | 177 | for (var c = 0; c < dataBox.Columns.Count; c++) 178 | { 179 | var column = dataBox.Columns[c]; 180 | column.MeasureWidth = actualWidths[c]; 181 | } 182 | 183 | return accumulatedWidth; 184 | } 185 | 186 | private static double AdjustAccumulatedWidth(double accumulatedWidth, double availableWidth) 187 | { 188 | if (double.IsPositiveInfinity(availableWidth)) 189 | { 190 | return accumulatedWidth; 191 | } 192 | return accumulatedWidth < availableWidth ? availableWidth : accumulatedWidth; 193 | } 194 | 195 | private static void MeasureCells(IEnumerable rows) 196 | { 197 | foreach (var row in rows) 198 | { 199 | var cellPresenter = GetCellsPresenter(row); 200 | if (cellPresenter is null) 201 | { 202 | continue; 203 | } 204 | 205 | var cells = cellPresenter.Children; 206 | 207 | for (int c = 0, cellsCount = cells.Count; c < cellsCount; ++c) 208 | { 209 | if (cells[c] is not DataBoxCell cell) 210 | { 211 | continue; 212 | } 213 | 214 | // TODO: Optimize measure performance.Do not measure twice cells. Should be done only once in DataBoxCellsPresenter.MeasureOverride(). 215 | // cell.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 216 | cell.MeasuredWidth = cell.DesiredSize.Width; 217 | } 218 | } 219 | } 220 | 221 | public static Size Measure(Size availableSize, DataBox dataBox, Func measureOverride, Action invalidateMeasure, IEnumerable rows) 222 | { 223 | var availableSizeWidth = availableSize.Width; 224 | var measureStarAsAuto = double.IsPositiveInfinity(availableSize.Width); 225 | 226 | dataBox.AvailableWidth = availableSize.Width; 227 | dataBox.AvailableHeight = availableSize.Height; 228 | 229 | MeasureCells(rows); 230 | 231 | var accumulatedWidth = SetColumnsActualWidth(rows, dataBox, measureStarAsAuto); 232 | var panelSize = availableSize.WithWidth(accumulatedWidth); 233 | 234 | // TODO: Optimize measure performance. 235 | // TODO: 236 | // invalidateMeasure(); 237 | 238 | panelSize = measureOverride(panelSize); 239 | panelSize = panelSize.WithWidth(AdjustAccumulatedWidth(accumulatedWidth, availableSizeWidth)); 240 | 241 | return panelSize; 242 | } 243 | 244 | public static Size Arrange(Size finalSize, DataBox dataBox, Func arrangeOverride, IEnumerable rows) 245 | { 246 | var finalSizeWidth = finalSize.Width; 247 | 248 | dataBox.AvailableWidth = finalSize.Width; 249 | dataBox.AvailableHeight = finalSize.Height; 250 | dataBox.AccumulatedWidth = SetColumnsActualWidth(rows, dataBox, false); 251 | var panelSize = finalSize.WithWidth(dataBox.AccumulatedWidth); 252 | 253 | panelSize = arrangeOverride(panelSize); 254 | panelSize = panelSize.WithWidth(AdjustAccumulatedWidth(dataBox.AccumulatedWidth, finalSizeWidth)); 255 | 256 | return panelSize; 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/DataBox/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Metadata; 2 | 3 | [assembly: XmlnsDefinition("https://github.com/avaloniaui", "DataBox")] 4 | [assembly: XmlnsDefinition("https://github.com/avaloniaui", "DataBox.Columns")] 5 | [assembly: XmlnsDefinition("https://github.com/avaloniaui", "DataBox.Controls")] 6 | [assembly: XmlnsDefinition("https://github.com/avaloniaui", "DataBox.Primitives")] 7 | [assembly: XmlnsDefinition("https://github.com/avaloniaui", "DataBox.Themes")] 8 | -------------------------------------------------------------------------------- /src/DataBox/Themes/Accents/Fluent.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 12,0,12,0 5 | 6 | 0.6 7 | 0.8 8 | 9 | M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z 10 | M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/DataBox/Themes/Controls/DataBox.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/DataBox/Themes/Controls/DataBoxCell.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/DataBox/Themes/Controls/DataBoxCellsPresenter.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/DataBox/Themes/Controls/DataBoxColumnHeader.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 26 | 34 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 49 | 50 | 53 | 54 | 58 | 59 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/DataBox/Themes/Controls/DataBoxColumnHeadersPresenter.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/DataBox/Themes/Controls/DataBoxPanel.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/DataBox/Themes/Controls/DataBoxRow.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 34 | 35 | 38 | 39 | 43 | 44 | 48 | 49 | 53 | 54 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/DataBox/Themes/Controls/DataBoxRowsPresenter.axaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/DataBox/Themes/DataBoxFluentTheme.axaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/DataBox/Themes/DataBoxFluentTheme.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Styling; 2 | 3 | namespace DataBox.Themes; 4 | 5 | public class DataBoxFluentTheme : Styles 6 | { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/VirtualPanel/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Metadata; 2 | 3 | [assembly: XmlnsDefinition("https://github.com/avaloniaui", "VirtualPanel")] 4 | -------------------------------------------------------------------------------- /src/VirtualPanel/VirtualPanel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Library 4 | netstandard2.0;net461;net6.0;net8.0 5 | True 6 | enable 7 | Debug;Release 8 | AnyCPU;x64 9 | 10 | 11 | 12 | VirtualPanel 13 | A virtualizing smooth scrolling panel control. 14 | virtualizing;smooth;scrolling;panel;control;xaml;axaml;avalonia;avaloniaui 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/DataBox.UnitTests/DataBox.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | Library 6 | False 7 | True 8 | False 9 | enable 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/DataBox.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Xunit; 3 | 4 | [assembly: AssemblyTitle("DataBox.UnitTests")] 5 | 6 | [assembly: CollectionBehavior(DisableTestParallelization = true)] 7 | --------------------------------------------------------------------------------