Browse Source

Merge branch 'master' into tinywm

tinywm
Nikita Tsukanov 10 years ago
parent
commit
834c1c87ec
  1. 2
      .gitmodules
  2. 21
      Perspex.sln
  3. 2
      nuget/build-version.ps1
  4. 6
      samples/BindingTest/App.config
  5. 40
      samples/BindingTest/App.cs
  6. 167
      samples/BindingTest/BindingTest.csproj
  7. 22
      samples/BindingTest/MainWindow.paml
  8. 21
      samples/BindingTest/MainWindow.paml.cs
  9. 36
      samples/BindingTest/Properties/AssemblyInfo.cs
  10. 44
      samples/BindingTest/ViewModels/MainWindowViewModel.cs
  11. 15
      samples/BindingTest/ViewModels/TestItem.cs
  12. 10
      samples/BindingTest/packages.config
  13. 2
      samples/TestApplication/GalleryStyle.cs
  14. 4
      samples/TestApplication/Program.cs
  15. 4
      samples/XamlTestApplication/Program.cs
  16. 58
      samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs
  17. 17
      samples/XamlTestApplicationPcl/ViewModels/TestItem.cs
  18. 14
      samples/XamlTestApplicationPcl/ViewModels/TestNode.cs
  19. 9
      samples/XamlTestApplicationPcl/Views/MainWindow.cs
  20. 47
      samples/XamlTestApplicationPcl/Views/MainWindow.paml
  21. 3
      samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj
  22. 72
      src/Gtk/Perspex.Cairo/Media/DrawingContext.cs
  23. 2
      src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs
  24. 2
      src/Gtk/Perspex.Cairo/Media/TileBrushes.cs
  25. 12
      src/Gtk/Perspex.Cairo/RenderTarget.cs
  26. 2
      src/Markup/Perspex.Markup.Xaml/Binding/SourceBindingEndpoint.cs
  27. 2
      src/Markup/Perspex.Markup.Xaml/Binding/TargetBindingEndpoint.cs
  28. 96
      src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs
  29. 21
      src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs
  30. 3
      src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs
  31. 9
      src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs
  32. 8
      src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs
  33. 10
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs
  34. 61
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs
  35. 12
      src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs
  36. 109
      src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs
  37. 52
      src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs
  38. 36
      src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs
  39. 41
      src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs
  40. 165
      src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs
  41. 16
      src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs
  42. 59
      src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs
  43. 65
      src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs
  44. 32
      src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs
  45. 18
      src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  46. 2
      src/Markup/Perspex.Markup.Xaml/OmniXAML
  47. 22
      src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj
  48. 26
      src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs
  49. 53
      src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs
  50. 102
      src/Markup/Perspex.Markup/Binding/ExpressionNode.cs
  51. 29
      src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs
  52. 115
      src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs
  53. 24
      src/Markup/Perspex.Markup/Binding/ExpressionParseException.cs
  54. 51
      src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs
  55. 131
      src/Markup/Perspex.Markup/Binding/IndexerNode.cs
  56. 40
      src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs
  57. 67
      src/Markup/Perspex.Markup/Binding/Parsers/ArgumentListParser.cs
  58. 125
      src/Markup/Perspex.Markup/Binding/Parsers/ExpressionParser.cs
  59. 51
      src/Markup/Perspex.Markup/Binding/Parsers/IdentifierParser.cs
  60. 35
      src/Markup/Perspex.Markup/Binding/Parsers/LiteralParser.cs
  61. 44
      src/Markup/Perspex.Markup/Binding/Parsers/Reader.cs
  62. 164
      src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs
  63. 89
      src/Markup/Perspex.Markup/Perspex.Markup.csproj
  64. 32
      src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs
  65. 8
      src/Markup/Perspex.Markup/packages.config
  66. 2
      src/Perspex.Base/BindingDescriptor.cs
  67. 38
      src/Perspex.Base/IObservablePropertyBag.cs
  68. 5
      src/Perspex.Base/IPropertyBag.cs
  69. 104
      src/Perspex.Base/PerspexObject.cs
  70. 26
      src/Perspex.Base/PriorityValue.cs
  71. 32
      src/Perspex.Base/Utilities/TypeUtilities.cs
  72. 2
      src/Perspex.Controls/Border.cs
  73. 2
      src/Perspex.Controls/ContentControl.cs
  74. 10
      src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs
  75. 2
      src/Perspex.Controls/IContentControl.cs
  76. 2
      src/Perspex.Controls/Image.cs
  77. 2
      src/Perspex.Controls/ItemsControl.cs
  78. 4
      src/Perspex.Controls/Panel.cs
  79. 8
      src/Perspex.Controls/Perspex.Controls.csproj
  80. 2
      src/Perspex.Controls/Presenters/TextPresenter.cs
  81. 2
      src/Perspex.Controls/Primitives/AccessText.cs
  82. 2
      src/Perspex.Controls/Primitives/SelectingItemsControl.cs
  83. 1
      src/Perspex.Controls/Properties/AssemblyInfo.cs
  84. 2
      src/Perspex.Controls/Shapes/Shape.cs
  85. 2
      src/Perspex.Controls/Templates/DataTemplateExtensions.cs
  86. 14
      src/Perspex.Controls/Templates/FuncDataTemplate.cs
  87. 10
      src/Perspex.Controls/Templates/FuncDataTemplate`1.cs
  88. 18
      src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs
  89. 18
      src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs
  90. 4
      src/Perspex.Controls/TextBlock.cs
  91. 2
      src/Perspex.Diagnostics/Views/LogicalTreeView.cs
  92. 2
      src/Perspex.Diagnostics/Views/VisualTreeView.cs
  93. 4
      src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs
  94. 2
      src/Perspex.HtmlRenderer/HtmlContainer.cs
  95. 2
      src/Perspex.HtmlRenderer/HtmlControl.cs
  96. 8
      src/Perspex.Input/IInputElement.cs
  97. 11
      src/Perspex.Input/InputElement.cs
  98. 59
      src/Perspex.Input/InputExtensions.cs
  99. 4
      src/Perspex.SceneGraph/IVisual.cs
  100. 2
      src/Perspex.SceneGraph/Media/Color.cs

2
.gitmodules

@ -7,4 +7,4 @@
branch = perspex-pcl
[submodule "src/Markup/Perspex.Markup.Xaml/OmniXAML"]
path = src/Markup/Perspex.Markup.Xaml/OmniXAML
url = https://github.com/SuperJMN/OmniXAML.git
url = https://github.com/Perspex/OmniXAML.git

21
Perspex.sln

@ -97,6 +97,12 @@ EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PlatformSupport", "src\Shared\PlatformSupport\PlatformSupport.shproj", "{E4D9629C-F168-4224-3F51-A5E482FFBC42}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.TinyWM", "src\Perspex.TinyWM\Perspex.TinyWM.csproj", "{1D36D6B3-2994-41C5-8330-19FBDEC5769B}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup", "src\Markup\Perspex.Markup\Perspex.Markup.csproj", "{6417E941-21BC-467B-A771-0DE389353CE6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Markup.UnitTests", "tests\Perspex.Markup.UnitTests\Perspex.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamlTestApplicationPcl", "samples\XamlTestApplicationPcl\XamlTestApplicationPcl.csproj", "{EA113F1A-D8D7-4142-9948-353270E7EBAE}"
EndProject
Global
@ -242,6 +248,18 @@ Global
{1D36D6B3-2994-41C5-8330-19FBDEC5769B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D36D6B3-2994-41C5-8330-19FBDEC5769B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D36D6B3-2994-41C5-8330-19FBDEC5769B}.Release|Any CPU.Build.0 = Release|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Release|Any CPU.Build.0 = Release|Any CPU
{8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8EF392D5-1416-45AA-9956-7CBBC3229E8A}.Release|Any CPU.Build.0 = Release|Any CPU
{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}.Release|Any CPU.Build.0 = Release|Any CPU
{EA113F1A-D8D7-4142-9948-353270E7EBAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA113F1A-D8D7-4142-9948-353270E7EBAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA113F1A-D8D7-4142-9948-353270E7EBAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -271,6 +289,9 @@ Global
{54F237D5-A70A-4752-9656-0C70B1A7B047} = {B9894058-278A-46B5-B6ED-AD613FCC03B3}
{FB05AC90-89BA-4F2F-A924-F37875FB547C} = {B9894058-278A-46B5-B6ED-AD613FCC03B3}
{E4D9629C-F168-4224-3F51-A5E482FFBC42} = {A689DEF5-D50F-4975-8B72-124C9EB54066}
{6417E941-21BC-467B-A771-0DE389353CE6} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{8EF392D5-1416-45AA-9956-7CBBC3229E8A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{EA113F1A-D8D7-4142-9948-353270E7EBAE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
EndGlobal

2
nuget/build-version.ps1

@ -28,6 +28,8 @@ Copy-Item ..\src\Perspex.Styling\bin\Release\Perspex.Styling.dll $lib
Copy-Item ..\src\Perspex.Styling\bin\Release\Perspex.Styling.xml $lib
Copy-Item ..\src\Perspex.Themes.Default\bin\Release\Perspex.Themes.Default.dll $lib
Copy-Item ..\src\Perspex.Themes.Default\bin\Release\Perspex.Themes.Default.xml $lib
Copy-Item ..\src\Markup\Perspex.Markup\bin\Release\Perspex.Markup.dll $lib
Copy-Item ..\src\Markup\Perspex.Markup\bin\Release\Perspex.Markup.xml $lib
Copy-Item ..\src\Markup\Perspex.Markup.Xaml\bin\Release\Perspex.Markup.Xaml.dll $lib
Copy-Item ..\src\Markup\Perspex.Markup.Xaml\bin\Release\Perspex.Markup.Xaml.xml $lib
Copy-Item ..\src\Perspex.HtmlRenderer\bin\Release\Perspex.HtmlRenderer.dll $lib

6
samples/BindingTest/App.config

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
</startup>
</configuration>

40
samples/BindingTest/App.cs

@ -0,0 +1,40 @@
using System;
using Perspex;
using Perspex.Controls;
using Perspex.Diagnostics;
using Perspex.Themes.Default;
using Serilog;
using Serilog.Filters;
namespace BindingTest
{
public class App : Application
{
public App()
{
RegisterServices();
InitializeSubsystems((int)Environment.OSVersion.Platform);
Styles = new DefaultTheme();
Log.Logger = new LoggerConfiguration()
.Filter.ByIncludingOnly(Matching.WithProperty("Area", "Property"))
.Filter.ByIncludingOnly(Matching.WithProperty("Property", "Text"))
.MinimumLevel.Verbose()
.WriteTo.Trace(outputTemplate: "[{Id:X8}] [{SourceContext}] {Message}")
.CreateLogger();
}
public static void AttachDevTools(Window window)
{
DevTools.Attach(window);
}
private static void Main()
{
var app = new App();
var window = new MainWindow();
window.Show();
app.Run(window);
}
}
}

167
samples/BindingTest/BindingTest.csproj

@ -0,0 +1,167 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BindingTest</RootNamespace>
<AssemblyName>BindingTest</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\..\packages\Serilog.1.5.9\lib\net45\Serilog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Serilog.FullNetFx, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
<HintPath>..\..\packages\Serilog.1.5.9\lib\net45\Serilog.FullNetFx.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Splat, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.cs" />
<Compile Include="MainWindow.paml.cs">
<DependentUpon>MainWindow.paml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModels\MainWindowViewModel.cs" />
<Compile Include="ViewModels\TestItem.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<EmbeddedResource Include="MainWindow.paml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</EmbeddedResource>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Markup\Perspex.Markup.Xaml\Perspex.Markup.Xaml.csproj">
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Perspex.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Perspex.Markup\Perspex.Markup.csproj">
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Perspex.Markup</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Animation\Perspex.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Application\Perspex.Application.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Perspex.Application</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Controls\Perspex.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Perspex.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Diagnostics\Perspex.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Perspex.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Input\Perspex.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Perspex.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.ReactiveUI\Perspex.ReactiveUI.csproj">
<Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
<Name>Perspex.ReactiveUI</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Styling\Perspex.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Perspex.Themes.Default\Perspex.Themes.Default.csproj">
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Perspex.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Perspex.Direct2D1\Perspex.Direct2D1.csproj">
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
<Name>Perspex.Direct2D1</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Perspex.Win32\Perspex.Win32.csproj">
<Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
<Name>Perspex.Win32</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

22
samples/BindingTest/MainWindow.paml

@ -0,0 +1,22 @@
<Window xmlns="https://github.com/perspex">
<StackPanel Orientation="Horizontal">
<StackPanel Margin="18" Gap="4" Width="200">
<TextBlock FontSize="16" Text="Simple Bindings"/>
<TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding StringValue}"/>
<TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding StringValue, Mode=OneWay}"/>
<TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding StringValue, Mode=OneTime}"/>
<TextBox Watermark="One Way To Source" UseFloatingWatermark="True" Text="{Binding StringValue, Mode=OneWayToSource}"/>
</StackPanel>
<StackPanel Margin="18" Gap="4" Width="200">
<TextBlock FontSize="16" Text="Collection Bindings"/>
<TextBox Watermark="Items[1].StringValue" UseFloatingWatermark="True" Text="{Binding Items[1].StringValue}"/>
<Button Command="{Binding ShuffleItems}">Shuffle</Button>
</StackPanel>
<StackPanel Margin="18" Gap="4" Width="200">
<TextBlock FontSize="16" Text="Negated Bindings"/>
<TextBox Watermark="Boolean String" UseFloatingWatermark="True" Text="{Binding BooleanString}"/>
<CheckBox IsChecked="{Binding !BooleanString}">!BooleanString</CheckBox>
<CheckBox IsChecked="{Binding !!BooleanString}">!!BooleanString</CheckBox>
</StackPanel>
</StackPanel>
</Window>

21
samples/BindingTest/MainWindow.paml.cs

@ -0,0 +1,21 @@
using BindingTest.ViewModels;
using Perspex.Controls;
using Perspex.Markup.Xaml;
namespace BindingTest
{
public class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
this.DataContext = new MainWindowViewModel();
App.AttachDevTools(this);
}
private void InitializeComponent()
{
PerspexXamlLoader.Load(this);
}
}
}

36
samples/BindingTest/Properties/AssemblyInfo.cs

@ -0,0 +1,36 @@
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("BindingTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BindingTest")]
[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("08b3e6b9-1cd5-443c-9f61-6d49d1c5f162")]
// 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")]

44
samples/BindingTest/ViewModels/MainWindowViewModel.cs

@ -0,0 +1,44 @@
using System;
using System.Collections.ObjectModel;
using ReactiveUI;
namespace BindingTest.ViewModels
{
public class MainWindowViewModel : ReactiveObject
{
private string _booleanString = "True";
private string _stringValue = "Simple Binding";
public MainWindowViewModel()
{
Items = new ObservableCollection<TestItem>
{
new TestItem { StringValue = "Foo" },
new TestItem { StringValue = "Bar" },
new TestItem { StringValue = "Baz" },
};
ShuffleItems = ReactiveCommand.Create();
ShuffleItems.Subscribe(_ =>
{
var r = new Random();
Items.Move(r.Next(Items.Count), 1);
});
}
public ObservableCollection<TestItem> Items { get; }
public ReactiveCommand<object> ShuffleItems { get; }
public string BooleanString
{
get { return _booleanString; }
set { this.RaiseAndSetIfChanged(ref _booleanString, value); }
}
public string StringValue
{
get { return _stringValue; }
set { this.RaiseAndSetIfChanged(ref _stringValue, value); }
}
}
}

15
samples/BindingTest/ViewModels/TestItem.cs

@ -0,0 +1,15 @@
using ReactiveUI;
namespace BindingTest.ViewModels
{
public class TestItem : ReactiveObject
{
private string stringValue = "String Value";
public string StringValue
{
get { return stringValue; }
set { this.RaiseAndSetIfChanged(ref this.stringValue, value); }
}
}
}

10
samples/BindingTest/packages.config

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.2.5" targetFramework="net46" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net46" />
<package id="Rx-Linq" version="2.2.5" targetFramework="net46" />
<package id="Rx-Main" version="2.2.5" targetFramework="net46" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="net46" />
<package id="Serilog" version="1.5.9" targetFramework="net46" />
<package id="Splat" version="1.6.2" targetFramework="net46" />
</packages>

2
samples/TestApplication/GalleryStyle.cs

@ -61,7 +61,7 @@ namespace TestApplication
{
DataTemplates = new DataTemplates
{
new DataTemplate<string>(x => new Border
new FuncDataTemplate<string>(x => new Border
{
[~Border.BackgroundProperty] = control[~TemplatedControl.BackgroundProperty],
Padding = new Thickness(10),

4
samples/TestApplication/Program.cs

@ -86,7 +86,7 @@ namespace TestApplication
{
DataTemplates = new DataTemplates
{
new TreeDataTemplate<Node>(
new FuncTreeDataTemplate<Node>(
x => new TextBlock { Text = x.Name },
x => x.Children,
x => true),
@ -405,7 +405,7 @@ namespace TestApplication
Margin = new Thickness(10),
DataTemplates = new DataTemplates
{
new DataTemplate<Item>(x =>
new FuncDataTemplate<Item>(x =>
new StackPanel
{
Gap = 4,

4
samples/XamlTestApplication/Program.cs

@ -23,10 +23,10 @@ namespace XamlTestApplication
{
};
var window = new MainWindow();
window.Show();
Application.Current.Run(window);
}
}
}
}

58
samples/XamlTestApplicationPcl/ViewModels/MainWindowViewModel.cs

@ -0,0 +1,58 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
namespace XamlTestApplication.ViewModels
{
public class MainWindowViewModel
{
public MainWindowViewModel()
{
Items = new List<TestItem>();
for (int i = 0; i < 10; ++i)
{
Items.Add(new TestItem($"Item {i}", $"Item {i} Value"));
}
Nodes = new List<TestNode>
{
new TestNode
{
Header = "Root",
SubHeader = "Root Item",
Children = new[]
{
new TestNode
{
Header = "Child 1",
SubHeader = "Child 1 Value",
},
new TestNode
{
Header = "Child 2",
SubHeader = "Child 2 Value",
Children = new[]
{
new TestNode
{
Header = "Grandchild",
SubHeader = "Grandchild Value",
},
new TestNode
{
Header = "Grandmaster Flash",
SubHeader = "White Lines",
},
}
},
}
}
};
}
public List<TestItem> Items { get; }
public List<TestNode> Nodes { get; }
}
}

17
samples/XamlTestApplicationPcl/ViewModels/TestItem.cs

@ -0,0 +1,17 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace XamlTestApplication.ViewModels
{
public class TestItem
{
public TestItem(string header, string subheader)
{
Header = header;
SubHeader = subheader;
}
public string Header { get; }
public string SubHeader { get; }
}
}

14
samples/XamlTestApplicationPcl/ViewModels/TestNode.cs

@ -0,0 +1,14 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
namespace XamlTestApplication.ViewModels
{
public class TestNode
{
public string Header { get; set; }
public string SubHeader { get; set; }
public IEnumerable<TestNode> Children { get; set; }
}
}

9
samples/XamlTestApplicationPcl/Views/MainWindow.cs

@ -1,15 +1,10 @@
// Copyright (c) The Perspex 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.Globalization;
using System.IO;
using System.Reflection;
using System.Resources;
using OmniXaml;
using Perspex.Controls;
using Perspex.Diagnostics;
using Perspex.Markup.Xaml;
using XamlTestApplication.ViewModels;
namespace XamlTestApplication.Views
{
@ -18,7 +13,7 @@ namespace XamlTestApplication.Views
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
DevTools.Attach(this);
}

47
samples/XamlTestApplicationPcl/Views/MainWindow.paml

@ -1,6 +1,7 @@
<Window x:Class="XamlTestApplication.MainWindow"
xmlns="https://github.com/perspex"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:XamlTestApplication.ViewModels;assembly=XamlTestApplicationPcl"
Title="Perspex Test Application" Height="350" Width="525" SizeToContent="WidthAndHeight" >
<Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="*,*">
<TabControl Grid.Row="1" Grid.ColumnSpan="2" Padding="5">
@ -48,30 +49,36 @@
</TabItem>
<TabItem Header="Lists">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<ListBox SelectedIndex="0">
<ListBoxItem>
<StackPanel>
<TextBlock Text="Item 1" FontSize="24" />
<TextBlock Text="Item 1 Value" />
</StackPanel>
</ListBoxItem>
<ListBoxItem>
<StackPanel>
<TextBlock Text="Item 2" FontSize="24" />
<TextBlock Text="Item 2 Value" />
</StackPanel>
</ListBoxItem>
</ListBox>
<DropDown VerticalAlignment="Center" SelectedIndex="0">
<ListBox Items="{Binding Items}">
<ListBox.DataTemplates>
<DataTemplate DataType="vm:TestItem">
<StackPanel>
<TextBlock Text="Item 1" FontSize="24" />
<TextBlock Text="Item 1 Value" />
<TextBlock Text="{Binding Header}" FontSize="24"/>
<TextBlock Text="{Binding SubHeader}"/>
</StackPanel>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
<DropDown VerticalAlignment="Center" SelectedIndex="0">
<StackPanel>
<TextBlock Text="Item 1" FontSize="24" />
<TextBlock Text="Item 1 Value" />
</StackPanel>
<StackPanel>
<TextBlock Text="Item 2" FontSize="24" />
<TextBlock Text="Item 2 Value" />
</StackPanel>
</DropDown>
<TreeView Items="{Binding Nodes}">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TestNode" ItemsSource="{Binding Children}">
<StackPanel>
<TextBlock Text="Item 2" FontSize="24" />
<TextBlock Text="Item 2 Value" />
<TextBlock Text="{Binding Header}" FontSize="24"/>
<TextBlock Text="{Binding SubHeader}"/>
</StackPanel>
</DropDown>
</TreeDataTemplate>
</TreeView.DataTemplates>
</TreeView>
</StackPanel>
</TabItem>
<TabItem Header="Layout">

3
samples/XamlTestApplicationPcl/XamlTestApplicationPcl.csproj

@ -41,6 +41,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModels\MainWindowViewModel.cs" />
<Compile Include="ViewModels\TestItem.cs" />
<Compile Include="ViewModels\TestNode.cs" />
<Compile Include="Views\MainWindow.cs" />
<Compile Include="XamlTestApp.cs" />
</ItemGroup>

72
src/Gtk/Perspex.Cairo/Media/DrawingContext.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
@ -16,7 +17,7 @@ namespace Perspex.Cairo.Media
/// <summary>
/// Draws using Direct2D1.
/// </summary>
public class DrawingContext : IDrawingContext, IDisposable
public class DrawingContext : IDrawingContextImpl, IDisposable
{
/// <summary>
/// The cairo context.
@ -30,7 +31,6 @@ namespace Perspex.Cairo.Media
public DrawingContext(Cairo.Surface surface)
{
_context = new Cairo.Context(surface);
CurrentTransform = Matrix.Identity;
}
/// <summary>
@ -40,15 +40,23 @@ namespace Perspex.Cairo.Media
public DrawingContext(Gdk.Drawable drawable)
{
_context = Gdk.CairoHelper.Create(drawable);
CurrentTransform = Matrix.Identity;
}
private Matrix _transform = Matrix.Identity;
/// <summary>
/// Gets the current transform of the drawing context.
/// </summary>
public Matrix CurrentTransform
public Matrix Transform
{
get; }
get { return _transform; }
set
{
_transform = value;
_context.Matrix = value.ToCairo();
}
}
/// <summary>
/// Ends a draw operation.
@ -131,28 +139,30 @@ namespace Perspex.Cairo.Media
{
var impl = geometry.PlatformImpl as StreamGeometryImpl;
using (var pop = PushTransform(impl.Transform))
var oldMatrix = Transform;
Transform = impl.Transform * Transform;
if (brush != null)
{
_context.AppendPath(impl.Path);
if (brush != null)
using (var b = SetBrush(brush, geometry.Bounds.Size))
{
using (var b = SetBrush(brush, geometry.Bounds.Size))
{
if (pen != null)
_context.FillPreserve();
else
_context.Fill();
}
if (pen != null)
_context.FillPreserve();
else
_context.Fill();
}
}
Transform = oldMatrix;
if (pen != null)
{
using (var p = SetPen(pen, geometry.Bounds.Size))
{
_context.Stroke();
}
_context.AppendPath(impl.Path);
using (var p = SetPen(pen, geometry.Bounds.Size))
{
_context.Stroke();
}
}
}
@ -208,31 +218,37 @@ namespace Perspex.Cairo.Media
/// </summary>
/// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns>
public IDisposable PushClip(Rect clip)
public void PushClip(Rect clip)
{
_context.Save();
_context.Rectangle(clip.ToCairo());
_context.Clip();
}
return Disposable.Create(() => _context.Restore());
public void PopClip()
{
_context.Restore();
}
readonly Stack<double> _opacityStack = new Stack<double>();
/// <summary>
/// Pushes an opacity value.
/// </summary>
/// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns>
public IDisposable PushOpacity(double opacity)
public void PushOpacity(double opacity)
{
var tmp = opacityOverride;
_opacityStack.Push(opacityOverride);
if (opacity < 1.0f)
opacityOverride = opacity;
opacityOverride *= opacity;
return Disposable.Create(() =>
{
opacityOverride = tmp;
});
}
public void PopOpacity()
{
opacityOverride = _opacityStack.Pop();
}
/// <summary>

2
src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs

@ -39,7 +39,7 @@ namespace Perspex.Cairo.Media.Imaging
Surface.WriteToPng(fileName);
}
public IDrawingContext CreateDrawingContext()
public Perspex.Media.DrawingContext CreateDrawingContext()
{
return _renderTarget.CreateDrawingContext();
}

2
src/Gtk/Perspex.Cairo/Media/TileBrushes.cs

@ -113,7 +113,7 @@ namespace Perspex.Cairo.Media
out drawRect);
using (ctx.PushClip(drawRect))
using (ctx.PushTransform(transform))
using (ctx.PushPostTransform(transform))
{
ctx.Render(visual);
}

12
src/Gtk/Perspex.Cairo/RenderTarget.cs

@ -7,6 +7,7 @@ using Perspex.Cairo.Media;
using Perspex.Media;
using Perspex.Platform;
using Perspex.Rendering;
using DrawingContext = Perspex.Media.DrawingContext;
namespace Perspex.Cairo
{
@ -50,12 +51,13 @@ namespace Perspex.Cairo
/// <summary>
/// Creates a cairo surface that targets a platform-specific resource.
/// </summary>
/// <returns>A surface wrapped in an <see cref="IDrawingContext"/>.</returns>
public IDrawingContext CreateDrawingContext()
/// <returns>A surface wrapped in an <see cref="Perspex.Media.DrawingContext"/>.</returns>
public DrawingContext CreateDrawingContext()
{
if(_surface != null)
return new DrawingContext(_surface);
return new DrawingContext(_window.GdkWindow);
var ctx = _surface != null
? new Media.DrawingContext(_surface)
: new Media.DrawingContext(_window.GdkWindow);
return new DrawingContext(ctx);
}
public void Dispose() => _surface?.Dispose();

2
src/Markup/Perspex.Markup.Xaml/DataBinding/SourceBindingEndpoint.cs → src/Markup/Perspex.Markup.Xaml/Binding/SourceBindingEndpoint.cs

@ -4,7 +4,7 @@
using System;
using System.ComponentModel;
namespace Perspex.Markup.Xaml.DataBinding
namespace Perspex.Markup.Xaml.Binding
{
public class SourceBindingEndpoint
{

2
src/Markup/Perspex.Markup.Xaml/DataBinding/TargetBindingEndpoint.cs → src/Markup/Perspex.Markup.Xaml/Binding/TargetBindingEndpoint.cs

@ -1,7 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Xaml.DataBinding
namespace Perspex.Markup.Xaml.Binding
{
public class TargetBindingEndpoint
{

96
src/Markup/Perspex.Markup.Xaml/Binding/XamlBinding.cs

@ -0,0 +1,96 @@
// Copyright (c) The Perspex 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.Reactive.Linq;
using System.Reactive.Subjects;
using OmniXaml.TypeConversion;
using Perspex.Controls;
using Perspex.Markup.Binding;
namespace Perspex.Markup.Xaml.Binding
{
public class XamlBinding
{
private readonly ITypeConverterProvider _typeConverterProvider;
public XamlBinding()
{
}
public XamlBinding(ITypeConverterProvider typeConverterProvider)
{
_typeConverterProvider = typeConverterProvider;
}
public string SourcePropertyPath { get; set; }
public BindingMode BindingMode { get; set; }
public void Bind(IObservablePropertyBag instance, PerspexProperty property)
{
var subject = new ExpressionSubject(CreateExpressionObserver(instance, property));
if (subject != null)
{
Bind(instance, property, subject);
}
}
public ExpressionObserver CreateExpressionObserver(
IObservablePropertyBag instance,
PerspexProperty property)
{
IObservable<object> dataContext = null;
if (property != Control.DataContextProperty)
{
dataContext = instance.GetObservable(Control.DataContextProperty);
}
else
{
var parent = instance.InheritanceParent as IObservablePropertyBag;
if (parent != null)
{
dataContext = parent.GetObservable(Control.DataContextProperty);
}
}
if (dataContext != null)
{
var result = new ExpressionObserver(null, SourcePropertyPath);
dataContext.Subscribe(x => result.Root = x);
return result;
}
return null;
}
internal void Bind(IObservablePropertyBag target, PerspexProperty property, ISubject<object> subject)
{
var mode = BindingMode == BindingMode.Default ?
property.DefaultBindingMode : BindingMode;
switch (mode)
{
case BindingMode.Default:
case BindingMode.OneWay:
target.Bind(property, subject);
break;
case BindingMode.TwoWay:
target.BindTwoWay(property, subject);
break;
case BindingMode.OneTime:
target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
{
subject.Take(1).Subscribe(x => target.SetValue(property, x));
});
break;
case BindingMode.OneWayToSource:
target.GetObservable(property).Subscribe(subject);
break;
}
}
}
}

21
src/Markup/Perspex.Markup.Xaml/Binding/XamlBindingDefinition.cs

@ -0,0 +1,21 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Controls;
namespace Perspex.Markup.Xaml.Binding
{
public class XamlBindingDefinition
{
public XamlBindingDefinition(
string sourcePropertyPath,
BindingMode bindingMode)
{
SourcePropertyPath = sourcePropertyPath;
BindingMode = bindingMode;
}
public string SourcePropertyPath { get; }
public BindingMode BindingMode { get; }
}
}

3
src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs

@ -15,7 +15,8 @@ namespace Perspex.Markup.Xaml.Context
public PerspexObjectAssembler(IWiringContext wiringContext, ObjectAssemblerSettings objectAssemblerSettings = null)
{
var mapping = new DeferredLoaderMapping();
mapping.Map<XamlDataTemplate>(template => template.Content, new TemplateLoader());
mapping.Map<DataTemplate>(template => template.Content, new TemplateLoader());
mapping.Map<TreeDataTemplate>(template => template.Content, new TemplateLoader());
var assembler = new ObjectAssembler(wiringContext, new TopDownValueContext(), objectAssemblerSettings);
_objectAssembler = new TemplateHostingObjectAssembler(assembler, mapping);

9
src/Markup/Perspex.Markup.Xaml/Context/PerspexTypeRepository.cs

@ -5,28 +5,25 @@ using System;
using Glass;
using OmniXaml;
using OmniXaml.Typing;
using Perspex.Markup.Xaml.DataBinding;
using Perspex.Markup.Xaml.Binding;
namespace Perspex.Markup.Xaml.Context
{
public class PerspexTypeRepository : XamlTypeRepository
{
private readonly ITypeFactory _typeFactory;
private readonly IPerspexPropertyBinder _propertyBinder;
public PerspexTypeRepository(IXamlNamespaceRegistry xamlNamespaceRegistry,
ITypeFactory typeFactory,
ITypeFeatureProvider featureProvider,
IPerspexPropertyBinder propertyBinder) : base(xamlNamespaceRegistry, typeFactory, featureProvider)
ITypeFeatureProvider featureProvider) : base(xamlNamespaceRegistry, typeFactory, featureProvider)
{
_typeFactory = typeFactory;
_propertyBinder = propertyBinder;
}
public override XamlType GetXamlType(Type type)
{
Guard.ThrowIfNull(type, nameof(type));
return new PerspexXamlType(type, this, _typeFactory, FeatureProvider, _propertyBinder);
return new PerspexXamlType(type, this, _typeFactory, FeatureProvider);
}
}
}

8
src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs

@ -15,7 +15,7 @@ using Perspex.Controls;
using Perspex.Input;
using Perspex.Markup.Xaml.Templates;
using Perspex.Markup.Xaml.Converters;
using Perspex.Markup.Xaml.DataBinding;
using Perspex.Markup.Xaml.Binding;
using Perspex.Markup.Xaml.MarkupExtensions;
using Perspex.Media;
using Perspex.Media.Imaging;
@ -43,8 +43,7 @@ namespace Perspex.Markup.Xaml.Context
private static ITypeContext CreateTypeContext(ITypeFactory typeFactory, TypeFeatureProvider featureProvider)
{
var xamlNamespaceRegistry = CreateXamlNamespaceRegistry();
var perspexPropertyBinder = new PerspexPropertyBinder(featureProvider.ConverterProvider);
var typeRepository = new PerspexTypeRepository(xamlNamespaceRegistry, typeFactory, featureProvider, perspexPropertyBinder);
var typeRepository = new PerspexTypeRepository(xamlNamespaceRegistry, typeFactory, featureProvider);
typeRepository.RegisterMetadata(new Metadata<Setter>().WithMemberDependency(x => x.Value, x => x.Property));
typeRepository.RegisterMetadata(
@ -116,6 +115,7 @@ namespace Perspex.Markup.Xaml.Context
var contentProperties = new Collection<ContentPropertyDefinition>
{
new ContentPropertyDefinition(typeof(ContentControl), "Content"),
new ContentPropertyDefinition(typeof(DataTemplate), "Content"),
new ContentPropertyDefinition(typeof(Decorator), "Child"),
new ContentPropertyDefinition(typeof(ItemsControl), "Items"),
new ContentPropertyDefinition(typeof(GradientBrush), "GradientStops"),
@ -123,7 +123,7 @@ namespace Perspex.Markup.Xaml.Context
new ContentPropertyDefinition(typeof(Style), "Setters"),
new ContentPropertyDefinition(typeof(TextBlock), "Text"),
new ContentPropertyDefinition(typeof(TextBox), "Text"),
new ContentPropertyDefinition(typeof(XamlDataTemplate), "Content"),
new ContentPropertyDefinition(typeof(TreeDataTemplate), "Content"),
};
contentPropertyProvider.AddAll(contentProperties);

10
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMember.cs

@ -1,7 +1,7 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Markup.Xaml.DataBinding;
using Perspex.Markup.Xaml.Binding;
using OmniXaml;
using OmniXaml.Typing;
@ -9,21 +9,17 @@ namespace Perspex.Markup.Xaml.Context
{
public class PerspexXamlMember : XamlMember
{
private readonly IPerspexPropertyBinder _propertyBinder;
public PerspexXamlMember(string name,
XamlType owner,
IXamlTypeRepository xamlTypeRepository,
ITypeFeatureProvider featureProvider,
IPerspexPropertyBinder propertyBinder)
ITypeFeatureProvider featureProvider)
: base(name, owner, xamlTypeRepository, featureProvider)
{
_propertyBinder = propertyBinder;
}
protected override IXamlMemberValuePlugin LookupXamlMemberValueConnector()
{
return new PerspexXamlMemberValuePlugin(this, _propertyBinder);
return new PerspexXamlMemberValuePlugin(this);
}
public override string ToString()

61
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs

@ -2,12 +2,14 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Glass;
using OmniXaml.ObjectAssembler;
using OmniXaml.Typing;
using Perspex.Controls;
using Perspex.Markup.Xaml.DataBinding;
using Perspex.Markup.Xaml.Binding;
using Perspex.Styling;
namespace Perspex.Markup.Xaml.Context
@ -15,19 +17,18 @@ namespace Perspex.Markup.Xaml.Context
public class PerspexXamlMemberValuePlugin : MemberValuePlugin
{
private readonly XamlMember _xamlMember;
private readonly IPerspexPropertyBinder _propertyBinder;
public PerspexXamlMemberValuePlugin(XamlMember xamlMember, IPerspexPropertyBinder propertyBinder) : base(xamlMember)
public PerspexXamlMemberValuePlugin(XamlMember xamlMember)
: base(xamlMember)
{
_xamlMember = xamlMember;
_propertyBinder = propertyBinder;
}
public override void SetValue(object instance, object value)
{
if (value is XamlBindingDefinition)
{
HandleXamlBindingDefinition((XamlBindingDefinition)value);
HandleXamlBindingDefinition(instance, (XamlBindingDefinition)value);
}
else if (IsPerspexProperty)
{
@ -54,22 +55,48 @@ namespace Perspex.Markup.Xaml.Context
po.SetValue(pp, value);
}
private void HandleXamlBindingDefinition(XamlBindingDefinition xamlBindingDefinition)
private void HandleXamlBindingDefinition(object instance, XamlBindingDefinition def)
{
PerspexObject subjectObject = xamlBindingDefinition.Target;
_propertyBinder.Create(xamlBindingDefinition);
if (_xamlMember.XamlType.UnderlyingType == typeof(XamlBindingDefinition))
{
// TODO: This should search base classes.
var property = instance.GetType().GetTypeInfo().GetDeclaredProperty(_xamlMember.Name);
var observableForDataContext = subjectObject.GetObservable(Control.DataContextProperty);
observableForDataContext.Where(o => o != null).Subscribe(_ => BindToDataContextWhenItsSet(xamlBindingDefinition));
}
if (property == null || !property.CanWrite)
{
throw new InvalidOperationException(
$"Cannot assign to '{_xamlMember.Name}' on '{instance.GetType()}");
}
private void BindToDataContextWhenItsSet(XamlBindingDefinition definition)
{
var target = definition.Target;
var dataContext = target.DataContext;
property.SetValue(instance, def);
}
else
{
var perspexObject = instance as PerspexObject;
var binding = _propertyBinder.GetBinding(target, definition.TargetProperty);
binding.BindToDataContext(dataContext);
if (perspexObject == null)
{
throw new InvalidOperationException(
$"Cannot bind to an object of type '{instance.GetType()}");
}
var property = perspexObject.GetRegisteredProperties()
.FirstOrDefault(x => x.Name == _xamlMember.Name);
if (property == null)
{
throw new InvalidOperationException(
$"Cannot find '{_xamlMember.Name}' on '{instance.GetType()}");
}
var binding = new XamlBinding
{
BindingMode = def.BindingMode,
SourcePropertyPath = def.SourcePropertyPath,
};
binding.Bind(perspexObject, property);
}
}
// ReSharper disable once MemberCanBePrivate.Global

12
src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs

@ -4,28 +4,22 @@
using System;
using OmniXaml;
using OmniXaml.Typing;
using Perspex.Markup.Xaml.DataBinding;
using Perspex.Markup.Xaml.Binding;
namespace Perspex.Markup.Xaml.Context
{
public class PerspexXamlType : XamlType
{
private readonly IPerspexPropertyBinder _propertyBinder;
public PerspexXamlType(Type type,
IXamlTypeRepository typeRepository,
ITypeFactory typeFactory,
ITypeFeatureProvider featureProvider,
IPerspexPropertyBinder propertyBinder) : base(type, typeRepository, typeFactory, featureProvider)
ITypeFeatureProvider featureProvider) : base(type, typeRepository, typeFactory, featureProvider)
{
_propertyBinder = propertyBinder;
}
protected IPerspexPropertyBinder PropertyBinder => _propertyBinder;
protected override XamlMember LookupMember(string name)
{
return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider, _propertyBinder);
return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider);
}
public override string ToString()

109
src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/ObservablePropertyBranch.cs

@ -1,109 +0,0 @@
// Copyright (c) The Perspex 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.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Glass;
namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking
{
public class ObservablePropertyBranch
{
private readonly object _instance;
private readonly PropertyPath _propertyPath;
private readonly PropertyMountPoint _mountPoint;
public ObservablePropertyBranch(object instance, PropertyPath propertyPath)
{
Guard.ThrowIfNull(instance, nameof(instance));
Guard.ThrowIfNull(propertyPath, nameof(propertyPath));
_instance = instance;
_propertyPath = propertyPath;
_mountPoint = new PropertyMountPoint(instance, propertyPath);
var properties = GetPropertiesThatRaiseNotifications();
Values = CreateUnifiedObservableFromNodes(properties);
}
public IObservable<object> Values { get; private set; }
private IObservable<object> CreateUnifiedObservableFromNodes(IEnumerable<PropertyDefinition> subscriptions)
{
return subscriptions.Select(GetObservableFromProperty).Merge();
}
private IObservable<object> GetObservableFromProperty(PropertyDefinition subscription)
{
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
parentOnPropertyChanged => subscription.Parent.PropertyChanged += parentOnPropertyChanged,
parentOnPropertyChanged => subscription.Parent.PropertyChanged -= parentOnPropertyChanged)
.Where(pattern => pattern.EventArgs.PropertyName == subscription.PropertyName)
.Select(pattern => _mountPoint.Value);
}
private IEnumerable<PropertyDefinition> GetPropertiesThatRaiseNotifications()
{
return GetSubscriptionsRecursive(_instance, _propertyPath, 0);
}
private IEnumerable<PropertyDefinition> GetSubscriptionsRecursive(object current, PropertyPath propertyPath, int i)
{
var subscriptions = new List<PropertyDefinition>();
var inpc = current as INotifyPropertyChanged;
if (inpc == null)
{
return subscriptions;
}
var nextPropertyName = propertyPath.Chunks[i];
subscriptions.Add(new PropertyDefinition(inpc, nextPropertyName));
if (i < _propertyPath.Chunks.Length)
{
var currentObjectTypeInfo = current.GetType().GetTypeInfo();
var nextProperty = currentObjectTypeInfo.GetDeclaredProperty(nextPropertyName);
var nextInstance = nextProperty.GetValue(current);
if (i < _propertyPath.Chunks.Length - 1)
{
subscriptions.AddRange(GetSubscriptionsRecursive(nextInstance, propertyPath, i + 1));
}
}
return subscriptions;
}
public object Value
{
get
{
return _mountPoint.Value;
}
set
{
_mountPoint.Value = value;
}
}
public Type Type => _mountPoint.ProperyType;
private class PropertyDefinition
{
public PropertyDefinition(INotifyPropertyChanged parent, string propertyName)
{
Parent = parent;
PropertyName = propertyName;
}
public INotifyPropertyChanged Parent { get; }
public string PropertyName { get; }
}
}
}

52
src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyMountPoint.cs

@ -1,52 +0,0 @@
// Copyright (c) The Perspex 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.Reflection;
using Glass;
namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking
{
public class PropertyMountPoint
{
private readonly TargettedProperty _referencedTargettedProperty;
public PropertyMountPoint(object origin, PropertyPath propertyPath)
{
Guard.ThrowIfNull(origin, nameof(origin));
Guard.ThrowIfNull(propertyPath, nameof(propertyPath));
_referencedTargettedProperty = GetReferencedPropertyInfo(origin, propertyPath, 0);
}
private static TargettedProperty GetReferencedPropertyInfo(object current, PropertyPath propertyPath, int level)
{
var typeInfo = current.GetType().GetTypeInfo();
var leftPropertyInfo = typeInfo.GetDeclaredProperty(propertyPath.Chunks[level]);
if (level == propertyPath.Chunks.Length - 1)
{
return new TargettedProperty(current, leftPropertyInfo);
}
var nextInstance = leftPropertyInfo.GetValue(current);
return GetReferencedPropertyInfo(nextInstance, propertyPath, level + 1);
}
public object Value
{
get
{
return _referencedTargettedProperty.Value;
}
set
{
_referencedTargettedProperty.Value = value;
}
}
public Type ProperyType => _referencedTargettedProperty.PropertyType;
}
}

36
src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/PropertyPath.cs

@ -1,36 +0,0 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking
{
public class PropertyPath
{
private string[] _chunks;
private PropertyPath(PropertyPath propertyPath)
{
_chunks = propertyPath.Chunks;
}
public PropertyPath(string path)
{
_chunks = path.Split('.');
}
public string[] Chunks
{
get { return _chunks; }
set { _chunks = value; }
}
public PropertyPath Clone()
{
return new PropertyPath(this);
}
public override string ToString()
{
return string.Join(".", _chunks);
}
}
}

41
src/Markup/Perspex.Markup.Xaml/DataBinding/ChangeTracking/TargettedProperty.cs

@ -1,41 +0,0 @@
// Copyright (c) The Perspex 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.Reflection;
using Glass;
namespace Perspex.Markup.Xaml.DataBinding.ChangeTracking
{
internal class TargettedProperty
{
private readonly object _instance;
private readonly PropertyInfo _propertyInfo;
public TargettedProperty(object instance, PropertyInfo propertyInfo)
{
Guard.ThrowIfNull(instance, nameof(instance));
Guard.ThrowIfNull(propertyInfo, nameof(propertyInfo));
_instance = instance;
_propertyInfo = propertyInfo;
}
public object Value
{
get
{
return _propertyInfo.GetValue(_instance);
}
set
{
_propertyInfo.SetValue(_instance, value);
}
}
public Type PropertyType => _propertyInfo.PropertyType;
public string Name => _propertyInfo.Name;
}
}

165
src/Markup/Perspex.Markup.Xaml/DataBinding/DataContextChangeSynchronizer.cs

@ -1,165 +0,0 @@
// Copyright (c) The Perspex 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.Globalization;
using System.Reactive.Linq;
using System.Reflection;
using Glass;
using OmniXaml.TypeConversion;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
namespace Perspex.Markup.Xaml.DataBinding
{
public class DataContextChangeSynchronizer
{
private readonly BindingTarget _bindingTarget;
private readonly ITypeConverter _targetPropertyTypeConverter;
private readonly TargetBindingEndpoint _bindingEndpoint;
private readonly ObservablePropertyBranch _sourceEndpoint;
public DataContextChangeSynchronizer(BindingSource bindingSource, BindingTarget bindingTarget, ITypeConverterProvider typeConverterProvider)
{
_bindingTarget = bindingTarget;
Guard.ThrowIfNull(bindingTarget.Object, nameof(bindingTarget.Object));
Guard.ThrowIfNull(bindingTarget.Property, nameof(bindingTarget.Property));
Guard.ThrowIfNull(bindingSource.SourcePropertyPath, nameof(bindingSource.SourcePropertyPath));
Guard.ThrowIfNull(bindingSource.Source, nameof(bindingSource.Source));
Guard.ThrowIfNull(typeConverterProvider, nameof(typeConverterProvider));
_bindingEndpoint = new TargetBindingEndpoint(bindingTarget.Object, bindingTarget.Property);
_sourceEndpoint = new ObservablePropertyBranch(bindingSource.Source, bindingSource.SourcePropertyPath);
_targetPropertyTypeConverter = typeConverterProvider.GetTypeConverter(bindingTarget.Property.PropertyType);
}
public class BindingTarget
{
private readonly PerspexObject _obj;
private readonly PerspexProperty _property;
public BindingTarget(PerspexObject @object, PerspexProperty property)
{
_obj = @object;
_property = property;
}
public PerspexObject Object => _obj;
public PerspexProperty Property => _property;
public object Value
{
get { return _obj.GetValue(_property); }
set { _obj.SetValue(_property, value); }
}
}
public class BindingSource
{
private readonly PropertyPath _sourcePropertyPath;
private readonly object _source;
public BindingSource(PropertyPath sourcePropertyPath, object source)
{
_sourcePropertyPath = sourcePropertyPath;
_source = source;
}
public PropertyPath SourcePropertyPath => _sourcePropertyPath;
public object Source => _source;
}
public void StartUpdatingTargetWhenSourceChanges()
{
// TODO: commenting out this line will make the existing value to be skipped from the SourceValues. This is not supposed to happen. Is it?
_bindingTarget.Value = ConvertedValue(_sourceEndpoint.Value, _bindingTarget.Property.PropertyType);
// We use the native Bind method from PerspexObject to subscribe to the SourceValues observable
_bindingTarget.Object.Bind(_bindingTarget.Property, SourceValues);
}
public void StartUpdatingSourceWhenTargetChanges()
{
// We subscribe to the TargetValues and each time we have a new value, we update the source with it
TargetValues.Subscribe(newValue => _sourceEndpoint.Value = newValue);
}
private IObservable<object> SourceValues
{
get
{
return _sourceEndpoint.Values.Select(originalValue => ConvertedValue(originalValue, _bindingTarget.Property.PropertyType));
}
}
private IObservable<object> TargetValues
{
get
{
return _bindingEndpoint.Object
.GetObservable(_bindingEndpoint.Property).Select(o => ConvertedValue(o, _sourceEndpoint.Type));
}
}
private bool CanAssignWithoutConversion
{
get
{
var sourceTypeInfo = _sourceEndpoint.Type.GetTypeInfo();
var targetTypeInfo = _bindingEndpoint.Property.PropertyType.GetTypeInfo();
var compatible = targetTypeInfo.IsAssignableFrom(sourceTypeInfo);
return compatible;
}
}
private object ConvertedValue(object originalValue, Type propertyType)
{
object converted;
if (TryConvert(originalValue, propertyType, out converted))
{
return converted;
}
return null;
}
private bool TryConvert(object originalValue, Type targetType, out object finalValue)
{
if (originalValue != null)
{
if (CanAssignWithoutConversion)
{
finalValue = originalValue;
return true;
}
if (_targetPropertyTypeConverter != null)
{
if (_targetPropertyTypeConverter.CanConvertTo(null, targetType))
{
object convertedValue = _targetPropertyTypeConverter.ConvertTo(
null,
CultureInfo.InvariantCulture,
originalValue,
targetType);
if (convertedValue != null)
{
finalValue = convertedValue;
return true;
}
}
}
}
else
{
finalValue = null;
return true;
}
finalValue = null;
return false;
}
}
}

16
src/Markup/Perspex.Markup.Xaml/DataBinding/IPerspexPropertyBinder.cs

@ -1,16 +0,0 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
namespace Perspex.Markup.Xaml.DataBinding
{
public interface IPerspexPropertyBinder
{
XamlBinding GetBinding(PerspexObject po, PerspexProperty pp);
IEnumerable<XamlBinding> GetBindings(PerspexObject source);
XamlBinding Create(XamlBindingDefinition xamlBinding);
}
}

59
src/Markup/Perspex.Markup.Xaml/DataBinding/PerspexPropertyBinder.cs

@ -1,59 +0,0 @@
// Copyright (c) The Perspex 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.Generic;
using System.Linq;
using OmniXaml.TypeConversion;
namespace Perspex.Markup.Xaml.DataBinding
{
public class PerspexPropertyBinder : IPerspexPropertyBinder
{
private readonly ITypeConverterProvider _typeConverterProvider;
private readonly HashSet<XamlBinding> _bindings;
public PerspexPropertyBinder(ITypeConverterProvider typeConverterProvider)
{
_typeConverterProvider = typeConverterProvider;
_bindings = new HashSet<XamlBinding>();
}
public XamlBinding GetBinding(PerspexObject po, PerspexProperty pp)
{
return _bindings.First(xamlBinding => xamlBinding.Target == po && xamlBinding.TargetProperty == pp);
}
public IEnumerable<XamlBinding> GetBindings(PerspexObject source)
{
return from binding in _bindings
where binding.Target == source
select binding;
}
public XamlBinding Create(XamlBindingDefinition xamlBinding)
{
if (xamlBinding.Target == null)
{
throw new InvalidOperationException();
}
if (xamlBinding.TargetProperty == null)
{
throw new InvalidOperationException();
}
var binding = new XamlBinding(_typeConverterProvider)
{
BindingMode = xamlBinding.BindingMode,
SourcePropertyPath = xamlBinding.SourcePropertyPath,
Target = xamlBinding.Target,
TargetProperty = xamlBinding.TargetProperty
};
_bindings.Add(binding);
return binding;
}
}
}

65
src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBinding.cs

@ -1,65 +0,0 @@
// Copyright (c) The Perspex 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.Diagnostics;
using OmniXaml.TypeConversion;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
namespace Perspex.Markup.Xaml.DataBinding
{
public class XamlBinding
{
private readonly ITypeConverterProvider _typeConverterProvider;
private DataContextChangeSynchronizer _changeSynchronizer;
public XamlBinding(ITypeConverterProvider typeConverterProvider)
{
_typeConverterProvider = typeConverterProvider;
}
public PerspexObject Target { get; set; }
public PerspexProperty TargetProperty { get; set; }
public PropertyPath SourcePropertyPath { get; set; }
public BindingMode BindingMode { get; set; }
public void BindToDataContext(object dataContext)
{
if (dataContext == null)
{
return;
}
try
{
var bindingSource = new DataContextChangeSynchronizer.BindingSource(SourcePropertyPath, dataContext);
var bindingTarget = new DataContextChangeSynchronizer.BindingTarget(Target, TargetProperty);
_changeSynchronizer = new DataContextChangeSynchronizer(bindingSource, bindingTarget, _typeConverterProvider);
if (BindingMode == BindingMode.TwoWay)
{
_changeSynchronizer.StartUpdatingTargetWhenSourceChanges();
_changeSynchronizer.StartUpdatingSourceWhenTargetChanges();
}
if (BindingMode == BindingMode.OneWay)
{
_changeSynchronizer.StartUpdatingTargetWhenSourceChanges();
}
if (BindingMode == BindingMode.OneWayToSource)
{
_changeSynchronizer.StartUpdatingSourceWhenTargetChanges();
}
}
catch (Exception e)
{
Debug.WriteLine(e);
}
}
}
}

32
src/Markup/Perspex.Markup.Xaml/DataBinding/XamlBindingDefinition.cs

@ -1,32 +0,0 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Perspex.Controls;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
namespace Perspex.Markup.Xaml.DataBinding
{
public class XamlBindingDefinition
{
private readonly PropertyPath _sourcePropertyPath;
private readonly BindingMode _bindingMode;
private readonly Control _target;
private readonly PerspexProperty _targetProperty;
public XamlBindingDefinition(Control target, PerspexProperty targetProperty, PropertyPath sourcePropertyPath, BindingMode bindingMode)
{
_target = target;
_targetProperty = targetProperty;
_sourcePropertyPath = sourcePropertyPath;
_bindingMode = bindingMode;
}
public Control Target => _target;
public PerspexProperty TargetProperty => _targetProperty;
public PropertyPath SourcePropertyPath => _sourcePropertyPath;
public BindingMode BindingMode => _bindingMode;
}
}

18
src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -4,8 +4,7 @@
using System.Linq;
using OmniXaml;
using Perspex.Controls;
using Perspex.Markup.Xaml.DataBinding;
using Perspex.Markup.Xaml.DataBinding.ChangeTracking;
using Perspex.Markup.Xaml.Binding;
namespace Perspex.Markup.Xaml.MarkupExtensions
{
@ -22,23 +21,10 @@ namespace Perspex.Markup.Xaml.MarkupExtensions
public override object ProvideValue(MarkupExtensionContext extensionContext)
{
var target = extensionContext.TargetObject as Control;
var targetProperty = extensionContext.TargetProperty;
var targetPropertyName = targetProperty.Name;
var perspexProperty = target.GetRegisteredProperties().First(property => property.Name == targetPropertyName);
return new XamlBindingDefinition
(
target,
perspexProperty,
new PropertyPath(Path),
Mode == BindingMode.Default ? BindingMode.OneWay : Mode
);
return new XamlBindingDefinition(Path, Mode);
}
/// <summary> The source path (for CLR bindings).</summary>
public string Path { get; set; }
public BindingMode Mode { get; set; }
}
}

2
src/Markup/Perspex.Markup.Xaml/OmniXAML

@ -1 +1 @@
Subproject commit 49e6ec001f5873cf2290e0bc1f6f06ca9b9cf808
Subproject commit 2a6309a4d9b60b848241a34bf2adfa16b52c7a85

22
src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj

@ -38,6 +38,10 @@
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Binding\SourceBindingEndpoint.cs" />
<Compile Include="Binding\TargetBindingEndpoint.cs" />
<Compile Include="Binding\XamlBinding.cs" />
<Compile Include="Binding\XamlBindingDefinition.cs" />
<Compile Include="Converters\ColorTypeConverter.cs" />
<Compile Include="Converters\KeyGestureConverter.cs" />
<Compile Include="Converters\RelativePointTypeConverter.cs" />
@ -54,10 +58,6 @@
<Compile Include="Converters\BrushTypeConverter.cs" />
<Compile Include="Converters\BitmapTypeConverter.cs" />
<Compile Include="Converters\GridLengthTypeConverter.cs" />
<Compile Include="DataBinding\ChangeTracking\ObservablePropertyBranch.cs" />
<Compile Include="DataBinding\ChangeTracking\PropertyMountPoint.cs" />
<Compile Include="DataBinding\ChangeTracking\PropertyPath.cs" />
<Compile Include="DataBinding\ChangeTracking\TargettedProperty.cs" />
<Compile Include="Context\PerspexParserFactory.cs" />
<Compile Include="OmniXAML\Source\Glass\AutoKeyDictionary.cs" />
<Compile Include="OmniXAML\Source\Glass\ChangeTracking\ObservableProperty.cs" />
@ -228,13 +228,6 @@
<Compile Include="Parsers\SelectorParser.cs" />
<Compile Include="Parsers\SelectorGrammar.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="DataBinding\DataContextChangeSynchronizer.cs" />
<Compile Include="DataBinding\IPerspexPropertyBinder.cs" />
<Compile Include="DataBinding\PerspexPropertyBinder.cs" />
<Compile Include="DataBinding\SourceBindingEndpoint.cs" />
<Compile Include="DataBinding\TargetBindingEndpoint.cs" />
<Compile Include="DataBinding\XamlBinding.cs" />
<Compile Include="DataBinding\XamlBindingDefinition.cs" />
<Compile Include="Templates\TemplateLoader.cs" />
<Compile Include="Templates\Template.cs" />
<Compile Include="Templates\TemplateContent.cs" />
@ -244,8 +237,9 @@
<Compile Include="Context\PerspexXamlMember.cs" />
<Compile Include="Context\PerspexXamlMemberValuePlugin.cs" />
<Compile Include="Context\PerspexXamlType.cs" />
<Compile Include="Templates\XamlDataTemplate.cs" />
<Compile Include="Templates\DataTemplate.cs" />
<Compile Include="PerspexXamlLoader.cs" />
<Compile Include="Templates\TreeDataTemplate.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@ -289,6 +283,10 @@
<Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project>
<Name>Perspex.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Markup\Perspex.Markup.csproj">
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Perspex.Markup</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10">

26
src/Markup/Perspex.Markup.Xaml/Templates/XamlDataTemplate.cs → src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs

@ -8,38 +8,26 @@ using Perspex.Controls.Templates;
namespace Perspex.Markup.Xaml.Templates
{
[ContentProperty("Content")]
public class XamlDataTemplate : IDataTemplate
public class DataTemplate : IDataTemplate
{
private bool MyMatch(object data)
public Type DataType { get; set; }
public TemplateContent Content { get; set; }
public bool Match(object data)
{
if (DataType == null)
{
throw new InvalidOperationException("XAML DataTemplates must have a DataType");
throw new InvalidOperationException("DataTemplate must have a DataType.");
}
return DataType == data.GetType();
}
private Control CreateVisualTreeForItem(object data)
public IControl Build(object data)
{
var visualTreeForItem = Content.Load();
visualTreeForItem.DataContext = data;
return visualTreeForItem;
}
public Type DataType { get; set; }
public TemplateContent Content { get; set; }
public IControl Build(object param)
{
return CreateVisualTreeForItem(param);
}
public bool Match(object data)
{
return MyMatch(data);
}
}
}

53
src/Markup/Perspex.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -0,0 +1,53 @@
// Copyright (c) The Perspex 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.Reactive.Linq;
using Perspex.Controls;
using Perspex.Controls.Templates;
using Perspex.Markup.Binding;
using Perspex.Markup.Xaml.Binding;
namespace Perspex.Markup.Xaml.Templates
{
public class TreeDataTemplate : ITreeDataTemplate
{
public Type DataType { get; set; }
public TemplateContent Content { get; set; }
public XamlBindingDefinition ItemsSource { get; set; }
public bool Match(object data)
{
if (DataType == null)
{
throw new InvalidOperationException("DataTemplate must have a DataType.");
}
return DataType == data.GetType();
}
public IEnumerable ItemsSelector(object item)
{
if (ItemsSource != null)
{
var obs = new ExpressionObserver(item, ItemsSource.SourcePropertyPath);
return obs.Take(1).Wait() as IEnumerable;
}
return null;
}
public bool IsExpanded(object item)
{
return true;
}
public IControl Build(object data)
{
var visualTreeForItem = Content.Load();
visualTreeForItem.DataContext = data;
return visualTreeForItem;
}
}
}

102
src/Markup/Perspex.Markup/Binding/ExpressionNode.cs

@ -0,0 +1,102 @@
// Copyright (c) The Perspex 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.Reactive.Subjects;
namespace Perspex.Markup.Binding
{
internal abstract class ExpressionNode : IObservable<object>
{
private object _target;
private Subject<object> _subject;
private object _value = PerspexProperty.UnsetValue;
public ExpressionNode Next { get; set; }
public object Target
{
get { return _target; }
set
{
if (_target != null)
{
Unsubscribe(_target);
}
_target = value;
if (_target != null)
{
SubscribeAndUpdate(_target);
}
else
{
CurrentValue = PerspexProperty.UnsetValue;
}
if (Next != null)
{
Next.Target = CurrentValue;
}
}
}
public object CurrentValue
{
get
{
return _value;
}
set
{
_value = value;
if (Next != null)
{
Next.Target = value;
}
if (_subject != null)
{
_subject.OnNext(value);
}
}
}
public virtual bool SetValue(object value)
{
return Next?.SetValue(value) ?? false;
}
public virtual IDisposable Subscribe(IObserver<object> observer)
{
if (Next != null)
{
return Next.Subscribe(observer);
}
else
{
if (_subject == null)
{
_subject = new Subject<object>();
}
observer.OnNext(CurrentValue);
return _subject.Subscribe(observer);
}
}
protected virtual void SubscribeAndUpdate(object target)
{
CurrentValue = target;
}
protected virtual void Unsubscribe(object target)
{
}
}
}

29
src/Markup/Perspex.Markup/Binding/ExpressionNodeBuilder.cs

@ -0,0 +1,29 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Markup.Binding.Parsers;
namespace Perspex.Markup.Binding
{
internal static class ExpressionNodeBuilder
{
public static ExpressionNode Build(string expression)
{
if (string.IsNullOrWhiteSpace(expression))
{
throw new ArgumentException("'expression' may not be empty.");
}
var reader = new Reader(expression);
var node = ExpressionParser.Parse(reader);
if (!reader.End)
{
throw new ExpressionParseException(reader, "Expected end of expression.");
}
return node;
}
}
}

115
src/Markup/Perspex.Markup/Binding/ExpressionObserver.cs

@ -0,0 +1,115 @@
// Copyright (c) The Perspex 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.Reactive;
using System.Reactive.Disposables;
namespace Perspex.Markup.Binding
{
/// <summary>
/// Observes and sets the value of an expression on an object.
/// </summary>
public class ExpressionObserver : ObservableBase<object>, IDescription
{
private object _root;
private int _count;
private ExpressionNode _node;
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="root">The root object.</param>
/// <param name="expression">The expression.</param>
public ExpressionObserver(object root, string expression)
{
_root = root;
_node = ExpressionNodeBuilder.Build(expression);
Expression = expression;
}
/// <summary>
/// Attempts to set the value of a property expression.
/// </summary>
/// <param name="value">The value to set.</param>
/// <returns>
/// True if the value could be set; false if the expression does not evaluate to a
/// property.
/// </returns>
public bool SetValue(object value)
{
IncrementCount();
try
{
return _node.SetValue(value);
}
finally
{
DecrementCount();
}
}
/// <summary>
/// Gets the expression being observed.
/// </summary>
public string Expression { get; }
/// <summary>
/// Gets or sets the root object that the expression is being observed on.
/// </summary>
public object Root
{
get
{
return _root;
}
set
{
if (_root != value)
{
_root = value;
if (_count > 0)
{
_node.Target = _root;
}
}
}
}
/// <inheritdoc/>
string IDescription.Description => Expression;
/// <inheritdoc/>
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
IncrementCount();
var subscription = _node.Subscribe(observer);
return Disposable.Create(() =>
{
DecrementCount();
subscription.Dispose();
});
}
private void IncrementCount()
{
if (_count++ == 0)
{
_node.Target = Root;
}
}
private void DecrementCount()
{
if (--_count == 0)
{
_node.Target = null;
}
}
}
}

24
src/Markup/Perspex.Markup/Binding/ExpressionParseException.cs

@ -0,0 +1,24 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Perspex.Markup.Binding.Parsers;
namespace Perspex.Markup.Binding
{
public class ExpressionParseException : Exception
{
internal ExpressionParseException(int column, string message)
: base(message)
{
Column = column;
}
internal ExpressionParseException(Reader r, string message)
: this(r.Position, message)
{
}
public int Column { get; }
}
}

51
src/Markup/Perspex.Markup/Binding/ExpressionSubject.cs

@ -0,0 +1,51 @@
// Copyright (c) The Perspex 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.Reactive.Linq;
using System.Reactive.Subjects;
namespace Perspex.Markup.Binding
{
/// <summary>
/// Turns an <see cref="ExpressionObserver"/> into a subject that can be bound two-ways.
/// </summary>
public class ExpressionSubject : ISubject<object>, IDescription
{
private ExpressionObserver _inner;
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="inner">The <see cref="ExpressionObserver"/>.</param>
public ExpressionSubject(ExpressionObserver inner)
{
_inner = inner;
}
/// <inheritdoc/>
string IDescription.Description => _inner.Expression;
/// <inheritdoc/>
public void OnCompleted()
{
}
/// <inheritdoc/>
public void OnError(Exception error)
{
}
/// <inheritdoc/>
public void OnNext(object value)
{
_inner.SetValue(value);
}
/// <inheritdoc/>
public IDisposable Subscribe(IObserver<object> observer)
{
return _inner.Subscribe(observer);
}
}
}

131
src/Markup/Perspex.Markup/Binding/IndexerNode.cs

@ -0,0 +1,131 @@
// Copyright (c) The Perspex 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.Linq;
using System.Reflection;
namespace Perspex.Markup.Binding
{
internal class IndexerNode : ExpressionNode
{
private int[] _intArgs;
public IndexerNode(IList<object> arguments)
{
Arguments = arguments;
var intArgs = Arguments.OfType<int>().ToArray();
if (intArgs.Length == arguments.Count)
{
_intArgs = intArgs;
}
}
public IList<object> Arguments { get; }
protected override void SubscribeAndUpdate(object target)
{
CurrentValue = GetValue(target);
var incc = target as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += CollectionChanged;
}
}
protected override void Unsubscribe(object target)
{
var incc = target as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged -= CollectionChanged;
}
}
private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
bool update = false;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
update = _intArgs[0] >= e.NewStartingIndex;
break;
case NotifyCollectionChangedAction.Remove:
update = _intArgs[0] >= e.OldStartingIndex;
break;
case NotifyCollectionChangedAction.Replace:
update = _intArgs[0] >= e.NewStartingIndex &&
_intArgs[0] < e.NewStartingIndex + e.NewItems.Count;
break;
case NotifyCollectionChangedAction.Move:
update = (_intArgs[0] >= e.NewStartingIndex &&
_intArgs[0] < e.NewStartingIndex + e.NewItems.Count) ||
(_intArgs[0] >= e.OldStartingIndex &&
_intArgs[0] < e.OldStartingIndex + e.OldItems.Count);
break;
case NotifyCollectionChangedAction.Reset:
update = true;
break;
}
if (update)
{
CurrentValue = GetValue(sender);
}
}
private object GetValue(object target)
{
var typeInfo = target.GetType().GetTypeInfo();
var list = target as IList;
if (typeInfo.IsArray && _intArgs != null)
{
var array = (Array)target;
if (InBounds(_intArgs, array))
{
return array.GetValue(_intArgs);
}
}
else if (target is IList && _intArgs?.Length == 1)
{
if (_intArgs[0] < list.Count)
{
return list[_intArgs[0]];
}
}
return PerspexProperty.UnsetValue;
}
private bool InBounds(int[] args, Array array)
{
if (args.Length == array.Rank)
{
for (var i = 0; i < args.Length; ++i)
{
if (args[i] >= array.GetLength(i))
{
return false;
}
}
return true;
}
else
{
return false;
}
}
}
}

40
src/Markup/Perspex.Markup/Binding/LogicalNotNode.cs

@ -0,0 +1,40 @@
// Copyright (c) The Perspex 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.Globalization;
using System.Reactive.Linq;
namespace Perspex.Markup.Binding
{
internal class LogicalNotNode : ExpressionNode
{
public override bool SetValue(object value)
{
throw new NotSupportedException("Cannot set a negated binding.");
}
public override IDisposable Subscribe(IObserver<object> observer)
{
return Next.Select(x => Negate(x)).Subscribe(observer);
}
private object Negate(object v)
{
if (v != PerspexProperty.UnsetValue)
{
try
{
var boolean = Convert.ToBoolean(v, CultureInfo.InvariantCulture);
return !boolean;
}
catch
{
// TODO: Maybe should log something here.
}
}
return PerspexProperty.UnsetValue;
}
}
}

67
src/Markup/Perspex.Markup/Binding/Parsers/ArgumentListParser.cs

@ -0,0 +1,67 @@
// Copyright (c) The Perspex 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.Generic;
namespace Perspex.Markup.Binding.Parsers
{
internal static class ArgumentListParser
{
public static IList<object> Parse(Reader r, char open, char close)
{
if (r.Peek == open)
{
var result = new List<object>();
r.Take();
while (!r.End)
{
var literal = LiteralParser.Parse(r);
if (literal != null)
{
result.Add(literal);
}
else
{
throw new ExpressionParseException(r, "Expected integer.");
}
r.SkipWhitespace();
if (r.End)
{
throw new ExpressionParseException(r, "Expected ','.");
}
else if (r.TakeIf(close))
{
return result;
}
else
{
if (r.Take() != ',')
{
throw new ExpressionParseException(r, "Expected ','.");
}
r.SkipWhitespace();
}
}
if (!r.End)
{
r.Take();
return result;
}
else
{
throw new ExpressionParseException(r, "Expected ']'.");
}
}
return null;
}
}
}

125
src/Markup/Perspex.Markup/Binding/Parsers/ExpressionParser.cs

@ -0,0 +1,125 @@
// Copyright (c) The Perspex 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.Generic;
using System.Linq;
namespace Perspex.Markup.Binding.Parsers
{
internal static class ExpressionParser
{
public static ExpressionNode Parse(Reader r)
{
var nodes = new List<ExpressionNode>();
var state = State.Start;
while (!r.End && state != State.End)
{
switch (state)
{
case State.Start:
state = ParseStart(r, nodes);
break;
case State.AfterMember:
state = ParseAfterMember(r, nodes);
break;
case State.BeforeMember:
state = ParseBeforeMember(r, nodes);
break;
}
}
if (state == State.BeforeMember)
{
throw new ExpressionParseException(r, "Unexpected end of expression.");
}
for (int n = 0; n < nodes.Count - 1; ++n)
{
nodes[n].Next = nodes[n + 1];
}
return nodes.FirstOrDefault();
}
private static State ParseStart(Reader r, IList<ExpressionNode> nodes)
{
if (ParseNot(r))
{
nodes.Add(new LogicalNotNode());
return State.Start;
}
else
{
var identifier = IdentifierParser.Parse(r);
if (identifier != null)
{
nodes.Add(new PropertyAccessorNode(identifier));
return State.AfterMember;
}
}
return State.End;
}
private static State ParseAfterMember(Reader r, IList<ExpressionNode> nodes)
{
if (ParseMemberAccessor(r))
{
return State.BeforeMember;
}
else
{
var args = ArgumentListParser.Parse(r, '[', ']');
if (args != null)
{
if (args.Count == 0)
{
throw new ExpressionParseException(r, "Indexer may not be empty.");
}
nodes.Add(new IndexerNode(args));
return State.AfterMember;
}
}
return State.End;
}
private static State ParseBeforeMember(Reader r, IList<ExpressionNode> nodes)
{
var identifier = IdentifierParser.Parse(r);
if (identifier != null)
{
nodes.Add(new PropertyAccessorNode(identifier));
return State.AfterMember;
}
return State.End;
}
private static bool ParseNot(Reader r)
{
return !r.End && r.TakeIf('!');
}
private static bool ParseMemberAccessor(Reader r)
{
return !r.End && r.TakeIf('.');
}
private enum State
{
Start,
AfterMember,
BeforeMember,
End,
}
}
}

51
src/Markup/Perspex.Markup/Binding/Parsers/IdentifierParser.cs

@ -0,0 +1,51 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Globalization;
using System.Text;
namespace Perspex.Markup.Binding.Parsers
{
internal static class IdentifierParser
{
public static string Parse(Reader r)
{
if (IsValidIdentifierStart(r.Peek))
{
var result = new StringBuilder();
while (!r.End && IsValidIdentifierChar(r.Peek))
{
result.Append(r.Take());
}
return result.ToString();
}
else
{
return null;
}
}
private static bool IsValidIdentifierStart(char c)
{
return char.IsLetter(c) || c == '_';
}
private static bool IsValidIdentifierChar(char c)
{
if (IsValidIdentifierStart(c))
{
return true;
}
else
{
var cat = CharUnicodeInfo.GetUnicodeCategory(c);
return cat == UnicodeCategory.NonSpacingMark ||
cat == UnicodeCategory.SpacingCombiningMark ||
cat == UnicodeCategory.ConnectorPunctuation ||
cat == UnicodeCategory.Format;
}
}
}
}

35
src/Markup/Perspex.Markup/Binding/Parsers/LiteralParser.cs

@ -0,0 +1,35 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Globalization;
using System.Text;
namespace Perspex.Markup.Binding.Parsers
{
internal static class LiteralParser
{
public static object Parse(Reader r)
{
if (char.IsDigit(r.Peek))
{
StringBuilder result = new StringBuilder();
while (!r.End)
{
if (char.IsDigit(r.Peek))
{
result.Append(r.Take());
}
else
{
break;
}
}
return int.Parse(result.ToString(), CultureInfo.InvariantCulture);
}
return null;
}
}
}

44
src/Markup/Perspex.Markup/Binding/Parsers/Reader.cs

@ -0,0 +1,44 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Perspex.Markup.Binding.Parsers
{
internal class Reader
{
private string _s;
private int _i;
public Reader(string s)
{
_s = s;
}
public bool End => _i == _s.Length;
public char Peek => _s[_i];
public int Position => _i;
public char Take() => _s[_i++];
public void SkipWhitespace()
{
while (!End && char.IsWhiteSpace(Peek))
{
Take();
}
}
public bool TakeIf(char c)
{
if (Peek == c)
{
Take();
return true;
}
else
{
return false;
}
}
}
}

164
src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs

@ -0,0 +1,164 @@
// Copyright (c) The Perspex 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.ComponentModel;
using System.Reactive.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Perspex.Markup.Binding
{
internal class PropertyAccessorNode : ExpressionNode
{
private PropertyInfo _propertyInfo;
private IDisposable _subscription;
public PropertyAccessorNode(string propertyName)
{
PropertyName = propertyName;
}
public string PropertyName { get; }
public override bool SetValue(object value)
{
if (Next != null)
{
return Next.SetValue(value);
}
else
{
if (_propertyInfo != null && _propertyInfo.CanWrite)
{
_propertyInfo.SetValue(Target, value);
return true;
}
return false;
}
}
protected override void SubscribeAndUpdate(object target)
{
bool set = false;
if (target != null)
{
_propertyInfo = FindProperty(target, PropertyName);
if (_propertyInfo != null)
{
ReadValue(target);
set = true;
var inpc = target as INotifyPropertyChanged;
if (inpc != null)
{
inpc.PropertyChanged += PropertyChanged;
}
}
}
else
{
_propertyInfo = null;
}
if (!set)
{
CurrentValue = PerspexProperty.UnsetValue;
}
}
protected override void Unsubscribe(object target)
{
var inpc = target as INotifyPropertyChanged;
if (inpc != null)
{
inpc.PropertyChanged -= PropertyChanged;
}
}
private static PropertyInfo FindProperty(object target, string propertyName)
{
var typeInfo = target.GetType().GetTypeInfo();
do
{
var result = typeInfo.GetDeclaredProperty(propertyName);
if (result != null)
{
return result;
}
else
{
typeInfo = typeInfo.BaseType?.GetTypeInfo();
}
} while (typeInfo != null);
return null;
}
private void ReadValue(object target)
{
var value = _propertyInfo.GetValue(target);
var observable = value as IObservable<object>;
var command = value as ICommand;
var task = value as Task;
bool set = false;
// ReactiveCommand is an IObservable but we want to bind to it, not its value.
if (observable != null && command == null)
{
CurrentValue = PerspexProperty.UnsetValue;
set = true;
_subscription = observable
.ObserveOn(SynchronizationContext.Current)
.Subscribe(x => CurrentValue = x);
}
else if (task != null)
{
var resultProperty = task.GetType().GetTypeInfo().GetDeclaredProperty("Result");
if (resultProperty != null)
{
if (task.Status == TaskStatus.RanToCompletion)
{
CurrentValue = resultProperty.GetValue(task);
set = true;
}
else
{
task.ContinueWith(
x => CurrentValue = resultProperty.GetValue(task),
TaskScheduler.FromCurrentSynchronizationContext())
.ConfigureAwait(false);
}
}
}
else
{
CurrentValue = value;
set = true;
}
if (!set)
{
CurrentValue = PerspexProperty.UnsetValue;
}
}
private void PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == PropertyName)
{
ReadValue(sender);
}
}
}
}

89
src/Markup/Perspex.Markup/Perspex.Markup.csproj

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{6417E941-21BC-467B-A771-0DE389353CE6}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex.Markup</RootNamespace>
<AssemblyName>Perspex.Markup</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Perspex.Markup.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Perspex.Markup.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="Binding\ExpressionNodeBuilder.cs" />
<Compile Include="Binding\ExpressionParseException.cs" />
<Compile Include="Binding\ExpressionSubject.cs" />
<Compile Include="Binding\LogicalNotNode.cs" />
<Compile Include="Binding\IndexerNode.cs" />
<Compile Include="Binding\Parsers\ArgumentListParser.cs" />
<Compile Include="Binding\Parsers\LiteralParser.cs" />
<Compile Include="Binding\Parsers\IdentifierParser.cs" />
<Compile Include="Binding\Parsers\ExpressionParser.cs" />
<Compile Include="Binding\Parsers\Reader.cs" />
<Compile Include="Binding\PropertyAccessorNode.cs" />
<Compile Include="Binding\ExpressionNode.cs" />
<Compile Include="Binding\ExpressionObserver.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Rx-PlatformServices.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

32
src/Markup/Perspex.Markup/Properties/AssemblyInfo.cs

@ -0,0 +1,32 @@
using System.Resources;
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("Perspex.Markup")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Perspex.Markup")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// 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")]
[assembly: InternalsVisibleTo("Perspex.Markup.UnitTests")]

8
src/Markup/Perspex.Markup/packages.config

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Linq" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-Main" version="2.2.5" targetFramework="portable45-net45+win8" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable45-net45+win8" />
</packages>

2
src/Perspex.Base/BindingDescriptor.cs

@ -27,7 +27,7 @@ namespace Perspex
TwoWay,
/// <summary>
/// Copies the target to the source one time and then disposes of the binding.
/// Updates the target when the application starts or when the data context changes.
/// </summary>
OneTime,

38
src/Perspex.Base/IObservablePropertyBag.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Subjects;
namespace Perspex
{
@ -39,6 +40,43 @@ namespace Perspex
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Initiates a two-way binding between <see cref="PerspexProperty"/>s.
/// </summary>
/// <param name="property">The property on this object.</param>
/// <param name="source">The source object.</param>
/// <param name="sourceProperty">The property on the source object.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
/// <remarks>
/// The binding is first carried out from <paramref name="source"/> to this.
/// </remarks>
IDisposable BindTwoWay(
PerspexProperty property,
PerspexObject source,
PerspexProperty sourceProperty,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Initiates a two-way binding between a <see cref="PerspexProperty"/> and an
/// <see cref="ISubject{Object}"/>.
/// </summary>
/// <param name="property">The property on this object.</param>
/// <param name="source">The subject to bind to.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
/// <remarks>
/// The binding is first carried out from <paramref name="source"/> to this.
/// </remarks>
IDisposable BindTwoWay(
PerspexProperty property,
ISubject<object> source,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Gets an observable for a <see cref="PerspexProperty"/>.
/// </summary>

5
src/Perspex.Base/IPropertyBag.cs

@ -8,6 +8,11 @@ namespace Perspex
/// </summary>
public interface IPropertyBag
{
/// <summary>
/// Gets the object that inherited <see cref="PerspexProperty"/> values are inherited from.
/// </summary>
IPropertyBag InheritanceParent { get; }
/// <summary>
/// Clears a <see cref="PerspexProperty"/>'s local value.
/// </summary>

104
src/Perspex.Base/PerspexObject.cs

@ -7,6 +7,7 @@ using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using Perspex.Reactive;
using Perspex.Utilities;
@ -99,6 +100,11 @@ namespace Perspex
remove { _inpcChanged -= value; }
}
/// <summary>
/// Gets the object that inherited <see cref="PerspexProperty"/> values are inherited from.
/// </summary>
IPropertyBag IPropertyBag.InheritanceParent => InheritanceParent;
/// <summary>
/// Gets or sets the parent object that inherited <see cref="PerspexProperty"/> values
/// are inherited from.
@ -186,7 +192,7 @@ namespace Perspex
if (sourceBinding == null && mode > BindingMode.OneWay)
{
throw new InvalidOperationException("Can only bind OneWay to plain IObservable.");
mode = BindingMode.OneWay;
}
switch (mode)
@ -334,7 +340,7 @@ namespace Perspex
PropertyChanged -= handler;
});
},
GetObservableDescription(property));
GetDescription(property));
}
/// <summary>
@ -377,7 +383,7 @@ namespace Perspex
PropertyChanged -= handler;
});
},
GetObservableDescription(property));
GetDescription(property));
}
/// <summary>
@ -456,8 +462,15 @@ namespace Perspex
public bool IsSet(PerspexProperty property)
{
Contract.Requires<ArgumentNullException>(property != null);
PriorityValue value;
if (_values.TryGetValue(property, out value))
{
return value.Value != PerspexProperty.UnsetValue;
}
return _values.ContainsKey(property);
return false;
}
/// <summary>
@ -492,6 +505,7 @@ namespace Perspex
throw new ArgumentException($"The property {property.Name} is readonly.");
}
LogPropertySet(property, value, priority);
property.Setter(this, value);
}
else
@ -524,14 +538,9 @@ namespace Perspex
_values.Add(property, v);
}
LogPropertySet(property, value, priority);
v.SetValue(value, (int)priority);
}
_propertyLog.Verbose(
"Set {Property} to {$Value} with priority {Priority}",
property,
value,
priority);
}
/// <summary>
@ -557,6 +566,7 @@ namespace Perspex
throw new ArgumentException($"The property {property.Name} is readonly.");
}
LogPropertySet(property, value, priority);
property.Setter(this, value);
}
else
@ -593,14 +603,15 @@ namespace Perspex
_propertyLog.Verbose(
"Bound {Property} to {Binding} with priority LocalValue",
property,
source);
GetDescription(source));
return source.Subscribe(x => SetValue(property, x));
return source
.Select(x => TypeUtilities.CastOrDefault(x, property.PropertyType, false))
.Subscribe(x => SetValue(property, x));
}
else
{
PriorityValue v;
IDescription description = source as IDescription;
if (!IsRegistered(property))
{
@ -616,7 +627,7 @@ namespace Perspex
_propertyLog.Verbose(
"Bound {Property} to {Binding} with priority {Priority}",
property,
source,
GetDescription(source),
priority);
return v.Add(source, (int)priority);
@ -658,7 +669,7 @@ namespace Perspex
}
/// <summary>
/// Initialites a two-way bind between <see cref="PerspexProperty"/>s.
/// Initiates a two-way binding between <see cref="PerspexProperty"/>s.
/// </summary>
/// <param name="property">The property on this object.</param>
/// <param name="source">The source object.</param>
@ -676,11 +687,46 @@ namespace Perspex
PerspexProperty sourceProperty,
BindingPriority priority = BindingPriority.LocalValue)
{
_propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}",
property,
source,
priority);
return new CompositeDisposable(
Bind(property, source.GetObservable(sourceProperty)),
source.Bind(sourceProperty, GetObservable(property)));
}
/// <summary>
/// Initiates a two-way binding between a <see cref="PerspexProperty"/> and an
/// <see cref="ISubject{Object}"/>.
/// </summary>
/// <param name="property">The property on this object.</param>
/// <param name="source">The subject to bind to.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
/// <remarks>
/// The binding is first carried out from <paramref name="source"/> to this.
/// </remarks>
public IDisposable BindTwoWay(
PerspexProperty property,
ISubject<object> source,
BindingPriority priority = BindingPriority.LocalValue)
{
_propertyLog.Verbose(
"Bound two way {Property} to {Binding} with priority {Priority}",
property,
GetDescription(source),
priority);
return new CompositeDisposable(
Bind(property, source),
GetObservable(property).Subscribe(source));
}
/// <summary>
/// Forces the specified property to be revalidated.
/// </summary>
@ -926,11 +972,37 @@ namespace Perspex
/// </summary>
/// <param name="property">The property</param>
/// <returns>The description.</returns>
private string GetObservableDescription(PerspexProperty property)
private string GetDescription(PerspexProperty property)
{
return string.Format("{0}.{1}", GetType().Name, property.Name);
}
/// <summary>
/// Gets a description of an observable that van be used in logs.
/// </summary>
/// <param name="o">The observable.</param>
/// <returns>The description.</returns>
private string GetDescription(IObservable<object> o)
{
var description = o as IDescription;
return description?.Description ?? o.ToString();
}
/// <summary>
/// Logs a property set message.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The new value.</param>
/// <param name="priority">The priority.</param>
private void LogPropertySet(PerspexProperty property, object value, BindingPriority priority)
{
_propertyLog.Verbose(
"Set {Property} to {$Value} with priority {Priority}",
property,
value,
priority);
}
/// <summary>
/// Throws an exception indicating that the specified property is not registered on this
/// object.

26
src/Perspex.Base/PriorityValue.cs

@ -212,25 +212,23 @@ namespace Perspex
/// <param name="priority">The priority level that the value came from.</param>
private void UpdateValue(object value, int priority)
{
if (!TypeUtilities.TryCast(_valueType, value, out value))
if (TypeUtilities.TryCast(_valueType, value, out value))
{
throw new InvalidOperationException(string.Format(
"Invalid value for Property '{0}': {1} ({2})",
_name,
value,
value?.GetType().FullName ?? "(null)"));
}
var old = _value;
var old = _value;
if (_validate != null)
{
value = _validate(value);
}
if (_validate != null)
ValuePriority = priority;
_value = value;
_changed.OnNext(Tuple.Create(old, _value));
}
else
{
value = _validate(value);
// TODO: Log error.
}
ValuePriority = priority;
_value = value;
_changed.OnNext(Tuple.Create(old, _value));
}
/// <summary>

32
src/Perspex.Base/Utilities/TypeUtilities.cs

@ -32,8 +32,9 @@ namespace Perspex.Utilities
/// <param name="to">The type to cast to.</param>
/// <param name="value">The value to cast.</param>
/// <param name="result">If sucessful, contains the cast value.</param>
/// <param name="allowUnset">Allow <see cref="PerspexProperty.UnsetValue"/>.</param>
/// <returns>True if the cast was sucessful, otherwise false.</returns>
public static bool TryCast(Type to, object value, out object result)
public static bool TryCast(Type to, object value, out object result, bool allowUnset = true)
{
Contract.Requires<ArgumentNullException>(to != null);
@ -46,7 +47,7 @@ namespace Perspex.Utilities
var from = value.GetType();
if (value == PerspexProperty.UnsetValue)
if (allowUnset && value == PerspexProperty.UnsetValue)
{
result = value;
return true;
@ -77,5 +78,32 @@ namespace Perspex.Utilities
result = null;
return false;
}
/// <summary>
/// Casts a value to a type, returning the default for that type if the value could not be
/// cast.
/// </summary>
/// <param name="value">The value to cast.</param>
/// <param name="type">The type to cast to..</param>
/// <param name="allowUnset">Allow <see cref="PerspexProperty.UnsetValue"/>.</param>
/// <returns>A value of <paramref name="type"/>.</returns>
public static object CastOrDefault(object value, Type type, bool allowUnset = true)
{
var typeInfo = type.GetTypeInfo();
object result;
if (TypeUtilities.TryCast(type, value, out result, allowUnset))
{
return result;
}
else if (typeInfo.IsValueType)
{
return Activator.CreateInstance(type);
}
else
{
return null;
}
}
}
}

2
src/Perspex.Controls/Border.cs

@ -83,7 +83,7 @@ namespace Perspex.Controls
/// Renders the control.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
var background = Background;
var borderBrush = BorderBrush;

2
src/Perspex.Controls/ContentControl.cs

@ -11,7 +11,7 @@ using Perspex.Layout;
namespace Perspex.Controls
{
/// <summary>
/// Displays <see cref="Content"/> according to a <see cref="DataTemplate"/>.
/// Displays <see cref="Content"/> according to a <see cref="FuncDataTemplate"/>.
/// </summary>
public class ContentControl : TemplatedControl, IContentControl, IReparentingHost
{

10
src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@ -16,7 +16,7 @@ namespace Perspex.Controls.Generators
/// <typeparam name="T">The type of the container.</typeparam>
public class TreeItemContainerGenerator<T> : ITreeItemContainerGenerator where T : TreeViewItem, new()
{
private readonly Dictionary<object, T> _containers = new Dictionary<object, T>();
private Dictionary<object, T> _containers = new Dictionary<object, T>();
private readonly Subject<ItemContainers> _containersInitialized = new Subject<ItemContainers>();
@ -102,7 +102,9 @@ namespace Perspex.Controls.Generators
/// <returns>The removed controls.</returns>
public IList<IControl> ClearContainers()
{
throw new NotImplementedException();
var result = _containers;
_containers = new Dictionary<object, T>();
return result.Values.Cast<IControl>().ToList();
}
/// <summary>
@ -192,14 +194,14 @@ namespace Perspex.Controls.Generators
if (template == null)
{
template = DataTemplate.Default;
template = FuncDataTemplate.Default;
}
var treeTemplate = template as ITreeDataTemplate;
if (treeTemplate == null)
{
treeTemplate = new TreeDataTemplate(typeof(object), template.Build, x => null);
treeTemplate = new FuncTreeDataTemplate(typeof(object), template.Build, x => null);
}
return treeTemplate;

2
src/Perspex.Controls/IContentControl.cs

@ -7,7 +7,7 @@ namespace Perspex.Controls
{
/// <summary>
/// Defines a control that displays <see cref="Content"/> according to a
/// <see cref="Perspex.Controls.Templates.DataTemplate"/>.
/// <see cref="Perspex.Controls.Templates.FuncDataTemplate"/>.
/// </summary>
public interface IContentControl : IControl
{

2
src/Perspex.Controls/Image.cs

@ -46,7 +46,7 @@ namespace Perspex.Controls
/// Renders the control.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
Bitmap source = Source;

2
src/Perspex.Controls/ItemsControl.cs

@ -156,8 +156,6 @@ namespace Perspex.Controls
/// <param name="e">The event args.</param>
protected virtual void ItemsChanged(PerspexPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine($"{this.GetType().Name} set items");
var incc = e.OldValue as INotifyCollectionChanged;
if (incc != null)

4
src/Perspex.Controls/Panel.cs

@ -172,10 +172,10 @@ namespace Perspex.Controls
}
/// <summary>
/// Renders the visual to a <see cref="IDrawingContext"/>.
/// Renders the visual to a <see cref="DrawingContext"/>.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
Brush background = Background;
if (background != null)

8
src/Perspex.Controls/Perspex.Controls.csproj

@ -72,7 +72,7 @@
<Compile Include="Primitives\ScrollInfoAdapter.cs" />
<Compile Include="Canvas.cs" />
<Compile Include="Templates\ControlTemplate`2.cs" />
<Compile Include="Templates\DataTemplate`1.cs" />
<Compile Include="Templates\FuncDataTemplate`1.cs" />
<Compile Include="Templates\FuncMemberSelector.cs" />
<Compile Include="Templates\IControlTemplate.cs" />
<Compile Include="Templates\FuncTemplate`2.cs" />
@ -102,7 +102,7 @@
<Compile Include="Controls.cs" />
<Compile Include="Templates\ControlTemplate.cs" />
<Compile Include="Templates\DataTemplateExtensions.cs" />
<Compile Include="Templates\DataTemplate.cs" />
<Compile Include="Templates\FuncDataTemplate.cs" />
<Compile Include="Generators\TreeItemContainerGenerator.cs" />
<Compile Include="Generators\ItemContainerGenerator`1.cs" />
<Compile Include="Generators\ItemContainerGenerator.cs" />
@ -129,7 +129,7 @@
<Compile Include="Templates\ITemplate`2.cs" />
<Compile Include="Templates\ITemplate`1.cs" />
<Compile Include="Templates\ITreeDataTemplate.cs" />
<Compile Include="Templates\TreeDataTemplate.cs" />
<Compile Include="Templates\FuncTreeDataTemplate.cs" />
<Compile Include="ToolTip.cs" />
<Compile Include="UserControl.cs" />
<Compile Include="Templates\TemplateExtensions.cs" />
@ -157,7 +157,7 @@
<Compile Include="TextBox.cs" />
<Compile Include="Presenters\TextPresenter.cs" />
<Compile Include="Primitives\ToggleButton.cs" />
<Compile Include="Templates\TreeDataTemplate`1.cs" />
<Compile Include="Templates\FuncTreeDataTemplate`1.cs" />
<Compile Include="TreeView.cs" />
<Compile Include="TreeViewItem.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

