Browse Source

Started refactoring events.

Tunnelled and bubbling events can now be represented by a single
RoutedEvent. Next need to implement 'handledEventsToo'.
pull/39/head
Steven Kirk 11 years ago
parent
commit
aa63b5fa15
  1. 2
      Perspex.Controls/Button.cs
  2. 2
      Perspex.Controls/Control.cs
  3. 6
      Perspex.Controls/Primitives/Thumb.cs
  4. 22
      Perspex.Input/InputElement.cs
  5. 251
      Perspex.Interactive.UnitTests/InteractiveTests.cs
  6. 99
      Perspex.Interactive.UnitTests/Perspex.Interactive.UnitTests.csproj
  7. 36
      Perspex.Interactive.UnitTests/Properties/AssemblyInfo.cs
  8. 6
      Perspex.Interactive.UnitTests/packages.config
  9. 83
      Perspex.Interactivity/Interactive.cs
  10. 23
      Perspex.Interactivity/RoutedEvent.cs
  11. 12
      Perspex.Interactivity/RoutedEventArgs.cs
  12. 10
      Perspex.SceneGraph/VisualTree/VisualExtensions.cs
  13. 11
      Perspex.sln

2
Perspex.Controls/Button.cs

@ -22,7 +22,7 @@ namespace Perspex.Controls
PerspexProperty.Register<Button, ClickMode>("ClickMode");
public static readonly RoutedEvent<RoutedEventArgs> ClickEvent =
RoutedEvent.Register<Button, RoutedEventArgs>("Click", RoutingStrategy.Bubble);
RoutedEvent.Register<Button, RoutedEventArgs>("Click", RoutingStrategies.Bubble);
static Button()
{

2
Perspex.Controls/Control.cs

@ -30,7 +30,7 @@ namespace Perspex.Controls
PerspexProperty.Register<Control, ITemplatedControl>("TemplatedParent");
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategy.Bubble);
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
private static readonly IPerspexReadOnlyList<ILogical> EmptyChildren = new PerspexSingleItemList<ILogical>();

6
Perspex.Controls/Primitives/Thumb.cs

