diff --git a/Perspex.Controls/Control.cs b/Perspex.Controls/Control.cs index 6f14f054eb..8ff7b6a6f0 100644 --- a/Perspex.Controls/Control.cs +++ b/Perspex.Controls/Control.cs @@ -11,6 +11,7 @@ namespace Perspex.Controls using System.Linq; using System.Reactive.Linq; using Perspex.Input; + using Perspex.Interactivity; using Perspex.Media; using Perspex.Rendering; using Perspex.Styling; @@ -33,6 +34,9 @@ namespace Perspex.Controls public static readonly PerspexProperty TemplatedParentProperty = PerspexProperty.Register("TemplatedParent", inherits: true); + public static readonly RoutedEvent RequestBringIntoViewEvent = + RoutedEvent.Register("RequestBringIntoView", RoutingStrategy.Bubble); + private Classes classes = new Classes(); private DataTemplates dataTemplates; @@ -155,6 +159,23 @@ namespace Perspex.Controls internal set { this.SetValue(TemplatedParentProperty, value); } } + public void BringIntoView() + { + this.BringIntoView(new Rect(this.ActualSize)); + } + + public void BringIntoView(Rect rect) + { + var ev = new RequestBringIntoViewEventArgs + { + RoutedEvent = RequestBringIntoViewEvent, + TargetObject = this, + TargetRect = rect, + }; + + this.RaiseEvent(ev); + } + protected static void PseudoClass(PerspexProperty property, string className) { PseudoClass(property, x => x, className); diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj index caec58e98c..e0b1dee911 100644 --- a/Perspex.Controls/Perspex.Controls.csproj +++ b/Perspex.Controls/Perspex.Controls.csproj @@ -71,8 +71,10 @@ + + diff --git a/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/Perspex.Controls/Presenters/ScrollContentPresenter.cs index 16d47d9904..e7e18590e4 100644 --- a/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -8,6 +8,8 @@ namespace Perspex.Controls.Presenters { using System; using System.Linq; + using System.Reactive.Disposables; + using Perspex.Controls.Primitives; using Perspex.Input; using Perspex.Layout; @@ -22,11 +24,25 @@ namespace Perspex.Controls.Presenters public static readonly PerspexProperty ViewportProperty = ScrollViewer.ViewportProperty.AddOwner(); + public static readonly PerspexProperty CanScrollHorizontallyProperty = + PerspexProperty.Register("CanScrollHorizontally", true); + + private IDisposable contentBindings; + static ScrollContentPresenter() { Control.AffectsArrange(OffsetProperty); } + public ScrollContentPresenter() + { + this.GetObservable(ContentProperty).Subscribe(this.ContentChanged); + + this.AddHandler( + Control.RequestBringIntoViewEvent, + new EventHandler(this.BringIntoViewRequested)); + } + public Size Extent { get { return this.GetValue(ExtentProperty); } @@ -45,13 +61,25 @@ namespace Perspex.Controls.Presenters private set { this.SetValue(ViewportProperty, value); } } + public bool CanScrollHorizontally + { + get { return this.GetValue(CanScrollHorizontallyProperty); } + } + protected override Size MeasureOverride(Size availableSize) { var content = this.Content as ILayoutable; if (content != null) { - content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + var measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity); + + if (!this.CanScrollHorizontally) + { + measureSize = measureSize.WithWidth(availableSize.Width); + } + + content.Measure(measureSize); var size = content.DesiredSize.Value; this.Extent = size; return size.Constrain(availableSize); @@ -88,5 +116,50 @@ namespace Perspex.Controls.Presenters e.Handled = true; } } + + private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e) + { + var transform = e.TargetObject.TransformToVisual(this.GetVisualChildren().Single()); + var rect = e.TargetRect * transform; + var offset = this.Offset; + + if (rect.Bottom > offset.Y + this.Viewport.Height) + { + offset = offset.WithY(rect.Bottom - this.Viewport.Height); + } + + if (rect.Y < offset.Y) + { + offset = offset.WithY(rect.Y); + } + + if (rect.Right > offset.X + this.Viewport.Width) + { + offset = offset.WithX(rect.Right - this.Viewport.Width); + } + + if (rect.X < offset.X) + { + offset = offset.WithX(rect.X); + } + + this.Offset = offset; + } + + private void ContentChanged(object content) + { + var scrollInfo = content as IScrollInfo; + + if (this.contentBindings != null) + { + this.contentBindings.Dispose(); + this.contentBindings = null; + } + + if (scrollInfo != null) + { + this.contentBindings = this.Bind(CanScrollHorizontallyProperty, scrollInfo.CanScrollHorizontally); + } + } } } diff --git a/Perspex.Controls/Presenters/TextPresenter.cs b/Perspex.Controls/Presenters/TextPresenter.cs index 550b85038c..4c6abb5d54 100644 --- a/Perspex.Controls/Presenters/TextPresenter.cs +++ b/Perspex.Controls/Presenters/TextPresenter.cs @@ -10,12 +10,13 @@ namespace Perspex.Controls using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; + using Perspex.Controls.Primitives; using Perspex.Controls.Utils; using Perspex.Input; using Perspex.Media; using Perspex.Threading; - public class TextPresenter : TextBlock + public class TextPresenter : TextBlock, IScrollInfo { public static readonly PerspexProperty AcceptsReturnProperty = TextBox.AcceptsReturnProperty.AddOwner(); @@ -36,6 +37,8 @@ namespace Perspex.Controls private bool caretBlink; + private IObservable canScrollHorizontally; + static TextPresenter() { FocusableProperty.OverrideDefaultValue(typeof(TextPresenter), true); @@ -47,13 +50,16 @@ namespace Perspex.Controls this.caretTimer.Interval = TimeSpan.FromMilliseconds(500); this.caretTimer.Tick += this.CaretTimerTick; + this.canScrollHorizontally = this.GetObservable(TextWrappingProperty) + .Select(x => x == TextWrapping.NoWrap); + Observable.Merge( this.GetObservable(SelectionStartProperty), this.GetObservable(SelectionEndProperty)) .Subscribe(_ => this.InvalidateFormattedText()); - this.GetObservable(TextBox.CaretIndexProperty) - .Subscribe(_ => this.CaretMoved()); + this.GetObservable(TextPresenter.CaretIndexProperty) + .Subscribe(this.CaretIndexChanged); } public bool AcceptsReturn @@ -91,6 +97,16 @@ namespace Perspex.Controls get { return base.FormattedText; } } + IObservable IScrollInfo.CanScrollHorizontally + { + get { return this.canScrollHorizontally; } + } + + IObservable IScrollInfo.IsHorizontalScrollBarVisible + { + get { return Observable.Never().StartWith(false); } + } + public int GetCaretIndex(Point point) { var hit = this.FormattedText.HitTestPoint(point); @@ -312,12 +328,15 @@ namespace Perspex.Controls } } - internal void CaretMoved() + internal void CaretIndexChanged(int caretIndex) { this.caretBlink = true; this.caretTimer.Stop(); this.caretTimer.Start(); this.InvalidateVisual(); + + var rect = this.FormattedText.HitTestTextPosition(caretIndex); + this.BringIntoView(rect); } private void MoveHorizontal(int count, ModifierKeys modifiers) diff --git a/Perspex.Controls/Primitives/IScrollInfo.cs b/Perspex.Controls/Primitives/IScrollInfo.cs new file mode 100644 index 0000000000..7e60b18558 --- /dev/null +++ b/Perspex.Controls/Primitives/IScrollInfo.cs @@ -0,0 +1,25 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.Primitives +{ + using System; + + /// + /// Allows the child of a to communicate with the scroll viewer. + /// + /// + /// Note that this interface has a different purpose to IScrollInfo in WPF! I would give it + /// a different name, but it does what it suggests - give information about the scrolling + /// requirements of a control. + /// + public interface IScrollInfo + { + IObservable CanScrollHorizontally { get; } + + IObservable IsHorizontalScrollBarVisible { get; } + } +} diff --git a/Perspex.Controls/RequestBringIntoViewEventArgs.cs b/Perspex.Controls/RequestBringIntoViewEventArgs.cs new file mode 100644 index 0000000000..00de55c6f3 --- /dev/null +++ b/Perspex.Controls/RequestBringIntoViewEventArgs.cs @@ -0,0 +1,17 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using Perspex.Interactivity; + + public class RequestBringIntoViewEventArgs : RoutedEventArgs + { + public IVisual TargetObject { get; set; } + + public Rect TargetRect { get; set; } + } +} diff --git a/Perspex.Controls/ScrollViewer.cs b/Perspex.Controls/ScrollViewer.cs index 65dca3e99b..ae95d38808 100644 --- a/Perspex.Controls/ScrollViewer.cs +++ b/Perspex.Controls/ScrollViewer.cs @@ -22,11 +22,34 @@ namespace Perspex.Controls public static readonly PerspexProperty ViewportProperty = PerspexProperty.Register("Viewport"); - private ScrollContentPresenter presenter; + public static readonly PerspexProperty CanScrollHorizontallyProperty = + PerspexProperty.Register("CanScrollHorizontally", false); - private ScrollBar horizontalScrollBar; + public static readonly PerspexProperty IsHorizontalScrollBarVisibleProperty = + PerspexProperty.Register("IsHorizontalScrollBarVisible"); - private ScrollBar verticalScrollBar; + public static readonly PerspexProperty HorizontalScrollBarMaximumProperty = + PerspexProperty.Register("HorizontalScrollBarMaximum"); + + public static readonly PerspexProperty HorizontalScrollBarValueProperty = + PerspexProperty.Register("HorizontalScrollBarValue"); + + public static readonly PerspexProperty HorizontalScrollBarViewportSizeProperty = + PerspexProperty.Register("HorizontalScrollBarViewportSize"); + + public static readonly PerspexProperty IsVerticalScrollBarVisibleProperty = + PerspexProperty.Register("IsVerticalScrollBarVisible"); + + public static readonly PerspexProperty VerticalScrollBarMaximumProperty = + PerspexProperty.Register("VerticalScrollBarMaximum"); + + public static readonly PerspexProperty VerticalScrollBarValueProperty = + PerspexProperty.Register("VerticalScrollBarValue"); + + public static readonly PerspexProperty VerticalScrollBarViewportSizeProperty = + PerspexProperty.Register("VerticalScrollBarViewportSize"); + + private IDisposable contentBindings; static ScrollViewer() { @@ -34,6 +57,54 @@ namespace Perspex.Controls AffectsCoercion(ViewportProperty, OffsetProperty); } + public ScrollViewer() + { + var extentAndViewport = Observable.CombineLatest( + this.GetObservable(ExtentProperty), + this.GetObservable(ViewportProperty)) + .Select(x => new { Extent = x[0], Viewport = x[1] }); + + this.Bind( + IsHorizontalScrollBarVisibleProperty, + extentAndViewport.Select(x => x.Extent.Width > x.Viewport.Width), + BindingPriority.Style); + + this.Bind( + HorizontalScrollBarMaximumProperty, + extentAndViewport.Select(x => x.Extent.Width - x.Viewport.Width)); + + this.Bind( + HorizontalScrollBarViewportSizeProperty, + extentAndViewport.Select(x => (x.Viewport.Width / x.Extent.Width) * (x.Extent.Width - x.Viewport.Width))); + + this.Bind( + IsVerticalScrollBarVisibleProperty, + extentAndViewport.Select(x => x.Extent.Height > x.Viewport.Height), + BindingPriority.Style); + + this.Bind( + VerticalScrollBarMaximumProperty, + extentAndViewport.Select(x => x.Extent.Height - x.Viewport.Height)); + + this.Bind( + VerticalScrollBarViewportSizeProperty, + extentAndViewport.Select(x => (x.Viewport.Height / x.Extent.Height) * (x.Extent.Height - x.Viewport.Height))); + + this.GetObservable(OffsetProperty).Subscribe(x => + { + this.SetValue(HorizontalScrollBarValueProperty, x.X); + this.SetValue(VerticalScrollBarValueProperty, x.Y); + }); + + var scrollBarOffset = Observable.CombineLatest( + this.GetObservable(HorizontalScrollBarValueProperty), + this.GetObservable(VerticalScrollBarValueProperty)) + .Select(x => new Vector(x[0], x[1])) + .Subscribe(x => this.Offset = x); + + this.GetObservable(ContentProperty).Subscribe(this.ContentChanged); + } + public Size Extent { get { return this.GetValue(ExtentProperty); } @@ -52,62 +123,15 @@ namespace Perspex.Controls private set { this.SetValue(ViewportProperty, value); } } - protected override Size MeasureOverride(Size availableSize) + public bool CanScrollHorizontally { - return base.MeasureOverride(availableSize); + get { return this.GetValue(CanScrollHorizontallyProperty); } + set { this.SetValue(CanScrollHorizontallyProperty, value); } } - protected override void OnTemplateApplied() + protected override Size MeasureOverride(Size availableSize) { - this.presenter = this.GetTemplateChild("presenter"); - this.horizontalScrollBar = this.GetTemplateChild("horizontalScrollBar"); - this.verticalScrollBar = this.GetTemplateChild("verticalScrollBar"); - - this[!ExtentProperty] = this.presenter[!ExtentProperty]; - this[!ViewportProperty] = this.presenter[!ViewportProperty]; - this.presenter[!OffsetProperty] = this[!OffsetProperty]; - - var extentAndViewport = Observable.CombineLatest( - this.GetObservable(ExtentProperty).StartWith(this.Extent), - this.GetObservable(ViewportProperty).StartWith(this.Viewport)) - .Select(x => new { Extent = x[0], Viewport = x[1] }); - - this.horizontalScrollBar.Bind( - Visual.IsVisibleProperty, - extentAndViewport.Select(x => x.Extent.Width > x.Viewport.Width)); - - this.horizontalScrollBar.Bind( - ScrollBar.MaximumProperty, - extentAndViewport.Select(x => x.Extent.Width - x.Viewport.Width)); - - this.horizontalScrollBar.Bind( - ScrollBar.ViewportSizeProperty, - extentAndViewport.Select(x => (x.Viewport.Width / x.Extent.Width) * (x.Extent.Width - x.Viewport.Width))); - - this.verticalScrollBar.Bind( - Visual.IsVisibleProperty, - extentAndViewport.Select(x => x.Extent.Height > x.Viewport.Height)); - - this.verticalScrollBar.Bind( - ScrollBar.MaximumProperty, - extentAndViewport.Select(x => x.Extent.Height - x.Viewport.Height)); - - this.verticalScrollBar.Bind( - ScrollBar.ViewportSizeProperty, - extentAndViewport.Select(x => (x.Viewport.Height / x.Extent.Height) * (x.Extent.Height - x.Viewport.Height))); - - var offset = Observable.CombineLatest( - this.horizontalScrollBar.GetObservable(ScrollBar.ValueProperty), - this.verticalScrollBar.GetObservable(ScrollBar.ValueProperty)) - .Select(x => new Vector(x[0], x[1])); - - this.presenter.GetObservable(ScrollContentPresenter.OffsetProperty).Subscribe(x => - { - this.horizontalScrollBar.Value = x.X; - this.verticalScrollBar.Value = x.Y; - }); - - this.Bind(OffsetProperty, offset); + return base.MeasureOverride(availableSize); } private static double Clamp(double value, double min, double max) @@ -132,5 +156,23 @@ namespace Perspex.Controls return value; } } + + private void ContentChanged(object content) + { + var scrollInfo = content as IScrollInfo; + + if (this.contentBindings != null) + { + this.contentBindings.Dispose(); + this.contentBindings = null; + } + + if (scrollInfo != null) + { + this.contentBindings = this.Bind( + IsHorizontalScrollBarVisibleProperty, + scrollInfo.IsHorizontalScrollBarVisible); + } + } } } diff --git a/Perspex.SceneGraph/IVisual.cs b/Perspex.SceneGraph/IVisual.cs index 2b6d68c2eb..5688c796bb 100644 --- a/Perspex.SceneGraph/IVisual.cs +++ b/Perspex.SceneGraph/IVisual.cs @@ -63,5 +63,13 @@ namespace Perspex /// /// The context. void Render(IDrawingContext context); + + /// + /// Returns a transform that transforms the visual's coordinates into the coordinates + /// of the specified . + /// + /// + /// A containing the transform. + Matrix TransformToVisual(IVisual visual); } } diff --git a/Perspex.SceneGraph/Point.cs b/Perspex.SceneGraph/Point.cs index 4a8f2d2de3..08e665d5e7 100644 --- a/Perspex.SceneGraph/Point.cs +++ b/Perspex.SceneGraph/Point.cs @@ -82,6 +82,13 @@ namespace Perspex return new Point(a.x - b.x, a.y - b.y); } + public static Point operator *(Point point, Matrix matrix) + { + return new Point( + (point.X * matrix.M11) + (point.Y * matrix.M21) + matrix.OffsetX, + (point.X * matrix.M12) + (point.Y * matrix.M22) + matrix.OffsetY); + } + public static implicit operator Vector(Point p) { return new Vector(p.x, p.y); diff --git a/Perspex.SceneGraph/Rect.cs b/Perspex.SceneGraph/Rect.cs index 592fd7db83..82df33050c 100644 --- a/Perspex.SceneGraph/Rect.cs +++ b/Perspex.SceneGraph/Rect.cs @@ -226,6 +226,11 @@ namespace Perspex height); } + public static Rect operator *(Rect rect, Matrix matrix) + { + return new Rect(rect.TopLeft * matrix, rect.BottomRight * matrix); + } + public static Rect operator /(Rect rect, Vector scale) { double centerX = rect.x + (rect.width / 2); diff --git a/Perspex.SceneGraph/Vector.cs b/Perspex.SceneGraph/Vector.cs index 32b58ed609..0053b49fe1 100644 --- a/Perspex.SceneGraph/Vector.cs +++ b/Perspex.SceneGraph/Vector.cs @@ -6,6 +6,7 @@ namespace Perspex { + using System; using System.Globalization; /// @@ -78,5 +79,15 @@ namespace Perspex { return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", this.x, this.y); } + + public Vector WithX(double x) + { + return new Vector(x, this.y); + } + + public Vector WithY(double y) + { + return new Vector(this.x, y); + } } } diff --git a/Perspex.SceneGraph/Visual.cs b/Perspex.SceneGraph/Visual.cs index cad7f46381..2e5238dcf4 100644 --- a/Perspex.SceneGraph/Visual.cs +++ b/Perspex.SceneGraph/Visual.cs @@ -104,6 +104,13 @@ namespace Perspex Contract.Requires(context != null); } + public Matrix TransformToVisual(IVisual visual) + { + var thisOffset = GetOffsetFromRoot(this); + var thatOffset = GetOffsetFromRoot(visual); + return Matrix.Translation(-thisOffset) * Matrix.Translation(thatOffset); + } + protected static void AffectsRender(PerspexProperty property) { property.Changed.Subscribe(AffectsRenderInvalidate); @@ -177,6 +184,19 @@ namespace Perspex } } + private static Vector GetOffsetFromRoot(IVisual v) + { + var result = new Vector(); + + while (!(v is IRenderRoot)) + { + result = new Vector(result.X + v.Bounds.X, result.Y + v.Bounds.Y); + v = v.VisualParent; + } + + return result; + } + private void SetVisualParent(Visual value) { if (this.visualParent != value) diff --git a/Perspex.Themes.Default/ScrollViewerStyle.cs b/Perspex.Themes.Default/ScrollViewerStyle.cs index c58d5d4fb0..a1ff62f6a7 100644 --- a/Perspex.Themes.Default/ScrollViewerStyle.cs +++ b/Perspex.Themes.Default/ScrollViewerStyle.cs @@ -48,18 +48,30 @@ namespace Perspex.Themes.Default new ScrollContentPresenter { Id = "presenter", - [~ContentPresenter.ContentProperty] = control[~ScrollViewer.ContentProperty], + [~ScrollContentPresenter.ContentProperty] = control[~ScrollViewer.ContentProperty], + [~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty], + [~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty], + [~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty], + [~ScrollContentPresenter.CanScrollHorizontallyProperty] = control[~ScrollViewer.CanScrollHorizontallyProperty], }, new ScrollBar { Id = "horizontalScrollBar", Orientation = Orientation.Horizontal, + [~ScrollBar.IsVisibleProperty] = control[~ScrollViewer.IsHorizontalScrollBarVisibleProperty], + [~ScrollBar.MaximumProperty] = control[~ScrollViewer.HorizontalScrollBarMaximumProperty], + [~~ScrollBar.ValueProperty] = control[~~ScrollViewer.HorizontalScrollBarValueProperty], + [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty], [Grid.RowProperty] = 1, }, new ScrollBar { Id = "verticalScrollBar", Orientation = Orientation.Vertical, + [~ScrollBar.IsVisibleProperty] = control[~ScrollViewer.IsVerticalScrollBarVisibleProperty], + [~ScrollBar.MaximumProperty] = control[~ScrollViewer.VerticalScrollBarMaximumProperty], + [~~ScrollBar.ValueProperty] = control[~~ScrollViewer.VerticalScrollBarValueProperty], + [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty], [Grid.ColumnProperty] = 1, }, }, diff --git a/Perspex.Themes.Default/TextBoxStyle.cs b/Perspex.Themes.Default/TextBoxStyle.cs index e748910e3f..0c609a9a06 100644 --- a/Perspex.Themes.Default/TextBoxStyle.cs +++ b/Perspex.Themes.Default/TextBoxStyle.cs @@ -47,15 +47,18 @@ namespace Perspex.Themes.Default [~Border.BackgroundProperty] = control[~TextBox.BackgroundProperty], [~Border.BorderBrushProperty] = control[~TextBox.BorderBrushProperty], [~Border.BorderThicknessProperty] = control[~TextBox.BorderThicknessProperty], - Content = new TextPresenter + Content = new ScrollViewer { - [~TextPresenter.AcceptsReturnProperty] = control[~TextBox.AcceptsReturnProperty], - [~TextPresenter.AcceptsTabProperty] = control[~TextBox.AcceptsTabProperty], - [~TextPresenter.CaretIndexProperty] = control[~TextBox.CaretIndexProperty], - [~TextPresenter.SelectionStartProperty] = control[~TextBox.SelectionStartProperty], - [~TextPresenter.SelectionEndProperty] = control[~TextBox.SelectionEndProperty], - [~TextPresenter.TextProperty] = control[~TextBox.TextProperty], - [~TextPresenter.TextWrappingProperty] = control[~TextBox.TextWrappingProperty], + Content = new TextPresenter + { + [~TextPresenter.AcceptsReturnProperty] = control[~TextBox.AcceptsReturnProperty], + [~TextPresenter.AcceptsTabProperty] = control[~TextBox.AcceptsTabProperty], + [~TextPresenter.CaretIndexProperty] = control[~TextBox.CaretIndexProperty], + [~TextPresenter.SelectionStartProperty] = control[~TextBox.SelectionStartProperty], + [~TextPresenter.SelectionEndProperty] = control[~TextBox.SelectionEndProperty], + [~TextPresenter.TextProperty] = control[~TextBox.TextProperty], + [~TextPresenter.TextWrappingProperty] = control[~TextBox.TextWrappingProperty], + } } }; diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index eeb19ddfb7..a3e520117d 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -234,8 +234,12 @@ namespace TestApplication }, new TextBox { - Text = "A wrapping text box. Lorem ipsum dolor sit amet.", + AcceptsReturn = true, + Text = "A wrapping text box. " + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin venenatis dui quis libero suscipit tincidunt. " + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin venenatis dui quis libero suscipit tincidunt.", TextWrapping = TextWrapping.Wrap, + MaxHeight = 100, }, } }, @@ -268,6 +272,7 @@ namespace TestApplication { Width = 200, Height = 200, + CanScrollHorizontally = true, Content = new Image { Source = new Bitmap("github_icon.png"),