diff --git a/Avalonia.sln b/Avalonia.sln index 48c2d36796..e5ea4529f3 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -172,6 +172,7 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -196,7 +197,6 @@ Global samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 - samples\TestApplicationShared\TestApplicationShared.projitems*{ff69b927-c545-49ae-8e16-3d14d621aa12}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -2120,24 +2120,24 @@ Global {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.AppStore|Mono.ActiveCfg = Debug|Any CPU {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.AppStore|x86.ActiveCfg = Release|Any CPU {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.AppStore|x86.Build.0 = Release|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|iPhone.Build.0 = Debug|Any CPU + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|Any CPU.ActiveCfg = Debug|x86 + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|Any CPU.Build.0 = Debug|x86 + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|iPhone.ActiveCfg = Debug|x86 + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|iPhone.Build.0 = Debug|x86 {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|Mono.ActiveCfg = Debug|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|x86.ActiveCfg = Debug|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|x86.Build.0 = Debug|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|Any CPU.Build.0 = Release|Any CPU + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|x86.ActiveCfg = Debug|x86 + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Debug|x86.Build.0 = Debug|x86 + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|Any CPU.ActiveCfg = Release|x86 + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|Any CPU.Build.0 = Release|x86 {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|iPhone.ActiveCfg = Release|Any CPU {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|iPhone.Build.0 = Release|Any CPU {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|Mono.ActiveCfg = Release|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|x86.ActiveCfg = Release|Any CPU - {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|x86.Build.0 = Release|Any CPU + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|x86.ActiveCfg = Release|x86 + {D35A9F3D-8BB0-496E-BF72-444038A7DEBB}.Release|x86.Build.0 = Release|x86 {52F55355-D120-42AC-8116-8410A7D602FA}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {52F55355-D120-42AC-8116-8410A7D602FA}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {52F55355-D120-42AC-8116-8410A7D602FA}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU @@ -2404,6 +2404,62 @@ Global {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|Mono.Build.0 = Release|Any CPU {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|x86.ActiveCfg = Release|Any CPU {F1FDC5B0-4654-416F-AE69-E3E9BBD87801}.Release|x86.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|Mono.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Ad-Hoc|x86.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|Any CPU.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|Any CPU.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|iPhone.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|iPhone.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|Mono.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|Mono.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|Mono.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|x86.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|x86.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.AppStore|x86.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|iPhone.Build.0 = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|iPhone.Deploy.0 = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|Mono.ActiveCfg = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|x86.ActiveCfg = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|x86.Build.0 = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Debug|x86.Deploy.0 = Debug|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|Any CPU.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|Any CPU.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|iPhone.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|iPhone.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|iPhone.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU + {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|Mono.ActiveCfg = Release|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2460,5 +2516,6 @@ Global {C7A69145-60B6-4882-97D6-A3921DD43978} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {BD7F352C-6DC1-4740-BAF2-2D34A038728C} = {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} EndGlobalSection EndGlobal diff --git a/build.cake b/build.cake index e3507d05fa..cb8ed5b9f5 100644 --- a/build.cake +++ b/build.cake @@ -635,21 +635,19 @@ Task("Run-Unit-Tests") .WithCriteria(() => !skipTests) .Does(() => { - var pattern = "./tests/Avalonia.*.UnitTests/bin/" + dirSuffix + "/Avalonia.*.UnitTests.dll"; - - Func ExcludeWindowsTests = i => { - return !(i.Path.FullPath.IndexOf("Direct2D", StringComparison.OrdinalIgnoreCase) >= 0); - }; - - var unitTests = isRunningOnWindows ? GetFiles(pattern) : GetFiles(pattern, ExcludeWindowsTests); + var unitTests = GetDirectories("./tests/Avalonia.*.UnitTests") + .Select(dir => System.IO.Path.GetFileName(dir.FullPath)) + .Where(name => isRunningOnWindows ? true : !(name.IndexOf("Direct2D", StringComparison.OrdinalIgnoreCase) >= 0)) + .Select(name => MakeAbsolute(File("./tests/" + name + "/bin/" + dirSuffix + "/" + name + ".dll"))) + .ToList(); if (isRunningOnWindows) { - var windowsTests = GetFiles("./tests/Avalonia.DesignerSupport.Tests/bin/" + dirSuffix + "/*Tests.dll") + - GetFiles("./tests/Avalonia.LeakTests/bin/" + dirSuffix + "/*Tests.dll") + - GetFiles("./tests/Avalonia.RenderTests/bin/" + dirSuffix + "/*Tests.dll"); + var windowsTests = GetFiles("./tests/Avalonia.DesignerSupport.Tests/bin/" + dirSuffix + "/*.Tests.dll") + + GetFiles("./tests/Avalonia.LeakTests/bin/" + dirSuffix + "/*.LeakTests.dll") + + GetFiles("./tests/Avalonia.RenderTests/bin/" + dirSuffix + "/*.RenderTests.dll"); - unitTests += windowsTests; + unitTests.AddRange(windowsTests); } var toolPath = (isPlatformAnyCPU || isPlatformX86) ? @@ -662,10 +660,7 @@ Task("Run-Unit-Tests") Parallelism = ParallelismOption.None }; - if (isRunningOnWindows) - { - settings.NoAppDomain = false; - } + settings.NoAppDomain = !isRunningOnWindows; foreach (var file in unitTests) { diff --git a/docs/spec/binding-from-code.md b/docs/spec/binding-from-code.md new file mode 100644 index 0000000000..76f3aa55c9 --- /dev/null +++ b/docs/spec/binding-from-code.md @@ -0,0 +1,156 @@ +# Binding from Code + +Avalonia binding from code works somewhat differently to WPF/UWP. At the low level, Avalonia's +binding system is based on Reactive Extensions' `IObservable` which is then built upon by XAML +bindings (which can also be instantiated in code). + +## Binding to an observable + +You can bind a property to an observable using the `AvaloniaObject.Bind` method: + +```csharp +// We use an Rx Subject here so we can push new values using OnNext +var source = new Subject(); +var textBlock = new TextBlock(); + +// Bind TextBlock.Text to source +textBlock.Bind(TextBlock.TextProperty, source); + +// Set textBlock.Text to "hello" +source.OnNext("hello"); +// Set textBlock.Text to "world!" +source.OnNext("world!"); +``` + +## Binding priorities + +You can also pass a priority to a binding. *Note: Priorities only apply to styled properties: they* +*are ignored for direct properties.* + +The priority is passed using the `BindingPriority` enum, which looks like this: + +```csharp +/// +/// The priority of a binding. +/// +public enum BindingPriority +{ + /// + /// A value that comes from an animation. + /// + Animation = -1, + + /// + /// A local value: this is the default. + /// + LocalValue = 0, + + /// + /// A triggered style binding. + /// + /// + /// A style trigger is a selector such as .class which overrides a + /// binding. In this way, a basic control can have + /// for example a Background from the templated parent which changes when the + /// control has the :pointerover class. + /// + StyleTrigger, + + /// + /// A binding to a property on the templated parent. + /// + TemplatedParent, + + /// + /// A style binding. + /// + Style, + + /// + /// The binding is uninitialized. + /// + Unset = int.MaxValue, +} +``` + +Bindings with a priority with a smaller number take precedence over bindings with a higher value +priority, and bindings added more recently take precedence over other bindings with the same +priority. Whenever the binding produces `AvaloniaProperty.UnsetValue` then the next binding in the +priority order is selected. + +## Setting a binding in an object initializer + +It is often useful to set up bindings in object initializers. You can do this using the indexer: + +```csharp +var source = new Subject(); +var textBlock = new TextBlock +{ + Foreground = Brushes.Red, + MaxWidth = 200, + [!TextBlock.TextProperty] = source.ToBinding(), +}; +``` + +Using this method you can also easily bind a property on one control to a property on another: + +```csharp +var textBlock1 = new TextBlock(); +var textBlock2 = new TextBlock +{ + Foreground = Brushes.Red, + MaxWidth = 200, + [!TextBlock.TextProperty] = textBlock1[!TextBlock.TextProperty], +}; +``` + +Of course the indexer can be used outside object initializers too: + +```csharp +textBlock2[!TextBlock.TextProperty] = textBlock1[!TextBlock.TextProperty]; +``` + +# Transforming binding values + +Because we're working with observables, we can easily transform the values we're binding! + +```csharp +var source = new Subject(); +var textBlock = new TextBlock +{ + Foreground = Brushes.Red, + MaxWidth = 200, + [!TextBlock.TextProperty] = source.Select(x => "Hello " + x).ToBinding(), +}; +``` + +# Using XAML bindings from code + +Sometimes when you want the additional features that XAML bindings provide, it's easier to use XAML bindings from code. For example, using only observables you could bind to a property on `DataContext` like this: + +```csharp +var textBlock = new TextBlock(); +var viewModelProperty = textBlock.GetObservable(TextBlock.DataContext) + .OfType() + .Select(x => x?.Name); +textBlock.Bind(TextBlock, viewModelProperty); +``` + +However, it might be preferable to use a XAML binding in this case: + +```csharp +var textBlock = new TextBlock +{ + [!TextBlock.TextProperty] = new Binding("Name") +}; +``` + +By using XAML binding objects, you get access to binding to named controls and [all the other features that XAML bindings bring](binding-from.xaml.md): + +```csharp +var textBlock = new TextBlock +{ + [!TextBlock.TextProperty] = new Binding("Text") { ElementName = "other" } +}; +``` + diff --git a/docs/spec/binding-from-xaml.md b/docs/spec/binding-from-xaml.md new file mode 100644 index 0000000000..143e3627c8 --- /dev/null +++ b/docs/spec/binding-from-xaml.md @@ -0,0 +1,99 @@ +# Binding from XAML + +Binding from XAML works on the whole the same as in other XAML frameworks: you use the `{Binding}` +markup extension. Avalonia does have some extra syntacic niceties however. Here's an overview of +what you can currently do in Avalonia: + +## Binding to a property on the DataContext + +By default a binding binds to a property on the `DataContext`, e.g.: + +```xml + + + + +``` + +An empty binding binds to DataContext itself + +```xml + + + + +``` + +This usage is identical to WPF/UWP etc. + +## Two way bindings and more + +You can also specify a binding `Mode`: + +```xml + + +``` + +This usage is identical to WPF/UWP etc. + +## Binding to a property on the templated parent + +When you're creating a control template and you want to bind to the templated parent you can use: + +```xml + + + +``` + +This usage is identical to WPF/UWP etc. + +## Binding to a named control + +If you want to bind to a property on another (named) control, you can use `ElementName` as in +WPF/UWP: + +```xml + + +``` + +However Avalonia also introduces a shorthand syntax for this: + +```xml + +``` + +## Negating bindings + +You can also negate the value of a binding using the `!` operator: + +```xml + +``` + +Here, the `TextBox` will only be enabled when the view model signals that it has no errors. Behind +the scenes, Avalonia tries to convert the incoming value to a boolean, and if it can be converted +it negates the value. If the incoming value cannot be converted to a boolean then no value will be +pushed to the binding target. + +This syntax is specific to Avalonia. + +## Binding to tasks and observables + +You can subscribe to the result of a task or an observable by using the `^` stream binding operator. + +```xml + + +``` + +This syntax is specific to Avalonia. + +*Note: the stream operator is actually extensible, see +[here](https://github.com/AvaloniaUI/Avalonia/blob/master/src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs) +for the interface to implement and [here](https://github.com/AvaloniaUI/Avalonia/blob/master/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs#L47) +for the registration.* diff --git a/docs/spec/toc.yml b/docs/spec/toc.yml index f225084c4e..b0981017e0 100644 --- a/docs/spec/toc.yml +++ b/docs/spec/toc.yml @@ -8,3 +8,7 @@ href: working-with-properties.md - name: Logging href: logging.md +- name: Binding from XAML + href: binding-from-xaml.md +- name: Binding from Code + href: binding-from-code.md diff --git a/docs/spec/working-with-properties.md b/docs/spec/working-with-properties.md index 74dd60a9b2..a8a383b733 100644 --- a/docs/spec/working-with-properties.md +++ b/docs/spec/working-with-properties.md @@ -71,6 +71,8 @@ property to the first: Console.WriteLine(textBlock2.Text); ``` +To read more about creating bindings from code, see [Binding from Code](binding-from-code.md). + # Subscribing to a Property on Any Object The `GetObservable` method returns an observable that tracks changes to a diff --git a/docs/tutorial/from-wpf.md b/docs/tutorial/from-wpf.md index f25de9cde4..f8bb3defa2 100644 --- a/docs/tutorial/from-wpf.md +++ b/docs/tutorial/from-wpf.md @@ -40,17 +40,6 @@ placed in a `DataTemplates` collection on each control (and on `Application`): -`ItemsControl`s don't currently have an `ItemTemplate` property: instead just -place the template for your items into the control's `DataTemplates`, e.g. - - - - - - - - - Data templates in Avalonia can also target interfaces and derived classes (which cannot be done in WPF) and so the order of `DataTemplate`s can be important: `DataTemplate`s within the same collection are evaluated in declaration order @@ -92,13 +81,8 @@ referred to using the `{StyleResource}` markup extension both inside and outside styles. For non-style-related resources, we suggest defining them in code and referring -to them in markup using the `{Static}` markup extension. There are [various -reasons](http://www.codemag.com/article/1501091) for this, but briefly: - -- Resources have to be parsed -- The tree has to be traversed to find them -- XAML doesn't handle immutable objects -- XAML syntax can be long-winded compared to C# +to them in markup using the `{Static}` markup extension. To read more about the reasoning for this, +see [this issue comment](https://github.com/AvaloniaUI/Avalonia/issues/462#issuecomment-191849723). ## Grid diff --git a/samples/ControlCatalog.Android/Assets/AboutAssets.txt b/samples/ControlCatalog.Android/Assets/AboutAssets.txt new file mode 100644 index 0000000000..ee39886295 --- /dev/null +++ b/samples/ControlCatalog.Android/Assets/AboutAssets.txt @@ -0,0 +1,19 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories) and given a Build Action of "AndroidAsset". + +These files will be deployed with you package and will be accessible using Android's +AssetManager, like this: + +public class ReadAsset : Activity +{ + protected override void OnCreate (Bundle bundle) + { + base.OnCreate (bundle); + + InputStream input = Assets.Open ("my_asset.txt"); + } +} + +Additionally, some Android functions will automatically load asset files: + +Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); \ No newline at end of file diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj new file mode 100644 index 0000000000..f83b1f3422 --- /dev/null +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -0,0 +1,169 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {29132311-1848-4FD6-AE0C-4FF841151BD3} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Properties + ControlCatalog.Android + ControlCatalog.Android + 512 + true + Resources\Resource.Designer.cs + Off + False + v4.4 + Properties\AndroidManifest.xml + + + True + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + True + None + False + False + False + armeabi,armeabi-v7a,x86 + Xamarin + False + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + False + Full + True + False + False + armeabi,armeabi-v7a,x86 + Xamarin + False + False + False + False + False + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + + + + + + {7B92AF71-6287-4693-9DCB-BD5B6E927E23} + Avalonia.Android + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Avalonia.Animation + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Avalonia.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Avalonia.Controls + + + {7062ae20-5dcc-4442-9645-8195bdece63e} + Avalonia.Diagnostics + + + {4a1abb09-9047-4bd5-a4ad-a055e52c5ee0} + Avalonia.DotNetFrameworkRuntime + + + {5fb2b005-0a7f-4dad-add4-3ed01444e63d} + Avalonia.HtmlRenderer + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Avalonia.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Avalonia.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Avalonia.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Avalonia.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Avalonia.Styling + + + {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} + Avalonia.Themes.Default + + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Avalonia.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Avalonia.Markup + + + {bd43f7c0-396b-4aa1-bad9-dfde54d51298} + Avalonia.Skia.Android + + + {d0a739b9-3c68-4ba6-a328-41606954b6bd} + ControlCatalog + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.v2.ncrunchproject b/samples/ControlCatalog.Android/ControlCatalog.Android.v2.ncrunchproject new file mode 100644 index 0000000000..e1b4d7cf28 --- /dev/null +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.v2.ncrunchproject @@ -0,0 +1,26 @@ + + true + 1000 + false + false + false + true + false + false + true + false + false + false + true + false + true + true + true + 60000 + + + + AutoDetect + STA + x86 + \ No newline at end of file diff --git a/samples/ControlCatalog.Android/GettingStarted.Xamarin b/samples/ControlCatalog.Android/GettingStarted.Xamarin new file mode 100644 index 0000000000..e9d4f6a411 --- /dev/null +++ b/samples/ControlCatalog.Android/GettingStarted.Xamarin @@ -0,0 +1,4 @@ + + GS\Android\CS\AndroidApp\GettingStarted.html + false + \ No newline at end of file diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs new file mode 100644 index 0000000000..3f357b0e70 --- /dev/null +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -0,0 +1,46 @@ +using System; +using Android.App; + +using Android.OS; +using Android.Content.PM; +using Avalonia.Android.Platform.Specific; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using Avalonia.Styling; +using Avalonia.Themes.Default; +using Avalonia; + +namespace ControlCatalog.Android +{ + [Activity(Label = "ControlCatalog.Android", MainLauncher = true, Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)] + public class MainActivity : AvaloniaActivity + { + public MainActivity() : base(typeof (App)) + { + + } + + protected override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + + App app; + if (Avalonia.Application.Current != null) + app = (App)Avalonia.Application.Current; + else + { + app = new App(); + AppBuilder.Configure(app) + .UseAndroid() + .UseSkia() + .SetupWithoutStarting(); + } + + var mainWindow = new MainWindow(); + app.Run(mainWindow); + } + } +} + diff --git a/samples/ControlCatalog.Android/Properties/AndroidManifest.xml b/samples/ControlCatalog.Android/Properties/AndroidManifest.xml new file mode 100644 index 0000000000..e39ec39f1c --- /dev/null +++ b/samples/ControlCatalog.Android/Properties/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog.Android/Properties/AssemblyInfo.cs b/samples/ControlCatalog.Android/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..baeec94648 --- /dev/null +++ b/samples/ControlCatalog.Android/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Android.App; + +// 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.Android")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ControlCatalog.Android")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: ComVisible(false)] + +// 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/ControlCatalog.Android/Resources/AboutResources.txt b/samples/ControlCatalog.Android/Resources/AboutResources.txt new file mode 100644 index 0000000000..c2bca974c4 --- /dev/null +++ b/samples/ControlCatalog.Android/Resources/AboutResources.txt @@ -0,0 +1,44 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.strings.first_string to reference the first +string in the dictionary file values/strings.xml. \ No newline at end of file diff --git a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs new file mode 100644 index 0000000000..96f0e76fd8 --- /dev/null +++ b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs @@ -0,0 +1,114 @@ +#pragma warning disable 1591 +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +[assembly: global::Android.Runtime.ResourceDesignerAttribute("ControlCatalog.Android.Resource", IsApplication=true)] + +namespace ControlCatalog.Android +{ + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + public partial class Resource + { + + static Resource() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + public static void UpdateIdValues() + { + global::Avalonia.Android.Resource.String.ApplicationName = global::ControlCatalog.Android.Resource.String.ApplicationName; + global::Avalonia.Android.Resource.String.Hello = global::ControlCatalog.Android.Resource.String.Hello; + } + + public partial class Attribute + { + + static Attribute() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Attribute() + { + } + } + + public partial class Drawable + { + + // aapt resource value: 0x7f020000 + public const int Icon = 2130837504; + + static Drawable() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Drawable() + { + } + } + + public partial class Id + { + + // aapt resource value: 0x7f050000 + public const int MyButton = 2131034112; + + static Id() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Id() + { + } + } + + public partial class Layout + { + + // aapt resource value: 0x7f030000 + public const int Main = 2130903040; + + static Layout() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private Layout() + { + } + } + + public partial class String + { + + // aapt resource value: 0x7f040001 + public const int ApplicationName = 2130968577; + + // aapt resource value: 0x7f040000 + public const int Hello = 2130968576; + + static String() + { + global::Android.Runtime.ResourceIdManager.UpdateIdValues(); + } + + private String() + { + } + } + } +} +#pragma warning restore 1591 diff --git a/samples/ControlCatalog.Android/Resources/drawable/Icon.png b/samples/ControlCatalog.Android/Resources/drawable/Icon.png new file mode 100644 index 0000000000..8074c4c571 Binary files /dev/null and b/samples/ControlCatalog.Android/Resources/drawable/Icon.png differ diff --git a/samples/ControlCatalog.Android/Resources/layout/Main.axml b/samples/ControlCatalog.Android/Resources/layout/Main.axml new file mode 100644 index 0000000000..570c96ad72 --- /dev/null +++ b/samples/ControlCatalog.Android/Resources/layout/Main.axml @@ -0,0 +1,13 @@ + + +