@ -13,13 +13,13 @@ namespace Perspex.Controls.Primitives
public class Thumb : TemplatedControl
{
public static readonly RoutedEvent<VectorEventArgs> DragStartedEvent =
RoutedEvent.Register<Thumb, VectorEventArgs>("DragStarted", RoutingStrategy.Bubble);
RoutedEvent.Register<Thumb, VectorEventArgs>("DragStarted", RoutingStrategies.Bubble);
public static readonly RoutedEvent<VectorEventArgs> DragDeltaEvent =
RoutedEvent.Register<Thumb, VectorEventArgs>("DragDelta", RoutingStrategy.Bubble);
RoutedEvent.Register<Thumb, VectorEventArgs>("DragDelta", RoutingStrategies.Bubble);
public static readonly RoutedEvent<VectorEventArgs> DragCompletedEvent =
RoutedEvent.Register<Thumb, VectorEventArgs>("DragCompleted", RoutingStrategy.Bubble);
RoutedEvent.Register<Thumb, VectorEventArgs>("DragCompleted", RoutingStrategies.Bubble);
private Point? lastPoint;

22
Perspex.Input/InputElement.cs

@ -30,37 +30,37 @@ namespace Perspex.Input
PerspexProperty.Register<InputElement, bool>("IsPointerOver");
public static readonly RoutedEvent<RoutedEventArgs> GotFocusEvent =
RoutedEvent.Register<InputElement, RoutedEventArgs>("GotFocus", RoutingStrategy.Bubble);
RoutedEvent.Register<InputElement, RoutedEventArgs>("GotFocus", RoutingStrategies.Bubble);
public static readonly RoutedEvent<RoutedEventArgs> LostFocusEvent =
RoutedEvent.Register<InputElement, RoutedEventArgs>("LostFocus", RoutingStrategy.Bubble);
RoutedEvent.Register<InputElement, RoutedEventArgs>("LostFocus", RoutingStrategies.Bubble);
public static readonly RoutedEvent<KeyEventArgs> KeyDownEvent =
RoutedEvent.Register<InputElement, KeyEventArgs>("KeyDown", RoutingStrategy.Bubble);
RoutedEvent.Register<InputElement, KeyEventArgs>("KeyDown", RoutingStrategies.Bubble);
public static readonly RoutedEvent<KeyEventArgs> PreviewKeyDownEvent =
RoutedEvent.Register<InputElement, KeyEventArgs>("PreviewKeyDown", RoutingStrategy.Tunnel);
RoutedEvent.Register<InputElement, KeyEventArgs>("PreviewKeyDown", RoutingStrategies.Tunnel);
public static readonly RoutedEvent<PointerPressEventArgs> PreviewPointerPressedEvent =
RoutedEvent.Register<InputElement, PointerPressEventArgs>("PreviewPointerPressed", RoutingStrategy.Tunnel);
RoutedEvent.Register<InputElement, PointerPressEventArgs>("PreviewPointerPressed", RoutingStrategies.Tunnel);
public static readonly RoutedEvent<PointerEventArgs> PointerEnterEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerEnter", RoutingStrategy.Direct);
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerEnter", RoutingStrategies.Direct);
public static readonly RoutedEvent<PointerEventArgs> PointerLeaveEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerLeave", RoutingStrategy.Direct);
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerLeave", RoutingStrategies.Direct);
public static readonly RoutedEvent<PointerEventArgs> PointerMovedEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerMove", RoutingStrategy.Bubble);
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerMove", RoutingStrategies.Bubble);
public static readonly RoutedEvent<PointerPressEventArgs> PointerPressedEvent =
RoutedEvent.Register<InputElement, PointerPressEventArgs>("PointerPressed", RoutingStrategy.Bubble);
RoutedEvent.Register<InputElement, PointerPressEventArgs>("PointerPressed", RoutingStrategies.Bubble);
public static readonly RoutedEvent<PointerEventArgs> PointerReleasedEvent =
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerReleased", RoutingStrategy.Bubble);
RoutedEvent.Register<InputElement, PointerEventArgs>("PointerReleased", RoutingStrategies.Bubble);
public static readonly RoutedEvent<PointerWheelEventArgs> PointerWheelChangedEvent =
RoutedEvent.Register<InputElement, PointerWheelEventArgs>("PointerWheelChanged", RoutingStrategy.Bubble);
RoutedEvent.Register<InputElement, PointerWheelEventArgs>("PointerWheelChanged", RoutingStrategies.Bubble);
static InputElement()
{

251
Perspex.Interactive.UnitTests/InteractiveTests.cs

@ -0,0 +1,251 @@
// -----------------------------------------------------------------------
// <copyright file="InteractiveTests.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Interactive.UnitTests
{
using System;
using System.Collections.Generic;
using System.Linq;
using Perspex.Collections;
using Perspex.Interactivity;
using Perspex.VisualTree;
using Xunit;
public class InteractiveTests
{
[Fact]
public void Direct_Event_Should_Go_Straight_To_Source()
{
var ev = new RoutedEvent("test", RoutingStrategies.Direct, typeof(RoutedEventArgs), typeof(TestInteractive));
var invoked = new List<string>();
EventHandler<RoutedEventArgs> handler = (s, e) => invoked.Add(((TestInteractive)s).Id);
var target = this.CreateTree(ev, handler, RoutingStrategies.Direct);
var args = new RoutedEventArgs(ev, target);
target.RaiseEvent(args);
Assert.Equal(new[] { "2b" }, invoked);
}
[Fact]
public void Direct_Event_Should_Have_Route_Set_To_Direct()
{
var ev = new RoutedEvent("test", RoutingStrategies.Direct, typeof(RoutedEventArgs), typeof(TestInteractive));
bool called = false;
EventHandler<RoutedEventArgs> handler = (s, e) =>
{
Assert.Equal(RoutingStrategies.Direct, e.Route);
called = true;
};
var target = this.CreateTree(ev, handler, RoutingStrategies.Direct);
var args = new RoutedEventArgs(ev, target);
target.RaiseEvent(args);
Assert.True(called);
}
[Fact]
public void Bubbling_Event_Should_Bubble_Up()
{
var ev = new RoutedEvent("test", RoutingStrategies.Bubble, typeof(RoutedEventArgs), typeof(TestInteractive));
var invoked = new List<string>();
EventHandler<RoutedEventArgs> handler = (s, e) => invoked.Add(((TestInteractive)s).Id);
var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel);
var args = new RoutedEventArgs(ev, target);
target.RaiseEvent(args);
Assert.Equal(new[] { "2b", "1" }, invoked);
}
[Fact]
public void Tunneling_Event_Should_Tunnel()
{
var ev = new RoutedEvent("test", RoutingStrategies.Tunnel, typeof(RoutedEventArgs), typeof(TestInteractive));
var invoked = new List<string>();
EventHandler<RoutedEventArgs> handler = (s, e) => invoked.Add(((TestInteractive)s).Id);
var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel);
var args = new RoutedEventArgs(ev, target);
target.RaiseEvent(args);
Assert.Equal(new[] { "1", "2b" }, invoked);
}
[Fact]
public void Tunneling_Bubbling_Event_Should_Tunnel_Then_Bubble_Up()
{
var ev = new RoutedEvent(
"test",
RoutingStrategies.Bubble | RoutingStrategies.Tunnel,
typeof(RoutedEventArgs),
typeof(TestInteractive));
var invoked = new List<string>();
EventHandler<RoutedEventArgs> handler = (s, e) => invoked.Add(((TestInteractive)s).Id);
var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel);
var args = new RoutedEventArgs(ev, target);
target.RaiseEvent(args);
Assert.Equal(new[] { "1", "2b", "2b", "1" }, invoked);
}
[Fact]
public void Events_Should_Have_Route_Set()
{
var ev = new RoutedEvent(
"test",
RoutingStrategies.Bubble | RoutingStrategies.Tunnel,
typeof(RoutedEventArgs),
typeof(TestInteractive));
var invoked = new List<RoutingStrategies>();
EventHandler<RoutedEventArgs> handler = (s, e) => invoked.Add(e.Route);
var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel);
var args = new RoutedEventArgs(ev, target);
target.RaiseEvent(args);
Assert.Equal(new[]
{
RoutingStrategies.Tunnel,
RoutingStrategies.Tunnel,
RoutingStrategies.Bubble,
RoutingStrategies.Bubble,
},
invoked);
}
[Fact]
public void Direct_Subscription_Should_Not_Catch_Tunneling_Or_Bubbling()
{
var ev = new RoutedEvent(
"test",
RoutingStrategies.Bubble | RoutingStrategies.Tunnel,
typeof(RoutedEventArgs),
typeof(TestInteractive));
var count = 0;
EventHandler<RoutedEventArgs> handler = (s, e) =>
{
++count;
};
var target = this.CreateTree(ev, handler, RoutingStrategies.Direct);
var args = new RoutedEventArgs(ev, target);
target.RaiseEvent(args);
Assert.Equal(0, count);
}
[Fact]
public void Bubbling_Subscription_Should_Not_Catch_Tunneling()
{
var ev = new RoutedEvent(
"test",
RoutingStrategies.Bubble | RoutingStrategies.Tunnel,
typeof(RoutedEventArgs),
typeof(TestInteractive));
var count = 0;
EventHandler<RoutedEventArgs> handler = (s, e) =>
{
Assert.Equal(RoutingStrategies.Bubble, e.Route);
++count;
};
var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble);
var args = new RoutedEventArgs(ev, target);
target.RaiseEvent(args);
Assert.Equal(2, count);
}
[Fact]
public void Tunneling_Subscription_Should_Not_Catch_Bubbling()
{
var ev = new RoutedEvent(
"test",
RoutingStrategies.Bubble | RoutingStrategies.Tunnel,
typeof(RoutedEventArgs),
typeof(TestInteractive));
var count = 0;
EventHandler<RoutedEventArgs> handler = (s, e) =>
{
Assert.Equal(RoutingStrategies.Tunnel, e.Route);
++count;
};
var target = this.CreateTree(ev, handler, RoutingStrategies.Tunnel);
var args = new RoutedEventArgs(ev, target);
target.RaiseEvent(args);
Assert.Equal(2, count);
}
private TestInteractive CreateTree(
RoutedEvent ev,
EventHandler<RoutedEventArgs> handler,
RoutingStrategies handlerRoutes)
{
TestInteractive target;
var tree = new TestInteractive
{
Id = "1",
Children = new[]
{
new TestInteractive
{
Id = "2a",
},
(target = new TestInteractive
{
Id = "2b",
Children = new[]
{
new TestInteractive
{
Id = "3",
},
},
}),
}
};
foreach (var i in tree.GetSelfAndVisualDescendents().Cast<Interactive>())
{
i.AddHandler(ev, handler, handlerRoutes, false);
}
return target;
}
private class TestInteractive : Interactive
{
public string Id { get; set; }
public IEnumerable<IVisual> Children
{
get
{
return ((IVisual)this).VisualChildren.AsEnumerable();
}
set
{
this.AddVisualChildren(value.Cast<Visual>());
}
}
}
}
}

