diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..9a0da4aa9b
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,17 @@
+This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible:
+
+- What does the pull request do?
+- What is the current behavior?
+- What is the updated/expected behavior with this PR?
+- How was the solution implemented (if it's not obvious)?
+
+Checklist:
+
+- [ ] Added unit tests (if possible)?
+- [ ] Added XML documentation to any related classes?
+- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation
+
+If the pull request fixes issue(s) list them like this:
+
+Fixes #123
+Fixes #456
\ No newline at end of file
diff --git a/Avalonia.sln b/Avalonia.sln
index 7cf2cf3b8a..88914fe188 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2024
+VisualStudioVersion = 15.0.27130.2027
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@@ -11,7 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Layout", "src\Aval
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windows", "Windows", "{B39A8919-9F95-48FE-AD7B-76E08B509888}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32", "src\Windows\Avalonia.Win32\Avalonia.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32", "src\Windows\Avalonia.Win32\Avalonia.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Direct2D1", "src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj", "{3E908F67-5543-4879-A1DC-08EACE79B3CD}"
EndProject
@@ -126,10 +126,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\Rende
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}"
EndProject
-Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Avalonia.Win32.Shared", "src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.shproj", "{9DEFC6B7-845B-4D8F-AFC0-D32BF0032B8C}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.NetStandard", "src\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj", "{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetCoreRuntime", "src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj", "{7863EA94-F0FB-4386-BF8C-E5BFA761560A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia", "src\Skia\Avalonia.Skia\Avalonia.Skia.csproj", "{7D2D3083-71DD-4CC9-8907-39A0D86FB322}"
@@ -196,14 +192,11 @@ Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4
- src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{40759a76-d0f2-464e-8000-6ff0f5c4bd7c}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7863ea94-f0fb-4386-bf8c-e5bfa761560a}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4
- src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4
- src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{9defc6b7-845b-4d8f-afc0-d32bf0032b8c}*SharedItemsImports = 13
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{dabfd304-d6a4-4752-8123-c2ccf7ac7831}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
EndGlobalSection
@@ -369,6 +362,7 @@ Global
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
+ {811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|x86.ActiveCfg = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|x86.Build.0 = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -378,6 +372,7 @@ Global
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
+ {811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|x86.ActiveCfg = Release|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|x86.Build.0 = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
@@ -1994,46 +1989,6 @@ Global
{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.ActiveCfg = Release|Any CPU
{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Build.0 = Release|Any CPU
{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Deploy.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|NetCoreOnly.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|NetCoreOnly.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|x86.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|Any CPU.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhone.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhone.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|NetCoreOnly.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|NetCoreOnly.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|x86.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|x86.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhone.Build.0 = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|x86.ActiveCfg = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|x86.Build.0 = Debug|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|Any CPU.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhone.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhone.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|NetCoreOnly.Build.0 = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|x86.ActiveCfg = Release|Any CPU
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|x86.Build.0 = Release|Any CPU
{7863EA94-F0FB-4386-BF8C-E5BFA761560A}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{7863EA94-F0FB-4386-BF8C-E5BFA761560A}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{7863EA94-F0FB-4386-BF8C-E5BFA761560A}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@@ -2623,8 +2578,6 @@ Global
{C7A69145-60B6-4882-97D6-A3921DD43978} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
- {9DEFC6B7-845B-4D8F-AFC0-D32BF0032B8C} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
- {40759A76-D0F2-464E-8000-6FF0F5C4BD7C} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3}
{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
diff --git a/build/Rx.props b/build/Rx.props
index 323026f5e2..7078e31195 100644
--- a/build/Rx.props
+++ b/build/Rx.props
@@ -5,6 +5,5 @@
-
diff --git a/build/System.Drawing.Common.props b/build/System.Drawing.Common.props
new file mode 100644
index 0000000000..a568152bbd
--- /dev/null
+++ b/build/System.Drawing.Common.props
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/packages.cake b/packages.cake
index bc290fce22..17411aef4c 100644
--- a/packages.cake
+++ b/packages.cake
@@ -370,14 +370,13 @@ public class Packages
new NuGetPackSettings()
{
Id = "Avalonia.Win32",
- Dependencies = new []
+ Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
- },
+ }.Deps(new string[]{null}, "System.Drawing.Common"),
Files = new []
{
- new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/Avalonia.Win32.dll", Target = "lib/net45" },
- new NuSpecContent { Source = "Avalonia.Win32.NetStandard/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }
+ new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Windows"),
OutputDirectory = parameters.NugetRoot
diff --git a/parameters.cake b/parameters.cake
index c727b3107f..e224cce151 100644
--- a/parameters.cake
+++ b/parameters.cake
@@ -97,7 +97,7 @@ public class Parameters
else
{
// Use AssemblyVersion with Build as version
- Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-alpha";
+ Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta";
}
}
diff --git a/readme.md b/readme.md
index 906c3a4b5c..2b26cbdd1a 100644
--- a/readme.md
+++ b/readme.md
@@ -2,15 +2,15 @@
# Avalonia
-| Gitter Chat | Windows Build Status | Linux/Mac Build Status |
-|---|---|---|
-| [](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [](https://travis-ci.org/AvaloniaUI/Avalonia) |
+| Gitter Chat | Windows Build Status | Linux/Mac Build Status | Open Collective |
+|---|---|---|---|
+| [](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [](https://travis-ci.org/AvaloniaUI/Avalonia) | [](#backers) [](#sponsors) |
## About
Avalonia is a WPF-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Linux (GTK), MacOS, Android and iOS.
-Avalonia is now in alpha. This means that framework is now at a stage where you can have a play and hopefully create simple applications. There's still a lot missing, and you *will* find bugs, and the API *will* change, but this represents the first time where we've made it somewhat easy to have a play and experiment with the framework.
+**Avalonia is currently in beta** which means that the framework is generally usable for writing applications, but there may be some bugs and breaking changes as we continue development.
| Control catalog | Desktop platforms | Mobile platforms |
|---|---|---|
@@ -35,16 +35,46 @@ https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts
## Documentation
-As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/tutorial/gettingstarted) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
+As mentioned above, Avalonia is still in beta and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
-There's also a high-level [architecture document](http://avaloniaui.net/spec/architecture) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.
+There's also a high-level [architecture document](http://avaloniaui.net/architecture/project-structure) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.
Contributions are always welcome!
## Building and Using
-See the [build instructions here](http://avaloniaui.net/guidelines/build).
+See the [build instructions here](http://avaloniaui.net/contributing/build).
## Contributing
-Please read the [contribution guidelines](http://avaloniaui.net/guidelines/contributing) before submitting a pull request.
+Please read the [contribution guidelines](http://avaloniaui.net/contributing/contributing) before submitting a pull request.
+
+### Contributors
+
+This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)].
+
+
+
+### Backers
+
+Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)]
+
+
+
+
+### Sponsors
+
+Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/Avalonia#sponsor)]
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/BindingTest/MainWindow.xaml b/samples/BindingTest/MainWindow.xaml
index b0a4a5b7ed..4eb45e07c5 100644
--- a/samples/BindingTest/MainWindow.xaml
+++ b/samples/BindingTest/MainWindow.xaml
@@ -1,4 +1,5 @@
@@ -6,6 +7,9 @@
+
+
+
@@ -40,6 +44,10 @@
+
+
diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs
index b151cabf43..a2048005a4 100644
--- a/samples/ControlCatalog.Desktop/Program.cs
+++ b/samples/ControlCatalog.Desktop/Program.cs
@@ -10,6 +10,7 @@ namespace ControlCatalog
{
internal class Program
{
+ [STAThread]
static void Main(string[] args)
{
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args
diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs
index 346535d39d..b45a93455e 100644
--- a/samples/ControlCatalog.NetCore/Program.cs
+++ b/samples/ControlCatalog.NetCore/Program.cs
@@ -9,8 +9,10 @@ namespace ControlCatalog.NetCore
{
static class Program
{
+
static void Main(string[] args)
{
+ Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA);
if (args.Contains("--wait-for-attach"))
{
Console.WriteLine("Attach debugger and use 'Set next statement'");
diff --git a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
index 77f38d3bd7..c1c5cdcaf7 100644
--- a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
+++ b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
@@ -170,15 +170,14 @@
Avalonia.Themes.Default
- {D2D3083-71DD-4CC9-8907-39A0D86FB322}
+ {7d2d3083-71dd-4cc9-8907-39a0d86fb322}
Avalonia.Skia
- false
- false
{d0a739b9-3c68-4ba6-a328-41606954b6bd}
ControlCatalog
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 0b4463ddb7..b8a8479a49 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -1,177 +1,17 @@
- netstandard2.0
- False
- false
+ netstandard2.0
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
- Designer
-
-
-
-
- App.xaml
-
-
- MainView.xaml
-
-
- DecoratedWindow.xaml
-
-
- MainWindow.xaml
-
-
- DialogsPage.xaml
-
-
- BorderPage.xaml
-
-
- ButtonPage.xaml
-
-
- CalendarPage.xaml
-
-
- CanvasPage.xaml
-
-
- CarouselPage.xaml
-
-
- ContextMenuPage.xaml
-
-
- CheckBoxPage.xaml
-
-
- DropDownPage.xaml
-
-
- ExpanderPage.xaml
-
-
- ImagePage.xaml
-
-
- LayoutTransformControlPage.xaml
+
+ %(Filename)
-
- MenuPage.xaml
-
-
- ProgressBarPage.xaml
-
-
- RadioButtonPage.xaml
-
-
- SliderPage.xaml
-
-
- TreeViewPage.xaml
-
-
- TextBoxPage.xaml
-
-
- ToolTipPage.xaml
-
-
-
-
-
-
-
-
-
-
-
-
+
Designer
+
+
@@ -188,20 +28,6 @@
-
-
-
-
-
- Designer
-
-
-
-
- MSBuild:Compile
-
-
-
-
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index e15dd7d69e..377871f658 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -5,18 +5,23 @@
+
+
+
+
+
@@ -24,4 +29,4 @@
-
\ No newline at end of file
+
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
new file mode 100644
index 0000000000..943fadf100
--- /dev/null
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
@@ -0,0 +1,59 @@
+
+
+ AutoCompleteBox
+ A control into which the user can input text
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
new file mode 100644
index 0000000000..6f3b8361cd
--- /dev/null
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
@@ -0,0 +1,143 @@
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
+using Avalonia.Markup;
+using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.Data;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ControlCatalog.Pages
+{
+ public class AutoCompleteBoxPage : UserControl
+ {
+ public class StateData
+ {
+ public string Name { get; private set; }
+ public string Abbreviation { get; private set; }
+ public string Capital { get; private set; }
+
+ public StateData(string name, string abbreviatoin, string capital)
+ {
+ Name = name;
+ Abbreviation = abbreviatoin;
+ Capital = capital;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+
+ private StateData[] BuildAllStates()
+ {
+ return new StateData[]
+ {
+ new StateData("Alabama","AL","Montgomery"),
+ new StateData("Alaska","AK","Juneau"),
+ new StateData("Arizona","AZ","Phoenix"),
+ new StateData("Arkansas","AR","Little Rock"),
+ new StateData("California","CA","Sacramento"),
+ new StateData("Colorado","CO","Denver"),
+ new StateData("Connecticut","CT","Hartford"),
+ new StateData("Delaware","DE","Dover"),
+ new StateData("Florida","FL","Tallahassee"),
+ new StateData("Georgia","GA","Atlanta"),
+ new StateData("Hawaii","HI","Honolulu"),
+ new StateData("Idaho","ID","Boise"),
+ new StateData("Illinois","IL","Springfield"),
+ new StateData("Indiana","IN","Indianapolis"),
+ new StateData("Iowa","IA","Des Moines"),
+ new StateData("Kansas","KS","Topeka"),
+ new StateData("Kentucky","KY","Frankfort"),
+ new StateData("Louisiana","LA","Baton Rouge"),
+ new StateData("Maine","ME","Augusta"),
+ new StateData("Maryland","MD","Annapolis"),
+ new StateData("Massachusetts","MA","Boston"),
+ new StateData("Michigan","MI","Lansing"),
+ new StateData("Minnesota","MN","St. Paul"),
+ new StateData("Mississippi","MS","Jackson"),
+ new StateData("Missouri","MO","Jefferson City"),
+ new StateData("Montana","MT","Helena"),
+ new StateData("Nebraska","NE","Lincoln"),
+ new StateData("Nevada","NV","Carson City"),
+ new StateData("New Hampshire","NH","Concord"),
+ new StateData("New Jersey","NJ","Trenton"),
+ new StateData("New Mexico","NM","Santa Fe"),
+ new StateData("New York","NY","Albany"),
+ new StateData("North Carolina","NC","Raleigh"),
+ new StateData("North Dakota","ND","Bismarck"),
+ new StateData("Ohio","OH","Columbus"),
+ new StateData("Oklahoma","OK","Oklahoma City"),
+ new StateData("Oregon","OR","Salem"),
+ new StateData("Pennsylvania","PA","Harrisburg"),
+ new StateData("Rhode Island","RI","Providence"),
+ new StateData("South Carolina","SC","Columbia"),
+ new StateData("South Dakota","SD","Pierre"),
+ new StateData("Tennessee","TN","Nashville"),
+ new StateData("Texas","TX","Austin"),
+ new StateData("Utah","UT","Salt Lake City"),
+ new StateData("Vermont","VT","Montpelier"),
+ new StateData("Virginia","VA","Richmond"),
+ new StateData("Washington","WA","Olympia"),
+ new StateData("West Virginia","WV","Charleston"),
+ new StateData("Wisconsin","WI","Madison"),
+ new StateData("Wyoming","WY","Cheyenne"),
+ };
+ }
+ public StateData[] States { get; private set; }
+
+ public AutoCompleteBoxPage()
+ {
+ this.InitializeComponent();
+
+ States = BuildAllStates();
+
+ foreach (AutoCompleteBox box in GetAllAutoCompleteBox())
+ {
+ box.Items = States;
+ }
+
+ var converter = new FuncMultiValueConverter(parts =>
+ {
+ return String.Format("{0} ({1})", parts.ToArray());
+ });
+ var binding = new MultiBinding { Converter = converter };
+ binding.Bindings.Add(new Binding("Name"));
+ binding.Bindings.Add(new Binding("Abbreviation"));
+
+ var multibindingBox = this.FindControl("MultiBindingBox");
+ multibindingBox.ValueMemberBinding = binding;
+
+ var asyncBox = this.FindControl("AsyncBox");
+ asyncBox.AsyncPopulator = PopulateAsync;
+ }
+ private IEnumerable GetAllAutoCompleteBox()
+ {
+ return
+ this.GetLogicalDescendants()
+ .OfType();
+ }
+
+ private bool StringContains(string str, string query)
+ {
+ return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
+ }
+ private async Task> PopulateAsync(string searchText, CancellationToken cancellationToken)
+ {
+ await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken);
+
+ return
+ States.Where(data => StringContains(data.Name, searchText) || StringContains(data.Capital, searchText))
+ .ToList();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
new file mode 100644
index 0000000000..1797fb48bc
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
@@ -0,0 +1,24 @@
+
+
+
+ ButtonSpinner
+ The ButtonSpinner control allows you to add button spinners to any element and then respond to the Spin event to manipulate that element.
+
+
+ AllowSpin
+ ShowButtonSpinner
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
new file mode 100644
index 0000000000..1f753ab3ea
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
@@ -0,0 +1,54 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class ButtonSpinnerPage : UserControl
+ {
+ public ButtonSpinnerPage()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void OnSpin(object sender, SpinEventArgs e)
+ {
+ var spinner = (ButtonSpinner)sender;
+ var txtBox = (TextBlock)spinner.Content;
+
+ int value = Array.IndexOf(_mountains, txtBox.Text);
+ if (e.Direction == SpinDirection.Increase)
+ value++;
+ else
+ value--;
+
+ if (value < 0)
+ value = _mountains.Length - 1;
+ else if (value >= _mountains.Length)
+ value = 0;
+
+ txtBox.Text = _mountains[value];
+ }
+
+ private readonly string[] _mountains = new[]
+ {
+ "Everest",
+ "K2 (Mount Godwin Austen)",
+ "Kangchenjunga",
+ "Lhotse",
+ "Makalu",
+ "Cho Oyu",
+ "Dhaulagiri",
+ "Manaslu",
+ "Nanga Parbat",
+ "Annapurna"
+ };
+ }
+}
diff --git a/samples/ControlCatalog/Pages/DatePickerPage.xaml b/samples/ControlCatalog/Pages/DatePickerPage.xaml
new file mode 100644
index 0000000000..92cfa7e178
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DatePickerPage.xaml
@@ -0,0 +1,46 @@
+
+
+ DatePicker
+ A control for selecting dates with a calendar drop-down
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/DatePickerPage.xaml.cs b/samples/ControlCatalog/Pages/DatePickerPage.xaml.cs
new file mode 100644
index 0000000000..ef01887c9e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DatePickerPage.xaml.cs
@@ -0,0 +1,36 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using System;
+
+namespace ControlCatalog.Pages
+{
+ public class DatePickerPage : UserControl
+ {
+ public DatePickerPage()
+ {
+ InitializeComponent();
+
+ var dp1 = this.FindControl("DatePicker1");
+ var dp2 = this.FindControl("DatePicker2");
+ var dp3 = this.FindControl("DatePicker3");
+ var dp4 = this.FindControl("DatePicker4");
+ var dp5 = this.FindControl("DatePicker5");
+
+ dp1.SelectedDate = DateTime.Today;
+ dp2.SelectedDate = DateTime.Today.AddDays(10);
+ dp3.SelectedDate = DateTime.Today.AddDays(20);
+ dp5.SelectedDate = DateTime.Today;
+
+ dp4.TemplateApplied += (s, e) =>
+ {
+ dp4.BlackoutDates.AddDatesInPast();
+ };
+
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml b/samples/ControlCatalog/Pages/DragAndDropPage.xaml
new file mode 100644
index 0000000000..af679d2f9a
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml
@@ -0,0 +1,19 @@
+
+
+ Drag+Drop
+ Example of Drag+Drop capabilities
+
+
+
+ Drag Me
+
+
+ Drop some text or files here
+
+
+
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
new file mode 100644
index 0000000000..718f21314e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
@@ -0,0 +1,71 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Markup.Xaml;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace ControlCatalog.Pages
+{
+ public class DragAndDropPage : UserControl
+ {
+ private TextBlock _DropState;
+ private TextBlock _DragState;
+ private Border _DragMe;
+ private int DragCount = 0;
+
+ public DragAndDropPage()
+ {
+ this.InitializeComponent();
+
+ _DragMe.PointerPressed += DoDrag;
+
+ AddHandler(DragDrop.DropEvent, Drop);
+ AddHandler(DragDrop.DragOverEvent, DragOver);
+ }
+
+ private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
+ {
+ DataObject dragData = new DataObject();
+ dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
+
+ var result = await DragDrop.DoDragDrop(dragData, DragDropEffects.Copy);
+ switch(result)
+ {
+ case DragDropEffects.Copy:
+ _DragState.Text = "The text was copied"; break;
+ case DragDropEffects.Link:
+ _DragState.Text = "The text was linked"; break;
+ case DragDropEffects.None:
+ _DragState.Text = "The drag operation was canceled"; break;
+ }
+ }
+
+ private void DragOver(object sender, DragEventArgs e)
+ {
+ // Only allow Copy or Link as Drop Operations.
+ e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
+
+ // Only allow if the dragged data contains text or filenames.
+ if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames))
+ e.DragEffects = DragDropEffects.None;
+ }
+
+ private void Drop(object sender, DragEventArgs e)
+ {
+ if (e.Data.Contains(DataFormats.Text))
+ _DropState.Text = e.Data.GetText();
+ else if (e.Data.Contains(DataFormats.FileNames))
+ _DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+
+ _DropState = this.Find("DropState");
+ _DragState = this.Find("DragState");
+ _DragMe = this.Find("DragMe");
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
new file mode 100644
index 0000000000..a5c911f47d
--- /dev/null
+++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
@@ -0,0 +1,80 @@
+
+
+ Numeric up-down control
+ Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.
+
+ Features:
+
+
+ ShowButtonSpinner:
+
+
+ IsReadOnly:
+
+
+ AllowSpin:
+
+
+ ClipValueToMinMax:
+
+
+
+
+ FormatString:
+
+
+
+
+
+
+
+
+
+
+
+
+ ButtonSpinnerLocation:
+
+
+ CultureInfo:
+
+
+ Watermark:
+
+
+ Text:
+
+
+
+ Minimum:
+
+
+ Maximum:
+
+
+ Increment:
+
+
+ Value:
+
+
+
+
+
+
+ Usage of NumericUpDown:
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
new file mode 100644
index 0000000000..92da64d87e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Markup.Xaml;
+using ReactiveUI;
+
+namespace ControlCatalog.Pages
+{
+ public class NumericUpDownPage : UserControl
+ {
+ public NumericUpDownPage()
+ {
+ this.InitializeComponent();
+ var viewModel = new NumbersPageViewModel();
+ DataContext = viewModel;
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ }
+
+ public class NumbersPageViewModel : ReactiveObject
+ {
+ private IList _formats;
+ private FormatObject _selectedFormat;
+ private IList _spinnerLocations;
+
+ public NumbersPageViewModel()
+ {
+ SelectedFormat = Formats.FirstOrDefault();
+ }
+
+ public IList Formats
+ {
+ get
+ {
+ return _formats ?? (_formats = new List()
+ {
+ new FormatObject() {Name = "Currency", Value = "C2"},
+ new FormatObject() {Name = "Fixed point", Value = "F2"},
+ new FormatObject() {Name = "General", Value = "G"},
+ new FormatObject() {Name = "Number", Value = "N"},
+ new FormatObject() {Name = "Percent", Value = "P"},
+ new FormatObject() {Name = "Degrees", Value = "{0:N2} °"},
+ });
+ }
+ }
+
+ public IList SpinnerLocations
+ {
+ get
+ {
+ if (_spinnerLocations == null)
+ {
+ _spinnerLocations = new List();
+ foreach (Location value in Enum.GetValues(typeof(Location)))
+ {
+ _spinnerLocations.Add(value);
+ }
+ }
+ return _spinnerLocations ;
+ }
+ }
+
+ public IList Cultures { get; } = new List()
+ {
+ new CultureInfo("en-US"),
+ new CultureInfo("en-GB"),
+ new CultureInfo("fr-FR"),
+ new CultureInfo("ar-DZ"),
+ new CultureInfo("zh-CN"),
+ new CultureInfo("cs-CZ")
+ };
+
+ public FormatObject SelectedFormat
+ {
+ get { return _selectedFormat; }
+ set { this.RaiseAndSetIfChanged(ref _selectedFormat, value); }
+ }
+ }
+
+ public class FormatObject
+ {
+ public string Value { get; set; }
+ public string Name { get; set; }
+ }
+}
diff --git a/samples/ControlCatalog/Properties/AssemblyInfo.cs b/samples/ControlCatalog/Properties/AssemblyInfo.cs
deleted file mode 100644
index 30c069d7d8..0000000000
--- a/samples/ControlCatalog/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("ControlCatalog")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("ControlCatalog")]
-[assembly: AssemblyCopyright("Copyright © 2015")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("61bec86c-f307-4295-b5b8-9428610d7d55")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
index 4271d05f91..e0f3e92c74 100644
--- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
+++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
@@ -17,7 +17,9 @@
-
+
+ PreserveNewest
+
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
index 0b683239fb..78f744cea0 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
@@ -36,7 +36,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_clientSize = value;
UpdateParams();
}
-
+
+ public void SetMinMaxSize(Size minSize, Size maxSize)
+ {
+ }
+
public IScreenImpl Screen { get; }
public Point Position
diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs
index 9d4d40bfef..fdb04b6dfc 100644
--- a/src/Avalonia.Base/AttachedProperty.cs
+++ b/src/Avalonia.Base/AttachedProperty.cs
@@ -9,7 +9,7 @@ namespace Avalonia
/// An attached avalonia property.
///
/// The type of the property's value.
- public class AttachedProperty : StyledPropertyBase
+ public class AttachedProperty : StyledProperty
{
///
/// Initializes a new instance of the class.
@@ -35,11 +35,10 @@ namespace Avalonia
///
/// The owner type.
/// The property.
- public StyledProperty AddOwner() where TOwner : IAvaloniaObject
+ public new AttachedProperty AddOwner() where TOwner : IAvaloniaObject
{
- var result = new StyledProperty(this, typeof(TOwner));
- AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
- return result;
+ AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
+ return this;
}
}
}
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index a46d567d28..4ab813333d 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -12,7 +12,6 @@ using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.Threading;
using Avalonia.Utilities;
-using System.Reactive.Concurrency;
namespace Avalonia
{
@@ -218,11 +217,6 @@ namespace Avalonia
}
else
{
- if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
- {
- ThrowNotRegistered(property);
- }
-
return GetValueInternal(property);
}
}
@@ -377,11 +371,6 @@ namespace Avalonia
{
PriorityValue v;
- if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
- {
- ThrowNotRegistered(property);
- }
-
if (!_values.TryGetValue(property, out v))
{
v = CreatePriorityValue(property);
@@ -804,11 +793,6 @@ namespace Avalonia
var originalValue = value;
- if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
- {
- ThrowNotRegistered(property);
- }
-
if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value))
{
throw new ArgumentException(string.Format(
@@ -836,18 +820,32 @@ namespace Avalonia
}
///
- /// Given a returns a registered avalonia property that is
- /// equal or throws if not found.
+ /// Given a direct property, returns a registered avalonia property that is equivalent or
+ /// throws if not found.
///
/// The property.
/// The registered property.
- public AvaloniaProperty GetRegistered(AvaloniaProperty property)
+ private AvaloniaProperty GetRegistered(AvaloniaProperty property)
{
- var result = AvaloniaPropertyRegistry.Instance.FindRegistered(this, property);
+ var direct = property as IDirectPropertyAccessor;
+
+ if (direct == null)
+ {
+ throw new AvaloniaInternalException(
+ "AvaloniaObject.GetRegistered should only be called for direct properties");
+ }
+
+ if (property.OwnerType.IsAssignableFrom(GetType()))
+ {
+ return property;
+ }
+
+ var result = AvaloniaPropertyRegistry.Instance.GetRegistered(this)
+ .FirstOrDefault(x => x == property);
if (result == null)
{
- ThrowNotRegistered(property);
+ throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}");
}
return result;
@@ -898,15 +896,5 @@ namespace Avalonia
value,
priority);
}
-
- ///
- /// Throws an exception indicating that the specified property is not registered on this
- /// object.
- ///
- /// The property
- private void ThrowNotRegistered(AvaloniaProperty p)
- {
- throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}");
- }
}
}
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index fb78e3b2a0..f7dabd3a43 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -311,7 +311,9 @@ namespace Avalonia
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits);
- AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);
+ var registry = AvaloniaPropertyRegistry.Instance;
+ registry.Register(typeof(TOwner), result);
+ registry.RegisterAttached(typeof(THost), result);
return result;
}
@@ -344,7 +346,9 @@ namespace Avalonia
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty(name, ownerType, metadata, inherits);
- AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);
+ var registry = AvaloniaPropertyRegistry.Instance;
+ registry.Register(ownerType, result);
+ registry.RegisterAttached(typeof(THost), result);
return result;
}
diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
index ec1643427b..c0a4ace6ed 100644
--- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Reflection;
using System.Runtime.CompilerServices;
namespace Avalonia
@@ -14,23 +13,14 @@ namespace Avalonia
///
public class AvaloniaPropertyRegistry
{
- ///
- /// The registered properties by type.
- ///
private readonly Dictionary> _registered =
new Dictionary>();
-
- ///
- /// The registered properties by type cached values to increase performance.
- ///
- private readonly Dictionary> _registeredCache =
- new Dictionary>();
-
- ///
- /// The registered attached properties by owner type.
- ///
private readonly Dictionary> _attached =
new Dictionary>();
+ private readonly Dictionary> _registeredCache =
+ new Dictionary>();
+ private readonly Dictionary> _attachedCache =
+ new Dictionary>();
///
/// Gets the instance
@@ -39,51 +29,68 @@ namespace Avalonia
= new AvaloniaPropertyRegistry();
///
- /// Gets all attached s registered by an owner.
+ /// Gets all non-attached s registered on a type.
///
- /// The owner type.
+ /// The type.
/// A collection of definitions.
- public IEnumerable GetAttached(Type ownerType)
+ public IEnumerable GetRegistered(Type type)
{
- Dictionary inner;
+ Contract.Requires(type != null);
+
+ if (_registeredCache.TryGetValue(type, out var result))
+ {
+ return result;
+ }
- // Ensure the type's static ctor has been run.
- RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle);
+ var t = type;
+ result = new List();
- if (_attached.TryGetValue(ownerType, out inner))
+ while (t != null)
{
- return inner.Values;
+ // Ensure the type's static ctor has been run.
+ RuntimeHelpers.RunClassConstructor(t.TypeHandle);
+
+ if (_registered.TryGetValue(t, out var registered))
+ {
+ result.AddRange(registered.Values);
+ }
+
+ t = t.BaseType;
}
- return Enumerable.Empty();
+ _registeredCache.Add(type, result);
+ return result;
}
///
- /// Gets all s registered on a type.
+ /// Gets all attached s registered on a type.
///
/// The type.
/// A collection of definitions.
- public IEnumerable GetRegistered(Type type)
+ public IEnumerable GetRegisteredAttached(Type type)
{
Contract.Requires(type != null);
- while (type != null)
+ if (_attachedCache.TryGetValue(type, out var result))
{
- // Ensure the type's static ctor has been run.
- RuntimeHelpers.RunClassConstructor(type.TypeHandle);
+ return result;
+ }
- Dictionary inner;
+ var t = type;
+ result = new List();
- if (_registered.TryGetValue(type, out inner))
+ while (t != null)
+ {
+ if (_attached.TryGetValue(t, out var attached))
{
- foreach (var p in inner)
- {
- yield return p.Value;
- }
+ result.AddRange(attached.Values);
}
- type = type.GetTypeInfo().BaseType;
+ t = t.BaseType;
}
+
+ _attachedCache.Add(type, result);
+ return result;
}
///
@@ -99,142 +106,92 @@ namespace Avalonia
}
///
- /// Finds a registered on a type.
+ /// Finds a registered non-attached property on a type by name.
///
/// The type.
- /// The property.
- /// The registered property or null if not found.
- ///
- /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
- /// different object but is equal according to .
- ///
- public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property)
+ /// The property name.
+ ///
+ /// The registered property or null if no matching property found.
+ ///
+ ///
+ /// The property name contains a '.'.
+ ///
+ public AvaloniaProperty FindRegistered(Type type, string name)
{
- Type currentType = type;
- Dictionary cache;
- AvaloniaProperty result;
+ Contract.Requires(type != null);
+ Contract.Requires(name != null);
- if (_registeredCache.TryGetValue(type, out cache))
+ if (name.Contains('.'))
{
- if (cache.TryGetValue(property.Id, out result))
- {
- return result;
- }
+ throw new InvalidOperationException("Attached properties not supported.");
}
- while (currentType != null)
- {
- Dictionary inner;
-
- if (_registered.TryGetValue(currentType, out inner))
- {
- if (inner.TryGetValue(property.Id, out result))
- {
- if (cache == null)
- {
- _registeredCache[type] = cache = new Dictionary();
- }
-
- cache[property.Id] = result;
-
- return result;
- }
- }
-
- currentType = currentType.GetTypeInfo().BaseType;
- }
-
- return null;
+ return GetRegistered(type).FirstOrDefault(x => x.Name == name);
}
///
- /// Finds registered on an object.
+ /// Finds a registered non-attached property on a type by name.
///
/// The object.
- /// The property.
- /// The registered property or null if not found.
- ///
- /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
- /// different object but is equal according to .
- ///
- public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property)
+ /// The property name.
+ ///
+ /// The registered property or null if no matching property found.
+ ///
+ ///
+ /// The property name contains a '.'.
+ ///
+ public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
{
- return FindRegistered(o.GetType(), property);
+ Contract.Requires(o != null);
+ Contract.Requires(name != null);
+
+ return FindRegistered(o.GetType(), name);
}
///
- /// Finds a registered property on a type by name.
+ /// Finds a registered attached property on a type by name.
///
/// The type.
- ///
- /// The property name. If an attached property it should be in the form
- /// "OwnerType.PropertyName".
- ///
+ /// The owner type.
+ /// The property name.
///
/// The registered property or null if no matching property found.
///
- public AvaloniaProperty FindRegistered(Type type, string name)
+ ///
+ /// The property name contains a '.'.
+ ///
+ public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name)
{
Contract.Requires(type != null);
+ Contract.Requires(ownerType != null);
Contract.Requires(name != null);
- var parts = name.Split('.');
- var types = GetImplementedTypes(type).ToList();
-
- if (parts.Length < 1 || parts.Length > 2)
+ if (name.Contains('.'))
{
- throw new ArgumentException("Invalid property name.");
+ throw new InvalidOperationException("Attached properties not supported.");
}
- string propertyName;
- var results = GetRegistered(type);
-
- if (parts.Length == 1)
- {
- propertyName = parts[0];
- results = results.Where(x => !x.IsAttached || types.Contains(x.OwnerType.Name));
- }
- else
- {
- if (!types.Contains(parts[0]))
- {
- results = results.Where(x => x.OwnerType.Name == parts[0]);
- }
-
- propertyName = parts[1];
- }
-
- return results.FirstOrDefault(x => x.Name == propertyName);
+ return GetRegisteredAttached(type).FirstOrDefault(x => x.Name == name);
}
///
- /// Finds a registered property on an object by name.
+ /// Finds a registered non-attached property on a type by name.
///
/// The object.
- ///
- /// The property name. If an attached property it should be in the form
- /// "OwnerType.PropertyName".
- ///
+ /// The owner type.
+ /// The property name.
///
/// The registered property or null if no matching property found.
///
- public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
+ ///
+ /// The property name contains a '.'.
+ ///
+ public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name)
{
- return FindRegistered(o.GetType(), name);
- }
+ Contract.Requires(o != null);
+ Contract.Requires(name != null);
- ///
- /// Returns a type and all its base types.
- ///
- /// The type.
- /// The type and all its base types.
- private IEnumerable GetImplementedTypes(Type type)
- {
- while (type != null)
- {
- yield return type.Name;
- type = type.GetTypeInfo().BaseType;
- }
+ return FindRegisteredAttached(o.GetType(), ownerType, name);
}
///
@@ -245,7 +202,11 @@ namespace Avalonia
/// True if the property is registered, otherwise false.
public bool IsRegistered(Type type, AvaloniaProperty property)
{
- return FindRegistered(type, property) != null;
+ Contract.Requires(type != null);
+ Contract.Requires(property != null);
+
+ return Instance.GetRegistered(type).Any(x => x == property) ||
+ Instance.GetRegisteredAttached(type).Any(x => x == property);
}
///
@@ -256,6 +217,9 @@ namespace Avalonia
/// True if the property is registered, otherwise false.
public bool IsRegistered(object o, AvaloniaProperty property)
{
+ Contract.Requires(o != null);
+ Contract.Requires(property != null);
+
return IsRegistered(o.GetType(), property);
}
@@ -274,34 +238,53 @@ namespace Avalonia
Contract.Requires(type != null);
Contract.Requires(property != null);
- Dictionary inner;
-
- if (!_registered.TryGetValue(type, out inner))
+ if (!_registered.TryGetValue(type, out var inner))
{
inner = new Dictionary();
+ inner.Add(property.Id, property);
_registered.Add(type, inner);
}
-
- if (!inner.ContainsKey(property.Id))
+ else if (!inner.ContainsKey(property.Id))
{
inner.Add(property.Id, property);
}
+
+ _registeredCache.Clear();
+ }
- if (property.IsAttached)
+ ///
+ /// Registers an attached on a type.
+ ///
+ /// The type.
+ /// The property.
+ ///
+ /// You won't usually want to call this method directly, instead use the
+ ///
+ /// method.
+ ///
+ public void RegisterAttached(Type type, AvaloniaProperty property)
+ {
+ Contract.Requires(type != null);
+ Contract.Requires(property != null);
+
+ if (!property.IsAttached)
{
- if (!_attached.TryGetValue(property.OwnerType, out inner))
- {
- inner = new Dictionary();
- _attached.Add(property.OwnerType, inner);
- }
+ throw new InvalidOperationException(
+ "Cannot register a non-attached property as attached.");
+ }
- if (!inner.ContainsKey(property.Id))
- {
- inner.Add(property.Id, property);
- }
+ if (!_attached.TryGetValue(type, out var inner))
+ {
+ inner = new Dictionary();
+ inner.Add(property.Id, property);
+ _attached.Add(type, inner);
+ }
+ else
+ {
+ inner.Add(property.Id, property);
}
- _registeredCache.Clear();
+ _attachedCache.Clear();
}
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
index b90dccf74e..84ac85d3db 100644
--- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
@@ -117,7 +117,7 @@ namespace Avalonia.Collections
_inner = new Dictionary();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[]"));
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));
if (CollectionChanged != null)
@@ -222,4 +222,4 @@ namespace Avalonia.Collections
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
index 54cd132b95..b27b06a277 100644
--- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
@@ -34,14 +34,18 @@ namespace Avalonia.Collections
///
/// An action called when the collection is reset.
///
+ ///
+ /// Indicates if a weak subscription should be used to track changes to the collection.
+ ///
/// A disposable used to terminate the subscription.
public static IDisposable ForEachItem(
this IAvaloniaReadOnlyList collection,
Action added,
Action removed,
- Action reset)
+ Action reset,
+ bool weakSubscription = false)
{
- return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset);
+ return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset, weakSubscription);
}
///
@@ -63,12 +67,16 @@ namespace Avalonia.Collections
/// An action called when the collection is reset. This will be followed by calls to
/// for each item present in the collection after the reset.
///
+ ///
+ /// Indicates if a weak subscription should be used to track changes to the collection.
+ ///
/// A disposable used to terminate the subscription.
public static IDisposable ForEachItem(
this IAvaloniaReadOnlyList collection,
Action added,
Action removed,
- Action reset)
+ Action reset,
+ bool weakSubscription = false)
{
void Add(int index, IList items)
{
@@ -118,9 +126,17 @@ namespace Avalonia.Collections
};
Add(0, (IList)collection);
- collection.CollectionChanged += handler;
- return Disposable.Create(() => collection.CollectionChanged -= handler);
+ if (weakSubscription)
+ {
+ return collection.WeakSubscribe(handler);
+ }
+ else
+ {
+ collection.CollectionChanged += handler;
+
+ return Disposable.Create(() => collection.CollectionChanged -= handler);
+ }
}
public static IAvaloniaReadOnlyList CreateDerivedList(
diff --git a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
new file mode 100644
index 0000000000..d295cb91ce
--- /dev/null
+++ b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
@@ -0,0 +1,127 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Reactive;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Avalonia.Utilities;
+
+namespace Avalonia.Collections
+{
+ public static class NotifyCollectionChangedExtensions
+ {
+ ///
+ /// Gets a weak observable for the CollectionChanged event.
+ ///
+ /// The collection.
+ /// An observable.
+ public static IObservable GetWeakCollectionChangedObservable(
+ this INotifyCollectionChanged collection)
+ {
+ Contract.Requires(collection != null);
+
+ return new WeakCollectionChangedObservable(new WeakReference(collection));
+ }
+
+ ///
+ /// Subcribes to the CollectionChanged event using a weak subscription.
+ ///
+ /// The collection.
+ ///
+ /// An action called when the collection event is raised.
+ ///
+ /// A disposable used to terminate the subscription.
+ public static IDisposable WeakSubscribe(
+ this INotifyCollectionChanged collection,
+ NotifyCollectionChangedEventHandler handler)
+ {
+ Contract.Requires(collection != null);
+ Contract.Requires(handler != null);
+
+ return
+ collection.GetWeakCollectionChangedObservable()
+ .Subscribe(e => handler.Invoke(collection, e));
+ }
+
+ ///
+ /// Subcribes to the CollectionChanged event using a weak subscription.
+ ///
+ /// The collection.
+ ///
+ /// An action called when the collection event is raised.
+ ///
+ /// A disposable used to terminate the subscription.
+ public static IDisposable WeakSubscribe(
+ this INotifyCollectionChanged collection,
+ Action handler)
+ {
+ Contract.Requires(collection != null);
+ Contract.Requires(handler != null);
+
+ return
+ collection.GetWeakCollectionChangedObservable()
+ .Subscribe(handler);
+ }
+
+ private class WeakCollectionChangedObservable : ObservableBase,
+ IWeakSubscriber
+ {
+ private WeakReference _sourceReference;
+ private readonly Subject _changed = new Subject();
+
+ private int _count;
+
+ public WeakCollectionChangedObservable(WeakReference source)
+ {
+ _sourceReference = source;
+ }
+
+ public void OnEvent(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ _changed.OnNext(e);
+ }
+
+ protected override IDisposable SubscribeCore(IObserver observer)
+ {
+ if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
+ {
+ if (_count++ == 0)
+ {
+ WeakSubscriptionManager.Subscribe(
+ instance,
+ nameof(instance.CollectionChanged),
+ this);
+ }
+
+ return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
+ .Subscribe(observer);
+ }
+ else
+ {
+ _changed.OnCompleted();
+ observer.OnCompleted();
+ return Disposable.Empty;
+ }
+ }
+
+ private void DecrementCount()
+ {
+ if (--_count == 0)
+ {
+ if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
+ {
+ WeakSubscriptionManager.Unsubscribe(
+ instance,
+ nameof(instance.CollectionChanged),
+ this);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs
index 8352528285..1ce73c20ba 100644
--- a/src/Avalonia.Base/DirectProperty.cs
+++ b/src/Avalonia.Base/DirectProperty.cs
@@ -75,6 +75,9 @@ namespace Avalonia
///
public Action Setter { get; }
+ ///
+ Type IDirectPropertyAccessor.Owner => typeof(TOwner);
+
///
/// Registers the direct property on another type.
///
diff --git a/src/Avalonia.Base/IDirectPropertyAccessor.cs b/src/Avalonia.Base/IDirectPropertyAccessor.cs
index 62aeef73c7..4f46652693 100644
--- a/src/Avalonia.Base/IDirectPropertyAccessor.cs
+++ b/src/Avalonia.Base/IDirectPropertyAccessor.cs
@@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
+
namespace Avalonia
{
///
@@ -14,6 +16,11 @@ namespace Avalonia
///
bool IsReadOnly { get; }
+ ///
+ /// Gets the class that registered the property.
+ ///
+ Type Owner { get; }
+
///
/// Gets the value of the property on the instance.
///
diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs
index 7d29a4f969..cf7acb3e8a 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.cs
@@ -81,12 +81,14 @@ namespace Avalonia.Threading
///
public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
+ Contract.Requires(action != null);
return _jobRunner?.InvokeAsync(action, priority);
}
///
public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
+ Contract.Requires(action != null);
_jobRunner?.Post(action, priority);
}
diff --git a/src/Avalonia.Base/Utilities/StringTokenizer.cs b/src/Avalonia.Base/Utilities/StringTokenizer.cs
new file mode 100644
index 0000000000..2559e52932
--- /dev/null
+++ b/src/Avalonia.Base/Utilities/StringTokenizer.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Globalization;
+using static System.Char;
+
+namespace Avalonia.Utilities
+{
+ public struct StringTokenizer : IDisposable
+ {
+ private const char DefaultSeparatorChar = ',';
+
+ private readonly string _s;
+ private readonly int _length;
+ private readonly char _separator;
+ private readonly string _exceptionMessage;
+ private readonly IFormatProvider _formatProvider;
+ private int _index;
+ private int _tokenIndex;
+ private int _tokenLength;
+
+ public StringTokenizer(string s, IFormatProvider formatProvider, string exceptionMessage = null)
+ : this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage)
+ {
+ _formatProvider = formatProvider;
+ }
+
+ public StringTokenizer(string s, char separator = DefaultSeparatorChar, string exceptionMessage = null)
+ {
+ _s = s ?? throw new ArgumentNullException(nameof(s));
+ _length = s?.Length ?? 0;
+ _separator = separator;
+ _exceptionMessage = exceptionMessage;
+ _formatProvider = CultureInfo.InvariantCulture;
+ _index = 0;
+ _tokenIndex = -1;
+ _tokenLength = 0;
+
+ while (_index < _length && IsWhiteSpace(_s, _index))
+ {
+ _index++;
+ }
+ }
+
+ public string CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength);
+
+ public void Dispose()
+ {
+ if (_index != _length)
+ {
+ throw GetFormatException();
+ }
+ }
+
+ public bool TryReadInt32(out Int32 result, char? separator = null)
+ {
+ var success = TryReadString(out var stringResult, separator);
+ result = success ? int.Parse(stringResult, _formatProvider) : 0;
+ return success;
+ }
+
+ public int ReadInt32(char? separator = null)
+ {
+ if (!TryReadInt32(out var result, separator))
+ {
+ throw GetFormatException();
+ }
+
+ return result;
+ }
+
+ public bool TryReadDouble(out double result, char? separator = null)
+ {
+ var success = TryReadString(out var stringResult, separator);
+ result = success ? double.Parse(stringResult, _formatProvider) : 0;
+ return success;
+ }
+
+ public double ReadDouble(char? separator = null)
+ {
+ if (!TryReadDouble(out var result, separator))
+ {
+ throw GetFormatException();
+ }
+
+ return result;
+ }
+
+ public bool TryReadString(out string result, char? separator = null)
+ {
+ var success = TryReadToken(separator ?? _separator);
+ result = CurrentToken;
+ return success;
+ }
+
+ public string ReadString(char? separator = null)
+ {
+ if (!TryReadString(out var result, separator))
+ {
+ throw GetFormatException();
+ }
+
+ return result;
+ }
+
+ private bool TryReadToken(char separator)
+ {
+ _tokenIndex = -1;
+
+ if (_index >= _length)
+ {
+ return false;
+ }
+
+ var c = _s[_index];
+
+ var index = _index;
+ var length = 0;
+
+ while (_index < _length)
+ {
+ c = _s[_index];
+
+ if (IsWhiteSpace(c) || c == separator)
+ {
+ break;
+ }
+
+ _index++;
+ length++;
+ }
+
+ SkipToNextToken(separator);
+
+ _tokenIndex = index;
+ _tokenLength = length;
+
+ if (_tokenLength < 1)
+ {
+ throw GetFormatException();
+ }
+
+ return true;
+ }
+
+ private void SkipToNextToken(char separator)
+ {
+ if (_index < _length)
+ {
+ var c = _s[_index];
+
+ if (c != separator && !IsWhiteSpace(c))
+ {
+ throw GetFormatException();
+ }
+
+ var length = 0;
+
+ while (_index < _length)
+ {
+ c = _s[_index];
+
+ if (c == separator)
+ {
+ length++;
+ _index++;
+
+ if (length > 1)
+ {
+ throw GetFormatException();
+ }
+ }
+ else
+ {
+ if (!IsWhiteSpace(c))
+ {
+ break;
+ }
+
+ _index++;
+ }
+ }
+
+ if (length > 0 && _index >= _length)
+ {
+ throw GetFormatException();
+ }
+ }
+ }
+
+ private FormatException GetFormatException() =>
+ _exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException();
+
+ private static char GetSeparatorFromFormatProvider(IFormatProvider provider)
+ {
+ var c = DefaultSeparatorChar;
+
+ var formatInfo = NumberFormatInfo.GetInstance(provider);
+ if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0])
+ {
+ c = ';';
+ }
+
+ return c;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 06c1a8b4cc..6fdca557eb 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -2,16 +2,17 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Reactive.Concurrency;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
+using Avalonia.Input.Raw;
using Avalonia.Layout;
-using Avalonia.Rendering;
+using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
-using System.Reactive.Concurrency;
namespace Avalonia
{
@@ -234,7 +235,9 @@ namespace Avalonia
.Bind().ToConstant(_styler)
.Bind().ToSingleton()
.Bind().ToConstant(this)
- .Bind().ToConstant(AvaloniaScheduler.Instance);
+ .Bind().ToConstant(AvaloniaScheduler.Instance)
+ .Bind().ToConstant(DragDropDevice.Instance)
+ .Bind().ToTransient();
}
}
}
diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
new file mode 100644
index 0000000000..8e801d606b
--- /dev/null
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -0,0 +1,2726 @@
+// (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.ObjectModel;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Collections;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Controls.Utils;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Provides data for the
+ ///
+ /// event.
+ ///
+ public class PopulatedEventArgs : EventArgs
+ {
+ ///
+ /// Gets the list of possible matches added to the drop-down portion of
+ /// the
+ /// control.
+ ///
+ /// The list of possible matches added to the
+ /// .
+ public IEnumerable Data { get; private set; }
+
+ ///
+ /// Initializes a new instance of the
+ /// .
+ ///
+ /// The list of possible matches added to the
+ /// drop-down portion of the
+ /// control.
+ public PopulatedEventArgs(IEnumerable data)
+ {
+ Data = data;
+ }
+ }
+
+ ///
+ /// Provides data for the
+ ///
+ /// event.
+ ///
+ /// Stable
+ public class PopulatingEventArgs : CancelEventArgs
+ {
+ ///
+ /// Gets the text that is used to determine which items to display in
+ /// the
+ /// control.
+ ///
+ /// The text that is used to determine which items to display in
+ /// the .
+ public string Parameter { get; private set; }
+
+ ///
+ /// Initializes a new instance of the
+ /// .
+ ///
+ /// The value of the
+ ///
+ /// property, which is used to filter items for the
+ /// control.
+ public PopulatingEventArgs(string parameter)
+ {
+ Parameter = parameter;
+ }
+ }
+
+ ///
+ /// Represents the filter used by the
+ /// control to
+ /// determine whether an item is a possible match for the specified text.
+ ///
+ /// true to indicate is a possible match
+ /// for ; otherwise false.
+ /// The string used as the basis for filtering.
+ /// The item that is compared with the
+ /// parameter.
+ /// The type used for filtering the
+ /// . This type can
+ /// be either a string or an object.
+ /// Stable
+ public delegate bool AutoCompleteFilterPredicate(string search, T item);
+
+ ///
+ /// Specifies how text in the text box portion of the
+ /// control is used
+ /// to filter items specified by the
+ ///
+ /// property for display in the drop-down.
+ ///
+ /// Stable
+ public enum AutoCompleteFilterMode
+ {
+ ///
+ /// Specifies that no filter is used. All items are returned.
+ ///
+ None = 0,
+
+ ///
+ /// Specifies a culture-sensitive, case-insensitive filter where the
+ /// returned items start with the specified text. The filter uses the
+ ///
+ /// method, specifying
+ /// as
+ /// the string comparison criteria.
+ ///
+ StartsWith = 1,
+
+ ///
+ /// Specifies a culture-sensitive, case-sensitive filter where the
+ /// returned items start with the specified text. The filter uses the
+ ///
+ /// method, specifying
+ /// as the string
+ /// comparison criteria.
+ ///
+ StartsWithCaseSensitive = 2,
+
+ ///
+ /// Specifies an ordinal, case-insensitive filter where the returned
+ /// items start with the specified text. The filter uses the
+ ///
+ /// method, specifying
+ /// as the
+ /// string comparison criteria.
+ ///
+ StartsWithOrdinal = 3,
+
+ ///
+ /// Specifies an ordinal, case-sensitive filter where the returned items
+ /// start with the specified text. The filter uses the
+ ///
+ /// method, specifying as
+ /// the string comparison criteria.
+ ///
+ StartsWithOrdinalCaseSensitive = 4,
+
+ ///
+ /// Specifies a culture-sensitive, case-insensitive filter where the
+ /// returned items contain the specified text.
+ ///
+ Contains = 5,
+
+ ///
+ /// Specifies a culture-sensitive, case-sensitive filter where the
+ /// returned items contain the specified text.
+ ///
+ ContainsCaseSensitive = 6,
+
+ ///
+ /// Specifies an ordinal, case-insensitive filter where the returned
+ /// items contain the specified text.
+ ///
+ ContainsOrdinal = 7,
+
+ ///
+ /// Specifies an ordinal, case-sensitive filter where the returned items
+ /// contain the specified text.
+ ///
+ ContainsOrdinalCaseSensitive = 8,
+
+ ///
+ /// Specifies a culture-sensitive, case-insensitive filter where the
+ /// returned items equal the specified text. The filter uses the
+ ///
+ /// method, specifying
+ /// as
+ /// the search comparison criteria.
+ ///
+ Equals = 9,
+
+ ///
+ /// Specifies a culture-sensitive, case-sensitive filter where the
+ /// returned items equal the specified text. The filter uses the
+ ///
+ /// method, specifying
+ /// as the string
+ /// comparison criteria.
+ ///
+ EqualsCaseSensitive = 10,
+
+ ///
+ /// Specifies an ordinal, case-insensitive filter where the returned
+ /// items equal the specified text. The filter uses the
+ ///
+ /// method, specifying
+ /// as the
+ /// string comparison criteria.
+ ///
+ EqualsOrdinal = 11,
+
+ ///
+ /// Specifies an ordinal, case-sensitive filter where the returned items
+ /// equal the specified text. The filter uses the
+ ///
+ /// method, specifying as
+ /// the string comparison criteria.
+ ///
+ EqualsOrdinalCaseSensitive = 12,
+
+ ///
+ /// Specifies that a custom filter is used. This mode is used when the
+ ///
+ /// or
+ ///
+ /// properties are set.
+ ///
+ Custom = 13,
+ }
+
+ ///
+ /// Represents a control that provides a text box for user input and a
+ /// drop-down that contains possible matches based on the input in the text
+ /// box.
+ ///
+ public class AutoCompleteBox : TemplatedControl
+ {
+ ///
+ /// Specifies the name of the selection adapter TemplatePart.
+ ///
+ private const string ElementSelectionAdapter = "PART_SelectionAdapter";
+
+ ///
+ /// Specifies the name of the Selector TemplatePart.
+ ///
+ private const string ElementSelector = "PART_SelectingItemsControl";
+
+ ///
+ /// Specifies the name of the Popup TemplatePart.
+ ///
+ private const string ElementPopup = "PART_Popup";
+
+ ///
+ /// The name for the text box part.
+ ///
+ private const string ElementTextBox = "PART_TextBox";
+
+ private IEnumerable _itemsEnumerable;
+
+ ///
+ /// Gets or sets a local cached copy of the items data.
+ ///
+ private List