2
src/Perspex.Controls/Presenters/TextPresenter.cs

@ -69,7 +69,7 @@ namespace Perspex.Controls.Presenters
return hit.TextPosition + (hit.IsTrailing ? 1 : 0);
}
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;

2
src/Perspex.Controls/Primitives/AccessText.cs

@ -62,7 +62,7 @@ namespace Perspex.Controls.Primitives
/// Renders the <see cref="AccessText"/> to a drawing context.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
base.Render(context);

2
src/Perspex.Controls/Primitives/SelectingItemsControl.cs

@ -295,8 +295,6 @@ namespace Perspex.Controls.Primitives
/// <param name="e">The event args.</param>
private void SelectedIndexChanged(PerspexPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine($"{this.GetType().Name} set selected index");
var index = (int)e.OldValue;
if (index != -1)

1
src/Perspex.Controls/Properties/AssemblyInfo.cs

@ -9,5 +9,6 @@ using Perspex.Metadata;
[assembly: InternalsVisibleTo("Perspex.Controls.UnitTests")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Presenters")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Primitives")]
[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Controls.Shapes")]

2
src/Perspex.Controls/Shapes/Shape.cs

@ -90,7 +90,7 @@ namespace Perspex.Controls.Shapes
set { SetValue(StrokeThicknessProperty, value); }
}
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
var geometry = RenderedGeometry;

