diff --git a/.gitmodules b/.gitmodules index 2d11fdfa9e..79694800b2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"] path = src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github url = https://github.com/kekekeks/XamlX.git +[submodule "src/Avalonia.Controls.DataGrid"] + path = src/Avalonia.Controls.DataGrid + url = https://github.com/AvaloniaUI/Avalonia.Controls.DataGrid.git diff --git a/Avalonia.sln b/Avalonia.sln index 1b977839ea..9d60fe2e45 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -170,14 +170,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "sam EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless", "src\Headless\Avalonia.Headless\Avalonia.Headless.csproj", "{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC}" @@ -301,6 +297,7 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.RenderTests.WpfCompare", "tests\Avalonia.RenderTests.WpfCompare\Avalonia.RenderTests.WpfCompare.csproj", "{9AE1B827-21AC-4063-AB22-C8804B7F931E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Automation", "src\Windows\Avalonia.Win32.Automation\Avalonia.Win32.Automation.csproj", "{0097673D-DBCE-476E-82FE-E78A56E58AA2}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XEmbedSample", "samples\XEmbedSample\XEmbedSample.csproj", "{255614F5-CB64-4ECA-A026-E0B1AF6A2EF4}" EndProject Global @@ -497,10 +494,6 @@ Global {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.Build.0 = Release|Any CPU - {3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|Any CPU.Build.0 = Release|Any CPU {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.Build.0 = Debug|Any CPU {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -509,10 +502,6 @@ Global {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|Any CPU.Build.0 = Debug|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|Any CPU.Build.0 = Release|Any CPU - {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.Build.0 = Release|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -754,7 +743,6 @@ Global {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} - {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7} {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} diff --git a/samples/AppWithoutLifetime/AppWithoutLifetime.csproj b/samples/AppWithoutLifetime/AppWithoutLifetime.csproj index 2e959798d0..303daa451f 100644 --- a/samples/AppWithoutLifetime/AppWithoutLifetime.csproj +++ b/samples/AppWithoutLifetime/AppWithoutLifetime.csproj @@ -9,7 +9,6 @@ - diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 157009a5e3..022118d3ab 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -28,11 +28,8 @@ - - + - - diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 2b55c5bad8..a29a23ff61 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -18,7 +18,6 @@ namespace ControlCatalog private FluentTheme? _fluentTheme; private SimpleTheme? _simpleTheme; private IStyle? _colorPickerFluent, _colorPickerSimple; - private IStyle? _dataGridFluent, _dataGridSimple; public App() { @@ -35,8 +34,6 @@ namespace ControlCatalog _simpleTheme = (SimpleTheme)Resources["SimpleTheme"]!; _colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!; _colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!; - _dataGridFluent = (IStyle)Resources["DataGridFluent"]!; - _dataGridSimple = (IStyle)Resources["DataGridSimple"]!; SetCatalogThemes(CatalogTheme.Fluent); } @@ -83,13 +80,11 @@ namespace ControlCatalog { app._themeStylesContainer[0] = app._fluentTheme!; app._themeStylesContainer[1] = app._colorPickerFluent!; - app._themeStylesContainer[2] = app._dataGridFluent!; } else if (theme == CatalogTheme.Simple) { app._themeStylesContainer[0] = app._simpleTheme!; app._themeStylesContainer[1] = app._colorPickerSimple!; - app._themeStylesContainer[2] = app._dataGridSimple!; } if (shouldReopenWindow) diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index edcbcbe988..8ef96169df 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -26,7 +26,6 @@ - diff --git a/samples/ControlCatalog/Models/Countries.cs b/samples/ControlCatalog/Models/Countries.cs deleted file mode 100644 index 20b6a85137..0000000000 --- a/samples/ControlCatalog/Models/Countries.cs +++ /dev/null @@ -1,256 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; - -namespace ControlCatalog.Models -{ - public static class Countries - { - static IEnumerable GetCountries() - { - yield return new Country("Afghanistan", "ASIA (EX. NEAR EAST)", 31056997, 647500, 48, 0, 23.06, 163.07, 700, 36, 3.2, 46.6, 20.34); - yield return new Country("Albania", "EASTERN EUROPE", 3581655, 28748, 124.6, 1.26, -4.93, 21.52, 4500, 86.5, 71.2, 15.11, 5.22); - yield return new Country("Algeria", "NORTHERN AFRICA", 32930091, 2381740, 13.8, 0.04, -0.39, 31, 6000, 70, 78.1, 17.14, 4.61); - yield return new Country("American Samoa", "OCEANIA", 57794, 199, 290.4, 58.29, -20.71, 9.27, 8000, 97, 259.5, 22.46, 3.27); - yield return new Country("Andorra", "WESTERN EUROPE", 71201, 468, 152.1, 0, 6.6, 4.05, 19000, 100, 497.2, 8.71, 6.25); - yield return new Country("Angola", "SUB-SAHARAN AFRICA", 12127071, 1246700, 9.7, 0.13, 0, 191.19, 1900, 42, 7.8, 45.11, 24.2); - yield return new Country("Anguilla", "LATIN AMER. & CARIB", 13477, 102, 132.1, 59.8, 10.76, 21.03, 8600, 95, 460, 14.17, 5.34); - yield return new Country("Antigua & Barbuda", "LATIN AMER. & CARIB", 69108, 443, 156, 34.54, -6.15, 19.46, 11000, 89, 549.9, 16.93, 5.37); - yield return new Country("Argentina", "LATIN AMER. & CARIB", 39921833, 2766890, 14.4, 0.18, 0.61, 15.18, 11200, 97.1, 220.4, 16.73, 7.55); - yield return new Country("Armenia", "C.W. OF IND. STATES", 2976372, 29800, 99.9, 0, -6.47, 23.28, 3500, 98.6, 195.7, 12.07, 8.23); - yield return new Country("Aruba", "LATIN AMER. & CARIB", 71891, 193, 372.5, 35.49, 0, 5.89, 28000, 97, 516.1, 11.03, 6.68); - yield return new Country("Australia", "OCEANIA", 20264082, 7686850, 2.6, 0.34, 3.98, 4.69, 29000, 100, 565.5, 12.14, 7.51); - yield return new Country("Austria", "WESTERN EUROPE", 8192880, 83870, 97.7, 0, 2, 4.66, 30000, 98, 452.2, 8.74, 9.76); - yield return new Country("Azerbaijan", "C.W. OF IND. STATES", 7961619, 86600, 91.9, 0, -4.9, 81.74, 3400, 97, 137.1, 20.74, 9.75); - yield return new Country("The Bahamas", "LATIN AMER. & CARIB", 303770, 13940, 21.8, 25.41, -2.2, 25.21, 16700, 95.6, 460.6, 17.57, 9.05); - yield return new Country("Bahrain", "NEAR EAST", 698585, 665, 1050.5, 24.21, 1.05, 17.27, 16900, 89.1, 281.3, 17.8, 4.14); - yield return new Country("Bangladesh", "ASIA (EX. NEAR EAST)", 147365352, 144000, 1023.4, 0.4, -0.71, 62.6, 1900, 43.1, 7.3, 29.8, 8.27); - yield return new Country("Barbados", "LATIN AMER. & CARIB", 279912, 431, 649.5, 22.51, -0.31, 12.5, 15700, 97.4, 481.9, 12.71, 8.67); - yield return new Country("Belarus", "C.W. OF IND. STATES", 10293011, 207600, 49.6, 0, 2.54, 13.37, 6100, 99.6, 319.1, 11.16, 14.02); - yield return new Country("Belgium", "WESTERN EUROPE", 10379067, 30528, 340, 0.22, 1.23, 4.68, 29100, 98, 462.6, 10.38, 10.27); - yield return new Country("Belize", "LATIN AMER. & CARIB", 287730, 22966, 12.5, 1.68, 0, 25.69, 4900, 94.1, 115.7, 28.84, 5.72); - yield return new Country("Benin", "SUB-SAHARAN AFRICA", 7862944, 112620, 69.8, 0.11, 0, 85, 1100, 40.9, 9.7, 38.85, 12.22); - yield return new Country("Bermuda", "NORTHERN AMERICA", 65773, 53, 1241, 194.34, 2.49, 8.53, 36000, 98, 851.4, 11.4, 7.74); - yield return new Country("Bhutan", "ASIA (EX. NEAR EAST)", 2279723, 47000, 48.5, 0, 0, 100.44, 1300, 42.2, 14.3, 33.65, 12.7); - yield return new Country("Bolivia", "LATIN AMER. & CARIB", 8989046, 1098580, 8.2, 0, -1.32, 53.11, 2400, 87.2, 71.9, 23.3, 7.53); - yield return new Country("Bosnia & Herzegovina", "EASTERN EUROPE", 4498976, 51129, 88, 0.04, 0.31, 21.05, 6100,null, 215.4, 8.77, 8.27); - yield return new Country("Botswana", "SUB-SAHARAN AFRICA", 1639833, 600370, 2.7, 0, 0, 54.58, 9000, 79.8, 80.5, 23.08, 29.5); - yield return new Country("Brazil", "LATIN AMER. & CARIB", 188078227, 8511965, 22.1, 0.09, -0.03, 29.61, 7600, 86.4, 225.3, 16.56, 6.17); - yield return new Country("British Virgin Is.", "LATIN AMER. & CARIB", 23098, 153, 151, 52.29, 10.01, 18.05, 16000, 97.8, 506.5, 14.89, 4.42); - yield return new Country("Brunei", "ASIA (EX. NEAR EAST)", 379444, 5770, 65.8, 2.79, 3.59, 12.61, 18600, 93.9, 237.2, 18.79, 3.45); - yield return new Country("Bulgaria", "EASTERN EUROPE", 7385367, 110910, 66.6, 0.32, -4.58, 20.55, 7600, 98.6, 336.3, 9.65, 14.27); - yield return new Country("Burkina Faso", "SUB-SAHARAN AFRICA", 13902972, 274200, 50.7, 0, 0, 97.57, 1100, 26.6, 7, 45.62, 15.6); - yield return new Country("Burma", "ASIA (EX. NEAR EAST)", 47382633, 678500, 69.8, 0.28, -1.8, 67.24, 1800, 85.3, 10.1, 17.91, 9.83); - yield return new Country("Burundi", "SUB-SAHARAN AFRICA", 8090068, 27830, 290.7, 0, -0.06, 69.29, 600, 51.6, 3.4, 42.22, 13.46); - yield return new Country("Cambodia", "ASIA (EX. NEAR EAST)", 13881427, 181040, 76.7, 0.24, 0, 71.48, 1900, 69.4, 2.6, 26.9, 9.06); - yield return new Country("Cameroon", "SUB-SAHARAN AFRICA", 17340702, 475440, 36.5, 0.08, 0, 68.26, 1800, 79, 5.7, 33.89, 13.47); - yield return new Country("Canada", "NORTHERN AMERICA", 33098932, 9984670, 3.3, 2.02, 5.96, 4.75, 29800, 97, 552.2, 10.78, 7.8); - yield return new Country("Cape Verde", "SUB-SAHARAN AFRICA", 420979, 4033, 104.4, 23.93, -12.07, 47.77, 1400, 76.6, 169.6, 24.87, 6.55); - yield return new Country("Cayman Islands", "LATIN AMER. & CARIB", 45436, 262, 173.4, 61.07, 18.75, 8.19, 35000, 98, 836.3, 12.74, 4.89); - yield return new Country("Central African Rep.", "SUB-SAHARAN AFRICA", 4303356, 622984, 6.9, 0, 0, 91, 1100, 51, 2.3, 33.91, 18.65); - yield return new Country("Chad", "SUB-SAHARAN AFRICA", 9944201, 1284000, 7.7, 0, -0.11, 93.82, 1200, 47.5, 1.3, 45.73, 16.38); - yield return new Country("Chile", "LATIN AMER. & CARIB", 16134219, 756950, 21.3, 0.85, 0, 8.8, 9900, 96.2, 213, 15.23, 5.81); - yield return new Country("China", "ASIA (EX. NEAR EAST)", 1313973713, 9596960, 136.9, 0.15, -0.4, 24.18, 5000, 90.9, 266.7, 13.25, 6.97); - yield return new Country("Colombia", "LATIN AMER. & CARIB", 43593035, 1138910, 38.3, 0.28, -0.31, 20.97, 6300, 92.5, 176.2, 20.48, 5.58); - yield return new Country("Comoros", "SUB-SAHARAN AFRICA", 690948, 2170, 318.4, 15.67, 0, 74.93, 700, 56.5, 24.5, 36.93, 8.2); - yield return new Country("Congo, Dem.Rep.", "SUB - SAHARAN AFRICA", 62660551, 2345410, 26.7, 0, 0, 94.69, 700, 65.5, 0.2, 43.69, 13.27); - yield return new Country("Congo, Repub.of the", "SUB - SAHARAN AFRICA", 3702314, 342000, 10.8, 0.05, -0.17, 93.86, 700, 83.8, 3.7, 42.57, 12.93); - yield return new Country("Cook Islands", "OCEANIA", 21388, 240, 89.1, 50,null,null, 5000, 95, 289.9, 21,null); - yield return new Country("Costa Rica", "LATIN AMER. & CARIB", 4075261, 51100, 79.8, 2.52, 0.51, 9.95, 9100, 96, 340.7, 18.32, 4.36); - yield return new Country("Cote d'Ivoire", "SUB-SAHARAN AFRICA", 17654843, 322460, 54.8, 0.16, -0.07, 90.83, 1400, 50.9, 14.6, 35.11, 14.84); - yield return new Country("Croatia", "EASTERN EUROPE", 4494749, 56542, 79.5, 10.32, 1.58, 6.84, 10600, 98.5, 420.4, 9.61, 11.48); - yield return new Country("Cuba", "LATIN AMER. & CARIB", 11382820, 110860, 102.7, 3.37, -1.58, 6.33, 2900, 97, 74.7, 11.89, 7.22); - yield return new Country("Cyprus", "NEAR EAST", 784301, 9250, 84.8, 7.01, 0.43, 7.18, 19200, 97.6,null, 12.56, 7.68); - yield return new Country("Czech Republic", "EASTERN EUROPE", 10235455, 78866, 129.8, 0, 0.97, 3.93, 15700, 99.9, 314.3, 9.02, 10.59); - yield return new Country("Denmark", "WESTERN EUROPE", 5450661, 43094, 126.5, 16.97, 2.48, 4.56, 31100, 100, 614.6, 11.13, 10.36); - yield return new Country("Djibouti", "SUB-SAHARAN AFRICA", 486530, 23000, 21.2, 1.37, 0, 104.13, 1300, 67.9, 22.8, 39.53, 19.31); - yield return new Country("Dominica", "LATIN AMER. & CARIB", 68910, 754, 91.4, 19.63, -13.87, 14.15, 5400, 94, 304.8, 15.27, 6.73); - yield return new Country("Dominican Republic", "LATIN AMER. & CARIB", 9183984, 48730, 188.5, 2.64, -3.22, 32.38, 6000, 84.7, 97.4, 23.22, 5.73); - yield return new Country("East Timor", "ASIA (EX. NEAR EAST)", 1062777, 15007, 70.8, 4.7, 0, 47.41, 500, 58.6,null, 26.99, 6.24); - yield return new Country("Ecuador", "LATIN AMER. & CARIB", 13547510, 283560, 47.8, 0.79, -8.58, 23.66, 3300, 92.5, 125.6, 22.29, 4.23); - yield return new Country("Egypt", "NORTHERN AFRICA", 78887007, 1001450, 78.8, 0.24, -0.22, 32.59, 4000, 57.7, 131.8, 22.94, 5.23); - yield return new Country("El Salvador", "LATIN AMER. & CARIB", 6822378, 21040, 324.3, 1.46, -3.74, 25.1, 4800, 80.2, 142.4, 26.61, 5.78); - yield return new Country("Equatorial Guinea", "SUB-SAHARAN AFRICA", 540109, 28051, 19.3, 1.06, 0, 85.13, 2700, 85.7, 18.5, 35.59, 15.06); - yield return new Country("Eritrea", "SUB-SAHARAN AFRICA", 4786994, 121320, 39.5, 1.84, 0, 74.87, 700, 58.6, 7.9, 34.33, 9.6); - yield return new Country("Estonia", "BALTICS", 1324333, 45226, 29.3, 8.39, -3.16, 7.87, 12300, 99.8, 333.8, 10.04, 13.25); - yield return new Country("Ethiopia", "SUB-SAHARAN AFRICA", 74777981, 1127127, 66.3, 0, 0, 95.32, 700, 42.7, 8.2, 37.98, 14.86); - yield return new Country("Faroe Islands", "WESTERN EUROPE", 47246, 1399, 33.8, 79.84, 1.41, 6.24, 22000,null, 503.8, 14.05, 8.7); - yield return new Country("Fiji", "OCEANIA", 905949, 18270, 49.6, 6.18, -3.14, 12.62, 5800, 93.7, 112.6, 22.55, 5.65); - yield return new Country("Finland", "WESTERN EUROPE", 5231372, 338145, 15.5, 0.37, 0.95, 3.57, 27400, 100, 405.3, 10.45, 9.86); - yield return new Country("France", "WESTERN EUROPE", 60876136, 547030, 111.3, 0.63, 0.66, 4.26, 27600, 99, 586.4, 11.99, 9.14); - yield return new Country("French Guiana", "LATIN AMER. & CARIB", 199509, 91000, 2.2, 0.42, 6.27, 12.07, 8300, 83, 255.6, 20.46, 4.88); - yield return new Country("French Polynesia", "OCEANIA", 274578, 4167, 65.9, 60.6, 2.94, 8.44, 17500, 98, 194.5, 16.68, 4.69); - yield return new Country("Gabon", "SUB-SAHARAN AFRICA", 1424906, 267667, 5.3, 0.33, 0, 53.64, 5500, 63.2, 27.4, 36.16, 12.25); - yield return new Country("Gambia, The", "SUB - SAHARAN AFRICA", 1641564, 11300, 145.3, 0.71, 1.57, 72.02, 1700, 40.1, 26.8, 39.37, 12.25); - yield return new Country("Gaza Strip", "NEAR EAST", 1428757, 360, 3968.8, 11.11, 1.6, 22.93, 600,null, 244.3, 39.45, 3.8); - yield return new Country("Georgia", "C.W. OF IND. STATES", 4661473, 69700, 66.9, 0.44, -4.7, 18.59, 2500, 99, 146.6, 10.41, 9.23); - yield return new Country("Germany", "WESTERN EUROPE", 82422299, 357021, 230.9, 0.67, 2.18, 4.16, 27600, 99, 667.9, 8.25, 10.62); - yield return new Country("Ghana", "SUB-SAHARAN AFRICA", 22409572, 239460, 93.6, 0.23, -0.64, 51.43, 2200, 74.8, 14.4, 30.52, 9.72); - yield return new Country("Gibraltar", "WESTERN EUROPE", 27928, 7, 3989.7, 171.43, 0, 5.13, 17500,null, 877.7, 10.74, 9.31); - yield return new Country("Greece", "WESTERN EUROPE", 10688058, 131940, 81, 10.37, 2.35, 5.53, 20000, 97.5, 589.7, 9.68, 10.24); - yield return new Country("Greenland", "NORTHERN AMERICA", 56361, 2166086, 0, 2.04, -8.37, 15.82, 20000,null, 448.9, 15.93, 7.84); - yield return new Country("Grenada", "LATIN AMER. & CARIB", 89703, 344, 260.8, 35.17, -13.92, 14.62, 5000, 98, 364.5, 22.08, 6.88); - yield return new Country("Guadeloupe", "LATIN AMER. & CARIB", 452776, 1780, 254.4, 17.19, -0.15, 8.6, 8000, 90, 463.8, 15.05, 6.09); - yield return new Country("Guam", "OCEANIA", 171019, 541, 316.1, 23.2, 0, 6.94, 21000, 99, 492, 18.79, 4.48); - yield return new Country("Guatemala", "LATIN AMER. & CARIB", 12293545, 108890, 112.9, 0.37, -1.67, 35.93, 4100, 70.6, 92.1, 29.88, 5.2); - yield return new Country("Guernsey", "WESTERN EUROPE", 65409, 78, 838.6, 64.1, 3.84, 4.71, 20000,null, 842.4, 8.81, 10.01); - yield return new Country("Guinea", "SUB-SAHARAN AFRICA", 9690222, 245857, 39.4, 0.13, -3.06, 90.37, 2100, 35.9, 2.7, 41.76, 15.48); - yield return new Country("Guinea-Bissau", "SUB-SAHARAN AFRICA", 1442029, 36120, 39.9, 0.97, -1.57, 107.17, 800, 42.4, 7.4, 37.22, 16.53); - yield return new Country("Guyana", "LATIN AMER. & CARIB", 767245, 214970, 3.6, 0.21, -2.07, 33.26, 4000, 98.8, 143.5, 18.28, 8.28); - yield return new Country("Haiti", "LATIN AMER. & CARIB", 8308504, 27750, 299.4, 6.38, -3.4, 73.45, 1600, 52.9, 16.9, 36.44, 12.17); - yield return new Country("Honduras", "LATIN AMER. & CARIB", 7326496, 112090, 65.4, 0.73, -1.99, 29.32, 2600, 76.2, 67.5, 28.24, 5.28); - yield return new Country("Hong Kong", "ASIA (EX. NEAR EAST)", 6940432, 1092, 6355.7, 67.12, 5.24, 2.97, 28800, 93.5, 546.7, 7.29, 6.29); - yield return new Country("Hungary", "EASTERN EUROPE", 9981334, 93030, 107.3, 0, 0.86, 8.57, 13900, 99.4, 336.2, 9.72, 13.11); - yield return new Country("Iceland", "WESTERN EUROPE", 299388, 103000, 2.9, 4.83, 2.38, 3.31, 30900, 99.9, 647.7, 13.64, 6.72); - yield return new Country("India", "ASIA (EX. NEAR EAST)", 1095351995, 3287590, 333.2, 0.21, -0.07, 56.29, 2900, 59.5, 45.4, 22.01, 8.18); - yield return new Country("Indonesia", "ASIA (EX. NEAR EAST)", 245452739, 1919440, 127.9, 2.85, 0, 35.6, 3200, 87.9, 52, 20.34, 6.25); - yield return new Country("Iran", "ASIA (EX. NEAR EAST)", 68688433, 1648000, 41.7, 0.15, -0.84, 41.58, 7000, 79.4, 276.4, 17, 5.55); - yield return new Country("Iraq", "NEAR EAST", 26783383, 437072, 61.3, 0.01, 0, 50.25, 1500, 40.4, 38.6, 31.98, 5.37); - yield return new Country("Ireland", "WESTERN EUROPE", 4062235, 70280, 57.8, 2.06, 4.99, 5.39, 29600, 98, 500.5, 14.45, 7.82); - yield return new Country("Isle of Man", "WESTERN EUROPE", 75441, 572, 131.9, 27.97, 5.36, 5.93, 21000,null, 676, 11.05, 11.19); - yield return new Country("Israel", "NEAR EAST", 6352117, 20770, 305.8, 1.31, 0.68, 7.03, 19800, 95.4, 462.3, 17.97, 6.18); - yield return new Country("Italy", "WESTERN EUROPE", 58133509, 301230, 193, 2.52, 2.07, 5.94, 26700, 98.6, 430.9, 8.72, 10.4); - yield return new Country("Jamaica", "LATIN AMER. & CARIB", 2758124, 10991, 250.9, 9.3, -4.92, 12.36, 3900, 87.9, 124, 20.82, 6.52); - yield return new Country("Japan", "ASIA (EX. NEAR EAST)", 127463611, 377835, 337.4, 7.87, 0, 3.26, 28200, 99, 461.2, 9.37, 9.16); - yield return new Country("Jersey", "WESTERN EUROPE", 91084, 116, 785.2, 60.34, 2.76, 5.24, 24800,null, 811.3, 9.3, 9.28); - yield return new Country("Jordan", "NEAR EAST", 5906760, 92300, 64, 0.03, 6.59, 17.35, 4300, 91.3, 104.5, 21.25, 2.65); - yield return new Country("Kazakhstan", "C.W. OF IND. STATES", 15233244, 2717300, 5.6, 0, -3.35, 29.21, 6300, 98.4, 164.1, 16, 9.42); - yield return new Country("Kenya", "SUB-SAHARAN AFRICA", 34707817, 582650, 59.6, 0.09, -0.1, 61.47, 1000, 85.1, 8.1, 39.72, 14.02); - yield return new Country("Kiribati", "OCEANIA", 105432, 811, 130, 140.94, 0, 48.52, 800,null, 42.7, 30.65, 8.26); - yield return new Country("North Korea", "ASIA(EX.NEAR EAST)", 23113019, 120540, 191.8, 2.07, 0, 24.04, 1300, 99, 42.4, 15.54, 7.13); - yield return new Country("South Korea", "ASIA(EX.NEAR EAST)", 48846823, 98480, 496, 2.45, 0, 7.05, 17800, 97.9, 486.1, 10, 5.85); - yield return new Country("Kuwait", "NEAR EAST", 2418393, 17820, 135.7, 2.8, 14.18, 9.95, 19000, 83.5, 211, 21.94, 2.41); - yield return new Country("Kyrgyzstan", "C.W. OF IND. STATES", 5213898, 198500, 26.3, 0, -2.45, 35.64, 1600, 97, 84, 22.8, 7.08); - yield return new Country("Laos", "ASIA (EX. NEAR EAST)", 6368481, 236800, 26.9, 0, 0, 85.22, 1700, 66.4, 14.1, 35.49, 11.55); - yield return new Country("Latvia", "BALTICS", 2274735, 64589, 35.2, 0.82, -2.23, 9.55, 10200, 99.8, 321.4, 9.24, 13.66); - yield return new Country("Lebanon", "NEAR EAST", 3874050, 10400, 372.5, 2.16, 0, 24.52, 4800, 87.4, 255.6, 18.52, 6.21); - yield return new Country("Lesotho", "SUB-SAHARAN AFRICA", 2022331, 30355, 66.6, 0, -0.74, 84.23, 3000, 84.8, 23.7, 24.75, 28.71); - yield return new Country("Liberia", "SUB-SAHARAN AFRICA", 3042004, 111370, 27.3, 0.52, 0, 128.87, 1000, 57.5, 2.3, 44.77, 23.1); - yield return new Country("Libya", "NORTHERN AFRICA", 5900754, 1759540, 3.4, 0.1, 0, 24.6, 6400, 82.6, 127.1, 26.49, 3.48); - yield return new Country("Liechtenstein", "WESTERN EUROPE", 33987, 160, 212.4, 0, 4.85, 4.7, 25000, 100, 585.5, 10.21, 7.18); - yield return new Country("Lithuania", "BALTICS", 3585906, 65200, 55, 0.14, -0.71, 6.89, 11400, 99.6, 223.4, 8.75, 10.98); - yield return new Country("Luxembourg", "WESTERN EUROPE", 474413, 2586, 183.5, 0, 8.97, 4.81, 55100, 100, 515.4, 11.94, 8.41); - yield return new Country("Macau", "ASIA (EX. NEAR EAST)", 453125, 28, 16183, 146.43, 4.86, 4.39, 19400, 94.5, 384.9, 8.48, 4.47); - yield return new Country("Macedonia", "EASTERN EUROPE", 2050554, 25333, 80.9, 0, -1.45, 10.09, 6700,null, 260, 12.02, 8.77); - yield return new Country("Madagascar", "SUB-SAHARAN AFRICA", 18595469, 587040, 31.7, 0.82, 0, 76.83, 800, 68.9, 3.6, 41.41, 11.11); - yield return new Country("Malawi", "SUB-SAHARAN AFRICA", 13013926, 118480, 109.8, 0, 0, 103.32, 600, 62.7, 7.9, 43.13, 19.33); - yield return new Country("Malaysia", "ASIA (EX. NEAR EAST)", 24385858, 329750, 74, 1.42, 0, 17.7, 9000, 88.7, 179, 22.86, 5.05); - yield return new Country("Maldives", "ASIA (EX. NEAR EAST)", 359008, 300, 1196.7, 214.67, 0, 56.52, 3900, 97.2, 90, 34.81, 7.06); - yield return new Country("Mali", "SUB-SAHARAN AFRICA", 11716829, 1240000, 9.5, 0, -0.33, 116.79, 900, 46.4, 6.4, 49.82, 16.89); - yield return new Country("Malta", "WESTERN EUROPE", 400214, 316, 1266.5, 62.28, 2.07, 3.89, 17700, 92.8, 505, 10.22, 8.1); - yield return new Country("Marshall Islands", "OCEANIA", 60422, 11854, 5.1, 3.12, -6.04, 29.45, 1600, 93.7, 91.2, 33.05, 4.78); - yield return new Country("Martinique", "LATIN AMER. & CARIB", 436131, 1100, 396.5, 31.82, -0.05, 7.09, 14400, 97.7, 394.4, 13.74, 6.48); - yield return new Country("Mauritania", "SUB-SAHARAN AFRICA", 3177388, 1030700, 3.1, 0.07, 0, 70.89, 1800, 41.7, 12.9, 40.99, 12.16); - yield return new Country("Mauritius", "SUB-SAHARAN AFRICA", 1240827, 2040, 608.3, 8.68, -0.9, 15.03, 11400, 85.6, 289.3, 15.43, 6.86); - yield return new Country("Mayotte", "SUB-SAHARAN AFRICA", 201234, 374, 538.1, 49.52, 6.78, 62.4, 2600,null, 49.7, 40.95, 7.7); - yield return new Country("Mexico", "LATIN AMER. & CARIB", 107449525, 1972550, 54.5, 0.47, -4.87, 20.91, 9000, 92.2, 181.6, 20.69, 4.74); - yield return new Country("Micronesia, Fed.St.", "OCEANIA", 108004, 702, 153.9, 870.66, -20.99, 30.21, 2000, 89, 114.8, 24.68, 4.75); - yield return new Country("Moldova", "C.W. OF IND. STATES", 4466706, 33843, 132, 0, -0.26, 40.42, 1800, 99.1, 208.1, 15.7, 12.64); - yield return new Country("Monaco", "WESTERN EUROPE", 32543, 2, 16271.5, 205, 7.75, 5.43, 27000, 99, 1035.6, 9.19, 12.91); - yield return new Country("Mongolia", "ASIA (EX. NEAR EAST)", 2832224, 1564116, 1.8, 0, 0, 53.79, 1800, 97.8, 55.1, 21.59, 6.95); - yield return new Country("Montserrat", "LATIN AMER. & CARIB", 9439, 102, 92.5, 39.22, 0, 7.35, 3400, 97,null, 17.59, 7.1); - yield return new Country("Morocco", "NORTHERN AFRICA", 33241259, 446550, 74.4, 0.41, -0.98, 41.62, 4000, 51.7, 40.4, 21.98, 5.58); - yield return new Country("Mozambique", "SUB-SAHARAN AFRICA", 19686505, 801590, 24.6, 0.31, 0, 130.79, 1200, 47.8, 3.5, 35.18, 21.35); - yield return new Country("Namibia", "SUB-SAHARAN AFRICA", 2044147, 825418, 2.5, 0.19, 0, 48.98, 7200, 84, 62.6, 24.32, 18.86); - yield return new Country("Nauru", "OCEANIA", 13287, 21, 632.7, 142.86, 0, 9.95, 5000,null, 143, 24.76, 6.7); - yield return new Country("Nepal", "ASIA (EX. NEAR EAST)", 28287147, 147181, 192.2, 0, 0, 66.98, 1400, 45.2, 15.9, 30.98, 9.31); - yield return new Country("Netherlands", "WESTERN EUROPE", 16491461, 41526, 397.1, 1.09, 2.91, 5.04, 28600, 99, 460.8, 10.9, 8.68); - yield return new Country("Netherlands Antilles", "LATIN AMER. & CARIB", 221736, 960, 231, 37.92, -0.41, 10.03, 11400, 96.7, 365.3, 14.78, 6.45); - yield return new Country("New Caledonia", "OCEANIA", 219246, 19060, 11.5, 11.83, 0, 7.72, 15000, 91, 252.2, 18.11, 5.69); - yield return new Country("New Zealand", "OCEANIA", 4076140, 268680, 15.2, 5.63, 4.05, 5.85, 21600, 99, 441.7, 13.76, 7.53); - yield return new Country("Nicaragua", "LATIN AMER. & CARIB", 5570129, 129494, 43, 0.7, -1.22, 29.11, 2300, 67.5, 39.7, 24.51, 4.45); - yield return new Country("Niger", "SUB-SAHARAN AFRICA", 12525094, 1267000, 9.9, 0, -0.67, 121.69, 800, 17.6, 1.9, 50.73, 20.91); - yield return new Country("Nigeria", "SUB-SAHARAN AFRICA", 131859731, 923768, 142.7, 0.09, 0.26, 98.8, 900, 68, 9.3, 40.43, 16.94); - yield return new Country("N. Mariana Islands", "OCEANIA", 82459, 477, 172.9, 310.69, 9.61, 7.11, 12500, 97, 254.7, 19.43, 2.29); - yield return new Country("Norway", "WESTERN EUROPE", 4610820, 323802, 14.2, 7.77, 1.74, 3.7, 37800, 100, 461.7, 11.46, 9.4); - yield return new Country("Oman", "NEAR EAST", 3102229, 212460, 14.6, 0.98, 0.28, 19.51, 13100, 75.8, 85.5, 36.24, 3.81); - yield return new Country("Pakistan", "ASIA (EX. NEAR EAST)", 165803560, 803940, 206.2, 0.13, -2.77, 72.44, 2100, 45.7, 31.8, 29.74, 8.23); - yield return new Country("Palau", "OCEANIA", 20579, 458, 44.9, 331.66, 2.85, 14.84, 9000, 92, 325.6, 18.03, 6.8); - yield return new Country("Panama", "LATIN AMER. & CARIB", 3191319, 78200, 40.8, 3.18, -0.91, 20.47, 6300, 92.6, 137.9, 21.74, 5.36); - yield return new Country("Papua New Guinea", "OCEANIA", 5670544, 462840, 12.3, 1.11, 0, 51.45, 2200, 64.6, 10.9, 29.36, 7.25); - yield return new Country("Paraguay", "LATIN AMER. & CARIB", 6506464, 406750, 16, 0, -0.08, 25.63, 4700, 94, 49.2, 29.1, 4.49); - yield return new Country("Peru", "LATIN AMER. & CARIB", 28302603, 1285220, 22, 0.19, -1.05, 31.94, 5100, 90.9, 79.5, 20.48, 6.23); - yield return new Country("Philippines", "ASIA (EX. NEAR EAST)", 89468677, 300000, 298.2, 12.1, -1.5, 23.51, 4600, 92.6, 38.4, 24.89, 5.41); - yield return new Country("Poland", "EASTERN EUROPE", 38536869, 312685, 123.3, 0.16, -0.49, 8.51, 11100, 99.8, 306.3, 9.85, 9.89); - yield return new Country("Portugal", "WESTERN EUROPE", 10605870, 92391, 114.8, 1.94, 3.57, 5.05, 18000, 93.3, 399.2, 10.72, 10.5); - yield return new Country("Puerto Rico", "LATIN AMER. & CARIB", 3927188, 13790, 284.8, 3.63, -1.46, 8.24, 16800, 94.1, 283.1, 12.77, 7.65); - yield return new Country("Qatar", "NEAR EAST", 885359, 11437, 77.4, 4.92, 16.29, 18.61, 21500, 82.5, 232, 15.56, 4.72); - yield return new Country("Reunion", "SUB-SAHARAN AFRICA", 787584, 2517, 312.9, 8.22, 0, 7.78, 5800, 88.9, 380.9, 18.9, 5.49); - yield return new Country("Romania", "EASTERN EUROPE", 22303552, 237500, 93.9, 0.09, -0.13, 26.43, 7000, 98.4, 196.9, 10.7, 11.77); - yield return new Country("Russia", "C.W. OF IND. STATES", 142893540, 17075200, 8.4, 0.22, 1.02, 15.39, 8900, 99.6, 280.6, 9.95, 14.65); - yield return new Country("Rwanda", "SUB-SAHARAN AFRICA", 8648248, 26338, 328.4, 0, 0, 91.23, 1300, 70.4, 2.7, 40.37, 16.09); - yield return new Country("Saint Helena", "SUB-SAHARAN AFRICA", 7502, 413, 18.2, 14.53, 0, 19, 2500, 97, 293.3, 12.13, 6.53); - yield return new Country("Saint Kitts & Nevis", "LATIN AMER. & CARIB", 39129, 261, 149.9, 51.72, -7.11, 14.49, 8800, 97, 638.9, 18.02, 8.33); - yield return new Country("Saint Lucia", "LATIN AMER. & CARIB", 168458, 616, 273.5, 25.65, -2.67, 13.53, 5400, 67, 303.3, 19.68, 5.08); - yield return new Country("St Pierre & Miquelon", "NORTHERN AMERICA", 7026, 242, 29, 49.59, -4.86, 7.54, 6900, 99, 683.2, 13.52, 6.83); - yield return new Country("Saint Vincent and the Grenadines", "LATIN AMER. & CARIB", 117848, 389, 303, 21.59, -7.64, 14.78, 2900, 96, 190.9, 16.18, 5.98); - yield return new Country("Samoa", "OCEANIA", 176908, 2944, 60.1, 13.69, -11.7, 27.71, 5600, 99.7, 75.2, 16.43, 6.62); - yield return new Country("San Marino", "WESTERN EUROPE", 29251, 61, 479.5, 0, 10.98, 5.73, 34600, 96, 704.3, 10.02, 8.17); - yield return new Country("Sao Tome & Principe", "SUB-SAHARAN AFRICA", 193413, 1001, 193.2, 20.88, -2.72, 43.11, 1200, 79.3, 36.2, 40.25, 6.47); - yield return new Country("Saudi Arabia", "NEAR EAST", 27019731, 1960582, 13.8, 0.13, -2.71, 13.24, 11800, 78.8, 140.6, 29.34, 2.58); - yield return new Country("Senegal", "SUB-SAHARAN AFRICA", 11987121, 196190, 61.1, 0.27, 0.2, 55.51, 1600, 40.2, 22.2, 32.78, 9.42); - yield return new Country("Serbia", "EASTERN EUROPE", 9396411, 88361, 106.3, 0, -1.33, 12.89, 2200, 93, 285.8,null,null); - yield return new Country("Seychelles", "SUB-SAHARAN AFRICA", 81541, 455, 179.2, 107.91, -5.69, 15.53, 7800, 58, 262.4, 16.03, 6.29); - yield return new Country("Sierra Leone", "SUB-SAHARAN AFRICA", 6005250, 71740, 83.7, 0.56, 0, 143.64, 500, 31.4, 4, 45.76, 23.03); - yield return new Country("Singapore", "ASIA (EX. NEAR EAST)", 4492150, 693, 6482.2, 27.85, 11.53, 2.29, 23700, 92.5, 411.4, 9.34, 4.28); - yield return new Country("Slovakia", "EASTERN EUROPE", 5439448, 48845, 111.4, 0, 0.3, 7.41, 13300,null, 220.1, 10.65, 9.45); - yield return new Country("Slovenia", "EASTERN EUROPE", 2010347, 20273, 99.2, 0.23, 1.12, 4.45, 19000, 99.7, 406.1, 8.98, 10.31); - yield return new Country("Solomon Islands", "OCEANIA", 552438, 28450, 19.4, 18.67, 0, 21.29, 1700,null, 13.4, 30.01, 3.92); - yield return new Country("Somalia", "SUB-SAHARAN AFRICA", 8863338, 637657, 13.9, 0.47, 5.37, 116.7, 500, 37.8, 11.3, 45.13, 16.63); - yield return new Country("South Africa", "SUB-SAHARAN AFRICA", 44187637, 1219912, 36.2, 0.23, -0.29, 61.81, 10700, 86.4, 107, 18.2, 22); - yield return new Country("Spain", "WESTERN EUROPE", 40397842, 504782, 80, 0.98, 0.99, 4.42, 22000, 97.9, 453.5, 10.06, 9.72); - yield return new Country("Sri Lanka", "ASIA (EX. NEAR EAST)", 20222240, 65610, 308.2, 2.04, -1.31, 14.35, 3700, 92.3, 61.5, 15.51, 6.52); - yield return new Country("Sudan", "SUB-SAHARAN AFRICA", 41236378, 2505810, 16.5, 0.03, -0.02, 62.5, 1900, 61.1, 16.3, 34.53, 8.97); - yield return new Country("Suriname", "LATIN AMER. & CARIB", 439117, 163270, 2.7, 0.24, -8.81, 23.57, 4000, 93, 184.7, 18.02, 7.27); - yield return new Country("Swaziland", "SUB-SAHARAN AFRICA", 1136334, 17363, 65.5, 0, 0, 69.27, 4900, 81.6, 30.8, 27.41, 29.74); - yield return new Country("Sweden", "WESTERN EUROPE", 9016596, 449964, 20, 0.72, 1.67, 2.77, 26800, 99, 715, 10.27, 10.31); - yield return new Country("Switzerland", "WESTERN EUROPE", 7523934, 41290, 182.2, 0, 4.05, 4.39, 32700, 99, 680.9, 9.71, 8.49); - yield return new Country("Syria", "NEAR EAST", 18881361, 185180, 102, 0.1, 0, 29.53, 3300, 76.9, 153.8, 27.76, 4.81); - yield return new Country("Taiwan", "ASIA (EX. NEAR EAST)", 23036087, 35980, 640.3, 4.35, 0, 6.4, 23400, 96.1, 591, 12.56, 6.48); - yield return new Country("Tajikistan", "C.W. OF IND. STATES", 7320815, 143100, 51.2, 0, -2.86, 110.76, 1000, 99.4, 33.5, 32.65, 8.25); - yield return new Country("Tanzania", "SUB-SAHARAN AFRICA", 37445392, 945087, 39.6, 0.15, -2.06, 98.54, 600, 78.2, 4, 37.71, 16.39); - yield return new Country("Thailand", "ASIA (EX. NEAR EAST)", 64631595, 514000, 125.7, 0.63, 0, 20.48, 7400, 92.6, 108.9, 13.87, 7.04); - yield return new Country("Togo", "SUB-SAHARAN AFRICA", 5548702, 56785, 97.7, 0.1, 0, 66.61, 1500, 60.9, 10.6, 37.01, 9.83); - yield return new Country("Tonga", "OCEANIA", 114689, 748, 153.3, 56.02, 0, 12.62, 2200, 98.5, 97.7, 25.37, 5.28); - yield return new Country("Trinidad & Tobago", "LATIN AMER. & CARIB", 1065842, 5128, 207.9, 7.06, -10.83, 24.31, 9500, 98.6, 303.5, 12.9, 10.57); - yield return new Country("Tunisia", "NORTHERN AFRICA", 10175014, 163610, 62.2, 0.7, -0.57, 24.77, 6900, 74.2, 123.6, 15.52, 5.13); - yield return new Country("Turkey", "NEAR EAST", 70413958, 780580, 90.2, 0.92, 0, 41.04, 6700, 86.5, 269.5, 16.62, 5.97); - yield return new Country("Turkmenistan", "C.W. OF IND. STATES", 5042920, 488100, 10.3, 0, -0.86, 73.08, 5800, 98, 74.6, 27.61, 8.6); - yield return new Country("Turks & Caicos Is", "LATIN AMER. & CARIB", 21152, 430, 49.2, 90.47, 11.68, 15.67, 9600, 98, 269.5, 21.84, 4.21); - yield return new Country("Tuvalu", "OCEANIA", 11810, 26, 454.2, 92.31, 0, 20.03, 1100,null, 59.3, 22.18, 7.11); - yield return new Country("Uganda", "SUB-SAHARAN AFRICA", 28195754, 236040, 119.5, 0, 0, 67.83, 1400, 69.9, 3.6, 47.35, 12.24); - yield return new Country("Ukraine", "C.W. OF IND. STATES", 46710816, 603700, 77.4, 0.46, -0.39, 20.34, 5400, 99.7, 259.9, 8.82, 14.39); - yield return new Country("United Arab Emirates", "NEAR EAST", 2602713, 82880, 31.4, 1.59, 1.03, 14.51, 23200, 77.9, 475.3, 18.96, 4.4); - yield return new Country("United Kingdom", "WESTERN EUROPE", 60609153, 244820, 247.6, 5.08, 2.19, 5.16, 27700, 99, 543.5, 10.71, 10.13); - yield return new Country("United States", "NORTHERN AMERICA", 298444215, 9631420, 31, 0.21, 3.41, 6.5, 37800, 97, 898, 14.14, 8.26); - yield return new Country("Uruguay", "LATIN AMER. & CARIB", 3431932, 176220, 19.5, 0.37, -0.32, 11.95, 12800, 98, 291.4, 13.91, 9.05); - yield return new Country("Uzbekistan", "C.W. OF IND. STATES", 27307134, 447400, 61, 0, -1.72, 71.1, 1700, 99.3, 62.9, 26.36, 7.84); - yield return new Country("Vanuatu", "OCEANIA", 208869, 12200, 17.1, 20.72, 0, 55.16, 2900, 53, 32.6, 22.72, 7.82); - yield return new Country("Venezuela", "LATIN AMER. & CARIB", 25730435, 912050, 28.2, 0.31, -0.04, 22.2, 4800, 93.4, 140.1, 18.71, 4.92); - yield return new Country("Vietnam", "ASIA (EX. NEAR EAST)", 84402966, 329560, 256.1, 1.05, -0.45, 25.95, 2500, 90.3, 187.7, 16.86, 6.22); - yield return new Country("Virgin Islands", "LATIN AMER. & CARIB", 108605, 1910, 56.9, 9.84, -8.94, 8.03, 17200,null, 652.8, 13.96, 6.43); - yield return new Country("Wallis and Futuna", "OCEANIA", 16025, 274, 58.5, 47.08,null,null, 3700, 50, 118.6,null,null); - yield return new Country("West Bank", "NEAR EAST", 2460492, 5860, 419.9, 0, 2.98, 19.62, 800,null, 145.2, 31.67, 3.92); - yield return new Country("Yemen", "NEAR EAST", 21456188, 527970, 40.6, 0.36, 0, 61.5, 800, 50.2, 37.2, 42.89, 8.3); - yield return new Country("Zambia", "SUB-SAHARAN AFRICA", 11502010, 752614, 15.3, 0, 0, 88.29, 800, 80.6, 8.2, 41, 19.93); - yield return new Country("Zimbabwe", "SUB-SAHARAN AFRICA", 12236805, 390580, 31.3, 0, 0, 67.69, 1900, 90.7, 26.8, 28.01, 21.84); - } - - static IReadOnlyList? _all; - - public static IReadOnlyList All - { - get - { - if(_all == null) - { - _all = GetCountries().ToList().AsReadOnly(); - } - - return _all; - } - - } - } -} diff --git a/samples/ControlCatalog/Models/Country.cs b/samples/ControlCatalog/Models/Country.cs deleted file mode 100644 index 072383a31c..0000000000 --- a/samples/ControlCatalog/Models/Country.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace ControlCatalog.Models -{ - public class Country - { - public string Name { get; private set; } - public string Region { get; private set; } - public int Population { get; private set; } - //Square Miles - public int Area { get; private set; } - //Per Square Mile - public double PopulationDensity { get; private set; } - //Coast / Area - public double CoastLine { get; private set; } - public double? NetMigration { get; private set; } - //per 1000 births - public double? InfantMortality { get; private set; } - public int GDP { get; private set; } - public double? LiteracyPercent { get; private set; } - //per 1000 - public double? Phones { get; private set; } - public double? BirthRate { get; private set; } - public double? DeathRate { get; private set; } - - public Country(string name, string region, int population, int area, double density, double coast, double? migration, - double? infantMorality, int gdp, double? literacy, double? phones, double? birth, double? death) - { - Name = name; - Region = region; - Population = population; - Area = area; - PopulationDensity = density; - CoastLine = coast; - NetMigration = migration; - InfantMortality = infantMorality; - GDP = gdp; - LiteracyPercent = literacy; - BirthRate = birth; - DeathRate = death; - } - } -} diff --git a/samples/ControlCatalog/Models/GDPValueConverter.cs b/samples/ControlCatalog/Models/GDPValueConverter.cs deleted file mode 100644 index cad1574e7a..0000000000 --- a/samples/ControlCatalog/Models/GDPValueConverter.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using Avalonia.Data.Converters; -using Avalonia.Media; - -namespace ControlCatalog.Models -{ - public class GDPValueConverter : IValueConverter - { - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if (value is int gdp) - { - if (gdp <= 5000) - return new SolidColorBrush(Colors.Orange, 0.6); - else if (gdp <= 10000) - return new SolidColorBrush(Colors.Yellow, 0.6); - else - return new SolidColorBrush(Colors.LightGreen, 0.6); - } - - return value; - } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } -} diff --git a/samples/ControlCatalog/Models/GDPdLengthConverter.cs b/samples/ControlCatalog/Models/GDPdLengthConverter.cs deleted file mode 100644 index 034e664305..0000000000 --- a/samples/ControlCatalog/Models/GDPdLengthConverter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Globalization; -using Avalonia.Data.Converters; - -namespace ControlCatalog.Models; - -internal class GDPdLengthConverter : IValueConverter -{ - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if (value is double d) - { - return new Avalonia.Controls.DataGridLength(d,Avalonia.Controls.DataGridLengthUnitType.Pixel,d,d); - } - else if (value is decimal d2) - { - var dv =System.Convert.ToDouble(d2); - return new Avalonia.Controls.DataGridLength(dv, Avalonia.Controls.DataGridLengthUnitType.Pixel, dv, dv); - } - return value; - } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if (value is Avalonia.Controls.DataGridLength width) - { - return System.Convert.ToDecimal(width.DisplayValue); - } - return value; - } -} diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index a968b50666..3187e13f9f 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -1,141 +1,11 @@ - + x:Class="ControlCatalog.Pages.DataGridPage"> + + + + + - - - - - - - - - - - - - - - A control for displaying and interacting with a data source. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs index 149b36fc3f..a4fe9619fb 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs @@ -1,101 +1,17 @@ -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Linq; +using System.Diagnostics; using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using ControlCatalog.Models; -using Avalonia.Collections; -using Avalonia.Controls.Primitives; -using Avalonia.Data; -using Avalonia.Threading; +using Avalonia.Interactivity; -namespace ControlCatalog.Pages +namespace ControlCatalog.Pages; + +public class DataGridPage : UserControl { - public class DataGridPage : UserControl + private void OnLinkClicked(object? sender, RoutedEventArgs e) { - public DataGridPage() - { - this.InitializeComponent(); - - var dataGridSortDescription = DataGridSortDescription.FromPath(nameof(Country.Region), ListSortDirection.Ascending, new ReversedStringComparer()); - var collectionView1 = new DataGridCollectionView(Countries.All); - collectionView1.SortDescriptions.Add(dataGridSortDescription); - var dg1 = this.Get("dataGrid1"); - dg1.IsReadOnly = true; - dg1.Sorting += (s, a) => - { - var binding = (a.Column as DataGridBoundColumn)?.Binding as Binding; - - if (binding?.Path is string property - && property == dataGridSortDescription.PropertyPath - && !collectionView1.SortDescriptions.Contains(dataGridSortDescription)) - { - collectionView1.SortDescriptions.Add(dataGridSortDescription); - } - }; - dg1.ItemsSource = collectionView1; - - var dg2 = this.Get("dataGridGrouping"); - dg2.IsReadOnly = true; - - var collectionView2 = new DataGridCollectionView(Countries.All); - collectionView2.GroupDescriptions.Add(new DataGridPathGroupDescription("Region")); - - dg2.ItemsSource = collectionView2; - - var dg3 = this.Get("dataGridEdit"); - dg3.IsReadOnly = false; - - var list = new ObservableCollection - { - new Person { FirstName = "John", LastName = "Doe" , Age = 30}, - new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true , Age = 40 }, - new Person { FirstName = "Zack", LastName = "Ward" , Age = 50 } - }; - DataGrid3Source = list; - - var addButton = this.Get("btnAdd"); - addButton.Click += (a, b) => list.Add(new Person()); - - DataContext = this; - } - - public IEnumerable DataGrid3Source { get; } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } - - private class ReversedStringComparer : IComparer, IComparer - { - public int Compare(object? x, object? y) - { - if (x is string left && y is string right) - { - var reversedLeft = new string(left.Reverse().ToArray()); - var reversedRight = new string(right.Reverse().ToArray()); - return reversedLeft.CompareTo(reversedRight); - } - - return Comparer.Default.Compare(x, y); - } - } - - private void NumericUpDown_OnTemplateApplied(object sender, TemplateAppliedEventArgs e) + Process.Start(new ProcessStartInfo { - // We want to focus the TextBox of the NumericUpDown. To do so we search for this control when the template - // is applied, but we postpone the action until the control is actually loaded. - if (e.NameScope.Find("PART_TextBox") is {} textBox) - { - Dispatcher.UIThread.InvokeAsync(() => - { - textBox.Focus(); - textBox.SelectAll(); - }, DispatcherPriority.Loaded); - } - } + FileName = "https://github.com/AvaloniaUI/Avalonia.Controls.DataGrid", + UseShellExecute = true + }); } } diff --git a/samples/GpuInterop/GpuInterop.csproj b/samples/GpuInterop/GpuInterop.csproj index 5fbef07abc..d8fd135ebd 100644 --- a/samples/GpuInterop/GpuInterop.csproj +++ b/samples/GpuInterop/GpuInterop.csproj @@ -14,7 +14,6 @@ - diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj index d1a654e1ba..571149950f 100644 --- a/samples/Sandbox/Sandbox.csproj +++ b/samples/Sandbox/Sandbox.csproj @@ -11,7 +11,6 @@ - diff --git a/samples/SingleProjectSandbox/SingleProjectSandbox.csproj b/samples/SingleProjectSandbox/SingleProjectSandbox.csproj index dd3b9f170b..bbf83d66fa 100644 --- a/samples/SingleProjectSandbox/SingleProjectSandbox.csproj +++ b/samples/SingleProjectSandbox/SingleProjectSandbox.csproj @@ -19,7 +19,6 @@ - diff --git a/src/Avalonia.Controls.DataGrid b/src/Avalonia.Controls.DataGrid new file mode 160000 index 0000000000..85a0b32ef6 --- /dev/null +++ b/src/Avalonia.Controls.DataGrid @@ -0,0 +1 @@ +Subproject commit 85a0b32ef6d963c1d67619ca3e2f6da0bc43ac9a diff --git a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridAutomationPeer.cs b/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridAutomationPeer.cs deleted file mode 100644 index 5d181d8567..0000000000 --- a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridAutomationPeer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Avalonia.Automation.Peers; - -namespace Avalonia.Controls.Automation.Peers; - -public class DataGridAutomationPeer : ControlAutomationPeer -{ - public DataGridAutomationPeer(DataGrid owner) - : base(owner) - { - } - - public new DataGrid Owner => (DataGrid)base.Owner; - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.DataGrid; - } -} diff --git a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridCellAutomationPeer.cs b/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridCellAutomationPeer.cs deleted file mode 100644 index f1301be70d..0000000000 --- a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridCellAutomationPeer.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Avalonia.Automation.Peers; - -namespace Avalonia.Controls.Automation.Peers; - -public class DataGridCellAutomationPeer : ContentControlAutomationPeer -{ - public DataGridCellAutomationPeer(DataGridCell owner) - : base(owner) - { - } - - public new DataGridCell Owner => (DataGridCell)base.Owner; - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.Custom; - } - - protected override bool IsContentElementCore() => true; - - protected override bool IsControlElementCore() => true; -} diff --git a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridColumnHeaderAutomationPeer.cs b/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridColumnHeaderAutomationPeer.cs deleted file mode 100644 index bfcf00607f..0000000000 --- a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridColumnHeaderAutomationPeer.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Avalonia.Automation.Peers; - -namespace Avalonia.Controls.Automation.Peers; - -public class DataGridColumnHeaderAutomationPeer : ContentControlAutomationPeer -{ - public DataGridColumnHeaderAutomationPeer(DataGridColumnHeader owner) - : base(owner) - { - } - - public new DataGridColumnHeader Owner => (DataGridColumnHeader)base.Owner; - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.HeaderItem; - } - - protected override bool IsContentElementCore() => false; - - protected override bool IsControlElementCore() => true; -} diff --git a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridColumnHeadersPresenterAutomationPeer.cs b/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridColumnHeadersPresenterAutomationPeer.cs deleted file mode 100644 index f84951206e..0000000000 --- a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridColumnHeadersPresenterAutomationPeer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Avalonia.Automation.Peers; -using Avalonia.Controls.Primitives; - -namespace Avalonia.Controls.Automation.Peers; - -public class DataGridColumnHeadersPresenterAutomationPeer : ControlAutomationPeer -{ - public DataGridColumnHeadersPresenterAutomationPeer(DataGridColumnHeadersPresenter owner) - : base(owner) - { - } - - public new DataGridColumnHeadersPresenter Owner => (DataGridColumnHeadersPresenter)base.Owner; - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.Header; - } - - protected override bool IsContentElementCore() => false; - - protected override bool IsControlElementCore() => true; -} diff --git a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridDetailsPresenterAutomationPeer.cs b/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridDetailsPresenterAutomationPeer.cs deleted file mode 100644 index 58b79541c5..0000000000 --- a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridDetailsPresenterAutomationPeer.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Avalonia.Automation.Peers; -using Avalonia.Controls.Primitives; - -namespace Avalonia.Controls.Automation.Peers; - -public class DataGridDetailsPresenterAutomationPeer : ControlAutomationPeer -{ - public DataGridDetailsPresenterAutomationPeer(DataGridDetailsPresenter owner) - : base(owner) - { - } - - public new DataGridDetailsPresenter Owner => (DataGridDetailsPresenter)base.Owner; -} diff --git a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridRowAutomationPeer.cs b/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridRowAutomationPeer.cs deleted file mode 100644 index 4e137a4e74..0000000000 --- a/src/Avalonia.Controls.DataGrid/Automation/Peers/DataGridRowAutomationPeer.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Avalonia.Controls; - -namespace Avalonia.Automation.Peers -{ - public class DataGridRowAutomationPeer : ControlAutomationPeer - { - public DataGridRowAutomationPeer(DataGridRow owner) - : base(owner) - { - } - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.DataItem; - } - - protected override bool IsContentElementCore() => true; - protected override bool IsControlElementCore() => true; - } -} diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj deleted file mode 100644 index bc7885fa53..0000000000 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - $(AvsCurrentTargetFramework);$(AvsLegacyTargetFrameworks);netstandard2.0 - - - - - - - - - - - - - - - - - - - diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs deleted file mode 100644 index c329492b5e..0000000000 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs +++ /dev/null @@ -1,4380 +0,0 @@ -// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using Avalonia.Controls.Utils; -using Avalonia.Utilities; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace Avalonia.Collections -{ - /// - /// Event argument used for page index change notifications. The requested page move - /// can be canceled by setting e.Cancel to True. - /// - public sealed class PageChangingEventArgs : CancelEventArgs - { - /// - /// Constructor that takes the target page index - /// - /// Index of the requested page - public PageChangingEventArgs(int newPageIndex) - { - NewPageIndex = newPageIndex; - } - - /// - /// Gets the index of the requested page - /// - public int NewPageIndex - { - get; - private set; - } - } - - /// Defines a method that enables a collection to provide a custom view for specialized sorting, filtering, grouping, and currency. - public interface IDataGridCollectionViewFactory - { - /// Returns a custom view for specialized sorting, filtering, grouping, and currency. - /// A custom view for specialized sorting, filtering, grouping, and currency. - IDataGridCollectionView CreateView(); - } - - /// - /// DataGrid-readable view over an IEnumerable. - /// - public sealed class DataGridCollectionView : IDataGridCollectionView, IDataGridEditableCollectionView, IList, INotifyPropertyChanged - { - /// - /// Since there's nothing in the un-cancelable event args that is mutable, - /// just create one instance to be used universally. - /// - private static readonly DataGridCurrentChangingEventArgs uncancelableCurrentChangingEventArgs = new DataGridCurrentChangingEventArgs(false); - - /// - /// Value that we cache for the PageIndex if we are in a DeferRefresh, - /// and the user has attempted to move to a different page. - /// - private int _cachedPageIndex = -1; - - /// - /// Value that we cache for the PageSize if we are in a DeferRefresh, - /// and the user has attempted to change the PageSize. - /// - private int _cachedPageSize; - - /// - /// CultureInfo used in this DataGridCollectionView - /// - private CultureInfo _culture; - - /// - /// Private accessor for the Monitor we use to prevent recursion - /// - private SimpleMonitor _currentChangedMonitor = new SimpleMonitor(); - - /// - /// Private accessor for the CurrentItem - /// - private object _currentItem; - - /// - /// Private accessor for the CurrentPosition - /// - private int _currentPosition; - - /// - /// The number of requests to defer Refresh() - /// - private int _deferLevel; - - /// - /// The item we are currently editing - /// - private object _editItem; - - /// - /// Private accessor for the Filter - /// - private Func _filter; - - /// - /// Private accessor for the CollectionViewFlags - /// - private CollectionViewFlags _flags = CollectionViewFlags.ShouldProcessCollectionChanged; - - /// - /// Private accessor for the Grouping data - /// - private CollectionViewGroupRoot _group; - - /// - /// Private accessor for the InternalList - /// - private IList _internalList; - - /// - /// Keeps track of whether groups have been applied to the - /// collection already or not. Note that this can still be set - /// to false even though we specify a GroupDescription, as the - /// collection may not have gone through the PrepareGroups function. - /// - private bool _isGrouping; - - /// - /// Private accessor for indicating whether we want to point to the temporary grouping data for calculations - /// - private bool _isUsingTemporaryGroup; - - /// - /// ConstructorInfo obtained from reflection for generating new items - /// - private ConstructorInfo _itemConstructor; - - /// - /// Whether we have the correct ConstructorInfo information for the ItemConstructor - /// - private bool _itemConstructorIsValid; - - /// - /// The new item we are getting ready to add to the collection - /// - private object _newItem; - - /// - /// Private accessor for the PageIndex - /// - private int _pageIndex = -1; - - /// - /// Private accessor for the PageSize - /// - private int _pageSize; - - /// - /// Whether the source needs to poll for changes - /// (if it did not implement INotifyCollectionChanged) - /// - private bool _pollForChanges; - - /// - /// Private accessor for the SortDescriptions - /// - private DataGridSortDescriptionCollection _sortDescriptions; - - /// - /// Private accessor for the SourceCollection - /// - private IEnumerable _sourceCollection; - - /// - /// Private accessor for the Grouping data on the entire collection - /// - private CollectionViewGroupRoot _temporaryGroup; - - /// - /// Timestamp used to see if there was a collection change while - /// processing enumerator changes - /// - private int _timestamp; - - /// - /// Private accessor for the TrackingEnumerator - /// - private IEnumerator _trackingEnumerator; - - /// - /// Helper constructor that sets default values for isDataSorted and isDataInGroupOrder. - /// - /// The source for the collection - public DataGridCollectionView(IEnumerable source) - : this(source, false /*isDataSorted*/, false /*isDataInGroupOrder*/) - { - } - - /// - /// Initializes a new instance of the DataGridCollectionView class. - /// - /// The source for the collection - /// Determines whether the source is already sorted - /// Whether the source is already in the correct order for grouping - public DataGridCollectionView(IEnumerable source, bool isDataSorted, bool isDataInGroupOrder) - { - _sourceCollection = source ?? throw new ArgumentNullException(nameof(source)); - - SetFlag(CollectionViewFlags.IsDataSorted, isDataSorted); - SetFlag(CollectionViewFlags.IsDataInGroupOrder, isDataInGroupOrder); - - _temporaryGroup = new CollectionViewGroupRoot(this, isDataInGroupOrder); - _group = new CollectionViewGroupRoot(this, false); - _group.GroupDescriptionChanged += OnGroupDescriptionChanged; - _group.GroupDescriptions.CollectionChanged += OnGroupByChanged; - - CopySourceToInternalList(); - _trackingEnumerator = source.GetEnumerator(); - - // set currency - if (_internalList.Count > 0) - { - SetCurrent(_internalList[0], 0, 1); - } - else - { - SetCurrent(null, -1, 0); - } - - // Set flag for whether the collection is empty - SetFlag(CollectionViewFlags.CachedIsEmpty, Count == 0); - - // If we implement INotifyCollectionChanged - if (source is INotifyCollectionChanged coll) - { - coll.CollectionChanged += (_, args) => ProcessCollectionChanged(args); - } - else - { - // If the source doesn't raise collection change events, try to - // detect changes by polling the enumerator - _pollForChanges = true; - } - } - - /// - /// Raise this event when the (filtered) view changes - /// - public event NotifyCollectionChangedEventHandler CollectionChanged; - - /// - /// CollectionChanged event (per INotifyCollectionChanged). - /// - event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged - { - add { CollectionChanged += value; } - remove { CollectionChanged -= value; } - } - - /// - /// Raised when the CurrentItem property changed - /// - public event EventHandler CurrentChanged; - - /// - /// Raised when the CurrentItem property is changing - /// - public event EventHandler CurrentChanging; - - /// - /// Raised when a page index change completed - /// - //TODO Paging - public event EventHandler PageChanged; - - /// - /// Raised when a page index change is requested - /// - //TODO Paging - public event EventHandler PageChanging; - - /// - /// PropertyChanged event. - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// PropertyChanged event (per INotifyPropertyChanged) - /// - event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged - { - add { PropertyChanged += value; } - remove { PropertyChanged -= value; } - } - - /// - /// Enum for CollectionViewFlags - /// - //TODO Paging - [Flags] - private enum CollectionViewFlags - { - /// - /// Whether the list of items (after applying the sort and filters, if any) - /// is already in the correct order for grouping. - /// - IsDataInGroupOrder = 0x01, - - /// - /// Whether the source collection is already sorted according to the SortDescriptions collection - /// - IsDataSorted = 0x02, - - /// - /// Whether we should process the collection changed event - /// - ShouldProcessCollectionChanged = 0x04, - - /// - /// Whether the current item is before the first - /// - IsCurrentBeforeFirst = 0x08, - - /// - /// Whether the current item is after the last - /// - IsCurrentAfterLast = 0x10, - - /// - /// Whether we need to refresh - /// - NeedsRefresh = 0x20, - - /// - /// Whether we cache the IsEmpty value - /// - CachedIsEmpty = 0x40, - - /// - /// Indicates whether a page index change is in process or not - /// - IsPageChanging = 0x80, - - /// - /// Whether we need to move to another page after EndDefer - /// - IsMoveToPageDeferred = 0x100, - - /// - /// Whether we need to update the PageSize after EndDefer - /// - IsUpdatePageSizeDeferred = 0x200 - } - - private Type _itemType; - private Type ItemType - { - get - { - if (_itemType == null) - _itemType = GetItemType(true); - - return _itemType; - } - } - - /// - /// Gets a value indicating whether the view supports AddNew. - /// - public bool CanAddNew - { - get - { - return !IsEditingItem && - (SourceList != null && !SourceList.IsFixedSize && CanConstructItem); - } - } - - /// - /// Gets a value indicating whether the view supports the notion of "pending changes" - /// on the current edit item. This may vary, depending on the view and the particular - /// item. For example, a view might return true if the current edit item - /// implements IEditableObject, or if the view has special knowledge about - /// the item that it can use to support rollback of pending changes. - /// - public bool CanCancelEdit - { - get { return _editItem is IEditableObject; } - } - - /// - /// Gets a value indicating whether the PageIndex value is allowed to change or not. - /// - //TODO Paging - public bool CanChangePage - { - get { return true; } - } - - /// - /// Gets a value indicating whether we support filtering with this ICollectionView. - /// - public bool CanFilter - { - get { return true; } - } - - /// - /// Gets a value indicating whether this view supports grouping. - /// When this returns false, the rest of the interface is ignored. - /// - public bool CanGroup - { - get { return true; } - } - - /// - /// Gets a value indicating whether the view supports Remove and RemoveAt. - /// - public bool CanRemove - { - get - { - return !IsEditingItem && !IsAddingNew && - (SourceList != null && !SourceList.IsFixedSize); - } - } - - /// - /// Gets a value indicating whether we support sorting with this ICollectionView. - /// - public bool CanSort - { - get { return true; } - } - - /// - /// Gets the number of records in the view after - /// filtering, sorting, and paging. - /// - //TODO Paging - public int Count - { - get - { - EnsureCollectionInSync(); - VerifyRefreshNotDeferred(); - - // if we have paging - if (PageSize > 0 && PageIndex > -1) - { - if (IsGrouping && !_isUsingTemporaryGroup) - { - return _group.ItemCount; - } - else - { - return Math.Max(0, Math.Min(PageSize, InternalCount - (_pageSize * PageIndex))); - } - } - else - { - if (IsGrouping) - { - if (_isUsingTemporaryGroup) - { - return _temporaryGroup.ItemCount; - } - else - { - return _group.ItemCount; - } - } - else - { - return InternalCount; - } - } - } - } - - /// - /// Gets or sets Culture to use during sorting. - /// - public CultureInfo Culture - { - get - { - return _culture; - } - - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (_culture != value) - { - _culture = value; - OnPropertyChanged(nameof(Culture)); - } - } - } - - /// - /// Gets the new item when an AddNew transaction is in progress - /// Otherwise it returns null. - /// - public object CurrentAddItem - { - get - { - return _newItem; - } - - private set - { - if (_newItem != value) - { - Debug.Assert(value == null || _newItem == null, "Old and new _newItem values are unexpectedly non null"); - _newItem = value; - OnPropertyChanged(nameof(IsAddingNew)); - OnPropertyChanged(nameof(CurrentAddItem)); - } - } - } - - /// - /// Gets the affected item when an EditItem transaction is in progress - /// Otherwise it returns null. - /// - public object CurrentEditItem - { - get - { - return _editItem; - } - - private set - { - if (_editItem != value) - { - Debug.Assert(value == null || _editItem == null, "Old and new _editItem values are unexpectedly non null"); - bool oldCanCancelEdit = CanCancelEdit; - _editItem = value; - OnPropertyChanged(nameof(IsEditingItem)); - OnPropertyChanged(nameof(CurrentEditItem)); - if (oldCanCancelEdit != CanCancelEdit) - { - OnPropertyChanged(nameof(CanCancelEdit)); - } - } - } - } - - /// - /// Gets the "current item" for this view - /// - public object CurrentItem - { - get - { - VerifyRefreshNotDeferred(); - return _currentItem; - } - } - - /// - /// Gets the ordinal position of the CurrentItem within the - /// (optionally sorted and filtered) view. - /// - public int CurrentPosition - { - get - { - VerifyRefreshNotDeferred(); - return _currentPosition; - } - } - - private string GetOperationNotAllowedDuringAddOrEditText(string action) - { - return $"'{action}' is not allowed during an AddNew or EditItem transaction."; - } - private string GetOperationNotAllowedText(string action, string transaction = null) - { - if (String.IsNullOrWhiteSpace(transaction)) - { - return $"'{action}' is not allowed for this view."; - } - else - { - return $"'{action}' is not allowed during a transaction started by '{transaction}'."; - } - } - - /// - /// Gets or sets the Filter, which is a callback set by the consumer of the ICollectionView - /// and used by the implementation of the ICollectionView to determine if an - /// item is suitable for inclusion in the view. - /// - /// - /// Simpler implementations do not support filtering and will throw a NotSupportedException. - /// Use property to test if filtering is supported before - /// assigning a non-null value. - /// - public Func Filter - { - get - { - return _filter; - } - - set - { - if (IsAddingNew || IsEditingItem) - { - throw new InvalidOperationException(GetOperationNotAllowedDuringAddOrEditText(nameof(Filter))); - } - - if (!CanFilter) - { - throw new NotSupportedException("The Filter property cannot be set when the CanFilter property returns false."); - } - - if (_filter != value) - { - _filter = value; - RefreshOrDefer(); - OnPropertyChanged(nameof(Filter)); - } - } - } - - /// - /// Gets the description of grouping, indexed by level. - /// - public AvaloniaList GroupDescriptions - { - get - { - return _group?.GroupDescriptions; - } - } - - int IDataGridCollectionView.GroupingDepth => GroupDescriptions?.Count ?? 0; - string IDataGridCollectionView.GetGroupingPropertyNameAtDepth(int level) - { - var groups = GroupDescriptions; - if(groups != null && level >= 0 && level < groups.Count) - { - return groups[level].PropertyName; - } - else - { - return String.Empty; - } - } - - /// - /// Gets the top-level groups, constructed according to the descriptions - /// given in GroupDescriptions. - /// - public IAvaloniaReadOnlyList Groups - { - get - { - if (!IsGrouping) - { - return null; - } - - return RootGroup?.Items; - } - } - - /// - /// Gets a value indicating whether an "AddNew" transaction is in progress. - /// - public bool IsAddingNew - { - get { return _newItem != null; } - } - - /// - /// Gets a value indicating whether currency is beyond the end (End-Of-File). - /// - /// Whether IsCurrentAfterLast - public bool IsCurrentAfterLast - { - get - { - VerifyRefreshNotDeferred(); - return CheckFlag(CollectionViewFlags.IsCurrentAfterLast); - } - } - - /// - /// Gets a value indicating whether currency is before the beginning (Beginning-Of-File). - /// - /// Whether IsCurrentBeforeFirst - public bool IsCurrentBeforeFirst - { - get - { - VerifyRefreshNotDeferred(); - return CheckFlag(CollectionViewFlags.IsCurrentBeforeFirst); - } - } - - /// - /// Gets a value indicating whether an EditItem transaction is in progress. - /// - public bool IsEditingItem - { - get { return _editItem != null; } - } - - /// - /// Gets a value indicating whether the resulting (filtered) view is empty. - /// - public bool IsEmpty - { - get - { - EnsureCollectionInSync(); - return InternalCount == 0; - } - } - - /// - /// Gets a value indicating whether a page index change is in process or not. - /// - //TODO Paging - public bool IsPageChanging - { - get - { - return CheckFlag(CollectionViewFlags.IsPageChanging); - } - - private set - { - if (CheckFlag(CollectionViewFlags.IsPageChanging) != value) - { - SetFlag(CollectionViewFlags.IsPageChanging, value); - OnPropertyChanged(nameof(IsPageChanging)); - } - } - } - - /// - /// Gets the minimum number of items known to be in the source collection - /// that verify the current filter if any - /// - public int ItemCount - { - get - { - return InternalList.Count; - } - } - - /// - /// Gets a value indicating whether this view needs to be refreshed. - /// - public bool NeedsRefresh - { - get { return CheckFlag(CollectionViewFlags.NeedsRefresh); } - } - - /// - /// Gets the current page we are on. (zero based) - /// - //TODO Paging - public int PageIndex - { - get - { - return _pageIndex; - } - } - - /// - /// Gets or sets the number of items to display on a page. If the - /// PageSize = 0, then we are not paging, and will display all items - /// in the collection. Otherwise, we will have separate pages for - /// the items to display. - /// - //TODO Paging - public int PageSize - { - get - { - return _pageSize; - } - set - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "PageSize cannot have a negative value."); - } - - // if the Refresh is currently deferred, cache the desired PageSize - // and set the flag so that once the defer is over, we can then - // update the PageSize. - if (IsRefreshDeferred) - { - // set cached value and flag so that we update the PageSize on EndDefer - _cachedPageSize = value; - SetFlag(CollectionViewFlags.IsUpdatePageSizeDeferred, true); - return; - } - - // to see whether or not to fire an OnPropertyChanged - int oldCount = Count; - - if (_pageSize != value) - { - // Remember current currency values for upcoming OnPropertyChanged notifications - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - // Check if there is a current edited or new item so changes can be committed first. - if (CurrentAddItem != null || CurrentEditItem != null) - { - // Check with the ICollectionView.CurrentChanging listeners if it's OK to - // change the currency. If not, then we can't fire the event to allow them to - // commit their changes. So, we will not be able to change the PageSize. - if (!OkToChangeCurrent()) - { - throw new InvalidOperationException("Changing the PageSize is not allowed during an AddNew or EditItem transaction."); - } - - // Currently CommitNew()/CommitEdit()/CancelNew()/CancelEdit() can't handle committing or - // cancelling an item that is no longer on the current page. That's acceptable and means that - // the potential _newItem or _editItem needs to be committed before this PageSize change. - // The reason why we temporarily reset currency here is to give a chance to the bound - // controls to commit or cancel their potential edits/addition. The DataForm calls ForceEndEdit() - // for example as a result of changing currency. - SetCurrentToPosition(-1); - RaiseCurrencyChanges(true /*fireChangedEvent*/, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - - // If the bound controls did not successfully end their potential item editing/addition, we - // need to throw an exception to show that the PageSize change failed. - if (CurrentAddItem != null || CurrentEditItem != null) - { - throw new InvalidOperationException("Changing the PageSize is not allowed during an AddNew or EditItem transaction."); - } - } - - _pageSize = value; - OnPropertyChanged(nameof(PageSize)); - - if (_pageSize == 0) - { - // update the groups for the current page - //*************************************** - PrepareGroups(); - - // if we are not paging - MoveToPage(-1); - } - else if (_pageIndex != 0) - { - if (!CheckFlag(CollectionViewFlags.IsMoveToPageDeferred)) - { - // if the temporaryGroup was not created yet and is out of sync - // then create it so that we can use it as a reference while paging. - if (IsGrouping && _temporaryGroup.ItemCount != InternalList.Count) - { - PrepareTemporaryGroups(); - } - - MoveToFirstPage(); - } - } - else if (IsGrouping) - { - // if the temporaryGroup was not created yet and is out of sync - // then create it so that we can use it as a reference while paging. - if (_temporaryGroup.ItemCount != InternalList.Count) - { - // update the groups that get created for the - // entire collection as well as the current page - PrepareTemporaryGroups(); - } - - // update the groups for the current page - PrepareGroupsForCurrentPage(); - } - - // if the count has changed - if (Count != oldCount) - { - OnPropertyChanged(nameof(Count)); - } - - // reset currency values - ResetCurrencyValues(oldCurrentItem, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - - // send a notification that our collection has been updated - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Reset)); - - // now raise currency changes at the end - RaiseCurrencyChanges(false, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - } - } - } - - /// - /// Gets the Sort criteria to sort items in collection. - /// - /// - /// - /// Clear a sort criteria by assigning SortDescription.Empty to this property. - /// One or more sort criteria in form of - /// can be used, each specifying a property and direction to sort by. - /// - /// - /// - /// Simpler implementations do not support sorting and will throw a NotSupportedException. - /// Use property to test if sorting is supported before adding - /// to SortDescriptions. - /// - public DataGridSortDescriptionCollection SortDescriptions - { - get - { - if (_sortDescriptions == null) - { - SetSortDescriptions(new DataGridSortDescriptionCollection()); - } - - return _sortDescriptions; - } - } - - /// - /// Gets the source of the IEnumerable collection we are using for our view. - /// - public IEnumerable SourceCollection - { - get { return _sourceCollection; } - } - - /// - /// Gets the total number of items in the view before paging is applied. - /// - public int TotalItemCount - { - get - { - return InternalList.Count; - } - } - - /// - /// Gets a value indicating whether we have a valid ItemConstructor of the correct type - /// - private bool CanConstructItem - { - get - { - if (!_itemConstructorIsValid) - { - EnsureItemConstructor(); - } - - return _itemConstructor != null; - } - } - - /// - /// Gets the private count without taking paging or - /// placeholders into account - /// - private int InternalCount - { - get { return InternalList.Count; } - } - - /// - /// Gets the InternalList - /// - private IList InternalList - { - get { return _internalList; } - } - - /// - /// Gets a value indicating whether CurrentItem and CurrentPosition are - /// up-to-date with the state and content of the collection. - /// - private bool IsCurrentInSync - { - get - { - if (IsCurrentInView) - { - return GetItemAt(CurrentPosition).Equals(CurrentItem); - } - else - { - return CurrentItem == null; - } - } - } - - /// - /// Gets a value indicating whether the current item is in the view - /// - private bool IsCurrentInView - { - get - { - VerifyRefreshNotDeferred(); - - // Calling IndexOf will check whether the specified currentItem - // is within the (paged) view. - return IndexOf(CurrentItem) >= 0; - } - } - - /// - /// Gets a value indicating whether or not we have grouping - /// taking place in this collection. - /// - private bool IsGrouping - { - get { return _isGrouping; } - } - - bool IDataGridCollectionView.IsGrouping => IsGrouping; - - /// - /// Gets a value indicating whether there - /// is still an outstanding DeferRefresh in - /// use. If at all possible, derived classes - /// should not call Refresh if IsRefreshDeferred - /// is true. - /// - private bool IsRefreshDeferred - { - get { return _deferLevel > 0; } - } - - /// - /// Gets whether the current page is empty and we need - /// to move to a previous page. - /// - //TODO Paging - private bool NeedToMoveToPreviousPage - { - get { return (PageSize > 0 && Count == 0 && PageIndex != 0 && PageCount == PageIndex); } - } - - /// - /// Gets a value indicating whether we are on the last local page - /// - //TODO Paging - private bool OnLastLocalPage - { - get - { - if (PageSize == 0) - { - return false; - } - - Debug.Assert(PageCount > 0, "Unexpected PageCount <= 0"); - - // if we have no items (PageCount==1) or there is just one page - if (PageCount == 1) - { - return true; - } - - return (PageIndex == PageCount - 1); - } - } - - /// - /// Gets the number of pages we currently have - /// - //TODO Paging - private int PageCount - { - get { return (_pageSize > 0) ? Math.Max(1, (int)Math.Ceiling((double)ItemCount / _pageSize)) : 0; } - } - - /// - /// Gets the root of the Group that we expose to the user - /// - private CollectionViewGroupRoot RootGroup - { - get - { - return _isUsingTemporaryGroup ? _temporaryGroup : _group; - } - } - - /// - /// Gets the SourceCollection as an IList - /// - private IList SourceList - { - get { return SourceCollection as IList; } - } - - /// - /// Gets Timestamp used by the NewItemAwareEnumerator to determine if a - /// collection change has occurred since the enumerator began. (If so, - /// MoveNext should throw.) - /// - private int Timestamp - { - get { return _timestamp; } - } - - /// - /// Gets a value indicating whether a private copy of the data - /// is needed for sorting, filtering, and paging. We want any deriving - /// classes to also be able to access this value to see whether or not - /// to use the default source collection, or the internal list. - /// - //TODO Paging - private bool UsesLocalArray - { - get { return SortDescriptions.Count > 0 || Filter != null || _pageSize > 0 || GroupDescriptions.Count > 0; } - } - - /// - /// Return the item at the specified index - /// - /// Index of the item we want to retrieve - /// The item at the specified index - public object this[int index] - { - get { return GetItemAt(index); } - } - - bool IList.IsFixedSize => SourceList?.IsFixedSize ?? true; - bool IList.IsReadOnly => SourceList?.IsReadOnly ?? true; - bool ICollection.IsSynchronized => false; - object ICollection.SyncRoot => this; - - object IList.this[int index] - { - get => this[index]; - set - { - SourceList[index] = value; - if (SourceList is not INotifyCollectionChanged) - { - // TODO: implement Replace - ProcessCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, value)); - } - } - } - - /// - /// Add a new item to the underlying collection. Returns the new item. - /// After calling AddNew and changing the new item as desired, either - /// CommitNew or CancelNew" should be called to complete the transaction. - /// - /// The new item we are adding - //TODO Paging - public object AddNew() - { - EnsureCollectionInSync(); - VerifyRefreshNotDeferred(); - - if (IsEditingItem) - { - // Implicitly close a previous EditItem - CommitEdit(); - } - - // Implicitly close a previous AddNew - CommitNew(); - - // Checking CanAddNew will validate that we have the correct itemConstructor - if (!CanAddNew) - { - throw new InvalidOperationException(GetOperationNotAllowedText(nameof(AddNew))); - } - - object newItem = null; - - if (_itemConstructor != null) - { - newItem = _itemConstructor.Invoke(null); - } - - try - { - // temporarily disable the CollectionChanged event - // handler so filtering, sorting, or grouping - // doesn't get applied yet - SetFlag(CollectionViewFlags.ShouldProcessCollectionChanged, false); - - if (SourceList != null) - { - SourceList.Add(newItem); - } - } - finally - { - SetFlag(CollectionViewFlags.ShouldProcessCollectionChanged, true); - } - - // Modify our _trackingEnumerator so that it shows that our collection is "up to date" - // and will not refresh for now. - _trackingEnumerator = _sourceCollection.GetEnumerator(); - - int addIndex; - int removeIndex = -1; - - // Adjust index based on where it should be displayed in view. - if (PageSize > 0) - { - // if the page is full (Count==PageSize), then replace last item (Count-1). - // otherwise, we just append at end (Count). - addIndex = Count - ((Count == PageSize) ? 1 : 0); - - // if the page is full, remove the last item to make space for the new one. - removeIndex = (Count == PageSize) ? addIndex : -1; - } - else - { - // for non-paged lists, we want to insert the item - // as the last item in the view - addIndex = Count; - } - - // if we need to remove an item from the view due to paging - if (removeIndex > -1) - { - object removeItem = GetItemAt(removeIndex); - if (IsGrouping) - { - _group.RemoveFromSubgroups(removeItem); - } - - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - removeItem, - removeIndex)); - } - - // add the new item to the internal list - _internalList.Insert(ConvertToInternalIndex(addIndex), newItem); - OnPropertyChanged(nameof(ItemCount)); - - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - AdjustCurrencyForAdd(null, addIndex); - - if (IsGrouping) - { - _group.InsertSpecialItem(_group.Items.Count, newItem, false); - if (PageSize > 0) - { - _temporaryGroup.InsertSpecialItem(_temporaryGroup.Items.Count, newItem, false); - } - } - - // fire collection changed. - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - newItem, - addIndex)); - - RaiseCurrencyChanges(false, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - - // set the current new item - CurrentAddItem = newItem; - - MoveCurrentTo(newItem); - - // if the new item is editable, call BeginEdit on it - if (newItem is IEditableObject editableObject) - { - editableObject.BeginEdit(); - } - - return newItem; - } - - /// - /// Complete the transaction started by . - /// The pending changes (if any) to the item are discarded. - /// - public void CancelEdit() - { - if (IsAddingNew) - { - throw new InvalidOperationException(GetOperationNotAllowedText(nameof(CancelEdit), nameof(AddNew))); - } - else if (!CanCancelEdit) - { - throw new InvalidOperationException("CancelEdit is not supported for the current edit item."); - } - - VerifyRefreshNotDeferred(); - - if (CurrentEditItem == null) - { - return; - } - - object editItem = CurrentEditItem; - CurrentEditItem = null; - - if (editItem is IEditableObject ieo) - { - ieo.CancelEdit(); - } - else - { - throw new InvalidOperationException("CancelEdit is not supported for the current edit item."); - } - } - - /// - /// Complete the transaction started by AddNew. The new - /// item is removed from the collection. - /// - //TODO Paging - public void CancelNew() - { - if (IsEditingItem) - { - throw new InvalidOperationException(GetOperationNotAllowedText(nameof(CancelNew), nameof(EditItem))); - } - - VerifyRefreshNotDeferred(); - - if (CurrentAddItem == null) - { - return; - } - - // get index of item before it is removed - int index = IndexOf(CurrentAddItem); - - // remove the new item from the underlying collection - try - { - // temporarily disable the CollectionChanged event - // handler so filtering, sorting, or grouping - // doesn't get applied yet - SetFlag(CollectionViewFlags.ShouldProcessCollectionChanged, false); - - if (SourceList != null) - { - SourceList.Remove(CurrentAddItem); - } - } - finally - { - SetFlag(CollectionViewFlags.ShouldProcessCollectionChanged, true); - } - - // Modify our _trackingEnumerator so that it shows that our collection is "up to date" - // and will not refresh for now. - _trackingEnumerator = _sourceCollection.GetEnumerator(); - - // fire the correct events - if (CurrentAddItem != null) - { - object newItem = EndAddNew(true); - - int addIndex = -1; - - // Adjust index based on where it should be displayed in view. - if (PageSize > 0 && !OnLastLocalPage) - { - // if there is paging and we are not on the last page, we need - // to bring in an item from the next page. - addIndex = Count - 1; - } - - // remove the new item from the internal list - InternalList.Remove(newItem); - - if (IsGrouping) - { - _group.RemoveSpecialItem(_group.Items.Count - 1, newItem, false); - if (PageSize > 0) - { - _temporaryGroup.RemoveSpecialItem(_temporaryGroup.Items.Count - 1, newItem, false); - } - } - - OnPropertyChanged(nameof(ItemCount)); - - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - AdjustCurrencyForRemove(index); - - // fire collection changed. - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - newItem, - index)); - - RaiseCurrencyChanges(false, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - - // if we need to add an item into the view due to paging - if (addIndex > -1) - { - int internalIndex = ConvertToInternalIndex(addIndex); - object addItem = null; - if (IsGrouping) - { - addItem = _temporaryGroup.LeafAt(internalIndex); - _group.AddToSubgroups(addItem, loading: false); - } - else - { - addItem = InternalItemAt(internalIndex); - } - - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - addItem, - IndexOf(addItem))); - } - } - } - - /// - /// Complete the transaction started by . - /// The pending changes (if any) to the item are committed. - /// - //TODO Paging - public void CommitEdit() - { - if (IsAddingNew) - { - throw new InvalidOperationException(GetOperationNotAllowedText(nameof(CommitEdit), nameof(AddNew))); - } - - VerifyRefreshNotDeferred(); - - if (CurrentEditItem == null) - { - return; - } - - object editItem = CurrentEditItem; - CurrentEditItem = null; - - if (editItem is IEditableObject ieo) - { - ieo.EndEdit(); - } - - if (UsesLocalArray) - { - // first remove the item from the array so that we can insert into the correct position - int removeIndex = IndexOf(editItem); - int internalRemoveIndex = InternalIndexOf(editItem); - _internalList.Remove(editItem); - - // check whether to restore currency to the item being edited - object restoreCurrencyTo = (editItem == CurrentItem) ? editItem : null; - - if (removeIndex >= 0 && IsGrouping) - { - // we can't just call RemoveFromSubgroups, as the group name - // for the item may have changed during the edit. - _group.RemoveItemFromSubgroupsByExhaustiveSearch(editItem); - if (PageSize > 0) - { - _temporaryGroup.RemoveItemFromSubgroupsByExhaustiveSearch(editItem); - } - } - - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - // only adjust currency and fire the event if we actually removed the item - if (removeIndex >= 0) - { - AdjustCurrencyForRemove(removeIndex); - - // raise the remove event so we can next insert it into the correct place - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - editItem, - removeIndex)); - } - - // check to see that the item will be added back in - bool passedFilter = PassesFilter(editItem); - - // if we removed all items from the current page, - // move to the previous page. we do not need to - // fire additional notifications, as moving the page will - // trigger a reset. - if (NeedToMoveToPreviousPage && !passedFilter) - { - MoveToPreviousPage(); - return; - } - - // next process adding it into the correct location - ProcessInsertToCollection(editItem, internalRemoveIndex); - - int pageStartIndex = PageIndex * PageSize; - int nextPageStartIndex = pageStartIndex + PageSize; - - if (IsGrouping) - { - int leafIndex = -1; - if (passedFilter && PageSize > 0) - { - _temporaryGroup.AddToSubgroups(editItem, false /*loading*/); - leafIndex = _temporaryGroup.LeafIndexOf(editItem); - } - - // if we are not paging, we should just be able to add the item. - // otherwise, we need to validate that it is within the current page. - if (passedFilter && (PageSize == 0 || - (pageStartIndex <= leafIndex && nextPageStartIndex > leafIndex))) - { - _group.AddToSubgroups(editItem, false /*loading*/); - int addIndex = IndexOf(editItem); - AdjustCurrencyForEdit(restoreCurrencyTo, addIndex); - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - editItem, - addIndex)); - } - else if (PageSize > 0) - { - int addIndex = -1; - if (passedFilter && leafIndex < pageStartIndex) - { - // if the item was added to an earlier page, then we need to bring - // in the item that would have been pushed down to this page - addIndex = pageStartIndex; - } - else if (!OnLastLocalPage && removeIndex >= 0) - { - // if the item was added to a later page, then we need to bring in the - // first item from the next page - addIndex = nextPageStartIndex - 1; - } - - object addItem = _temporaryGroup.LeafAt(addIndex); - if (addItem != null) - { - _group.AddToSubgroups(addItem, false /*loading*/); - addIndex = IndexOf(addItem); - AdjustCurrencyForEdit(restoreCurrencyTo, addIndex); - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - addItem, - addIndex)); - } - } - } - else - { - // if we are still within the view - int addIndex = IndexOf(editItem); - if (addIndex >= 0) - { - AdjustCurrencyForEdit(restoreCurrencyTo, addIndex); - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - editItem, - addIndex)); - } - else if (PageSize > 0) - { - // calculate whether the item was inserted into the previous page - bool insertedToPreviousPage = PassesFilter(editItem) && - (InternalIndexOf(editItem) < ConvertToInternalIndex(0)); - addIndex = insertedToPreviousPage ? 0 : Count - 1; - - // don't fire the event if we are on the last page - // and we don't have any items to bring in. - if (insertedToPreviousPage || (!OnLastLocalPage && removeIndex >= 0)) - { - AdjustCurrencyForEdit(restoreCurrencyTo, addIndex); - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - GetItemAt(addIndex), - addIndex)); - } - } - } - - // now raise currency changes at the end - RaiseCurrencyChanges(true, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - } - else if (!Contains(editItem)) - { - // if the item did not belong to the collection, add it - InternalList.Add(editItem); - } - } - - /// - /// Complete the transaction started by AddNew. We follow the WPF - /// convention in that the view's sort, filter, and paging - /// specifications (if any) are applied to the new item. - /// - //TODO Paging - public void CommitNew() - { - if (IsEditingItem) - { - throw new InvalidOperationException(GetOperationNotAllowedText(nameof(CommitNew), nameof(EditItem))); - } - - VerifyRefreshNotDeferred(); - - if (CurrentAddItem == null) - { - return; - } - - // End the AddNew transaction - object newItem = EndAddNew(false); - - // keep track of the current item - object previousCurrentItem = CurrentItem; - - // Modify our _trackingEnumerator so that it shows that our collection is "up to date" - // and will not refresh for now. - _trackingEnumerator = _sourceCollection.GetEnumerator(); - - if (UsesLocalArray) - { - // first remove the item from the array so that we can insert into the correct position - int removeIndex = Count - 1; - int internalIndex = _internalList.IndexOf(newItem); - _internalList.Remove(newItem); - - if (IsGrouping) - { - _group.RemoveSpecialItem(_group.Items.Count - 1, newItem, false); - if (PageSize > 0) - { - _temporaryGroup.RemoveSpecialItem(_temporaryGroup.Items.Count - 1, newItem, false); - } - } - - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - AdjustCurrencyForRemove(removeIndex); - - // raise the remove event so we can next insert it into the correct place - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - newItem, - removeIndex)); - - // check to see that the item will be added back in - bool passedFilter = PassesFilter(newItem); - - // next process adding it into the correct location - ProcessInsertToCollection(newItem, internalIndex); - - int pageStartIndex = PageIndex * PageSize; - int nextPageStartIndex = pageStartIndex + PageSize; - - if (IsGrouping) - { - int leafIndex = -1; - if (passedFilter && PageSize > 0) - { - _temporaryGroup.AddToSubgroups(newItem, false /*loading*/); - leafIndex = _temporaryGroup.LeafIndexOf(newItem); - } - - // if we are not paging, we should just be able to add the item. - // otherwise, we need to validate that it is within the current page. - if (passedFilter && (PageSize == 0 || - (pageStartIndex <= leafIndex && nextPageStartIndex > leafIndex))) - { - _group.AddToSubgroups(newItem, false /*loading*/); - int addIndex = IndexOf(newItem); - - // adjust currency to either the previous current item if possible - // or to the item at the end of the list where the new item was. - if (previousCurrentItem != null) - { - if (Contains(previousCurrentItem)) - { - AdjustCurrencyForAdd(previousCurrentItem, addIndex); - } - else - { - AdjustCurrencyForAdd(GetItemAt(Count - 1), addIndex); - } - } - - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - newItem, - addIndex)); - } - else - { - if (!passedFilter && (PageSize == 0 || OnLastLocalPage)) - { - AdjustCurrencyForRemove(removeIndex); - } - else if (PageSize > 0) - { - int addIndex = -1; - if (passedFilter && leafIndex < pageStartIndex) - { - // if the item was added to an earlier page, then we need to bring - // in the item that would have been pushed down to this page - addIndex = pageStartIndex; - } - else if (!OnLastLocalPage) - { - // if the item was added to a later page, then we need to bring in the - // first item from the next page - addIndex = nextPageStartIndex - 1; - } - - object addItem = _temporaryGroup.LeafAt(addIndex); - if (addItem != null) - { - _group.AddToSubgroups(addItem, false /*loading*/); - addIndex = IndexOf(addItem); - - // adjust currency to either the previous current item if possible - // or to the item at the end of the list where the new item was. - if (previousCurrentItem != null) - { - if (Contains(previousCurrentItem)) - { - AdjustCurrencyForAdd(previousCurrentItem, addIndex); - } - else - { - AdjustCurrencyForAdd(GetItemAt(Count - 1), addIndex); - } - } - - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - addItem, - addIndex)); - } - } - } - } - else - { - // if we are still within the view - int addIndex = IndexOf(newItem); - if (addIndex >= 0) - { - AdjustCurrencyForAdd(newItem, addIndex); - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - newItem, - addIndex)); - } - else - { - if (!passedFilter && (PageSize == 0 || OnLastLocalPage)) - { - AdjustCurrencyForRemove(removeIndex); - } - else if (PageSize > 0) - { - bool insertedToPreviousPage = InternalIndexOf(newItem) < ConvertToInternalIndex(0); - addIndex = insertedToPreviousPage ? 0 : Count - 1; - - // don't fire the event if we are on the last page - // and we don't have any items to bring in. - if (insertedToPreviousPage || !OnLastLocalPage) - { - AdjustCurrencyForAdd(null, addIndex); - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - GetItemAt(addIndex), - addIndex)); - } - } - } - } - - // we want to fire the current changed event, even if we kept - // the same current item and position, since the item was - // removed/added back to the collection - RaiseCurrencyChanges(true, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - } - } - - /// - /// Return true if the item belongs to this view. No assumptions are - /// made about the item. This method will behave similarly to IList.Contains(). - /// If the caller knows that the item belongs to the - /// underlying collection, it is more efficient to call PassesFilter. - /// - /// The item we are checking to see whether it is within the collection - /// Boolean value of whether or not the collection contains the item - public bool Contains(object item) - { - EnsureCollectionInSync(); - VerifyRefreshNotDeferred(); - return IndexOf(item) >= 0; - } - - /// - /// Enter a Defer Cycle. - /// Defer cycles are used to coalesce changes to the ICollectionView. - /// - /// IDisposable used to notify that we no longer need to defer, when we dispose - public IDisposable DeferRefresh() - { - if (IsAddingNew || IsEditingItem) - { - throw new InvalidOperationException(GetOperationNotAllowedDuringAddOrEditText(nameof(DeferRefresh))); - } - - ++_deferLevel; - return new DeferHelper(this); - } - - /// - /// Begins an editing transaction on the given item. The transaction is - /// completed by calling either CommitEdit or CancelEdit. Any changes made - /// to the item during the transaction are considered "pending", provided - /// that the view supports the notion of "pending changes" for the given item. - /// - /// Item we want to edit - public void EditItem(object item) - { - VerifyRefreshNotDeferred(); - - if (IsAddingNew) - { - if (Object.Equals(item, CurrentAddItem)) - { - // EditItem(newItem) is a no-op - return; - } - - // implicitly close a previous AddNew - CommitNew(); - } - - // implicitly close a previous EditItem transaction - CommitEdit(); - - CurrentEditItem = item; - - if (item is IEditableObject ieo) - { - ieo.BeginEdit(); - } - } - - /// - /// Implementation of IEnumerable.GetEnumerator(). - /// This provides a way to enumerate the members of the collection - /// without changing the currency. - /// - /// IEnumerator for the collection - //TODO Paging - public IEnumerator GetEnumerator() - { - EnsureCollectionInSync(); - VerifyRefreshNotDeferred(); - - if (IsGrouping) - { - return RootGroup?.GetLeafEnumerator(); - } - - // if we are paging - if (PageSize > 0) - { - List list = new List(); - - // if we are in the middle of asynchronous load - if (PageIndex < 0) - { - return list.GetEnumerator(); - } - - for (int index = _pageSize * PageIndex; - index < (int)Math.Min(_pageSize * (PageIndex + 1), InternalList.Count); - index++) - { - list.Add(InternalList[index]); - } - - return new NewItemAwareEnumerator(this, list.GetEnumerator(), CurrentAddItem); - } - else - { - return new NewItemAwareEnumerator(this, InternalList.GetEnumerator(), CurrentAddItem); - } - } - - /// - /// Interface Implementation for GetEnumerator() - /// - /// IEnumerator that we get from our internal collection - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Retrieve item at the given zero-based index in this DataGridCollectionView, after the source collection - /// is filtered, sorted, and paged. - /// - /// - /// Thrown if index is out of range - /// - /// Index of the item we want to retrieve - /// Item at specified index - public object GetItemAt(int index) - { - EnsureCollectionInSync(); - VerifyRefreshNotDeferred(); - - // for indices larger than the count - if (index >= Count || index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (IsGrouping) - { - return RootGroup?.LeafAt(_isUsingTemporaryGroup ? ConvertToInternalIndex(index) : index); - } - - if (IsAddingNew && UsesLocalArray && index == Count - 1) - { - return CurrentAddItem; - } - - return InternalItemAt(ConvertToInternalIndex(index)); - } - - /// - /// Return the index where the given item appears, or -1 if doesn't appear. - /// - /// Item we are searching for - /// Index of specified item - //TODO Paging - public int IndexOf(object item) - { - EnsureCollectionInSync(); - VerifyRefreshNotDeferred(); - - if (IsGrouping) - { - return RootGroup?.LeafIndexOf(item) ?? -1; - } - if (IsAddingNew && Object.Equals(item, CurrentAddItem) && UsesLocalArray) - { - return Count - 1; - } - - int internalIndex = InternalIndexOf(item); - - if (PageSize > 0 && internalIndex != -1) - { - if ((internalIndex >= (PageIndex * _pageSize)) && - (internalIndex < ((PageIndex + 1) * _pageSize))) - { - return internalIndex - (PageIndex * _pageSize); - } - else - { - return -1; - } - } - else - { - return internalIndex; - } - } - - /// - /// Move to the given item. - /// - /// Item we want to move the currency to - /// Whether the operation was successful - public bool MoveCurrentTo(object item) - { - VerifyRefreshNotDeferred(); - - // if already on item, don't do anything - if (Object.Equals(CurrentItem, item)) - { - // also check that we're not fooled by a false null currentItem - if (item != null || IsCurrentInView) - { - return IsCurrentInView; - } - } - - // if the item is not found IndexOf() will return -1, and - // the MoveCurrentToPosition() below will move current to BeforeFirst - // The IndexOf function takes into account paging, filtering, and sorting - return MoveCurrentToPosition(IndexOf(item)); - } - - /// - /// Move to the first item. - /// - /// Whether the operation was successful - public bool MoveCurrentToFirst() - { - VerifyRefreshNotDeferred(); - - return MoveCurrentToPosition(0); - } - - /// - /// Move to the last item. - /// - /// Whether the operation was successful - public bool MoveCurrentToLast() - { - VerifyRefreshNotDeferred(); - - int index = Count - 1; - - return MoveCurrentToPosition(index); - } - - /// - /// Move to the next item. - /// - /// Whether the operation was successful - public bool MoveCurrentToNext() - { - VerifyRefreshNotDeferred(); - - int index = CurrentPosition + 1; - - if (index <= Count) - { - return MoveCurrentToPosition(index); - } - else - { - return false; - } - } - - /// - /// Move CurrentItem to this index - /// - /// Position we want to move the currency to - /// True if the resulting CurrentItem is an item within the view; otherwise False - public bool MoveCurrentToPosition(int position) - { - VerifyRefreshNotDeferred(); - - // We want to allow the user to set the currency to just - // beyond the last item. EnumerableCollectionView in WPF - // also checks (position > Count) though the ListCollectionView - // looks for (position >= Count). - if (position < -1 || position > Count) - { - throw new ArgumentOutOfRangeException(nameof(position)); - } - - if ((position != CurrentPosition || !IsCurrentInSync) - && OkToChangeCurrent()) - { - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - SetCurrentToPosition(position); - OnCurrentChanged(); - - if (IsCurrentAfterLast != oldIsCurrentAfterLast) - { - OnPropertyChanged(nameof(IsCurrentAfterLast)); - } - - if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst) - { - OnPropertyChanged(nameof(IsCurrentBeforeFirst)); - } - - OnPropertyChanged(nameof(CurrentPosition)); - OnPropertyChanged(nameof(CurrentItem)); - } - - return IsCurrentInView; - } - - /// - /// Move to the previous item. - /// - /// Whether the operation was successful - public bool MoveCurrentToPrevious() - { - VerifyRefreshNotDeferred(); - - int index = CurrentPosition - 1; - - if (index >= -1) - { - return MoveCurrentToPosition(index); - } - else - { - return false; - } - } - - /// - /// Moves to the first page. - /// - /// Whether or not the move was successful. - //TODO Paging - public bool MoveToFirstPage() - { - return MoveToPage(0); - } - - /// - /// Moves to the last page. - /// The move is only attempted when TotalItemCount is known. - /// - /// Whether or not the move was successful. - //TODO Paging - public bool MoveToLastPage() - { - if (TotalItemCount != -1 && PageSize > 0) - { - return MoveToPage(PageCount - 1); - } - else - { - return false; - } - } - - /// - /// Moves to the page after the current page we are on. - /// - /// Whether or not the move was successful. - //TODO Paging - public bool MoveToNextPage() - { - return MoveToPage(_pageIndex + 1); - } - - /// - /// Requests a page move to page . - /// - /// Index of the target page - /// Whether or not the move was successfully initiated. - //TODO Paging - public bool MoveToPage(int pageIndex) - { - // Boundary checks for negative pageIndex - if (pageIndex < -1) - { - return false; - } - - // if the Refresh is deferred, cache the requested PageIndex so that we - // can move to the desired page when EndDefer is called. - if (IsRefreshDeferred) - { - // set cached value and flag so that we move to the page on EndDefer - _cachedPageIndex = pageIndex; - SetFlag(CollectionViewFlags.IsMoveToPageDeferred, true); - return false; - } - - // check for invalid pageIndex - if (pageIndex == -1 && PageSize > 0) - { - return false; - } - - // Check if the target page is out of bound, or equal to the current page - if (pageIndex >= PageCount || _pageIndex == pageIndex) - { - return false; - } - - // Check with the ICollectionView.CurrentChanging listeners if it's OK to move - // on to another page - if (!OkToChangeCurrent()) - { - return false; - } - - if (RaisePageChanging(pageIndex) && pageIndex != -1) - { - // Page move was cancelled. Abort the move, but only if the target index isn't -1. - return false; - } - - // Check if there is a current edited or new item so changes can be committed first. - if (CurrentAddItem != null || CurrentEditItem != null) - { - // Remember current currency values for upcoming OnPropertyChanged notifications - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - // Currently CommitNew()/CommitEdit()/CancelNew()/CancelEdit() can't handle committing or - // cancelling an item that is no longer on the current page. That's acceptable and means that - // the potential _newItem or _editItem needs to be committed before this page move. - // The reason why we temporarily reset currency here is to give a chance to the bound - // controls to commit or cancel their potential edits/addition. The DataForm calls ForceEndEdit() - // for example as a result of changing currency. - SetCurrentToPosition(-1); - RaiseCurrencyChanges(true /*fireChangedEvent*/, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - - // If the bound controls did not successfully end their potential item editing/addition, the - // page move needs to be aborted. - if (CurrentAddItem != null || CurrentEditItem != null) - { - // Since PageChanging was raised and not cancelled, a PageChanged notification needs to be raised - // even though the PageIndex actually did not change. - RaisePageChanged(); - - // Restore original currency - Debug.Assert(CurrentItem == null, "Unexpected CurrentItem != null"); - Debug.Assert(CurrentPosition == -1, "Unexpected CurrentPosition != -1"); - Debug.Assert(IsCurrentBeforeFirst, "Unexpected IsCurrentBeforeFirst == false"); - Debug.Assert(!IsCurrentAfterLast, "Unexpected IsCurrentAfterLast == true"); - - SetCurrentToPosition(oldCurrentPosition); - RaiseCurrencyChanges(false /*fireChangedEvent*/, null /*oldCurrentItem*/, -1 /*oldCurrentPosition*/, - true /*oldIsCurrentBeforeFirst*/, false /*oldIsCurrentAfterLast*/); - - return false; - } - - // Finally raise a CurrentChanging notification for the upcoming currency change - // that will occur in CompletePageMove(pageIndex). - OnCurrentChanging(); - } - - IsPageChanging = true; - CompletePageMove(pageIndex); - - return true; - } - - /// - /// Moves to the page before the current page we are on. - /// - /// Whether or not the move was successful. - //TODO Paging - public bool MoveToPreviousPage() - { - return MoveToPage(_pageIndex - 1); - } - - /// - /// Return true if the item belongs to this view. The item is assumed to belong to the - /// underlying DataCollection; this method merely takes filters into account. - /// It is commonly used during collection-changed notifications to determine if the added/removed - /// item requires processing. - /// Returns true if no filter is set on collection view. - /// - /// The item to compare against the Filter - /// Whether the item passes the filter - public bool PassesFilter(object item) - { - if (Filter != null) - { - return Filter(item); - } - - return true; - } - - /// - /// Re-create the view, using any SortDescriptions and/or Filters. - /// - public void Refresh() - { - if (this is IDataGridEditableCollectionView ecv && (ecv.IsAddingNew || ecv.IsEditingItem)) - { - throw new InvalidOperationException(GetOperationNotAllowedDuringAddOrEditText(nameof(Refresh))); - } - - RefreshInternal(); - } - - /// - /// Remove the given item from the underlying collection. It - /// needs to be in the current filtered, sorted, and paged view - /// to call - /// - /// Item we want to remove - public void Remove(object item) - { - int index = IndexOf(item); - if (index >= 0) - { - RemoveAt(index); - } - } - - /// - /// Remove the item at the given index from the underlying collection. - /// The index is interpreted with respect to the view (filtered, sorted, - /// and paged list). - /// - /// Index of the item we want to remove - //TODO Paging - public void RemoveAt(int index) - { - if (index < 0 || index >= Count) - { - throw new ArgumentOutOfRangeException(nameof(index), "Index was out of range. Must be non-negative and less than the size of the collection."); - } - - if (IsEditingItem || IsAddingNew) - { - throw new InvalidOperationException(GetOperationNotAllowedDuringAddOrEditText(nameof(RemoveAt))); - } - else if (!CanRemove) - { - throw new InvalidOperationException("Remove/RemoveAt is not supported."); - } - - VerifyRefreshNotDeferred(); - - // convert the index from "view-relative" to "list-relative" - object item = GetItemAt(index); - - // before we remove the item, see if we are not on the last page - // and will have to bring in a new item to replace it - bool replaceItem = PageSize > 0 && !OnLastLocalPage; - - try - { - // temporarily disable the CollectionChanged event - // handler so filtering, sorting, or grouping - // doesn't get applied yet - SetFlag(CollectionViewFlags.ShouldProcessCollectionChanged, false); - - if (SourceList != null) - { - SourceList.Remove(item); - } - } - finally - { - SetFlag(CollectionViewFlags.ShouldProcessCollectionChanged, true); - } - - // Modify our _trackingEnumerator so that it shows that our collection is "up to date" - // and will not refresh for now. - _trackingEnumerator = _sourceCollection.GetEnumerator(); - - Debug.Assert(index == IndexOf(item), "IndexOf returned unexpected value"); - - // remove the item from the internal list - _internalList.Remove(item); - - if (IsGrouping) - { - if (PageSize > 0) - { - _temporaryGroup.RemoveFromSubgroups(item); - } - _group.RemoveFromSubgroups(item); - } - - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - AdjustCurrencyForRemove(index); - - // fire remove notification - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - item, - index)); - - RaiseCurrencyChanges(false, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - - // if we removed all items from the current page, - // move to the previous page. we do not need to - // fire additional notifications, as moving the page will - // trigger a reset. - if (NeedToMoveToPreviousPage) - { - MoveToPreviousPage(); - return; - } - - // if we are paging, we may have to fire another notification for the item - // that needs to replace the one we removed on this page. - if (replaceItem) - { - // we first need to add the item into the current group - if (IsGrouping) - { - object newItem = _temporaryGroup.LeafAt((PageSize * (PageIndex + 1)) - 1); - if (newItem != null) - { - _group.AddToSubgroups(newItem, loading: false); - } - } - - // fire the add notification - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - GetItemAt(PageSize - 1), - PageSize - 1)); - } - } - - /// - /// Helper for SortList to handle nested properties (e.g. Address.Street) - /// - /// parent object - /// property names path - /// property type that we want to check for - /// child object - private static object InvokePath(object item, string propertyPath, Type propertyType) - { - object propertyValue = TypeHelper.GetNestedPropertyValue(item, propertyPath, propertyType, out Exception exception); - if (exception != null) - { - throw exception; - } - return propertyValue; - } - - /// - /// Fix up CurrentPosition and CurrentItem after a collection change - /// - /// Item that we want to set currency to - /// Index of item involved in the collection change - private void AdjustCurrencyForAdd(object newCurrentItem, int index) - { - if (newCurrentItem != null) - { - int newItemIndex = IndexOf(newCurrentItem); - - // if we already have the correct currency set, we don't - // want to unnecessarily fire events - if (newItemIndex >= 0 && (newItemIndex != CurrentPosition || !IsCurrentInSync)) - { - OnCurrentChanging(); - SetCurrent(newCurrentItem, newItemIndex); - } - return; - } - - if (Count == 1) - { - if (CurrentItem != null || CurrentPosition != -1) - { - // fire current changing notification - OnCurrentChanging(); - } - - // added first item; set current at BeforeFirst - SetCurrent(null, -1); - } - else if (index <= CurrentPosition) - { - // fire current changing notification - OnCurrentChanging(); - - // adjust current index if insertion is earlier - int newPosition = CurrentPosition + 1; - if (newPosition >= Count) - { - // if currency was on last item and it got shifted up, - // keep currency on last item. - newPosition = Count - 1; - } - SetCurrent(GetItemAt(newPosition), newPosition); - } - } - - /// - /// Fix up CurrentPosition and CurrentItem after a collection change - /// - /// Item that we want to set currency to - /// Index of item involved in the collection change - private void AdjustCurrencyForEdit(object newCurrentItem, int index) - { - if (newCurrentItem != null && IndexOf(newCurrentItem) >= 0) - { - OnCurrentChanging(); - SetCurrent(newCurrentItem, IndexOf(newCurrentItem)); - return; - } - - if (index <= CurrentPosition) - { - // fire current changing notification - OnCurrentChanging(); - - // adjust current index if insertion is earlier - int newPosition = CurrentPosition + 1; - if (newPosition < Count) - { - // CurrentItem might be out of sync if underlying list is not INCC - // or if this Add is the result of a Replace (Rem + Add) - SetCurrent(GetItemAt(newPosition), newPosition); - } - else - { - SetCurrent(null, Count); - } - } - } - - /// - /// Fix up CurrentPosition and CurrentItem after a collection change - /// The index can be -1 if the item was removed from a previous page - /// - /// Index of item involved in the collection change - private void AdjustCurrencyForRemove(int index) - { - // adjust current index if deletion is earlier - if (index < CurrentPosition) - { - // fire current changing notification - OnCurrentChanging(); - - SetCurrent(CurrentItem, CurrentPosition - 1); - } - - // adjust current index if > Count - if (CurrentPosition >= Count) - { - // fire current changing notification - OnCurrentChanging(); - - SetCurrentToPosition(Count - 1); - } - - // make sure that current position and item are in sync - if (!IsCurrentInSync) - { - // fire current changing notification - OnCurrentChanging(); - - SetCurrentToPosition(CurrentPosition); - } - } - - /// - /// Returns true if specified flag in flags is set. - /// - /// Flag we are checking for - /// Whether the specified flag is set - private bool CheckFlag(CollectionViewFlags flags) - { - return _flags.HasAllFlags(flags); - } - - /// - /// Called to complete the page move operation to set the - /// current page index. - /// - /// Final page index - //TODO Paging - private void CompletePageMove(int pageIndex) - { - Debug.Assert(_pageIndex != pageIndex, "Unexpected _pageIndex == pageIndex"); - - // to see whether or not to fire an OnPropertyChanged - int oldCount = Count; - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - _pageIndex = pageIndex; - - // update the groups - if (IsGrouping && PageSize > 0) - { - PrepareGroupsForCurrentPage(); - } - - // update currency - if (Count >= 1) - { - SetCurrent(GetItemAt(0), 0); - } - else - { - SetCurrent(null, -1); - } - - IsPageChanging = false; - OnPropertyChanged(nameof(PageIndex)); - RaisePageChanged(); - - // if the count has changed - if (Count != oldCount) - { - OnPropertyChanged(nameof(Count)); - } - - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Reset)); - - // Always raise CurrentChanged since the calling method MoveToPage(pageIndex) raised CurrentChanging. - RaiseCurrencyChanges(true /*fireChangedEvent*/, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - } - - /// - /// Convert a value for the index passed in to the index it would be - /// relative to the InternalIndex property. - /// - /// Index to convert - /// Value for the InternalIndex - //TODO Paging - private int ConvertToInternalIndex(int index) - { - Debug.Assert(index > -1, "Unexpected index == -1"); - if (PageSize > 0) - { - return (_pageSize * PageIndex) + index; - } - else - { - return index; - } - } - - /// - /// Copy all items from the source collection to the internal list for processing. - /// - private void CopySourceToInternalList() - { - _internalList = new List(); - - IEnumerator enumerator = SourceCollection.GetEnumerator(); - - while (enumerator.MoveNext()) - { - _internalList.Add(enumerator.Current); - } - } - - /// - /// Common functionality used by CommitNew, CancelNew, and when the - /// new item is removed by Remove or Refresh. - /// - /// Whether we canceled the add - /// The new item we ended adding - private object EndAddNew(bool cancel) - { - object newItem = CurrentAddItem; - - CurrentAddItem = null; // leave "adding-new" mode - - if (newItem is IEditableObject ieo) - { - if (cancel) - { - ieo.CancelEdit(); - } - else - { - ieo.EndEdit(); - } - } - - return newItem; - } - - /// - /// Subtracts from the deferLevel counter and calls Refresh() if there are no other defers - /// - private void EndDefer() - { - --_deferLevel; - - if (_deferLevel == 0) - { - if (CheckFlag(CollectionViewFlags.IsUpdatePageSizeDeferred)) - { - SetFlag(CollectionViewFlags.IsUpdatePageSizeDeferred, false); - PageSize = _cachedPageSize; - } - - if (CheckFlag(CollectionViewFlags.IsMoveToPageDeferred)) - { - SetFlag(CollectionViewFlags.IsMoveToPageDeferred, false); - MoveToPage(_cachedPageIndex); - _cachedPageIndex = -1; - } - - if (CheckFlag(CollectionViewFlags.NeedsRefresh)) - { - Refresh(); - } - } - } - - /// - /// Makes sure that the ItemConstructor is set for the correct type - /// - private void EnsureItemConstructor() - { - if (!_itemConstructorIsValid) - { - Type itemType = ItemType; - if (itemType != null) - { - _itemConstructor = itemType.GetConstructor(Type.EmptyTypes); - _itemConstructorIsValid = true; - } - } - } - - /// - /// If the IEnumerable has changed, bring the collection up to date. - /// (This isn't necessary if the IEnumerable is also INotifyCollectionChanged - /// because we keep the collection in sync incrementally.) - /// - private void EnsureCollectionInSync() - { - // if the IEnumerable is not a INotifyCollectionChanged - if (_pollForChanges) - { - try - { - _trackingEnumerator.MoveNext(); - } - catch (InvalidOperationException) - { - // When the collection has been modified, calling MoveNext() - // on the enumerator throws an InvalidOperationException, stating - // that the collection has been modified. Therefore, we know when - // to update our internal collection. - _trackingEnumerator = SourceCollection.GetEnumerator(); - RefreshOrDefer(); - } - } - } - - /// - /// Helper function used to determine the type of an item - /// - /// Whether we should use a representative item - /// The type of the items in the collection - private Type GetItemType(bool useRepresentativeItem) - { - Type collectionType = SourceCollection.GetType(); - Type[] interfaces = collectionType.GetInterfaces(); - - // Look for IEnumerable. All generic collections should implement - // We loop through the interface list, rather than call - // GetInterface(IEnumerableT), so that we handle an ambiguous match - // (by using the first match) without an exception. - for (int i = 0; i < interfaces.Length; ++i) - { - Type interfaceType = interfaces[i]; - if (interfaceType.Name == typeof(IEnumerable<>).Name) - { - // found IEnumerable<>, extract T - Type[] typeParameters = interfaceType.GetGenericArguments(); - if (typeParameters.Length == 1) - { - return typeParameters[0]; - } - } - } - - // No generic information found. Use a representative item instead. - if (useRepresentativeItem) - { - // get type of a representative item - object item = GetRepresentativeItem(); - if (item != null) - { - return item.GetType(); - } - } - - return null; - } - - /// - /// Gets a representative item from the collection - /// - /// An item that can represent the collection - private object GetRepresentativeItem() - { - if (IsEmpty) - { - return null; - } - - IEnumerator enumerator = GetEnumerator(); - while (enumerator.MoveNext()) - { - object item = enumerator.Current; - // Since this collection view does not support a NewItemPlaceholder, - // simply return the first non-null item. - if (item != null) - { - return item; - } - } - - return null; - } - - /// - /// Return index of item in the internal list. - /// - /// The item we are checking - /// Integer value on where in the InternalList the object is located - private int InternalIndexOf(object item) - { - return InternalList.IndexOf(item); - } - - /// - /// Return item at the given index in the internal list. - /// - /// The index we are checking - /// The item at the specified index - private object InternalItemAt(int index) - { - if (index >= 0 && index < InternalList.Count) - { - return InternalList[index]; - } - else - { - return null; - } - } - - /// - /// Ask listeners (via ICollectionView.CurrentChanging event) if it's OK to change currency - /// - /// False if a listener cancels the change, True otherwise - private bool OkToChangeCurrent() - { - DataGridCurrentChangingEventArgs args = new DataGridCurrentChangingEventArgs(); - OnCurrentChanging(args); - return !args.Cancel; - } - - /// - /// Notify listeners that this View has changed - /// - /// - /// CollectionViews (and sub-classes) should take their filter/sort/grouping/paging - /// into account before calling this method to forward CollectionChanged events. - /// - /// - /// The NotifyCollectionChangedEventArgs to be passed to the EventHandler - /// - //TODO Paging - private void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - - unchecked - { - // invalidate enumerators because of a change - ++_timestamp; - } - - if (CollectionChanged != null) - { - if (args.Action != NotifyCollectionChangedAction.Add || PageSize == 0 || args.NewStartingIndex < Count) - { - CollectionChanged(this, args); - } - } - - // Collection changes change the count unless an item is being - // replaced within the collection. - if (args.Action != NotifyCollectionChangedAction.Replace) - { - OnPropertyChanged(nameof(Count)); - } - - bool listIsEmpty = IsEmpty; - if (listIsEmpty != CheckFlag(CollectionViewFlags.CachedIsEmpty)) - { - SetFlag(CollectionViewFlags.CachedIsEmpty, listIsEmpty); - OnPropertyChanged(nameof(IsEmpty)); - } - } - - /// - /// Raises the CurrentChanged event - /// - private void OnCurrentChanged() - { - if (CurrentChanged != null && _currentChangedMonitor.Enter()) - { - using (_currentChangedMonitor) - { - CurrentChanged(this, EventArgs.Empty); - } - } - } - - /// - /// Raise a CurrentChanging event that is not cancelable. - /// This is called by CollectionChanges (Add, Remove, and Refresh) that - /// affect the CurrentItem. - /// - /// - /// This CurrentChanging event cannot be canceled. - /// - private void OnCurrentChanging() - { - OnCurrentChanging(uncancelableCurrentChangingEventArgs); - } - - /// - /// Raises the CurrentChanging event - /// - /// - /// CancelEventArgs used by the consumer of the event. args.Cancel will - /// be true after this call if the CurrentItem should not be changed for - /// any reason. - /// - /// - /// This CurrentChanging event cannot be canceled. - /// - private void OnCurrentChanging(DataGridCurrentChangingEventArgs args) - { - if (args == null) - { - throw new ArgumentNullException(nameof(args)); - } - - if (_currentChangedMonitor.Busy) - { - if (args.IsCancelable) - { - args.Cancel = true; - } - - return; - } - - CurrentChanging?.Invoke(this, args); - } - - /// - /// GroupBy changed handler - /// - /// CollectionViewGroup whose GroupBy has changed - /// Arguments for the NotifyCollectionChanged event - private void OnGroupByChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (IsAddingNew || IsEditingItem) - { - throw new InvalidOperationException(GetOperationNotAllowedDuringAddOrEditText("Grouping")); - } - - RefreshOrDefer(); - } - - /// - /// GroupDescription changed handler - /// - /// CollectionViewGroup whose GroupDescription has changed - /// Arguments for the GroupDescriptionChanged event - //TODO Paging - private void OnGroupDescriptionChanged(object sender, EventArgs e) - { - if (IsAddingNew || IsEditingItem) - { - throw new InvalidOperationException(GetOperationNotAllowedDuringAddOrEditText("Grouping")); - } - - // we want to make sure that the data is refreshed before we try to move to a page - // since the refresh would take care of the filtering, sorting, and grouping. - RefreshOrDefer(); - - if (PageSize > 0) - { - if (IsRefreshDeferred) - { - // set cached value and flag so that we move to first page on EndDefer - _cachedPageIndex = 0; - SetFlag(CollectionViewFlags.IsMoveToPageDeferred, true); - } - else - { - MoveToFirstPage(); - } - } - } - - /// - /// Raises a PropertyChanged event. - /// - /// PropertyChangedEventArgs for this change - private void OnPropertyChanged(PropertyChangedEventArgs e) - { - PropertyChanged?.Invoke(this, e); - } - - /// - /// Helper to raise a PropertyChanged event. - /// - /// Property name for the property that changed - private void OnPropertyChanged(string propertyName) - { - OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); - } - - /// - /// Sets up the ActiveComparer for the CollectionViewGroupRoot specified - /// - /// The CollectionViewGroupRoot - private void PrepareGroupingComparer(CollectionViewGroupRoot groupRoot) - { - if (groupRoot == _temporaryGroup || PageSize == 0) - { - if (groupRoot.ActiveComparer is DataGridCollectionViewGroupInternal.ListComparer listComparer) - { - listComparer.ResetList(InternalList); - } - else - { - groupRoot.ActiveComparer = new DataGridCollectionViewGroupInternal.ListComparer(InternalList); - } - } - else if (groupRoot == _group) - { - // create the new comparer based on the current _temporaryGroup - groupRoot.ActiveComparer = new DataGridCollectionViewGroupInternal.CollectionViewGroupComparer(_temporaryGroup); - } - } - - /// - /// Use the GroupDescriptions to place items into their respective groups. - /// This assumes that there is no paging, so we just group the entire collection - /// of items that the CollectionView holds. - /// - private void PrepareGroups() - { - // we should only use this method if we aren't paging - Debug.Assert(PageSize == 0, "Unexpected PageSize != 0"); - - _group.Clear(); - _group.Initialize(); - - _group.IsDataInGroupOrder = CheckFlag(CollectionViewFlags.IsDataInGroupOrder); - - // set to false so that we access internal collection items - // instead of the group items, as they have been cleared - _isGrouping = false; - - if (_group.GroupDescriptions.Count > 0) - { - for (int num = 0, count = _internalList.Count; num < count; ++num) - { - object item = _internalList[num]; - if (item != null && (!IsAddingNew || !object.Equals(CurrentAddItem, item))) - { - _group.AddToSubgroups(item, loading: true); - } - } - if (IsAddingNew) - { - _group.InsertSpecialItem(_group.Items.Count, CurrentAddItem, true); - } - } - - _isGrouping = _group.GroupBy != null; - - // now we set the value to false, so that subsequent adds will insert - // into the correct groups. - _group.IsDataInGroupOrder = false; - - // reset the grouping comparer - PrepareGroupingComparer(_group); - } - - /// - /// Use the GroupDescriptions to place items into their respective groups. - /// Because of the fact that we have paging, it is possible that we are only - /// going to need a subset of the items to be displayed. However, before we - /// actually group the entire collection, we can't display the items in the - /// correct order. We therefore want to just create a temporary group with - /// the entire collection, and then using this data we can create the group - /// that is exposed with just the items we need. - /// - private void PrepareTemporaryGroups() - { - _temporaryGroup = new CollectionViewGroupRoot(this, CheckFlag(CollectionViewFlags.IsDataInGroupOrder)); - - foreach (var gd in _group.GroupDescriptions) - { - _temporaryGroup.GroupDescriptions.Add(gd); - } - - _temporaryGroup.Initialize(); - - // set to false so that we access internal collection items - // instead of the group items, as they have been cleared - _isGrouping = false; - - if (_temporaryGroup.GroupDescriptions.Count > 0) - { - for (int num = 0, count = _internalList.Count; num < count; ++num) - { - object item = _internalList[num]; - if (item != null && (!IsAddingNew || !object.Equals(CurrentAddItem, item))) - { - _temporaryGroup.AddToSubgroups(item, loading: true); - } - } - if (IsAddingNew) - { - _temporaryGroup.InsertSpecialItem(_temporaryGroup.Items.Count, CurrentAddItem, true); - } - } - - _isGrouping = _temporaryGroup.GroupBy != null; - - // reset the grouping comparer - PrepareGroupingComparer(_temporaryGroup); - } - - /// - /// Update our Groups private accessor to point to the subset of data - /// covered by the current page, or to display the entire group if paging is not - /// being used. - /// - //TODO Paging - private void PrepareGroupsForCurrentPage() - { - _group.Clear(); - _group.Initialize(); - - // set to indicate that we will be pulling data from the temporary group data - _isUsingTemporaryGroup = true; - - // since we are getting our data from the temporary group, it should - // already be in group order - _group.IsDataInGroupOrder = true; - _group.ActiveComparer = null; - - if (GroupDescriptions.Count > 0) - { - for (int num = 0, count = Count; num < count; ++num) - { - object item = GetItemAt(num); - if (item != null && (!IsAddingNew || !object.Equals(CurrentAddItem, item))) - { - _group.AddToSubgroups(item, loading: true); - } - } - if (IsAddingNew) - { - _group.InsertSpecialItem(_group.Items.Count, CurrentAddItem, true); - } - } - - // set flag to indicate that we do not need to access the temporary data any longer - _isUsingTemporaryGroup = false; - - // now we set the value to false, so that subsequent adds will insert - // into the correct groups. - _group.IsDataInGroupOrder = false; - - // reset the grouping comparer - PrepareGroupingComparer(_group); - - _isGrouping = _group.GroupBy != null; - } - - /// - /// Create, filter and sort the local index array. - /// called from Refresh(), override in derived classes as needed. - /// - /// new IEnumerable to associate this view with - /// new local array to use for this view - private IList PrepareLocalArray(IEnumerable enumerable) - { - Debug.Assert(enumerable != null, "Input list to filter/sort should not be null"); - - // filter the collection's array into the local array - List localList = new List(); - - foreach (object item in enumerable) - { - if (Filter == null || PassesFilter(item)) - { - localList.Add(item); - } - } - - // sort the local array - if (!CheckFlag(CollectionViewFlags.IsDataSorted) && SortDescriptions.Count > 0) - { - localList = SortList(localList); - } - - return localList; - } - - /// - /// Process an Add operation from an INotifyCollectionChanged event handler. - /// - /// Item added to the source collection - /// Index item was added into - //TODO Paging - private void ProcessAddEvent(object addedItem, int addIndex) - { - // item to fire remove notification for if necessary - object removeNotificationItem = null; - if (PageSize > 0 && !IsGrouping) - { - removeNotificationItem = (Count == PageSize) ? - GetItemAt(PageSize - 1) : null; - } - - // process the add by filtering and sorting the item - ProcessInsertToCollection( - addedItem, - addIndex); - - // next check if we need to add an item into the current group - bool needsGrouping = false; - if (Count == 1 && GroupDescriptions.Count > 0) - { - // if this is the first item being added - // we want to setup the groups with the - // correct element type comparer - if (PageSize > 0) - { - PrepareGroupingComparer(_temporaryGroup); - } - PrepareGroupingComparer(_group); - } - - if (IsGrouping) - { - int leafIndex = -1; - - if (PageSize > 0) - { - _temporaryGroup.AddToSubgroups(addedItem, false /*loading*/); - leafIndex = _temporaryGroup.LeafIndexOf(addedItem); - } - - // if we are not paging, we should just be able to add the item. - // otherwise, we need to validate that it is within the current page. - if (PageSize == 0 || (PageIndex + 1) * PageSize > leafIndex) - { - needsGrouping = true; - - int pageStartIndex = PageIndex * PageSize; - - // if the item was inserted on a previous page - if (pageStartIndex > leafIndex && PageSize > 0) - { - addedItem = _temporaryGroup.LeafAt(pageStartIndex); - } - - // if we're grouping and have more items than the - // PageSize will allow, remove the last item - if (PageSize > 0 && _group.ItemCount == PageSize) - { - removeNotificationItem = _group.LeafAt(PageSize - 1); - _group.RemoveFromSubgroups(removeNotificationItem); - } - } - } - - // if we are paging, we may have to fire another notification for the item - // that needs to be removed for the one we added on this page. - if (PageSize > 0 && !OnLastLocalPage && - (((IsGrouping && removeNotificationItem != null) || - (!IsGrouping && (PageIndex + 1) * PageSize > InternalIndexOf(addedItem))))) - { - if (removeNotificationItem != null && removeNotificationItem != addedItem) - { - AdjustCurrencyForRemove(PageSize - 1); - - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - removeNotificationItem, - PageSize - 1)); - } - } - - // if we need to add the item into the current group - // that will be displayed - if (needsGrouping) - { - this._group.AddToSubgroups(addedItem, false /*loading*/); - } - - int addedIndex = IndexOf(addedItem); - - // if the item is within the current page - if (addedIndex >= 0) - { - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - AdjustCurrencyForAdd(null, addedIndex); - - // fire add notification - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - addedItem, - addedIndex)); - - RaiseCurrencyChanges(false, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - } - else if (PageSize > 0) - { - // otherwise if the item was added into a previous page - int internalIndex = InternalIndexOf(addedItem); - - if (internalIndex < ConvertToInternalIndex(0)) - { - // fire add notification for item pushed in - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - GetItemAt(0), - 0)); - } - } - } - - /// - /// Process CollectionChanged event on source collection - /// that implements INotifyCollectionChanged. - /// - /// - /// The NotifyCollectionChangedEventArgs to be processed. - /// - private void ProcessCollectionChanged(NotifyCollectionChangedEventArgs args) - { - // if we do not want to handle the CollectionChanged event, return - if (!CheckFlag(CollectionViewFlags.ShouldProcessCollectionChanged)) - { - return; - } - - if (args.Action == NotifyCollectionChangedAction.Reset) - { - // if we have no items now, clear our own internal list - if (!SourceCollection.GetEnumerator().MoveNext()) - { - _internalList.Clear(); - } - - // calling Refresh, will fire the collectionchanged event - RefreshOrDefer(); - return; - } - - // fire notifications for removes - if (args.OldItems != null && - (args.Action == NotifyCollectionChangedAction.Remove || - args.Action == NotifyCollectionChangedAction.Replace)) - { - foreach (var removedItem in args.OldItems) - { - ProcessRemoveEvent(removedItem, args.Action == NotifyCollectionChangedAction.Replace); - } - } - - // fire notifications for adds - if (args.NewItems != null && - (args.Action == NotifyCollectionChangedAction.Add || - args.Action == NotifyCollectionChangedAction.Replace)) - { - for (var i = 0; i < args.NewItems.Count; i++) - { - if (Filter == null || PassesFilter(args.NewItems[i])) - { - ProcessAddEvent(args.NewItems[i], args.NewStartingIndex + i); - } - } - } - if (args.Action != NotifyCollectionChangedAction.Replace) - { - OnPropertyChanged(nameof(ItemCount)); - } - } - - /// - /// Process a Remove operation from an INotifyCollectionChanged event handler. - /// - /// Item removed from the source collection - /// Whether this was part of a Replace operation - //TODO Paging - private void ProcessRemoveEvent(object removedItem, bool isReplace) - { - int internalRemoveIndex = -1; - - if (IsGrouping) - { - internalRemoveIndex = PageSize > 0 ? _temporaryGroup.LeafIndexOf(removedItem) : - _group.LeafIndexOf(removedItem); - } - else - { - internalRemoveIndex = InternalIndexOf(removedItem); - } - - int removeIndex = IndexOf(removedItem); - - // remove the item from the collection - _internalList.Remove(removedItem); - - // only fire the remove if it was removed from either the current page, or a previous page - bool needToRemove = (PageSize == 0 && removeIndex >= 0) || (internalRemoveIndex < (PageIndex + 1) * PageSize); - - if (IsGrouping) - { - if (PageSize > 0) - { - _temporaryGroup.RemoveFromSubgroups(removedItem); - } - - if (needToRemove) - { - _group.RemoveFromSubgroups(removeIndex >= 0 ? removedItem : _group.LeafAt(0)); - } - } - - if (needToRemove) - { - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - AdjustCurrencyForRemove(removeIndex); - - // fire remove notification - // if we removed from current page, remove from removeIndex, - // if we removed from previous page, remove first item (index=0) - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Remove, - removedItem, - Math.Max(0, removeIndex))); - - RaiseCurrencyChanges(false, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - - // if we removed all items from the current page, - // move to the previous page. we do not need to - // fire additional notifications, as moving the page will - // trigger a reset. - if (NeedToMoveToPreviousPage && !isReplace) - { - MoveToPreviousPage(); - return; - } - - // if we are paging, we may have to fire another notification for the item - // that needs to replace the one we removed on this page. - if (PageSize > 0 && Count == PageSize) - { - // we first need to add the item into the current group - if (IsGrouping) - { - object newItem = _temporaryGroup.LeafAt((PageSize * (PageIndex + 1)) - 1); - if (newItem != null) - { - _group.AddToSubgroups(newItem, false /*loading*/); - } - } - - // fire the add notification - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Add, - GetItemAt(PageSize - 1), - PageSize - 1)); - } - } - } - - /// - /// Handles adding an item into the collection, and applying sorting, filtering, grouping, paging. - /// - /// Item to insert in the collection - /// Index to insert item into - private void ProcessInsertToCollection(object item, int index) - { - // first check to see if it passes the filter - if (Filter == null || PassesFilter(item)) - { - if (SortDescriptions.Count > 0) - { - var itemType = ItemType; - foreach (var sort in SortDescriptions) - sort.Initialize(itemType); - - // create the SortFieldComparer to use - var sortFieldComparer = new MergedComparer(this); - - // check if the item would be in sorted order if inserted into the specified index - // otherwise, calculate the correct sorted index - if (index < 0 || /* if item was not originally part of list */ - (index > 0 && (sortFieldComparer.Compare(item, InternalItemAt(index - 1)) < 0)) || /* item has moved up in the list */ - ((index < InternalList.Count - 1) && (sortFieldComparer.Compare(item, InternalItemAt(index)) > 0))) /* item has moved down in the list */ - { - index = sortFieldComparer.FindInsertIndex(item, _internalList); - } - } - - // make sure that the specified insert index is within the valid range - // otherwise, just add it to the end. the index can be set to an invalid - // value if the item was originally not in the collection, on a different - // page, or if it had been previously filtered out. - if (index < 0 || index > _internalList.Count) - { - index = _internalList.Count; - } - - _internalList.Insert(index, item); - } - } - - /// - /// Raises Currency Change events - /// - /// Whether to fire the CurrentChanged event even if the parameters have not changed - /// CurrentItem before processing changes - /// CurrentPosition before processing changes - /// IsCurrentBeforeFirst before processing changes - /// IsCurrentAfterLast before processing changes - private void RaiseCurrencyChanges(bool fireChangedEvent, object oldCurrentItem, int oldCurrentPosition, bool oldIsCurrentBeforeFirst, bool oldIsCurrentAfterLast) - { - // fire events for currency changes - if (fireChangedEvent || CurrentItem != oldCurrentItem || CurrentPosition != oldCurrentPosition) - { - OnCurrentChanged(); - } - if (CurrentItem != oldCurrentItem) - { - OnPropertyChanged(nameof(CurrentItem)); - } - if (CurrentPosition != oldCurrentPosition) - { - OnPropertyChanged(nameof(CurrentPosition)); - } - if (IsCurrentAfterLast != oldIsCurrentAfterLast) - { - OnPropertyChanged(nameof(IsCurrentAfterLast)); - } - if (IsCurrentBeforeFirst != oldIsCurrentBeforeFirst) - { - OnPropertyChanged(nameof(IsCurrentBeforeFirst)); - } - } - - /// - /// Raises the PageChanged event - /// - private void RaisePageChanged() - { - PageChanged?.Invoke(this, EventArgs.Empty); - } - - /// - /// Raises the PageChanging event - /// - /// Index of the requested page - /// True if the event is cancelled (e.Cancel was set to True), False otherwise - private bool RaisePageChanging(int newPageIndex) - { - EventHandler handler = PageChanging; - if (handler != null) - { - PageChangingEventArgs pageChangingEventArgs = new PageChangingEventArgs(newPageIndex); - handler(this, pageChangingEventArgs); - return pageChangingEventArgs.Cancel; - } - - return false; - } - - /// - /// Will call RefreshOverride and clear the NeedsRefresh flag - /// - private void RefreshInternal() - { - RefreshOverride(); - SetFlag(CollectionViewFlags.NeedsRefresh, false); - } - - /// - /// Refresh, or mark that refresh is needed when defer cycle completes. - /// - private void RefreshOrDefer() - { - if (IsRefreshDeferred) - { - SetFlag(CollectionViewFlags.NeedsRefresh, true); - } - else - { - RefreshInternal(); - } - } - - /// - /// Re-create the view, using any SortDescriptions. - /// Also updates currency information. - /// - //TODO Paging - private void RefreshOverride() - { - object oldCurrentItem = CurrentItem; - int oldCurrentPosition = CurrentPosition; - bool oldIsCurrentAfterLast = IsCurrentAfterLast; - bool oldIsCurrentBeforeFirst = IsCurrentBeforeFirst; - - // set IsGrouping to false - _isGrouping = false; - - // force currency off the collection (gives user a chance to save dirty information) - OnCurrentChanging(); - - // if there's no sort/filter/paging/grouping, just use the collection's array - if (UsesLocalArray) - { - try - { - // apply filtering/sorting through the PrepareLocalArray method - _internalList = PrepareLocalArray(_sourceCollection); - - // apply grouping - if (PageSize == 0) - { - PrepareGroups(); - } - else - { - PrepareTemporaryGroups(); - PrepareGroupsForCurrentPage(); - } - } - catch (TargetInvocationException e) - { - // If there's an exception while invoking PrepareLocalArray, - // we want to unwrap it and throw its inner exception - if (e.InnerException != null) - { - throw e.InnerException; - } - else - { - throw; - } - } - } - else - { - CopySourceToInternalList(); - } - - // check if PageIndex is still valid after filter/sort - if (PageSize > 0 && - PageIndex > 0 && - PageIndex >= PageCount) - { - MoveToPage(PageCount - 1); - } - - // reset currency values - ResetCurrencyValues(oldCurrentItem, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - - OnCollectionChanged( - new NotifyCollectionChangedEventArgs( - NotifyCollectionChangedAction.Reset)); - - // now raise currency changes at the end - RaiseCurrencyChanges(false, oldCurrentItem, oldCurrentPosition, oldIsCurrentBeforeFirst, oldIsCurrentAfterLast); - } - - /// - /// Set currency back to the previous value it had if possible. If the item is no longer in view - /// then either use the first item in the view, or if the list is empty, use null. - /// - /// CurrentItem before processing changes - /// IsCurrentBeforeFirst before processing changes - /// IsCurrentAfterLast before processing changes - private void ResetCurrencyValues(object oldCurrentItem, bool oldIsCurrentBeforeFirst, bool oldIsCurrentAfterLast) - { - if (oldIsCurrentBeforeFirst || IsEmpty) - { - SetCurrent(null, -1); - } - else if (oldIsCurrentAfterLast) - { - SetCurrent(null, Count); - } - else - { - // try to set currency back to old current item - // if there are duplicates, use the position of the first matching item - int newPosition = IndexOf(oldCurrentItem); - - // if the old current item is no longer in view - if (newPosition < 0) - { - // if we are adding a new item, set it as the current item, otherwise, set it to null - newPosition = 0; - - if (newPosition < Count) - { - SetCurrent(GetItemAt(newPosition), newPosition); - } - else if (!IsEmpty) - { - SetCurrent(GetItemAt(0), 0); - } - else - { - SetCurrent(null, -1); - } - } - else - { - SetCurrent(oldCurrentItem, newPosition); - } - } - } - - /// - /// Set CurrentItem and CurrentPosition, no questions asked! - /// - /// - /// CollectionViews (and sub-classes) should use this method to update - /// the Current values. - /// - /// New CurrentItem - /// New CurrentPosition - private void SetCurrent(object newItem, int newPosition) - { - int count = (newItem != null) ? 0 : (IsEmpty ? 0 : Count); - SetCurrent(newItem, newPosition, count); - } - - /// - /// Set CurrentItem and CurrentPosition, no questions asked! - /// - /// - /// This method can be called from a constructor - it does not call - /// any virtuals. The 'count' parameter is substitute for the real Count, - /// used only when newItem is null. - /// In that case, this method sets IsCurrentAfterLast to true if and only - /// if newPosition >= count. This distinguishes between a null belonging - /// to the view and the dummy null when CurrentPosition is past the end. - /// - /// New CurrentItem - /// New CurrentPosition - /// Numbers of items in the collection - private void SetCurrent(object newItem, int newPosition, int count) - { - if (newItem != null) - { - // non-null item implies position is within range. - // We ignore count - it's just a placeholder - SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, false); - SetFlag(CollectionViewFlags.IsCurrentAfterLast, false); - } - else if (count == 0) - { - // empty collection - by convention both flags are true and position is -1 - SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, true); - SetFlag(CollectionViewFlags.IsCurrentAfterLast, true); - newPosition = -1; - } - else - { - // null item, possibly within range. - SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, newPosition < 0); - SetFlag(CollectionViewFlags.IsCurrentAfterLast, newPosition >= count); - } - - _currentItem = newItem; - _currentPosition = newPosition; - } - - /// - /// Just move it. No argument check, no events, just move current to position. - /// - /// Position to move the current item to - private void SetCurrentToPosition(int position) - { - if (position < 0) - { - SetFlag(CollectionViewFlags.IsCurrentBeforeFirst, true); - SetCurrent(null, -1); - } - else if (position >= Count) - { - SetFlag(CollectionViewFlags.IsCurrentAfterLast, true); - SetCurrent(null, Count); - } - else - { - SetFlag(CollectionViewFlags.IsCurrentBeforeFirst | CollectionViewFlags.IsCurrentAfterLast, false); - SetCurrent(GetItemAt(position), position); - } - } - - /// - /// Sets the specified Flag(s) - /// - /// Flags we want to set - /// Value we want to set these flags to - private void SetFlag(CollectionViewFlags flags, bool value) - { - if (value) - { - _flags = _flags | flags; - } - else - { - _flags = _flags & ~flags; - } - } - - /// - /// Set new SortDescription collection; re-hook collection change notification handler - /// - /// SortDescriptionCollection to set the property value to - private void SetSortDescriptions(DataGridSortDescriptionCollection descriptions) - { - if (_sortDescriptions != null) - { - _sortDescriptions.CollectionChanged -= SortDescriptionsChanged; - } - - _sortDescriptions = descriptions; - - if (_sortDescriptions != null) - { - Debug.Assert(_sortDescriptions.Count == 0, "must be empty SortDescription collection"); - _sortDescriptions.CollectionChanged += SortDescriptionsChanged; - } - } - - /// - /// SortDescription was added/removed, refresh DataGridCollectionView - /// - /// Sender that triggered this handler - /// NotifyCollectionChangedEventArgs for this change - private void SortDescriptionsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (IsAddingNew || IsEditingItem) - { - throw new InvalidOperationException(GetOperationNotAllowedDuringAddOrEditText("Sorting")); - } - - // we want to make sure that the data is refreshed before we try to move to a page - // since the refresh would take care of the filtering, sorting, and grouping. - RefreshOrDefer(); - - if (PageSize > 0) - { - if (IsRefreshDeferred) - { - // set cached value and flag so that we move to first page on EndDefer - _cachedPageIndex = 0; - SetFlag(CollectionViewFlags.IsMoveToPageDeferred, true); - } - else - { - MoveToFirstPage(); - } - } - - OnPropertyChanged("SortDescriptions"); - } - - /// - /// Sort the List based on the SortDescriptions property. - /// - /// List of objects to sort - /// The sorted list - private List SortList(List list) - { - Debug.Assert(list != null, "Input list to sort should not be null"); - - IEnumerable seq = (IEnumerable)list; - IComparer comparer = new CultureSensitiveComparer(Culture); - var itemType = ItemType; - - foreach (DataGridSortDescription sort in SortDescriptions) - { - sort.Initialize(itemType); - - if (seq is IOrderedEnumerable orderedEnum) - { - seq = sort.ThenBy(orderedEnum); - } - else - { - seq = sort.OrderBy(seq); - } - } - - return seq.ToList(); - } - - /// - /// Helper to validate that we are not in the middle of a DeferRefresh - /// and throw if that is the case. - /// - private void VerifyRefreshNotDeferred() - { - // If the Refresh is being deferred to change filtering or sorting of the - // data by this DataGridCollectionView, then DataGridCollectionView will not reflect the correct - // state of the underlying data. - if (IsRefreshDeferred) - { - throw new InvalidOperationException("Cannot change or check the contents or current position of the CollectionView while Refresh is being deferred."); - } - } - - int IList.Add(object value) - { - var index = SourceList.Add(value); - if (SourceList is not INotifyCollectionChanged) - { - ProcessCollectionChanged( - new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value)); - } - return index; - } - - void IList.Clear() - { - SourceList.Clear(); - if (SourceList is not INotifyCollectionChanged) - { - ProcessCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - } - - void IList.Insert(int index, object value) - { - SourceList.Insert(index, value); - if (SourceList is not INotifyCollectionChanged) - { - // TODO: implement Insert - ProcessCollectionChanged( - new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, value)); - } - } - void ICollection.CopyTo(Array array, int index) => InternalList.CopyTo(array, index); - - /// - /// Creates a comparer class that takes in a CultureInfo as a parameter, - /// which it will use when comparing strings. - /// - private class CultureSensitiveComparer : IComparer - { - /// - /// Private accessor for the CultureInfo of our comparer - /// - private CultureInfo _culture; - - /// - /// Creates a comparer which will respect the CultureInfo - /// that is passed in when comparing strings. - /// - /// The CultureInfo to use in string comparisons - public CultureSensitiveComparer(CultureInfo culture) - : base() - { - _culture = culture ?? CultureInfo.InvariantCulture; - } - - /// - /// Compares two objects and returns a value indicating whether one is less than, equal to or greater than the other. - /// - /// first item to compare - /// second item to compare - /// Negative number if x is less than y, zero if equal, and a positive number if x is greater than y - /// - /// Compares the 2 items using the specified CultureInfo for string and using the default object comparer for all other objects. - /// - public int Compare(object x, object y) - { - if (x == null) - { - if (y != null) - { - return -1; - } - return 0; - } - if (y == null) - { - return 1; - } - - // at this point x and y are not null - if (x.GetType() == typeof(string) && y.GetType() == typeof(string)) - { - return _culture.CompareInfo.Compare((string)x, (string)y); - } - else - { - return Comparer.Default.Compare(x, y); - } - } - } - - /// - /// Used to keep track of Defer calls on the DataGridCollectionView, which - /// will prevent the user from calling Refresh() on the view. In order - /// to allow refreshes again, the user will have to call IDisposable.Dispose, - /// to end the Defer operation. - /// - private class DeferHelper : IDisposable - { - /// - /// Private reference to the CollectionView that created this DeferHelper - /// - private DataGridCollectionView collectionView; - - /// - /// Initializes a new instance of the DeferHelper class - /// - /// CollectionView that created this DeferHelper - public DeferHelper(DataGridCollectionView collectionView) - { - this.collectionView = collectionView; - } - - /// - /// Cleanup method called when done using this class - /// - public void Dispose() - { - if (collectionView != null) - { - collectionView.EndDefer(); - collectionView = null; - } - GC.SuppressFinalize(this); - } - } - - /// - /// A simple monitor class to help prevent re-entrant calls - /// - private class SimpleMonitor : IDisposable - { - /// - /// Whether the monitor is entered - /// - private bool entered; - - /// - /// Gets a value indicating whether we have been entered or not - /// - public bool Busy - { - get { return entered; } - } - - /// - /// Sets a value indicating that we have been entered - /// - /// Boolean value indicating whether we were already entered - public bool Enter() - { - if (entered) - { - return false; - } - - entered = true; - return true; - } - - /// - /// Cleanup method called when done using this class - /// - public void Dispose() - { - entered = false; - GC.SuppressFinalize(this); - } - } - - /// - /// IEnumerator generated using the new item taken into account - /// - private class NewItemAwareEnumerator : IEnumerator - { - private enum Position - { - /// - /// Whether the position is before the new item - /// - BeforeNewItem, - - /// - /// Whether the position is on the new item that is being created - /// - OnNewItem, - - /// - /// Whether the position is after the new item - /// - AfterNewItem - } - - /// - /// Initializes a new instance of the NewItemAwareEnumerator class. - /// - /// The DataGridCollectionView we are creating the enumerator for - /// The baseEnumerator that we pass in - /// The new item we are adding to the collection - public NewItemAwareEnumerator(DataGridCollectionView collectionView, IEnumerator baseEnumerator, object newItem) - { - _collectionView = collectionView; - _timestamp = collectionView.Timestamp; - _baseEnumerator = baseEnumerator; - _newItem = newItem; - } - - /// - /// Implements the MoveNext function for IEnumerable - /// - /// Whether we can move to the next item - public bool MoveNext() - { - if (_timestamp != _collectionView.Timestamp) - { - throw new InvalidOperationException("Collection was modified; enumeration operation cannot execute."); - } - - switch (_position) - { - case Position.BeforeNewItem: - if (_baseEnumerator.MoveNext() && - (_newItem == null || _baseEnumerator.Current != _newItem - || _baseEnumerator.MoveNext())) - { - // advance base, skipping the new item - } - else if (_newItem != null) - { - // if base has reached the end, move to new item - _position = Position.OnNewItem; - } - else - { - return false; - } - return true; - } - - // in all other cases, simply advance base, skipping the new item - _position = Position.AfterNewItem; - return _baseEnumerator.MoveNext() && - (_newItem == null - || _baseEnumerator.Current != _newItem - || _baseEnumerator.MoveNext()); - } - - /// - /// Gets the Current value for IEnumerable - /// - public object Current - { - get - { - return (_position == Position.OnNewItem) ? _newItem : _baseEnumerator.Current; - } - } - - /// - /// Implements the Reset function for IEnumerable - /// - public void Reset() - { - _position = Position.BeforeNewItem; - _baseEnumerator.Reset(); - } - - /// - /// CollectionView that we are creating the enumerator for - /// - private DataGridCollectionView _collectionView; - - /// - /// The Base Enumerator that we are passing in - /// - private IEnumerator _baseEnumerator; - - /// - /// The position we are appending items to the enumerator - /// - private Position _position; - - /// - /// Reference to any new item that we want to add to the collection - /// - private object _newItem; - - /// - /// Timestamp to let us know whether there have been updates to the collection - /// - private int _timestamp; - } - - internal class MergedComparer - { - private readonly IComparer[] _comparers; - - public MergedComparer(DataGridSortDescriptionCollection coll) - { - _comparers = MakeComparerArray(coll); - } - public MergedComparer(DataGridCollectionView collectionView) - : this(collectionView.SortDescriptions) - { } - - private static IComparer[] MakeComparerArray(DataGridSortDescriptionCollection coll) - { - return - coll.Select(c => c.Comparer) - .ToArray(); - } - - /// - /// Compares two objects and returns a value indicating whether one is less than, equal to or greater than the other. - /// - /// first item to compare - /// second item to compare - /// Negative number if x is less than y, zero if equal, and a positive number if x is greater than y - /// - /// Compares the 2 items using the list of property names and directions. - /// - public int Compare(object x, object y) - { - int result = 0; - - // compare both objects by each of the properties until property values don't match - for (int k = 0; k < _comparers.Length; ++k) - { - var comparer = _comparers[k]; - result = comparer.Compare(x, y); - - if (result != 0) - { - break; - } - } - - return result; - } - - /// - /// Steps through the given list using the comparer to find where - /// to insert the specified item to maintain sorted order - /// - /// Item to insert into the list - /// List where we want to insert the item - /// Index where we should insert into - public int FindInsertIndex(object x, IList list) - { - int min = 0; - int max = list.Count - 1; - int index; - - // run a binary search to find the right index - // to insert into. - while (min <= max) - { - index = (min + max) / 2; - - int result = Compare(x, list[index]); - if (result == 0) - { - return index; - } - else if (result > 0) - { - min = index + 1; - } - else - { - max = index - 1; - } - } - - return min; - } - } - } -} diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs deleted file mode 100644 index 587dd228a3..0000000000 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs +++ /dev/null @@ -1,1369 +0,0 @@ -// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; -using System.Globalization; -using System.Text; -using Avalonia.Controls; -using Avalonia.Controls.Utils; -using Avalonia.Data; -using Avalonia.Data.Converters; -using Avalonia.Utilities; - -namespace Avalonia.Collections -{ - public abstract class DataGridGroupDescription : INotifyPropertyChanged - { - public AvaloniaList GroupKeys { get; } - - public DataGridGroupDescription() - { - GroupKeys = new AvaloniaList(); - GroupKeys.CollectionChanged += (sender, e) => OnPropertyChanged(new PropertyChangedEventArgs(nameof(GroupKeys))); - } - - protected virtual event PropertyChangedEventHandler PropertyChanged; - event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged - { - add - { - PropertyChanged += value; - } - - remove - { - PropertyChanged -= value; - } - } - protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) - { - PropertyChanged?.Invoke(this, e); - } - - public virtual string PropertyName => String.Empty; - public abstract object GroupKeyFromItem(object item, int level, CultureInfo culture); - public virtual bool KeysMatch(object groupKey, object itemKey) - { - return object.Equals(groupKey, itemKey); - } - } - public class DataGridPathGroupDescription : DataGridGroupDescription - { - private string _propertyPath; - private Type _propertyType; - private IValueConverter _valueConverter; - private StringComparison _stringComparison = StringComparison.Ordinal; - - public DataGridPathGroupDescription(string propertyPath) - { - _propertyPath = propertyPath; - } - - public override object GroupKeyFromItem(object item, int level, CultureInfo culture) - { - object GetKey(object o) - { - if(o == null) - return null; - - if (_propertyType == null) - _propertyType = GetPropertyType(o); - - return InvokePath(o, _propertyPath, _propertyType); - } - - var key = GetKey(item); - if (key == null) - key = item; - - var valueConverter = ValueConverter; - if (valueConverter != null) - key = valueConverter.Convert(key, typeof(object), level, culture); - - return key; - } - public override bool KeysMatch(object groupKey, object itemKey) - { - if(groupKey is string k1 && itemKey is string k2) - { - return String.Equals(k1, k2, _stringComparison); - } - else - return base.KeysMatch(groupKey, itemKey); - } - public override string PropertyName => _propertyPath; - - public IValueConverter ValueConverter { get => _valueConverter; set => _valueConverter = value; } - - private Type GetPropertyType(object o) - { - return o.GetType().GetNestedPropertyType(_propertyPath); - } - private static object InvokePath(object item, string propertyPath, Type propertyType) - { - object propertyValue = TypeHelper.GetNestedPropertyValue(item, propertyPath, propertyType, out Exception exception); - if (exception != null) - { - throw exception; - } - return propertyValue; - } - } - - public abstract class DataGridCollectionViewGroup : INotifyPropertyChanged - { - private int _itemCount; - - public object Key { get; } - public int ItemCount => _itemCount; - public IAvaloniaReadOnlyList Items => ProtectedItems; - - protected AvaloniaList ProtectedItems { get; } - protected int ProtectedItemCount - { - get { return _itemCount; } - set - { - _itemCount = value; - OnPropertyChanged(new PropertyChangedEventArgs(nameof(ItemCount))); - } - } - - protected DataGridCollectionViewGroup(object key) - { - Key = key; - ProtectedItems = new AvaloniaList(); - } - - public abstract bool IsBottomLevel { get; } - - protected virtual event PropertyChangedEventHandler PropertyChanged; - event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged - { - add - { - PropertyChanged += value; - } - - remove - { - PropertyChanged -= value; - } - } - protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) - { - PropertyChanged?.Invoke(this, e); - } - } - internal class DataGridCollectionViewGroupInternal : DataGridCollectionViewGroup - { - /// - /// GroupDescription used to define how to group the items - /// - private DataGridGroupDescription _groupBy; - - /// - /// Parent group of this CollectionViewGroupInternal - /// - private readonly DataGridCollectionViewGroupInternal _parentGroup; - - /// - /// Used for detecting stale enumerators - /// - private int _version; - - public DataGridCollectionViewGroupInternal(object key, DataGridCollectionViewGroupInternal parent) - : base(key) - { - _parentGroup = parent; - } - - public override bool IsBottomLevel => _groupBy == null; - - internal int FullCount { get; set; } - - internal DataGridGroupDescription GroupBy - { - get { return _groupBy; } - set - { - bool oldIsBottomLevel = IsBottomLevel; - - if (_groupBy != null) - { - ((INotifyPropertyChanged)_groupBy).PropertyChanged -= OnGroupByChanged; - } - - _groupBy = value; - - if (_groupBy != null) - { - ((INotifyPropertyChanged)_groupBy).PropertyChanged += OnGroupByChanged; - } - - if (oldIsBottomLevel != IsBottomLevel) - { - OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsBottomLevel))); - } - } - } - - private void OnGroupByChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - OnGroupByChanged(); - } - protected virtual void OnGroupByChanged() - { - _parentGroup?.OnGroupByChanged(); - } - - /// - /// Gets or sets the most recent index where activity took place - /// - internal int LastIndex { get; set; } - - /// - /// Gets the first item (leaf) added to this group. If this can't be determined, - /// DependencyProperty.UnsetValue. - /// - internal object SeedItem - { - get - { - if (ItemCount > 0 && (GroupBy == null || GroupBy.GroupKeys.Count == 0)) - { - // look for first item, child by child - for (int k = 0, n = Items.Count; k < n; ++k) - { - if (!(Items[k] is DataGridCollectionViewGroupInternal subgroup)) - { - // child is an item - return it - return Items[k]; - } - else if (subgroup.ItemCount > 0) - { - // child is a nonempty subgroup - ask it - return subgroup.SeedItem; - } - //// otherwise child is an empty subgroup - go to next child - } - - // we shouldn't get here, but just in case... - - return AvaloniaProperty.UnsetValue; - } - else - { - // the group is empty, or it has explicit subgroups. - // In either case, we cannot determine the first item - - // it could have gone into any of the subgroups. - return AvaloniaProperty.UnsetValue; - } - } - } - - private DataGridCollectionViewGroupInternal Parent => _parentGroup; - - /// - /// Adds the specified item to the collection - /// - /// Item to add - internal void Add(object item) - { - ChangeCounts(item, +1); - ProtectedItems.Add(item); - } - - /// - /// Clears the collection of items - /// - internal void Clear() - { - ProtectedItems.Clear(); - FullCount = 1; - ProtectedItemCount = 0; - } - - /// - /// Finds the index of the specified item - /// - /// Item we are looking for - /// Seed of the item we are looking for - /// Comparer used to find the item - /// Low range of item index - /// High range of item index - /// Index of the specified item - protected virtual int FindIndex(object item, object seed, IComparer comparer, int low, int high) - { - int index; - - if (comparer != null) - { - if (comparer is ListComparer listComparer) - { - // reset the IListComparer before each search. This cannot be done - // any less frequently (e.g. in Root.AddToSubgroups), due to the - // possibility that the item may appear in more than one subgroup. - listComparer.Reset(); - } - - if (comparer is CollectionViewGroupComparer groupComparer) - { - // reset the CollectionViewGroupComparer before each search. This cannot be done - // any less frequently (e.g. in Root.AddToSubgroups), due to the - // possibility that the item may appear in more than one subgroup. - groupComparer.Reset(); - } - - for (index = low; index < high; ++index) - { - object seed1 = (ProtectedItems[index] is DataGridCollectionViewGroupInternal subgroup) ? subgroup.SeedItem : ProtectedItems[index]; - if (seed1 == AvaloniaProperty.UnsetValue) - { - continue; - } - if (comparer.Compare(seed, seed1) < 0) - { - break; - } - } - } - else - { - index = high; - } - - return index; - } - - /// - /// Returns an enumerator over the leaves governed by this group - /// - /// Enumerator of leaves - internal IEnumerator GetLeafEnumerator() - { - return new LeafEnumerator(this); - } - - /// - /// Insert a new item or subgroup and return its index. Seed is a - /// representative from the subgroup (or the item itself) that - /// is used to position the new item/subgroup w.r.t. the order given - /// by the comparer. (If comparer is null, just add at the end). - /// - /// Item we are looking for - /// Seed of the item we are looking for - /// Comparer used to find the item - /// The index where the item was inserted - internal int Insert(object item, object seed, IComparer comparer) - { - // never insert the new item/group before the explicit subgroups - int low = (GroupBy == null) ? 0 : GroupBy.GroupKeys.Count; - int index = FindIndex(item, seed, comparer, low, ProtectedItems.Count); - - // now insert the item - ChangeCounts(item, +1); - ProtectedItems.Insert(index, item); - - return index; - } - - /// - /// Return the item at the given index within the list of leaves governed - /// by this group - /// - /// Index of the leaf - /// Item at given index - internal object LeafAt(int index) - { - for (int k = 0, n = Items.Count; k < n; ++k) - { - if (Items[k] is DataGridCollectionViewGroupInternal subgroup) - { - // current item is a group - either drill in, or skip over - if (index < subgroup.ItemCount) - { - return subgroup.LeafAt(index); - } - else - { - index -= subgroup.ItemCount; - } - } - else - { - // current item is a leaf - see if we're done - if (index == 0) - { - return Items[k]; - } - else - { - index -= 1; - } - } - } - - return null; - } - - /// - /// Returns the index of the given item within the list of leaves governed - /// by the full group structure. The item must be a (direct) child of this - /// group. The caller provides the index of the item within this group, - /// if known, or -1 if not. - /// - /// Item we are looking for - /// Index of the leaf - /// Number of items under that leaf - internal int LeafIndexFromItem(object item, int index) - { - int result = 0; - - // accumulate the number of predecessors at each level - for (DataGridCollectionViewGroupInternal group = this; - group != null; - item = group, group = group.Parent, index = -1) - { - // accumulate the number of predecessors at the level of item - for (int k = 0, n = group.Items.Count; k < n; ++k) - { - // if we've reached the item, move up to the next level - if ((index < 0 && Object.Equals(item, group.Items[k])) || - index == k) - { - break; - } - - // accumulate leaf count - DataGridCollectionViewGroupInternal subgroup = group.Items[k] as DataGridCollectionViewGroupInternal; - result += subgroup?.ItemCount ?? 1; - } - } - - return result; - } - - /// - /// Returns the index of the given item within the list of leaves governed - /// by this group - /// - /// Item we are looking for - /// Number of items under that leaf - internal int LeafIndexOf(object item) - { - int leaves = 0; // number of leaves we've passed over so far - for (int k = 0, n = Items.Count; k < n; ++k) - { - if (Items[k] is DataGridCollectionViewGroupInternal subgroup) - { - int subgroupIndex = subgroup.LeafIndexOf(item); - if (subgroupIndex < 0) - { - leaves += subgroup.ItemCount; // item not in this subgroup - } - else - { - return leaves + subgroupIndex; // item is in this subgroup - } - } - else - { - // current item is a leaf - compare it directly - if (Object.Equals(item, Items[k])) - { - return leaves; - } - else - { - leaves += 1; - } - } - } - - // item not found - return -1; - } - - /// - /// Removes the specified item from the collection - /// - /// Item to remove - /// Whether we want to return the leaf index - /// Leaf index where item was removed, if value was specified. Otherwise '-1' - internal int Remove(object item, bool returnLeafIndex) - { - int index = -1; - int localIndex = ProtectedItems.IndexOf(item); - - if (localIndex >= 0) - { - if (returnLeafIndex) - { - index = LeafIndexFromItem(null, localIndex); - } - - ChangeCounts(item, -1); - ProtectedItems.RemoveAt(localIndex); - } - - return index; - } - - /// - /// Removes an empty group from the PagedCollectionView grouping - /// - /// Empty subgroup to remove - private static void RemoveEmptyGroup(DataGridCollectionViewGroupInternal group) - { - DataGridCollectionViewGroupInternal parent = group.Parent; - - if (parent != null) - { - DataGridGroupDescription groupBy = parent.GroupBy; - int index = parent.ProtectedItems.IndexOf(group); - - // remove the subgroup unless it is one of the explicit groups - if (index >= groupBy.GroupKeys.Count) - { - parent.Remove(group, false); - } - } - } - - /// - /// Update the item count of the CollectionViewGroup - /// - /// CollectionViewGroup to update - /// Delta to change count by - protected void ChangeCounts(object item, int delta) - { - bool changeLeafCount = !(item is DataGridCollectionViewGroup); - - for (DataGridCollectionViewGroupInternal group = this; - group != null; - group = group._parentGroup) - { - group.FullCount += delta; - if (changeLeafCount) - { - group.ProtectedItemCount += delta; - - if (group.ProtectedItemCount == 0) - { - RemoveEmptyGroup(group); - } - } - } - - unchecked - { - // this invalidates enumerators - ++_version; - } - } - - /// - /// Enumerator for the leaves in the CollectionViewGroupInternal class. - /// - private class LeafEnumerator : IEnumerator - { - private object _current; // current item - private DataGridCollectionViewGroupInternal _group; // parent group - private int _index; // current index into Items - private IEnumerator _subEnum; // enumerator over current subgroup - private int _version; // parent group's version at ctor - - /// - /// Initializes a new instance of the LeafEnumerator class. - /// - /// CollectionViewGroupInternal that uses the enumerator - public LeafEnumerator(DataGridCollectionViewGroupInternal group) - { - _group = group; - DoReset(); // don't call virtual Reset in ctor - } - - /// - /// Private helper to reset the enumerator - /// - private void DoReset() - { - Debug.Assert(_group != null, "_group should have been initialized in constructor"); - _version = _group._version; - _index = -1; - _subEnum = null; - } - - /// - /// Reset implementation for IEnumerator - /// - void IEnumerator.Reset() - { - DoReset(); - } - - /// - /// MoveNext implementation for IEnumerator - /// - /// Returns whether the MoveNext operation was successful - bool IEnumerator.MoveNext() - { - Debug.Assert(_group != null, "_group should have been initialized in constructor"); - - // check for invalidated enumerator - if (_group._version != _version) - { - throw new InvalidOperationException(); - } - - // move forward to the next leaf - while (_subEnum == null || !_subEnum.MoveNext()) - { - // done with the current top-level item. Move to the next one. - ++_index; - if (_index >= _group.Items.Count) - { - return false; - } - - DataGridCollectionViewGroupInternal subgroup = _group.Items[_index] as DataGridCollectionViewGroupInternal; - if (subgroup == null) - { - // current item is a leaf - it's the new Current - _current = _group.Items[_index]; - _subEnum = null; - return true; - } - else - { - // current item is a subgroup - get its enumerator - _subEnum = subgroup.GetLeafEnumerator(); - } - } - - // the loop terminates only when we have a subgroup enumerator - // positioned at the new Current item - _current = _subEnum.Current; - return true; - } - - /// - /// Gets the current implementation for IEnumerator - /// - object IEnumerator.Current - { - get - { - Debug.Assert(_group != null, "_group should have been initialized in constructor"); - - if (_index < 0 || _index >= _group.Items.Count) - { - throw new InvalidOperationException(); - } - - return _current; - } - } - - } - - // / - // / This comparer is used to insert an item into a group in a position consistent - // / with a given IList. It only works when used in the pattern that FindIndex - // / uses, namely first call Reset(), then call Compare(item, itemSequence) any number of - // / times with the same item (the new item) as the first argument, and a sequence - // / of items as the second argument that appear in the IList in the same sequence. - // / This makes the total search time linear in the size of the IList. (To give - // / the correct answer regardless of the sequence of arguments would involve - // / calling IndexOf and leads to O(N^2) total search time.) - // / - internal class ListComparer : IComparer - { - /// - /// Constructor for the ListComparer that takes - /// in an IList. - /// - /// IList used to compare on - internal ListComparer(IList list) - { - ResetList(list); - } - - /// - /// Sets the index that we start comparing - /// from to 0. - /// - internal void Reset() - { - _index = 0; - } - - /// - /// Sets our IList to a new instance - /// of a list being passed in and resets - /// the index. - /// - /// IList used to compare on - internal void ResetList(IList list) - { - _list = list; - _index = 0; - } - - /// - /// Compares objects x and y to see which one - /// should appear first. - /// - /// The first object - /// The second object - /// -1 if x is less than y, +1 otherwise - public int Compare(object x, object y) - { - if (Object.Equals(x, y)) - { - return 0; - } - - // advance the index until seeing one x or y - int n = (_list != null) ? _list.Count : 0; - for (; _index < n; ++_index) - { - object z = _list[_index]; - if (Object.Equals(x, z)) - { - return -1; // x occurs first, so x < y - } - else if (Object.Equals(y, z)) - { - return +1; // y occurs first, so x > y - } - } - - // if we don't see either x or y, declare x > y. - // This has the effect of putting x at the end of the list. - return +1; - } - - private int _index; - private IList _list; - } - - // / - // / This comparer is used to insert an item into a group in a position consistent - // / with a given CollectionViewGroupRoot. We will only use this when dealing with - // / a temporary CollectionViewGroupRoot that points to the correct grouping of the - // / entire collection, and we have paging that requires us to keep the paged group - // / consistent with the order of items in the temporary group. - // / - internal class CollectionViewGroupComparer : IComparer - { - /// - /// Constructor for the CollectionViewGroupComparer that takes - /// in an CollectionViewGroupRoot. - /// - /// CollectionViewGroupRoot used to compare on - internal CollectionViewGroupComparer(CollectionViewGroupRoot group) - { - ResetGroup(group); - } - - /// - /// Sets the index that we start comparing - /// from to 0. - /// - internal void Reset() - { - _index = 0; - } - - /// - /// Sets our group to a new instance of a - /// CollectionViewGroupRoot being passed in - /// and resets the index. - /// - /// CollectionViewGroupRoot used to compare on - internal void ResetGroup(CollectionViewGroupRoot group) - { - _group = group; - _index = 0; - } - - /// - /// Compares objects x and y to see which one - /// should appear first. - /// - /// The first object - /// The second object - /// -1 if x is less than y, +1 otherwise - public int Compare(object x, object y) - { - if (Object.Equals(x, y)) - { - return 0; - } - - // advance the index until seeing one x or y - int n = (_group != null) ? _group.ItemCount : 0; - for (; _index < n; ++_index) - { - object z = _group.LeafAt(_index); - if (Object.Equals(x, z)) - { - return -1; // x occurs first, so x < y - } - else if (Object.Equals(y, z)) - { - return +1; // y occurs first, so x > y - } - } - - // if we don't see either x or y, declare x > y. - // This has the effect of putting x at the end of the list. - return +1; - } - - private int _index; - private CollectionViewGroupRoot _group; - } - - } - - internal class CollectionViewGroupRoot : DataGridCollectionViewGroupInternal, INotifyCollectionChanged - { - /// - /// String constant used for the Root Name - /// - private const string RootName = "Root"; - - /// - /// Private accessor for empty object instance - /// - private static readonly object UseAsItemDirectly = new object(); - - /// - /// Private accessor for the top level GroupDescription - /// - private static DataGridGroupDescription topLevelGroupDescription; - - /// - /// Private accessor for an ObservableCollection containing group descriptions - /// - private readonly AvaloniaList _groupBy = new AvaloniaList(); - - /// - /// Indicates whether the list of items (after applying the sort and filters, if any) - /// is already in the correct order for grouping. - /// - private bool _isDataInGroupOrder; - - /// - /// Private accessor for the owning ICollectionView - /// - private readonly IDataGridCollectionView _view; - - /// - /// Raise this event when the (grouped) view changes - /// - public event NotifyCollectionChangedEventHandler CollectionChanged; - - /// - /// Raise this event when the GroupDescriptions change - /// - internal event EventHandler GroupDescriptionChanged; - - /// - /// Initializes a new instance of the CollectionViewGroupRoot class. - /// - /// CollectionView that contains this grouping - /// True if items are already in correct order for grouping - internal CollectionViewGroupRoot(IDataGridCollectionView view, bool isDataInGroupOrder) - : base(RootName, null) - { - _view = view; - _isDataInGroupOrder = isDataInGroupOrder; - } - - /// - /// Gets the description of grouping, indexed by level. - /// - public virtual AvaloniaList GroupDescriptions => _groupBy; - - /// - /// Gets or sets the current IComparer being used - /// - internal IComparer ActiveComparer { get; set; } - - /// - /// Gets the culture to use during sorting. - /// - internal CultureInfo Culture - { - get - { - Debug.Assert(_view != null, "this._view should have been set from the constructor"); - return _view.Culture; - } - } - - /// - /// Gets or sets a value indicating whether the data is in group order - /// - internal bool IsDataInGroupOrder - { - get { return _isDataInGroupOrder; } - set { _isDataInGroupOrder = value; } - } - - /// - /// Finds the index of the specified item - /// - /// Item we are looking for - /// Seed of the item we are looking for - /// Comparer used to find the item - /// Low range of item index - /// High range of item index - /// Index of the specified item - protected override int FindIndex(object item, object seed, IComparer comparer, int low, int high) - { - // root group needs to adjust the bounds of the search to exclude the new item (if any) - if (_view is IDataGridEditableCollectionView iecv && iecv.IsAddingNew) - { - --high; - } - - return base.FindIndex(item, seed, comparer, low, high); - } - - /// - /// Initializes the group descriptions - /// - internal void Initialize() - { - if (topLevelGroupDescription == null) - { - topLevelGroupDescription = new TopLevelGroupDescription(); - } - - InitializeGroup(this, 0, null); - } - - /// - /// Inserts specified item into the collection - /// - /// Index to insert into - /// Item to insert - /// Whether we are currently loading - internal void InsertSpecialItem(int index, object item, bool loading) - { - ChangeCounts(item, +1); - ProtectedItems.Insert(index, item); - - if (!loading) - { - int globalIndex = LeafIndexFromItem(item, index); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, globalIndex)); - } - } - - /// - /// Notify listeners that this View has changed - /// - /// - /// CollectionViews (and sub-classes) should take their filter/sort/grouping - /// into account before calling this method to forward CollectionChanged events. - /// - /// The NotifyCollectionChangedEventArgs to be passed to the EventHandler - public void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - Debug.Assert(args != null, "Arguments passed in should not be null"); - CollectionChanged?.Invoke(this, args); - } - - /// - /// Notify host that a group description has changed somewhere in the tree - /// - protected override void OnGroupByChanged() - { - GroupDescriptionChanged?.Invoke(this, EventArgs.Empty); - } - - /// - /// Remove specified item from subgroups - /// - /// Item to remove - /// Whether the operation was successful - internal bool RemoveFromSubgroups(object item) - { - return RemoveFromSubgroups(item, this, 0); - } - - /// - /// Remove specified item from subgroups using an exhaustive search - /// - /// Item to remove - internal void RemoveItemFromSubgroupsByExhaustiveSearch(object item) - { - RemoveItemFromSubgroupsByExhaustiveSearch(this, item); - } - - /// - /// Removes specified item into the collection - /// - /// Index to remove from - /// Item to remove - /// Whether we are currently loading - internal void RemoveSpecialItem(int index, object item, bool loading) - { - Debug.Assert(Object.Equals(item, ProtectedItems[index]), "RemoveSpecialItem finds inconsistent data"); - int globalIndex = -1; - - if (!loading) - { - globalIndex = LeafIndexFromItem(item, index); - } - - ChangeCounts(item, -1); - ProtectedItems.RemoveAt(index); - - if (!loading) - { - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, globalIndex)); - } - } - - /// - /// Adds specified item to subgroups - /// - /// Item to add - /// Whether we are currently loading - internal void AddToSubgroups(object item, bool loading) - { - AddToSubgroups(item, this, 0, loading); - } - - /// - /// Add an item to the subgroup with the given name - /// - /// Item to add - /// Group to add item to - /// The level of grouping. - /// Name of subgroup to add to - /// Whether we are currently loading - private void AddToSubgroup(object item, DataGridCollectionViewGroupInternal group, int level, object key, bool loading) - { - DataGridCollectionViewGroupInternal subgroup; - int index = (_isDataInGroupOrder) ? group.LastIndex : 0; - - // find the desired subgroup - for (int n = group.Items.Count; index < n; ++index) - { - subgroup = group.Items[index] as DataGridCollectionViewGroupInternal; - if (subgroup == null) - { - continue; // skip children that are not groups - } - - if (group.GroupBy.KeysMatch(subgroup.Key, key)) - { - group.LastIndex = index; - AddToSubgroups(item, subgroup, level + 1, loading); - return; - } - } - - // the item didn't match any subgroups. Create a new subgroup and add the item. - subgroup = new DataGridCollectionViewGroupInternal(key, group); - InitializeGroup(subgroup, level + 1, item); - - if (loading) - { - group.Add(subgroup); - group.LastIndex = index; - } - else - { - // using insert will find the correct sort index to - // place the subgroup, and will default to the last - // position if no ActiveComparer is specified - group.Insert(subgroup, item, ActiveComparer); - } - - AddToSubgroups(item, subgroup, level + 1, loading); - } - - /// - /// Add an item to the desired subgroup(s) of the given group - /// - /// Item to add - /// Group to add item to - /// The level of grouping - /// Whether we are currently loading - private void AddToSubgroups(object item, DataGridCollectionViewGroupInternal group, int level, bool loading) - { - object key = GetGroupKey(item, group.GroupBy, level); - - if (key == UseAsItemDirectly) - { - // the item belongs to the group itself (not to any subgroups) - if (loading) - { - group.Add(item); - } - else - { - int localIndex = group.Insert(item, item, ActiveComparer); - int index = group.LeafIndexFromItem(item, localIndex); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); - } - } - else if(key is ICollection keyList) - { - // the item belongs to multiple subgroups - foreach (object o in keyList) - { - AddToSubgroup(item, group, level, o, loading); - } - } - else - { - // the item belongs to one subgroup - AddToSubgroup(item, group, level, key, loading); - } - } - - public virtual Func GroupBySelector { get; set; } - - /// - /// Returns the description of how to divide the given group into subgroups - /// - /// CollectionViewGroup to get group description from - /// The level of grouping - /// GroupDescription of how to divide the given group - private DataGridGroupDescription GetGroupDescription(DataGridCollectionViewGroup group, int level) - { - DataGridGroupDescription result = null; - if (group == this) - { - group = null; - } - - if (result == null && GroupBySelector != null) - { - result = GroupBySelector?.Invoke(group, level); - } - - if (result == null && level < GroupDescriptions.Count) - { - result = GroupDescriptions[level]; - } - - return result; - } - - /// - /// Get the group name(s) for the given item - /// - /// Item to get group name for - /// GroupDescription for the group - /// The level of grouping - /// Group names for the specified item - private object GetGroupKey(object item, DataGridGroupDescription groupDescription, int level) - { - if (groupDescription != null) - { - return groupDescription.GroupKeyFromItem(item, level, Culture); - } - else - { - return UseAsItemDirectly; - } - } - - /// - /// Initialize the given group - /// - /// Group to initialize - /// The level of grouping - /// The seed item to compare with to see where to insert - private void InitializeGroup(DataGridCollectionViewGroupInternal group, int level, object seedItem) - { - // set the group description for dividing the group into subgroups - DataGridGroupDescription groupDescription = GetGroupDescription(group, level); - group.GroupBy = groupDescription; - - // create subgroups for each of the explicit names - var keys = groupDescription?.GroupKeys; - if (keys != null) - { - for (int k = 0, n = keys.Count; k < n; ++k) - { - DataGridCollectionViewGroupInternal subgroup = new DataGridCollectionViewGroupInternal(keys[k], group); - InitializeGroup(subgroup, level + 1, seedItem); - group.Add(subgroup); - } - } - - group.LastIndex = 0; - } - - /// - /// Remove an item from the direct children of a group. - /// - /// Group to remove item from - /// Item to remove - /// True if item could not be removed - private bool RemoveFromGroupDirectly(DataGridCollectionViewGroupInternal group, object item) - { - int leafIndex = group.Remove(item, true); - if (leafIndex >= 0) - { - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, leafIndex)); - return false; - } - else - { - return true; - } - } - - /// - /// Remove an item from the subgroup with the given name. - /// - /// Item to remove - /// Group to remove item from - /// The level of grouping - /// Name of item to remove - /// Return true if the item was not in one of the subgroups it was supposed to be. - private bool RemoveFromSubgroup(object item, DataGridCollectionViewGroupInternal group, int level, object key) - { - bool itemIsMissing = false; - DataGridCollectionViewGroupInternal subgroup; - - // find the desired subgroup - for (int index = 0, n = group.Items.Count; index < n; ++index) - { - subgroup = group.Items[index] as DataGridCollectionViewGroupInternal; - if (subgroup == null) - { - continue; // skip children that are not groups - } - - if (group.GroupBy.KeysMatch(subgroup.Key, key)) - { - if (RemoveFromSubgroups(item, subgroup, level + 1)) - { - itemIsMissing = true; - } - - return itemIsMissing; - } - } - - // the item didn't match any subgroups. It should have. - return true; - } - - /// - /// Remove an item from the desired subgroup(s) of the given group. - /// - /// Item to remove - /// Group to remove item from - /// The level of grouping - /// Return true if the item was not in one of the subgroups it was supposed to be. - private bool RemoveFromSubgroups(object item, DataGridCollectionViewGroupInternal group, int level) - { - bool itemIsMissing = false; - object key = GetGroupKey(item, group.GroupBy, level); - - if (key == UseAsItemDirectly) - { - // the item belongs to the group itself (not to any subgroups) - itemIsMissing = RemoveFromGroupDirectly(group, item); - } - else if (key is ICollection keyList) - { - // the item belongs to multiple subgroups - foreach (object o in keyList) - { - if (RemoveFromSubgroup(item, group, level, o)) - { - itemIsMissing = true; - } - } - } - else - { - // the item belongs to one subgroup - if (RemoveFromSubgroup(item, group, level, key)) - { - itemIsMissing = true; - } - } - - return itemIsMissing; - } - - /// - /// The item did not appear in one or more of the subgroups it - /// was supposed to. This can happen if the item's properties - /// change so that the group names we used to insert it are - /// different from the names used to remove it. If this happens, - /// remove the item the hard way. - /// - /// Group to remove item from - /// Item to remove - private void RemoveItemFromSubgroupsByExhaustiveSearch(DataGridCollectionViewGroupInternal group, object item) - { - // try to remove the item from the direct children - // this function only returns true if it failed to remove from group directly - // in which case we will step through and search exhaustively - if (RemoveFromGroupDirectly(group, item)) - { - // if that didn't work, recurse into each subgroup - // (loop runs backwards in case an entire group is deleted) - for (int k = group.Items.Count - 1; k >= 0; --k) - { - if (group.Items[k] is DataGridCollectionViewGroupInternal subgroup) - { - RemoveItemFromSubgroupsByExhaustiveSearch(subgroup, item); - } - } - } - } - - /// - /// TopLevelGroupDescription class - /// - private class TopLevelGroupDescription : DataGridGroupDescription - { - /// - /// Initializes a new instance of the TopLevelGroupDescription class. - /// - public TopLevelGroupDescription() - { - } - - /// - /// We have to implement this abstract method, but it should never be called - /// - /// Item to get group name from - /// The level of grouping - /// Culture used for sorting - /// We do not return a value here - public override object GroupKeyFromItem(object item, int level, CultureInfo culture) - { - Debug.Assert(true, "We have to implement this abstract method, but it should never be called"); - return null; - } - } - } - -} diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs deleted file mode 100644 index 6503e9e825..0000000000 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs +++ /dev/null @@ -1,324 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.Linq; -using Avalonia.Controls.Utils; - -namespace Avalonia.Collections -{ - public abstract class DataGridSortDescription - { - public virtual string PropertyPath => null; - - public virtual ListSortDirection Direction => ListSortDirection.Ascending; - public bool HasPropertyPath => !String.IsNullOrEmpty(PropertyPath); - public abstract IComparer Comparer { get; } - - public virtual IOrderedEnumerable OrderBy(IEnumerable seq) - { - return seq.OrderBy(o => o, Comparer); - } - public virtual IOrderedEnumerable ThenBy(IOrderedEnumerable seq) - { - return seq.ThenBy(o => o, Comparer); - } - - public virtual DataGridSortDescription SwitchSortDirection() - { - return this; - } - - internal virtual void Initialize(Type itemType) - { } - - private static object InvokePath(object item, string propertyPath, Type propertyType) - { - object propertyValue = TypeHelper.GetNestedPropertyValue(item, propertyPath, propertyType, out Exception exception); - if (exception != null) - { - throw exception; - } - return propertyValue; - } - - /// - /// Creates a comparer class that takes in a CultureInfo as a parameter, - /// which it will use when comparing strings. - /// - private class CultureSensitiveComparer : Comparer - { - /// - /// Private accessor for the CultureInfo of our comparer - /// - private CultureInfo _culture; - - /// - /// Creates a comparer which will respect the CultureInfo - /// that is passed in when comparing strings. - /// - /// The CultureInfo to use in string comparisons - public CultureSensitiveComparer(CultureInfo culture) - : base() - { - _culture = culture ?? CultureInfo.InvariantCulture; - } - - /// - /// Compares two objects and returns a value indicating whether one is less than, equal to or greater than the other. - /// - /// first item to compare - /// second item to compare - /// Negative number if x is less than y, zero if equal, and a positive number if x is greater than y - /// - /// Compares the 2 items using the specified CultureInfo for string and using the default object comparer for all other objects. - /// - public override int Compare(object x, object y) - { - if (x == null) - { - if (y != null) - { - return -1; - } - return 0; - } - if (y == null) - { - return 1; - } - - // at this point x and y are not null - if (x.GetType() == typeof(string) && y.GetType() == typeof(string)) - { - return _culture.CompareInfo.Compare((string)x, (string)y); - } - else - { - return Comparer.Default.Compare(x, y); - } - } - - } - - private class DataGridPathSortDescription : DataGridSortDescription - { - private readonly ListSortDirection _direction; - private readonly string _propertyPath; - private readonly Lazy _cultureSensitiveComparer; - private readonly Lazy> _comparer; - private Type _propertyType; - private IComparer _internalComparer; - private IComparer _internalComparerTyped; - private IComparer InternalComparer - { - get - { - if (_internalComparerTyped == null && _internalComparer != null) - { - if (_internalComparer is IComparer c) - _internalComparerTyped = c; - else - _internalComparerTyped = Comparer.Create((x, y) => _internalComparer.Compare(x, y)); - } - - return _internalComparerTyped; - } - } - - public override string PropertyPath => _propertyPath; - public override IComparer Comparer => _comparer.Value; - public override ListSortDirection Direction => _direction; - - public DataGridPathSortDescription(string propertyPath, ListSortDirection direction, IComparer internalComparer, CultureInfo culture) - { - _propertyPath = propertyPath; - _direction = direction; - _cultureSensitiveComparer = new Lazy(() => new CultureSensitiveComparer(culture ?? CultureInfo.CurrentCulture)); - _internalComparer = internalComparer; - _comparer = new Lazy>(() => Comparer.Create((x, y) => Compare(x, y))); - } - private DataGridPathSortDescription(DataGridPathSortDescription inner, ListSortDirection direction) - { - _propertyPath = inner._propertyPath; - _direction = direction; - _propertyType = inner._propertyType; - _cultureSensitiveComparer = inner._cultureSensitiveComparer; - _internalComparer = inner._internalComparer; - _internalComparerTyped = inner._internalComparerTyped; - - _comparer = new Lazy>(() => Comparer.Create((x, y) => Compare(x, y))); - } - - private object GetValue(object o) - { - if (o == null) - return null; - - if (HasPropertyPath) - return InvokePath(o, _propertyPath, _propertyType); - - if (_propertyType == o.GetType()) - return o; - else - return null; - } - - private IComparer GetComparerForType(Type type) - { - if (type == typeof(string)) - return _cultureSensitiveComparer.Value; - else - return GetComparerForNotStringType(type); - } - - internal static IComparer GetComparerForNotStringType(Type type) - { -#if NET6_0_OR_GREATER - if(System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported == false) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && type.GetGenericArguments()[0].IsAssignableTo(typeof(IComparable))) - return Comparer.Create((x, y) => - { - if (x == null) - return y == null ? 0 : -1; - else - return (x as IComparable)!.CompareTo(y); - }); - else if (type.IsAssignableTo(typeof(IComparable))) //enum should be here - return Comparer.Create((x, y) => (x as IComparable)!.CompareTo(y)); - else - return Comparer.Create((x, y) => 0); //avoid using reflection to avoid crash on AOT - } - else -#endif - return (typeof(Comparer<>).MakeGenericType(type).GetProperty("Default")).GetValue(null, null) as IComparer; - - } - - private Type GetPropertyType(object o) - { - return o.GetType().GetNestedPropertyType(_propertyPath); - } - - private int Compare(object x, object y) - { - int result = 0; - - if(_propertyType == null) - { - if(x != null) - { - _propertyType = GetPropertyType(x); - } - if(_propertyType == null && y != null) - { - _propertyType = GetPropertyType(y); - } - } - - object v1 = GetValue(x); - object v2 = GetValue(y); - - if (_propertyType != null && _internalComparer == null) - _internalComparer = GetComparerForType(_propertyType); - - result = _internalComparer?.Compare(v1, v2) ?? 0; - - if (Direction == ListSortDirection.Descending) - return -result; - else - return result; - } - - internal override void Initialize(Type itemType) - { - base.Initialize(itemType); - - if(_propertyType == null) - _propertyType = itemType.GetNestedPropertyType(_propertyPath); - if (_internalComparer == null && _propertyType != null) - _internalComparer = GetComparerForType(_propertyType); - } - public override IOrderedEnumerable OrderBy(IEnumerable seq) - { - if (Direction == ListSortDirection.Descending) - { - return seq.OrderByDescending(o => GetValue(o), InternalComparer); - } - else - { - return seq.OrderBy(o => GetValue(o), InternalComparer); - } - } - public override IOrderedEnumerable ThenBy(IOrderedEnumerable seq) - { - if (Direction == ListSortDirection.Descending) - { - return seq.ThenByDescending(o => GetValue(o), InternalComparer); - } - else - { - return seq.ThenBy(o => GetValue(o), InternalComparer); - } - } - - public override DataGridSortDescription SwitchSortDirection() - { - var newDirection = _direction == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending; - return new DataGridPathSortDescription(this, newDirection); - } - } - - public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction = ListSortDirection.Ascending, CultureInfo culture = null) - { - return new DataGridPathSortDescription(propertyPath, direction, null, culture); - } - - public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction, IComparer comparer) - { - return new DataGridPathSortDescription(propertyPath, direction, comparer, null); - } - - public static DataGridSortDescription FromComparer(IComparer comparer, ListSortDirection direction = ListSortDirection.Ascending) - { - return new DataGridComparerSortDescription(comparer, direction); - } - } - - public class DataGridComparerSortDescription : DataGridSortDescription - { - private readonly IComparer _innerComparer; - private readonly ListSortDirection _direction; - private readonly IComparer _comparer; - - public IComparer SourceComparer => _innerComparer; - public override IComparer Comparer => _comparer; - public override ListSortDirection Direction => _direction; - public DataGridComparerSortDescription(IComparer comparer, ListSortDirection direction) - { - _innerComparer = comparer; - _direction = direction; - _comparer = Comparer.Create((x, y) => Compare(x, y)); - } - - private int Compare(object x, object y) - { - int result = _innerComparer.Compare(x, y); - - if (Direction == ListSortDirection.Descending) - return -result; - else - return result; - } - public override DataGridSortDescription SwitchSortDirection() - { - var newDirection = _direction == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending; - return new DataGridComparerSortDescription(_innerComparer, newDirection); - } - } - - public class DataGridSortDescriptionCollection : AvaloniaList - { } -} diff --git a/src/Avalonia.Controls.DataGrid/Collections/IDataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/IDataGridCollectionView.cs deleted file mode 100644 index 07da567849..0000000000 --- a/src/Avalonia.Controls.DataGrid/Collections/IDataGridCollectionView.cs +++ /dev/null @@ -1,233 +0,0 @@ -// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Globalization; -using System.Text; - -namespace Avalonia.Collections -{ - /// Provides data for the event. - public class DataGridCurrentChangingEventArgs : EventArgs - { - private bool _cancel; - private bool _isCancelable; - - /// Initializes a new instance of the class and sets the property to true. - public DataGridCurrentChangingEventArgs() - { - Initialize(true); - } - - /// Initializes a new instance of the class and sets the property to the specified value. - /// true to disable the ability to cancel a change; false to enable cancellation. - public DataGridCurrentChangingEventArgs(bool isCancelable) - { - Initialize(isCancelable); - } - - private void Initialize(bool isCancelable) - { - _isCancelable = isCancelable; - } - - /// Gets a value that indicates whether the change can be canceled. - /// true if the event can be canceled; false if the event cannot be canceled. - public bool IsCancelable - { - get - { - return _isCancelable; - } - } - - /// Gets or sets a value that indicates whether the change should be canceled. - /// true if the event should be canceled; otherwise, false. The default is false. - /// The property value is false. - public bool Cancel - { - get - { - return _cancel; - } - set - { - if (IsCancelable) - _cancel = value; - else if (value) - throw new InvalidOperationException("CurrentChanging Cannot Be Canceled"); - } - } - } - - /// Enables collections to have the functionalities of current record management, custom sorting, filtering, and grouping. - public interface IDataGridCollectionView : IEnumerable, INotifyCollectionChanged - { - /// Gets or sets the cultural information for any operations of the view that may differ by culture, such as sorting. - /// The culture information to use during culture-sensitive operations. - CultureInfo Culture { get; set; } - - /// Indicates whether the specified item belongs to this collection view. - /// true if the item belongs to this collection view; otherwise, false. - /// The object to check. - bool Contains(object item); - - /// Gets the underlying collection. - /// The underlying collection. - IEnumerable SourceCollection { get; } - - /// Gets or sets a callback that is used to determine whether an item is appropriate for inclusion in the view. - /// A method that is used to determine whether an item is appropriate for inclusion in the view. - Func Filter { get; set; } - - /// Gets a value that indicates whether this view supports filtering by way of the property. - /// true if this view supports filtering; otherwise, false. - bool CanFilter { get; } - - /// Gets a collection of instances that describe how the items in the collection are sorted in the view. - /// A collection of values that describe how the items in the collection are sorted in the view. - DataGridSortDescriptionCollection SortDescriptions { get; } - - /// Gets a value that indicates whether this view supports sorting by way of the property. - /// true if this view supports sorting; otherwise, false. - bool CanSort { get; } - - /// Gets a value that indicates whether this view supports grouping by way of the property. - /// true if this view supports grouping; otherwise, false. - bool CanGroup { get; } - - /// Gets a collection of objects that describe how the items in the collection are grouped in the view. - /// A collection of objects that describe how the items in the collection are grouped in the view. - //ObservableCollection GroupDescriptions { get; } - - bool IsGrouping { get; } - int GroupingDepth { get; } - string GetGroupingPropertyNameAtDepth(int level); - - /// Gets the top-level groups. - /// A read-only collection of the top-level groups or null if there are no groups. - IAvaloniaReadOnlyList Groups { get; } - - /// Gets a value that indicates whether the view is empty. - /// true if the view is empty; otherwise, false. - bool IsEmpty { get; } - - /// Recreates the view. - void Refresh(); - - /// Enters a defer cycle that you can use to merge changes to the view and delay automatic refresh. - /// The typical usage is to create a using scope with an implementation of this method and then include multiple view-changing calls within the scope. The implementation should delay automatic refresh until after the using scope exits. - IDisposable DeferRefresh(); - - /// Gets the current item in the view. - /// The current item in the view or null if there is no current item. - object CurrentItem { get; } - - /// Gets the ordinal position of the in the view. - /// The ordinal position of the in the view. - int CurrentPosition { get; } - - /// Gets a value that indicates whether the of the view is beyond the end of the collection. - /// true if the of the view is beyond the end of the collection; otherwise, false. - bool IsCurrentAfterLast { get; } - - /// Gets a value that indicates whether the of the view is beyond the start of the collection. - /// true if the of the view is beyond the start of the collection; otherwise, false. - bool IsCurrentBeforeFirst { get; } - - /// Sets the first item in the view as the . - /// true if the resulting is an item in the view; otherwise, false. - bool MoveCurrentToFirst(); - - /// Sets the last item in the view as the . - /// true if the resulting is an item in the view; otherwise, false. - bool MoveCurrentToLast(); - - /// Sets the item after the in the view as the . - /// true if the resulting is an item in the view; otherwise, false. - bool MoveCurrentToNext(); - - /// Sets the item before the in the view to the . - /// true if the resulting is an item in the view; otherwise, false. - bool MoveCurrentToPrevious(); - - /// Sets the specified item in the view as the . - /// true if the resulting is an item in the view; otherwise, false. - /// The item to set as the current item. - bool MoveCurrentTo(object item); - - /// Sets the item at the specified index to be the in the view. - /// true if the resulting is an item in the view; otherwise, false. - /// The index to set the to. - bool MoveCurrentToPosition(int position); - - /// Occurs before the current item changes. - event EventHandler CurrentChanging; - - /// Occurs after the current item has been changed. - event EventHandler CurrentChanged; - } - internal interface IDataGridEditableCollectionView - { - /// Gets a value that indicates whether a new item can be added to the collection. - /// true if a new item can be added to the collection; otherwise, false. - bool CanAddNew { get; } - - /// Adds a new item to the underlying collection. - /// The new item that is added to the collection. - object AddNew(); - - /// Ends the add transaction and saves the pending new item. - void CommitNew(); - - /// Ends the add transaction and discards the pending new item. - void CancelNew(); - - /// Gets a value that indicates whether an add transaction is in progress. - /// true if an add transaction is in progress; otherwise, false. - bool IsAddingNew { get; } - - /// Gets the item that is being added during the current add transaction. - /// The item that is being added if is true; otherwise, null. - object CurrentAddItem { get; } - - /// Gets a value that indicates whether an item can be removed from the collection. - /// true if an item can be removed from the collection; otherwise, false. - bool CanRemove { get; } - - /// Removes the item at the specified position from the collection. - /// Index of item to remove. - void RemoveAt(int index); - - /// Removes the specified item from the collection. - /// The item to remove. - void Remove(object item); - - /// Begins an edit transaction on the specified item. - /// The item to edit. - void EditItem(object item); - - /// Ends the edit transaction and saves the pending changes. - void CommitEdit(); - - /// Ends the edit transaction and, if possible, restores the original value of the item. - void CancelEdit(); - - /// Gets a value that indicates whether the collection view can discard pending changes and restore the original values of an edited object. - /// true if the collection view can discard pending changes and restore the original values of an edited object; otherwise, false. - bool CanCancelEdit { get; } - - /// Gets a value that indicates whether an edit transaction is in progress. - /// true if an edit transaction is in progress; otherwise, false. - bool IsEditingItem { get; } - - /// Gets the item in the collection that is being edited. - /// The item that is being edited if is true; otherwise, null. - object CurrentEditItem { get; } - } -} diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs deleted file mode 100644 index 2542b98df3..0000000000 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ /dev/null @@ -1,6236 +0,0 @@ -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using Avalonia.Collections; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Templates; -using Avalonia.Data; -using Avalonia.Input; -using Avalonia.Interactivity; -using Avalonia.Media; -using Avalonia.VisualTree; -using Avalonia.Utilities; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; -using System.Text; -using System.Linq; -using Avalonia.Input.Platform; -using System.ComponentModel.DataAnnotations; -using Avalonia.Automation.Peers; -using Avalonia.Controls.Automation.Peers; -using Avalonia.Controls.Utils; -using Avalonia.Layout; -using Avalonia.Controls.Metadata; -using Avalonia.Input.GestureRecognizers; -using Avalonia.Styling; -using Avalonia.Reactive; - -namespace Avalonia.Controls -{ - /// - /// Displays data in a customizable grid. - /// - [TemplatePart(DATAGRID_elementBottomRightCornerHeaderName, typeof(Visual))] - [TemplatePart(DATAGRID_elementColumnHeadersPresenterName, typeof(DataGridColumnHeadersPresenter))] - [TemplatePart(DATAGRID_elementFrozenColumnScrollBarSpacerName, typeof(Control))] - [TemplatePart(DATAGRID_elementHorizontalScrollbarName, typeof(ScrollBar))] - [TemplatePart(DATAGRID_elementRowsPresenterName, typeof(DataGridRowsPresenter))] - [TemplatePart(DATAGRID_elementTopLeftCornerHeaderName, typeof(ContentControl))] - [TemplatePart(DATAGRID_elementTopRightCornerHeaderName, typeof(ContentControl))] - [TemplatePart(DATAGRID_elementVerticalScrollbarName, typeof(ScrollBar))] - [PseudoClasses(":invalid", ":empty-rows", ":empty-columns")] - public partial class DataGrid : TemplatedControl - { - private const string DATAGRID_elementRowsPresenterName = "PART_RowsPresenter"; - private const string DATAGRID_elementColumnHeadersPresenterName = "PART_ColumnHeadersPresenter"; - private const string DATAGRID_elementFrozenColumnScrollBarSpacerName = "PART_FrozenColumnScrollBarSpacer"; - private const string DATAGRID_elementHorizontalScrollbarName = "PART_HorizontalScrollbar"; - private const string DATAGRID_elementTopLeftCornerHeaderName = "PART_TopLeftCornerHeader"; - private const string DATAGRID_elementTopRightCornerHeaderName = "PART_TopRightCornerHeader"; - private const string DATAGRID_elementBottomRightCornerHeaderName = "PART_BottomRightCorner"; - private const string DATAGRID_elementVerticalScrollbarName = "PART_VerticalScrollbar"; - internal const bool DATAGRID_defaultCanUserReorderColumns = true; - internal const bool DATAGRID_defaultCanUserResizeColumns = true; - internal const bool DATAGRID_defaultCanUserSortColumns = true; - - /// - /// The default order to use for columns when there is no - /// value available for the property. - /// - /// - /// The value of 10,000 comes from the DataAnnotations spec, allowing - /// some properties to be ordered at the beginning and some at the end. - /// - private const int DATAGRID_defaultColumnDisplayOrder = 10000; - - private const double DATAGRID_horizontalGridLinesThickness = 1; - private const double DATAGRID_minimumRowHeaderWidth = 4; - private const double DATAGRID_minimumColumnHeaderHeight = 4; - internal const double DATAGRID_maximumStarColumnWidth = 10000; - internal const double DATAGRID_minimumStarColumnWidth = 0.001; - private const double DATAGRID_mouseWheelDelta = 50.0; - private const double DATAGRID_maxHeadersThickness = 32768; - - private const double DATAGRID_defaultRowHeight = 22; - internal const double DATAGRID_defaultRowGroupSublevelIndent = 20; - private const double DATAGRID_defaultMinColumnWidth = 20; - private const double DATAGRID_defaultMaxColumnWidth = double.PositiveInfinity; - - private List _bindingValidationErrors; - private IDisposable _validationSubscription; - - private INotifyCollectionChanged _topLevelGroup; - private ContentControl _clipboardContentControl; - - private Visual _bottomRightCorner; - private DataGridColumnHeadersPresenter _columnHeadersPresenter; - private DataGridRowsPresenter _rowsPresenter; - private ScrollBar _vScrollBar; - private ScrollBar _hScrollBar; - - private ContentControl _topLeftCornerHeader; - private ContentControl _topRightCornerHeader; - private Control _frozenColumnScrollBarSpacer; - - // the sum of the widths in pixels of the scrolling columns preceding - // the first displayed scrolling column - private double _horizontalOffset; - - // the number of pixels of the firstDisplayedScrollingCol which are not displayed - private double _negHorizontalOffset; - private byte _autoGeneratingColumnOperationCount; - private bool _areHandlersSuspended; - private bool _autoSizingColumns; - private IndexToValueTable _collapsedSlotsTable; - private Control _clickedElement; - - // used to store the current column during a Reset - private int _desiredCurrentColumnIndex; - private int _editingColumnIndex; - - // this is a workaround only for the scenarios where we need it, it is not all encompassing nor always updated - private RoutedEventArgs _editingEventArgs; - private bool _executingLostFocusActions; - private bool _flushCurrentCellChanged; - private bool _focusEditingControl; - private Visual _focusedObject; - private byte _horizontalScrollChangesIgnored; - private DataGridRow _focusedRow; - private bool _ignoreNextScrollBarsLayout; - - // Nth row of rows 0..N that make up the RowHeightEstimate - private int _lastEstimatedRow; - private List _loadedRows; - - // prevents reentry into the VerticalScroll event handler - private Queue _lostFocusActions; - private int _noSelectionChangeCount; - private int _noCurrentCellChangeCount; - private bool _makeFirstDisplayedCellCurrentCellPending; - private bool _measured; - private int? _mouseOverRowIndex; // -1 is used for the 'new row' - private DataGridColumn _previousCurrentColumn; - private object _previousCurrentItem; - private double[] _rowGroupHeightsByLevel; - private double _rowHeaderDesiredWidth; - private Size? _rowsPresenterAvailableSize; - private bool _scrollingByHeight; - private IndexToValueTable _showDetailsTable; - private bool _successfullyUpdatedSelection; - private DataGridSelectedItemsCollection _selectedItems; - private bool _temporarilyResetCurrentCell; - private object _uneditedValue; // Represents the original current cell value at the time it enters editing mode. - - // An approximation of the sum of the heights in pixels of the scrolling rows preceding - // the first displayed scrolling row. Since the scrolled off rows are discarded, the grid - // does not know their actual height. The heights used for the approximation are the ones - // set as the rows were scrolled off. - private double _verticalOffset; - private byte _verticalScrollChangesIgnored; - - public event EventHandler HorizontalScroll; - public event EventHandler VerticalScroll; - - /// - /// Identifies the CanUserReorderColumns dependency property. - /// - public static readonly StyledProperty CanUserReorderColumnsProperty = - AvaloniaProperty.Register(nameof(CanUserReorderColumns)); - - /// - /// Gets or sets a value that indicates whether the user can change - /// the column display order by dragging column headers with the mouse. - /// - public bool CanUserReorderColumns - { - get { return GetValue(CanUserReorderColumnsProperty); } - set { SetValue(CanUserReorderColumnsProperty, value); } - } - - /// - /// Identifies the CanUserResizeColumns dependency property. - /// - public static readonly StyledProperty CanUserResizeColumnsProperty = - AvaloniaProperty.Register(nameof(CanUserResizeColumns)); - - /// - /// Gets or sets a value that indicates whether the user can adjust column widths using the mouse. - /// - public bool CanUserResizeColumns - { - get { return GetValue(CanUserResizeColumnsProperty); } - set { SetValue(CanUserResizeColumnsProperty, value); } - } - - /// - /// Identifies the CanUserSortColumns dependency property. - /// - public static readonly StyledProperty CanUserSortColumnsProperty = - AvaloniaProperty.Register(nameof(CanUserSortColumns), true); - - /// - /// Gets or sets a value that indicates whether the user can sort columns by clicking the column header. - /// - public bool CanUserSortColumns - { - get { return GetValue(CanUserSortColumnsProperty); } - set { SetValue(CanUserSortColumnsProperty, value); } - } - - /// - /// Identifies the ColumnHeaderHeight dependency property. - /// - public static readonly StyledProperty ColumnHeaderHeightProperty = - AvaloniaProperty.Register( - nameof(ColumnHeaderHeight), - defaultValue: double.NaN, - validate: IsValidColumnHeaderHeight); - - private static bool IsValidColumnHeaderHeight(double value) - { - return double.IsNaN(value) || - (value >= DATAGRID_minimumColumnHeaderHeight && value <= DATAGRID_maxHeadersThickness); - } - - /// - /// Gets or sets the height of the column headers row. - /// - public double ColumnHeaderHeight - { - get { return GetValue(ColumnHeaderHeightProperty); } - set { SetValue(ColumnHeaderHeightProperty, value); } - } - - /// - /// Identifies the ColumnWidth dependency property. - /// - public static readonly StyledProperty ColumnWidthProperty = - AvaloniaProperty.Register(nameof(ColumnWidth), defaultValue: DataGridLength.Auto); - - /// - /// Identifies the dependency property. - /// - public static readonly StyledProperty RowThemeProperty = - AvaloniaProperty.Register(nameof(RowTheme)); - - /// - /// Gets or sets the theme applied to all rows. - /// - public ControlTheme RowTheme - { - get { return GetValue(RowThemeProperty); } - set { SetValue(RowThemeProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - public static readonly StyledProperty CellThemeProperty = - AvaloniaProperty.Register(nameof(CellTheme)); - - /// - /// Gets or sets the theme applied to all cells. - /// - public ControlTheme CellTheme - { - get { return GetValue(CellThemeProperty); } - set { SetValue(CellThemeProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - public static readonly StyledProperty ColumnHeaderThemeProperty = - AvaloniaProperty.Register(nameof(ColumnHeaderTheme)); - - /// - /// Gets or sets the theme applied to all column headers. - /// - public ControlTheme ColumnHeaderTheme - { - get { return GetValue(ColumnHeaderThemeProperty); } - set { SetValue(ColumnHeaderThemeProperty, value); } - } - - /// - /// Identifies the dependency property. - /// - public static readonly StyledProperty RowGroupThemeProperty = - AvaloniaProperty.Register(nameof(RowGroupTheme)); - - /// - /// Gets or sets the theme applied to all row groups. - /// - public ControlTheme RowGroupTheme - { - get { return GetValue(RowGroupThemeProperty); } - set { SetValue(RowGroupThemeProperty, value); } - } - - /// - /// Gets or sets the standard width or automatic sizing mode of columns in the control. - /// - public DataGridLength ColumnWidth - { - get { return GetValue(ColumnWidthProperty); } - set { SetValue(ColumnWidthProperty, value); } - } - - public static readonly StyledProperty FrozenColumnCountProperty = - AvaloniaProperty.Register( - nameof(FrozenColumnCount), - validate: ValidateFrozenColumnCount); - - /// - /// Gets or sets the number of columns that the user cannot scroll horizontally. - /// - public int FrozenColumnCount - { - get { return GetValue(FrozenColumnCountProperty); } - set { SetValue(FrozenColumnCountProperty, value); } - } - - private static bool ValidateFrozenColumnCount(int value) => value >= 0; - - public static readonly StyledProperty GridLinesVisibilityProperty = - AvaloniaProperty.Register(nameof(GridLinesVisibility)); - - /// - /// Gets or sets a value that indicates which grid lines separating inner cells are shown. - /// - public DataGridGridLinesVisibility GridLinesVisibility - { - get { return GetValue(GridLinesVisibilityProperty); } - set { SetValue(GridLinesVisibilityProperty, value); } - } - - public static readonly StyledProperty HeadersVisibilityProperty = - AvaloniaProperty.Register(nameof(HeadersVisibility)); - - /// - /// Gets or sets a value that indicates the visibility of row and column headers. - /// - public DataGridHeadersVisibility HeadersVisibility - { - get { return GetValue(HeadersVisibilityProperty); } - set { SetValue(HeadersVisibilityProperty, value); } - } - - public static readonly StyledProperty HorizontalGridLinesBrushProperty = - AvaloniaProperty.Register(nameof(HorizontalGridLinesBrush)); - - /// - /// Gets or sets the that is used to paint grid lines separating rows. - /// - public IBrush HorizontalGridLinesBrush - { - get { return GetValue(HorizontalGridLinesBrushProperty); } - set { SetValue(HorizontalGridLinesBrushProperty, value); } - } - - public static readonly StyledProperty HorizontalScrollBarVisibilityProperty = - AvaloniaProperty.Register(nameof(HorizontalScrollBarVisibility)); - - /// - /// Gets or sets a value that indicates how the horizontal scroll bar is displayed. - /// - public ScrollBarVisibility HorizontalScrollBarVisibility - { - get { return GetValue(HorizontalScrollBarVisibilityProperty); } - set { SetValue(HorizontalScrollBarVisibilityProperty, value); } - } - - public static readonly StyledProperty IsReadOnlyProperty = - AvaloniaProperty.Register(nameof(IsReadOnly)); - - /// - /// Gets or sets a value that indicates whether the user can edit the values in the control. - /// - public bool IsReadOnly - { - get { return GetValue(IsReadOnlyProperty); } - set { SetValue(IsReadOnlyProperty, value); } - } - - public static readonly StyledProperty AreRowGroupHeadersFrozenProperty = - AvaloniaProperty.Register( - nameof(AreRowGroupHeadersFrozen), - defaultValue: true); - - /// - /// Gets or sets a value that indicates whether the row group header sections - /// remain fixed at the width of the display area or can scroll horizontally. - /// - public bool AreRowGroupHeadersFrozen - { - get { return GetValue(AreRowGroupHeadersFrozenProperty); } - set { SetValue(AreRowGroupHeadersFrozenProperty, value); } - } - - private void OnAreRowGroupHeadersFrozenChanged(AvaloniaPropertyChangedEventArgs e) - { - var value = (bool)e.NewValue; - ProcessFrozenColumnCount(); - - // Update elements in the RowGroupHeader that were previously frozen - if (value) - { - if (_rowsPresenter != null) - { - foreach (Control element in _rowsPresenter.Children) - { - if (element is DataGridRowGroupHeader groupHeader) - { - groupHeader.ClearFrozenStates(); - } - } - } - } - } - - /// - /// Defines the property. - /// - public static readonly AttachedProperty IsScrollInertiaEnabledProperty = - ScrollViewer.IsScrollInertiaEnabledProperty.AddOwner(); - - /// - /// Gets or sets whether scroll gestures should include inertia in their behavior and value. - /// - public bool IsScrollInertiaEnabled - { - get => GetValue(IsScrollInertiaEnabledProperty); - set => SetValue(IsScrollInertiaEnabledProperty, value); - } - - private bool _isValid = true; - - public static readonly DirectProperty IsValidProperty = - AvaloniaProperty.RegisterDirect( - nameof(IsValid), - o => o.IsValid); - - public bool IsValid - { - get { return _isValid; } - internal set - { - SetAndRaise(IsValidProperty, ref _isValid, value); - PseudoClasses.Set(":invalid", !value); - } - } - - public static readonly StyledProperty MaxColumnWidthProperty = - AvaloniaProperty.Register( - nameof(MaxColumnWidth), - defaultValue: DATAGRID_defaultMaxColumnWidth, - validate: IsValidColumnWidth); - - private static bool IsValidColumnWidth(double value) - { - return !double.IsNaN(value) && value > 0; - } - - /// - /// Gets or sets the maximum width of columns in the . - /// - public double MaxColumnWidth - { - get { return GetValue(MaxColumnWidthProperty); } - set { SetValue(MaxColumnWidthProperty, value); } - } - - public static readonly StyledProperty MinColumnWidthProperty = - AvaloniaProperty.Register( - nameof(MinColumnWidth), - defaultValue: DATAGRID_defaultMinColumnWidth, - validate: IsValidMinColumnWidth); - - private static bool IsValidMinColumnWidth(double value) - { - return !double.IsNaN(value) && !double.IsPositiveInfinity(value) && value >= 0; - } - - /// - /// Gets or sets the minimum width of columns in the . - /// - public double MinColumnWidth - { - get { return GetValue(MinColumnWidthProperty); } - set { SetValue(MinColumnWidthProperty, value); } - } - - public static readonly StyledProperty RowBackgroundProperty = - AvaloniaProperty.Register(nameof(RowBackground)); - - /// - /// Gets or sets the that is used to paint row backgrounds. - /// - public IBrush RowBackground - { - get { return GetValue(RowBackgroundProperty); } - set { SetValue(RowBackgroundProperty, value); } - } - - public static readonly StyledProperty RowHeightProperty = - AvaloniaProperty.Register( - nameof(RowHeight), - defaultValue: double.NaN, - validate: IsValidRowHeight); - private static bool IsValidRowHeight(double value) - { - return double.IsNaN(value) || - (value >= DataGridRow.DATAGRIDROW_minimumHeight && - value <= DataGridRow.DATAGRIDROW_maximumHeight); - } - - /// - /// Gets or sets the standard height of rows in the control. - /// - public double RowHeight - { - get { return GetValue(RowHeightProperty); } - set { SetValue(RowHeightProperty, value); } - } - - public static readonly StyledProperty RowHeaderWidthProperty = - AvaloniaProperty.Register( - nameof(RowHeaderWidth), - defaultValue: double.NaN, - validate: IsValidRowHeaderWidth); - private static bool IsValidRowHeaderWidth(double value) - { - return double.IsNaN(value) || - (value >= DATAGRID_minimumRowHeaderWidth && - value <= DATAGRID_maxHeadersThickness); - } - - /// - /// Gets or sets the width of the row header column. - /// - public double RowHeaderWidth - { - get { return GetValue(RowHeaderWidthProperty); } - set { SetValue(RowHeaderWidthProperty, value); } - } - - public static readonly StyledProperty SelectionModeProperty = - AvaloniaProperty.Register(nameof(SelectionMode)); - - /// - /// Gets or sets the selection behavior of the data grid. - /// - public DataGridSelectionMode SelectionMode - { - get { return GetValue(SelectionModeProperty); } - set { SetValue(SelectionModeProperty, value); } - } - - public static readonly StyledProperty VerticalGridLinesBrushProperty = - AvaloniaProperty.Register(nameof(VerticalGridLinesBrush)); - - /// - /// Gets or sets the that is used to paint grid lines separating columns. - /// - public IBrush VerticalGridLinesBrush - { - get { return GetValue(VerticalGridLinesBrushProperty); } - set { SetValue(VerticalGridLinesBrushProperty, value); } - } - - public static readonly StyledProperty VerticalScrollBarVisibilityProperty = - AvaloniaProperty.Register(nameof(VerticalScrollBarVisibility)); - - /// - /// Gets or sets a value that indicates how the vertical scroll bar is displayed. - /// - public ScrollBarVisibility VerticalScrollBarVisibility - { - get { return GetValue(VerticalScrollBarVisibilityProperty); } - set { SetValue(VerticalScrollBarVisibilityProperty, value); } - } - - public static readonly StyledProperty> DropLocationIndicatorTemplateProperty = - AvaloniaProperty.Register>(nameof(DropLocationIndicatorTemplate)); - - /// - /// Gets or sets the template that is used when rendering the column headers. - /// - public ITemplate DropLocationIndicatorTemplate - { - get { return GetValue(DropLocationIndicatorTemplateProperty); } - set { SetValue(DropLocationIndicatorTemplateProperty, value); } - } - - private int _selectedIndex = -1; - private object _selectedItem; - - public static readonly DirectProperty SelectedIndexProperty = - AvaloniaProperty.RegisterDirect( - nameof(SelectedIndex), - o => o.SelectedIndex, - (o, v) => o.SelectedIndex = v, - defaultBindingMode: BindingMode.TwoWay); - - /// - /// Gets or sets the index of the current selection. - /// - /// - /// The index of the current selection, or -1 if the selection is empty. - /// - public int SelectedIndex - { - get { return _selectedIndex; } - set { SetAndRaise(SelectedIndexProperty, ref _selectedIndex, value); } - } - - public static readonly DirectProperty SelectedItemProperty = - AvaloniaProperty.RegisterDirect( - nameof(SelectedItem), - o => o.SelectedItem, - (o, v) => o.SelectedItem = v, - defaultBindingMode: BindingMode.TwoWay); - - /// - /// Gets or sets the data item corresponding to the selected row. - /// - public object SelectedItem - { - get { return _selectedItem; } - set { SetAndRaise(SelectedItemProperty, ref _selectedItem, value); } - } - - public static readonly StyledProperty ClipboardCopyModeProperty = - AvaloniaProperty.Register( - nameof(ClipboardCopyMode), - defaultValue: DataGridClipboardCopyMode.ExcludeHeader); - - /// - /// The property which determines how DataGrid content is copied to the Clipboard. - /// - public DataGridClipboardCopyMode ClipboardCopyMode - { - get { return GetValue(ClipboardCopyModeProperty); } - set { SetValue(ClipboardCopyModeProperty, value); } - } - - public static readonly StyledProperty AutoGenerateColumnsProperty = - AvaloniaProperty.Register(nameof(AutoGenerateColumns)); - - /// - /// Gets or sets a value that indicates whether columns are created - /// automatically when the property is set. - /// - public bool AutoGenerateColumns - { - get { return GetValue(AutoGenerateColumnsProperty); } - set { SetValue(AutoGenerateColumnsProperty, value); } - } - - private void OnAutoGenerateColumnsChanged(AvaloniaPropertyChangedEventArgs e) - { - var value = (bool)e.NewValue; - if (value) - { - InitializeElements(recycleRows: false); - } - else - { - RemoveAutoGeneratedColumns(); - } - } - - /// - /// Identifies the ItemsSource property. - /// - public static readonly StyledProperty ItemsSourceProperty = - AvaloniaProperty.Register(nameof(ItemsSource)); - - /// - /// Gets or sets a collection that is used to generate the content of the control. - /// - public IEnumerable ItemsSource - { - get => GetValue(ItemsSourceProperty); - set => SetValue(ItemsSourceProperty, value); - } - - public static readonly StyledProperty AreRowDetailsFrozenProperty = - AvaloniaProperty.Register(nameof(AreRowDetailsFrozen)); - - /// - /// Gets or sets a value that indicates whether the row details sections remain - /// fixed at the width of the display area or can scroll horizontally. - /// - public bool AreRowDetailsFrozen - { - get { return GetValue(AreRowDetailsFrozenProperty); } - set { SetValue(AreRowDetailsFrozenProperty, value); } - } - - public static readonly StyledProperty RowDetailsTemplateProperty = - AvaloniaProperty.Register(nameof(RowDetailsTemplate)); - - /// - /// Gets or sets the template that is used to display the content of the details section of rows. - /// - public IDataTemplate RowDetailsTemplate - { - get { return GetValue(RowDetailsTemplateProperty); } - set { SetValue(RowDetailsTemplateProperty, value); } - } - - public static readonly StyledProperty RowDetailsVisibilityModeProperty = - AvaloniaProperty.Register(nameof(RowDetailsVisibilityMode)); - - /// - /// Gets or sets a value that indicates when the details sections of rows are displayed. - /// - public DataGridRowDetailsVisibilityMode RowDetailsVisibilityMode - { - get { return GetValue(RowDetailsVisibilityModeProperty); } - set { SetValue(RowDetailsVisibilityModeProperty, value); } - } - - - public static readonly DirectProperty CollectionViewProperty = - AvaloniaProperty.RegisterDirect(nameof(CollectionView), - o => o.CollectionView); - - /// - /// Gets current . - /// - public IDataGridCollectionView CollectionView => - DataConnection.CollectionView; - - static DataGrid() - { - AffectsMeasure( - ColumnHeaderHeightProperty, - HorizontalScrollBarVisibilityProperty, - VerticalScrollBarVisibilityProperty); - - ItemsSourceProperty.Changed.AddClassHandler((x, e) => x.OnItemsSourcePropertyChanged(e)); - CanUserResizeColumnsProperty.Changed.AddClassHandler((x, e) => x.OnCanUserResizeColumnsChanged(e)); - ColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnColumnWidthChanged(e)); - FrozenColumnCountProperty.Changed.AddClassHandler((x, e) => x.OnFrozenColumnCountChanged(e)); - GridLinesVisibilityProperty.Changed.AddClassHandler((x, e) => x.OnGridLinesVisibilityChanged(e)); - HeadersVisibilityProperty.Changed.AddClassHandler((x, e) => x.OnHeadersVisibilityChanged(e)); - HorizontalGridLinesBrushProperty.Changed.AddClassHandler((x, e) => x.OnHorizontalGridLinesBrushChanged(e)); - IsReadOnlyProperty.Changed.AddClassHandler((x, e) => x.OnIsReadOnlyChanged(e)); - MaxColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnMaxColumnWidthChanged(e)); - MinColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnMinColumnWidthChanged(e)); - RowHeightProperty.Changed.AddClassHandler((x, e) => x.OnRowHeightChanged(e)); - RowHeaderWidthProperty.Changed.AddClassHandler((x, e) => x.OnRowHeaderWidthChanged(e)); - SelectionModeProperty.Changed.AddClassHandler((x, e) => x.OnSelectionModeChanged(e)); - VerticalGridLinesBrushProperty.Changed.AddClassHandler((x, e) => x.OnVerticalGridLinesBrushChanged(e)); - SelectedIndexProperty.Changed.AddClassHandler((x, e) => x.OnSelectedIndexChanged(e)); - SelectedItemProperty.Changed.AddClassHandler((x, e) => x.OnSelectedItemChanged(e)); - IsEnabledProperty.Changed.AddClassHandler((x, e) => x.DataGrid_IsEnabledChanged(e)); - AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler((x, e) => x.OnAreRowGroupHeadersFrozenChanged(e)); - RowDetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsTemplateChanged(e)); - RowDetailsVisibilityModeProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsVisibilityModeChanged(e)); - AutoGenerateColumnsProperty.Changed.AddClassHandler((x, e) => x.OnAutoGenerateColumnsChanged(e)); - - FocusableProperty.OverrideDefaultValue(true); - } - - /// - /// Initializes a new instance of the class. - /// - public DataGrid() - { - KeyDown += DataGrid_KeyDown; - KeyUp += DataGrid_KeyUp; - - //TODO: Check if override works - GotFocus += DataGrid_GotFocus; - LostFocus += DataGrid_LostFocus; - - _loadedRows = new List(); - _lostFocusActions = new Queue(); - _selectedItems = new DataGridSelectedItemsCollection(this); - RowGroupHeadersTable = new IndexToValueTable(); - _bindingValidationErrors = new List(); - - DisplayData = new DataGridDisplayData(this); - ColumnsInternal = CreateColumnsInstance(); - ColumnsInternal.CollectionChanged += ColumnsInternal_CollectionChanged; - - RowHeightEstimate = DATAGRID_defaultRowHeight; - RowDetailsHeightEstimate = 0; - _rowHeaderDesiredWidth = 0; - - DataConnection = new DataGridDataConnection(this); - _showDetailsTable = new IndexToValueTable(); - _collapsedSlotsTable = new IndexToValueTable(); - - AnchorSlot = -1; - _lastEstimatedRow = -1; - _editingColumnIndex = -1; - _mouseOverRowIndex = null; - CurrentCellCoordinates = new DataGridCellCoordinates(-1, -1); - - RowGroupHeaderHeightEstimate = DATAGRID_defaultRowHeight; - - UpdatePseudoClasses(); - } - - protected override AutomationPeer OnCreateAutomationPeer() - { - return new DataGridAutomationPeer(this); - } - - private void SetValueNoCallback(AvaloniaProperty property, T value, BindingPriority priority = BindingPriority.LocalValue) - { - _areHandlersSuspended = true; - try - { - SetValue(property, value, priority); - } - finally - { - _areHandlersSuspended = false; - } - } - - private void OnRowDetailsVisibilityModeChanged(AvaloniaPropertyChangedEventArgs e) - { - UpdateRowDetailsVisibilityMode((DataGridRowDetailsVisibilityMode)e.NewValue); - } - - private void OnRowDetailsTemplateChanged(AvaloniaPropertyChangedEventArgs e) - { - - // Update the RowDetails templates if necessary - if (_rowsPresenter != null) - { - foreach (DataGridRow row in GetAllRows()) - { - if (GetRowDetailsVisibility(row.Index)) - { - // DetailsPreferredHeight is initialized when the DetailsElement's size changes. - row.ApplyDetailsTemplate(initializeDetailsPreferredHeight: false); - } - } - } - - UpdateRowDetailsHeightEstimate(); - InvalidateMeasure(); - } - - /// - /// ItemsSourceProperty property changed handler. - /// - /// The event arguments. - private void OnItemsSourcePropertyChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended) - { - Debug.Assert(DataConnection != null); - - var oldCollectionView = DataConnection.CollectionView; - - var oldValue = (IEnumerable)e.OldValue; - var newItemsSource = (IEnumerable)e.NewValue; - - if (LoadingOrUnloadingRow) - { - SetValueNoCallback(ItemsSourceProperty, oldValue); - throw DataGridError.DataGrid.CannotChangeItemsWhenLoadingRows(); - } - - // Try to commit edit on the old DataSource, but force a cancel if it fails - if (!CommitEdit()) - { - CancelEdit(DataGridEditingUnit.Row, false); - } - - DataConnection.UnWireEvents(DataConnection.DataSource); - DataConnection.ClearDataProperties(); - ClearRowGroupHeadersTable(); - - // The old selected indexes are no longer relevant. There's a perf benefit from - // updating the selected indexes with a null DataSource, because we know that all - // of the previously selected indexes have been removed from selection - DataConnection.DataSource = null; - _selectedItems.UpdateIndexes(); - CoerceSelectedItem(); - - // Wrap an IEnumerable in an ICollectionView if it's not already one - bool setDefaultSelection = false; - if (newItemsSource is IDataGridCollectionView newCollectionView) - { - setDefaultSelection = true; - } - else - { - newCollectionView = newItemsSource is not null - ? DataGridDataConnection.CreateView(newItemsSource) - : default; - } - - DataConnection.DataSource = newCollectionView; - - if (oldCollectionView != DataConnection.CollectionView) - { - RaisePropertyChanged(CollectionViewProperty, - oldCollectionView, - newCollectionView); - } - - if (DataConnection.DataSource != null) - { - // Setup the column headers - if (DataConnection.DataType != null) - { - foreach (var column in ColumnsInternal.GetDisplayedColumns()) - { - if (column is DataGridBoundColumn boundColumn) - { - boundColumn.SetHeaderFromBinding(); - } - } - } - DataConnection.WireEvents(DataConnection.DataSource); - } - - // Wait for the current cell to be set before we raise any SelectionChanged events - _makeFirstDisplayedCellCurrentCellPending = true; - - // Clear out the old rows and remove the generated columns - ClearRows(false); //recycle - RemoveAutoGeneratedColumns(); - - // Set the SlotCount (from the data count and number of row group headers) before we make the default selection - PopulateRowGroupHeadersTable(); - SelectedItem = null; - if (DataConnection.CollectionView != null && setDefaultSelection) - { - SelectedItem = DataConnection.CollectionView.CurrentItem; - } - - // Treat this like the DataGrid has never been measured because all calculations at - // this point are invalid until the next layout cycle. For instance, the ItemsSource - // can be set when the DataGrid is not part of the visual tree - _measured = false; - InvalidateMeasure(); - - UpdatePseudoClasses(); - } - } - - private void ColumnsInternal_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Add - || e.Action == NotifyCollectionChangedAction.Remove - || e.Action == NotifyCollectionChangedAction.Reset) - { - UpdatePseudoClasses(); - } - } - - internal void UpdatePseudoClasses() - { - PseudoClasses.Set(":empty-columns", !ColumnsInternal.GetVisibleColumns().Any()); - PseudoClasses.Set(":empty-rows", !DataConnection.Any()); - } - - private void OnSelectedIndexChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended) - { - int index = (int)e.NewValue; - - // GetDataItem returns null if index is >= Count, we do not check newValue - // against Count here to avoid enumerating through an Enumerable twice - // Setting SelectedItem coerces the finally value of the SelectedIndex - object newSelectedItem = (index < 0) ? null : DataConnection.GetDataItem(index); - SelectedItem = newSelectedItem; - if (SelectedItem != newSelectedItem) - { - SetValueNoCallback(SelectedIndexProperty, (int)e.OldValue); - } - } - } - - private void OnSelectedItemChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended) - { - int rowIndex = (e.NewValue == null) ? -1 : DataConnection.IndexOf(e.NewValue); - if (rowIndex == -1) - { - // If the Item is null or it's not found, clear the Selection - if (!CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true)) - { - // Edited value couldn't be committed or aborted - SetValueNoCallback(SelectedItemProperty, e.OldValue); - return; - } - - // Clear all row selections - ClearRowSelection(resetAnchorSlot: true); - - if (DataConnection.CollectionView != null) - { - DataConnection.CollectionView.MoveCurrentTo(null); - } - } - else - { - int slot = SlotFromRowIndex(rowIndex); - if (slot != CurrentSlot) - { - if (!CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true)) - { - // Edited value couldn't be committed or aborted - SetValueNoCallback(SelectedItemProperty, e.OldValue); - return; - } - if (slot >= SlotCount || slot < -1) - { - if (DataConnection.CollectionView != null) - { - DataConnection.CollectionView.MoveCurrentToPosition(rowIndex); - } - } - } - - int oldSelectedIndex = SelectedIndex; - SetValueNoCallback(SelectedIndexProperty, rowIndex); - try - { - _noSelectionChangeCount++; - int columnIndex = CurrentColumnIndex; - - if (columnIndex == -1) - { - columnIndex = FirstDisplayedNonFillerColumnIndex; - } - if (IsSlotOutOfSelectionBounds(slot)) - { - ClearRowSelection(slotException: slot, setAnchorSlot: true); - return; - } - - UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false); - } - finally - { - NoSelectionChangeCount--; - } - - if (!_successfullyUpdatedSelection) - { - SetValueNoCallback(SelectedIndexProperty, oldSelectedIndex); - SetValueNoCallback(SelectedItemProperty, e.OldValue); - } - } - } - } - - private void OnVerticalGridLinesBrushChanged(AvaloniaPropertyChangedEventArgs e) - { - if (_rowsPresenter != null) - { - foreach (DataGridRow row in GetAllRows()) - { - row.EnsureGridLines(); - } - } - } - - private void OnSelectionModeChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended) - { - ClearRowSelection(resetAnchorSlot: true); - } - } - - private void OnRowHeaderWidthChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended) - { - EnsureRowHeaderWidth(); - } - } - - private void OnRowHeightChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended) - { - InvalidateRowHeightEstimate(); - // Re-measure all the rows due to the Height change - InvalidateRowsMeasure(invalidateIndividualElements: true); - // DataGrid needs to update the layout information and the ScrollBars - InvalidateMeasure(); - } - } - - private void OnMinColumnWidthChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended) - { - double oldValue = (double)e.OldValue; - foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns()) - { - OnColumnMinWidthChanged(column, Math.Max(column.MinWidth, oldValue)); - } - } - } - - private void OnMaxColumnWidthChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended) - { - var oldValue = (double)e.OldValue; - foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns()) - { - OnColumnMaxWidthChanged(column, Math.Min(column.MaxWidth, oldValue)); - } - } - } - - private void OnIsReadOnlyChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended) - { - var value = (bool)e.NewValue; - if (value && !CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true)) - { - CancelEdit(DataGridEditingUnit.Row, raiseEvents: false); - } - } - } - - private void OnHorizontalGridLinesBrushChanged(AvaloniaPropertyChangedEventArgs e) - { - if (!_areHandlersSuspended && _rowsPresenter != null) - { - foreach (DataGridRow row in GetAllRows()) - { - row.EnsureGridLines(); - } - } - } - - private void OnHeadersVisibilityChanged(AvaloniaPropertyChangedEventArgs e) - { - var oldValue = (DataGridHeadersVisibility)e.OldValue; - var newValue = (DataGridHeadersVisibility)e.NewValue; - bool hasFlags(DataGridHeadersVisibility value, DataGridHeadersVisibility flags) => ((value & flags) == flags); - - bool newValueCols = hasFlags(newValue, DataGridHeadersVisibility.Column); - bool newValueRows = hasFlags(newValue, DataGridHeadersVisibility.Row); - bool oldValueCols = hasFlags(oldValue, DataGridHeadersVisibility.Column); - bool oldValueRows = hasFlags(oldValue, DataGridHeadersVisibility.Row); - - // Columns - if (newValueCols != oldValueCols) - { - if (_columnHeadersPresenter != null) - { - EnsureColumnHeadersVisibility(); - if (!newValueCols) - { - _columnHeadersPresenter.Measure(default); - } - else - { - EnsureVerticalGridLines(); - } - InvalidateMeasure(); - } - } - - // Rows - if (newValueRows != oldValueRows) - { - if (_rowsPresenter != null) - { - foreach (Control element in _rowsPresenter.Children) - { - if (element is DataGridRow row) - { - row.EnsureHeaderStyleAndVisibility(null); - if (newValueRows) - { - row.ApplyState(); - row.EnsureHeaderVisibility(); - } - } - else if (element is DataGridRowGroupHeader rowGroupHeader) - { - rowGroupHeader.EnsureHeaderVisibility(); - } - } - InvalidateRowHeightEstimate(); - InvalidateRowsMeasure(invalidateIndividualElements: true); - } - } - - if (_topLeftCornerHeader != null) - { - _topLeftCornerHeader.IsVisible = newValueRows && newValueCols; - if (_topLeftCornerHeader.IsVisible) - { - _topLeftCornerHeader.Measure(default); - } - } - - } - - private void OnGridLinesVisibilityChanged(AvaloniaPropertyChangedEventArgs e) - { - foreach (DataGridRow row in GetAllRows()) - { - row.EnsureGridLines(); - row.InvalidateHorizontalArrange(); - } - } - - private void OnFrozenColumnCountChanged(AvaloniaPropertyChangedEventArgs e) - { - ProcessFrozenColumnCount(); - } - - private void ProcessFrozenColumnCount() - { - CorrectColumnFrozenStates(); - ComputeScrollBarsLayout(); - - InvalidateColumnHeadersArrange(); - InvalidateCellsArrange(); - } - - private void OnColumnWidthChanged(AvaloniaPropertyChangedEventArgs e) - { - var value = (DataGridLength)e.NewValue; - - foreach (DataGridColumn column in ColumnsInternal.GetDisplayedColumns()) - { - if (column.InheritsWidth) - { - column.SetWidthInternalNoCallback(value); - } - } - - EnsureHorizontalLayout(); - } - - private void OnCanUserResizeColumnsChanged(AvaloniaPropertyChangedEventArgs e) - { - EnsureHorizontalLayout(); - } - - /// - /// Occurs one time for each public, non-static property in the bound data type when the - /// property is changed and the - /// property is true. - /// - public event EventHandler AutoGeneratingColumn; - - /// - /// Occurs before a cell or row enters editing mode. - /// - public event EventHandler BeginningEdit; - - /// - /// Occurs after cell editing has ended. - /// - public event EventHandler CellEditEnded; - - /// - /// Occurs immediately before cell editing has ended. - /// - public event EventHandler CellEditEnding; - - /// - /// Occurs when cell is mouse-pressed. - /// - public event EventHandler CellPointerPressed; - - /// - /// Occurs when the - /// property of a column changes. - /// - public event EventHandler ColumnDisplayIndexChanged; - - /// - /// Raised when column reordering ends, to allow subscribers to clean up. - /// - public event EventHandler ColumnReordered; - - /// - /// Raised when starting a column reordering action. Subscribers to this event can - /// set tooltip and caret UIElements, constrain tooltip position, indicate that - /// a preview should be shown, or cancel reordering. - /// - public event EventHandler ColumnReordering; - - /// - /// Occurs when a different cell becomes the current cell. - /// - public event EventHandler CurrentCellChanged; - - /// - /// Occurs after a - /// is instantiated, so that you can customize it before it is used. - /// - public event EventHandler LoadingRow; - - /// - /// Occurs when a cell in a enters editing mode. - /// - /// - public event EventHandler PreparingCellForEdit; - - /// - /// Occurs when the row has been successfully committed or cancelled. - /// - public event EventHandler RowEditEnded; - - /// - /// Occurs immediately before the row has been successfully committed or cancelled. - /// - public event EventHandler RowEditEnding; - - public static readonly RoutedEvent SelectionChangedEvent = - RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble); - - /// - /// Occurs when the or - /// property value changes. - /// - public event EventHandler SelectionChanged - { - add { AddHandler(SelectionChangedEvent, value); } - remove { RemoveHandler(SelectionChangedEvent, value); } - } - - /// - /// Occurs when the sorting request is triggered. - /// - public event EventHandler Sorting; - - /// - /// Occurs when a - /// object becomes available for reuse. - /// - public event EventHandler UnloadingRow; - - /// - /// Occurs when a new row details template is applied to a row, so that you can customize - /// the details section before it is used. - /// - public event EventHandler LoadingRowDetails; - - /// - /// Occurs when the - /// property value changes. - /// - public event EventHandler RowDetailsVisibilityChanged; - - /// - /// Occurs when a row details element becomes available for reuse. - /// - public event EventHandler UnloadingRowDetails; - - /// - /// Gets a collection that contains all the columns in the control. - /// - public ObservableCollection Columns - { - get - { - // we use a backing field here because the field's type - // is a subclass of the property's - return ColumnsInternal; - } - } - - /// - /// Gets or sets the column that contains the current cell. - /// - public DataGridColumn CurrentColumn - { - get - { - if (CurrentColumnIndex == -1) - { - return null; - } - Debug.Assert(CurrentColumnIndex < ColumnsItemsInternal.Count); - return ColumnsItemsInternal[CurrentColumnIndex]; - } - set - { - DataGridColumn dataGridColumn = value; - if (dataGridColumn == null) - { - throw DataGridError.DataGrid.ValueCannotBeSetToNull("value", "CurrentColumn"); - } - if (CurrentColumn != dataGridColumn) - { - if (dataGridColumn.OwningGrid != this) - { - // Provided column does not belong to this DataGrid - throw DataGridError.DataGrid.ColumnNotInThisDataGrid(); - } - if (!dataGridColumn.IsVisible) - { - // CurrentColumn cannot be set to an invisible column - throw DataGridError.DataGrid.ColumnCannotBeCollapsed(); - } - if (CurrentSlot == -1) - { - // There is no current row so the current column cannot be set - throw DataGridError.DataGrid.NoCurrentRow(); - } - bool beginEdit = _editingColumnIndex != -1; - - //exitEditingMode, keepFocus, raiseEvents - if (!EndCellEdit(DataGridEditAction.Commit, true, ContainsFocus, true)) - { - // Edited value couldn't be committed or aborted - return; - } - - UpdateSelectionAndCurrency(dataGridColumn.Index, CurrentSlot, DataGridSelectionAction.None, false); //scrollIntoView - Debug.Assert(_successfullyUpdatedSelection); - - if (beginEdit && - _editingColumnIndex == -1 && - CurrentSlot != -1 && - CurrentColumnIndex != -1 && - CurrentColumnIndex == dataGridColumn.Index && - dataGridColumn.OwningGrid == this && - !GetColumnEffectiveReadOnlyState(dataGridColumn)) - { - // Returning to editing mode since the grid was in that mode prior to the EndCellEdit call above. - BeginCellEdit(new RoutedEventArgs()); - } - } - } - } - - /// - /// Gets a list that contains the data items corresponding to the selected rows. - /// - public IList SelectedItems - { - get { return _selectedItems as IList; } - } - - internal DataGridColumnCollection ColumnsInternal - { - get; - } - - internal int AnchorSlot - { - get; - private set; - } - - internal double ActualRowHeaderWidth - { - get - { - if (!AreRowHeadersVisible) - { - return 0; - } - else - { - return !double.IsNaN(RowHeaderWidth) ? RowHeaderWidth : RowHeadersDesiredWidth; - } - } - } - - internal double ActualRowsPresenterHeight - { - get - { - if (_rowsPresenter != null) - { - return _rowsPresenter.Bounds.Height; - } - return 0; - } - } - - internal bool AreColumnHeadersVisible - { - get - { - return (HeadersVisibility & DataGridHeadersVisibility.Column) == DataGridHeadersVisibility.Column; - } - } - - internal bool AreRowHeadersVisible - { - get - { - return (HeadersVisibility & DataGridHeadersVisibility.Row) == DataGridHeadersVisibility.Row; - } - } - - /// - /// Indicates whether or not at least one auto-sizing column is waiting for all the rows - /// to be measured before its final width is determined. - /// - internal bool AutoSizingColumns - { - get - { - return _autoSizingColumns; - } - set - { - if (_autoSizingColumns && !value && ColumnsInternal != null) - { - double adjustment = CellsWidth - ColumnsInternal.VisibleEdgedColumnsWidth; - AdjustColumnWidths(0, adjustment, false); - foreach (DataGridColumn column in ColumnsInternal.GetVisibleColumns()) - { - column.IsInitialDesiredWidthDetermined = true; - } - ColumnsInternal.EnsureVisibleEdgedColumnsWidth(); - ComputeScrollBarsLayout(); - InvalidateColumnHeadersMeasure(); - InvalidateRowsMeasure(true); - } - _autoSizingColumns = value; - } - } - - internal double AvailableSlotElementRoom - { - get; - set; - } - - internal double CellsEstimatedHeight - { - get - { - return RowsPresenterAvailableSize?.Height ?? 0; - } - } - - // Width currently available for cells this value is smaller. This width is reduced by the existence of RowHeaders - // or a vertical scrollbar. Layout is asynchronous so changes to the RowHeaders or the vertical scrollbar are - // not reflected immediately - internal double CellsWidth - { - get - { - double rowsWidth = double.PositiveInfinity; - if (RowsPresenterAvailableSize.HasValue) - { - rowsWidth = Math.Max(0, RowsPresenterAvailableSize.Value.Width - ActualRowHeaderWidth); - } - return double.IsPositiveInfinity(rowsWidth) ? ColumnsInternal.VisibleEdgedColumnsWidth : rowsWidth; - } - } - - internal DataGridColumnHeadersPresenter ColumnHeaders => _columnHeadersPresenter; - - internal List ColumnsItemsInternal => ColumnsInternal.ItemsInternal; - - internal bool ContainsFocus - { - get; - private set; - } - - internal int CurrentColumnIndex - { - get - { - return CurrentCellCoordinates.ColumnIndex; - } - - private set - { - CurrentCellCoordinates.ColumnIndex = value; - } - } - - internal int CurrentSlot - { - get - { - return CurrentCellCoordinates.Slot; - } - - private set - { - CurrentCellCoordinates.Slot = value; - } - } - - internal DataGridDataConnection DataConnection - { - get; - private set; - } - - internal DataGridDisplayData DisplayData - { - get; - private set; - } - - internal int EditingColumnIndex - { - get; - private set; - } - - internal DataGridRow EditingRow - { - get; - private set; - } - - internal double FirstDisplayedScrollingColumnHiddenWidth => _negHorizontalOffset; - - // When the RowsPresenter's width increases, the HorizontalOffset will be incorrect until - // the scrollbar's layout is recalculated, which doesn't occur until after the cells are measured. - // This property exists to account for this scenario, and avoid collapsing the incorrect cells. - internal double HorizontalAdjustment - { - get; - private set; - } - - internal static double HorizontalGridLinesThickness => DATAGRID_horizontalGridLinesThickness; - - // the sum of the widths in pixels of the scrolling columns preceding - // the first displayed scrolling column - internal double HorizontalOffset - { - get - { - return _horizontalOffset; - } - set - { - if (value < 0) - { - value = 0; - } - double widthNotVisible = Math.Max(0, ColumnsInternal.VisibleEdgedColumnsWidth - CellsWidth); - if (value > widthNotVisible) - { - value = widthNotVisible; - } - if (value == _horizontalOffset) - { - return; - } - - if (_hScrollBar != null && value != _hScrollBar.Value) - { - _hScrollBar.Value = value; - } - _horizontalOffset = value; - - DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn(); - // update the lastTotallyDisplayedScrollingCol - ComputeDisplayedColumns(); - } - } - - internal ScrollBar HorizontalScrollBar => _hScrollBar; - - internal IndexToValueTable RowGroupHeadersTable - { - get; - private set; - } - - internal bool LoadingOrUnloadingRow - { - get; - private set; - } - - internal bool InDisplayIndexAdjustments - { - get; - set; - } - - internal int? MouseOverRowIndex - { - get - { - return _mouseOverRowIndex; - } - set - { - if (_mouseOverRowIndex != value) - { - DataGridRow oldMouseOverRow = null; - if (_mouseOverRowIndex.HasValue) - { - int oldSlot = SlotFromRowIndex(_mouseOverRowIndex.Value); - if (IsSlotVisible(oldSlot)) - { - oldMouseOverRow = DisplayData.GetDisplayedElement(oldSlot) as DataGridRow; - } - } - - _mouseOverRowIndex = value; - - // State for the old row needs to be applied after setting the new value - if (oldMouseOverRow != null) - { - oldMouseOverRow.ApplyState(); - } - - if (_mouseOverRowIndex.HasValue) - { - int newSlot = SlotFromRowIndex(_mouseOverRowIndex.Value); - if (IsSlotVisible(newSlot)) - { - DataGridRow newMouseOverRow = DisplayData.GetDisplayedElement(newSlot) as DataGridRow; - Debug.Assert(newMouseOverRow != null); - if (newMouseOverRow != null) - { - newMouseOverRow.ApplyState(); - } - } - } - } - } - } - - internal double NegVerticalOffset - { - get; - private set; - } - - internal int NoCurrentCellChangeCount - { - get - { - return _noCurrentCellChangeCount; - } - set - { - _noCurrentCellChangeCount = value; - if (value == 0) - { - FlushCurrentCellChanged(); - } - } - } - - internal double RowDetailsHeightEstimate - { - get; - private set; - } - - internal double RowHeadersDesiredWidth - { - get - { - return _rowHeaderDesiredWidth; - } - set - { - // We only auto grow - if (_rowHeaderDesiredWidth < value) - { - double oldActualRowHeaderWidth = ActualRowHeaderWidth; - _rowHeaderDesiredWidth = value; - if (oldActualRowHeaderWidth != ActualRowHeaderWidth) - { - EnsureRowHeaderWidth(); - } - } - } - } - - internal double RowGroupHeaderHeightEstimate - { - get; - private set; - } - - internal double RowHeightEstimate - { - get; - private set; - } - - internal Size? RowsPresenterAvailableSize - { - get - { - return _rowsPresenterAvailableSize; - } - set - { - if (_rowsPresenterAvailableSize.HasValue && value.HasValue && value.Value.Width > RowsPresenterAvailableSize.Value.Width) - { - // When the available cells width increases, the horizontal offset can be incorrect. - // Store away an adjustment to use during the CellsPresenter's measure, so that the - // ShouldDisplayCell method correctly determines if a cell will be in view. - // - // | h. offset | new available cells width | - // |-------------->|----------------------------------------->| - // __________________________________________________ | - // | | | | | | - // | column0 | column1 | column2 | column3 |<----->| - // | | | | | adj. | - // - double adjustment = (_horizontalOffset + value.Value.Width) - ColumnsInternal.VisibleEdgedColumnsWidth; - HorizontalAdjustment = Math.Min(HorizontalOffset, Math.Max(0, adjustment)); - } - else - { - HorizontalAdjustment = 0; - } - _rowsPresenterAvailableSize = value; - } - } - - internal double[] RowGroupSublevelIndents - { - get; - private set; - } - - // This flag indicates whether selection has actually changed during a selection operation, - // and exists to ensure that FlushSelectionChanged doesn't unnecessarily raise SelectionChanged. - internal bool SelectionHasChanged - { - get; - set; - } - - internal int SlotCount - { - get; - private set; - } - - /// - /// Indicates whether or not to use star-sizing logic. If the DataGrid has infinite available space, - /// then star sizing doesn't make sense. In this case, all star columns grow to a predefined size of - /// 10,000 pixels in order to show the developer that star columns shouldn't be used. - /// - internal bool UsesStarSizing - { - get - { - if (ColumnsInternal != null) - { - return ColumnsInternal.VisibleStarColumnCount > 0 && - (!RowsPresenterAvailableSize.HasValue || !double.IsPositiveInfinity(RowsPresenterAvailableSize.Value.Width)); - } - return false; - } - } - - internal ScrollBar VerticalScrollBar => _vScrollBar; - - internal int VisibleSlotCount - { - get; - set; - } - - /// - /// Gets the data item bound to the row that contains the current cell. - /// - protected object CurrentItem - { - get - { - if (CurrentSlot == -1 || ItemsSource == null || RowGroupHeadersTable.Contains(CurrentSlot)) - { - return null; - } - return DataConnection.GetDataItem(RowIndexFromSlot(CurrentSlot)); - } - } - - private DataGridCellCoordinates CurrentCellCoordinates - { - get; - set; - } - - private int FirstDisplayedNonFillerColumnIndex - { - get - { - DataGridColumn column = ColumnsInternal.FirstVisibleNonFillerColumn; - if (column != null) - { - if (column.IsFrozen) - { - return column.Index; - } - else - { - if (DisplayData.FirstDisplayedScrollingCol >= column.Index) - { - return DisplayData.FirstDisplayedScrollingCol; - } - else - { - return column.Index; - } - } - } - return -1; - } - } - - private bool IsHorizontalScrollBarOverCells - { - get - { - return _columnHeadersPresenter != null && Grid.GetColumnSpan(_columnHeadersPresenter) == 2; - } - } - - private bool IsVerticalScrollBarOverCells - { - get - { - return _rowsPresenter != null && Grid.GetRowSpan(_rowsPresenter) == 2; - } - } - - private int NoSelectionChangeCount - { - get - { - return _noSelectionChangeCount; - } - set - { - _noSelectionChangeCount = value; - if (value == 0) - { - FlushSelectionChanged(); - } - } - } - - /// - /// Enters editing mode for the current cell and current row (if they're not already in editing mode). - /// - /// True if operation was successful. False otherwise. - public bool BeginEdit() - { - return BeginEdit(null); - } - - /// - /// Enters editing mode for the current cell and current row (if they're not already in editing mode). - /// - /// Provides information about the user gesture that caused the call to BeginEdit. Can be null. - /// True if operation was successful. False otherwise. - public bool BeginEdit(RoutedEventArgs editingEventArgs) - { - if (CurrentColumnIndex == -1 || !GetRowSelection(CurrentSlot)) - { - return false; - } - - Debug.Assert(CurrentColumnIndex >= 0); - Debug.Assert(CurrentColumnIndex < ColumnsItemsInternal.Count); - Debug.Assert(CurrentSlot >= -1); - Debug.Assert(CurrentSlot < SlotCount); - Debug.Assert(EditingRow == null || EditingRow.Slot == CurrentSlot); - - if (GetColumnEffectiveReadOnlyState(CurrentColumn)) - { - // Current column is read-only - return false; - } - return BeginCellEdit(editingEventArgs); - } - - /// - /// Cancels editing mode and restores the original value. - /// - /// True if operation was successful. False otherwise. - public bool CancelEdit() - { - return CancelEdit(DataGridEditingUnit.Row); - } - - /// - /// Cancels editing mode for the specified DataGridEditingUnit and restores its original value. - /// - /// Specifies whether to cancel edit for a Cell or Row. - /// True if operation was successful. False otherwise. - public bool CancelEdit(DataGridEditingUnit editingUnit) - { - return CancelEdit(editingUnit, raiseEvents: true); - } - - /// - /// Commits editing mode and pushes changes to the backend. - /// - /// True if operation was successful. False otherwise. - public bool CommitEdit() - { - return CommitEdit(DataGridEditingUnit.Row, true); - } - - /// - /// Commits editing mode for the specified DataGridEditingUnit and pushes changes to the backend. - /// - /// Specifies whether to commit edit for a Cell or Row. - /// Editing mode is left if True. - /// True if operation was successful. False otherwise. - public bool CommitEdit(DataGridEditingUnit editingUnit, bool exitEditingMode) - { - if (!EndCellEdit( - editAction: DataGridEditAction.Commit, - exitEditingMode: editingUnit == DataGridEditingUnit.Cell ? exitEditingMode : true, - keepFocus: ContainsFocus, - raiseEvents: true)) - { - return false; - } - if (editingUnit == DataGridEditingUnit.Row) - { - return EndRowEdit(DataGridEditAction.Commit, exitEditingMode, raiseEvents: true); - } - return true; - } - - /// - /// Scrolls the specified item or RowGroupHeader and/or column into view. - /// If item is not null: scrolls the row representing the item into view; - /// If column is not null: scrolls the column into view; - /// If both item and column are null, the method returns without scrolling. - /// - /// an item from the DataGrid's items source or a CollectionViewGroup from the collection view - /// a column from the DataGrid's columns collection - public void ScrollIntoView(object item, DataGridColumn column) - { - if ((column == null && (item == null || FirstDisplayedNonFillerColumnIndex == -1)) - || (column != null && column.OwningGrid != this)) - { - // no-op - return; - } - if (item == null) - { - // scroll column into view - ScrollSlotIntoView( - column.Index, - DisplayData.FirstScrollingSlot, - forCurrentCellChange: false, - forceHorizontalScroll: true); - } - else - { - int slot = -1; - DataGridRowGroupInfo rowGroupInfo = null; - if (item is DataGridCollectionViewGroup collectionViewGroup) - { - rowGroupInfo = RowGroupInfoFromCollectionViewGroup(collectionViewGroup); - if (rowGroupInfo == null) - { - Debug.Assert(false); - return; - } - slot = rowGroupInfo.Slot; - } - else - { - // the row index will be set to -1 if the item is null or not in the list - int rowIndex = DataConnection.IndexOf(item); - if (rowIndex == -1) - { - return; - } - slot = SlotFromRowIndex(rowIndex); - } - - int columnIndex = (column == null) ? FirstDisplayedNonFillerColumnIndex : column.Index; - - if (_collapsedSlotsTable.Contains(slot)) - { - // We need to expand all parent RowGroups so that the slot is visible - if (rowGroupInfo != null) - { - ExpandRowGroupParentChain(rowGroupInfo.Level - 1, rowGroupInfo.Slot); - } - else - { - rowGroupInfo = RowGroupHeadersTable.GetValueAt(RowGroupHeadersTable.GetPreviousIndex(slot)); - Debug.Assert(rowGroupInfo != null); - if (rowGroupInfo != null) - { - ExpandRowGroupParentChain(rowGroupInfo.Level, rowGroupInfo.Slot); - } - } - - // Update Scrollbar and display information - NegVerticalOffset = 0; - SetVerticalOffset(0); - ResetDisplayedRows(); - DisplayData.FirstScrollingSlot = 0; - ComputeScrollBarsLayout(); - } - - ScrollSlotIntoView( - columnIndex, slot, - forCurrentCellChange: true, - forceHorizontalScroll: true); - } - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - if (DataConnection.DataSource != null && !DataConnection.EventsWired) - { - DataConnection.WireEvents(DataConnection.DataSource); - InitializeElements(true /*recycleRows*/); - } - } - - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - // When wired to INotifyCollectionChanged, the DataGrid will be cleaned up by GC - if (DataConnection.DataSource != null && DataConnection.EventsWired) - { - DataConnection.UnWireEvents(DataConnection.DataSource); - } - } - - /// - /// Arranges the content of the . - /// - /// - /// The final area within the parent that this element should use to arrange itself and its children. - /// - /// - /// The actual size used by the . - /// - protected override Size ArrangeOverride(Size finalSize) - { - if (_makeFirstDisplayedCellCurrentCellPending) - { - MakeFirstDisplayedCellCurrentCell(); - } - - if (Bounds.Width != finalSize.Width) - { - // If our final width has changed, we might need to update the filler - InvalidateColumnHeadersArrange(); - InvalidateCellsArrange(); - } - - return base.ArrangeOverride(finalSize); - } - - /// - /// Measures the children of a to prepare for - /// arranging them during the - /// pass. - /// - /// - /// The size that the determines it needs during layout, based on its calculations of child object allocated sizes. - /// - /// - /// The available size that this element can give to child elements. Indicates an upper limit that - /// child elements should not exceed. - /// - protected override Size MeasureOverride(Size availableSize) - { - // Delay layout until after the initial measure to avoid invalid calculations when the - // DataGrid is not part of the visual tree - if (!_measured) - { - _measured = true; - - // We don't need to clear the rows because it was already done when the ItemsSource changed - RefreshRowsAndColumns(clearRows: false); - - //// Update our estimates now that the DataGrid has all of the information necessary - UpdateRowDetailsHeightEstimate(); - - // Update frozen columns to account for columns added prior to loading or autogenerated columns - if (FrozenColumnCountWithFiller > 0) - { - ProcessFrozenColumnCount(); - } - } - - Size desiredSize; - // This is a shortcut to skip layout if we don't have any columns - if (ColumnsInternal.VisibleEdgedColumnsWidth == 0) - { - if (_hScrollBar != null && _hScrollBar.IsVisible) - { - _hScrollBar.IsVisible = false; - } - if (_vScrollBar != null && _vScrollBar.IsVisible) - { - _vScrollBar.IsVisible = false; - } - desiredSize = base.MeasureOverride(availableSize); - } - else - { - if (_rowsPresenter != null) - { - _rowsPresenter.InvalidateMeasure(); - } - - InvalidateColumnHeadersMeasure(); - - desiredSize = base.MeasureOverride(availableSize); - - ComputeScrollBarsLayout(); - } - - return desiredSize; - } - - /// - protected override void OnDataContextBeginUpdate() - { - base.OnDataContextBeginUpdate(); - - NotifyDataContextPropertyForAllRowCells(GetAllRows(), true); - } - - /// - protected override void OnDataContextEndUpdate() - { - base.OnDataContextEndUpdate(); - - NotifyDataContextPropertyForAllRowCells(GetAllRows(), false); - } - - /// - /// Raises the BeginningEdit event. - /// - protected virtual void OnBeginningEdit(DataGridBeginningEditEventArgs e) - { - BeginningEdit?.Invoke(this, e); - } - - /// - /// Raises the CellEditEnded event. - /// - protected virtual void OnCellEditEnded(DataGridCellEditEndedEventArgs e) - { - CellEditEnded?.Invoke(this, e); - } - - /// - /// Raises the CellEditEnding event. - /// - protected virtual void OnCellEditEnding(DataGridCellEditEndingEventArgs e) - { - CellEditEnding?.Invoke(this, e); - } - - /// - /// Raises the CellPointerPressed event. - /// - internal virtual void OnCellPointerPressed(DataGridCellPointerPressedEventArgs e) - { - CellPointerPressed?.Invoke(this, e); - } - - /// - /// Raises the CurrentCellChanged event. - /// - protected virtual void OnCurrentCellChanged(EventArgs e) - { - CurrentCellChanged?.Invoke(this, e); - } - - /// - /// Raises the LoadingRow event for row preparation. - /// - protected virtual void OnLoadingRow(DataGridRowEventArgs e) - { - EventHandler handler = LoadingRow; - if (handler != null) - { - Debug.Assert(!_loadedRows.Contains(e.Row)); - _loadedRows.Add(e.Row); - LoadingOrUnloadingRow = true; - handler(this, e); - LoadingOrUnloadingRow = false; - Debug.Assert(_loadedRows.Contains(e.Row)); - _loadedRows.Remove(e.Row); - } - } - - /// - /// Scrolls the DataGrid according to the direction of the delta. - /// - /// PointerWheelEventArgs - protected override void OnPointerWheelChanged(PointerWheelEventArgs e) - { - var delta = e.Delta; - - // KeyModifiers.Shift should scroll in horizontal direction. This does not work on every platform. - // If Shift-Key is pressed and X is close to 0 we swap the Vector. - if (e.KeyModifiers == KeyModifiers.Shift && MathUtilities.IsZero(delta.X)) - { - delta = new Vector(delta.Y, delta.X); - } - - if(UpdateScroll(delta * DATAGRID_mouseWheelDelta)) - { - e.Handled = true; - } - else - { - e.Handled = e.Handled || !ScrollViewer.GetIsScrollChainingEnabled(this); - } - } - - internal bool UpdateScroll(Vector delta) - { - if (IsEnabled && DisplayData.NumDisplayedScrollingElements > 0) - { - var handled = false; - var ignoreInvalidate = false; - var scrollHeight = 0d; - - // Vertical scroll handling - if (delta.Y > 0) - { - scrollHeight = Math.Max(-_verticalOffset, -delta.Y); - } - else if (delta.Y < 0) - { - if (_vScrollBar != null && VerticalScrollBarVisibility == ScrollBarVisibility.Visible) - { - scrollHeight = Math.Min(Math.Max(0, _vScrollBar.Maximum - _verticalOffset), -delta.Y); - } - else - { - double maximum = EdgedRowsHeightCalculated - CellsEstimatedHeight; - scrollHeight = Math.Min(Math.Max(0, maximum - _verticalOffset), -delta.Y); - } - } - - if (scrollHeight != 0) - { - DisplayData.PendingVerticalScrollHeight = scrollHeight; - handled = true; - } - - // Horizontal scroll handling - if (delta.X != 0) - { - var horizontalOffset = HorizontalOffset - delta.X; - var widthNotVisible = Math.Max(0, ColumnsInternal.VisibleEdgedColumnsWidth - CellsWidth); - - if (horizontalOffset < 0) - { - horizontalOffset = 0; - } - if (horizontalOffset > widthNotVisible) - { - horizontalOffset = widthNotVisible; - } - - if (UpdateHorizontalOffset(horizontalOffset)) - { - // We don't need to invalidate once again after UpdateHorizontalOffset. - ignoreInvalidate = true; - handled = true; - } - } - - if (handled) - { - if (!ignoreInvalidate) - { - InvalidateRowsMeasure(invalidateIndividualElements: false); - } - return true; - } - } - - return false; - } - - /// - /// Raises the PreparingCellForEdit event. - /// - protected virtual void OnPreparingCellForEdit(DataGridPreparingCellForEditEventArgs e) - { - PreparingCellForEdit?.Invoke(this, e); - } - - /// - /// Raises the RowEditEnded event. - /// - protected virtual void OnRowEditEnded(DataGridRowEditEndedEventArgs e) - { - RowEditEnded?.Invoke(this, e); - } - - /// - /// Raises the RowEditEnding event. - /// - protected virtual void OnRowEditEnding(DataGridRowEditEndingEventArgs e) - { - RowEditEnding?.Invoke(this, e); - } - - /// - /// Raises the SelectionChanged event and clears the _selectionChanged. - /// This event won't get raised again until after _selectionChanged is set back to true. - /// - protected virtual void OnSelectionChanged(SelectionChangedEventArgs e) - { - RaiseEvent(e); - } - - /// - /// Raises the UnloadingRow event for row recycling. - /// - protected virtual void OnUnloadingRow(DataGridRowEventArgs e) - { - EventHandler handler = UnloadingRow; - if (handler != null) - { - LoadingOrUnloadingRow = true; - handler(this, e); - LoadingOrUnloadingRow = false; - } - } - - /// - /// Comparator class so we can sort list by the display index - /// - public class DisplayIndexComparer : IComparer - { - int IComparer.Compare(DataGridColumn x, DataGridColumn y) - { - return (x.DisplayIndexWithFiller < y.DisplayIndexWithFiller) ? -1 : 1; - } - } - - /// - /// Builds the visual tree for the column header when a new template is applied. - /// - //TODO Validation UI - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - // The template has changed, so we need to refresh the visuals - _measured = false; - - if (_columnHeadersPresenter != null) - { - // If we're applying a new template, we want to remove the old column headers first - _columnHeadersPresenter.Children.Clear(); - } - - _columnHeadersPresenter = e.NameScope.Find(DATAGRID_elementColumnHeadersPresenterName); - - if (_columnHeadersPresenter != null) - { - if (ColumnsInternal.FillerColumn != null) - { - ColumnsInternal.FillerColumn.IsRepresented = false; - } - _columnHeadersPresenter.OwningGrid = this; - - // Columns were added before our Template was applied, add the ColumnHeaders now - List sortedInternal = new List(ColumnsItemsInternal); - sortedInternal.Sort(new DisplayIndexComparer()); - foreach (DataGridColumn column in sortedInternal) - { - InsertDisplayedColumnHeader(column); - } - } - - if (_rowsPresenter != null) - { - // If we're applying a new template, we want to remove the old rows first - UnloadElements(recycle: false); - } - - _rowsPresenter = e.NameScope.Find(DATAGRID_elementRowsPresenterName); - - if (_rowsPresenter != null) - { - _rowsPresenter.OwningGrid = this; - InvalidateRowHeightEstimate(); - UpdateRowDetailsHeightEstimate(); - } - - _frozenColumnScrollBarSpacer = e.NameScope.Find(DATAGRID_elementFrozenColumnScrollBarSpacerName); - - if (_hScrollBar != null) - { - _hScrollBar.Scroll -= HorizontalScrollBar_Scroll; - } - - _hScrollBar = e.NameScope.Find(DATAGRID_elementHorizontalScrollbarName); - - if (_hScrollBar != null) - { - _hScrollBar.IsTabStop = false; - _hScrollBar.Maximum = 0.0; - _hScrollBar.Orientation = Orientation.Horizontal; - _hScrollBar.IsVisible = false; - _hScrollBar.Scroll += HorizontalScrollBar_Scroll; - } - - if (_vScrollBar != null) - { - _vScrollBar.Scroll -= VerticalScrollBar_Scroll; - } - - _vScrollBar = e.NameScope.Find(DATAGRID_elementVerticalScrollbarName); - - if (_vScrollBar != null) - { - _vScrollBar.IsTabStop = false; - _vScrollBar.Maximum = 0.0; - _vScrollBar.Orientation = Orientation.Vertical; - _vScrollBar.IsVisible = false; - _vScrollBar.Scroll += VerticalScrollBar_Scroll; - } - - _topLeftCornerHeader = e.NameScope.Find(DATAGRID_elementTopLeftCornerHeaderName); - EnsureTopLeftCornerHeader(); // EnsureTopLeftCornerHeader checks for a null _topLeftCornerHeader; - _topRightCornerHeader = e.NameScope.Find(DATAGRID_elementTopRightCornerHeaderName); - _bottomRightCorner = e.NameScope.Find(DATAGRID_elementBottomRightCornerHeaderName); - } - - /// - /// Cancels editing mode for the specified DataGridEditingUnit and restores its original value. - /// - /// Specifies whether to cancel edit for a Cell or Row. - /// Specifies whether or not to raise editing events - /// True if operation was successful. False otherwise. - internal bool CancelEdit(DataGridEditingUnit editingUnit, bool raiseEvents) - { - if (!EndCellEdit( - DataGridEditAction.Cancel, - exitEditingMode: true, - keepFocus: ContainsFocus, - raiseEvents: raiseEvents)) - { - return false; - } - - if (editingUnit == DataGridEditingUnit.Row) - { - return EndRowEdit(DataGridEditAction.Cancel, true, raiseEvents); - } - - return true; - } - - /// - /// call when: selection changes or SelectedItems object changes - /// - internal void CoerceSelectedItem() - { - object selectedItem = null; - - if (SelectionMode == DataGridSelectionMode.Extended && - CurrentSlot != -1 && - _selectedItems.ContainsSlot(CurrentSlot)) - { - selectedItem = CurrentItem; - } - else if (_selectedItems.Count > 0) - { - selectedItem = _selectedItems[0]; - } - - SetValueNoCallback(SelectedItemProperty, selectedItem); - - // Update the SelectedIndex - int newIndex = -1; - - if (selectedItem != null) - { - newIndex = DataConnection.IndexOf(selectedItem); - } - - SetValueNoCallback(SelectedIndexProperty, newIndex); - } - - internal static DataGridCell GetOwningCell(Control element) - { - Debug.Assert(element != null); - DataGridCell cell = element as DataGridCell; - while (element != null && cell == null) - { - element = element.Parent as Control; - cell = element as DataGridCell; - } - return cell; - } - - internal IEnumerable GetSelectionInclusive(int startRowIndex, int endRowIndex) - { - int endSlot = SlotFromRowIndex(endRowIndex); - foreach (int slot in _selectedItems.GetSlots(SlotFromRowIndex(startRowIndex))) - { - if (slot > endSlot) - { - break; - } - yield return DataConnection.GetDataItem(RowIndexFromSlot(slot)); - } - } - - internal void InitializeElements(bool recycleRows) - { - try - { - _noCurrentCellChangeCount++; - - // The underlying collection has changed and our editing row (if there is one) - // is no longer relevant, so we should force a cancel edit. - CancelEdit(DataGridEditingUnit.Row, raiseEvents: false); - - // We want to persist selection throughout a reset, so store away the selected items - List selectedItemsCache = new List(_selectedItems.SelectedItemsCache); - - if (recycleRows) - { - RefreshRows(recycleRows, clearRows: true); - } - else - { - RefreshRowsAndColumns(clearRows: true); - } - - // Re-select the old items - _selectedItems.SelectedItemsCache = selectedItemsCache; - CoerceSelectedItem(); - if (RowDetailsVisibilityMode != DataGridRowDetailsVisibilityMode.Collapsed) - { - UpdateRowDetailsVisibilityMode(RowDetailsVisibilityMode); - } - - // The currently displayed rows may have incorrect visual states because of the selection change - ApplyDisplayedRowsState(DisplayData.FirstScrollingSlot, DisplayData.LastScrollingSlot); - } - finally - { - NoCurrentCellChangeCount--; - } - } - - internal bool IsDoubleClickRecordsClickOnCall(Control element) - { - if (_clickedElement == element) - { - _clickedElement = null; - return true; - } - else - { - _clickedElement = element; - return false; - } - } - - // Returns the item or the CollectionViewGroup that is used as the DataContext for a given slot. - // If the DataContext is an item, rowIndex is set to the index of the item within the collection - internal object ItemFromSlot(int slot, ref int rowIndex) - { - if (RowGroupHeadersTable.Contains(slot)) - { - return RowGroupHeadersTable.GetValueAt(slot)?.CollectionViewGroup; - } - else - { - rowIndex = RowIndexFromSlot(slot); - return DataConnection.GetDataItem(rowIndex); - } - } - - internal bool ProcessDownKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessDownKeyInternal(shift, ctrl); - } - - internal bool ProcessEndKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessEndKey(shift, ctrl); - } - - internal bool ProcessEnterKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessEnterKey(shift, ctrl); - } - - internal bool ProcessHomeKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessHomeKey(shift, ctrl); - } - - internal void ProcessHorizontalScroll(ScrollEventType scrollEventType) - { - if (_horizontalScrollChangesIgnored > 0) - { - return; - } - - // If the user scrolls with the buttons, we need to update the new value of the scroll bar since we delay - // this calculation. If they scroll in another other way, the scroll bar's correct value has already been set - double scrollBarValueDifference = 0; - if (scrollEventType == ScrollEventType.SmallIncrement) - { - scrollBarValueDifference = GetHorizontalSmallScrollIncrease(); - } - else if (scrollEventType == ScrollEventType.SmallDecrement) - { - scrollBarValueDifference = -GetHorizontalSmallScrollDecrease(); - } - _horizontalScrollChangesIgnored++; - try - { - if (scrollBarValueDifference != 0) - { - Debug.Assert(_horizontalOffset + scrollBarValueDifference >= 0); - _hScrollBar.Value = _horizontalOffset + scrollBarValueDifference; - } - UpdateHorizontalOffset(_hScrollBar.Value); - } - finally - { - _horizontalScrollChangesIgnored--; - } - } - - internal bool ProcessLeftKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessLeftKey(shift, ctrl); - } - - internal bool ProcessNextKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessNextKey(shift, ctrl); - } - - internal bool ProcessPriorKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessPriorKey(shift, ctrl); - } - - internal bool ProcessRightKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessRightKey(shift, ctrl); - } - - /// - /// Selects items and updates currency based on parameters - /// - /// column index to make current - /// data item or CollectionViewGroup to make current - /// slot to use in case the item is no longer valid - /// selection action to perform - /// whether or not the new current item should be scrolled into view - internal void ProcessSelectionAndCurrency(int columnIndex, object item, int backupSlot, DataGridSelectionAction action, bool scrollIntoView) - { - _noSelectionChangeCount++; - _noCurrentCellChangeCount++; - try - { - int slot = -1; - if (item is DataGridCollectionViewGroup group) - { - DataGridRowGroupInfo groupInfo = RowGroupInfoFromCollectionViewGroup(group); - if (groupInfo != null) - { - slot = groupInfo.Slot; - } - } - else - { - slot = SlotFromRowIndex(DataConnection.IndexOf(item)); - } - if (slot == -1) - { - slot = backupSlot; - } - if (slot < 0 || slot > SlotCount) - { - return; - } - - switch (action) - { - case DataGridSelectionAction.AddCurrentToSelection: - SetRowSelection(slot, isSelected: true, setAnchorSlot: true); - break; - case DataGridSelectionAction.RemoveCurrentFromSelection: - SetRowSelection(slot, isSelected: false, setAnchorSlot: false); - break; - case DataGridSelectionAction.SelectFromAnchorToCurrent: - if (SelectionMode == DataGridSelectionMode.Extended && AnchorSlot != -1) - { - int anchorSlot = AnchorSlot; - if (slot <= anchorSlot) - { - SetRowsSelection(slot, anchorSlot); - } - else - { - SetRowsSelection(anchorSlot, slot); - } - } - else - { - goto case DataGridSelectionAction.SelectCurrent; - } - break; - case DataGridSelectionAction.SelectCurrent: - ClearRowSelection(slot, setAnchorSlot: true); - break; - case DataGridSelectionAction.None: - break; - } - - if (CurrentSlot != slot || (CurrentColumnIndex != columnIndex && columnIndex != -1)) - { - if (columnIndex == -1) - { - if (CurrentColumnIndex != -1) - { - columnIndex = CurrentColumnIndex; - } - else - { - DataGridColumn firstVisibleColumn = ColumnsInternal.FirstVisibleNonFillerColumn; - if (firstVisibleColumn != null) - { - columnIndex = firstVisibleColumn.Index; - } - } - } - if (columnIndex != -1) - { - if (!SetCurrentCellCore( - columnIndex, slot, - commitEdit: true, - endRowEdit: SlotFromRowIndex(SelectedIndex) != slot) - || (scrollIntoView && - !ScrollSlotIntoView( - columnIndex, slot, - forCurrentCellChange: true, - forceHorizontalScroll: false))) - { - return; - } - } - } - _successfullyUpdatedSelection = true; - } - finally - { - NoCurrentCellChangeCount--; - NoSelectionChangeCount--; - } - } - - internal bool ProcessUpKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessUpKey(shift, ctrl); - } - - //internal void ProcessVerticalScroll(double oldValue, double newValue) - internal void ProcessVerticalScroll(ScrollEventType scrollEventType) - { - if (_verticalScrollChangesIgnored > 0) - { - return; - } - Debug.Assert(MathUtilities.LessThanOrClose(_vScrollBar.Value, _vScrollBar.Maximum)); - - _verticalScrollChangesIgnored++; - try - { - Debug.Assert(_vScrollBar != null); - if (scrollEventType == ScrollEventType.SmallIncrement) - { - DisplayData.PendingVerticalScrollHeight = GetVerticalSmallScrollIncrease(); - double newVerticalOffset = _verticalOffset + DisplayData.PendingVerticalScrollHeight; - if (newVerticalOffset > _vScrollBar.Maximum) - { - DisplayData.PendingVerticalScrollHeight -= newVerticalOffset - _vScrollBar.Maximum; - } - } - else if (scrollEventType == ScrollEventType.SmallDecrement) - { - if (MathUtilities.GreaterThan(NegVerticalOffset, 0)) - { - DisplayData.PendingVerticalScrollHeight -= NegVerticalOffset; - } - else - { - int previousScrollingSlot = GetPreviousVisibleSlot(DisplayData.FirstScrollingSlot); - if (previousScrollingSlot >= 0) - { - ScrollSlotIntoView(previousScrollingSlot, scrolledHorizontally: false); - } - return; - } - } - else - { - DisplayData.PendingVerticalScrollHeight = _vScrollBar.Value - _verticalOffset; - } - - if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight)) - { - // Invalidate so the scroll happens on idle - InvalidateRowsMeasure(invalidateIndividualElements: false); - } - } - finally - { - _verticalScrollChangesIgnored--; - } - } - - internal void RefreshRowsAndColumns(bool clearRows) - { - if (_measured) - { - try - { - _noCurrentCellChangeCount++; - - if (clearRows) - { - ClearRows(false); - ClearRowGroupHeadersTable(); - PopulateRowGroupHeadersTable(); - } - if (AutoGenerateColumns) - { - //Column auto-generation refreshes the rows too - AutoGenerateColumnsPrivate(); - } - foreach (DataGridColumn column in ColumnsItemsInternal) - { - //We don't need to refresh the state of AutoGenerated column headers because they're up-to-date - if (!column.IsAutoGenerated && column.HasHeaderCell) - { - column.HeaderCell.UpdatePseudoClasses(); - } - } - - RefreshRows(recycleRows: false, clearRows: false); - - if (Columns.Count > 0 && CurrentColumnIndex == -1) - { - MakeFirstDisplayedCellCurrentCell(); - } - else - { - _makeFirstDisplayedCellCurrentCellPending = false; - _desiredCurrentColumnIndex = -1; - FlushCurrentCellChanged(); - } - } - finally - { - NoCurrentCellChangeCount--; - } - } - else - { - if (clearRows) - { - ClearRows(recycle: false); - } - ClearRowGroupHeadersTable(); - PopulateRowGroupHeadersTable(); - } - } - - internal bool ScrollSlotIntoView(int columnIndex, int slot, bool forCurrentCellChange, bool forceHorizontalScroll) - { - Debug.Assert(columnIndex >= 0 && columnIndex < ColumnsItemsInternal.Count); - Debug.Assert(DisplayData.FirstDisplayedScrollingCol >= -1 && DisplayData.FirstDisplayedScrollingCol < ColumnsItemsInternal.Count); - Debug.Assert(DisplayData.LastTotallyDisplayedScrollingCol >= -1 && DisplayData.LastTotallyDisplayedScrollingCol < ColumnsItemsInternal.Count); - Debug.Assert(!IsSlotOutOfBounds(slot)); - Debug.Assert(DisplayData.FirstScrollingSlot >= -1 && DisplayData.FirstScrollingSlot < SlotCount); - Debug.Assert(ColumnsItemsInternal[columnIndex].IsVisible); - - if (CurrentColumnIndex >= 0 && - (CurrentColumnIndex != columnIndex || CurrentSlot != slot)) - { - if (!CommitEditForOperation(columnIndex, slot, forCurrentCellChange) || IsInnerCellOutOfBounds(columnIndex, slot)) - { - return false; - } - } - - double oldHorizontalOffset = HorizontalOffset; - - //scroll horizontally unless we're on a RowGroupHeader and we're not forcing horizontal scrolling - if ((forceHorizontalScroll || (slot != -1)) - && !ScrollColumnIntoView(columnIndex)) - { - return false; - } - - //scroll vertically - if (!ScrollSlotIntoView(slot, scrolledHorizontally: oldHorizontalOffset != HorizontalOffset)) - { - return false; - } - - return true; - } - - // Convenient overload that commits the current edit. - internal bool SetCurrentCellCore(int columnIndex, int slot) - { - return SetCurrentCellCore(columnIndex, slot, commitEdit: true, endRowEdit: true); - } - - internal bool UpdateHorizontalOffset(double newValue) - { - if (HorizontalOffset != newValue) - { - HorizontalOffset = newValue; - - InvalidateColumnHeadersMeasure(); - InvalidateRowsMeasure(true); - return true; - } - return false; - } - - internal bool UpdateSelectionAndCurrency(int columnIndex, int slot, DataGridSelectionAction action, bool scrollIntoView) - { - _successfullyUpdatedSelection = false; - - _noSelectionChangeCount++; - _noCurrentCellChangeCount++; - try - { - if (ColumnsInternal.RowGroupSpacerColumn.IsRepresented && - columnIndex == ColumnsInternal.RowGroupSpacerColumn.Index) - { - columnIndex = -1; - } - if (IsSlotOutOfSelectionBounds(slot) || (columnIndex != -1 && IsColumnOutOfBounds(columnIndex))) - { - return false; - } - - int newCurrentPosition = -1; - object item = ItemFromSlot(slot, ref newCurrentPosition); - - if (EditingRow != null && slot != EditingRow.Slot && !CommitEdit(DataGridEditingUnit.Row, true)) - { - return false; - } - - if (DataConnection.CollectionView != null && - DataConnection.CollectionView.CurrentPosition != newCurrentPosition) - { - DataConnection.MoveCurrentTo(item, slot, columnIndex, action, scrollIntoView); - } - else - { - ProcessSelectionAndCurrency(columnIndex, item, slot, action, scrollIntoView); - } - } - finally - { - NoCurrentCellChangeCount--; - NoSelectionChangeCount--; - } - - return _successfullyUpdatedSelection; - } - - internal void UpdateStateOnCurrentChanged(object currentItem, int currentPosition) - { - if (currentItem == CurrentItem && currentItem == SelectedItem && currentPosition == SelectedIndex) - { - // The DataGrid's CurrentItem is already up-to-date, so we don't need to do anything - return; - } - - int columnIndex = CurrentColumnIndex; - if (columnIndex == -1) - { - if (IsColumnOutOfBounds(_desiredCurrentColumnIndex) || - (ColumnsInternal.RowGroupSpacerColumn.IsRepresented && _desiredCurrentColumnIndex == ColumnsInternal.RowGroupSpacerColumn.Index)) - { - columnIndex = FirstDisplayedNonFillerColumnIndex; - } - else - { - columnIndex = _desiredCurrentColumnIndex; - } - } - _desiredCurrentColumnIndex = -1; - - try - { - _noSelectionChangeCount++; - _noCurrentCellChangeCount++; - - if (!CommitEdit()) - { - CancelEdit(DataGridEditingUnit.Row, false); - } - - ClearRowSelection(true); - if (currentItem == null) - { - SetCurrentCellCore(-1, -1); - } - else - { - int slot = SlotFromRowIndex(currentPosition); - ProcessSelectionAndCurrency(columnIndex, currentItem, slot, DataGridSelectionAction.SelectCurrent, false); - } - } - finally - { - NoCurrentCellChangeCount--; - NoSelectionChangeCount--; - } - } - - //TODO: Ensure right button is checked for - internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit) - { - KeyboardHelper.GetMetaKeyState(this, pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift); - return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl); - } - //TODO: Ensure left button is checked for - internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit) - { - KeyboardHelper.GetMetaKeyState(this, pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift); - return UpdateStateOnMouseLeftButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl); - } - - internal void UpdateVerticalScrollBar() - { - if (_vScrollBar != null && _vScrollBar.IsVisible) - { - double cellsHeight = CellsEstimatedHeight; - double edgedRowsHeightCalculated = EdgedRowsHeightCalculated; - UpdateVerticalScrollBar( - needVertScrollbar: edgedRowsHeightCalculated > cellsHeight, - forceVertScrollbar: VerticalScrollBarVisibility == ScrollBarVisibility.Visible, - totalVisibleHeight: edgedRowsHeightCalculated, - cellsHeight: cellsHeight); - } - } - - /// - /// If the editing element has focus, this method will set focus to the DataGrid itself - /// in order to force the element to lose focus. It will then wait for the editing element's - /// LostFocus event, at which point it will perform the specified action. - /// - /// NOTE: It is important to understand that the specified action will be performed when the editing - /// element loses focus only if this method returns true. If it returns false, then the action - /// will not be performed later on, and should instead be performed by the caller, if necessary. - /// - /// Action to perform after the editing element loses focus - /// True if the editing element had focus and the action was cached away; false otherwise - //TODO TabStop - internal bool WaitForLostFocus(Action action) - { - if (EditingRow != null && EditingColumnIndex != -1 && !_executingLostFocusActions) - { - DataGridColumn editingColumn = ColumnsItemsInternal[EditingColumnIndex]; - Control editingElement = editingColumn.GetCellContent(EditingRow); - if (editingElement != null && editingElement.ContainsChild(_focusedObject)) - { - Debug.Assert(_lostFocusActions != null); - _lostFocusActions.Enqueue(action); - editingElement.LostFocus += EditingElement_LostFocus; - //IsTabStop = true; - Focus(); - return true; - } - } - return false; - } - - /// - /// Raises the LoadingRowDetails for row details preparation - /// - protected virtual void OnLoadingRowDetails(DataGridRowDetailsEventArgs e) - { - EventHandler handler = LoadingRowDetails; - if (handler != null) - { - LoadingOrUnloadingRow = true; - handler(this, e); - LoadingOrUnloadingRow = false; - } - } - - /// - /// Raises the UnloadingRowDetails event - /// - protected virtual void OnUnloadingRowDetails(DataGridRowDetailsEventArgs e) - { - EventHandler handler = UnloadingRowDetails; - if (handler != null) - { - LoadingOrUnloadingRow = true; - handler(this, e); - LoadingOrUnloadingRow = false; - } - } - - internal void OnRowDetailsChanged() - { - if (!_scrollingByHeight) - { - // Update layout when RowDetails are expanded or collapsed, just updating the vertical scroll bar is not enough - // since rows could be added or removed - InvalidateMeasure(); - } - } - - private static void NotifyDataContextPropertyForAllRowCells(IEnumerable rowSource, bool arg2) - { - foreach (DataGridRow row in rowSource) - { - foreach (DataGridCell cell in row.Cells) - { - if (cell.Content is StyledElement cellContent) - { - DataContextProperty.Notifying?.Invoke(cellContent, arg2); - } - } - } - } - - private void UpdateRowDetailsVisibilityMode(DataGridRowDetailsVisibilityMode newDetailsMode) - { - int itemCount = DataConnection.Count; - if (_rowsPresenter != null && itemCount > 0) - { - bool newDetailsVisibility = false; - switch (newDetailsMode) - { - case DataGridRowDetailsVisibilityMode.Visible: - newDetailsVisibility = true; - _showDetailsTable.AddValues(0, itemCount, true); - break; - case DataGridRowDetailsVisibilityMode.Collapsed: - newDetailsVisibility = false; - _showDetailsTable.AddValues(0, itemCount, false); - break; - case DataGridRowDetailsVisibilityMode.VisibleWhenSelected: - _showDetailsTable.Clear(); - break; - } - - bool updated = false; - foreach (DataGridRow row in GetAllRows()) - { - if (row.IsVisible) - { - if (newDetailsMode == DataGridRowDetailsVisibilityMode.VisibleWhenSelected) - { - // For VisibleWhenSelected, we need to calculate the value for each individual row - newDetailsVisibility = _selectedItems.ContainsSlot(row.Slot); - } - if (row.AreDetailsVisible != newDetailsVisibility) - { - updated = true; - - row.SetDetailsVisibilityInternal(newDetailsVisibility, raiseNotification: true, animate: false); - } - } - } - if (updated) - { - UpdateDisplayedRows(DisplayData.FirstScrollingSlot, CellsEstimatedHeight); - InvalidateRowsMeasure(invalidateIndividualElements: false); - } - } - } - - private void AddNewCellPrivate(DataGridRow row, DataGridColumn column) - { - DataGridCell newCell = new DataGridCell(); - PopulateCellContent( - isCellEdited: false, - dataGridColumn: column, - dataGridRow: row, - dataGridCell: newCell); - if (row.OwningGrid != null) - { - newCell.OwningColumn = column; - newCell.IsVisible = column.IsVisible; - if (row.OwningGrid.CellTheme is {} cellTheme) - { - newCell.SetValue(ThemeProperty, cellTheme, BindingPriority.Template); - } - } - row.Cells.Insert(column.Index, newCell); - } - - private bool BeginCellEdit(RoutedEventArgs editingEventArgs) - { - if (CurrentColumnIndex == -1 || !GetRowSelection(CurrentSlot)) - { - return false; - } - - Debug.Assert(CurrentColumnIndex >= 0); - Debug.Assert(CurrentColumnIndex < ColumnsItemsInternal.Count); - Debug.Assert(CurrentSlot >= -1); - Debug.Assert(CurrentSlot < SlotCount); - Debug.Assert(EditingRow == null || EditingRow.Slot == CurrentSlot); - Debug.Assert(!GetColumnEffectiveReadOnlyState(CurrentColumn)); - Debug.Assert(CurrentColumn.IsVisible); - - if (_editingColumnIndex != -1) - { - // Current cell is already in edit mode - Debug.Assert(_editingColumnIndex == CurrentColumnIndex); - return true; - } - - // Get or generate the editing row if it doesn't exist - DataGridRow dataGridRow = EditingRow; - if (dataGridRow == null) - { - if (IsSlotVisible(CurrentSlot)) - { - dataGridRow = DisplayData.GetDisplayedElement(CurrentSlot) as DataGridRow; - Debug.Assert(dataGridRow != null); - } - else - { - dataGridRow = GenerateRow(RowIndexFromSlot(CurrentSlot), CurrentSlot); - } - } - Debug.Assert(dataGridRow != null); - - // Cache these to see if they change later - int currentRowIndex = CurrentSlot; - int currentColumnIndex = CurrentColumnIndex; - - // Raise the BeginningEdit event - DataGridCell dataGridCell = dataGridRow.Cells[CurrentColumnIndex]; - DataGridBeginningEditEventArgs e = new DataGridBeginningEditEventArgs(CurrentColumn, dataGridRow, editingEventArgs); - OnBeginningEdit(e); - if (e.Cancel - || currentRowIndex != CurrentSlot - || currentColumnIndex != CurrentColumnIndex - || !GetRowSelection(CurrentSlot) - || (EditingRow == null && !BeginRowEdit(dataGridRow))) - { - // If either BeginningEdit was canceled, currency/selection was changed in the event handler, - // or we failed opening the row for edit, then we can no longer continue BeginCellEdit - return false; - } - Debug.Assert(EditingRow != null); - Debug.Assert(EditingRow.Slot == CurrentSlot); - - // Finally, we can prepare the cell for editing - _editingColumnIndex = CurrentColumnIndex; - _editingEventArgs = editingEventArgs; - EditingRow.Cells[CurrentColumnIndex].UpdatePseudoClasses(); - PopulateCellContent( - isCellEdited: true, - dataGridColumn: CurrentColumn, - dataGridRow: dataGridRow, - dataGridCell: dataGridCell); - return true; - } - - //TODO Validation - private bool BeginRowEdit(DataGridRow dataGridRow) - { - Debug.Assert(EditingRow == null); - Debug.Assert(dataGridRow != null); - - Debug.Assert(CurrentSlot >= -1); - Debug.Assert(CurrentSlot < SlotCount); - - if (DataConnection.BeginEdit(dataGridRow.DataContext)) - { - EditingRow = dataGridRow; - GenerateEditingElements(); - return true; - } - return false; - } - - private bool CancelRowEdit(bool exitEditingMode) - { - if (EditingRow == null) - { - return true; - } - Debug.Assert(EditingRow != null && EditingRow.Index >= -1); - Debug.Assert(EditingRow.Slot < SlotCount); - Debug.Assert(CurrentColumn != null); - - object dataItem = EditingRow.DataContext; - if (!DataConnection.CancelEdit(dataItem)) - { - return false; - } - foreach (DataGridColumn column in Columns) - { - if (!exitEditingMode && column.Index == _editingColumnIndex && column is DataGridBoundColumn) - { - continue; - } - PopulateCellContent( - isCellEdited: !exitEditingMode && column.Index == _editingColumnIndex, - dataGridColumn: column, - dataGridRow: EditingRow, - dataGridCell: EditingRow.Cells[column.Index]); - } - return true; - } - - private bool CommitEditForOperation(int columnIndex, int slot, bool forCurrentCellChange) - { - if (forCurrentCellChange) - { - if (!EndCellEdit(DataGridEditAction.Commit, exitEditingMode: true, keepFocus: true, raiseEvents: true)) - { - return false; - } - if (CurrentSlot != slot && - !EndRowEdit(DataGridEditAction.Commit, exitEditingMode: true, raiseEvents: true)) - { - return false; - } - } - - if (IsColumnOutOfBounds(columnIndex)) - { - return false; - } - if (slot >= SlotCount) - { - // Current cell was reset because the commit deleted row(s). - // Since the user wants to change the current cell, we don't - // want to end up with no current cell. We pick the last row - // in the grid which may be the 'new row'. - int lastSlot = LastVisibleSlot; - if (forCurrentCellChange && - CurrentColumnIndex == -1 && - lastSlot != -1) - { - SetAndSelectCurrentCell(columnIndex, lastSlot, forceCurrentCellSelection: false); - } - // Interrupt operation because it has become invalid. - return false; - } - return true; - } - - //TODO Validation - private bool CommitRowEdit(bool exitEditingMode) - { - if (EditingRow == null) - { - return true; - } - Debug.Assert(EditingRow != null && EditingRow.Index >= -1); - Debug.Assert(EditingRow.Slot < SlotCount); - - //if (!ValidateEditingRow(scrollIntoView: true, wireEvents: false)) - if (!EditingRow.IsValid) - { - return false; - } - - DataConnection.EndEdit(EditingRow.DataContext); - - if (!exitEditingMode) - { - DataConnection.BeginEdit(EditingRow.DataContext); - } - return true; - } - - private void CompleteCellsCollection(DataGridRow dataGridRow) - { - Debug.Assert(dataGridRow != null); - int cellsInCollection = dataGridRow.Cells.Count; - if (ColumnsItemsInternal.Count > cellsInCollection) - { - for (int columnIndex = cellsInCollection; columnIndex < ColumnsItemsInternal.Count; columnIndex++) - { - AddNewCellPrivate(dataGridRow, ColumnsItemsInternal[columnIndex]); - } - } - } - - private void ComputeScrollBarsLayout() - { - if (_ignoreNextScrollBarsLayout) - { - _ignoreNextScrollBarsLayout = false; - // - - } - - bool isHorizontalScrollBarOverCells = IsHorizontalScrollBarOverCells; - bool isVerticalScrollBarOverCells = IsVerticalScrollBarOverCells; - - double cellsWidth = CellsWidth; - double cellsHeight = CellsEstimatedHeight; - - bool allowHorizScrollbar = false; - bool forceHorizScrollbar = false; - double horizScrollBarHeight = 0; - if (_hScrollBar != null) - { - forceHorizScrollbar = HorizontalScrollBarVisibility == ScrollBarVisibility.Visible; - allowHorizScrollbar = forceHorizScrollbar || (ColumnsInternal.VisibleColumnCount > 0 && - HorizontalScrollBarVisibility != ScrollBarVisibility.Disabled && - HorizontalScrollBarVisibility != ScrollBarVisibility.Hidden); - // Compensate if the horizontal scrollbar is already taking up space - if (!forceHorizScrollbar && _hScrollBar.IsVisible) - { - if (!isHorizontalScrollBarOverCells) - { - cellsHeight += _hScrollBar.DesiredSize.Height; - } - } - if (!isHorizontalScrollBarOverCells) - { - horizScrollBarHeight = _hScrollBar.Height + _hScrollBar.Margin.Top + _hScrollBar.Margin.Bottom; - } - } - - bool allowVertScrollbar = false; - bool forceVertScrollbar = false; - double vertScrollBarWidth = 0; - if (_vScrollBar != null) - { - forceVertScrollbar = VerticalScrollBarVisibility == ScrollBarVisibility.Visible; - allowVertScrollbar = forceVertScrollbar || (ColumnsItemsInternal.Count > 0 && - VerticalScrollBarVisibility != ScrollBarVisibility.Disabled && - VerticalScrollBarVisibility != ScrollBarVisibility.Hidden); - // Compensate if the vertical scrollbar is already taking up space - if (!forceVertScrollbar && _vScrollBar.IsVisible) - { - if (!isVerticalScrollBarOverCells) - { - cellsWidth += _vScrollBar.DesiredSize.Width; - } - } - if (!isVerticalScrollBarOverCells) - { - vertScrollBarWidth = _vScrollBar.Width + _vScrollBar.Margin.Left + _vScrollBar.Margin.Right; - } - } - - // Now cellsWidth is the width potentially available for displaying data cells. - // Now cellsHeight is the height potentially available for displaying data cells. - - bool needHorizScrollbar = false; - bool needVertScrollbar = false; - - double totalVisibleWidth = ColumnsInternal.VisibleEdgedColumnsWidth; - double totalVisibleFrozenWidth = ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth(); - - UpdateDisplayedRows(DisplayData.FirstScrollingSlot, CellsEstimatedHeight); - double totalVisibleHeight = EdgedRowsHeightCalculated; - - if (!forceHorizScrollbar && !forceVertScrollbar) - { - bool needHorizScrollbarWithoutVertScrollbar = false; - - if (allowHorizScrollbar && - MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) && - MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) && - MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight)) - { - double oldDataHeight = cellsHeight; - cellsHeight -= horizScrollBarHeight; - Debug.Assert(cellsHeight >= 0); - needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true; - - if (vertScrollBarWidth > 0 && - allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) || - MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth))) - { - // Would we still need a horizontal scrollbar without the vertical one? - UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight); - if (DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount) - { - needHorizScrollbar = MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth - vertScrollBarWidth); - } - } - - if (!needHorizScrollbar) - { - // Restore old data height because turns out a horizontal scroll bar wouldn't make sense - cellsHeight = oldDataHeight; - } - } - - // Store the current FirstScrollingSlot because removing the horizontal scrollbar could scroll - // the DataGrid up; however, if we realize later that we need to keep the horizontal scrollbar - // then we should use the first slot stored here which is not scrolled. - int firstScrollingSlot = DisplayData.FirstScrollingSlot; - - UpdateDisplayedRows(firstScrollingSlot, cellsHeight); - if (allowVertScrollbar && - MathUtilities.GreaterThan(cellsHeight, 0) && - MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) && - DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount) - { - cellsWidth -= vertScrollBarWidth; - Debug.Assert(cellsWidth >= 0); - needVertScrollbar = true; - } - - DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn(); - - // we compute the number of visible columns only after we set up the vertical scroll bar. - ComputeDisplayedColumns(); - - if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) && - allowHorizScrollbar && - needVertScrollbar && !needHorizScrollbar && - MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) && - MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) && - MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight)) - { - cellsWidth += vertScrollBarWidth; - cellsHeight -= horizScrollBarHeight; - Debug.Assert(cellsHeight >= 0); - needVertScrollbar = false; - - UpdateDisplayedRows(firstScrollingSlot, cellsHeight); - if (cellsHeight > 0 && - vertScrollBarWidth <= cellsWidth && - DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount) - { - cellsWidth -= vertScrollBarWidth; - Debug.Assert(cellsWidth >= 0); - needVertScrollbar = true; - } - if (needVertScrollbar) - { - needHorizScrollbar = true; - } - else - { - needHorizScrollbar = needHorizScrollbarWithoutVertScrollbar; - } - } - } - else if (forceHorizScrollbar && !forceVertScrollbar) - { - if (allowVertScrollbar) - { - if (cellsHeight > 0 && - MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) && - DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount) - { - cellsWidth -= vertScrollBarWidth; - Debug.Assert(cellsWidth >= 0); - needVertScrollbar = true; - } - DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn(); - ComputeDisplayedColumns(); - } - needHorizScrollbar = totalVisibleWidth > cellsWidth && totalVisibleFrozenWidth < cellsWidth; - } - else if (!forceHorizScrollbar && forceVertScrollbar) - { - if (allowHorizScrollbar) - { - if (cellsWidth > 0 && - MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight) && - MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) && - MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth)) - { - cellsHeight -= horizScrollBarHeight; - Debug.Assert(cellsHeight >= 0); - needHorizScrollbar = true; - UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight); - } - DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn(); - ComputeDisplayedColumns(); - } - needVertScrollbar = DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount; - } - else - { - Debug.Assert(forceHorizScrollbar && forceVertScrollbar); - Debug.Assert(allowHorizScrollbar && allowVertScrollbar); - DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn(); - ComputeDisplayedColumns(); - needVertScrollbar = DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount; - needHorizScrollbar = totalVisibleWidth > cellsWidth && totalVisibleFrozenWidth < cellsWidth; - } - - UpdateHorizontalScrollBar(needHorizScrollbar, forceHorizScrollbar, totalVisibleWidth, totalVisibleFrozenWidth, cellsWidth); - UpdateVerticalScrollBar(needVertScrollbar, forceVertScrollbar, totalVisibleHeight, cellsHeight); - - if (_topRightCornerHeader != null) - { - // Show the TopRightHeaderCell based on vertical ScrollBar visibility - if (AreColumnHeadersVisible && - _vScrollBar != null && _vScrollBar.IsVisible) - { - _topRightCornerHeader.IsVisible = true; - } - else - { - _topRightCornerHeader.IsVisible = false; - } - } - - if (_bottomRightCorner != null) - { - // Show the BottomRightCorner when both scrollbars are visible. - _bottomRightCorner.IsVisible = - _hScrollBar != null && _hScrollBar.IsVisible && - _vScrollBar != null && _vScrollBar.IsVisible; - } - - DisplayData.FullyRecycleElements(); - } - - /// - /// Handles the current editing element's LostFocus event by performing any actions that - /// were cached by the WaitForLostFocus method. - /// - /// Editing element - /// RoutedEventArgs - private void EditingElement_LostFocus(object sender, RoutedEventArgs e) - { - if (sender is Control editingElement) - { - editingElement.LostFocus -= EditingElement_LostFocus; - if (EditingRow != null && _editingColumnIndex != -1) - { - FocusEditingCell(true); - } - Debug.Assert(_lostFocusActions != null); - try - { - _executingLostFocusActions = true; - while (_lostFocusActions.Count > 0) - { - _lostFocusActions.Dequeue()(); - } - } - finally - { - _executingLostFocusActions = false; - } - } - } - - // Makes sure horizontal layout is updated to reflect any changes that affect it - private void EnsureHorizontalLayout() - { - ColumnsInternal.EnsureVisibleEdgedColumnsWidth(); - InvalidateColumnHeadersMeasure(); - InvalidateRowsMeasure(true); - InvalidateMeasure(); - } - - private void EnsureRowHeaderWidth() - { - if (AreRowHeadersVisible) - { - if (AreColumnHeadersVisible) - { - EnsureTopLeftCornerHeader(); - } - - if (_rowsPresenter != null) - { - - bool updated = false; - - foreach (Control element in _rowsPresenter.Children) - { - if (element is DataGridRow row) - { - // If the RowHeader resulted in a different width the last time it was measured, we need - // to re-measure it - if (row.HeaderCell != null && row.HeaderCell.DesiredSize.Width != ActualRowHeaderWidth) - { - row.HeaderCell.InvalidateMeasure(); - updated = true; - } - } - else if (element is DataGridRowGroupHeader groupHeader && groupHeader.HeaderCell != null && groupHeader.HeaderCell.DesiredSize.Width != ActualRowHeaderWidth) - { - groupHeader.HeaderCell.InvalidateMeasure(); - updated = true; - } - } - - if (updated) - { - // We need to update the width of the horizontal scrollbar if the rowHeaders' width actually changed - InvalidateMeasure(); - } - } - } - } - - private void EnsureRowsPresenterVisibility() - { - if (_rowsPresenter != null) - { - // RowCount doesn't need to be considered, doing so might cause extra Visibility changes - _rowsPresenter.IsVisible = (ColumnsInternal.FirstVisibleNonFillerColumn != null); - } - } - - private void EnsureTopLeftCornerHeader() - { - if (_topLeftCornerHeader != null) - { - _topLeftCornerHeader.IsVisible = (HeadersVisibility == DataGridHeadersVisibility.All); - - if (_topLeftCornerHeader.IsVisible) - { - if (!double.IsNaN(RowHeaderWidth)) - { - // RowHeaderWidth is set explicitly so we should use that - _topLeftCornerHeader.Width = RowHeaderWidth; - } - else if (VisibleSlotCount > 0) - { - // RowHeaders AutoSize and we have at least 1 row so take the desired width - _topLeftCornerHeader.Width = RowHeadersDesiredWidth; - } - } - } - } - - private void InvalidateCellsArrange() - { - foreach (DataGridRow row in GetAllRows()) - { - row.InvalidateHorizontalArrange(); - } - } - - private void InvalidateColumnHeadersArrange() - { - if (_columnHeadersPresenter != null) - { - _columnHeadersPresenter.InvalidateArrange(); - } - } - - private void InvalidateColumnHeadersMeasure() - { - if (_columnHeadersPresenter != null) - { - EnsureColumnHeadersVisibility(); - _columnHeadersPresenter.InvalidateMeasure(); - } - } - - private void InvalidateRowsArrange() - { - if (_rowsPresenter != null) - { - _rowsPresenter.InvalidateArrange(); - } - } - - private void InvalidateRowsMeasure(bool invalidateIndividualElements) - { - if (_rowsPresenter != null) - { - _rowsPresenter.InvalidateMeasure(); - - if (invalidateIndividualElements) - { - foreach (Control element in _rowsPresenter.Children) - { - element.InvalidateMeasure(); - } - } - } - } - - //TODO: Make override? - private void DataGrid_GotFocus(object sender, RoutedEventArgs e) - { - if (!ContainsFocus) - { - ContainsFocus = true; - ApplyDisplayedRowsState(DisplayData.FirstScrollingSlot, DisplayData.LastScrollingSlot); - if (CurrentColumnIndex != -1 && IsSlotVisible(CurrentSlot)) - { - if (DisplayData.GetDisplayedElement(CurrentSlot) is DataGridRow row) - { - row.Cells[CurrentColumnIndex].UpdatePseudoClasses(); - } - } - } - - // Keep track of which row contains the newly focused element - DataGridRow focusedRow = null; - Visual focusedElement = e.Source as Visual; - _focusedObject = focusedElement; - while (focusedElement != null) - { - focusedRow = focusedElement as DataGridRow; - if (focusedRow != null && focusedRow.OwningGrid == this && _focusedRow != focusedRow) - { - ResetFocusedRow(); - _focusedRow = focusedRow.IsVisible ? focusedRow : null; - break; - } - focusedElement = focusedElement.GetVisualParent(); - } - } - - //TODO: Check - private void DataGrid_IsEnabledChanged(AvaloniaPropertyChangedEventArgs e) - { - } - - private void DataGrid_KeyDown(object sender, KeyEventArgs e) - { - if (!e.Handled) - { - e.Handled = ProcessDataGridKey(e); - } - } - - private void DataGrid_KeyUp(object sender, KeyEventArgs e) - { - if (e.Key == Key.Tab && CurrentColumnIndex != -1 && e.Source == this) - { - bool success = - ScrollSlotIntoView( - CurrentColumnIndex, CurrentSlot, - forCurrentCellChange: false, - forceHorizontalScroll: true); - Debug.Assert(success); - if (CurrentColumnIndex != -1 && SelectedItem == null) - { - SetRowSelection(CurrentSlot, isSelected: true, setAnchorSlot: true); - } - } - } - - //TODO: Make override? - private void DataGrid_LostFocus(object sender, RoutedEventArgs e) - { - _focusedObject = null; - if (ContainsFocus) - { - bool focusLeftDataGrid = true; - bool dataGridWillReceiveRoutedEvent = true; - Visual focusedObject = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual; - DataGridColumn editingColumn = null; - - while (focusedObject != null) - { - if (focusedObject == this) - { - focusLeftDataGrid = false; - break; - } - - // Walk up the visual tree. If we hit the root, try using the framework element's - // parent. We do this because Popups behave differently with respect to the visual tree, - // and it could have a parent even if the VisualTreeHelper doesn't find it. - var parent = focusedObject.Parent as Visual; - if (parent == null) - { - parent = focusedObject.GetVisualParent(); - } - else - { - dataGridWillReceiveRoutedEvent = false; - } - focusedObject = parent; - } - - if (EditingRow != null && EditingColumnIndex != -1) - { - editingColumn = ColumnsItemsInternal[EditingColumnIndex]; - - if (focusLeftDataGrid && editingColumn is DataGridTemplateColumn) - { - dataGridWillReceiveRoutedEvent = false; - } - } - - if (focusLeftDataGrid && !(editingColumn is DataGridTemplateColumn)) - { - ContainsFocus = false; - if (EditingRow != null) - { - CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true); - } - ResetFocusedRow(); - ApplyDisplayedRowsState(DisplayData.FirstScrollingSlot, DisplayData.LastScrollingSlot); - if (CurrentColumnIndex != -1 && IsSlotVisible(CurrentSlot)) - { - if (DisplayData.GetDisplayedElement(CurrentSlot) is DataGridRow row) - { - row.Cells[CurrentColumnIndex].UpdatePseudoClasses(); - } - } - } - else if (!dataGridWillReceiveRoutedEvent) - { - if (focusedObject is Control focusedElement) - { - focusedElement.LostFocus += ExternalEditingElement_LostFocus; - } - } - } - } - - private void EditingElement_Initialized(object sender, EventArgs e) - { - var element = sender as Control; - if (element != null) - { - element.Initialized -= EditingElement_Initialized; - } - PreparingCellForEditPrivate(element); - } - - //TODO Validation - //TODO Binding - //TODO TabStop - private bool EndCellEdit(DataGridEditAction editAction, bool exitEditingMode, bool keepFocus, bool raiseEvents) - { - if (_editingColumnIndex == -1) - { - return true; - } - - var editingRow = EditingRow; - if (editingRow is null) - { - return true; - } - - Debug.Assert(_editingColumnIndex >= 0); - Debug.Assert(_editingColumnIndex < ColumnsItemsInternal.Count); - Debug.Assert(_editingColumnIndex == CurrentColumnIndex); - - // Cache these to see if they change later - int currentSlot = CurrentSlot; - int currentColumnIndex = CurrentColumnIndex; - - // We're ready to start ending, so raise the event - DataGridCell editingCell = editingRow.Cells[_editingColumnIndex]; - var editingElement = editingCell.Content as Control; - if (editingElement == null) - { - return false; - } - if (raiseEvents) - { - DataGridCellEditEndingEventArgs e = new DataGridCellEditEndingEventArgs(CurrentColumn, editingRow, editingElement, editAction); - OnCellEditEnding(e); - if (e.Cancel) - { - // CellEditEnding has been cancelled - return false; - } - - // Ensure that the current cell wasn't changed in the user's CellEditEnding handler - if (_editingColumnIndex == -1 || - currentSlot != CurrentSlot || - currentColumnIndex != CurrentColumnIndex) - { - return true; - } - Debug.Assert(EditingRow != null); - Debug.Assert(EditingRow.Slot == currentSlot); - Debug.Assert(_editingColumnIndex != -1); - Debug.Assert(_editingColumnIndex == CurrentColumnIndex); - } - - // If we're canceling, let the editing column repopulate its old value if it wants - if (editAction == DataGridEditAction.Cancel) - { - CurrentColumn.CancelCellEditInternal(editingElement, _uneditedValue); - - // Ensure that the current cell wasn't changed in the user column's CancelCellEdit - if (_editingColumnIndex == -1 || - currentSlot != CurrentSlot || - currentColumnIndex != CurrentColumnIndex) - { - return true; - } - Debug.Assert(EditingRow != null); - Debug.Assert(EditingRow.Slot == currentSlot); - Debug.Assert(_editingColumnIndex != -1); - Debug.Assert(_editingColumnIndex == CurrentColumnIndex); - } - - // If we're committing, explicitly update the source but watch out for any validation errors - if (editAction == DataGridEditAction.Commit) - { - void SetValidationStatus(ICellEditBinding binding) - { - if (binding.IsValid) - { - ResetValidationStatus(); - if (editingElement != null) - { - DataValidationErrors.ClearErrors(editingElement); - } - } - else - { - if (editingRow != null) - { - if (editingCell.IsValid) - { - editingCell.IsValid = false; - editingCell.UpdatePseudoClasses(); - } - - if (editingRow.IsValid) - { - editingRow.IsValid = false; - editingRow.ApplyState(); - } - } - - if (editingElement != null) - { - DataValidationErrors.SetError(editingElement, - new AggregateException(binding.ValidationErrors)); - } - } - } - - var editBinding = CurrentColumn?.CellEditBinding; - if (editBinding != null && !editBinding.CommitEdit()) - { - SetValidationStatus(editBinding); - _validationSubscription?.Dispose(); - _validationSubscription = editBinding.ValidationChanged.Subscribe(v => SetValidationStatus(editBinding)); - - ScrollSlotIntoView(CurrentColumnIndex, CurrentSlot, forCurrentCellChange: false, forceHorizontalScroll: true); - return false; - } - } - - ResetValidationStatus(); - - if (exitEditingMode) - { - CurrentColumn.EndCellEditInternal(); - _editingColumnIndex = -1; - editingCell.UpdatePseudoClasses(); - - //IsTabStop = true; - if (keepFocus && editingElement.ContainsFocusedElement()) - { - Focus(); - } - - PopulateCellContent( - isCellEdited: !exitEditingMode, - dataGridColumn: CurrentColumn, - dataGridRow: editingRow, - dataGridCell: editingCell); - - editingRow.InvalidateDesiredHeight(); - var column = editingCell.OwningColumn; - if (column.Width.IsSizeToCells || column.Width.IsAuto) - {// Invalidate desired width and force recalculation - column.SetWidthDesiredValue(0); - editingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width); - } - } - - // We're done, so raise the CellEditEnded event - if (raiseEvents) - { - OnCellEditEnded(new DataGridCellEditEndedEventArgs(CurrentColumn, editingRow, editAction)); - } - - // There's a chance that somebody reopened this cell for edit within the CellEditEnded handler, - // so we should return false if we were supposed to exit editing mode, but we didn't - return !(exitEditingMode && currentColumnIndex == _editingColumnIndex); - } - - //TODO Validation - private bool EndRowEdit(DataGridEditAction editAction, bool exitEditingMode, bool raiseEvents) - { - if (EditingRow == null || DataConnection.CommittingEdit) - { - return true; - } - if (_editingColumnIndex != -1 || (editAction == DataGridEditAction.Cancel && raiseEvents && - !((DataConnection.EditableCollectionView != null && DataConnection.EditableCollectionView.CanCancelEdit) || (EditingRow.DataContext is IEditableObject)))) - { - // Ending the row edit will fail immediately under the following conditions: - // 1. We haven't ended the cell edit yet. - // 2. We're trying to cancel edit when the underlying DataType is not an IEditableObject, - // because we have no way to properly restore the old value. We will only allow this to occur - // if raiseEvents == false, which means we're internally forcing a cancel. - return false; - } - DataGridRow editingRow = EditingRow; - - if (raiseEvents) - { - DataGridRowEditEndingEventArgs e = new DataGridRowEditEndingEventArgs(EditingRow, editAction); - OnRowEditEnding(e); - if (e.Cancel) - { - // RowEditEnding has been cancelled - return false; - } - - // Editing states might have been changed in the RowEditEnding handlers - if (_editingColumnIndex != -1) - { - return false; - } - if (editingRow != EditingRow) - { - return true; - } - } - - // Call the appropriate commit or cancel methods - if (editAction == DataGridEditAction.Commit) - { - if (!CommitRowEdit(exitEditingMode)) - { - return false; - } - } - else - { - if (!CancelRowEdit(exitEditingMode) && raiseEvents) - { - // We failed to cancel edit so we should abort unless we're forcing a cancel - return false; - } - } - ResetValidationStatus(); - - // Update the previously edited row's state - if (exitEditingMode && editingRow == EditingRow) - { - RemoveEditingElements(); - ResetEditingRow(); - } - - // Raise the RowEditEnded event - if (raiseEvents) - { - OnRowEditEnded(new DataGridRowEditEndedEventArgs(editingRow, editAction)); - } - - return true; - } - - private void EnsureColumnHeadersVisibility() - { - if (_columnHeadersPresenter != null) - { - _columnHeadersPresenter.IsVisible = AreColumnHeadersVisible; - } - } - - private void EnsureVerticalGridLines() - { - if (AreColumnHeadersVisible) - { - double totalColumnsWidth = 0; - foreach (DataGridColumn column in ColumnsInternal) - { - totalColumnsWidth += column.ActualWidth; - - column.HeaderCell.AreSeparatorsVisible = (column != ColumnsInternal.LastVisibleColumn || totalColumnsWidth < CellsWidth); - } - } - - foreach (DataGridRow row in GetAllRows()) - { - row.EnsureGridLines(); - } - } - - /// - /// Exits editing mode without trying to commit or revert the editing, and - /// without repopulating the edited row's cell. - /// - //TODO TabStop - private void ExitEdit(bool keepFocus) - { - if (EditingRow == null || DataConnection.CommittingEdit) - { - Debug.Assert(_editingColumnIndex == -1); - return; - } - - if (_editingColumnIndex != -1) - { - Debug.Assert(_editingColumnIndex >= 0); - Debug.Assert(_editingColumnIndex < ColumnsItemsInternal.Count); - Debug.Assert(_editingColumnIndex == CurrentColumnIndex); - Debug.Assert(EditingRow != null && EditingRow.Slot == CurrentSlot); - - _editingColumnIndex = -1; - EditingRow.Cells[CurrentColumnIndex].UpdatePseudoClasses(); - } - //IsTabStop = true; - if (IsSlotVisible(EditingRow.Slot)) - { - EditingRow.ApplyState(); - } - ResetEditingRow(); - if (keepFocus) - { - Focus(); - } - } - - private void ExternalEditingElement_LostFocus(object sender, RoutedEventArgs e) - { - if (sender is Control element) - { - element.LostFocus -= ExternalEditingElement_LostFocus; - DataGrid_LostFocus(sender, e); - } - } - - private void FlushCurrentCellChanged() - { - if (_makeFirstDisplayedCellCurrentCellPending) - { - return; - } - if (SelectionHasChanged) - { - // selection is changing, don't raise CurrentCellChanged until it's done - _flushCurrentCellChanged = true; - FlushSelectionChanged(); - return; - } - - // We don't want to expand all intermediate currency positions, so we only expand - // the last current item before we flush the event - if (_collapsedSlotsTable.Contains(CurrentSlot)) - { - DataGridRowGroupInfo rowGroupInfo = RowGroupHeadersTable.GetValueAt(RowGroupHeadersTable.GetPreviousIndex(CurrentSlot)); - Debug.Assert(rowGroupInfo != null); - if (rowGroupInfo != null) - { - ExpandRowGroupParentChain(rowGroupInfo.Level, rowGroupInfo.Slot); - } - } - - if (CurrentColumn != _previousCurrentColumn - || CurrentItem != _previousCurrentItem) - { - CoerceSelectedItem(); - _previousCurrentColumn = CurrentColumn; - _previousCurrentItem = CurrentItem; - - OnCurrentCellChanged(EventArgs.Empty); - } - - _flushCurrentCellChanged = false; - } - - private void FlushSelectionChanged() - { - if (SelectionHasChanged && _noSelectionChangeCount == 0 && !_makeFirstDisplayedCellCurrentCellPending) - { - CoerceSelectedItem(); - if (NoCurrentCellChangeCount != 0) - { - // current cell is changing, don't raise SelectionChanged until it's done - return; - } - SelectionHasChanged = false; - - if (_flushCurrentCellChanged) - { - FlushCurrentCellChanged(); - } - - SelectionChangedEventArgs e = _selectedItems.GetSelectionChangedEventArgs(); - if (e.AddedItems.Count > 0 || e.RemovedItems.Count > 0) - { - OnSelectionChanged(e); - } - } - } - - //TODO TabStop - private bool FocusEditingCell(bool setFocus) - { - Debug.Assert(CurrentColumnIndex >= 0); - Debug.Assert(CurrentColumnIndex < ColumnsItemsInternal.Count); - Debug.Assert(CurrentSlot >= -1); - Debug.Assert(CurrentSlot < SlotCount); - Debug.Assert(EditingRow != null && EditingRow.Slot == CurrentSlot); - Debug.Assert(_editingColumnIndex != -1); - - //IsTabStop = false; - _focusEditingControl = false; - - bool success = false; - DataGridCell dataGridCell = EditingRow.Cells[_editingColumnIndex]; - if (setFocus) - { - if (dataGridCell.ContainsFocusedElement()) - { - success = true; - } - else - { - dataGridCell.Focus(); - success = dataGridCell.ContainsFocusedElement(); - } - - _focusEditingControl = !success; - } - return success; - } - - // Calculates the amount to scroll for the ScrollLeft button - // This is a method rather than a property to emphasize a calculation - private double GetHorizontalSmallScrollDecrease() - { - // If the first column is covered up, scroll to the start of it when the user clicks the left button - if (_negHorizontalOffset > 0) - { - return _negHorizontalOffset; - } - else - { - // The entire first column is displayed, show the entire previous column when the user clicks - // the left button - DataGridColumn previousColumn = ColumnsInternal.GetPreviousVisibleScrollingColumn( - ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol]); - if (previousColumn != null) - { - return GetEdgedColumnWidth(previousColumn); - } - else - { - // There's no previous column so don't move - return 0; - } - } - } - - // Calculates the amount to scroll for the ScrollRight button - // This is a method rather than a property to emphasize a calculation - private double GetHorizontalSmallScrollIncrease() - { - if (DisplayData.FirstDisplayedScrollingCol >= 0) - { - return GetEdgedColumnWidth(ColumnsItemsInternal[DisplayData.FirstDisplayedScrollingCol]) - _negHorizontalOffset; - } - return 0; - } - - // Calculates the amount the ScrollDown button should scroll - // This is a method rather than a property to emphasize that calculations are taking place - private double GetVerticalSmallScrollIncrease() - { - if (DisplayData.FirstScrollingSlot >= 0) - { - return GetExactSlotElementHeight(DisplayData.FirstScrollingSlot) - NegVerticalOffset; - } - return 0; - } - - private void HorizontalScrollBar_Scroll(object sender, ScrollEventArgs e) - { - ProcessHorizontalScroll(e.ScrollEventType); - HorizontalScroll?.Invoke(sender, e); - } - - private bool IsColumnOutOfBounds(int columnIndex) - { - return columnIndex >= ColumnsItemsInternal.Count || columnIndex < 0; - } - - private bool IsInnerCellOutOfBounds(int columnIndex, int slot) - { - return IsColumnOutOfBounds(columnIndex) || IsSlotOutOfBounds(slot); - } - - private bool IsInnerCellOutOfSelectionBounds(int columnIndex, int slot) - { - return IsColumnOutOfBounds(columnIndex) || IsSlotOutOfSelectionBounds(slot); - } - - private bool IsSlotOutOfBounds(int slot) - { - return slot >= SlotCount || slot < -1 || _collapsedSlotsTable.Contains(slot); - } - - private bool IsSlotOutOfSelectionBounds(int slot) - { - if (RowGroupHeadersTable.Contains(slot)) - { - Debug.Assert(slot >= 0 && slot < SlotCount); - return false; - } - else - { - int rowIndex = RowIndexFromSlot(slot); - return rowIndex < 0 || rowIndex >= DataConnection.Count; - } - } - - private void MakeFirstDisplayedCellCurrentCell() - { - if (CurrentColumnIndex != -1) - { - _makeFirstDisplayedCellCurrentCellPending = false; - _desiredCurrentColumnIndex = -1; - FlushCurrentCellChanged(); - return; - } - if (SlotCount != SlotFromRowIndex(DataConnection.Count)) - { - _makeFirstDisplayedCellCurrentCellPending = true; - return; - } - - // No current cell, therefore no selection either - try to set the current cell to the - // ItemsSource's ICollectionView.CurrentItem if it exists, otherwise use the first displayed cell. - int slot = 0; - if (DataConnection.CollectionView != null) - { - if (DataConnection.CollectionView.IsCurrentBeforeFirst || - DataConnection.CollectionView.IsCurrentAfterLast) - { - slot = RowGroupHeadersTable.Contains(0) ? 0 : -1; - } - else - { - slot = SlotFromRowIndex(DataConnection.CollectionView.CurrentPosition); - } - } - else - { - if (SelectedIndex == -1) - { - // Try to default to the first row - slot = SlotFromRowIndex(0); - if (!IsSlotVisible(slot)) - { - slot = -1; - } - } - else - { - slot = SlotFromRowIndex(SelectedIndex); - } - } - int columnIndex = FirstDisplayedNonFillerColumnIndex; - if (_desiredCurrentColumnIndex >= 0 && _desiredCurrentColumnIndex < ColumnsItemsInternal.Count) - { - columnIndex = _desiredCurrentColumnIndex; - } - - SetAndSelectCurrentCell(columnIndex, slot, forceCurrentCellSelection: false); - AnchorSlot = slot; - _makeFirstDisplayedCellCurrentCellPending = false; - _desiredCurrentColumnIndex = -1; - FlushCurrentCellChanged(); - } - - private void PopulateCellContent(bool isCellEdited, - DataGridColumn dataGridColumn, - DataGridRow dataGridRow, - DataGridCell dataGridCell) - { - Debug.Assert(dataGridColumn != null); - Debug.Assert(dataGridRow != null); - Debug.Assert(dataGridCell != null); - - Control element = null; - DataGridBoundColumn dataGridBoundColumn = dataGridColumn as DataGridBoundColumn; - if (isCellEdited) - { - // Generate EditingElement and apply column style if available - element = dataGridColumn.GenerateEditingElementInternal(dataGridCell, dataGridRow.DataContext); - if (element != null) - { - - dataGridCell.Content = element; - if (element.IsInitialized) - { - PreparingCellForEditPrivate(element as Control); - } - else - { - // Subscribe to the new element's events - element.Initialized += EditingElement_Initialized; - } - } - } - else - { - // Generate Element and apply column style if available - element = dataGridColumn.GenerateElementInternal(dataGridCell, dataGridRow.DataContext); - dataGridCell.Content = element; - } - - - } - - private void PreparingCellForEditPrivate(Control editingElement) - { - if (_editingColumnIndex == -1 || - CurrentColumnIndex == -1 || - EditingRow.Cells[CurrentColumnIndex].Content != editingElement) - { - // The current cell has changed since the call to BeginCellEdit, so the fact - // that this element has loaded is no longer relevant - return; - } - - Debug.Assert(EditingRow != null); - Debug.Assert(_editingColumnIndex >= 0); - Debug.Assert(_editingColumnIndex < ColumnsItemsInternal.Count); - Debug.Assert(_editingColumnIndex == CurrentColumnIndex); - Debug.Assert(EditingRow != null && EditingRow.Slot == CurrentSlot); - - FocusEditingCell(setFocus: ContainsFocus || _focusEditingControl); - - // Prepare the cell for editing and raise the PreparingCellForEdit event for all columns - DataGridColumn dataGridColumn = CurrentColumn; - _uneditedValue = dataGridColumn.PrepareCellForEditInternal(editingElement, _editingEventArgs); - OnPreparingCellForEdit(new DataGridPreparingCellForEditEventArgs(dataGridColumn, EditingRow, _editingEventArgs, editingElement)); - } - - private bool ProcessAKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift, out bool alt); - - if (ctrl && !shift && !alt && SelectionMode == DataGridSelectionMode.Extended) - { - SelectAll(); - return true; - } - return false; - } - - //TODO TabStop - //TODO FlowDirection - private bool ProcessDataGridKey(KeyEventArgs e) - { - bool focusDataGrid = false; - switch (e.Key) - { - case Key.Tab: - return ProcessTabKey(e); - - case Key.Up: - focusDataGrid = ProcessUpKey(e); - break; - - case Key.Down: - focusDataGrid = ProcessDownKey(e); - break; - - case Key.PageDown: - focusDataGrid = ProcessNextKey(e); - break; - - case Key.PageUp: - focusDataGrid = ProcessPriorKey(e); - break; - - case Key.Left: - focusDataGrid = ProcessLeftKey(e); - break; - - case Key.Right: - focusDataGrid = ProcessRightKey(e); - break; - - case Key.F2: - return ProcessF2Key(e); - - case Key.Home: - focusDataGrid = ProcessHomeKey(e); - break; - - case Key.End: - focusDataGrid = ProcessEndKey(e); - break; - - case Key.Enter: - focusDataGrid = ProcessEnterKey(e); - break; - - case Key.Escape: - return ProcessEscapeKey(); - - case Key.A: - return ProcessAKey(e); - - case Key.C: - return ProcessCopyKey(e.KeyModifiers); - - case Key.Insert: - return ProcessCopyKey(e.KeyModifiers); - } - if (focusDataGrid) - { - Focus(); - } - return focusDataGrid; - } - - private bool ProcessDownKeyInternal(bool shift, bool ctrl) - { - DataGridColumn dataGridColumn = ColumnsInternal.FirstVisibleColumn; - int firstVisibleColumnIndex = (dataGridColumn == null) ? -1 : dataGridColumn.Index; - int lastSlot = LastVisibleSlot; - if (firstVisibleColumnIndex == -1 || lastSlot == -1) - { - return false; - } - - if (WaitForLostFocus(() => ProcessDownKeyInternal(shift, ctrl))) - { - return true; - } - - int nextSlot = -1; - if (CurrentSlot != -1) - { - nextSlot = GetNextVisibleSlot(CurrentSlot); - if (nextSlot >= SlotCount) - { - nextSlot = -1; - } - } - - _noSelectionChangeCount++; - try - { - int desiredSlot; - int columnIndex; - DataGridSelectionAction action; - if (CurrentColumnIndex == -1) - { - desiredSlot = FirstVisibleSlot; - columnIndex = firstVisibleColumnIndex; - action = DataGridSelectionAction.SelectCurrent; - } - else if (ctrl) - { - if (shift) - { - // Both Ctrl and Shift - desiredSlot = lastSlot; - columnIndex = CurrentColumnIndex; - action = (SelectionMode == DataGridSelectionMode.Extended) - ? DataGridSelectionAction.SelectFromAnchorToCurrent - : DataGridSelectionAction.SelectCurrent; - } - else - { - // Ctrl without Shift - desiredSlot = lastSlot; - columnIndex = CurrentColumnIndex; - action = DataGridSelectionAction.SelectCurrent; - } - } - else - { - if (nextSlot == -1) - { - return true; - } - if (shift) - { - // Shift without Ctrl - desiredSlot = nextSlot; - columnIndex = CurrentColumnIndex; - action = DataGridSelectionAction.SelectFromAnchorToCurrent; - } - else - { - // Neither Ctrl nor Shift - desiredSlot = nextSlot; - columnIndex = CurrentColumnIndex; - action = DataGridSelectionAction.SelectCurrent; - } - } - - UpdateSelectionAndCurrency(columnIndex, desiredSlot, action, scrollIntoView: true); - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - private bool ProcessEndKey(bool shift, bool ctrl) - { - DataGridColumn dataGridColumn = ColumnsInternal.LastVisibleColumn; - int lastVisibleColumnIndex = (dataGridColumn == null) ? -1 : dataGridColumn.Index; - int firstVisibleSlot = FirstVisibleSlot; - int lastVisibleSlot = LastVisibleSlot; - if (lastVisibleColumnIndex == -1 || firstVisibleSlot == -1) - { - return false; - } - - if (WaitForLostFocus(() => ProcessEndKey(shift, ctrl))) - { - return true; - } - - _noSelectionChangeCount++; - try - { - if (!ctrl) - { - return ProcessRightMost(lastVisibleColumnIndex, firstVisibleSlot); - } - else - { - DataGridSelectionAction action = (shift && SelectionMode == DataGridSelectionMode.Extended) - ? DataGridSelectionAction.SelectFromAnchorToCurrent - : DataGridSelectionAction.SelectCurrent; - - UpdateSelectionAndCurrency(lastVisibleColumnIndex, lastVisibleSlot, action, scrollIntoView: true); - } - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - private bool ProcessEnterKey(bool shift, bool ctrl) - { - int oldCurrentSlot = CurrentSlot; - - if (!ctrl) - { - // If Enter was used by a TextBox, we shouldn't handle the key - if (FocusManager.GetFocusManager(this)?.GetFocusedElement() is TextBox focusedTextBox - && focusedTextBox.AcceptsReturn) - { - return false; - } - - if (WaitForLostFocus(() => ProcessEnterKey(shift, ctrl))) - { - return true; - } - - // Enter behaves like down arrow - it commits the potential editing and goes down one cell. - if (!ProcessDownKeyInternal(false, ctrl)) - { - return false; - } - } - else if (WaitForLostFocus(() => ProcessEnterKey(shift, ctrl))) - { - return true; - } - - // Try to commit the potential editing - if (oldCurrentSlot == CurrentSlot && - EndCellEdit(DataGridEditAction.Commit, exitEditingMode: true, keepFocus: true, raiseEvents: true) && - EditingRow != null) - { - EndRowEdit(DataGridEditAction.Commit, exitEditingMode: true, raiseEvents: true); - ScrollIntoView(CurrentItem, CurrentColumn); - } - - return true; - } - - private bool ProcessEscapeKey() - { - if (WaitForLostFocus(() => ProcessEscapeKey())) - { - return true; - } - - if (_editingColumnIndex != -1) - { - // Revert the potential cell editing and exit cell editing. - EndCellEdit(DataGridEditAction.Cancel, exitEditingMode: true, keepFocus: true, raiseEvents: true); - return true; - } - else if (EditingRow != null) - { - // Revert the potential row editing and exit row editing. - EndRowEdit(DataGridEditAction.Cancel, exitEditingMode: true, raiseEvents: true); - return true; - } - return false; - } - - private bool ProcessF2Key(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - - if (!shift && !ctrl && - _editingColumnIndex == -1 && CurrentColumnIndex != -1 && GetRowSelection(CurrentSlot) && - !GetColumnEffectiveReadOnlyState(CurrentColumn)) - { - if (ScrollSlotIntoView(CurrentColumnIndex, CurrentSlot, forCurrentCellChange: false, forceHorizontalScroll: true)) - { - BeginCellEdit(e); - } - return true; - } - - return false; - } - - private bool ProcessHomeKey(bool shift, bool ctrl) - { - DataGridColumn dataGridColumn = ColumnsInternal.FirstVisibleNonFillerColumn; - int firstVisibleColumnIndex = (dataGridColumn == null) ? -1 : dataGridColumn.Index; - int firstVisibleSlot = FirstVisibleSlot; - if (firstVisibleColumnIndex == -1 || firstVisibleSlot == -1) - { - return false; - } - - if (WaitForLostFocus(() => ProcessHomeKey(shift, ctrl))) - { - return true; - } - - _noSelectionChangeCount++; - try - { - if (!ctrl) - { - return ProcessLeftMost(firstVisibleColumnIndex, firstVisibleSlot); - } - else - { - DataGridSelectionAction action = (shift && SelectionMode == DataGridSelectionMode.Extended) - ? DataGridSelectionAction.SelectFromAnchorToCurrent - : DataGridSelectionAction.SelectCurrent; - - UpdateSelectionAndCurrency(firstVisibleColumnIndex, firstVisibleSlot, action, scrollIntoView: true); - } - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - private bool ProcessLeftKey(bool shift, bool ctrl) - { - DataGridColumn dataGridColumn = ColumnsInternal.FirstVisibleNonFillerColumn; - int firstVisibleColumnIndex = (dataGridColumn == null) ? -1 : dataGridColumn.Index; - int firstVisibleSlot = FirstVisibleSlot; - if (firstVisibleColumnIndex == -1 || firstVisibleSlot == -1) - { - return false; - } - - if (WaitForLostFocus(() => ProcessLeftKey(shift, ctrl))) - { - return true; - } - - int previousVisibleColumnIndex = -1; - if (CurrentColumnIndex != -1) - { - dataGridColumn = ColumnsInternal.GetPreviousVisibleNonFillerColumn(ColumnsItemsInternal[CurrentColumnIndex]); - if (dataGridColumn != null) - { - previousVisibleColumnIndex = dataGridColumn.Index; - } - } - - _noSelectionChangeCount++; - try - { - if (ctrl) - { - return ProcessLeftMost(firstVisibleColumnIndex, firstVisibleSlot); - } - else - { - if (RowGroupHeadersTable.Contains(CurrentSlot)) - { - CollapseRowGroup(RowGroupHeadersTable.GetValueAt(CurrentSlot).CollectionViewGroup, collapseAllSubgroups: false); - } - else if (CurrentColumnIndex == -1) - { - UpdateSelectionAndCurrency( - firstVisibleColumnIndex, - firstVisibleSlot, - DataGridSelectionAction.SelectCurrent, - scrollIntoView: true); - } - else - { - if (previousVisibleColumnIndex == -1) - { - return true; - } - - UpdateSelectionAndCurrency( - previousVisibleColumnIndex, - CurrentSlot, - DataGridSelectionAction.None, - scrollIntoView: true); - } - } - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - // Ctrl Left <==> Home - private bool ProcessLeftMost(int firstVisibleColumnIndex, int firstVisibleSlot) - { - _noSelectionChangeCount++; - try - { - int desiredSlot; - DataGridSelectionAction action; - if (CurrentColumnIndex == -1) - { - desiredSlot = firstVisibleSlot; - action = DataGridSelectionAction.SelectCurrent; - Debug.Assert(_selectedItems.Count == 0); - } - else - { - desiredSlot = CurrentSlot; - action = DataGridSelectionAction.None; - } - - UpdateSelectionAndCurrency(firstVisibleColumnIndex, desiredSlot, action, scrollIntoView: true); - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - private bool ProcessNextKey(bool shift, bool ctrl) - { - DataGridColumn dataGridColumn = ColumnsInternal.FirstVisibleNonFillerColumn; - int firstVisibleColumnIndex = (dataGridColumn == null) ? -1 : dataGridColumn.Index; - if (firstVisibleColumnIndex == -1 || DisplayData.FirstScrollingSlot == -1) - { - return false; - } - - if (WaitForLostFocus(() => ProcessNextKey(shift, ctrl))) - { - return true; - } - - int nextPageSlot = CurrentSlot == -1 ? DisplayData.FirstScrollingSlot : CurrentSlot; - Debug.Assert(nextPageSlot != -1); - int slot = GetNextVisibleSlot(nextPageSlot); - - int scrollCount = DisplayData.NumTotallyDisplayedScrollingElements; - while (scrollCount > 0 && slot < SlotCount) - { - nextPageSlot = slot; - scrollCount--; - slot = GetNextVisibleSlot(slot); - } - - _noSelectionChangeCount++; - try - { - DataGridSelectionAction action; - int columnIndex; - if (CurrentColumnIndex == -1) - { - columnIndex = firstVisibleColumnIndex; - action = DataGridSelectionAction.SelectCurrent; - } - else - { - columnIndex = CurrentColumnIndex; - action = (shift && SelectionMode == DataGridSelectionMode.Extended) - ? action = DataGridSelectionAction.SelectFromAnchorToCurrent - : action = DataGridSelectionAction.SelectCurrent; - } - - UpdateSelectionAndCurrency(columnIndex, nextPageSlot, action, scrollIntoView: true); - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - private bool ProcessPriorKey(bool shift, bool ctrl) - { - DataGridColumn dataGridColumn = ColumnsInternal.FirstVisibleNonFillerColumn; - int firstVisibleColumnIndex = (dataGridColumn == null) ? -1 : dataGridColumn.Index; - if (firstVisibleColumnIndex == -1 || DisplayData.FirstScrollingSlot == -1) - { - return false; - } - - if (WaitForLostFocus(() => ProcessPriorKey(shift, ctrl))) - { - return true; - } - - int previousPageSlot = (CurrentSlot == -1) ? DisplayData.FirstScrollingSlot : CurrentSlot; - Debug.Assert(previousPageSlot != -1); - - int scrollCount = DisplayData.NumTotallyDisplayedScrollingElements; - int slot = GetPreviousVisibleSlot(previousPageSlot); - while (scrollCount > 0 && slot != -1) - { - previousPageSlot = slot; - scrollCount--; - slot = GetPreviousVisibleSlot(slot); - } - Debug.Assert(previousPageSlot != -1); - - _noSelectionChangeCount++; - try - { - int columnIndex; - DataGridSelectionAction action; - if (CurrentColumnIndex == -1) - { - columnIndex = firstVisibleColumnIndex; - action = DataGridSelectionAction.SelectCurrent; - } - else - { - columnIndex = CurrentColumnIndex; - action = (shift && SelectionMode == DataGridSelectionMode.Extended) - ? DataGridSelectionAction.SelectFromAnchorToCurrent - : DataGridSelectionAction.SelectCurrent; - } - - UpdateSelectionAndCurrency(columnIndex, previousPageSlot, action, scrollIntoView: true); - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - private bool ProcessRightKey(bool shift, bool ctrl) - { - DataGridColumn dataGridColumn = ColumnsInternal.LastVisibleColumn; - int lastVisibleColumnIndex = (dataGridColumn == null) ? -1 : dataGridColumn.Index; - int firstVisibleSlot = FirstVisibleSlot; - if (lastVisibleColumnIndex == -1 || firstVisibleSlot == -1) - { - return false; - } - - if (WaitForLostFocus(delegate { ProcessRightKey(shift, ctrl); })) - { - return true; - } - - int nextVisibleColumnIndex = -1; - if (CurrentColumnIndex != -1) - { - dataGridColumn = ColumnsInternal.GetNextVisibleColumn(ColumnsItemsInternal[CurrentColumnIndex]); - if (dataGridColumn != null) - { - nextVisibleColumnIndex = dataGridColumn.Index; - } - } - _noSelectionChangeCount++; - try - { - if (ctrl) - { - return ProcessRightMost(lastVisibleColumnIndex, firstVisibleSlot); - } - else - { - if (RowGroupHeadersTable.Contains(CurrentSlot)) - { - ExpandRowGroup(RowGroupHeadersTable.GetValueAt(CurrentSlot).CollectionViewGroup, expandAllSubgroups: false); - } - else if (CurrentColumnIndex == -1) - { - int firstVisibleColumnIndex = ColumnsInternal.FirstVisibleColumn == null ? -1 : ColumnsInternal.FirstVisibleColumn.Index; - - UpdateSelectionAndCurrency( - firstVisibleColumnIndex, - firstVisibleSlot, - DataGridSelectionAction.SelectCurrent, - scrollIntoView: true); - } - else - { - if (nextVisibleColumnIndex == -1) - { - return true; - } - - UpdateSelectionAndCurrency( - nextVisibleColumnIndex, - CurrentSlot, - DataGridSelectionAction.None, - scrollIntoView: true); - } - } - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - // Ctrl Right <==> End - private bool ProcessRightMost(int lastVisibleColumnIndex, int firstVisibleSlot) - { - _noSelectionChangeCount++; - try - { - int desiredSlot; - DataGridSelectionAction action; - if (CurrentColumnIndex == -1) - { - desiredSlot = firstVisibleSlot; - action = DataGridSelectionAction.SelectCurrent; - } - else - { - desiredSlot = CurrentSlot; - action = DataGridSelectionAction.None; - } - - UpdateSelectionAndCurrency(lastVisibleColumnIndex, desiredSlot, action, scrollIntoView: true); - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - private bool ProcessTabKey(KeyEventArgs e) - { - KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift); - return ProcessTabKey(e, shift, ctrl); - } - - private bool ProcessTabKey(KeyEventArgs e, bool shift, bool ctrl) - { - if (ctrl || _editingColumnIndex == -1 || IsReadOnly) - { - //Go to the next/previous control on the page when - // - Ctrl key is used - // - Potential current cell is not edited, or the datagrid is read-only. - return false; - } - - // Try to locate a writable cell before/after the current cell - Debug.Assert(CurrentColumnIndex != -1); - Debug.Assert(CurrentSlot != -1); - - int neighborVisibleWritableColumnIndex, neighborSlot; - DataGridColumn dataGridColumn; - if (shift) - { - dataGridColumn = ColumnsInternal.GetPreviousVisibleWritableColumn(ColumnsItemsInternal[CurrentColumnIndex]); - neighborSlot = GetPreviousVisibleSlot(CurrentSlot); - if (EditingRow != null) - { - while (neighborSlot != -1 && RowGroupHeadersTable.Contains(neighborSlot)) - { - neighborSlot = GetPreviousVisibleSlot(neighborSlot); - } - } - } - else - { - dataGridColumn = ColumnsInternal.GetNextVisibleWritableColumn(ColumnsItemsInternal[CurrentColumnIndex]); - neighborSlot = GetNextVisibleSlot(CurrentSlot); - if (EditingRow != null) - { - while (neighborSlot < SlotCount && RowGroupHeadersTable.Contains(neighborSlot)) - { - neighborSlot = GetNextVisibleSlot(neighborSlot); - } - } - } - neighborVisibleWritableColumnIndex = (dataGridColumn == null) ? -1 : dataGridColumn.Index; - - if (neighborVisibleWritableColumnIndex == -1 && (neighborSlot == -1 || neighborSlot >= SlotCount)) - { - // There is no previous/next row and no previous/next writable cell on the current row - return false; - } - - if (WaitForLostFocus(() => ProcessTabKey(e, shift, ctrl))) - { - return true; - } - - int targetSlot = -1, targetColumnIndex = -1; - - _noSelectionChangeCount++; - try - { - if (neighborVisibleWritableColumnIndex == -1) - { - targetSlot = neighborSlot; - if (shift) - { - Debug.Assert(ColumnsInternal.LastVisibleWritableColumn != null); - targetColumnIndex = ColumnsInternal.LastVisibleWritableColumn.Index; - } - else - { - Debug.Assert(ColumnsInternal.FirstVisibleWritableColumn != null); - targetColumnIndex = ColumnsInternal.FirstVisibleWritableColumn.Index; - } - } - else - { - targetSlot = CurrentSlot; - targetColumnIndex = neighborVisibleWritableColumnIndex; - } - - DataGridSelectionAction action; - if (targetSlot != CurrentSlot || (SelectionMode == DataGridSelectionMode.Extended)) - { - if (IsSlotOutOfBounds(targetSlot)) - { - return true; - } - action = DataGridSelectionAction.SelectCurrent; - } - else - { - action = DataGridSelectionAction.None; - } - - UpdateSelectionAndCurrency(targetColumnIndex, targetSlot, action, scrollIntoView: true); - } - finally - { - NoSelectionChangeCount--; - } - - if (_successfullyUpdatedSelection && !RowGroupHeadersTable.Contains(targetSlot)) - { - BeginCellEdit(e); - } - - // Return true to say we handled the key event even if the operation was unsuccessful. If we don't - // say we handled this event, the framework will continue to process the tab key and change focus. - return true; - } - - private bool ProcessUpKey(bool shift, bool ctrl) - { - DataGridColumn dataGridColumn = ColumnsInternal.FirstVisibleNonFillerColumn; - int firstVisibleColumnIndex = (dataGridColumn == null) ? -1 : dataGridColumn.Index; - int firstVisibleSlot = FirstVisibleSlot; - if (firstVisibleColumnIndex == -1 || firstVisibleSlot == -1) - { - return false; - } - - if (WaitForLostFocus(() => ProcessUpKey(shift, ctrl))) - { - return true; - } - - int previousVisibleSlot = (CurrentSlot != -1) ? GetPreviousVisibleSlot(CurrentSlot) : -1; - - _noSelectionChangeCount++; - - try - { - int slot; - int columnIndex; - DataGridSelectionAction action; - if (CurrentColumnIndex == -1) - { - slot = firstVisibleSlot; - columnIndex = firstVisibleColumnIndex; - action = DataGridSelectionAction.SelectCurrent; - } - else if (ctrl) - { - if (shift) - { - // Both Ctrl and Shift - slot = firstVisibleSlot; - columnIndex = CurrentColumnIndex; - action = (SelectionMode == DataGridSelectionMode.Extended) - ? DataGridSelectionAction.SelectFromAnchorToCurrent - : DataGridSelectionAction.SelectCurrent; - } - else - { - // Ctrl without Shift - slot = firstVisibleSlot; - columnIndex = CurrentColumnIndex; - action = DataGridSelectionAction.SelectCurrent; - } - } - else - { - if (previousVisibleSlot == -1) - { - return true; - } - if (shift) - { - // Shift without Ctrl - slot = previousVisibleSlot; - columnIndex = CurrentColumnIndex; - action = DataGridSelectionAction.SelectFromAnchorToCurrent; - } - else - { - // Neither Shift nor Ctrl - slot = previousVisibleSlot; - columnIndex = CurrentColumnIndex; - action = DataGridSelectionAction.SelectCurrent; - } - } - UpdateSelectionAndCurrency(columnIndex, slot, action, scrollIntoView: true); - } - finally - { - NoSelectionChangeCount--; - } - return _successfullyUpdatedSelection; - } - - private void RemoveDisplayedColumnHeader(DataGridColumn dataGridColumn) - { - if (_columnHeadersPresenter != null) - { - _columnHeadersPresenter.Children.Remove(dataGridColumn.HeaderCell); - } - } - - private void RemoveDisplayedColumnHeaders() - { - if (_columnHeadersPresenter != null) - { - _columnHeadersPresenter.Children.Clear(); - } - ColumnsInternal.FillerColumn.IsRepresented = false; - } - - private bool ResetCurrentCellCore() - { - return (CurrentColumnIndex == -1 || SetCurrentCellCore(-1, -1)); - } - - private void ResetEditingRow() - { - if (EditingRow != null - && EditingRow != _focusedRow - && !IsSlotVisible(EditingRow.Slot)) - { - // Unload the old editing row if it's off screen - EditingRow.Clip = null; - UnloadRow(EditingRow); - DisplayData.FullyRecycleElements(); - } - EditingRow = null; - } - - private void ResetFocusedRow() - { - if (_focusedRow != null - && _focusedRow != EditingRow - && !IsSlotVisible(_focusedRow.Slot)) - { - // Unload the old focused row if it's off screen - _focusedRow.Clip = null; - UnloadRow(_focusedRow); - DisplayData.FullyRecycleElements(); - } - _focusedRow = null; - } - - public void SelectAll() - { - SetRowsSelection(0, SlotCount - 1); - } - - private void SetAndSelectCurrentCell(int columnIndex, - int slot, - bool forceCurrentCellSelection) - { - DataGridSelectionAction action = forceCurrentCellSelection ? DataGridSelectionAction.SelectCurrent : DataGridSelectionAction.None; - UpdateSelectionAndCurrency(columnIndex, slot, action, scrollIntoView: false); - } - - // columnIndex = 2, rowIndex = -1 --> current cell belongs to the 'new row'. - // columnIndex = 2, rowIndex = 2 --> current cell is an inner cell - // columnIndex = -1, rowIndex = -1 --> current cell is reset - // columnIndex = -1, rowIndex = 2 --> Unexpected - private bool SetCurrentCellCore(int columnIndex, int slot, bool commitEdit, bool endRowEdit) - { - Debug.Assert(columnIndex < ColumnsItemsInternal.Count); - Debug.Assert(slot < SlotCount); - Debug.Assert(columnIndex == -1 || ColumnsItemsInternal[columnIndex].IsVisible); - Debug.Assert(!(columnIndex > -1 && slot == -1)); - - if (columnIndex == CurrentColumnIndex && - slot == CurrentSlot) - { - Debug.Assert(DataConnection != null); - Debug.Assert(_editingColumnIndex == -1 || _editingColumnIndex == CurrentColumnIndex); - Debug.Assert(EditingRow == null || EditingRow.Slot == CurrentSlot || DataConnection.CommittingEdit); - return true; - } - - Control oldDisplayedElement = null; - DataGridCellCoordinates oldCurrentCell = new DataGridCellCoordinates(CurrentCellCoordinates); - - object newCurrentItem = null; - if (!RowGroupHeadersTable.Contains(slot)) - { - int rowIndex = RowIndexFromSlot(slot); - if (rowIndex >= 0 && rowIndex < DataConnection.Count) - { - newCurrentItem = DataConnection.GetDataItem(rowIndex); - } - } - - if (CurrentColumnIndex > -1) - { - Debug.Assert(CurrentColumnIndex < ColumnsItemsInternal.Count); - Debug.Assert(CurrentSlot < SlotCount); - - if (!IsInnerCellOutOfBounds(oldCurrentCell.ColumnIndex, oldCurrentCell.Slot) && - IsSlotVisible(oldCurrentCell.Slot)) - { - oldDisplayedElement = DisplayData.GetDisplayedElement(oldCurrentCell.Slot); - } - - if (!RowGroupHeadersTable.Contains(oldCurrentCell.Slot) && !_temporarilyResetCurrentCell) - { - bool keepFocus = ContainsFocus; - if (commitEdit) - { - if (!EndCellEdit(DataGridEditAction.Commit, exitEditingMode: true, keepFocus: keepFocus, raiseEvents: true)) - { - return false; - } - // Resetting the current cell: setting it to (-1, -1) is not considered setting it out of bounds - if ((columnIndex != -1 && slot != -1 && IsInnerCellOutOfSelectionBounds(columnIndex, slot)) || - IsInnerCellOutOfSelectionBounds(oldCurrentCell.ColumnIndex, oldCurrentCell.Slot)) - { - return false; - } - - if (endRowEdit && !EndRowEdit(DataGridEditAction.Commit, exitEditingMode: true, raiseEvents: true)) - { - return false; - } - } - else - { - CancelEdit(DataGridEditingUnit.Row, false); - ExitEdit(keepFocus); - } - } - } - - if (newCurrentItem != null) - { - slot = SlotFromRowIndex(DataConnection.IndexOf(newCurrentItem)); - } - if (slot == -1 && columnIndex != -1) - { - return false; - } - CurrentColumnIndex = columnIndex; - CurrentSlot = slot; - - if (_temporarilyResetCurrentCell) - { - if (columnIndex != -1) - { - _temporarilyResetCurrentCell = false; - } - } - if (!_temporarilyResetCurrentCell && _editingColumnIndex != -1) - { - _editingColumnIndex = columnIndex; - } - - if (oldDisplayedElement != null) - { - if (oldDisplayedElement is DataGridRow row) - { - // Don't reset the state of the current cell if we're editing it because that would put it in an invalid state - UpdateCurrentState(oldDisplayedElement, oldCurrentCell.ColumnIndex, !(_temporarilyResetCurrentCell && row.IsEditing && _editingColumnIndex == oldCurrentCell.ColumnIndex)); - } - else - { - UpdateCurrentState(oldDisplayedElement, oldCurrentCell.ColumnIndex, applyCellState: false); - } - } - - if (CurrentColumnIndex > -1) - { - Debug.Assert(CurrentSlot > -1); - Debug.Assert(CurrentColumnIndex < ColumnsItemsInternal.Count); - Debug.Assert(CurrentSlot < SlotCount); - if (IsSlotVisible(CurrentSlot)) - { - UpdateCurrentState(DisplayData.GetDisplayedElement(CurrentSlot), CurrentColumnIndex, applyCellState: true); - } - } - - return true; - } - - private void SetVerticalOffset(double newVerticalOffset) - { - _verticalOffset = newVerticalOffset; - if (_vScrollBar != null && !MathUtilities.AreClose(newVerticalOffset, _vScrollBar.Value)) - { - _vScrollBar.Value = _verticalOffset; - } - } - - private void UpdateCurrentState(Control displayedElement, int columnIndex, bool applyCellState) - { - if (displayedElement is DataGridRow row) - { - if (AreRowHeadersVisible) - { - row.ApplyHeaderStatus(); - } - DataGridCell cell = row.Cells[columnIndex]; - if (applyCellState) - { - cell.UpdatePseudoClasses(); - } - } - else if (displayedElement is DataGridRowGroupHeader groupHeader) - { - groupHeader.UpdatePseudoClasses(); - if (AreRowHeadersVisible) - { - groupHeader.ApplyHeaderStatus(); - } - } - } - - private void UpdateHorizontalScrollBar(bool needHorizScrollbar, bool forceHorizScrollbar, double totalVisibleWidth, double totalVisibleFrozenWidth, double cellsWidth) - { - if (_hScrollBar != null) - { - if (needHorizScrollbar || forceHorizScrollbar) - { - // viewportSize - // v---v - //|<|_____|###|>| - // ^ ^ - // min max - - // we want to make the relative size of the thumb reflect the relative size of the viewing area - // viewportSize / (max + viewportSize) = cellsWidth / max - // -> viewportSize = max * cellsWidth / (max - cellsWidth) - - // always zero - _hScrollBar.Minimum = 0; - if (needHorizScrollbar) - { - // maximum travel distance -- not the total width - _hScrollBar.Maximum = totalVisibleWidth - cellsWidth; - Debug.Assert(totalVisibleFrozenWidth >= 0); - if (_frozenColumnScrollBarSpacer != null) - { - _frozenColumnScrollBarSpacer.Width = totalVisibleFrozenWidth; - } - Debug.Assert(_hScrollBar.Maximum >= 0); - - // width of the scrollable viewing area - double viewPortSize = Math.Max(0, cellsWidth - totalVisibleFrozenWidth); - _hScrollBar.ViewportSize = viewPortSize; - _hScrollBar.LargeChange = viewPortSize; - // The ScrollBar should be in sync with HorizontalOffset at this point. There's a resize case - // where the ScrollBar will coerce an old value here, but we don't want that - if (_hScrollBar.Value != _horizontalOffset) - { - _hScrollBar.Value = _horizontalOffset; - } - _hScrollBar.IsEnabled = true; - } - else - { - _hScrollBar.Maximum = 0; - _hScrollBar.ViewportSize = 0; - _hScrollBar.IsEnabled = false; - } - - if (!_hScrollBar.IsVisible) - { - // This will trigger a call to this method via Cells_SizeChanged for - _ignoreNextScrollBarsLayout = true; - // which no processing is needed. - _hScrollBar.IsVisible = true; - if (_hScrollBar.DesiredSize.Height == 0) - { - // We need to know the height for the rest of layout to work correctly so measure it now - _hScrollBar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - } - } - } - else - { - _hScrollBar.Maximum = 0; - if (_hScrollBar.IsVisible) - { - // This will trigger a call to this method via Cells_SizeChanged for - // which no processing is needed. - _hScrollBar.IsVisible = false; - _ignoreNextScrollBarsLayout = true; - } - } - } - } - - private void UpdateVerticalScrollBar(bool needVertScrollbar, bool forceVertScrollbar, double totalVisibleHeight, double cellsHeight) - { - if (_vScrollBar != null) - { - if (needVertScrollbar || forceVertScrollbar) - { - // viewportSize - // v---v - //|<|_____|###|>| - // ^ ^ - // min max - - // we want to make the relative size of the thumb reflect the relative size of the viewing area - // viewportSize / (max + viewportSize) = cellsWidth / max - // -> viewportSize = max * cellsHeight / (totalVisibleHeight - cellsHeight) - // -> = max * cellsHeight / (totalVisibleHeight - cellsHeight) - // -> = max * cellsHeight / max - // -> = cellsHeight - - // always zero - _vScrollBar.Minimum = 0; - if (needVertScrollbar && !double.IsInfinity(cellsHeight)) - { - // maximum travel distance -- not the total height - _vScrollBar.Maximum = totalVisibleHeight - cellsHeight; - Debug.Assert(_vScrollBar.Maximum >= 0); - - // total height of the display area - _vScrollBar.ViewportSize = cellsHeight; - _vScrollBar.IsEnabled = true; - } - else - { - _vScrollBar.Maximum = 0; - _vScrollBar.ViewportSize = 0; - _vScrollBar.IsEnabled = false; - } - - if (!_vScrollBar.IsVisible) - { - // This will trigger a call to this method via Cells_SizeChanged for - // which no processing is needed. - _vScrollBar.IsVisible = true; - if (_vScrollBar.DesiredSize.Width == 0) - { - // We need to know the width for the rest of layout to work correctly so measure it now - _vScrollBar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - } - _ignoreNextScrollBarsLayout = true; - } - } - else - { - _vScrollBar.Maximum = 0; - if (_vScrollBar.IsVisible) - { - // This will trigger a call to this method via Cells_SizeChanged for - // which no processing is needed. - _vScrollBar.IsVisible = false; - _ignoreNextScrollBarsLayout = true; - } - } - } - } - - private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e) - { - ProcessVerticalScroll(e.ScrollEventType); - VerticalScroll?.Invoke(sender, e); - } - - //TODO: Ensure right button is checked for - private bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl) - { - Debug.Assert(slot >= 0); - - if (shift || ctrl) - { - return true; - } - if (IsSlotOutOfBounds(slot)) - { - return true; - } - if (GetRowSelection(slot)) - { - return true; - } - // Unselect everything except the row that was clicked on - _noSelectionChangeCount++; - try - { - UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false); - } - finally - { - NoSelectionChangeCount--; - } - return true; - } - - //TODO: Ensure left button is checked for - private bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl) - { - bool beginEdit; - - Debug.Assert(slot >= 0); - - // Before changing selection, check if the current cell needs to be committed, and - // check if the current row needs to be committed. If any of those two operations are required and fail, - // do not change selection, and do not change current cell. - - bool wasInEdit = EditingColumnIndex != -1; - - if (IsSlotOutOfBounds(slot)) - { - return true; - } - - if (wasInEdit && (columnIndex != EditingColumnIndex || slot != CurrentSlot) && - WaitForLostFocus(() => UpdateStateOnMouseLeftButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl))) - { - return true; - } - - try - { - _noSelectionChangeCount++; - - beginEdit = allowEdit && - CurrentSlot == slot && - columnIndex != -1 && - (wasInEdit || CurrentColumnIndex == columnIndex) && - !GetColumnEffectiveReadOnlyState(ColumnsItemsInternal[columnIndex]); - - DataGridSelectionAction action; - if (SelectionMode == DataGridSelectionMode.Extended && shift) - { - // Shift select multiple rows - action = DataGridSelectionAction.SelectFromAnchorToCurrent; - } - else if (GetRowSelection(slot)) // Unselecting single row or Selecting a previously multi-selected row - { - if (!ctrl && SelectionMode == DataGridSelectionMode.Extended && _selectedItems.Count != 0) - { - // Unselect everything except the row that was clicked on - action = DataGridSelectionAction.SelectCurrent; - } - else if (ctrl && EditingRow == null) - { - action = DataGridSelectionAction.RemoveCurrentFromSelection; - } - else - { - action = DataGridSelectionAction.None; - } - } - else // Selecting a single row or multi-selecting with Ctrl - { - if (SelectionMode == DataGridSelectionMode.Single || !ctrl) - { - // Unselect the currently selected rows except the new selected row - action = DataGridSelectionAction.SelectCurrent; - } - else - { - action = DataGridSelectionAction.AddCurrentToSelection; - } - } - - UpdateSelectionAndCurrency(columnIndex, slot, action, scrollIntoView: false); - } - finally - { - NoSelectionChangeCount--; - } - - if (_successfullyUpdatedSelection && beginEdit && BeginCellEdit(pointerPressedEventArgs)) - { - FocusEditingCell(setFocus: true); - } - - return true; - } - - /// - /// Returns the Group at the indicated level or null if the item is not in the ItemsSource - /// - /// item - /// groupLevel - /// The group the given item falls under or null if the item is not in the ItemsSource - public DataGridCollectionViewGroup GetGroupFromItem(object item, int groupLevel) - { - int itemIndex = DataConnection.IndexOf(item); - if (itemIndex == -1) - { - return null; - } - int groupHeaderSlot = RowGroupHeadersTable.GetPreviousIndex(SlotFromRowIndex(itemIndex)); - DataGridRowGroupInfo rowGroupInfo = RowGroupHeadersTable.GetValueAt(groupHeaderSlot); - while (rowGroupInfo != null && rowGroupInfo.Level != groupLevel) - { - groupHeaderSlot = RowGroupHeadersTable.GetPreviousIndex(rowGroupInfo.Slot); - rowGroupInfo = RowGroupHeadersTable.GetValueAt(groupHeaderSlot); - } - return rowGroupInfo?.CollectionViewGroup; - } - - /// - /// Raises the LoadingRowGroup event - /// - /// EventArgs - protected virtual void OnLoadingRowGroup(DataGridRowGroupHeaderEventArgs e) - { - EventHandler handler = LoadingRowGroup; - if (handler != null) - { - LoadingOrUnloadingRow = true; - handler(this, e); - LoadingOrUnloadingRow = false; - } - } - - /// - /// Raises the UnLoadingRowGroup event - /// - /// EventArgs - protected virtual void OnUnloadingRowGroup(DataGridRowGroupHeaderEventArgs e) - { - EventHandler handler = UnloadingRowGroup; - if (handler != null) - { - LoadingOrUnloadingRow = true; - handler(this, e); - LoadingOrUnloadingRow = false; - } - } - - /// - /// Occurs before a DataGridRowGroupHeader header is used. - /// - public event EventHandler LoadingRowGroup; - - /// - /// Occurs when the DataGridRowGroupHeader is available for reuse. - /// - public event EventHandler UnloadingRowGroup; - - // Recursively expands parent RowGroupHeaders from the top down - private void ExpandRowGroupParentChain(int level, int slot) - { - if (level < 0) - { - return; - } - int previousHeaderSlot = RowGroupHeadersTable.GetPreviousIndex(slot + 1); - DataGridRowGroupInfo rowGroupInfo = null; - while (previousHeaderSlot >= 0) - { - rowGroupInfo = RowGroupHeadersTable.GetValueAt(previousHeaderSlot); - Debug.Assert(rowGroupInfo != null); - if (level == rowGroupInfo.Level) - { - if (_collapsedSlotsTable.Contains(rowGroupInfo.Slot)) - { - // Keep going up the chain - ExpandRowGroupParentChain(level - 1, rowGroupInfo.Slot - 1); - } - if (!rowGroupInfo.IsVisible) - { - EnsureRowGroupVisibility(rowGroupInfo, true, false); - } - return; - } - else - { - previousHeaderSlot = RowGroupHeadersTable.GetPreviousIndex(previousHeaderSlot); - } - } - } - - /// - /// This event is raised by OnCopyingRowClipboardContent method after the default row content is prepared. - /// Event listeners can modify or add to the row clipboard content. - /// - public event EventHandler CopyingRowClipboardContent; - - /// - /// This method raises the CopyingRowClipboardContent event. - /// - /// Contains the necessary information for generating the row clipboard content. - protected virtual void OnCopyingRowClipboardContent(DataGridRowClipboardEventArgs e) - { - CopyingRowClipboardContent?.Invoke(this, e); - } - - /// - /// This method formats a row (specified by a DataGridRowClipboardEventArgs) into - /// a single string to be added to the Clipboard when the DataGrid is copying its contents. - /// - /// DataGridRowClipboardEventArgs - /// The formatted string. - private string FormatClipboardContent(DataGridRowClipboardEventArgs e) - { - var text = StringBuilderCache.Acquire(); - var clipboardRowContent = e.ClipboardRowContent; - var numberOfItem = clipboardRowContent.Count; - for (int cellIndex = 0; cellIndex < numberOfItem; cellIndex++) - { - var cellContent = clipboardRowContent[cellIndex].Content?.ToString(); - cellContent = cellContent?.Replace("\"", "\"\""); - text.Append($"\"{cellContent}\""); - if (cellIndex < numberOfItem - 1) - { - text.Append('\t'); - } - else - { - text.Append('\r'); - text.Append('\n'); - } - } - return StringBuilderCache.GetStringAndRelease(text); - } - - /// - /// Handles the case where a 'Copy' key ('C' or 'Insert') has been pressed. If pressed in combination with - /// the control key, and the necessary prerequisites are met, the DataGrid will copy its contents - /// to the Clipboard as text. - /// - /// Whether or not the DataGrid handled the key press. - private bool ProcessCopyKey(KeyModifiers modifiers) - { - KeyboardHelper.GetMetaKeyState(this, modifiers, out bool ctrl, out bool shift, out bool alt); - - if (ctrl && !shift && !alt && ClipboardCopyMode != DataGridClipboardCopyMode.None && SelectedItems.Count > 0) - { - var textBuilder = StringBuilderCache.Acquire(); - - if (ClipboardCopyMode == DataGridClipboardCopyMode.IncludeHeader) - { - DataGridRowClipboardEventArgs headerArgs = new DataGridRowClipboardEventArgs(null, true); - foreach (DataGridColumn column in ColumnsInternal.GetVisibleColumns()) - { - headerArgs.ClipboardRowContent.Add(new DataGridClipboardCellContent(null, column, column.Header)); - } - OnCopyingRowClipboardContent(headerArgs); - textBuilder.Append(FormatClipboardContent(headerArgs)); - } - - for (int index = 0; index < SelectedItems.Count; index++) - { - object item = SelectedItems[index]; - DataGridRowClipboardEventArgs itemArgs = new DataGridRowClipboardEventArgs(item, false); - foreach (DataGridColumn column in ColumnsInternal.GetVisibleColumns()) - { - object content = column.GetCellValue(item, column.ClipboardContentBinding); - itemArgs.ClipboardRowContent.Add(new DataGridClipboardCellContent(item, column, content)); - } - OnCopyingRowClipboardContent(itemArgs); - textBuilder.Append(FormatClipboardContent(itemArgs)); - } - - string text = StringBuilderCache.GetStringAndRelease(textBuilder); - - if (!string.IsNullOrEmpty(text)) - { - CopyToClipboard(text); - return true; - } - } - return false; - } - - private async void CopyToClipboard(string text) - { - var clipboard = TopLevel.GetTopLevel(this)?.Clipboard; - - if (clipboard != null) - await clipboard.SetTextAsync(text); - } - - /// - /// This is an empty content control that's used during the DataGrid's copy procedure - /// to determine the value of a ClipboardContentBinding for a particular column and item. - /// - internal ContentControl ClipboardContentControl - { - get - { - if (_clipboardContentControl == null) - { - _clipboardContentControl = new ContentControl(); - } - return _clipboardContentControl; - } - } - - //TODO Validation UI - private void ResetValidationStatus() - { - // Clear the invalid status of the Cell, Row and DataGrid - if (EditingRow != null) - { - EditingRow.IsValid = true; - if (EditingRow.Index != -1) - { - foreach (DataGridCell cell in EditingRow.Cells) - { - if (!cell.IsValid) - { - cell.IsValid = true; - cell.UpdatePseudoClasses(); - } - } - EditingRow.ApplyState(); - } - } - IsValid = true; - - _validationSubscription?.Dispose(); - _validationSubscription = null; - } - - /// - /// Raises the AutoGeneratingColumn event. - /// - protected virtual void OnAutoGeneratingColumn(DataGridAutoGeneratingColumnEventArgs e) - { - AutoGeneratingColumn?.Invoke(this, e); - } - } -} diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs deleted file mode 100644 index 61a1eb2bf0..0000000000 --- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs +++ /dev/null @@ -1,153 +0,0 @@ -// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using Avalonia.Data; -using System; -using Avalonia.Controls.Utils; -using Avalonia.Markup.Xaml.MarkupExtensions; -using Avalonia.Metadata; -using Avalonia.Reactive; - -namespace Avalonia.Controls -{ - /// - /// Represents a column that can - /// bind to a property in the grid's data source. - /// - public abstract class DataGridBoundColumn : DataGridColumn - { - private IBinding _binding; - - /// - /// Gets or sets the binding that associates the column with a property in the data source. - /// - //TODO Binding - [AssignBinding] - [InheritDataTypeFromItems(nameof(DataGrid.ItemsSource), AncestorType = typeof(DataGrid))] - public virtual IBinding Binding - { - get - { - return _binding; - } - set - { - if (_binding != value) - { - if (OwningGrid != null && !OwningGrid.CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true)) - { - // Edited value couldn't be committed, so we force a CancelEdit - OwningGrid.CancelEdit(DataGridEditingUnit.Row, raiseEvents: false); - } - - _binding = value; - - if (_binding != null) - { - if(_binding is BindingBase binding) - { - if (binding.Mode == BindingMode.OneWayToSource) - { - throw new InvalidOperationException("DataGridColumn doesn't support BindingMode.OneWayToSource. Use BindingMode.TwoWay instead."); - } - - var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString(); - if (!string.IsNullOrEmpty(path) && binding.Mode == BindingMode.Default) - { - binding.Mode = BindingMode.TwoWay; - } - - if (binding.Converter == null && string.IsNullOrEmpty(binding.StringFormat)) - { - binding.Converter = DataGridValueConverter.Instance; - } - } - - // Apply the new Binding to existing rows in the DataGrid - if (OwningGrid != null) - { - OwningGrid.OnColumnBindingChanged(this); - } - } - - RemoveEditingElement(); - } - } - } - - /// - /// The binding that will be used to get or set cell content for the clipboard. - /// If the base ClipboardContentBinding is not explicitly set, this will return the value of Binding. - /// - public override IBinding ClipboardContentBinding - { - get - { - return base.ClipboardContentBinding ?? Binding; - } - set - { - base.ClipboardContentBinding = value; - } - } - - //TODO Rename - //TODO Validation - protected sealed override Control GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding editBinding) - { - Control element = GenerateEditingElementDirect(cell, dataItem); - editBinding = null; - - if (Binding != null) - { - editBinding = BindEditingElement(element, BindingTarget, Binding); - } - - return element; - } - - private static ICellEditBinding BindEditingElement(AvaloniaObject target, AvaloniaProperty property, IBinding binding) - { - var result = binding.Initiate(target, property, enableDataValidation: true); - - if (result != null) - { - if(result.Source is IAvaloniaSubject subject) - { - var bindingHelper = new CellEditBinding(subject); - var instanceBinding = new InstancedBinding(bindingHelper.InternalSubject, result.Mode, result.Priority); - - BindingOperations.Apply(target, property, instanceBinding, null); - return bindingHelper; - } - - BindingOperations.Apply(target, property, result, null); - } - - return null; - } - - protected abstract Control GenerateEditingElementDirect(DataGridCell cell, object dataItem); - - protected AvaloniaProperty BindingTarget { get; set; } - - internal void SetHeaderFromBinding() - { - if (OwningGrid != null && OwningGrid.DataConnection.DataType != null - && Header == null && Binding != null && Binding is BindingBase binding) - { - var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString(); - if (!string.IsNullOrWhiteSpace(path)) - { - var header = OwningGrid.DataConnection.DataType.GetDisplayName(path); - if (header != null) - { - Header = header; - } - } - } - } - } -} diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs deleted file mode 100644 index 3518738485..0000000000 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ /dev/null @@ -1,274 +0,0 @@ -// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using Avalonia.Automation; -using Avalonia.Automation.Peers; -using Avalonia.Controls.Automation.Peers; -using Avalonia.Controls.Metadata; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Shapes; -using Avalonia.Input; - -namespace Avalonia.Controls -{ - /// - /// Represents an individual cell. - /// - [TemplatePart(DATAGRIDCELL_elementRightGridLine, typeof(Rectangle))] - [PseudoClasses(":selected", ":current", ":edited", ":invalid", ":focus")] - public class DataGridCell : ContentControl - { - private const string DATAGRIDCELL_elementRightGridLine = "PART_RightGridLine"; - - private Rectangle _rightGridLine; - private DataGridColumn _owningColumn; - - bool _isValid = true; - - public static readonly DirectProperty IsValidProperty = - AvaloniaProperty.RegisterDirect( - nameof(IsValid), - o => o.IsValid); - - static DataGridCell() - { - PointerPressedEvent.AddClassHandler( - (x,e) => x.DataGridCell_PointerPressed(e), handledEventsToo: true); - FocusableProperty.OverrideDefaultValue(true); - IsTabStopProperty.OverrideDefaultValue(false); - AutomationProperties.IsOffscreenBehaviorProperty.OverrideDefaultValue(IsOffscreenBehavior.FromClip); - } - public DataGridCell() - { } - - public bool IsValid - { - get { return _isValid; } - internal set { SetAndRaise(IsValidProperty, ref _isValid, value); } - } - - internal DataGridColumn OwningColumn - { - get => _owningColumn; - set - { - if (_owningColumn != value) - { - _owningColumn = value; - OnOwningColumnSet(value); - } - } - } - internal DataGridRow OwningRow - { - get; - set; - } - - internal DataGrid OwningGrid - { - get { return OwningRow?.OwningGrid ?? OwningColumn?.OwningGrid; } - } - - internal double ActualRightGridLineWidth - { - get { return _rightGridLine?.Bounds.Width ?? 0; } - } - - internal int ColumnIndex - { - get { return OwningColumn?.Index ?? -1; } - } - - internal int RowIndex - { - get { return OwningRow?.Index ?? -1; } - } - - internal bool IsCurrent - { - get - { - return OwningGrid.CurrentColumnIndex == OwningColumn.Index && - OwningGrid.CurrentSlot == OwningRow.Slot; - } - } - - private bool IsEdited - { - get - { - return OwningGrid.EditingRow == OwningRow && - OwningGrid.EditingColumnIndex == ColumnIndex; - } - } - - private bool IsMouseOver - { - get - { - return OwningRow != null && OwningRow.MouseOverColumnIndex == ColumnIndex; - } - set - { - if (value != IsMouseOver) - { - if (value) - { - OwningRow.MouseOverColumnIndex = ColumnIndex; - } - else - { - OwningRow.MouseOverColumnIndex = null; - } - } - } - } - - protected override AutomationPeer OnCreateAutomationPeer() - { - return new DataGridCellAutomationPeer(this); - } - - /// - /// Builds the visual tree for the cell control when a new template is applied. - /// - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - UpdatePseudoClasses(); - _rightGridLine = e.NameScope.Find(DATAGRIDCELL_elementRightGridLine); - if (_rightGridLine != null && OwningColumn == null) - { - // Turn off the right GridLine for filler cells - _rightGridLine.IsVisible = false; - } - else - { - EnsureGridLine(null); - } - - } - protected override void OnPointerEntered(PointerEventArgs e) - { - base.OnPointerEntered(e); - - if (OwningRow != null) - { - IsMouseOver = true; - } - } - protected override void OnPointerExited(PointerEventArgs e) - { - base.OnPointerExited(e); - - if (OwningRow != null) - { - IsMouseOver = false; - } - } - - //TODO TabStop - private void DataGridCell_PointerPressed(PointerPressedEventArgs e) - { - // OwningGrid is null for TopLeftHeaderCell and TopRightHeaderCell because they have no OwningRow - if (OwningGrid == null) - { - return; - } - OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e)); - if (e.Handled) - { - return; - } - if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) - { - if (OwningGrid.IsTabStop) - { - OwningGrid.Focus(); - } - if (OwningRow != null) - { - var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled); - - // Do not handle PointerPressed with touch or pen, - // so we can start scroll gesture on the same event. - if (e.Pointer.Type != PointerType.Touch && e.Pointer.Type != PointerType.Pen) - { - e.Handled = handled; - } - } - } - else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed) - { - if (OwningGrid.IsTabStop) - { - OwningGrid.Focus(); - } - if (OwningRow != null) - { - e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled); - } - } - } - - internal void UpdatePseudoClasses() - { - if (OwningGrid == null || OwningColumn == null || OwningRow == null || !OwningRow.IsVisible || OwningRow.Slot == -1) - { - return; - } - - PseudoClasses.Set(":selected", OwningRow.IsSelected); - - PseudoClasses.Set(":current", IsCurrent); - - PseudoClasses.Set(":edited", IsEdited); - - PseudoClasses.Set(":invalid", !IsValid); - - PseudoClasses.Set(":focus", OwningGrid.IsFocused && IsCurrent); - } - - // Makes sure the right gridline has the proper stroke and visibility. If lastVisibleColumn is specified, the - // right gridline will be collapsed if this cell belongs to the lastVisibleColumn and there is no filler column - internal void EnsureGridLine(DataGridColumn lastVisibleColumn) - { - if (OwningGrid != null && _rightGridLine != null) - { - if (OwningGrid.VerticalGridLinesBrush != null && OwningGrid.VerticalGridLinesBrush != _rightGridLine.Fill) - { - _rightGridLine.Fill = OwningGrid.VerticalGridLinesBrush; - } - - bool newVisibility = - (OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.Vertical || OwningGrid.GridLinesVisibility == DataGridGridLinesVisibility.All) - && (OwningGrid.ColumnsInternal.FillerColumn.IsActive || OwningColumn != lastVisibleColumn); - - if (newVisibility != _rightGridLine.IsVisible) - { - _rightGridLine.IsVisible = newVisibility; - } - } - } - - private void OnOwningColumnSet(DataGridColumn column) - { - if (column == null) - { - Classes.Clear(); - ClearValue(ThemeProperty); - } - else - { - if (Theme != column.CellTheme) - { - Theme = column.CellTheme; - } - - Classes.Replace(column.CellStyleClasses); - } - } - } -} diff --git a/src/Avalonia.Controls.DataGrid/DataGridCellCollection.cs b/src/Avalonia.Controls.DataGrid/DataGridCellCollection.cs deleted file mode 100644 index a4dff8a2d8..0000000000 --- a/src/Avalonia.Controls.DataGrid/DataGridCellCollection.cs +++ /dev/null @@ -1,71 +0,0 @@ -// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Avalonia.Controls -{ - internal class DataGridCellCollection - { - private List _cells; - private DataGridRow _owningRow; - - internal event EventHandler CellAdded; - internal event EventHandler CellRemoved; - - public DataGridCellCollection(DataGridRow owningRow) - { - _owningRow = owningRow; - _cells = new List(); - } - - public int Count - { - get - { - return _cells.Count; - } - } - - public IEnumerator GetEnumerator() - { - return _cells.GetEnumerator(); - } - - public void Insert(int cellIndex, DataGridCell cell) - { - Debug.Assert(cellIndex >= 0 && cellIndex <= _cells.Count); - Debug.Assert(cell != null); - - cell.OwningRow = _owningRow; - _cells.Insert(cellIndex, cell); - - CellAdded?.Invoke(this, new DataGridCellEventArgs(cell)); - } - - public void RemoveAt(int cellIndex) - { - DataGridCell dataGridCell = _cells[cellIndex]; - _cells.RemoveAt(cellIndex); - dataGridCell.OwningRow = null; - CellRemoved?.Invoke(this, new DataGridCellEventArgs(dataGridCell)); - } - - public DataGridCell this[int index] - { - get - { - if (index < 0 || index >= _cells.Count) - { - throw DataGridError.DataGrid.ValueMustBeBetween("index", "Index", 0, true, _cells.Count, false); - } - return _cells[index]; - } - } - } -} diff --git a/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs b/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs deleted file mode 100644 index 26f0b53952..0000000000 --- a/src/Avalonia.Controls.DataGrid/DataGridCellCoordinates.cs +++ /dev/null @@ -1,57 +0,0 @@ -// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using System.Globalization; - -namespace Avalonia.Controls -{ - internal class DataGridCellCoordinates - { - public DataGridCellCoordinates(int columnIndex, int slot) - { - ColumnIndex = columnIndex; - Slot = slot; - } - - public DataGridCellCoordinates(DataGridCellCoordinates dataGridCellCoordinates) : this(dataGridCellCoordinates.ColumnIndex, dataGridCellCoordinates.Slot) - { - } - - public int ColumnIndex - { - get; - set; - } - - public int Slot - { - get; - set; - } - - public override bool Equals(object o) - { - if (o is DataGridCellCoordinates dataGridCellCoordinates) - { - return dataGridCellCoordinates.ColumnIndex == ColumnIndex && dataGridCellCoordinates.Slot == Slot; - } - return false; - } - - // There is build warning if this is missing - public override int GetHashCode() - { - return base.GetHashCode(); - } - -#if DEBUG - public override string ToString() - { - return "DataGridCellCoordinates {ColumnIndex = " + ColumnIndex.ToString(CultureInfo.CurrentCulture) + - ", Slot = " + Slot.ToString(CultureInfo.CurrentCulture) + "}"; - } -#endif - } -} diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs deleted file mode 100644 index b2deb1c26e..0000000000 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ /dev/null @@ -1,334 +0,0 @@ -// (c) Copyright Microsoft Corporation. -// This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. -// All other rights reserved. - -using Avalonia.Input; -using Avalonia.Interactivity; -using Avalonia.Layout; -using System; -using System.Collections.Specialized; - -namespace Avalonia.Controls -{ - /// - /// Represents a
- /// Clear a sort criteria by assigning SortDescription.Empty to this property. - /// One or more sort criteria in form of - /// can be used, each specifying a property and direction to sort by. - ///