99
Perspex.Interactive.UnitTests/Perspex.Interactive.UnitTests.csproj

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\xunit.runner.visualstudio.2.0.0-rc1-build1030\build\net20\xunit.runner.visualstudio.props" Condition="Exists('..\packages\xunit.runner.visualstudio.2.0.0-rc1-build1030\build\net20\xunit.runner.visualstudio.props')" />
<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>{08478EF5-44E8-42E9-92D6-15E00EC038D8}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Perspex.Interactive.UnitTests</RootNamespace>
<AssemblyName>Perspex.Interactive.UnitTests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>ff1a0665</NuGetPackageImportStamp>
</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>
</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>
</PropertyGroup>
<ItemGroup>
<Reference Include="Splat">
<HintPath>..\packages\Splat.1.6.0\lib\Net45\Splat.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="xunit">
<HintPath>..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="InteractiveTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NGenerics\NGenerics.csproj">
<Project>{415e048e-4611-4815-9cf2-d774e29079ac}</Project>
<Name>NGenerics</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Animation\Perspex.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Perspex.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Base\Perspex.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Perspex.Base</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Interactivity\Perspex.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Perspex.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.Layout\Perspex.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Perspex.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\Perspex.SceneGraph\Perspex.SceneGraph.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Perspex.SceneGraph</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\xunit.runner.visualstudio.2.0.0-rc1-build1030\build\net20\xunit.runner.visualstudio.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\xunit.runner.visualstudio.2.0.0-rc1-build1030\build\net20\xunit.runner.visualstudio.props'))" />
</Target>
<!-- 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>

36
Perspex.Interactive.UnitTests/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("Perspex.Interactive.UnitTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Perspex.Interactive.UnitTests")]
[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("08478ef5-44e8-42e9-92d6-15e00ec038d8")]
// 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")]

6
Perspex.Interactive.UnitTests/packages.config

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Splat" version="1.6.0" targetFramework="net45" />
<package id="xunit" version="1.9.2" targetFramework="net45" />
<package id="xunit.runner.visualstudio" version="2.0.0-rc1-build1030" targetFramework="net45" />
</packages>

83
Perspex.Interactivity/Interactive.cs

@ -16,22 +16,32 @@ namespace Perspex.Interactivity
public class Interactive : Layoutable, IInteractive
{
private Dictionary<RoutedEvent, List<Delegate>> eventHandlers = new Dictionary<RoutedEvent, List<Delegate>>();
public void AddHandler(RoutedEvent routedEvent, Delegate handler)
private Dictionary<RoutedEvent, List<Subscription>> eventHandlers =
new Dictionary<RoutedEvent, List<Subscription>>();
public void AddHandler(
RoutedEvent routedEvent,
Delegate handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false)
{
Contract.Requires<NullReferenceException>(routedEvent != null);
Contract.Requires<NullReferenceException>(handler != null);
List<Delegate> delegates;
List<Subscription> subscriptions;
if (!this.eventHandlers.TryGetValue(routedEvent, out delegates))
if (!this.eventHandlers.TryGetValue(routedEvent, out subscriptions))
{
delegates = new List<Delegate>();
this.eventHandlers.Add(routedEvent, delegates);
subscriptions = new List<Subscription>();
this.eventHandlers.Add(routedEvent, subscriptions);
}
delegates.Add(handler);
subscriptions.Add(new Subscription
{
Handler = handler,
Routes = routes,
AlsoIfHandled = handledEventsToo,
});
}
public IObservable<EventPattern<T>> GetObservable<T>(RoutedEvent<T> routedEvent) where T : RoutedEventArgs
@ -48,11 +58,11 @@ namespace Perspex.Interactivity
Contract.Requires<NullReferenceException>(routedEvent != null);
Contract.Requires<NullReferenceException>(handler != null);
List<Delegate> delegates;
List<Subscription> subscriptions;
if (this.eventHandlers.TryGetValue(routedEvent, out delegates))
if (this.eventHandlers.TryGetValue(routedEvent, out subscriptions))
{
delegates.Remove(handler);
subscriptions.RemoveAll(x => x.Handler == handler);
}
}
@ -65,17 +75,20 @@ namespace Perspex.Interactivity
if (!e.Handled)
{
switch (e.RoutedEvent.RoutingStrategy)
if (e.RoutedEvent.RoutingStrategies == RoutingStrategies.Direct)
{
e.Route = RoutingStrategies.Direct;
this.RaiseEventImpl(e);
}
if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Tunnel) != 0)
{
this.TunnelEvent(e);
}
if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Bubble) != 0)
{
case RoutingStrategy.Bubble:
this.BubbleEvent(e);
break;
case RoutingStrategy.Direct:
this.RaiseEventImpl(e);
break;
case RoutingStrategy.Tunnel:
this.TunnelEvent(e);
break;
this.BubbleEvent(e);
}
}
}
@ -84,6 +97,8 @@ namespace Perspex.Interactivity
{
Contract.Requires<NullReferenceException>(e != null);
e.Route = RoutingStrategies.Bubble;
foreach (var target in this.GetSelfAndVisualAncestors().OfType<Interactive>())
{
target.RaiseEventImpl(e);
@ -99,6 +114,8 @@ namespace Perspex.Interactivity
{
Contract.Requires<NullReferenceException>(e != null);
e.Route = RoutingStrategies.Tunnel;
foreach (var target in this.GetSelfAndVisualAncestors().OfType<Interactive>().Reverse())
{
target.RaiseEventImpl(e);
@ -114,17 +131,33 @@ namespace Perspex.Interactivity
{
Contract.Requires<NullReferenceException>(e != null);
List<Delegate> delegates;
List<Subscription> subscriptions;
e.RoutedEvent.InvokeRaised(this, e);
if (this.eventHandlers.TryGetValue(e.RoutedEvent, out delegates))
if (this.eventHandlers.TryGetValue(e.RoutedEvent, out subscriptions))
{
foreach (Delegate handler in delegates.ToList())
foreach (var sub in subscriptions)
{
handler.DynamicInvoke(this, e);
bool invoke =
(e.Route == RoutingStrategies.Direct && sub.Routes == RoutingStrategies.Direct) ||
(e.Route != RoutingStrategies.Direct && (e.Route & sub.Routes) != 0);
if (invoke)
{
sub.Handler.DynamicInvoke(this, e);
}
}
}
}
private class Subscription
{
public Delegate Handler { get; set; }
public RoutingStrategies Routes { get; set; }
public bool AlsoIfHandled { get; set; }
}
}
}

23
Perspex.Interactivity/RoutedEvent.cs

@ -10,18 +10,19 @@ namespace Perspex.Interactivity
using System.Linq.Expressions;
using System.Reflection;
public enum RoutingStrategy
[Flags]
public enum RoutingStrategies
{
Tunnel,
Bubble,
Direct,
Direct = 0,
Tunnel = 1,
Bubble = 2,
}
public class RoutedEvent
{
public RoutedEvent(
string name,
RoutingStrategy routingStrategy,
RoutingStrategies routingStrategies,
Type eventArgsType,
Type ownerType)
{
@ -34,7 +35,7 @@ namespace Perspex.Interactivity
this.EventArgsType = eventArgsType;
this.Name = name;
this.OwnerType = ownerType;
this.RoutingStrategy = routingStrategy;
this.RoutingStrategies = routingStrategies;
}
public event EventHandler<RoutedEventArgs> Raised;
@ -57,7 +58,7 @@ namespace Perspex.Interactivity
private set;
}
public RoutingStrategy RoutingStrategy
public RoutingStrategies RoutingStrategies
{
get;
private set;
@ -65,7 +66,7 @@ namespace Perspex.Interactivity
public static RoutedEvent<TEventArgs> Register<TOwner, TEventArgs>(
string name,
RoutingStrategy routingStrategy)
RoutingStrategies routingStrategy)
where TOwner : IInteractive
where TEventArgs : RoutedEventArgs
{
@ -76,7 +77,7 @@ namespace Perspex.Interactivity
public static RoutedEvent<TEventArgs> Register<TEventArgs>(
string name,
RoutingStrategy routingStrategy,
RoutingStrategies routingStrategy,
Type ownerType)
where TEventArgs : RoutedEventArgs
{
@ -97,8 +98,8 @@ namespace Perspex.Interactivity
public class RoutedEvent<TEventArgs> : RoutedEvent
where TEventArgs : RoutedEventArgs
{
public RoutedEvent(string name, RoutingStrategy routingStrategy, Type ownerType)
: base(name, routingStrategy, typeof(TEventArgs), ownerType)
public RoutedEvent(string name, RoutingStrategies routingStrategies, Type ownerType)
: base(name, routingStrategies, typeof(TEventArgs), ownerType)
{
Contract.Requires<NullReferenceException>(name != null);
Contract.Requires<NullReferenceException>(ownerType != null);

12
Perspex.Interactivity/RoutedEventArgs.cs

@ -10,12 +10,24 @@ namespace Perspex.Interactivity
public class RoutedEventArgs : EventArgs
{
public RoutedEventArgs()
{
}
public RoutedEventArgs(RoutedEvent routedEvent, IInteractive source)
{
this.RoutedEvent = routedEvent;
this.Source = this.OriginalSource = source;
}
public bool Handled { get; set; }
public IInteractive OriginalSource { get; set; }
public RoutedEvent RoutedEvent { get; set; }
public RoutingStrategies Route { get; set; }
public IInteractive Source { get; set; }
}
}

10
Perspex.SceneGraph/VisualTree/VisualExtensions.cs

@ -83,6 +83,16 @@ namespace Perspex.VisualTree
}
}
public static IEnumerable<IVisual> GetSelfAndVisualDescendents(this IVisual visual)
{
yield return visual;
foreach (var ancestor in visual.GetVisualDescendents())
{
yield return ancestor;
}
}
public static IVisual GetVisualParent(this IVisual visual)
{
return visual.VisualParent;

11
Perspex.sln

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0
# Visual Studio 14
VisualStudioVersion = 14.0.22512.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Win32", "Windows\Perspex.Win32\Perspex.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}"
EndProject
@ -63,6 +63,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{596AF7
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Interactive.UnitTests", "Perspex.Interactive.UnitTests\Perspex.Interactive.UnitTests.csproj", "{08478EF5-44E8-42E9-92D6-15E00EC038D8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -164,6 +166,10 @@ Global
{D211E587-D8BC-45B9-95A4-F297C8FA5200}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D211E587-D8BC-45B9-95A4-F297C8FA5200}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D211E587-D8BC-45B9-95A4-F297C8FA5200}.Release|Any CPU.Build.0 = Release|Any CPU
{08478EF5-44E8-42E9-92D6-15E00EC038D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08478EF5-44E8-42E9-92D6-15E00EC038D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08478EF5-44E8-42E9-92D6-15E00EC038D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08478EF5-44E8-42E9-92D6-15E00EC038D8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -180,5 +186,6 @@ Global
{2905FF23-53FB-45E6-AA49-6AF47A172056} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{415E048E-4611-4815-9CF2-D774E29079AC} = {2BAFBE53-7FA4-4BB9-976F-9AFCC4F9847D}
{DB070A10-BF39-4752-8456-86E9D5928478} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{08478EF5-44E8-42E9-92D6-15E00EC038D8} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
EndGlobal

Loading…
Cancel
Save