2
src/Perspex.Controls/Templates/DataTemplateExtensions.cs

@ -42,7 +42,7 @@ namespace Perspex.Controls.Templates
}
else
{
result = DataTemplate.Default.Build(data);
result = FuncDataTemplate.Default.Build(data);
}
result.DataContext = data;

14
src/Perspex.Controls/Templates/DataTemplate.cs → src/Perspex.Controls/Templates/FuncDataTemplate.cs

@ -9,13 +9,13 @@ namespace Perspex.Controls.Templates
/// <summary>
/// Builds a control for a piece of data.
/// </summary>
public class DataTemplate : FuncTemplate<object, IControl>, IDataTemplate
public class FuncDataTemplate : FuncTemplate<object, IControl>, IDataTemplate
{
/// <summary>
/// The default data template used in the case where not matching data template is found.
/// </summary>
public static readonly DataTemplate Default =
new DataTemplate(typeof(object), o => (o != null) ? new TextBlock { Text = o.ToString() } : null);
public static readonly FuncDataTemplate Default =
new FuncDataTemplate(typeof(object), o => (o != null) ? new TextBlock { Text = o.ToString() } : null);
/// <summary>
/// The implementation of the <see cref="Match"/> method.
@ -23,19 +23,19 @@ namespace Perspex.Controls.Templates
private readonly Func<object, bool> _match;
/// <summary>
/// Initializes a new instance of the <see cref="DataTemplate"/> class.
/// Initializes a new instance of the <see cref="FuncDataTemplate"/> class.
/// </summary>
/// <param name="type">The type of data which the data template matches.</param>
/// <param name="build">
/// A function which when passed an object of <paramref name="type"/> returns a control.
/// </param>
public DataTemplate(Type type, Func<object, IControl> build)
public FuncDataTemplate(Type type, Func<object, IControl> build)
: this(o => IsInstance(o, type), build)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DataTemplate"/> class.
/// Initializes a new instance of the <see cref="FuncDataTemplate"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
@ -43,7 +43,7 @@ namespace Perspex.Controls.Templates
/// <param name="build">
/// A function which returns a control for matching data.
/// </param>
public DataTemplate(Func<object, bool> match, Func<object, IControl> build)
public FuncDataTemplate(Func<object, bool> match, Func<object, IControl> build)
: base(build)
{
Contract.Requires<ArgumentNullException>(match != null);

10
src/Perspex.Controls/Templates/DataTemplate`1.cs → src/Perspex.Controls/Templates/FuncDataTemplate`1.cs

@ -9,21 +9,21 @@ namespace Perspex.Controls.Templates
/// Builds a control for a piece of data of specified type.
/// </summary>
/// <typeparam name="T">The type of the template's data.</typeparam>
public class DataTemplate<T> : DataTemplate
public class FuncDataTemplate<T> : FuncDataTemplate
{
/// <summary>
/// Initializes a new instance of the <see cref="DataTemplate{T}"/> class.
/// Initializes a new instance of the <see cref="FuncDataTemplate{T}"/> class.
/// </summary>
/// <param name="build">
/// A function which when passed an object of <typeparamref name="T"/> returns a control.
/// </param>
public DataTemplate(Func<T, IControl> build)
public FuncDataTemplate(Func<T, IControl> build)
: base(typeof(T), CastBuild(build))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="DataTemplate{T}"/> class.
/// Initializes a new instance of the <see cref="FuncDataTemplate{T}"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
@ -31,7 +31,7 @@ namespace Perspex.Controls.Templates
/// <param name="build">
/// A function which when passed an object of <typeparamref name="T"/> returns a control.
/// </param>
public DataTemplate(Func<T, bool> match, Func<T, IControl> build)
public FuncDataTemplate(Func<T, bool> match, Func<T, IControl> build)
: base(CastMatch(match), CastBuild(build))
{
}

18
src/Perspex.Controls/Templates/TreeDataTemplate.cs → src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs

@ -10,14 +10,14 @@ namespace Perspex.Controls.Templates
/// <summary>
/// A template used to build hierachical data.
/// </summary>
public class TreeDataTemplate : DataTemplate, ITreeDataTemplate
public class FuncTreeDataTemplate : FuncDataTemplate, ITreeDataTemplate
{
private readonly Func<object, IEnumerable> _itemsSelector;
private readonly Func<object, bool> _isExpanded;
/// <summary>
/// Initializes a new instance of the <see cref="TreeDataTemplate"/> class.
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
/// <param name="type">The type of data which the data template matches.</param>
/// <param name="build">
@ -27,7 +27,7 @@ namespace Perspex.Controls.Templates
/// A function which when passed an object of <paramref name="type"/> returns the child
/// items.
/// </param>
public TreeDataTemplate(
public FuncTreeDataTemplate(
Type type,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector)
@ -36,7 +36,7 @@ namespace Perspex.Controls.Templates
}
/// <summary>
/// Initializes a new instance of the <see cref="TreeDataTemplate"/> class.
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
/// <param name="type">The type of data which the data template matches.</param>
/// <param name="build">
@ -50,7 +50,7 @@ namespace Perspex.Controls.Templates
/// A function which when passed an object of <paramref name="type"/> returns the the
/// initial expanded state of the node.
/// </param>
public TreeDataTemplate(
public FuncTreeDataTemplate(
Type type,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector,
@ -60,7 +60,7 @@ namespace Perspex.Controls.Templates
}
/// <summary>
/// Initializes a new instance of the <see cref="TreeDataTemplate"/> class.
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
@ -71,7 +71,7 @@ namespace Perspex.Controls.Templates
/// <param name="itemsSelector">
/// A function which when passed a matching object returns the child items.
/// </param>
public TreeDataTemplate(
public FuncTreeDataTemplate(
Func<object, bool> match,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector)
@ -81,7 +81,7 @@ namespace Perspex.Controls.Templates
}
/// <summary>
/// Initializes a new instance of the <see cref="TreeDataTemplate"/> class.
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
@ -96,7 +96,7 @@ namespace Perspex.Controls.Templates
/// A function which when passed a matching object returns the the initial expanded state
/// of the node.
/// </param>
public TreeDataTemplate(
public FuncTreeDataTemplate(
Func<object, bool> match,
Func<object, IControl> build,
Func<object, IEnumerable> itemsSelector,

18
src/Perspex.Controls/Templates/TreeDataTemplate`1.cs → src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs

@ -10,10 +10,10 @@ namespace Perspex.Controls.Templates
/// A template used to build hierachical data.
/// </summary>
/// <typeparam name="T">The type of the template's data.</typeparam>
public class TreeDataTemplate<T> : TreeDataTemplate
public class FuncTreeDataTemplate<T> : FuncTreeDataTemplate
{
/// <summary>
/// Initializes a new instance of the <see cref="TreeDataTemplate{T}"/> class.
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
/// <param name="build">
/// A function which when passed an object of <typeparamref name="T"/> returns a control.
@ -22,7 +22,7 @@ namespace Perspex.Controls.Templates
/// A function which when passed an object of <typeparamref name="T"/> returns the child
/// items.
/// </param>
public TreeDataTemplate(
public FuncTreeDataTemplate(
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector)
: base(
@ -33,7 +33,7 @@ namespace Perspex.Controls.Templates
}
/// <summary>
/// Initializes a new instance of the <see cref="TreeDataTemplate{T}"/> class.
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
/// <param name="build">
/// A function which when passed an object of <typeparamref name="T"/> returns a control.
@ -46,7 +46,7 @@ namespace Perspex.Controls.Templates
/// A function which when passed an object of <typeparamref name="T"/> returns the the
/// initial expanded state of the node.
/// </param>
public TreeDataTemplate(
public FuncTreeDataTemplate(
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector,
Func<T, bool> isExpanded)
@ -59,7 +59,7 @@ namespace Perspex.Controls.Templates
}
/// <summary>
/// Initializes a new instance of the <see cref="TreeDataTemplate{T}"/> class.
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
@ -70,7 +70,7 @@ namespace Perspex.Controls.Templates
/// <param name="itemsSelector">
/// A function which when passed a matching object returns the child items.
/// </param>
public TreeDataTemplate(
public FuncTreeDataTemplate(
Func<T, bool> match,
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector)
@ -82,7 +82,7 @@ namespace Perspex.Controls.Templates
}
/// <summary>
/// Initializes a new instance of the <see cref="TreeDataTemplate{T}"/> class.
/// Initializes a new instance of the <see cref="FuncTreeDataTemplate{T}"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
@ -97,7 +97,7 @@ namespace Perspex.Controls.Templates
/// A function which when passed a matching object returns the the initial expanded state
/// of the node.
/// </param>
public TreeDataTemplate(
public FuncTreeDataTemplate(
Func<T, bool> match,
Func<T, Control> build,
Func<T, IEnumerable> itemsSelector,

4
src/Perspex.Controls/TextBlock.cs

@ -65,7 +65,7 @@ namespace Perspex.Controls
/// Defines the <see cref="Text"/> property.
/// </summary>
public static readonly PerspexProperty<string> TextProperty =
PerspexProperty.Register<TextBlock, string>(nameof(Text));
PerspexProperty.Register<TextBlock, string>(nameof(Text), defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="TextAlignment"/> property.
@ -214,7 +214,7 @@ namespace Perspex.Controls
/// Renders the <see cref="TextBlock"/> to a drawing context.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
Brush background = Background;

2
src/Perspex.Diagnostics/Views/LogicalTreeView.cs

@ -48,7 +48,7 @@ namespace Perspex.Diagnostics.Views
{
DataTemplates = new DataTemplates
{
new TreeDataTemplate<LogicalTreeNode>(GetHeader, x => x.Children),
new FuncTreeDataTemplate<LogicalTreeNode>(GetHeader, x => x.Children),
},
[!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes),
}),

2
src/Perspex.Diagnostics/Views/VisualTreeView.cs

@ -49,7 +49,7 @@ namespace Perspex.Diagnostics.Views
{
DataTemplates = new DataTemplates
{
new TreeDataTemplate<VisualTreeNode>(GetHeader, x => x.Children),
new FuncTreeDataTemplate<VisualTreeNode>(GetHeader, x => x.Children),
},
[!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes),
}),

4
src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs

@ -32,7 +32,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
/// <summary>
/// The wrapped Perspex graphics object
/// </summary>
private readonly IDrawingContext _g;
private readonly DrawingContext _g;
/// <summary>
/// if to release the graphics object on dispose
@ -51,7 +51,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters
/// <param name="g">the Perspex graphics object to use</param>
/// <param name="initialClip">the initial clip of the graphics</param>
/// <param name="releaseGraphics">optional: if to release the graphics object on dispose (default - false)</param>
public GraphicsAdapter(IDrawingContext g, RRect initialClip, bool releaseGraphics = false)
public GraphicsAdapter(DrawingContext g, RRect initialClip, bool releaseGraphics = false)
: base(PerspexAdapter.Instance, initialClip)
{
ArgChecker.AssertArgNotNull(g, "g");

2
src/Perspex.HtmlRenderer/HtmlContainer.cs

@ -360,7 +360,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex
/// </summary>
/// <param name="g">the device to use to render</param>
/// <param name="clip">the clip rectangle of the html container</param>
public void PerformPaint(IDrawingContext g, Rect clip)
public void PerformPaint(DrawingContext g, Rect clip)
{
ArgChecker.AssertArgNotNull(g, "g");

2
src/Perspex.HtmlRenderer/HtmlControl.cs

@ -327,7 +327,7 @@ namespace Perspex.Controls.Html
private Size RenderSize => new Size(Bounds.Width, Bounds.Height);
public override void Render(IDrawingContext context)
public override void Render(DrawingContext context)
{
context.FillRectangle(Background, new Rect(RenderSize));

8
src/Perspex.Input/IInputElement.cs

@ -114,14 +114,8 @@ namespace Perspex.Input
void Focus();
/// <summary>
/// Returns the input element that can be found within the current control at the specified
/// position.
/// Gets the key bindings for the element.
/// </summary>
/// <param name="p">The position, in control coordinates.</param>
/// <returns>The <see cref="IInputElement"/> at the specified position.</returns>
IInputElement InputHitTest(Point p);
List<KeyBinding> KeyBindings { get; }
}
}

11
src/Perspex.Input/InputElement.cs

@ -339,17 +339,6 @@ namespace Perspex.Input
public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
/// <summary>
/// Returns the input element that can be found within the current control at the specified
/// position.
/// </summary>
/// <param name="p">The position, in control coordinates.</param>
/// <returns>The <see cref="IInputElement"/> at the specified position.</returns>
public IInputElement InputHitTest(Point p)
{
return this.GetInputElementsAt(p).FirstOrDefault();
}
/// <summary>
/// Focuses the control.
/// </summary>

59
src/Perspex.Input/InputExtensions.cs

@ -7,8 +7,19 @@ using System.Linq;
namespace Perspex.Input
{
/// <summary>
/// Defines extensions for the <see cref="IInputElement"/> interface.
/// </summary>
public static class InputExtensions
{
/// <summary>
/// Returns the active input elements at a point on an <see cref="IInputElement"/>.
/// </summary>
/// <param name="element">The element to test.</param>
/// <param name="p">The point on <paramref name="element"/>.</param>
/// <returns>
/// The active input elements found at the point, ordered topmost first.
/// </returns>
public static IEnumerable<IInputElement> GetInputElementsAt(this IInputElement element, Point p)
{
Contract.Requires<ArgumentNullException>(element != null);
@ -22,7 +33,7 @@ namespace Perspex.Input
if (element.VisualChildren.Any())
{
foreach (var child in element.VisualChildren.OfType<IInputElement>())
foreach (var child in ZSort(element.VisualChildren.OfType<IInputElement>()))
{
foreach (var result in child.GetInputElementsAt(p))
{
@ -34,5 +45,51 @@ namespace Perspex.Input
yield return element;
}
}
/// <summary>
/// Returns the topmost active input element at a point on an <see cref="IInputElement"/>.
/// </summary>
/// <param name="element">The element to test.</param>
/// <param name="p">The point on <paramref name="element"/>.</param>
/// <returns>The topmost <see cref="IInputElement"/> at the specified position.</returns>
public static IInputElement InputHitTest(this IInputElement element, Point p)
{
return element.GetInputElementsAt(p).First();
}
private static IEnumerable<IInputElement> ZSort(IEnumerable<IInputElement> elements)
{
return elements
.Select((element, index) => new ZOrderElement
{
Element = element,
Index = index,
ZIndex = element.ZIndex,
})
.OrderBy(x => x, null)
.Select(x => x.Element);
}
private class ZOrderElement : IComparable<ZOrderElement>
{
public IInputElement Element { get; set; }
public int Index { get; set; }
public int ZIndex { get; set; }
public int CompareTo(ZOrderElement other)
{
var z = other.ZIndex - ZIndex;
if (z != 0)
{
return z;
}
else
{
return other.Index - Index;
}
}
}
}
}

4
src/Perspex.SceneGraph/IVisual.cs

@ -76,10 +76,10 @@ namespace Perspex
int ZIndex { get; set; }
/// <summary>
/// Renders the scene graph node to a <see cref="IDrawingContext"/>.
/// Renders the scene graph node to a <see cref="DrawingContext"/>.
/// </summary>
/// <param name="context">The context.</param>
void Render(IDrawingContext context);
void Render(DrawingContext context);
/// <summary>
/// Returns a transform that transforms the visual's coordinates into the coordinates

2
src/Perspex.SceneGraph/Media/Color.cs

@ -106,7 +106,7 @@ namespace Perspex.Media
throw new FormatException($"Invalid color string: '{s}'.");
}
return FromUInt32(uint.Parse(s.Substring(1), NumberStyles.HexNumber) | or);
return FromUInt32(uint.Parse(s.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture) | or);
}
else
{

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save