145 changed files with 4107 additions and 1283 deletions
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8" ?> |
|||
<configuration> |
|||
<startup> |
|||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /> |
|||
</startup> |
|||
</configuration> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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")] |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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); } |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 49e6ec001f5873cf2290e0bc1f6f06ca9b9cf808 |
|||
Subproject commit 2a6309a4d9b60b848241a34bf2adfa16b52c7a85 |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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, |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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")] |
|||
@ -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> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue