68 changed files with 1018 additions and 252 deletions
@ -0,0 +1,33 @@ |
|||||
|
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using Perspex.Markup.Xaml.Binding; |
||||
|
using OmniXaml; |
||||
|
using OmniXaml.Typing; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
namespace Perspex.Markup.Xaml.Context |
||||
|
{ |
||||
|
public class PerspexAttachableXamlMember : AttachableXamlMember |
||||
|
{ |
||||
|
public PerspexAttachableXamlMember(string name, |
||||
|
XamlType owner, |
||||
|
MethodInfo getter, |
||||
|
MethodInfo setter, |
||||
|
IXamlTypeRepository xamlTypeRepository, |
||||
|
ITypeFeatureProvider featureProvider) |
||||
|
: base(name, getter, setter, xamlTypeRepository, featureProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override IXamlMemberValuePlugin LookupXamlMemberValueConnector() |
||||
|
{ |
||||
|
return new PerspexXamlMemberValuePlugin(this); |
||||
|
} |
||||
|
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return "Perspex Attachable XAML Member " + base.ToString(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Perspex.Platform; |
||||
|
|
||||
|
namespace Perspex |
||||
|
{ |
||||
|
public abstract class PerspexDisposable : IDisposable |
||||
|
{ |
||||
|
#if DEBUG_DISPOSE
|
||||
|
public string DisposedAt { get; private set; } |
||||
|
#endif
|
||||
|
|
||||
|
|
||||
|
public bool IsDisposed { get; private set; } |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
IsDisposed = true; |
||||
|
#if DEBUG_DISPOSE
|
||||
|
DisposedAt = PerspexLocator.Current.GetService<IPclPlatformWrapper>().GetStackTrace(); |
||||
|
#endif
|
||||
|
DoDispose(); |
||||
|
} |
||||
|
|
||||
|
protected void CheckDisposed() |
||||
|
{ |
||||
|
if (IsDisposed) |
||||
|
throw new ObjectDisposedException(GetType().FullName |
||||
|
#if DEBUG_DISPOSE
|
||||
|
, "Disposed at: \n" + DisposedAt |
||||
|
#endif
|
||||
|
|
||||
|
); |
||||
|
} |
||||
|
|
||||
|
protected abstract void DoDispose(); |
||||
|
} |
||||
|
} |
||||
@ -1,11 +1,23 @@ |
|||||
// Copyright (c) The Perspex Project. All rights reserved.
|
// 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.
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using Perspex.Controls.Primitives; |
||||
|
|
||||
namespace Perspex.Controls |
namespace Perspex.Controls |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the placement for a <see cref="Popup"/> control.
|
||||
|
/// </summary>
|
||||
public enum PlacementMode |
public enum PlacementMode |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// The popup is placed at the bottom left of its target.
|
||||
|
/// </summary>
|
||||
Bottom, |
Bottom, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The popup is placed at the top right of its target.
|
||||
|
/// </summary>
|
||||
Right, |
Right, |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,18 +1,27 @@ |
|||||
// Copyright (c) The Perspex Project. All rights reserved.
|
// 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.
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
using System; |
|
||||
using Perspex.Controls; |
|
||||
using Perspex.Input.Raw; |
|
||||
|
|
||||
namespace Perspex.Platform |
namespace Perspex.Platform |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Defines a platform-specific popup window implementation.
|
||||
|
/// </summary>
|
||||
public interface IPopupImpl : ITopLevelImpl |
public interface IPopupImpl : ITopLevelImpl |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Sets the position of the popup.
|
||||
|
/// </summary>
|
||||
|
/// <param name="p">The position, in screen coordinates.</param>
|
||||
void SetPosition(Point p); |
void SetPosition(Point p); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Shows the popup.
|
||||
|
/// </summary>
|
||||
void Show(); |
void Show(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Hides the popup.
|
||||
|
/// </summary>
|
||||
void Hide(); |
void Hide(); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,14 +1,22 @@ |
|||||
using System; |
// Copyright (c) The Perspex Project. All rights reserved.
|
||||
using System.Collections.Generic; |
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Perspex.Platform; |
using Perspex.Platform; |
||||
|
|
||||
namespace Perspex.Controls.Platform |
namespace Perspex.Controls.Platform |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Defines a platform-specific system dialog implementation.
|
||||
|
/// </summary>
|
||||
public interface ISystemDialogImpl |
public interface ISystemDialogImpl |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Shows a file dialog.
|
||||
|
/// </summary>
|
||||
|
/// <param name="dialog">The details of the file dialog to show.</param>
|
||||
|
/// <param name="parent">The parent window.</param>
|
||||
|
/// <returns>A task returning the selected filenames.</returns>
|
||||
Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent); |
Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,206 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Perspex.Media.Imaging; |
||||
|
|
||||
|
namespace Perspex.Media |
||||
|
{ |
||||
|
public sealed class DrawingContext : IDisposable |
||||
|
{ |
||||
|
private readonly IDrawingContextImpl _impl; |
||||
|
private int _currentLevel; |
||||
|
|
||||
|
private Stack<TransformContainer> _transformContainers = new Stack<TransformContainer>(); |
||||
|
|
||||
|
struct TransformContainer |
||||
|
{ |
||||
|
public Matrix LocalTransform; |
||||
|
public Matrix ContainerTransform; |
||||
|
|
||||
|
public TransformContainer(Matrix localTransform, Matrix containerTransform) |
||||
|
{ |
||||
|
LocalTransform = localTransform; |
||||
|
ContainerTransform = containerTransform; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DrawingContext(IDrawingContextImpl impl) |
||||
|
{ |
||||
|
_impl = impl; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private Matrix _currentTransform = Matrix.Identity; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current transform of the drawing context.
|
||||
|
/// </summary>
|
||||
|
public Matrix CurrentTransform |
||||
|
{ |
||||
|
get { return _currentTransform; } |
||||
|
private set |
||||
|
{ |
||||
|
_currentTransform = value; |
||||
|
_impl.Transform = _currentTransform*_currentContainerTransform; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Matrix _currentContainerTransform = Matrix.Identity; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws a bitmap image.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The bitmap image.</param>
|
||||
|
/// <param name="opacity">The opacity to draw with.</param>
|
||||
|
/// <param name="sourceRect">The rect in the image to draw.</param>
|
||||
|
/// <param name="destRect">The rect in the output to draw to.</param>
|
||||
|
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect) |
||||
|
=> _impl.DrawImage(source, opacity, sourceRect, destRect); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws a line.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pen">The stroke pen.</param>
|
||||
|
/// <param name="p1">The first point of the line.</param>
|
||||
|
/// <param name="p2">The second point of the line.</param>
|
||||
|
public void DrawLine(Pen pen, Point p1, Point p2) => _impl.DrawLine(pen, p1, p2); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws a geometry.
|
||||
|
/// </summary>
|
||||
|
/// <param name="brush">The fill brush.</param>
|
||||
|
/// <param name="pen">The stroke pen.</param>
|
||||
|
/// <param name="geometry">The geometry.</param>
|
||||
|
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) => _impl.DrawGeometry(brush, pen, geometry); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws the outline of a rectangle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="pen">The pen.</param>
|
||||
|
/// <param name="rect">The rectangle bounds.</param>
|
||||
|
/// <param name="cornerRadius">The corner radius.</param>
|
||||
|
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0.0f) |
||||
|
=> _impl.DrawRectangle(pen, rect, cornerRadius); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws text.
|
||||
|
/// </summary>
|
||||
|
/// <param name="foreground">The foreground brush.</param>
|
||||
|
/// <param name="origin">The upper-left corner of the text.</param>
|
||||
|
/// <param name="text">The text.</param>
|
||||
|
public void DrawText(Brush foreground, Point origin, FormattedText text) |
||||
|
=> _impl.DrawText(foreground, origin, text); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Draws a filled rectangle.
|
||||
|
/// </summary>
|
||||
|
/// <param name="brush">The brush.</param>
|
||||
|
/// <param name="rect">The rectangle bounds.</param>
|
||||
|
/// <param name="cornerRadius">The corner radius.</param>
|
||||
|
public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0.0f) |
||||
|
=> _impl.FillRectangle(brush, rect, cornerRadius); |
||||
|
|
||||
|
public struct PushedState : IDisposable |
||||
|
{ |
||||
|
private readonly int _level; |
||||
|
private readonly DrawingContext _context; |
||||
|
private readonly Matrix _matrix; |
||||
|
private readonly PushedStateType _type; |
||||
|
|
||||
|
public enum PushedStateType |
||||
|
{ |
||||
|
None, |
||||
|
Matrix, |
||||
|
Opacity, |
||||
|
Clip, |
||||
|
MatrixContainer |
||||
|
} |
||||
|
|
||||
|
public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) |
||||
|
{ |
||||
|
_level = context._currentLevel += 1; |
||||
|
_context = context; |
||||
|
_type = type; |
||||
|
_matrix = matrix; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
if(_type == PushedStateType.None) |
||||
|
return; |
||||
|
if (_context._currentLevel != _level) |
||||
|
throw new InvalidOperationException("Wrong Push/Pop state order"); |
||||
|
_context._currentLevel--; |
||||
|
if (_type == PushedStateType.Matrix) |
||||
|
_context.CurrentTransform = _matrix; |
||||
|
else if(_type == PushedStateType.Clip) |
||||
|
_context._impl.PopClip(); |
||||
|
else if(_type == PushedStateType.Opacity) |
||||
|
_context._impl.PopOpacity(); |
||||
|
else if (_type == PushedStateType.MatrixContainer) |
||||
|
{ |
||||
|
var cont = _context._transformContainers.Pop(); |
||||
|
_context._currentContainerTransform = cont.ContainerTransform; |
||||
|
_context.CurrentTransform = cont.LocalTransform; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Pushes a clip rectange.
|
||||
|
/// </summary>
|
||||
|
/// <param name="clip">The clip rectangle.</param>
|
||||
|
/// <returns>A disposable used to undo the clip rectangle.</returns>
|
||||
|
public PushedState PushClip(Rect clip) |
||||
|
{ |
||||
|
_impl.PushClip(clip); |
||||
|
return new PushedState(this, PushedState.PushedStateType.Clip); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Pushes an opacity value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="opacity">The opacity.</param>
|
||||
|
/// <returns>A disposable used to undo the opacity.</returns>
|
||||
|
public PushedState PushOpacity(double opacity) |
||||
|
//TODO: Elimintate platform-specific push opacity call
|
||||
|
{ |
||||
|
_impl.PushOpacity(opacity); |
||||
|
return new PushedState(this, PushedState.PushedStateType.Opacity); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Pushes a matrix transformation.
|
||||
|
/// </summary>
|
||||
|
/// <param name="matrix">The matrix</param>
|
||||
|
/// <returns>A disposable used to undo the transformation.</returns>
|
||||
|
public PushedState PushPostTransform(Matrix matrix) => PushSetTransform(CurrentTransform*matrix); |
||||
|
|
||||
|
public PushedState PushPreTransform(Matrix matrix) => PushSetTransform(matrix*CurrentTransform); |
||||
|
|
||||
|
|
||||
|
PushedState PushSetTransform(Matrix matrix) |
||||
|
{ |
||||
|
var oldMatrix = CurrentTransform; |
||||
|
CurrentTransform = matrix; |
||||
|
|
||||
|
return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public PushedState PushTransformContainer() |
||||
|
{ |
||||
|
_transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform)); |
||||
|
_currentContainerTransform = CurrentTransform*_currentContainerTransform; |
||||
|
_currentTransform = Matrix.Identity; |
||||
|
return new PushedState(this, PushedState.PushedStateType.MatrixContainer); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() => _impl.Dispose(); |
||||
|
} |
||||
|
} |
||||
@ -1,76 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.ComponentModel.DataAnnotations; |
|
||||
using System.Reactive.Disposables; |
|
||||
using Perspex.Media.Imaging; |
|
||||
|
|
||||
namespace Perspex.Media |
|
||||
{ |
|
||||
public class ValidatingDrawingContext : IDrawingContext |
|
||||
{ |
|
||||
private readonly IDrawingContext _base; |
|
||||
|
|
||||
public ValidatingDrawingContext(IDrawingContext @base) |
|
||||
{ |
|
||||
_base = @base; |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
_base.Dispose(); |
|
||||
} |
|
||||
|
|
||||
public Matrix CurrentTransform => _base.CurrentTransform; |
|
||||
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect) |
|
||||
{ |
|
||||
_base.DrawImage(source, opacity, sourceRect, destRect); |
|
||||
} |
|
||||
|
|
||||
public void DrawLine(Pen pen, Point p1, Point p2) |
|
||||
{ |
|
||||
_base.DrawLine(pen, p1, p2); |
|
||||
} |
|
||||
|
|
||||
public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) |
|
||||
{ |
|
||||
_base.DrawGeometry(brush, pen, geometry); |
|
||||
} |
|
||||
|
|
||||
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0) |
|
||||
{ |
|
||||
_base.DrawRectangle(pen, rect, cornerRadius); |
|
||||
} |
|
||||
|
|
||||
public void DrawText(Brush foreground, Point origin, FormattedText text) |
|
||||
{ |
|
||||
_base.DrawText(foreground, origin, text); |
|
||||
} |
|
||||
|
|
||||
public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0) |
|
||||
{ |
|
||||
_base.FillRectangle(brush, rect, cornerRadius); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
Stack<IDisposable> _stateStack = new Stack<IDisposable>(); |
|
||||
|
|
||||
IDisposable Transform(IDisposable disposable) |
|
||||
{ |
|
||||
_stateStack.Push(disposable); |
|
||||
return Disposable.Create(() => |
|
||||
{ |
|
||||
var current = _stateStack.Peek(); |
|
||||
if (current != disposable) |
|
||||
throw new InvalidOperationException("Invalid push/pop order"); |
|
||||
current.Dispose(); |
|
||||
_stateStack.Pop(); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
public IDisposable PushClip(Rect clip) => Transform(_base.PushClip(clip)); |
|
||||
|
|
||||
public IDisposable PushOpacity(double opacity) => Transform(_base.PushOpacity(opacity)); |
|
||||
|
|
||||
public IDisposable PushTransform(Matrix matrix) => Transform(_base.PushTransform(matrix)); |
|
||||
} |
|
||||
} |
|
||||
Binary file not shown.
@ -0,0 +1,101 @@ |
|||||
|
// 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.Linq; |
||||
|
using Perspex.Controls; |
||||
|
using Perspex.Controls.Presenters; |
||||
|
using Perspex.Controls.Templates; |
||||
|
using Perspex.LogicalTree; |
||||
|
using Perspex.Styling; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Perspex.Controls.UnitTests |
||||
|
{ |
||||
|
public class TreeViewTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void LogicalChildren_Should_Be_Set() |
||||
|
{ |
||||
|
var target = new TreeView |
||||
|
{ |
||||
|
Template = new ControlTemplate(CreateTreeViewTemplate), |
||||
|
Items = new[] { "Foo", "Bar", "Baz " }, |
||||
|
}; |
||||
|
|
||||
|
target.ApplyTemplate(); |
||||
|
|
||||
|
Assert.Equal(3, target.GetLogicalChildren().Count()); |
||||
|
|
||||
|
foreach (var child in target.GetLogicalChildren()) |
||||
|
{ |
||||
|
Assert.IsType<TreeViewItem>(child); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DataContexts_Should_Be_Correctly_Set() |
||||
|
{ |
||||
|
var items = new object[] |
||||
|
{ |
||||
|
"Foo", |
||||
|
new Item("Bar"), |
||||
|
new TextBlock { Text = "Baz" }, |
||||
|
new TreeViewItem { Header = "Qux" }, |
||||
|
}; |
||||
|
|
||||
|
var target = new TreeView |
||||
|
{ |
||||
|
Template = new ControlTemplate(CreateTreeViewTemplate), |
||||
|
DataContext = "Base", |
||||
|
DataTemplates = new DataTemplates |
||||
|
{ |
||||
|
new FuncDataTemplate<Item>(x => new Button { Content = x }) |
||||
|
}, |
||||
|
Items = items, |
||||
|
}; |
||||
|
|
||||
|
target.ApplyTemplate(); |
||||
|
|
||||
|
var dataContexts = target.Presenter.Panel.Children |
||||
|
.Cast<Control>() |
||||
|
.Select(x => x.DataContext) |
||||
|
.ToList(); |
||||
|
|
||||
|
Assert.Equal( |
||||
|
new object[] { items[0], items[1], "Base", "Base" }, |
||||
|
dataContexts); |
||||
|
} |
||||
|
|
||||
|
private Control CreateTreeViewTemplate(ITemplatedControl parent) |
||||
|
{ |
||||
|
return new ScrollViewer |
||||
|
{ |
||||
|
Template = new ControlTemplate(CreateScrollViewerTemplate), |
||||
|
Content = new ItemsPresenter |
||||
|
{ |
||||
|
Name = "itemsPresenter", |
||||
|
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty), |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private Control CreateScrollViewerTemplate(ITemplatedControl parent) |
||||
|
{ |
||||
|
return new ScrollContentPresenter |
||||
|
{ |
||||
|
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private class Item |
||||
|
{ |
||||
|
public Item(string value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
|
||||
|
public string Value { get; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
// 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.Reactive.Linq; |
||||
|
using Perspex.Markup.Binding; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Perspex.Markup.UnitTests.Binding |
||||
|
{ |
||||
|
public class ExpressionObserverTests_SetValue |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Set_Simple_Property_Value() |
||||
|
{ |
||||
|
var data = new { Foo = "foo" }; |
||||
|
var target = new ExpressionObserver(data, "Foo"); |
||||
|
|
||||
|
target.SetValue("bar"); |
||||
|
|
||||
|
Assert.Equal("foo", data.Foo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Set_Value_On_Simple_Property_Chain() |
||||
|
{ |
||||
|
var data = new Class1 { Foo = new Class2 { Bar = "bar" } }; |
||||
|
var target = new ExpressionObserver(data, "Foo.Bar"); |
||||
|
|
||||
|
target.SetValue("foo"); |
||||
|
|
||||
|
Assert.Equal("foo", data.Foo.Bar); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Not_Try_To_Set_Value_On_Broken_Chain() |
||||
|
{ |
||||
|
var data = new Class1 { Foo = new Class2 { Bar = "bar" } }; |
||||
|
var target = new ExpressionObserver(data, "Foo.Bar"); |
||||
|
|
||||
|
// Ensure the ExpressionObserver's subscriptions are kept active.
|
||||
|
target.OfType<string>().Subscribe(x => { }); |
||||
|
|
||||
|
data.Foo = null; |
||||
|
|
||||
|
Assert.False(target.SetValue("foo")); |
||||
|
} |
||||
|
|
||||
|
private class Class1 : NotifyingBase |
||||
|
{ |
||||
|
private Class2 _foo; |
||||
|
|
||||
|
public Class2 Foo |
||||
|
{ |
||||
|
get { return _foo; } |
||||
|
set |
||||
|
{ |
||||
|
_foo = value; |
||||
|
RaisePropertyChanged(nameof(Foo)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class Class2 : NotifyingBase |
||||
|
{ |
||||
|
private string _bar; |
||||
|
|
||||
|
public string Bar |
||||
|
{ |
||||
|
get { return _bar; } |
||||
|
set |
||||
|
{ |
||||
|
_bar = value; |
||||
|
RaisePropertyChanged(nameof(Bar)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
// 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 System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
using Perspex.Markup.Xaml.Templates; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Perspex.Markup.Xaml.UnitTests.Templates |
||||
|
{ |
||||
|
public class DataTemplateTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void DataTemplate_Should_Match_Data_Of_Type() |
||||
|
{ |
||||
|
var target = new DataTemplate { DataType = typeof(Class1) }; |
||||
|
var data = new Class1(); |
||||
|
|
||||
|
Assert.True(target.Match(data)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DataTemplate_Should_Match_Data_Of_Derived_Type() |
||||
|
{ |
||||
|
var target = new DataTemplate { DataType = typeof(Class1) }; |
||||
|
var data = new Class2(); |
||||
|
|
||||
|
Assert.True(target.Match(data)); |
||||
|
} |
||||
|
|
||||
|
private class Class1 { } |
||||
|
private class Class2 : Class1 { } |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue