Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / MS / Internal / documents / TextBoxView.cs / 1 / TextBoxView.cs
//----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
// File: TextBoxView.cs
//
// Description: Content presenter for the TextBox.
//
//---------------------------------------------------------------------------
namespace System.Windows.Controls
{
using System.Windows.Documents;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Threading;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MS.Internal;
using MS.Internal.Text;
using MS.Internal.Documents;
using MS.Internal.PtsHost;
using System.Windows.Media.TextFormatting;
// Content presenter for the TextBox.
internal class TextBoxView : FrameworkElement, ITextView, IScrollInfo, IServiceProvider
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
// Static constructor.
static TextBoxView()
{
// Set a margin so that the bidi caret has room to render at the edges of content.
MarginProperty.OverrideMetadata(typeof(TextBoxView), new FrameworkPropertyMetadata(new Thickness(CaretElement.BidiCaretIndicatorWidth, 0, CaretElement.BidiCaretIndicatorWidth, 0)));
}
// Constructor.
internal TextBoxView(ITextBoxViewHost host)
{
Invariant.Assert(host is Control);
_host = host;
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//-----------------------------------------------------
#region Public Methods
// IServiceProvider for TextEditor/renderscope contract.
// Provides access to our ITextView implementation.
object IServiceProvider.GetService(Type serviceType)
{
object service = null;
if (serviceType == typeof(ITextView))
{
service = this;
}
return service;
}
///
///
///
void IScrollInfo.LineUp()
{
if (_scrollData != null)
{
_scrollData.LineUp(this);
}
}
///
///
///
void IScrollInfo.LineDown()
{
if (_scrollData != null)
{
_scrollData.LineDown(this);
}
}
///
///
///
void IScrollInfo.LineLeft()
{
if (_scrollData != null)
{
_scrollData.LineLeft(this);
}
}
///
///
///
void IScrollInfo.LineRight()
{
if (_scrollData != null)
{
_scrollData.LineRight(this);
}
}
///
///
///
void IScrollInfo.PageUp()
{
if (_scrollData != null)
{
_scrollData.PageUp(this);
}
}
///
///
///
void IScrollInfo.PageDown()
{
if (_scrollData != null)
{
_scrollData.PageDown(this);
}
}
///
///
///
void IScrollInfo.PageLeft()
{
if (_scrollData != null)
{
_scrollData.PageLeft(this);
}
}
///
///
///
void IScrollInfo.PageRight()
{
if (_scrollData != null)
{
_scrollData.PageRight(this);
}
}
///
///
///
void IScrollInfo.MouseWheelUp()
{
if (_scrollData != null)
{
_scrollData.MouseWheelUp(this);
}
}
///
///
///
void IScrollInfo.MouseWheelDown()
{
if (_scrollData != null)
{
_scrollData.MouseWheelDown(this);
}
}
///
///
///
void IScrollInfo.MouseWheelLeft()
{
if (_scrollData != null)
{
_scrollData.MouseWheelLeft(this);
}
}
///
///
///
void IScrollInfo.MouseWheelRight()
{
if (_scrollData != null)
{
_scrollData.MouseWheelRight(this);
}
}
///
///
///
void IScrollInfo.SetHorizontalOffset(double offset)
{
if (_scrollData != null)
{
_scrollData.SetHorizontalOffset(this, offset);
}
}
///
///
///
void IScrollInfo.SetVerticalOffset(double offset)
{
if (_scrollData != null)
{
_scrollData.SetVerticalOffset(this, offset);
}
}
///
///
///
Rect IScrollInfo.MakeVisible(Visual visual, Rect rectangle)
{
if (_scrollData == null)
{
rectangle = Rect.Empty;
}
else
{
rectangle = _scrollData.MakeVisible(this, visual, rectangle);
}
return rectangle;
}
///
///
///
bool IScrollInfo.CanVerticallyScroll
{
get
{
return (_scrollData != null) ? _scrollData.CanVerticallyScroll : false;
}
set
{
if (_scrollData != null)
{
_scrollData.CanVerticallyScroll = value;
}
}
}
///
///
///
bool IScrollInfo.CanHorizontallyScroll
{
get
{
return (_scrollData != null) ? _scrollData.CanHorizontallyScroll : false;
}
set
{
if (_scrollData != null)
{
_scrollData.CanHorizontallyScroll = value;
}
}
}
///
///
///
double IScrollInfo.ExtentWidth
{
get
{
return (_scrollData != null) ? _scrollData.ExtentWidth : 0;
}
}
///
///
///
double IScrollInfo.ExtentHeight
{
get
{
return (_scrollData != null) ? _scrollData.ExtentHeight : 0;
}
}
///
///
///
double IScrollInfo.ViewportWidth
{
get
{
return (_scrollData != null) ? _scrollData.ViewportWidth : 0;
}
}
///
///
///
double IScrollInfo.ViewportHeight
{
get
{
return (_scrollData != null) ? _scrollData.ViewportHeight : 0;
}
}
///
///
///
double IScrollInfo.HorizontalOffset
{
get
{
return (_scrollData != null) ? _scrollData.HorizontalOffset : 0;
}
}
///
///
///
double IScrollInfo.VerticalOffset
{
get
{
return (_scrollData != null) ? _scrollData.VerticalOffset : 0;
}
}
///
///
///
ScrollViewer IScrollInfo.ScrollOwner
{
get
{
return (_scrollData != null) ? _scrollData.ScrollOwner : null;
}
set
{
if (_scrollData == null)
{
// Create cached scroll info.
_scrollData = new ScrollData();
}
_scrollData.SetScrollOwner(this, value);
}
}
#endregion Public Methods
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
// Calculates ideal content size.
protected override Size MeasureOverride(Size constraint)
{
// Lazy init TextContainer listeners on the first measure.
EnsureTextContainerListeners();
// Lazy allocate _lineMetrics on the first measure.
if (_lineMetrics == null)
{
_lineMetrics = new List(1);
}
Size desiredSize;
// Init a cache we'll use here and in the following ArrangeOverride call.
_cache = null;
EnsureCache();
LineProperties lineProperties = _cache.LineProperties;
// Skip the measure if constraints have not changed.
bool widthChanged = !DoubleUtil.AreClose(constraint.Width, _previousConstraint.Width);
// If width changed and TextAlignment is Center or Right the visual offsets of the visible
// lines need to be recalculated.
if (widthChanged && lineProperties.TextAlignment != TextAlignment.Left)
{
_viewportLineVisuals = null;
}
bool constraintschanged = widthChanged &&
lineProperties.TextWrapping != TextWrapping.NoWrap;
if (_lineMetrics.Count == 0 || constraintschanged)
{
// Null out the dirty list when constraints change -- everything's dirty.
_dirtyList = null;
}
else if (_dirtyList == null && !this.IsBackgroundLayoutPending)
{
// No dirty region, no constraint change, no pending background layout.
desiredSize = _contentSize;
goto Exit;
}
// Treat an insert into an empty document just like a full invalidation,
// to allow background layout to run.
if (_dirtyList != null &&
_lineMetrics.Count == 1 && _lineMetrics[0].EndOffset == 0)
{
_lineMetrics.Clear();
_viewportLineVisuals = null;
_dirtyList = null;
}
Size safeConstraint = constraint;
// Make sure that TextFormatter limitations are not exceeded.
//
TextDpi.EnsureValidLineWidth(ref safeConstraint);
// Do the measure.
if (_dirtyList == null)
{
if (constraintschanged)
{
_lineMetrics.Clear();
_viewportLineVisuals = null;
}
desiredSize = FullMeasureTick(safeConstraint.Width, lineProperties);
}
else
{
desiredSize = IncrementalMeasure(safeConstraint.Width, lineProperties);
}
Invariant.Assert(_lineMetrics.Count >= 1);
_dirtyList = null;
double oldWidth = _contentSize.Width;
_contentSize = desiredSize;
// If the width has changed we need to reformat if we're centered or right aligned so the
// spacing gets properly updated.
if (oldWidth != desiredSize.Width && lineProperties.TextAlignment != TextAlignment.Left)
{
Rerender();
}
Exit:
// DesiredSize is set to the calculated size of the content.
// If hosted by ScrollViewer, desired size is limited to constraint.
if (_scrollData != null)
{
desiredSize.Width = Math.Min(constraint.Width, desiredSize.Width);
desiredSize.Height = Math.Min(constraint.Height, desiredSize.Height);
}
_previousConstraint = constraint;
return desiredSize;
}
// Arranges content within a specified constraint.
protected override Size ArrangeOverride(Size arrangeSize)
{
if (_lineMetrics == null || _lineMetrics.Count == 0)
{
// No matching MeasureOverride call.
goto Exit;
}
EnsureCache();
ArrangeScrollData(arrangeSize);
ArrangeVisuals(arrangeSize);
_cache = null;
FireTextViewUpdatedEvent();
InvalidateVisual();
Exit:
return arrangeSize;
}
// Render callback for this TextBoxView.
protected override void OnRender(DrawingContext context)
{
// Render a transparent Rect to enable hit-testing even when content does not fill
// the entire viewport.
//
context.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, new Rect(0, 0, this.RenderSize.Width, this.RenderSize.Height));
}
///
/// Derived class must implement to support Visual children. The method must return
/// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1.
///
/// By default a Visual does not have any children.
///
/// Remark:
/// During this virtual call it is not valid to modify the Visual tree.
///
protected override Visual GetVisualChild(int index)
{
if (index >= this.VisualChildrenCount)
{
throw new ArgumentOutOfRangeException("index");
}
return _visualChildren[index];
}
#endregion Protected Methods
//-----------------------------------------------------
//
// Protected Properties
//
//------------------------------------------------------
#region Protected Properties
///
/// Derived classes override this property to enable the Visual code to enumerate
/// the Visual children. Derived classes need to return the number of children
/// from this method.
///
/// By default a Visual does not have any children.
///
/// Remark:
/// During this virtual method the Visual tree must not be modified.
///
protected override int VisualChildrenCount
{
get
{
return (_visualChildren == null) ? 0 : _visualChildren.Count;
}
}
#endregion Protected Properties
//-----------------------------------------------------
//
// Internal Methods
//
//-----------------------------------------------------
#region Internal Methods
///
///
///
ITextPointer ITextView.GetTextPositionFromPoint(Point point, bool snapToText)
{
Invariant.Assert(this.IsLayoutValid);
point = TransformToDocumentSpace(point);
int lineIndex = GetLineIndexFromPoint(point, snapToText);
ITextPointer position;
if (lineIndex == -1)
{
position = null;
}
else
{
position = GetTextPositionFromDistance(lineIndex, point.X);
position.Freeze();
}
return position;
}
///
///
///
Rect ITextView.GetRectangleFromTextPosition(ITextPointer position)
{
Rect rect;
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
int offset = position.Offset;
if (offset > 0 && position.LogicalDirection == LogicalDirection.Backward)
{
// TextBoxLine always gets the forward Rect, so back up to preceding char.
offset--;
}
int lineIndex = GetLineIndexFromOffset(offset);
FlowDirection flowDirection;
LineProperties lineProperties;
using (TextBoxLine line = GetFormattedLine(lineIndex, out lineProperties))
{
rect = line.GetBoundsFromTextPosition(offset, out flowDirection);
}
if (!rect.IsEmpty) // Empty rects can't be modified.
{
rect.Y += lineIndex * _lineHeight;
// Return only TopLeft and Height.
// Adjust rect.Left by taking into account flow direction of the
// content and orientation of input position.
if (lineProperties.FlowDirection != flowDirection)
{
if (position.LogicalDirection == LogicalDirection.Forward || position.Offset == 0)
{
rect.X = rect.Right;
}
}
else
{
if (position.LogicalDirection == LogicalDirection.Backward && position.Offset > 0)
{
rect.X = rect.Right;
}
}
rect.Width = 0;
}
return TransformToVisualSpace(rect);
}
///
///
///
Rect ITextView.GetRawRectangleFromTextPosition(ITextPointer position, out Transform transform)
{
transform = Transform.Identity;
return ((ITextView)this).GetRectangleFromTextPosition(position);
}
///
///
///
Geometry ITextView.GetTightBoundingGeometryFromTextPositions(ITextPointer startPosition, ITextPointer endPosition)
{
Invariant.Assert(this.IsLayoutValid);
Geometry geometry = null;
double endOfParaGlyphWidth = ((Control)_host).FontSize * CaretElement.c_endOfParaMagicMultiplier;
// Since background layout may be running, clip to the computed region.
int startOffset = Math.Min(_lineMetrics[_lineMetrics.Count-1].EndOffset, startPosition.Offset);
int endOffset = Math.Min(_lineMetrics[_lineMetrics.Count - 1].EndOffset, endPosition.Offset);
// Find the intersection of the viewport with the requested range.
int firstLineIndex;
int lastLineIndex;
GetVisibleLines(out firstLineIndex, out lastLineIndex);
firstLineIndex = Math.Max(firstLineIndex, GetLineIndexFromOffset(startOffset, LogicalDirection.Forward));
lastLineIndex = Math.Min(lastLineIndex, GetLineIndexFromOffset(endOffset, LogicalDirection.Backward));
if (firstLineIndex > lastLineIndex)
{
// Visible region does not intersect with geometry.
return null;
}
// Partially covered lines require a line format, so we'll handle them specially.
// Only the first and last line are potentially partially covered.
bool firstLinePartiallyCovered = _lineMetrics[firstLineIndex].Offset < startOffset ||
_lineMetrics[firstLineIndex].EndOffset > endOffset;
bool lastLinePartiallyCovered = _lineMetrics[lastLineIndex].Offset < startOffset ||
_lineMetrics[lastLineIndex].EndOffset > endOffset;
TextAlignment alignment = this.CalculatedTextAlignment;
int lineIndex = firstLineIndex;
// If we don't cover the entire first line, special case it.
if (firstLinePartiallyCovered)
{
GetTightBoundingGeometryFromLineIndex(lineIndex, startOffset, endOffset, alignment, endOfParaGlyphWidth, ref geometry);
lineIndex++;
}
// If it is completely covered, adjust lastLineIndex such that we handle
// the last line in the loop below.
if (firstLineIndex <= lastLineIndex && !lastLinePartiallyCovered)
{
lastLineIndex++;
}
// Handle all the lines that are entirely covered -- they don't require any heavy lifting.
for (; lineIndex < lastLineIndex; lineIndex++)
{
double contentOffset = GetContentOffset(_lineMetrics[lineIndex].Width, alignment);
Rect rect = new Rect(contentOffset, lineIndex * _lineHeight, _lineMetrics[lineIndex].Width, _lineHeight);
// Add extra padding at the end of lines with linebreaks.
ITextPointer endOfLinePosition = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex].EndOffset, LogicalDirection.Backward);
if (TextPointerBase.IsNextToPlainLineBreak(endOfLinePosition, LogicalDirection.Backward))
{
rect.Width += endOfParaGlyphWidth;
}
rect = TransformToVisualSpace(rect);
CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
}
// If we don't cover the entire last line, special case it.
// Otherwise, we already handled it in the loop above.
if (lineIndex == lastLineIndex && lastLinePartiallyCovered)
{
GetTightBoundingGeometryFromLineIndex(lineIndex, startOffset, endOffset, alignment, endOfParaGlyphWidth, ref geometry);
}
return geometry;
}
///
///
///
ITextPointer ITextView.GetPositionAtNextLine(ITextPointer position, double suggestedX, int count, out double newSuggestedX, out int linesMoved)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
newSuggestedX = suggestedX;
int lineIndex = GetLineIndexFromPosition(position);
int nextLineIndex = Math.Max(0, Math.Min(_lineMetrics.Count - 1, lineIndex + count));
linesMoved = nextLineIndex - lineIndex;
ITextPointer nextLinePosition;
if (linesMoved == 0)
{
nextLinePosition = position.GetFrozenPointer(position.LogicalDirection);
}
else if (DoubleUtil.IsNaN(suggestedX))
{
nextLinePosition = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex + linesMoved].Offset, LogicalDirection.Forward);
}
else
{
suggestedX -= GetTextAlignmentCorrection(this.CalculatedTextAlignment, GetWrappingWidth(this.RenderSize.Width));
nextLinePosition = GetTextPositionFromDistance(nextLineIndex, suggestedX);
}
nextLinePosition.Freeze();
return nextLinePosition;
}
///
///
///
ITextPointer ITextView.GetPositionAtNextPage(ITextPointer position, Point suggestedOffset, int count, out Point newSuggestedOffset, out int pagesMoved)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
newSuggestedOffset = new Point();
pagesMoved = 0;
return null;
}
///
///
///
bool ITextView.IsAtCaretUnitBoundary(ITextPointer position)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
bool boundary = false;
int lineIndex = GetLineIndexFromPosition(position);
CharacterHit sourceCharacterHit = new CharacterHit();
if (position.LogicalDirection == LogicalDirection.Forward)
{
// Forward context, go to leading edge of position offset
sourceCharacterHit = new CharacterHit(position.Offset, 0);
}
else if (position.LogicalDirection == LogicalDirection.Backward)
{
if (position.Offset > _lineMetrics[lineIndex].Offset)
{
// For backward context, go to trailing edge of previous character
sourceCharacterHit = new CharacterHit(position.Offset - 1, 1);
}
else
{
// There is no previous trailing edge on this line. We don't consider this a unit boundary.
return false;
}
}
using (TextBoxLine line = GetFormattedLine(lineIndex))
{
boundary = line.IsAtCaretCharacterHit(sourceCharacterHit);
}
return boundary;
}
///
///
///
ITextPointer ITextView.GetNextCaretUnitPosition(ITextPointer position, LogicalDirection direction)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
// Special case document start/end.
if (position.Offset == 0 && direction == LogicalDirection.Backward)
{
return position.GetFrozenPointer(LogicalDirection.Forward);
}
else if (position.Offset == _host.TextContainer.SymbolCount && direction == LogicalDirection.Forward)
{
return position.GetFrozenPointer(LogicalDirection.Backward);
}
int lineIndex = GetLineIndexFromPosition(position);
CharacterHit sourceCharacterHit = new CharacterHit(position.Offset, 0);
CharacterHit nextCharacterHit;
using (TextBoxLine line = GetFormattedLine(lineIndex))
{
if (direction == LogicalDirection.Forward)
{
// Get the next caret position from the line
nextCharacterHit = line.GetNextCaretCharacterHit(sourceCharacterHit);
}
else
{
// Get previous caret position from the line
nextCharacterHit = line.GetPreviousCaretCharacterHit(sourceCharacterHit);
}
}
// Determine logical direction for next caret index and create TextPointer from it.
LogicalDirection logicalDirection;
if (nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength == _lineMetrics[lineIndex].EndOffset &&
direction == LogicalDirection.Forward)
{
// Going forward brought us to the end of a line, context must be forward for next line.
if (lineIndex == _lineMetrics.Count - 1)
{
// Last line so context must stay backward.
logicalDirection = LogicalDirection.Backward;
}
else
{
logicalDirection = LogicalDirection.Forward;
}
}
else if (nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength == _lineMetrics[lineIndex].Offset &&
direction == LogicalDirection.Backward)
{
// Going backward brought us to the start of a line, context must be backward for previous line.
if (lineIndex == 0)
{
// First line, so we will stay forward.
logicalDirection = LogicalDirection.Forward;
}
else
{
logicalDirection = LogicalDirection.Backward;
}
}
else
{
logicalDirection = (nextCharacterHit.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward;
}
ITextPointer nextCaretUnitPosition = _host.TextContainer.CreatePointerAtOffset(nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength, logicalDirection);
nextCaretUnitPosition.Freeze();
return nextCaretUnitPosition;
}
///
///
///
ITextPointer ITextView.GetBackspaceCaretUnitPosition(ITextPointer position)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
// Special case document start.
if (position.Offset == 0)
{
return position.GetFrozenPointer(LogicalDirection.Forward);
}
int lineIndex = GetLineIndexFromPosition(position, LogicalDirection.Backward);
CharacterHit sourceCharacterHit = new CharacterHit(position.Offset, 0);
CharacterHit backspaceCharacterHit;
using (TextBoxLine line = GetFormattedLine(lineIndex))
{
backspaceCharacterHit = line.GetBackspaceCaretCharacterHit(sourceCharacterHit);
}
LogicalDirection logicalDirection;
if (backspaceCharacterHit.FirstCharacterIndex + backspaceCharacterHit.TrailingLength == _lineMetrics[lineIndex].Offset)
{
// Going backward brought us to the start of a line, context must be backward for previous line
if (lineIndex == 0)
{
// First line, so we will stay forward.
logicalDirection = LogicalDirection.Forward;
}
else
{
logicalDirection = LogicalDirection.Backward;
}
}
else
{
logicalDirection = (backspaceCharacterHit.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward;
}
ITextPointer backspaceUnitPosition = _host.TextContainer.CreatePointerAtOffset(backspaceCharacterHit.FirstCharacterIndex + backspaceCharacterHit.TrailingLength, logicalDirection);
backspaceUnitPosition.Freeze();
return backspaceUnitPosition;
}
///
///
///
TextSegment ITextView.GetLineRange(ITextPointer position)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
int lineIndex = GetLineIndexFromPosition(position);
ITextPointer start = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex].Offset, LogicalDirection.Forward);
ITextPointer end = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex].Offset + _lineMetrics[lineIndex].ContentLength, LogicalDirection.Forward);
return new TextSegment(start, end, true);
}
///
///
///
ReadOnlyCollection ITextView.GetGlyphRuns(ITextPointer start, ITextPointer end)
{
// This method is not expected to be called.
Invariant.Assert(false);
return null;
}
///
///
///
bool ITextView.Contains(ITextPointer position)
{
return Contains(position);
}
///
///
///
void ITextView.BringPositionIntoViewAsync(ITextPointer position, object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
void ITextView.BringPointIntoViewAsync(Point point, object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
void ITextView.BringLineIntoViewAsync(ITextPointer position, double suggestedX, int count, object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
void ITextView.BringPageIntoViewAsync(ITextPointer position, Point suggestedOffset, int count, object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
void ITextView.CancelAsync(object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
bool ITextView.Validate()
{
UpdateLayout();
return this.IsLayoutValid;
}
///
///
///
bool ITextView.Validate(Point point)
{
return ((ITextView)this).Validate();
}
///
///
///
bool ITextView.Validate(ITextPointer position)
{
if (position.TextContainer != _host.TextContainer)
return false;
if (!this.IsLayoutValid)
{
// UpdateLayout has side-effects even when measure and arrange are clean,
// so avoid calling it unless we must.
UpdateLayout();
if (!this.IsLayoutValid)
{
// If we can't get the layout system to give us a valid
// measure/arrange, there's no hope.
return false;
}
}
// Force background layout iterations until we catch up
// with the position.
int lastValidOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset;
while (!Contains(position))
{
InvalidateMeasure();
UpdateLayout();
// UpdateLayout may invalidate the view.
if (!this.IsLayoutValid)
break;
// Break if background layout is not progressing.
int newLastValidOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset;
if (lastValidOffset >= newLastValidOffset)
break;
lastValidOffset = newLastValidOffset;
}
return this.IsLayoutValid && Contains(position);
}
///
///
///
void ITextView.ThrottleBackgroundTasksForUserInput()
{
if (_throttleBackgroundTimer == null)
{
// Start up a timer. Until the timer fires, we'll disable
// all background layout. This leaves the TextBox responsive
// to user input.
_throttleBackgroundTimer = new DispatcherTimer(DispatcherPriority.Background);
_throttleBackgroundTimer.Interval = new TimeSpan(0, 0, _throttleBackgroundSeconds);
_throttleBackgroundTimer.Tick += new EventHandler(OnThrottleBackgroundTimeout);
}
else
{
// Reset the timer.
_throttleBackgroundTimer.Stop();
}
_throttleBackgroundTimer.Start();
}
// Forces a full document invalidation.
// Called when properties that do affect layout (eg, FontSize)
// change value.
internal void Remeasure()
{
if (_lineMetrics != null)
{
_lineMetrics.Clear();
_viewportLineVisuals = null;
}
InvalidateMeasure();
}
// Forces a visual invalidation.
// Called when properties that do not affect layout (eg, ForegroundColor)
// change value.
internal void Rerender()
{
_viewportLineVisuals = null;
InvalidateArrange();
}
#endregion Internal Methods
//-----------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
// Control that owns this TextBoxView.
internal ITextBoxViewHost Host
{
get
{
return _host;
}
}
///
///
///
UIElement ITextView.RenderScope
{
get
{
return this;
}
}
///
///
///
ITextContainer ITextView.TextContainer
{
get
{
return _host.TextContainer;
}
}
///
///
///
bool ITextView.IsValid
{
get
{
return this.IsLayoutValid;
}
}
///
///
///
bool ITextView.RendersOwnSelection
{
get
{
return false;
}
}
///
///
///
ReadOnlyCollection ITextView.TextSegments
{
get
{
List segments = new List(1);
if (_lineMetrics != null)
{
ITextPointer start = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[0].Offset, LogicalDirection.Backward);
ITextPointer end = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[_lineMetrics.Count-1].EndOffset, LogicalDirection.Forward);
segments.Add(new TextSegment(start, end, true));
}
return new ReadOnlyCollection(segments);
}
}
#endregion Internal Properties
//-----------------------------------------------------
//
// Internal Events
//
//------------------------------------------------------
#region Internal Events
///
///
///
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
event BringPositionIntoViewCompletedEventHandler ITextView.BringPositionIntoViewCompleted
{
add { Invariant.Assert(false); }
remove { Invariant.Assert(false); }
}
///
///
///
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
event BringPointIntoViewCompletedEventHandler ITextView.BringPointIntoViewCompleted
{
add { Invariant.Assert(false); }
remove { Invariant.Assert(false); }
}
///
///
///
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
event BringLineIntoViewCompletedEventHandler ITextView.BringLineIntoViewCompleted
{
add { Invariant.Assert(false); }
remove { Invariant.Assert(false); }
}
///
///
///
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
event BringPageIntoViewCompletedEventHandler ITextView.BringPageIntoViewCompleted
{
add { Invariant.Assert(false); }
remove { Invariant.Assert(false); }
}
///
///
///
event EventHandler ITextView.Updated
{
add { UpdatedEvent += value; }
remove { UpdatedEvent -= value; }
}
#endregion Internal Events
//------------------------------------------------------
//
// Private Methods
//
//-----------------------------------------------------
#region Private Methods
// Initializes TextContainer event listeners.
// Called on the first Measure.
// We delay the init to avoid responding to events before we're attached
// to the visual tree, when it doesn't matter.
private void EnsureTextContainerListeners()
{
if (CheckFlags(Flags.TextContainerListenersInitialized))
return;
_host.TextContainer.Changing += new EventHandler(OnTextContainerChanging);
_host.TextContainer.Change += new TextContainerChangeEventHandler(OnTextContainerChange);
_host.TextContainer.Highlights.Changed += new HighlightChangedEventHandler(OnHighlightChanged);
SetFlags(true, Flags.TextContainerListenersInitialized);
}
// Initializes state used across a measure/arrange calculation.
private void EnsureCache()
{
if (_cache == null)
{
_cache = new TextCache(this);
}
}
// Reads the current (interesting) property values on the owning TextBox.
private LineProperties GetLineProperties()
{
TextProperties defaultTextProperties = new TextProperties((Control)_host, _host.IsTypographyDefaultValue);
// Pass page width and height as double.MaxValue when creating LineProperties, since TextBox does not restrict
// TextIndent or LineHeight.
return new LineProperties((Control)_host, (Control)_host, defaultTextProperties, null, this.CalculatedTextAlignment);
}
// Callback from the TextContainer when a change block starts.
private void OnTextContainerChanging(object sender, EventArgs args)
{
//
}
// Callback from the TextContainer on a document edit.
private void OnTextContainerChange(object sender, TextContainerChangeEventArgs args)
{
if (args.Count == 0)
{
// A no-op for this control. Happens when IMECharCount updates happen
// without corresponding SymbolCount changes.
return;
}
//
// Add the change to our dirty list.
//
if (_dirtyList == null)
{
_dirtyList = new DtrList();
}
DirtyTextRange dirtyTextRange = new DirtyTextRange(args);
_dirtyList.Merge(dirtyTextRange);
//
// Force a re-measure.
//
InvalidateMeasure();
}
// Callback from the TextContainer when a highlight changes.
private void OnHighlightChanged(object sender, HighlightChangedEventArgs args)
{
// The only supported highlight type for TextBoxView is SpellerHighlight.
if (args.OwnerType != typeof(SpellerHighlightLayer))
{
return;
}
//
// Add the change to our dirty list.
//
foreach (TextSegment segment in args.Ranges)
{
// Skip segments that don't intersect with the viewport.
if (this.IsLayoutValid)
{
int firstLineIndex;
int lastLineIndex;
GetVisibleLines(out firstLineIndex, out lastLineIndex);
if (_lineMetrics[firstLineIndex].Offset >= segment.End.Offset)
{
// This segment preceeds the viewport, skip it.
continue;
}
else if (_lineMetrics[lastLineIndex].EndOffset <= segment.Start.Offset)
{
// This segment follows the viewport, no need to look at it or following segments.
break;
}
}
// This segment is visible, prepare to invalidate arrange.
if (_dirtyList == null)
{
_dirtyList = new DtrList();
}
int positionsCovered = segment.End.Offset - segment.Start.Offset;
DirtyTextRange dirtyTextRange = new DirtyTextRange(segment.Start.Offset, positionsCovered, positionsCovered, true /* affectsRenderOnly */);
_dirtyList.Merge(dirtyTextRange);
}
//
// Force a visual update.
//
if (_dirtyList != null)
{
InvalidateArrange();
}
}
// Sets boolean state.
private void SetFlags(bool value, Flags flags)
{
_flags = value ? (_flags | flags) : (_flags & (~flags));
}
// Reads boolean state.
private bool CheckFlags(Flags flags)
{
return ((_flags & flags) == flags);
}
// Announces a layout change to any listeners.
private void FireTextViewUpdatedEvent()
{
if (UpdatedEvent != null)
{
UpdatedEvent(this, EventArgs.Empty);
}
}
// Returns the index of a line containing point, or -1 if no such
// line exists. If snapToText is true, the closest match is returned.
//
// Point must be in document space.
private int GetLineIndexFromPoint(Point point, bool snapToText)
{
Invariant.Assert(_lineMetrics.Count >= 1);
// Special case points above or below the content.
if (point.Y < 0)
{
return snapToText ? 0 : -1;
}
if (point.Y >= _lineHeight * _lineMetrics.Count)
{
return snapToText ? _lineMetrics.Count-1 : -1;
}
// Do a binary search to find the matching line.
int index = -1;
int min = 0;
int max = _lineMetrics.Count;
while (min < max)
{
index = min + (max - min) / 2;
LineRecord record = _lineMetrics[index];
double lineY = _lineHeight * index;
if (point.Y < lineY)
{
max = index;
}
else if (point.Y >= lineY + _lineHeight)
{
min = index + 1;
}
else
{
if (!snapToText &&
(point.X < 0 || point.X >= record.Width))
{
index = -1;
}
break;
}
}
return (min < max) ? index : -1;
}
// Returns the index of the line containing position.
private int GetLineIndexFromPosition(ITextPointer position)
{
return GetLineIndexFromOffset(position.Offset, position.LogicalDirection);
}
// Returns the index of the line containing position.
private int GetLineIndexFromPosition(ITextPointer position, LogicalDirection direction)
{
return GetLineIndexFromOffset(position.Offset, direction);
}
// Returns the index of the line containing the specified offset.
private int GetLineIndexFromOffset(int offset, LogicalDirection direction)
{
if (offset > 0 && direction == LogicalDirection.Backward)
{
// GetLineIndexFromOffset has forward bias, so backup for backward search.
offset--;
}
return GetLineIndexFromOffset(offset);
}
// Returns the index of the line containing the specified offset.
// Offset has forward direction -- we always return the following
// line in ambiguous cases.
private int GetLineIndexFromOffset(int offset)
{
int index = -1;
int min = 0;
int max = _lineMetrics.Count;
Invariant.Assert(_lineMetrics.Count >= 1);
while (true)
{
Invariant.Assert(min < max, "Couldn't find offset!");
index = min + (max - min) / 2;
LineRecord record = _lineMetrics[index];
if (offset < record.Offset)
{
max = index;
}
else if (offset > record.EndOffset)
{
min = index + 1;
}
else
{
if (offset == record.EndOffset && index < _lineMetrics.Count - 1)
{
// Go to the next line if we're between two lines.
index++;
}
break;
}
}
return index;
}
// Returns a formatted TextBoxLine at the specified index.
// Caller must Dispose the TextBoxLine.
// This method is expensive.
private TextBoxLine GetFormattedLine(int lineIndex)
{
LineProperties lineProperties;
return GetFormattedLine(lineIndex, out lineProperties);
}
// Returns a formatted TextBoxLine at the specified index.
// Caller must Dispose the TextBoxLine.
// This method is expensive.
private TextBoxLine GetFormattedLine(int lineIndex, out LineProperties lineProperties)
{
TextBoxLine line = new TextBoxLine(this);
LineRecord metrics = _lineMetrics[lineIndex];
lineProperties = GetLineProperties();
TextFormatter formatter = TextFormatter.FromCurrentDispatcher();
double width = GetWrappingWidth(this.RenderSize.Width);
double formatWidth = GetWrappingWidth(_previousConstraint.Width);
line.Format(metrics.Offset, formatWidth, width, lineProperties, new TextRunCache(), formatter);
Invariant.Assert(metrics.Length == line.Length, "Line is out of [....] with metrics!");
return line;
}
// Returns a TextPointer at the position closest to pixel offset x
// on a specified line.
private ITextPointer GetTextPositionFromDistance(int lineIndex, double x)
{
LineProperties lineProperties;
CharacterHit charIndex;
LogicalDirection logicalDirection;
using (TextBoxLine line = GetFormattedLine(lineIndex, out lineProperties))
{
charIndex = line.GetTextPositionFromDistance(x);
logicalDirection = (charIndex.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward;
}
return _host.TextContainer.CreatePointerAtOffset(charIndex.FirstCharacterIndex + charIndex.TrailingLength, logicalDirection);
}
// Updates IScrollInfo related state on an ArrangeOverride call.
private void ArrangeScrollData(Size arrangeSize)
{
if (_scrollData == null)
{
return;
}
bool invalidateScrollInfo = false;
if (!DoubleUtil.AreClose(_scrollData.Viewport, arrangeSize))
{
_scrollData.Viewport = arrangeSize;
invalidateScrollInfo = true;
}
if (!DoubleUtil.AreClose(_scrollData.Extent, _contentSize))
{
_scrollData.Extent = _contentSize;
invalidateScrollInfo = true;
}
Vector offset = new Vector(
Math.Max(0, Math.Min(_scrollData.ExtentWidth - _scrollData.ViewportWidth, _scrollData.HorizontalOffset)),
Math.Max(0, Math.Min(_scrollData.ExtentHeight - _scrollData.ViewportHeight, _scrollData.VerticalOffset)));
if (!DoubleUtil.AreClose(offset, _scrollData.Offset))
{
_scrollData.Offset = offset;
invalidateScrollInfo = true;
}
if (invalidateScrollInfo && _scrollData.ScrollOwner != null)
{
_scrollData.ScrollOwner.InvalidateScrollInfo();
}
}
// Updates line visuals on an ArrangeOverride call.
private void ArrangeVisuals(Size arrangeSize)
{
// If _dirtyList is non-null here, it means we
// have pending highlight changes to [....] to.
// These changes never affect line metrics, but
// they will clear out any cached Visuals affected.
if (_dirtyList != null)
{
InvalidateDirtyVisuals();
_dirtyList = null;
}
//
// Clear all visual children and initize state.
//
if (_visualChildren == null)
{
_visualChildren = new VisualCollection(this);
}
_visualChildren.Clear();
EnsureCache();
LineProperties lineProperties = _cache.LineProperties;
TextBoxLine line = new TextBoxLine(this);
//
// Calculate the current viewport extent, in lines.
// We won't do any work for lines that aren't visible.
//
int firstLineIndex;
int lastLineIndex;
GetVisibleLines(out firstLineIndex, out lastLineIndex);
SetViewportLines(firstLineIndex, lastLineIndex);
double width = GetWrappingWidth(arrangeSize.Width);
double horizontalOffset = GetTextAlignmentCorrection(lineProperties.TextAlignment, width);
double verticalOffset = this.VerticalAlignmentOffset;
if (_scrollData != null)
{
horizontalOffset -= _scrollData.HorizontalOffset;
verticalOffset -= _scrollData.VerticalOffset;
}
//
// Iterate across the visible lines.
// If we have a cached visual, simply update its current offset.
// Otherwise, allocate and render a new visual.
//
double formatWidth = GetWrappingWidth(_previousConstraint.Width);
for (int lineIndex = firstLineIndex; lineIndex <= lastLineIndex; lineIndex++)
{
DrawingVisual lineVisual = GetLineVisual(lineIndex);
if (lineVisual == null)
{
LineRecord metrics = _lineMetrics[lineIndex];
using (line)
{
line.Format(metrics.Offset, formatWidth, width, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
// We should be in [....] with current metrics, unless background layout is pending.
if (!this.IsBackgroundLayoutPending)
{
Invariant.Assert(metrics.Length == line.Length, "Line is out of [....] with metrics!");
}
lineVisual = line.CreateVisual();
}
SetLineVisual(lineIndex, lineVisual);
}
lineVisual.Offset = new Vector(horizontalOffset, verticalOffset + lineIndex * _lineHeight);
_visualChildren.Add(lineVisual);
}
}
// Called during Arrange, clears any cached line Visuals that intersect with highligh changes
// stored in the dirty range list.
private void InvalidateDirtyVisuals()
{
// Find the affected line, and reset its visual.
// Highlights never affect measure.
for (int i = 0; i < _dirtyList.Length; i++)
{
DirtyTextRange range = _dirtyList[i];
Invariant.Assert(range.AffectsRenderOnly); // We should never get any deltas affecting measure here.
Invariant.Assert(range.PositionsAdded == range.PositionsRemoved); // AffectsRenderOnly never changes document size.
int firstLineIndex = GetLineIndexFromOffset(range.StartIndex, LogicalDirection.Forward);
int endOffset = Math.Min(range.StartIndex + range.PositionsAdded, _host.TextContainer.SymbolCount);
int lastLineIndex = GetLineIndexFromOffset(endOffset, LogicalDirection.Backward);
for (int lineIndex = firstLineIndex; lineIndex <= lastLineIndex; lineIndex++)
{
ClearLineVisual(lineIndex);
}
}
}
// Transforms a Point in visual space (where (0, 0) is the upper-left
// corner of this FrameworkElement) to document space (where (0, 0) is
// the upper-left corner of the document, which may be scrolled to a
// negative offset relative to visual space).
private Point TransformToDocumentSpace(Point point)
{
if (_scrollData != null)
{
point = new Point(point.X + _scrollData.HorizontalOffset, point.Y + _scrollData.VerticalOffset);
}
point.X -= GetTextAlignmentCorrection(this.CalculatedTextAlignment, GetWrappingWidth(this.RenderSize.Width));
point.Y -= this.VerticalAlignmentOffset;
return point;
}
// Transforms a Rect in document space (where (0, 0) is
// the upper-left corner of the document, which may be scrolled to a
// negative offset relative to visual space) to visual space
// (where (0, 0) is the upper-left corner of this FrameworkElement).
private Rect TransformToVisualSpace(Rect rect)
{
if (_scrollData != null)
{
rect.X -= _scrollData.HorizontalOffset;
rect.Y -= _scrollData.VerticalOffset;
}
rect.X += GetTextAlignmentCorrection(this.CalculatedTextAlignment, GetWrappingWidth(this.RenderSize.Width));
rect.Y += this.VerticalAlignmentOffset;
return rect;
}
// Helper for GetTightBoundingGeometryFromTextPositions.
// Calculates the geometry of a single line intersected with a pair of document offsets.
private void GetTightBoundingGeometryFromLineIndex(int lineIndex, int unclippedStartOffset, int unclippedEndOffset, TextAlignment alignment, double endOfParaGlyphWidth, ref Geometry geometry)
{
IList bounds;
int startOffset = Math.Max(_lineMetrics[lineIndex].Offset, unclippedStartOffset);
int endOffset = Math.Min(_lineMetrics[lineIndex].EndOffset, unclippedEndOffset);
if (startOffset == endOffset) // GetRangeBounds does not accept empty runs.
{
// If we have any empty intersection, the only case to handle is when
// the empty range is exactly at the end of a line with a hard break.
// In that case we need to add the newline whitespace geometry.
if (unclippedStartOffset == _lineMetrics[lineIndex].EndOffset)
{
ITextPointer position = _host.TextContainer.CreatePointerAtOffset(unclippedStartOffset, LogicalDirection.Backward);
if (TextPointerBase.IsNextToPlainLineBreak(position, LogicalDirection.Backward))
{
Rect rect = new Rect(0, lineIndex * _lineHeight, endOfParaGlyphWidth, _lineHeight);
CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
}
}
else
{
Invariant.Assert(endOffset == _lineMetrics[lineIndex].Offset);
}
}
else
{
using (TextBoxLine line = GetFormattedLine(lineIndex))
{
bounds = line.GetRangeBounds(startOffset, endOffset - startOffset, 0, lineIndex * _lineHeight);
}
for (int i = 0; i < bounds.Count; i++)
{
Rect rect = TransformToVisualSpace(bounds[i]);
CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
}
// Add the Rect representing end-of-line, if the range covers the line end
// and the line has a hard line break.
if (unclippedEndOffset >= _lineMetrics[lineIndex].EndOffset)
{
ITextPointer endOfLinePosition = _host.TextContainer.CreatePointerAtOffset(endOffset, LogicalDirection.Backward);
if (TextPointerBase.IsNextToPlainLineBreak(endOfLinePosition, LogicalDirection.Backward))
{
double contentOffset = GetContentOffset(_lineMetrics[lineIndex].Width, alignment);
Rect rect = new Rect(contentOffset + _lineMetrics[lineIndex].Width, lineIndex * _lineHeight, endOfParaGlyphWidth, _lineHeight);
rect = TransformToVisualSpace(rect);
CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
}
}
}
}
// Returns the indices of the first and last lines that intersect
// with the current viewport.
private void GetVisibleLines(out int firstLineIndex, out int lastLineIndex)
{
Rect viewport = this.Viewport;
if (!viewport.IsEmpty)
{
firstLineIndex = (int)(viewport.Y / _lineHeight);
lastLineIndex = (int)Math.Ceiling((viewport.Y + viewport.Height) / _lineHeight) - 1;
// There may not be enough lines to fill the viewport, clip appropriately.
firstLineIndex = Math.Max(0, Math.Min(firstLineIndex, _lineMetrics.Count - 1));
lastLineIndex = Math.Max(0, Math.Min(lastLineIndex, _lineMetrics.Count - 1));
}
else
{
// If we're not hosted by a ScrollViewer, the viewport is the whole doc.
firstLineIndex = 0;
lastLineIndex = _lineMetrics.Count - 1;
}
}
// Performs one iteration of background measure.
// Background measure always works at the end of the current
// line metrics array -- invalidations to prevoiusly examined
// content is handled by incremental layout, synchronously.
//
// Returns the full content size, omitting any unanalyzed content
// at the document end.
private Size FullMeasureTick(double constraintWidth, LineProperties lineProperties)
{
Size desiredSize;
TextBoxLine line = new TextBoxLine(this);
int lineOffset;
bool endOfParagraph;
// Find the next position for this iteration.
if (_lineMetrics.Count == 0)
{
desiredSize = new Size();
lineOffset = 0;
}
else
{
desiredSize = _contentSize;
lineOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset;
}
// Calculate a stop time.
// We limit work to just a few milliseconds per iteration
// to avoid blocking the thread.
DateTime stopTime;
if ((ScrollBarVisibility)((Control)_host).GetValue(ScrollViewer.VerticalScrollBarVisibilityProperty) == ScrollBarVisibility.Auto)
{
// Workaround for bug 1766924.
// When VerticalScrollBarVisiblity == Auto, there's a problem with
// our interaction with ScrollViewer. Disable background layout to
// mitigate the problem until we can take a real fix in v.next.
//
stopTime = DateTime.MaxValue;
}
else
{
stopTime = DateTime.Now.AddMilliseconds(_maxMeasureTimeMs);
}
// Format lines until we hit the end of document or run out of time.
do
{
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
// This is a loop invariant, but has negligable cost.
//
_lineHeight = lineProperties.CalcLineAdvance(line.Height);
_lineMetrics.Add(new LineRecord(lineOffset, line));
// Desired width is always max of calculated line widths.
// Desired height is sum of all line heights.
desiredSize.Width = Math.Max(desiredSize.Width, line.Width);
desiredSize.Height += _lineHeight;
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
}
while (!endOfParagraph && DateTime.Now < stopTime);
if (!endOfParagraph)
{
// Ran out of time. Defer to background layout.
SetFlags(true, Flags.BackgroundLayoutPending);
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(OnBackgroundMeasure), null);
}
else
{
// Finished the entire document. Stop background layout.
SetFlags(false, Flags.BackgroundLayoutPending);
}
return desiredSize;
}
// Callback for the next background layout tick.
private object OnBackgroundMeasure(object o)
{
if (_throttleBackgroundTimer == null)
{
InvalidateMeasure();
}
return null;
}
// Measures content invalidated due to a TextContainer change (rather than
// a constraint change).
//
// Returns the full content size, omitting any unanalyzed content
// at the document end (due to pending background layout).
private Size IncrementalMeasure(double constraintWidth, LineProperties lineProperties)
{
Invariant.Assert(_dirtyList != null);
Invariant.Assert(_dirtyList.Length > 0); // We only allocate _dirtyList when it has content.
Size desiredSize = _contentSize;
DirtyTextRange range = _dirtyList[0];
// Background layout may be running, in which case we need to
// "clip" the scope of this incremental edit. We want to ignore
// changes that extend past the area of the document we're already
// tracking.
if (range.StartIndex > _lineMetrics[_lineMetrics.Count - 1].EndOffset)
{
Invariant.Assert(this.IsBackgroundLayoutPending);
return desiredSize;
}
// Merge the dirty list into a single superset DirtyTextRange.
//
int previousOffset = range.StartIndex;
int positionsAdded = range.PositionsAdded;
int positionsRemoved = range.PositionsRemoved;
for (int i = 1; i < _dirtyList.Length; i++)
{
range = _dirtyList[i];
if (range.StartIndex > _lineMetrics[_lineMetrics.Count - 1].EndOffset)
{
Invariant.Assert(this.IsBackgroundLayoutPending);
break;
}
int rangeDistance = range.StartIndex - previousOffset;
positionsAdded += rangeDistance + range.PositionsAdded;
positionsRemoved += rangeDistance + range.PositionsRemoved;
previousOffset = range.StartIndex;
}
range = new DirtyTextRange(_dirtyList[0].StartIndex, positionsAdded, positionsRemoved);
if (range.PositionsAdded >= range.PositionsRemoved)
{
IncrementalMeasureLinesAfterInsert(constraintWidth, lineProperties, range, ref desiredSize);
}
else if (range.PositionsAdded < range.PositionsRemoved)
{
IncrementalMeasureLinesAfterDelete(constraintWidth, lineProperties, range, ref desiredSize);
}
return desiredSize;
}
// Measures content invalidated due to a TextContainer change.
private void IncrementalMeasureLinesAfterInsert(double constraintWidth, LineProperties lineProperties, DirtyTextRange range, ref Size desiredSize)
{
int delta = range.PositionsAdded - range.PositionsRemoved;
Invariant.Assert(delta >= 0);
int lineIndex = GetLineIndexFromOffset(range.StartIndex, LogicalDirection.Forward);
if (delta > 0)
{
// Increment of the offsets of all following lines.
//
for (int i = lineIndex + 1; i < _lineMetrics.Count; i++)
{
_lineMetrics[i].Offset += delta;
}
}
TextBoxLine line = new TextBoxLine(this);
int lineOffset;
bool endOfParagraph = false;
// We need to re-format the previous line, because if someone inserted
// a hard break, the first directly affected line might now be shorter
// and mergeable with its predecessor.
if (lineIndex > 0) //
{
FormatFirstIncrementalLine(lineIndex - 1, constraintWidth, lineProperties, line, out lineOffset, out endOfParagraph);
}
else
{
lineOffset = _lineMetrics[lineIndex].Offset;
}
// Format the line directly affected by the change.
// If endOfParagraph == true, then the line was absorbed into its
// predessor (because its new content is thinner, or because the
// TextWrapping property changed).
if (!endOfParagraph)
{
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
_lineMetrics[lineIndex] = new LineRecord(lineOffset, line);
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
ClearLineVisual(lineIndex);
lineIndex++;
}
// Recalc the following lines not directly affected as needed.
SyncLineMetrics(range, constraintWidth, lineProperties, line, endOfParagraph, lineIndex, lineOffset);
desiredSize = BruteForceCalculateDesiredSize();
}
// Measures content invalidated due to a TextContainer change.
private void IncrementalMeasureLinesAfterDelete(double constraintWidth, LineProperties lineProperties, DirtyTextRange range, ref Size desiredSize)
{
int delta = range.PositionsAdded - range.PositionsRemoved;
Invariant.Assert(delta < 0);
int firstLineIndex = GetLineIndexFromOffset(range.StartIndex);
// Clip the scope of the affected lines to the region of the document
// we've already inspected. Clipping happens when background layout
// has not yet completed but an incremental update happens.
int endOffset = range.StartIndex + -delta - 1;
if (endOffset > _lineMetrics[_lineMetrics.Count - 1].EndOffset)
{
Invariant.Assert(this.IsBackgroundLayoutPending);
endOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset;
if (range.StartIndex == endOffset)
{
// Nothing left to do until background layout runs.
return;
}
}
int lastLineIndex = GetLineIndexFromOffset(endOffset);
// Increment the offsets of all following lines.
//
for (int i = lastLineIndex + 1; i < _lineMetrics.Count; i++)
{
_lineMetrics[i].Offset += delta;
}
TextBoxLine line = new TextBoxLine(this);
int lineIndex = firstLineIndex;
int lineOffset;
bool endOfParagraph;
// We need to re-format the previous line, because if someone inserted
// a hard break, the first directly affected line might now be shorter
// and mergeable with its predecessor.
if (lineIndex > 0) //
{
FormatFirstIncrementalLine(lineIndex - 1, constraintWidth, lineProperties, line, out lineOffset, out endOfParagraph);
}
else
{
lineOffset = _lineMetrics[lineIndex].Offset;
endOfParagraph = false;
}
//
// Update the first affected line. If it's completely covered, remove it entirely below.
if (!endOfParagraph &&
(range.StartIndex > lineOffset || range.StartIndex + -delta < _lineMetrics[lineIndex].EndOffset))
{
// Only part of the line is covered, reformat it.
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
_lineMetrics[lineIndex] = new LineRecord(lineOffset, line);
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
ClearLineVisual(lineIndex);
lineIndex++;
}
// Remove all the following lines that are completely covered.
//
_lineMetrics.RemoveRange(lineIndex, lastLineIndex - lineIndex + 1);
RemoveLineVisualRange(lineIndex, lastLineIndex - lineIndex + 1);
// Recalc the following lines not directly affected as needed.
SyncLineMetrics(range, constraintWidth, lineProperties, line, endOfParagraph, lineIndex, lineOffset);
desiredSize = BruteForceCalculateDesiredSize();
}
// Helper for IncrementalMeasureLinesAfterInsert, IncrementalMeasureLinesAfterDelete.
// Formats the line preceding the first directly affected line after a TextContainer change.
// In general this line might grow as content in the following line is absorbed.
private void FormatFirstIncrementalLine(int lineIndex, double constraintWidth, LineProperties lineProperties, TextBoxLine line,
out int lineOffset, out bool endOfParagraph)
{
int originalEndOffset = _lineMetrics[lineIndex].EndOffset;
lineOffset = _lineMetrics[lineIndex].Offset;
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
_lineMetrics[lineIndex] = new LineRecord(lineOffset, line);
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
// Don't clear the cached Visual unless something changed.
if (originalEndOffset != _lineMetrics[lineIndex].EndOffset)
{
ClearLineVisual(lineIndex);
}
}
// Helper for IncrementalMeasureLinesAfterInsert, IncrementalMeasureLinesAfterDelete.
// Formats line until we hit a synchronization point, a position where we know
// following lines could not be affected by the change.
private void SyncLineMetrics(DirtyTextRange range, double constraintWidth, LineProperties lineProperties, TextBoxLine line,
bool endOfParagraph, int lineIndex, int lineOffset)
{
bool offsetSyncOk = (range.PositionsAdded == 0 || range.PositionsRemoved == 0);
int lastCoveredCharOffset = range.StartIndex + Math.Max(range.PositionsAdded, range.PositionsRemoved);
// Keep updating lines until we find a synchronized position.
while (!endOfParagraph &&
(lineIndex == _lineMetrics.Count ||
!offsetSyncOk ||
lineOffset != _lineMetrics[lineIndex].Offset))
{
if (lineIndex < _lineMetrics.Count &&
lineOffset >= _lineMetrics[lineIndex].EndOffset)
{
// If the current line offset starts past the current line metric offset,
// remove the metric. This happens when the previous line
// frees up enough space to completely consume the following line.
// We can't simply replace the record without potentially missing our
// [....] position.
_lineMetrics.RemoveAt(lineIndex); //
RemoveLineVisualRange(lineIndex, 1);
}
else
{
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
LineRecord record = new LineRecord(lineOffset, line);
if (lineIndex == _lineMetrics.Count ||
lineOffset + line.Length <= _lineMetrics[lineIndex].Offset)
{
// The new line preceeds the old line, insert a new record.
//
_lineMetrics.Insert(lineIndex, record);
AddLineVisualPlaceholder(lineIndex);
}
else
{
// We expect to be colliding with the old line directly.
// If we extend past it, we're in danger of needlessly
// re-formatting the entire doc (ie, we miss the real
// [....] position and don't stop until EndOfParagraph).
Invariant.Assert(lineOffset < _lineMetrics[lineIndex].EndOffset);
_lineMetrics[lineIndex] = record;
ClearLineVisual(lineIndex);
// If this line ends past the invalidated region, and it
// has a hard line break, it's safe to synchronize on the next
// line metric with a matching start offset.
offsetSyncOk |= lastCoveredCharOffset <= record.EndOffset && line.HasLineBreak;
}
lineIndex++;
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
}
}
// Remove any trailing lines that got absorbed into the new last line.
if (endOfParagraph && lineIndex < _lineMetrics.Count)
{
int count = _lineMetrics.Count - lineIndex;
_lineMetrics.RemoveRange(lineIndex, count);
RemoveLineVisualRange(lineIndex, count);
}
}
// Calculates the bounding box of the content.
private Size BruteForceCalculateDesiredSize()
{
Size desiredSize = new Size();
//
for (int i = 0; i < _lineMetrics.Count; i++)
{
desiredSize.Width = Math.Max(desiredSize.Width, _lineMetrics[i].Width);
}
desiredSize.Height = _lineMetrics.Count * _lineHeight;
return desiredSize;
}
// Updates the array of cached Visuals matching lines in the viewport.
// Called on arrange as the viewport changes.
private void SetViewportLines(int firstLineIndex, int lastLineIndex)
{
List oldLineVisuals = _viewportLineVisuals;
int oldLineVisualsIndex = _viewportLineVisualsIndex;
// Assume we'll clear the cache.
_viewportLineVisuals = null;
_viewportLineVisualsIndex = -1;
int count = lastLineIndex - firstLineIndex + 1;
// Don't bother caching Visuals for single-line TextBoxes.
// In this common case memory is important and the single line will
// always be the one invalidated on an edit.
if (count <= 1)
{
return;
}
// Re-init the cache to match the new viewport size.
// Even if we don't have any Visuals to copy over from
// the previous cache, it's useful to pre-allocate space
// in the cache that will be filled incrementally during
// Arrange.
_viewportLineVisuals = new List(count);
_viewportLineVisuals.AddRange(new DrawingVisual[count]); //
_viewportLineVisualsIndex = firstLineIndex;
if (oldLineVisuals == null)
{
return;
}
// Copy over the intersection of the old viewport Visuals cache
// with the new one.
// It would be convenient if the code below assumed that if
// viewport size has changed, we never make it this far (the
// old viewport visuals should have been thrown away, since
// there's no way now to map to the new constraint).
//
// However, because of rounding error, we can end up in the situation
// where the indices/lengths between the two arrays vary, after
// an arrange invalidation.
int oldLastLineIndex = oldLineVisualsIndex + oldLineVisuals.Count - 1;
if (oldLineVisualsIndex <= lastLineIndex &&
oldLastLineIndex >= firstLineIndex)
{
int lineIndex = Math.Max(oldLineVisualsIndex, firstLineIndex);
int lineCount = Math.Min(oldLastLineIndex, firstLineIndex + count - 1) - lineIndex + 1;
for (int i = 0; i < lineCount; i++)
{
_viewportLineVisuals[lineIndex - _viewportLineVisualsIndex + i] = oldLineVisuals[lineIndex - oldLineVisualsIndex + i];
}
}
}
// Retrives the cached line Visual matching a line index in the
// current viewport. Will return null if no value is cached.
private DrawingVisual GetLineVisual(int lineIndex)
{
DrawingVisual lineVisual = null;
if (_viewportLineVisuals != null)
{
lineVisual = _viewportLineVisuals[lineIndex - _viewportLineVisualsIndex];
}
return lineVisual;
}
// Adds a Visual to the line Visuals cache.
private void SetLineVisual(int lineIndex, DrawingVisual lineVisual)
{
if (_viewportLineVisuals != null)
{
_viewportLineVisuals[lineIndex - _viewportLineVisualsIndex] = lineVisual;
}
}
// Adds an empty entry to the line Visuals cache.
private void AddLineVisualPlaceholder(int lineIndex)
{
if (_viewportLineVisuals != null)
{
// Clip to visible region.
if (lineIndex >= _viewportLineVisualsIndex &&
lineIndex < _viewportLineVisualsIndex + _viewportLineVisuals.Count)
{
_viewportLineVisuals.Insert(lineIndex - _viewportLineVisualsIndex, null);
}
}
}
// Invalidates a cached line Visual.
private void ClearLineVisual(int lineIndex)
{
if (_viewportLineVisuals != null)
{
// Clip to visible region.
if (lineIndex >= _viewportLineVisualsIndex &&
lineIndex < _viewportLineVisualsIndex + _viewportLineVisuals.Count)
{
_viewportLineVisuals[lineIndex - _viewportLineVisualsIndex] = null;
}
}
}
// Removes a range of Visuals from the line Visual cache.
private void RemoveLineVisualRange(int lineIndex, int count)
{
if (_viewportLineVisuals != null)
{
// Clip to visible region.
if (lineIndex < _viewportLineVisualsIndex)
{
count -= _viewportLineVisualsIndex - lineIndex;
count = Math.Max(0, count);
lineIndex = _viewportLineVisualsIndex;
}
if (lineIndex < _viewportLineVisualsIndex + _viewportLineVisuals.Count)
{
count = Math.Min(count, _viewportLineVisuals.Count - (lineIndex - _viewportLineVisualsIndex));
_viewportLineVisuals.RemoveRange(lineIndex - _viewportLineVisualsIndex, count);
}
}
}
// Callback for the background layout throttle timer.
// Resumes backgound layout.
private void OnThrottleBackgroundTimeout(object sender, EventArgs e)
{
_throttleBackgroundTimer.Stop();
_throttleBackgroundTimer = null;
if (this.IsBackgroundLayoutPending)
{
OnBackgroundMeasure(null);
}
}
// Returns the x-axis offset of content on a line, based on current
// text alignment.
private double GetContentOffset(double lineWidth, TextAlignment aligment)
{
double contentOffset;
double width = GetWrappingWidth(this.RenderSize.Width);
switch (aligment)
{
case TextAlignment.Right:
contentOffset = width - lineWidth;
break;
case TextAlignment.Center:
contentOffset = (width - lineWidth) / 2;
break;
default:
// Default is Left alignment, in this case offset is 0.
contentOffset = 0.0;
break;
}
return contentOffset;
}
// Converts a HorizontalAlignment enum to a TextAlignment enum.
private TextAlignment HorizontalAlignmentToTextAlignment(HorizontalAlignment horizontalAlignment)
{
TextAlignment textAlignment;
switch (horizontalAlignment)
{
case HorizontalAlignment.Left:
default:
textAlignment = TextAlignment.Left;
break;
case HorizontalAlignment.Right:
textAlignment = TextAlignment.Right;
break;
case HorizontalAlignment.Center:
textAlignment = TextAlignment.Center;
break;
case HorizontalAlignment.Stretch:
textAlignment = TextAlignment.Justify;
break;
}
return textAlignment;
}
///
///
///
private bool Contains(ITextPointer position)
{
Invariant.Assert(this.IsLayoutValid);
return position.TextContainer == _host.TextContainer &&
_lineMetrics != null &&
_lineMetrics[_lineMetrics.Count - 1].EndOffset >= position.Offset;
}
// Converts a render size width into a wrapping width for lines.
private double GetWrappingWidth(double width)
{
if (width < _contentSize.Width)
{
width = _contentSize.Width;
}
if (width > _previousConstraint.Width)
{
width = _previousConstraint.Width;
}
// Make sure that TextFormatter limitations are not exceeded.
//
TextDpi.EnsureValidLineWidth(ref width);
return width;
}
// When the content size exceeds the viewport size, TextLine will align
// its content such that the "extra" is clipped in inappropriate ways.
//
// TextAlignment.Center: line offset = -(contentWidth - viewportWidth) / 2
// TextAlignment.Right: line offset = -(contentWidth - viewportWidth)
//
// This method returns a value that exactly cancels out the undesired
// offset, which is used to adjust the content origin to local zero.
private double GetTextAlignmentCorrection(TextAlignment textAlignment, double width)
{
double correction = 0;
if (textAlignment != TextAlignment.Left &&
_contentSize.Width > width)
{
correction = -GetContentOffset(_contentSize.Width, textAlignment);
}
return correction;
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//-----------------------------------------------------
#region Private Properties
// True when measure and arrange are valid.
private bool IsLayoutValid
{
get
{
return this.IsMeasureValid && this.IsArrangeValid;
}
}
// Current visible region in document space.
private Rect Viewport
{
get
{
return _scrollData == null ? Rect.Empty :
new Rect(_scrollData.HorizontalOffset, _scrollData.VerticalOffset, _scrollData.ViewportWidth, _scrollData.ViewportHeight);
}
}
// True when background layout has not completed.
private bool IsBackgroundLayoutPending
{
get
{
return CheckFlags(Flags.BackgroundLayoutPending);
}
}
// Offset in pixels of the first line due to VerticalContentAlignment.
private double VerticalAlignmentOffset
{
get
{
double offset;
switch (((Control)_host).VerticalContentAlignment)
{
case VerticalAlignment.Top:
case VerticalAlignment.Stretch:
default:
offset = 0;
break;
case VerticalAlignment.Center:
offset = this.VerticalPadding / 2;
break;
case VerticalAlignment.Bottom:
offset = this.VerticalPadding;
break;
}
return offset;
}
}
// Calculated TextAlignment property value.
// Takes into account collisions between TextAlignment and HorizontalContentAlignment properties.
//
// TextAlignment always wins unless it has no local value and HorizontalContentAlignment does.
//
// In order of precedence:
// 1. Local value on TextAlignment.
// 2. Local value on HorizontalContentAlignment.
// 3. TextAlignment.
private TextAlignment CalculatedTextAlignment
{
get
{
TextAlignment alignment;
Control host = (Control)_host;
object o = host.ReadLocalValue(TextBox.TextAlignmentProperty);
if (o != DependencyProperty.UnsetValue)
{
// Re-evaluate o to resolve any databinding expressions.
o = host.GetValue(TextBox.TextAlignmentProperty);
alignment = (TextAlignment)o;
}
else
{
o = host.ReadLocalValue(TextBox.HorizontalContentAlignmentProperty);
if (o != DependencyProperty.UnsetValue)
{
// Re-evaluate o to resolve any databinding expressions.
o = host.GetValue(TextBox.HorizontalContentAlignmentProperty);
alignment = HorizontalAlignmentToTextAlignment((HorizontalAlignment)o);
}
else
{
alignment = (TextAlignment)host.GetValue(TextBox.TextAlignmentProperty);
}
}
return alignment;
}
}
// The delta between the current viewport height and the content height.
// Returns zero when content height is greater than viewport height.
private double VerticalPadding
{
get
{
double padding;
Rect viewport = this.Viewport;
if (viewport.IsEmpty)
{
padding = 0;
}
else
{
padding = Math.Max(0, viewport.Height - _contentSize.Height);
}
return padding;
}
}
#endregion Private Properties
//-----------------------------------------------------
//
// Private Types
//
//-----------------------------------------------------
#region Private Types
// Booleans for the _flags field.
[System.Flags]
private enum Flags
{
// When true, TextContainer listeners are hooked up.
TextContainerListenersInitialized = 0x1,
// When true, background layout is still running.
BackgroundLayoutPending = 0x2,
}
// Caches state used across a measure/arrange calculation.
// In addition to performance benefits, this ensures a consistent
// view of property values across measure/arrange.
private class TextCache
{
internal TextCache(TextBoxView owner)
{
_lineProperties = owner.GetLineProperties();
_textRunCache = new TextRunCache();
}
internal LineProperties LineProperties
{
get { return _lineProperties; }
}
internal TextRunCache TextRunCache
{
get { return _textRunCache; }
}
// Cached TextFormatter for this thread.
internal TextFormatter TextFormatter
{
get
{
if (_textFormatter == null)
{
_textFormatter = TextFormatter.FromCurrentDispatcher();
}
return _textFormatter;
}
}
private readonly LineProperties _lineProperties;
private readonly TextRunCache _textRunCache;
private TextFormatter _textFormatter;
}
// Line metrics array entry.
private class LineRecord
{
internal LineRecord(int offset, TextBoxLine line)
{
_offset = offset;
_length = line.Length;
_contentLength = line.ContentLength;
_width = line.Width;
}
internal int Offset
{
get { return _offset; }
set { _offset = value; }
}
internal int Length { get { return _length; } }
internal int ContentLength { get { return _contentLength; } }
internal double Width { get { return _width; } }
internal int EndOffset { get { return _offset + _length; } }
private int _offset;
private readonly int _length; //
private readonly int _contentLength;
private readonly double _width;
}
#endregion Private Types
//------------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
#region Private Fields
// TextBox that owns this TextBoxView.
private readonly ITextBoxViewHost _host;
// Bounding box of the content, up to the point reached by background layout.
private Size _contentSize;
// The most recent constraint passed to MeasureOverride.
// this.PreviousConstraint cannot be used because it can be affected
// by Margin, Width/Min/MaxWidth propreties and ClipToBounds.
private Size _previousConstraint;
// Caches state used across a measure/arrange calculation.
// In addition to performance benefits, this ensures a consistent
// view of property values across measure/arrange.
private TextCache _cache;
// Height of any line, in pixels.
private double _lineHeight;
// Visuals tracked by GetVisualChild/VisualChilrenCount overrides.
private VisualCollection _visualChildren;
// Array of cached line metrics.
private List _lineMetrics;
// Array of cached line Visuals for the current viewport.
private List _viewportLineVisuals;
// Index of first line in the _viewportLineVisuals array.
private int _viewportLineVisualsIndex;
// IScrollInfo state/code.
private ScrollData _scrollData;
// List of invalidated regions created by TextContainer changes.
private DtrList _dirtyList;
// Timer used to disable background layout during user interaction.
private DispatcherTimer _throttleBackgroundTimer;
// Boolean flags, set with Flags enum.
private Flags _flags;
// Updated event listeners.
private EventHandler UpdatedEvent;
// Max time slice to run FullMeasureTick.
private const uint _maxMeasureTimeMs = 200;
// Number of seconds to disable background layout after receiving
// user input.
private const int _throttleBackgroundSeconds = 2;
#endregion Private Fields
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
// File: TextBoxView.cs
//
// Description: Content presenter for the TextBox.
//
//---------------------------------------------------------------------------
namespace System.Windows.Controls
{
using System.Windows.Documents;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using System.Windows.Threading;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MS.Internal;
using MS.Internal.Text;
using MS.Internal.Documents;
using MS.Internal.PtsHost;
using System.Windows.Media.TextFormatting;
// Content presenter for the TextBox.
internal class TextBoxView : FrameworkElement, ITextView, IScrollInfo, IServiceProvider
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
// Static constructor.
static TextBoxView()
{
// Set a margin so that the bidi caret has room to render at the edges of content.
MarginProperty.OverrideMetadata(typeof(TextBoxView), new FrameworkPropertyMetadata(new Thickness(CaretElement.BidiCaretIndicatorWidth, 0, CaretElement.BidiCaretIndicatorWidth, 0)));
}
// Constructor.
internal TextBoxView(ITextBoxViewHost host)
{
Invariant.Assert(host is Control);
_host = host;
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//-----------------------------------------------------
#region Public Methods
// IServiceProvider for TextEditor/renderscope contract.
// Provides access to our ITextView implementation.
object IServiceProvider.GetService(Type serviceType)
{
object service = null;
if (serviceType == typeof(ITextView))
{
service = this;
}
return service;
}
///
///
///
void IScrollInfo.LineUp()
{
if (_scrollData != null)
{
_scrollData.LineUp(this);
}
}
///
///
///
void IScrollInfo.LineDown()
{
if (_scrollData != null)
{
_scrollData.LineDown(this);
}
}
///
///
///
void IScrollInfo.LineLeft()
{
if (_scrollData != null)
{
_scrollData.LineLeft(this);
}
}
///
///
///
void IScrollInfo.LineRight()
{
if (_scrollData != null)
{
_scrollData.LineRight(this);
}
}
///
///
///
void IScrollInfo.PageUp()
{
if (_scrollData != null)
{
_scrollData.PageUp(this);
}
}
///
///
///
void IScrollInfo.PageDown()
{
if (_scrollData != null)
{
_scrollData.PageDown(this);
}
}
///
///
///
void IScrollInfo.PageLeft()
{
if (_scrollData != null)
{
_scrollData.PageLeft(this);
}
}
///
///
///
void IScrollInfo.PageRight()
{
if (_scrollData != null)
{
_scrollData.PageRight(this);
}
}
///
///
///
void IScrollInfo.MouseWheelUp()
{
if (_scrollData != null)
{
_scrollData.MouseWheelUp(this);
}
}
///
///
///
void IScrollInfo.MouseWheelDown()
{
if (_scrollData != null)
{
_scrollData.MouseWheelDown(this);
}
}
///
///
///
void IScrollInfo.MouseWheelLeft()
{
if (_scrollData != null)
{
_scrollData.MouseWheelLeft(this);
}
}
///
///
///
void IScrollInfo.MouseWheelRight()
{
if (_scrollData != null)
{
_scrollData.MouseWheelRight(this);
}
}
///
///
///
void IScrollInfo.SetHorizontalOffset(double offset)
{
if (_scrollData != null)
{
_scrollData.SetHorizontalOffset(this, offset);
}
}
///
///
///
void IScrollInfo.SetVerticalOffset(double offset)
{
if (_scrollData != null)
{
_scrollData.SetVerticalOffset(this, offset);
}
}
///
///
///
Rect IScrollInfo.MakeVisible(Visual visual, Rect rectangle)
{
if (_scrollData == null)
{
rectangle = Rect.Empty;
}
else
{
rectangle = _scrollData.MakeVisible(this, visual, rectangle);
}
return rectangle;
}
///
///
///
bool IScrollInfo.CanVerticallyScroll
{
get
{
return (_scrollData != null) ? _scrollData.CanVerticallyScroll : false;
}
set
{
if (_scrollData != null)
{
_scrollData.CanVerticallyScroll = value;
}
}
}
///
///
///
bool IScrollInfo.CanHorizontallyScroll
{
get
{
return (_scrollData != null) ? _scrollData.CanHorizontallyScroll : false;
}
set
{
if (_scrollData != null)
{
_scrollData.CanHorizontallyScroll = value;
}
}
}
///
///
///
double IScrollInfo.ExtentWidth
{
get
{
return (_scrollData != null) ? _scrollData.ExtentWidth : 0;
}
}
///
///
///
double IScrollInfo.ExtentHeight
{
get
{
return (_scrollData != null) ? _scrollData.ExtentHeight : 0;
}
}
///
///
///
double IScrollInfo.ViewportWidth
{
get
{
return (_scrollData != null) ? _scrollData.ViewportWidth : 0;
}
}
///
///
///
double IScrollInfo.ViewportHeight
{
get
{
return (_scrollData != null) ? _scrollData.ViewportHeight : 0;
}
}
///
///
///
double IScrollInfo.HorizontalOffset
{
get
{
return (_scrollData != null) ? _scrollData.HorizontalOffset : 0;
}
}
///
///
///
double IScrollInfo.VerticalOffset
{
get
{
return (_scrollData != null) ? _scrollData.VerticalOffset : 0;
}
}
///
///
///
ScrollViewer IScrollInfo.ScrollOwner
{
get
{
return (_scrollData != null) ? _scrollData.ScrollOwner : null;
}
set
{
if (_scrollData == null)
{
// Create cached scroll info.
_scrollData = new ScrollData();
}
_scrollData.SetScrollOwner(this, value);
}
}
#endregion Public Methods
//------------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
// Calculates ideal content size.
protected override Size MeasureOverride(Size constraint)
{
// Lazy init TextContainer listeners on the first measure.
EnsureTextContainerListeners();
// Lazy allocate _lineMetrics on the first measure.
if (_lineMetrics == null)
{
_lineMetrics = new List(1);
}
Size desiredSize;
// Init a cache we'll use here and in the following ArrangeOverride call.
_cache = null;
EnsureCache();
LineProperties lineProperties = _cache.LineProperties;
// Skip the measure if constraints have not changed.
bool widthChanged = !DoubleUtil.AreClose(constraint.Width, _previousConstraint.Width);
// If width changed and TextAlignment is Center or Right the visual offsets of the visible
// lines need to be recalculated.
if (widthChanged && lineProperties.TextAlignment != TextAlignment.Left)
{
_viewportLineVisuals = null;
}
bool constraintschanged = widthChanged &&
lineProperties.TextWrapping != TextWrapping.NoWrap;
if (_lineMetrics.Count == 0 || constraintschanged)
{
// Null out the dirty list when constraints change -- everything's dirty.
_dirtyList = null;
}
else if (_dirtyList == null && !this.IsBackgroundLayoutPending)
{
// No dirty region, no constraint change, no pending background layout.
desiredSize = _contentSize;
goto Exit;
}
// Treat an insert into an empty document just like a full invalidation,
// to allow background layout to run.
if (_dirtyList != null &&
_lineMetrics.Count == 1 && _lineMetrics[0].EndOffset == 0)
{
_lineMetrics.Clear();
_viewportLineVisuals = null;
_dirtyList = null;
}
Size safeConstraint = constraint;
// Make sure that TextFormatter limitations are not exceeded.
//
TextDpi.EnsureValidLineWidth(ref safeConstraint);
// Do the measure.
if (_dirtyList == null)
{
if (constraintschanged)
{
_lineMetrics.Clear();
_viewportLineVisuals = null;
}
desiredSize = FullMeasureTick(safeConstraint.Width, lineProperties);
}
else
{
desiredSize = IncrementalMeasure(safeConstraint.Width, lineProperties);
}
Invariant.Assert(_lineMetrics.Count >= 1);
_dirtyList = null;
double oldWidth = _contentSize.Width;
_contentSize = desiredSize;
// If the width has changed we need to reformat if we're centered or right aligned so the
// spacing gets properly updated.
if (oldWidth != desiredSize.Width && lineProperties.TextAlignment != TextAlignment.Left)
{
Rerender();
}
Exit:
// DesiredSize is set to the calculated size of the content.
// If hosted by ScrollViewer, desired size is limited to constraint.
if (_scrollData != null)
{
desiredSize.Width = Math.Min(constraint.Width, desiredSize.Width);
desiredSize.Height = Math.Min(constraint.Height, desiredSize.Height);
}
_previousConstraint = constraint;
return desiredSize;
}
// Arranges content within a specified constraint.
protected override Size ArrangeOverride(Size arrangeSize)
{
if (_lineMetrics == null || _lineMetrics.Count == 0)
{
// No matching MeasureOverride call.
goto Exit;
}
EnsureCache();
ArrangeScrollData(arrangeSize);
ArrangeVisuals(arrangeSize);
_cache = null;
FireTextViewUpdatedEvent();
InvalidateVisual();
Exit:
return arrangeSize;
}
// Render callback for this TextBoxView.
protected override void OnRender(DrawingContext context)
{
// Render a transparent Rect to enable hit-testing even when content does not fill
// the entire viewport.
//
context.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, new Rect(0, 0, this.RenderSize.Width, this.RenderSize.Height));
}
///
/// Derived class must implement to support Visual children. The method must return
/// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1.
///
/// By default a Visual does not have any children.
///
/// Remark:
/// During this virtual call it is not valid to modify the Visual tree.
///
protected override Visual GetVisualChild(int index)
{
if (index >= this.VisualChildrenCount)
{
throw new ArgumentOutOfRangeException("index");
}
return _visualChildren[index];
}
#endregion Protected Methods
//-----------------------------------------------------
//
// Protected Properties
//
//------------------------------------------------------
#region Protected Properties
///
/// Derived classes override this property to enable the Visual code to enumerate
/// the Visual children. Derived classes need to return the number of children
/// from this method.
///
/// By default a Visual does not have any children.
///
/// Remark:
/// During this virtual method the Visual tree must not be modified.
///
protected override int VisualChildrenCount
{
get
{
return (_visualChildren == null) ? 0 : _visualChildren.Count;
}
}
#endregion Protected Properties
//-----------------------------------------------------
//
// Internal Methods
//
//-----------------------------------------------------
#region Internal Methods
///
///
///
ITextPointer ITextView.GetTextPositionFromPoint(Point point, bool snapToText)
{
Invariant.Assert(this.IsLayoutValid);
point = TransformToDocumentSpace(point);
int lineIndex = GetLineIndexFromPoint(point, snapToText);
ITextPointer position;
if (lineIndex == -1)
{
position = null;
}
else
{
position = GetTextPositionFromDistance(lineIndex, point.X);
position.Freeze();
}
return position;
}
///
///
///
Rect ITextView.GetRectangleFromTextPosition(ITextPointer position)
{
Rect rect;
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
int offset = position.Offset;
if (offset > 0 && position.LogicalDirection == LogicalDirection.Backward)
{
// TextBoxLine always gets the forward Rect, so back up to preceding char.
offset--;
}
int lineIndex = GetLineIndexFromOffset(offset);
FlowDirection flowDirection;
LineProperties lineProperties;
using (TextBoxLine line = GetFormattedLine(lineIndex, out lineProperties))
{
rect = line.GetBoundsFromTextPosition(offset, out flowDirection);
}
if (!rect.IsEmpty) // Empty rects can't be modified.
{
rect.Y += lineIndex * _lineHeight;
// Return only TopLeft and Height.
// Adjust rect.Left by taking into account flow direction of the
// content and orientation of input position.
if (lineProperties.FlowDirection != flowDirection)
{
if (position.LogicalDirection == LogicalDirection.Forward || position.Offset == 0)
{
rect.X = rect.Right;
}
}
else
{
if (position.LogicalDirection == LogicalDirection.Backward && position.Offset > 0)
{
rect.X = rect.Right;
}
}
rect.Width = 0;
}
return TransformToVisualSpace(rect);
}
///
///
///
Rect ITextView.GetRawRectangleFromTextPosition(ITextPointer position, out Transform transform)
{
transform = Transform.Identity;
return ((ITextView)this).GetRectangleFromTextPosition(position);
}
///
///
///
Geometry ITextView.GetTightBoundingGeometryFromTextPositions(ITextPointer startPosition, ITextPointer endPosition)
{
Invariant.Assert(this.IsLayoutValid);
Geometry geometry = null;
double endOfParaGlyphWidth = ((Control)_host).FontSize * CaretElement.c_endOfParaMagicMultiplier;
// Since background layout may be running, clip to the computed region.
int startOffset = Math.Min(_lineMetrics[_lineMetrics.Count-1].EndOffset, startPosition.Offset);
int endOffset = Math.Min(_lineMetrics[_lineMetrics.Count - 1].EndOffset, endPosition.Offset);
// Find the intersection of the viewport with the requested range.
int firstLineIndex;
int lastLineIndex;
GetVisibleLines(out firstLineIndex, out lastLineIndex);
firstLineIndex = Math.Max(firstLineIndex, GetLineIndexFromOffset(startOffset, LogicalDirection.Forward));
lastLineIndex = Math.Min(lastLineIndex, GetLineIndexFromOffset(endOffset, LogicalDirection.Backward));
if (firstLineIndex > lastLineIndex)
{
// Visible region does not intersect with geometry.
return null;
}
// Partially covered lines require a line format, so we'll handle them specially.
// Only the first and last line are potentially partially covered.
bool firstLinePartiallyCovered = _lineMetrics[firstLineIndex].Offset < startOffset ||
_lineMetrics[firstLineIndex].EndOffset > endOffset;
bool lastLinePartiallyCovered = _lineMetrics[lastLineIndex].Offset < startOffset ||
_lineMetrics[lastLineIndex].EndOffset > endOffset;
TextAlignment alignment = this.CalculatedTextAlignment;
int lineIndex = firstLineIndex;
// If we don't cover the entire first line, special case it.
if (firstLinePartiallyCovered)
{
GetTightBoundingGeometryFromLineIndex(lineIndex, startOffset, endOffset, alignment, endOfParaGlyphWidth, ref geometry);
lineIndex++;
}
// If it is completely covered, adjust lastLineIndex such that we handle
// the last line in the loop below.
if (firstLineIndex <= lastLineIndex && !lastLinePartiallyCovered)
{
lastLineIndex++;
}
// Handle all the lines that are entirely covered -- they don't require any heavy lifting.
for (; lineIndex < lastLineIndex; lineIndex++)
{
double contentOffset = GetContentOffset(_lineMetrics[lineIndex].Width, alignment);
Rect rect = new Rect(contentOffset, lineIndex * _lineHeight, _lineMetrics[lineIndex].Width, _lineHeight);
// Add extra padding at the end of lines with linebreaks.
ITextPointer endOfLinePosition = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex].EndOffset, LogicalDirection.Backward);
if (TextPointerBase.IsNextToPlainLineBreak(endOfLinePosition, LogicalDirection.Backward))
{
rect.Width += endOfParaGlyphWidth;
}
rect = TransformToVisualSpace(rect);
CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
}
// If we don't cover the entire last line, special case it.
// Otherwise, we already handled it in the loop above.
if (lineIndex == lastLineIndex && lastLinePartiallyCovered)
{
GetTightBoundingGeometryFromLineIndex(lineIndex, startOffset, endOffset, alignment, endOfParaGlyphWidth, ref geometry);
}
return geometry;
}
///
///
///
ITextPointer ITextView.GetPositionAtNextLine(ITextPointer position, double suggestedX, int count, out double newSuggestedX, out int linesMoved)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
newSuggestedX = suggestedX;
int lineIndex = GetLineIndexFromPosition(position);
int nextLineIndex = Math.Max(0, Math.Min(_lineMetrics.Count - 1, lineIndex + count));
linesMoved = nextLineIndex - lineIndex;
ITextPointer nextLinePosition;
if (linesMoved == 0)
{
nextLinePosition = position.GetFrozenPointer(position.LogicalDirection);
}
else if (DoubleUtil.IsNaN(suggestedX))
{
nextLinePosition = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex + linesMoved].Offset, LogicalDirection.Forward);
}
else
{
suggestedX -= GetTextAlignmentCorrection(this.CalculatedTextAlignment, GetWrappingWidth(this.RenderSize.Width));
nextLinePosition = GetTextPositionFromDistance(nextLineIndex, suggestedX);
}
nextLinePosition.Freeze();
return nextLinePosition;
}
///
///
///
ITextPointer ITextView.GetPositionAtNextPage(ITextPointer position, Point suggestedOffset, int count, out Point newSuggestedOffset, out int pagesMoved)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
newSuggestedOffset = new Point();
pagesMoved = 0;
return null;
}
///
///
///
bool ITextView.IsAtCaretUnitBoundary(ITextPointer position)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
bool boundary = false;
int lineIndex = GetLineIndexFromPosition(position);
CharacterHit sourceCharacterHit = new CharacterHit();
if (position.LogicalDirection == LogicalDirection.Forward)
{
// Forward context, go to leading edge of position offset
sourceCharacterHit = new CharacterHit(position.Offset, 0);
}
else if (position.LogicalDirection == LogicalDirection.Backward)
{
if (position.Offset > _lineMetrics[lineIndex].Offset)
{
// For backward context, go to trailing edge of previous character
sourceCharacterHit = new CharacterHit(position.Offset - 1, 1);
}
else
{
// There is no previous trailing edge on this line. We don't consider this a unit boundary.
return false;
}
}
using (TextBoxLine line = GetFormattedLine(lineIndex))
{
boundary = line.IsAtCaretCharacterHit(sourceCharacterHit);
}
return boundary;
}
///
///
///
ITextPointer ITextView.GetNextCaretUnitPosition(ITextPointer position, LogicalDirection direction)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
// Special case document start/end.
if (position.Offset == 0 && direction == LogicalDirection.Backward)
{
return position.GetFrozenPointer(LogicalDirection.Forward);
}
else if (position.Offset == _host.TextContainer.SymbolCount && direction == LogicalDirection.Forward)
{
return position.GetFrozenPointer(LogicalDirection.Backward);
}
int lineIndex = GetLineIndexFromPosition(position);
CharacterHit sourceCharacterHit = new CharacterHit(position.Offset, 0);
CharacterHit nextCharacterHit;
using (TextBoxLine line = GetFormattedLine(lineIndex))
{
if (direction == LogicalDirection.Forward)
{
// Get the next caret position from the line
nextCharacterHit = line.GetNextCaretCharacterHit(sourceCharacterHit);
}
else
{
// Get previous caret position from the line
nextCharacterHit = line.GetPreviousCaretCharacterHit(sourceCharacterHit);
}
}
// Determine logical direction for next caret index and create TextPointer from it.
LogicalDirection logicalDirection;
if (nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength == _lineMetrics[lineIndex].EndOffset &&
direction == LogicalDirection.Forward)
{
// Going forward brought us to the end of a line, context must be forward for next line.
if (lineIndex == _lineMetrics.Count - 1)
{
// Last line so context must stay backward.
logicalDirection = LogicalDirection.Backward;
}
else
{
logicalDirection = LogicalDirection.Forward;
}
}
else if (nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength == _lineMetrics[lineIndex].Offset &&
direction == LogicalDirection.Backward)
{
// Going backward brought us to the start of a line, context must be backward for previous line.
if (lineIndex == 0)
{
// First line, so we will stay forward.
logicalDirection = LogicalDirection.Forward;
}
else
{
logicalDirection = LogicalDirection.Backward;
}
}
else
{
logicalDirection = (nextCharacterHit.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward;
}
ITextPointer nextCaretUnitPosition = _host.TextContainer.CreatePointerAtOffset(nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength, logicalDirection);
nextCaretUnitPosition.Freeze();
return nextCaretUnitPosition;
}
///
///
///
ITextPointer ITextView.GetBackspaceCaretUnitPosition(ITextPointer position)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
// Special case document start.
if (position.Offset == 0)
{
return position.GetFrozenPointer(LogicalDirection.Forward);
}
int lineIndex = GetLineIndexFromPosition(position, LogicalDirection.Backward);
CharacterHit sourceCharacterHit = new CharacterHit(position.Offset, 0);
CharacterHit backspaceCharacterHit;
using (TextBoxLine line = GetFormattedLine(lineIndex))
{
backspaceCharacterHit = line.GetBackspaceCaretCharacterHit(sourceCharacterHit);
}
LogicalDirection logicalDirection;
if (backspaceCharacterHit.FirstCharacterIndex + backspaceCharacterHit.TrailingLength == _lineMetrics[lineIndex].Offset)
{
// Going backward brought us to the start of a line, context must be backward for previous line
if (lineIndex == 0)
{
// First line, so we will stay forward.
logicalDirection = LogicalDirection.Forward;
}
else
{
logicalDirection = LogicalDirection.Backward;
}
}
else
{
logicalDirection = (backspaceCharacterHit.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward;
}
ITextPointer backspaceUnitPosition = _host.TextContainer.CreatePointerAtOffset(backspaceCharacterHit.FirstCharacterIndex + backspaceCharacterHit.TrailingLength, logicalDirection);
backspaceUnitPosition.Freeze();
return backspaceUnitPosition;
}
///
///
///
TextSegment ITextView.GetLineRange(ITextPointer position)
{
Invariant.Assert(this.IsLayoutValid);
Invariant.Assert(Contains(position));
int lineIndex = GetLineIndexFromPosition(position);
ITextPointer start = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex].Offset, LogicalDirection.Forward);
ITextPointer end = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[lineIndex].Offset + _lineMetrics[lineIndex].ContentLength, LogicalDirection.Forward);
return new TextSegment(start, end, true);
}
///
///
///
ReadOnlyCollection ITextView.GetGlyphRuns(ITextPointer start, ITextPointer end)
{
// This method is not expected to be called.
Invariant.Assert(false);
return null;
}
///
///
///
bool ITextView.Contains(ITextPointer position)
{
return Contains(position);
}
///
///
///
void ITextView.BringPositionIntoViewAsync(ITextPointer position, object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
void ITextView.BringPointIntoViewAsync(Point point, object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
void ITextView.BringLineIntoViewAsync(ITextPointer position, double suggestedX, int count, object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
void ITextView.BringPageIntoViewAsync(ITextPointer position, Point suggestedOffset, int count, object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
void ITextView.CancelAsync(object userState)
{
// This method is not expected to be called.
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
Invariant.Assert(false);
}
///
///
///
bool ITextView.Validate()
{
UpdateLayout();
return this.IsLayoutValid;
}
///
///
///
bool ITextView.Validate(Point point)
{
return ((ITextView)this).Validate();
}
///
///
///
bool ITextView.Validate(ITextPointer position)
{
if (position.TextContainer != _host.TextContainer)
return false;
if (!this.IsLayoutValid)
{
// UpdateLayout has side-effects even when measure and arrange are clean,
// so avoid calling it unless we must.
UpdateLayout();
if (!this.IsLayoutValid)
{
// If we can't get the layout system to give us a valid
// measure/arrange, there's no hope.
return false;
}
}
// Force background layout iterations until we catch up
// with the position.
int lastValidOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset;
while (!Contains(position))
{
InvalidateMeasure();
UpdateLayout();
// UpdateLayout may invalidate the view.
if (!this.IsLayoutValid)
break;
// Break if background layout is not progressing.
int newLastValidOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset;
if (lastValidOffset >= newLastValidOffset)
break;
lastValidOffset = newLastValidOffset;
}
return this.IsLayoutValid && Contains(position);
}
///
///
///
void ITextView.ThrottleBackgroundTasksForUserInput()
{
if (_throttleBackgroundTimer == null)
{
// Start up a timer. Until the timer fires, we'll disable
// all background layout. This leaves the TextBox responsive
// to user input.
_throttleBackgroundTimer = new DispatcherTimer(DispatcherPriority.Background);
_throttleBackgroundTimer.Interval = new TimeSpan(0, 0, _throttleBackgroundSeconds);
_throttleBackgroundTimer.Tick += new EventHandler(OnThrottleBackgroundTimeout);
}
else
{
// Reset the timer.
_throttleBackgroundTimer.Stop();
}
_throttleBackgroundTimer.Start();
}
// Forces a full document invalidation.
// Called when properties that do affect layout (eg, FontSize)
// change value.
internal void Remeasure()
{
if (_lineMetrics != null)
{
_lineMetrics.Clear();
_viewportLineVisuals = null;
}
InvalidateMeasure();
}
// Forces a visual invalidation.
// Called when properties that do not affect layout (eg, ForegroundColor)
// change value.
internal void Rerender()
{
_viewportLineVisuals = null;
InvalidateArrange();
}
#endregion Internal Methods
//-----------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
#region Internal Properties
// Control that owns this TextBoxView.
internal ITextBoxViewHost Host
{
get
{
return _host;
}
}
///
///
///
UIElement ITextView.RenderScope
{
get
{
return this;
}
}
///
///
///
ITextContainer ITextView.TextContainer
{
get
{
return _host.TextContainer;
}
}
///
///
///
bool ITextView.IsValid
{
get
{
return this.IsLayoutValid;
}
}
///
///
///
bool ITextView.RendersOwnSelection
{
get
{
return false;
}
}
///
///
///
ReadOnlyCollection ITextView.TextSegments
{
get
{
List segments = new List(1);
if (_lineMetrics != null)
{
ITextPointer start = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[0].Offset, LogicalDirection.Backward);
ITextPointer end = _host.TextContainer.CreatePointerAtOffset(_lineMetrics[_lineMetrics.Count-1].EndOffset, LogicalDirection.Forward);
segments.Add(new TextSegment(start, end, true));
}
return new ReadOnlyCollection(segments);
}
}
#endregion Internal Properties
//-----------------------------------------------------
//
// Internal Events
//
//------------------------------------------------------
#region Internal Events
///
///
///
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
event BringPositionIntoViewCompletedEventHandler ITextView.BringPositionIntoViewCompleted
{
add { Invariant.Assert(false); }
remove { Invariant.Assert(false); }
}
///
///
///
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
event BringPointIntoViewCompletedEventHandler ITextView.BringPointIntoViewCompleted
{
add { Invariant.Assert(false); }
remove { Invariant.Assert(false); }
}
///
///
///
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
event BringLineIntoViewCompletedEventHandler ITextView.BringLineIntoViewCompleted
{
add { Invariant.Assert(false); }
remove { Invariant.Assert(false); }
}
///
///
///
// Caller should only call this method when !ITextView.Contains(position).
// Since TextBox is not paginated, this view always contains all TextContainer positions.
event BringPageIntoViewCompletedEventHandler ITextView.BringPageIntoViewCompleted
{
add { Invariant.Assert(false); }
remove { Invariant.Assert(false); }
}
///
///
///
event EventHandler ITextView.Updated
{
add { UpdatedEvent += value; }
remove { UpdatedEvent -= value; }
}
#endregion Internal Events
//------------------------------------------------------
//
// Private Methods
//
//-----------------------------------------------------
#region Private Methods
// Initializes TextContainer event listeners.
// Called on the first Measure.
// We delay the init to avoid responding to events before we're attached
// to the visual tree, when it doesn't matter.
private void EnsureTextContainerListeners()
{
if (CheckFlags(Flags.TextContainerListenersInitialized))
return;
_host.TextContainer.Changing += new EventHandler(OnTextContainerChanging);
_host.TextContainer.Change += new TextContainerChangeEventHandler(OnTextContainerChange);
_host.TextContainer.Highlights.Changed += new HighlightChangedEventHandler(OnHighlightChanged);
SetFlags(true, Flags.TextContainerListenersInitialized);
}
// Initializes state used across a measure/arrange calculation.
private void EnsureCache()
{
if (_cache == null)
{
_cache = new TextCache(this);
}
}
// Reads the current (interesting) property values on the owning TextBox.
private LineProperties GetLineProperties()
{
TextProperties defaultTextProperties = new TextProperties((Control)_host, _host.IsTypographyDefaultValue);
// Pass page width and height as double.MaxValue when creating LineProperties, since TextBox does not restrict
// TextIndent or LineHeight.
return new LineProperties((Control)_host, (Control)_host, defaultTextProperties, null, this.CalculatedTextAlignment);
}
// Callback from the TextContainer when a change block starts.
private void OnTextContainerChanging(object sender, EventArgs args)
{
//
}
// Callback from the TextContainer on a document edit.
private void OnTextContainerChange(object sender, TextContainerChangeEventArgs args)
{
if (args.Count == 0)
{
// A no-op for this control. Happens when IMECharCount updates happen
// without corresponding SymbolCount changes.
return;
}
//
// Add the change to our dirty list.
//
if (_dirtyList == null)
{
_dirtyList = new DtrList();
}
DirtyTextRange dirtyTextRange = new DirtyTextRange(args);
_dirtyList.Merge(dirtyTextRange);
//
// Force a re-measure.
//
InvalidateMeasure();
}
// Callback from the TextContainer when a highlight changes.
private void OnHighlightChanged(object sender, HighlightChangedEventArgs args)
{
// The only supported highlight type for TextBoxView is SpellerHighlight.
if (args.OwnerType != typeof(SpellerHighlightLayer))
{
return;
}
//
// Add the change to our dirty list.
//
foreach (TextSegment segment in args.Ranges)
{
// Skip segments that don't intersect with the viewport.
if (this.IsLayoutValid)
{
int firstLineIndex;
int lastLineIndex;
GetVisibleLines(out firstLineIndex, out lastLineIndex);
if (_lineMetrics[firstLineIndex].Offset >= segment.End.Offset)
{
// This segment preceeds the viewport, skip it.
continue;
}
else if (_lineMetrics[lastLineIndex].EndOffset <= segment.Start.Offset)
{
// This segment follows the viewport, no need to look at it or following segments.
break;
}
}
// This segment is visible, prepare to invalidate arrange.
if (_dirtyList == null)
{
_dirtyList = new DtrList();
}
int positionsCovered = segment.End.Offset - segment.Start.Offset;
DirtyTextRange dirtyTextRange = new DirtyTextRange(segment.Start.Offset, positionsCovered, positionsCovered, true /* affectsRenderOnly */);
_dirtyList.Merge(dirtyTextRange);
}
//
// Force a visual update.
//
if (_dirtyList != null)
{
InvalidateArrange();
}
}
// Sets boolean state.
private void SetFlags(bool value, Flags flags)
{
_flags = value ? (_flags | flags) : (_flags & (~flags));
}
// Reads boolean state.
private bool CheckFlags(Flags flags)
{
return ((_flags & flags) == flags);
}
// Announces a layout change to any listeners.
private void FireTextViewUpdatedEvent()
{
if (UpdatedEvent != null)
{
UpdatedEvent(this, EventArgs.Empty);
}
}
// Returns the index of a line containing point, or -1 if no such
// line exists. If snapToText is true, the closest match is returned.
//
// Point must be in document space.
private int GetLineIndexFromPoint(Point point, bool snapToText)
{
Invariant.Assert(_lineMetrics.Count >= 1);
// Special case points above or below the content.
if (point.Y < 0)
{
return snapToText ? 0 : -1;
}
if (point.Y >= _lineHeight * _lineMetrics.Count)
{
return snapToText ? _lineMetrics.Count-1 : -1;
}
// Do a binary search to find the matching line.
int index = -1;
int min = 0;
int max = _lineMetrics.Count;
while (min < max)
{
index = min + (max - min) / 2;
LineRecord record = _lineMetrics[index];
double lineY = _lineHeight * index;
if (point.Y < lineY)
{
max = index;
}
else if (point.Y >= lineY + _lineHeight)
{
min = index + 1;
}
else
{
if (!snapToText &&
(point.X < 0 || point.X >= record.Width))
{
index = -1;
}
break;
}
}
return (min < max) ? index : -1;
}
// Returns the index of the line containing position.
private int GetLineIndexFromPosition(ITextPointer position)
{
return GetLineIndexFromOffset(position.Offset, position.LogicalDirection);
}
// Returns the index of the line containing position.
private int GetLineIndexFromPosition(ITextPointer position, LogicalDirection direction)
{
return GetLineIndexFromOffset(position.Offset, direction);
}
// Returns the index of the line containing the specified offset.
private int GetLineIndexFromOffset(int offset, LogicalDirection direction)
{
if (offset > 0 && direction == LogicalDirection.Backward)
{
// GetLineIndexFromOffset has forward bias, so backup for backward search.
offset--;
}
return GetLineIndexFromOffset(offset);
}
// Returns the index of the line containing the specified offset.
// Offset has forward direction -- we always return the following
// line in ambiguous cases.
private int GetLineIndexFromOffset(int offset)
{
int index = -1;
int min = 0;
int max = _lineMetrics.Count;
Invariant.Assert(_lineMetrics.Count >= 1);
while (true)
{
Invariant.Assert(min < max, "Couldn't find offset!");
index = min + (max - min) / 2;
LineRecord record = _lineMetrics[index];
if (offset < record.Offset)
{
max = index;
}
else if (offset > record.EndOffset)
{
min = index + 1;
}
else
{
if (offset == record.EndOffset && index < _lineMetrics.Count - 1)
{
// Go to the next line if we're between two lines.
index++;
}
break;
}
}
return index;
}
// Returns a formatted TextBoxLine at the specified index.
// Caller must Dispose the TextBoxLine.
// This method is expensive.
private TextBoxLine GetFormattedLine(int lineIndex)
{
LineProperties lineProperties;
return GetFormattedLine(lineIndex, out lineProperties);
}
// Returns a formatted TextBoxLine at the specified index.
// Caller must Dispose the TextBoxLine.
// This method is expensive.
private TextBoxLine GetFormattedLine(int lineIndex, out LineProperties lineProperties)
{
TextBoxLine line = new TextBoxLine(this);
LineRecord metrics = _lineMetrics[lineIndex];
lineProperties = GetLineProperties();
TextFormatter formatter = TextFormatter.FromCurrentDispatcher();
double width = GetWrappingWidth(this.RenderSize.Width);
double formatWidth = GetWrappingWidth(_previousConstraint.Width);
line.Format(metrics.Offset, formatWidth, width, lineProperties, new TextRunCache(), formatter);
Invariant.Assert(metrics.Length == line.Length, "Line is out of [....] with metrics!");
return line;
}
// Returns a TextPointer at the position closest to pixel offset x
// on a specified line.
private ITextPointer GetTextPositionFromDistance(int lineIndex, double x)
{
LineProperties lineProperties;
CharacterHit charIndex;
LogicalDirection logicalDirection;
using (TextBoxLine line = GetFormattedLine(lineIndex, out lineProperties))
{
charIndex = line.GetTextPositionFromDistance(x);
logicalDirection = (charIndex.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward;
}
return _host.TextContainer.CreatePointerAtOffset(charIndex.FirstCharacterIndex + charIndex.TrailingLength, logicalDirection);
}
// Updates IScrollInfo related state on an ArrangeOverride call.
private void ArrangeScrollData(Size arrangeSize)
{
if (_scrollData == null)
{
return;
}
bool invalidateScrollInfo = false;
if (!DoubleUtil.AreClose(_scrollData.Viewport, arrangeSize))
{
_scrollData.Viewport = arrangeSize;
invalidateScrollInfo = true;
}
if (!DoubleUtil.AreClose(_scrollData.Extent, _contentSize))
{
_scrollData.Extent = _contentSize;
invalidateScrollInfo = true;
}
Vector offset = new Vector(
Math.Max(0, Math.Min(_scrollData.ExtentWidth - _scrollData.ViewportWidth, _scrollData.HorizontalOffset)),
Math.Max(0, Math.Min(_scrollData.ExtentHeight - _scrollData.ViewportHeight, _scrollData.VerticalOffset)));
if (!DoubleUtil.AreClose(offset, _scrollData.Offset))
{
_scrollData.Offset = offset;
invalidateScrollInfo = true;
}
if (invalidateScrollInfo && _scrollData.ScrollOwner != null)
{
_scrollData.ScrollOwner.InvalidateScrollInfo();
}
}
// Updates line visuals on an ArrangeOverride call.
private void ArrangeVisuals(Size arrangeSize)
{
// If _dirtyList is non-null here, it means we
// have pending highlight changes to [....] to.
// These changes never affect line metrics, but
// they will clear out any cached Visuals affected.
if (_dirtyList != null)
{
InvalidateDirtyVisuals();
_dirtyList = null;
}
//
// Clear all visual children and initize state.
//
if (_visualChildren == null)
{
_visualChildren = new VisualCollection(this);
}
_visualChildren.Clear();
EnsureCache();
LineProperties lineProperties = _cache.LineProperties;
TextBoxLine line = new TextBoxLine(this);
//
// Calculate the current viewport extent, in lines.
// We won't do any work for lines that aren't visible.
//
int firstLineIndex;
int lastLineIndex;
GetVisibleLines(out firstLineIndex, out lastLineIndex);
SetViewportLines(firstLineIndex, lastLineIndex);
double width = GetWrappingWidth(arrangeSize.Width);
double horizontalOffset = GetTextAlignmentCorrection(lineProperties.TextAlignment, width);
double verticalOffset = this.VerticalAlignmentOffset;
if (_scrollData != null)
{
horizontalOffset -= _scrollData.HorizontalOffset;
verticalOffset -= _scrollData.VerticalOffset;
}
//
// Iterate across the visible lines.
// If we have a cached visual, simply update its current offset.
// Otherwise, allocate and render a new visual.
//
double formatWidth = GetWrappingWidth(_previousConstraint.Width);
for (int lineIndex = firstLineIndex; lineIndex <= lastLineIndex; lineIndex++)
{
DrawingVisual lineVisual = GetLineVisual(lineIndex);
if (lineVisual == null)
{
LineRecord metrics = _lineMetrics[lineIndex];
using (line)
{
line.Format(metrics.Offset, formatWidth, width, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
// We should be in [....] with current metrics, unless background layout is pending.
if (!this.IsBackgroundLayoutPending)
{
Invariant.Assert(metrics.Length == line.Length, "Line is out of [....] with metrics!");
}
lineVisual = line.CreateVisual();
}
SetLineVisual(lineIndex, lineVisual);
}
lineVisual.Offset = new Vector(horizontalOffset, verticalOffset + lineIndex * _lineHeight);
_visualChildren.Add(lineVisual);
}
}
// Called during Arrange, clears any cached line Visuals that intersect with highligh changes
// stored in the dirty range list.
private void InvalidateDirtyVisuals()
{
// Find the affected line, and reset its visual.
// Highlights never affect measure.
for (int i = 0; i < _dirtyList.Length; i++)
{
DirtyTextRange range = _dirtyList[i];
Invariant.Assert(range.AffectsRenderOnly); // We should never get any deltas affecting measure here.
Invariant.Assert(range.PositionsAdded == range.PositionsRemoved); // AffectsRenderOnly never changes document size.
int firstLineIndex = GetLineIndexFromOffset(range.StartIndex, LogicalDirection.Forward);
int endOffset = Math.Min(range.StartIndex + range.PositionsAdded, _host.TextContainer.SymbolCount);
int lastLineIndex = GetLineIndexFromOffset(endOffset, LogicalDirection.Backward);
for (int lineIndex = firstLineIndex; lineIndex <= lastLineIndex; lineIndex++)
{
ClearLineVisual(lineIndex);
}
}
}
// Transforms a Point in visual space (where (0, 0) is the upper-left
// corner of this FrameworkElement) to document space (where (0, 0) is
// the upper-left corner of the document, which may be scrolled to a
// negative offset relative to visual space).
private Point TransformToDocumentSpace(Point point)
{
if (_scrollData != null)
{
point = new Point(point.X + _scrollData.HorizontalOffset, point.Y + _scrollData.VerticalOffset);
}
point.X -= GetTextAlignmentCorrection(this.CalculatedTextAlignment, GetWrappingWidth(this.RenderSize.Width));
point.Y -= this.VerticalAlignmentOffset;
return point;
}
// Transforms a Rect in document space (where (0, 0) is
// the upper-left corner of the document, which may be scrolled to a
// negative offset relative to visual space) to visual space
// (where (0, 0) is the upper-left corner of this FrameworkElement).
private Rect TransformToVisualSpace(Rect rect)
{
if (_scrollData != null)
{
rect.X -= _scrollData.HorizontalOffset;
rect.Y -= _scrollData.VerticalOffset;
}
rect.X += GetTextAlignmentCorrection(this.CalculatedTextAlignment, GetWrappingWidth(this.RenderSize.Width));
rect.Y += this.VerticalAlignmentOffset;
return rect;
}
// Helper for GetTightBoundingGeometryFromTextPositions.
// Calculates the geometry of a single line intersected with a pair of document offsets.
private void GetTightBoundingGeometryFromLineIndex(int lineIndex, int unclippedStartOffset, int unclippedEndOffset, TextAlignment alignment, double endOfParaGlyphWidth, ref Geometry geometry)
{
IList bounds;
int startOffset = Math.Max(_lineMetrics[lineIndex].Offset, unclippedStartOffset);
int endOffset = Math.Min(_lineMetrics[lineIndex].EndOffset, unclippedEndOffset);
if (startOffset == endOffset) // GetRangeBounds does not accept empty runs.
{
// If we have any empty intersection, the only case to handle is when
// the empty range is exactly at the end of a line with a hard break.
// In that case we need to add the newline whitespace geometry.
if (unclippedStartOffset == _lineMetrics[lineIndex].EndOffset)
{
ITextPointer position = _host.TextContainer.CreatePointerAtOffset(unclippedStartOffset, LogicalDirection.Backward);
if (TextPointerBase.IsNextToPlainLineBreak(position, LogicalDirection.Backward))
{
Rect rect = new Rect(0, lineIndex * _lineHeight, endOfParaGlyphWidth, _lineHeight);
CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
}
}
else
{
Invariant.Assert(endOffset == _lineMetrics[lineIndex].Offset);
}
}
else
{
using (TextBoxLine line = GetFormattedLine(lineIndex))
{
bounds = line.GetRangeBounds(startOffset, endOffset - startOffset, 0, lineIndex * _lineHeight);
}
for (int i = 0; i < bounds.Count; i++)
{
Rect rect = TransformToVisualSpace(bounds[i]);
CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
}
// Add the Rect representing end-of-line, if the range covers the line end
// and the line has a hard line break.
if (unclippedEndOffset >= _lineMetrics[lineIndex].EndOffset)
{
ITextPointer endOfLinePosition = _host.TextContainer.CreatePointerAtOffset(endOffset, LogicalDirection.Backward);
if (TextPointerBase.IsNextToPlainLineBreak(endOfLinePosition, LogicalDirection.Backward))
{
double contentOffset = GetContentOffset(_lineMetrics[lineIndex].Width, alignment);
Rect rect = new Rect(contentOffset + _lineMetrics[lineIndex].Width, lineIndex * _lineHeight, endOfParaGlyphWidth, _lineHeight);
rect = TransformToVisualSpace(rect);
CaretElement.AddGeometry(ref geometry, new RectangleGeometry(rect));
}
}
}
}
// Returns the indices of the first and last lines that intersect
// with the current viewport.
private void GetVisibleLines(out int firstLineIndex, out int lastLineIndex)
{
Rect viewport = this.Viewport;
if (!viewport.IsEmpty)
{
firstLineIndex = (int)(viewport.Y / _lineHeight);
lastLineIndex = (int)Math.Ceiling((viewport.Y + viewport.Height) / _lineHeight) - 1;
// There may not be enough lines to fill the viewport, clip appropriately.
firstLineIndex = Math.Max(0, Math.Min(firstLineIndex, _lineMetrics.Count - 1));
lastLineIndex = Math.Max(0, Math.Min(lastLineIndex, _lineMetrics.Count - 1));
}
else
{
// If we're not hosted by a ScrollViewer, the viewport is the whole doc.
firstLineIndex = 0;
lastLineIndex = _lineMetrics.Count - 1;
}
}
// Performs one iteration of background measure.
// Background measure always works at the end of the current
// line metrics array -- invalidations to prevoiusly examined
// content is handled by incremental layout, synchronously.
//
// Returns the full content size, omitting any unanalyzed content
// at the document end.
private Size FullMeasureTick(double constraintWidth, LineProperties lineProperties)
{
Size desiredSize;
TextBoxLine line = new TextBoxLine(this);
int lineOffset;
bool endOfParagraph;
// Find the next position for this iteration.
if (_lineMetrics.Count == 0)
{
desiredSize = new Size();
lineOffset = 0;
}
else
{
desiredSize = _contentSize;
lineOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset;
}
// Calculate a stop time.
// We limit work to just a few milliseconds per iteration
// to avoid blocking the thread.
DateTime stopTime;
if ((ScrollBarVisibility)((Control)_host).GetValue(ScrollViewer.VerticalScrollBarVisibilityProperty) == ScrollBarVisibility.Auto)
{
// Workaround for bug 1766924.
// When VerticalScrollBarVisiblity == Auto, there's a problem with
// our interaction with ScrollViewer. Disable background layout to
// mitigate the problem until we can take a real fix in v.next.
//
stopTime = DateTime.MaxValue;
}
else
{
stopTime = DateTime.Now.AddMilliseconds(_maxMeasureTimeMs);
}
// Format lines until we hit the end of document or run out of time.
do
{
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
// This is a loop invariant, but has negligable cost.
//
_lineHeight = lineProperties.CalcLineAdvance(line.Height);
_lineMetrics.Add(new LineRecord(lineOffset, line));
// Desired width is always max of calculated line widths.
// Desired height is sum of all line heights.
desiredSize.Width = Math.Max(desiredSize.Width, line.Width);
desiredSize.Height += _lineHeight;
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
}
while (!endOfParagraph && DateTime.Now < stopTime);
if (!endOfParagraph)
{
// Ran out of time. Defer to background layout.
SetFlags(true, Flags.BackgroundLayoutPending);
this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(OnBackgroundMeasure), null);
}
else
{
// Finished the entire document. Stop background layout.
SetFlags(false, Flags.BackgroundLayoutPending);
}
return desiredSize;
}
// Callback for the next background layout tick.
private object OnBackgroundMeasure(object o)
{
if (_throttleBackgroundTimer == null)
{
InvalidateMeasure();
}
return null;
}
// Measures content invalidated due to a TextContainer change (rather than
// a constraint change).
//
// Returns the full content size, omitting any unanalyzed content
// at the document end (due to pending background layout).
private Size IncrementalMeasure(double constraintWidth, LineProperties lineProperties)
{
Invariant.Assert(_dirtyList != null);
Invariant.Assert(_dirtyList.Length > 0); // We only allocate _dirtyList when it has content.
Size desiredSize = _contentSize;
DirtyTextRange range = _dirtyList[0];
// Background layout may be running, in which case we need to
// "clip" the scope of this incremental edit. We want to ignore
// changes that extend past the area of the document we're already
// tracking.
if (range.StartIndex > _lineMetrics[_lineMetrics.Count - 1].EndOffset)
{
Invariant.Assert(this.IsBackgroundLayoutPending);
return desiredSize;
}
// Merge the dirty list into a single superset DirtyTextRange.
//
int previousOffset = range.StartIndex;
int positionsAdded = range.PositionsAdded;
int positionsRemoved = range.PositionsRemoved;
for (int i = 1; i < _dirtyList.Length; i++)
{
range = _dirtyList[i];
if (range.StartIndex > _lineMetrics[_lineMetrics.Count - 1].EndOffset)
{
Invariant.Assert(this.IsBackgroundLayoutPending);
break;
}
int rangeDistance = range.StartIndex - previousOffset;
positionsAdded += rangeDistance + range.PositionsAdded;
positionsRemoved += rangeDistance + range.PositionsRemoved;
previousOffset = range.StartIndex;
}
range = new DirtyTextRange(_dirtyList[0].StartIndex, positionsAdded, positionsRemoved);
if (range.PositionsAdded >= range.PositionsRemoved)
{
IncrementalMeasureLinesAfterInsert(constraintWidth, lineProperties, range, ref desiredSize);
}
else if (range.PositionsAdded < range.PositionsRemoved)
{
IncrementalMeasureLinesAfterDelete(constraintWidth, lineProperties, range, ref desiredSize);
}
return desiredSize;
}
// Measures content invalidated due to a TextContainer change.
private void IncrementalMeasureLinesAfterInsert(double constraintWidth, LineProperties lineProperties, DirtyTextRange range, ref Size desiredSize)
{
int delta = range.PositionsAdded - range.PositionsRemoved;
Invariant.Assert(delta >= 0);
int lineIndex = GetLineIndexFromOffset(range.StartIndex, LogicalDirection.Forward);
if (delta > 0)
{
// Increment of the offsets of all following lines.
//
for (int i = lineIndex + 1; i < _lineMetrics.Count; i++)
{
_lineMetrics[i].Offset += delta;
}
}
TextBoxLine line = new TextBoxLine(this);
int lineOffset;
bool endOfParagraph = false;
// We need to re-format the previous line, because if someone inserted
// a hard break, the first directly affected line might now be shorter
// and mergeable with its predecessor.
if (lineIndex > 0) //
{
FormatFirstIncrementalLine(lineIndex - 1, constraintWidth, lineProperties, line, out lineOffset, out endOfParagraph);
}
else
{
lineOffset = _lineMetrics[lineIndex].Offset;
}
// Format the line directly affected by the change.
// If endOfParagraph == true, then the line was absorbed into its
// predessor (because its new content is thinner, or because the
// TextWrapping property changed).
if (!endOfParagraph)
{
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
_lineMetrics[lineIndex] = new LineRecord(lineOffset, line);
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
ClearLineVisual(lineIndex);
lineIndex++;
}
// Recalc the following lines not directly affected as needed.
SyncLineMetrics(range, constraintWidth, lineProperties, line, endOfParagraph, lineIndex, lineOffset);
desiredSize = BruteForceCalculateDesiredSize();
}
// Measures content invalidated due to a TextContainer change.
private void IncrementalMeasureLinesAfterDelete(double constraintWidth, LineProperties lineProperties, DirtyTextRange range, ref Size desiredSize)
{
int delta = range.PositionsAdded - range.PositionsRemoved;
Invariant.Assert(delta < 0);
int firstLineIndex = GetLineIndexFromOffset(range.StartIndex);
// Clip the scope of the affected lines to the region of the document
// we've already inspected. Clipping happens when background layout
// has not yet completed but an incremental update happens.
int endOffset = range.StartIndex + -delta - 1;
if (endOffset > _lineMetrics[_lineMetrics.Count - 1].EndOffset)
{
Invariant.Assert(this.IsBackgroundLayoutPending);
endOffset = _lineMetrics[_lineMetrics.Count - 1].EndOffset;
if (range.StartIndex == endOffset)
{
// Nothing left to do until background layout runs.
return;
}
}
int lastLineIndex = GetLineIndexFromOffset(endOffset);
// Increment the offsets of all following lines.
//
for (int i = lastLineIndex + 1; i < _lineMetrics.Count; i++)
{
_lineMetrics[i].Offset += delta;
}
TextBoxLine line = new TextBoxLine(this);
int lineIndex = firstLineIndex;
int lineOffset;
bool endOfParagraph;
// We need to re-format the previous line, because if someone inserted
// a hard break, the first directly affected line might now be shorter
// and mergeable with its predecessor.
if (lineIndex > 0) //
{
FormatFirstIncrementalLine(lineIndex - 1, constraintWidth, lineProperties, line, out lineOffset, out endOfParagraph);
}
else
{
lineOffset = _lineMetrics[lineIndex].Offset;
endOfParagraph = false;
}
//
// Update the first affected line. If it's completely covered, remove it entirely below.
if (!endOfParagraph &&
(range.StartIndex > lineOffset || range.StartIndex + -delta < _lineMetrics[lineIndex].EndOffset))
{
// Only part of the line is covered, reformat it.
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
_lineMetrics[lineIndex] = new LineRecord(lineOffset, line);
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
ClearLineVisual(lineIndex);
lineIndex++;
}
// Remove all the following lines that are completely covered.
//
_lineMetrics.RemoveRange(lineIndex, lastLineIndex - lineIndex + 1);
RemoveLineVisualRange(lineIndex, lastLineIndex - lineIndex + 1);
// Recalc the following lines not directly affected as needed.
SyncLineMetrics(range, constraintWidth, lineProperties, line, endOfParagraph, lineIndex, lineOffset);
desiredSize = BruteForceCalculateDesiredSize();
}
// Helper for IncrementalMeasureLinesAfterInsert, IncrementalMeasureLinesAfterDelete.
// Formats the line preceding the first directly affected line after a TextContainer change.
// In general this line might grow as content in the following line is absorbed.
private void FormatFirstIncrementalLine(int lineIndex, double constraintWidth, LineProperties lineProperties, TextBoxLine line,
out int lineOffset, out bool endOfParagraph)
{
int originalEndOffset = _lineMetrics[lineIndex].EndOffset;
lineOffset = _lineMetrics[lineIndex].Offset;
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
_lineMetrics[lineIndex] = new LineRecord(lineOffset, line);
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
// Don't clear the cached Visual unless something changed.
if (originalEndOffset != _lineMetrics[lineIndex].EndOffset)
{
ClearLineVisual(lineIndex);
}
}
// Helper for IncrementalMeasureLinesAfterInsert, IncrementalMeasureLinesAfterDelete.
// Formats line until we hit a synchronization point, a position where we know
// following lines could not be affected by the change.
private void SyncLineMetrics(DirtyTextRange range, double constraintWidth, LineProperties lineProperties, TextBoxLine line,
bool endOfParagraph, int lineIndex, int lineOffset)
{
bool offsetSyncOk = (range.PositionsAdded == 0 || range.PositionsRemoved == 0);
int lastCoveredCharOffset = range.StartIndex + Math.Max(range.PositionsAdded, range.PositionsRemoved);
// Keep updating lines until we find a synchronized position.
while (!endOfParagraph &&
(lineIndex == _lineMetrics.Count ||
!offsetSyncOk ||
lineOffset != _lineMetrics[lineIndex].Offset))
{
if (lineIndex < _lineMetrics.Count &&
lineOffset >= _lineMetrics[lineIndex].EndOffset)
{
// If the current line offset starts past the current line metric offset,
// remove the metric. This happens when the previous line
// frees up enough space to completely consume the following line.
// We can't simply replace the record without potentially missing our
// [....] position.
_lineMetrics.RemoveAt(lineIndex); //
RemoveLineVisualRange(lineIndex, 1);
}
else
{
using (line)
{
line.Format(lineOffset, constraintWidth, constraintWidth, lineProperties, _cache.TextRunCache, _cache.TextFormatter);
LineRecord record = new LineRecord(lineOffset, line);
if (lineIndex == _lineMetrics.Count ||
lineOffset + line.Length <= _lineMetrics[lineIndex].Offset)
{
// The new line preceeds the old line, insert a new record.
//
_lineMetrics.Insert(lineIndex, record);
AddLineVisualPlaceholder(lineIndex);
}
else
{
// We expect to be colliding with the old line directly.
// If we extend past it, we're in danger of needlessly
// re-formatting the entire doc (ie, we miss the real
// [....] position and don't stop until EndOfParagraph).
Invariant.Assert(lineOffset < _lineMetrics[lineIndex].EndOffset);
_lineMetrics[lineIndex] = record;
ClearLineVisual(lineIndex);
// If this line ends past the invalidated region, and it
// has a hard line break, it's safe to synchronize on the next
// line metric with a matching start offset.
offsetSyncOk |= lastCoveredCharOffset <= record.EndOffset && line.HasLineBreak;
}
lineIndex++;
lineOffset += line.Length;
endOfParagraph = line.EndOfParagraph;
}
}
}
// Remove any trailing lines that got absorbed into the new last line.
if (endOfParagraph && lineIndex < _lineMetrics.Count)
{
int count = _lineMetrics.Count - lineIndex;
_lineMetrics.RemoveRange(lineIndex, count);
RemoveLineVisualRange(lineIndex, count);
}
}
// Calculates the bounding box of the content.
private Size BruteForceCalculateDesiredSize()
{
Size desiredSize = new Size();
//
for (int i = 0; i < _lineMetrics.Count; i++)
{
desiredSize.Width = Math.Max(desiredSize.Width, _lineMetrics[i].Width);
}
desiredSize.Height = _lineMetrics.Count * _lineHeight;
return desiredSize;
}
// Updates the array of cached Visuals matching lines in the viewport.
// Called on arrange as the viewport changes.
private void SetViewportLines(int firstLineIndex, int lastLineIndex)
{
List oldLineVisuals = _viewportLineVisuals;
int oldLineVisualsIndex = _viewportLineVisualsIndex;
// Assume we'll clear the cache.
_viewportLineVisuals = null;
_viewportLineVisualsIndex = -1;
int count = lastLineIndex - firstLineIndex + 1;
// Don't bother caching Visuals for single-line TextBoxes.
// In this common case memory is important and the single line will
// always be the one invalidated on an edit.
if (count <= 1)
{
return;
}
// Re-init the cache to match the new viewport size.
// Even if we don't have any Visuals to copy over from
// the previous cache, it's useful to pre-allocate space
// in the cache that will be filled incrementally during
// Arrange.
_viewportLineVisuals = new List(count);
_viewportLineVisuals.AddRange(new DrawingVisual[count]); //
_viewportLineVisualsIndex = firstLineIndex;
if (oldLineVisuals == null)
{
return;
}
// Copy over the intersection of the old viewport Visuals cache
// with the new one.
// It would be convenient if the code below assumed that if
// viewport size has changed, we never make it this far (the
// old viewport visuals should have been thrown away, since
// there's no way now to map to the new constraint).
//
// However, because of rounding error, we can end up in the situation
// where the indices/lengths between the two arrays vary, after
// an arrange invalidation.
int oldLastLineIndex = oldLineVisualsIndex + oldLineVisuals.Count - 1;
if (oldLineVisualsIndex <= lastLineIndex &&
oldLastLineIndex >= firstLineIndex)
{
int lineIndex = Math.Max(oldLineVisualsIndex, firstLineIndex);
int lineCount = Math.Min(oldLastLineIndex, firstLineIndex + count - 1) - lineIndex + 1;
for (int i = 0; i < lineCount; i++)
{
_viewportLineVisuals[lineIndex - _viewportLineVisualsIndex + i] = oldLineVisuals[lineIndex - oldLineVisualsIndex + i];
}
}
}
// Retrives the cached line Visual matching a line index in the
// current viewport. Will return null if no value is cached.
private DrawingVisual GetLineVisual(int lineIndex)
{
DrawingVisual lineVisual = null;
if (_viewportLineVisuals != null)
{
lineVisual = _viewportLineVisuals[lineIndex - _viewportLineVisualsIndex];
}
return lineVisual;
}
// Adds a Visual to the line Visuals cache.
private void SetLineVisual(int lineIndex, DrawingVisual lineVisual)
{
if (_viewportLineVisuals != null)
{
_viewportLineVisuals[lineIndex - _viewportLineVisualsIndex] = lineVisual;
}
}
// Adds an empty entry to the line Visuals cache.
private void AddLineVisualPlaceholder(int lineIndex)
{
if (_viewportLineVisuals != null)
{
// Clip to visible region.
if (lineIndex >= _viewportLineVisualsIndex &&
lineIndex < _viewportLineVisualsIndex + _viewportLineVisuals.Count)
{
_viewportLineVisuals.Insert(lineIndex - _viewportLineVisualsIndex, null);
}
}
}
// Invalidates a cached line Visual.
private void ClearLineVisual(int lineIndex)
{
if (_viewportLineVisuals != null)
{
// Clip to visible region.
if (lineIndex >= _viewportLineVisualsIndex &&
lineIndex < _viewportLineVisualsIndex + _viewportLineVisuals.Count)
{
_viewportLineVisuals[lineIndex - _viewportLineVisualsIndex] = null;
}
}
}
// Removes a range of Visuals from the line Visual cache.
private void RemoveLineVisualRange(int lineIndex, int count)
{
if (_viewportLineVisuals != null)
{
// Clip to visible region.
if (lineIndex < _viewportLineVisualsIndex)
{
count -= _viewportLineVisualsIndex - lineIndex;
count = Math.Max(0, count);
lineIndex = _viewportLineVisualsIndex;
}
if (lineIndex < _viewportLineVisualsIndex + _viewportLineVisuals.Count)
{
count = Math.Min(count, _viewportLineVisuals.Count - (lineIndex - _viewportLineVisualsIndex));
_viewportLineVisuals.RemoveRange(lineIndex - _viewportLineVisualsIndex, count);
}
}
}
// Callback for the background layout throttle timer.
// Resumes backgound layout.
private void OnThrottleBackgroundTimeout(object sender, EventArgs e)
{
_throttleBackgroundTimer.Stop();
_throttleBackgroundTimer = null;
if (this.IsBackgroundLayoutPending)
{
OnBackgroundMeasure(null);
}
}
// Returns the x-axis offset of content on a line, based on current
// text alignment.
private double GetContentOffset(double lineWidth, TextAlignment aligment)
{
double contentOffset;
double width = GetWrappingWidth(this.RenderSize.Width);
switch (aligment)
{
case TextAlignment.Right:
contentOffset = width - lineWidth;
break;
case TextAlignment.Center:
contentOffset = (width - lineWidth) / 2;
break;
default:
// Default is Left alignment, in this case offset is 0.
contentOffset = 0.0;
break;
}
return contentOffset;
}
// Converts a HorizontalAlignment enum to a TextAlignment enum.
private TextAlignment HorizontalAlignmentToTextAlignment(HorizontalAlignment horizontalAlignment)
{
TextAlignment textAlignment;
switch (horizontalAlignment)
{
case HorizontalAlignment.Left:
default:
textAlignment = TextAlignment.Left;
break;
case HorizontalAlignment.Right:
textAlignment = TextAlignment.Right;
break;
case HorizontalAlignment.Center:
textAlignment = TextAlignment.Center;
break;
case HorizontalAlignment.Stretch:
textAlignment = TextAlignment.Justify;
break;
}
return textAlignment;
}
///
///
///
private bool Contains(ITextPointer position)
{
Invariant.Assert(this.IsLayoutValid);
return position.TextContainer == _host.TextContainer &&
_lineMetrics != null &&
_lineMetrics[_lineMetrics.Count - 1].EndOffset >= position.Offset;
}
// Converts a render size width into a wrapping width for lines.
private double GetWrappingWidth(double width)
{
if (width < _contentSize.Width)
{
width = _contentSize.Width;
}
if (width > _previousConstraint.Width)
{
width = _previousConstraint.Width;
}
// Make sure that TextFormatter limitations are not exceeded.
//
TextDpi.EnsureValidLineWidth(ref width);
return width;
}
// When the content size exceeds the viewport size, TextLine will align
// its content such that the "extra" is clipped in inappropriate ways.
//
// TextAlignment.Center: line offset = -(contentWidth - viewportWidth) / 2
// TextAlignment.Right: line offset = -(contentWidth - viewportWidth)
//
// This method returns a value that exactly cancels out the undesired
// offset, which is used to adjust the content origin to local zero.
private double GetTextAlignmentCorrection(TextAlignment textAlignment, double width)
{
double correction = 0;
if (textAlignment != TextAlignment.Left &&
_contentSize.Width > width)
{
correction = -GetContentOffset(_contentSize.Width, textAlignment);
}
return correction;
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Properties
//
//-----------------------------------------------------
#region Private Properties
// True when measure and arrange are valid.
private bool IsLayoutValid
{
get
{
return this.IsMeasureValid && this.IsArrangeValid;
}
}
// Current visible region in document space.
private Rect Viewport
{
get
{
return _scrollData == null ? Rect.Empty :
new Rect(_scrollData.HorizontalOffset, _scrollData.VerticalOffset, _scrollData.ViewportWidth, _scrollData.ViewportHeight);
}
}
// True when background layout has not completed.
private bool IsBackgroundLayoutPending
{
get
{
return CheckFlags(Flags.BackgroundLayoutPending);
}
}
// Offset in pixels of the first line due to VerticalContentAlignment.
private double VerticalAlignmentOffset
{
get
{
double offset;
switch (((Control)_host).VerticalContentAlignment)
{
case VerticalAlignment.Top:
case VerticalAlignment.Stretch:
default:
offset = 0;
break;
case VerticalAlignment.Center:
offset = this.VerticalPadding / 2;
break;
case VerticalAlignment.Bottom:
offset = this.VerticalPadding;
break;
}
return offset;
}
}
// Calculated TextAlignment property value.
// Takes into account collisions between TextAlignment and HorizontalContentAlignment properties.
//
// TextAlignment always wins unless it has no local value and HorizontalContentAlignment does.
//
// In order of precedence:
// 1. Local value on TextAlignment.
// 2. Local value on HorizontalContentAlignment.
// 3. TextAlignment.
private TextAlignment CalculatedTextAlignment
{
get
{
TextAlignment alignment;
Control host = (Control)_host;
object o = host.ReadLocalValue(TextBox.TextAlignmentProperty);
if (o != DependencyProperty.UnsetValue)
{
// Re-evaluate o to resolve any databinding expressions.
o = host.GetValue(TextBox.TextAlignmentProperty);
alignment = (TextAlignment)o;
}
else
{
o = host.ReadLocalValue(TextBox.HorizontalContentAlignmentProperty);
if (o != DependencyProperty.UnsetValue)
{
// Re-evaluate o to resolve any databinding expressions.
o = host.GetValue(TextBox.HorizontalContentAlignmentProperty);
alignment = HorizontalAlignmentToTextAlignment((HorizontalAlignment)o);
}
else
{
alignment = (TextAlignment)host.GetValue(TextBox.TextAlignmentProperty);
}
}
return alignment;
}
}
// The delta between the current viewport height and the content height.
// Returns zero when content height is greater than viewport height.
private double VerticalPadding
{
get
{
double padding;
Rect viewport = this.Viewport;
if (viewport.IsEmpty)
{
padding = 0;
}
else
{
padding = Math.Max(0, viewport.Height - _contentSize.Height);
}
return padding;
}
}
#endregion Private Properties
//-----------------------------------------------------
//
// Private Types
//
//-----------------------------------------------------
#region Private Types
// Booleans for the _flags field.
[System.Flags]
private enum Flags
{
// When true, TextContainer listeners are hooked up.
TextContainerListenersInitialized = 0x1,
// When true, background layout is still running.
BackgroundLayoutPending = 0x2,
}
// Caches state used across a measure/arrange calculation.
// In addition to performance benefits, this ensures a consistent
// view of property values across measure/arrange.
private class TextCache
{
internal TextCache(TextBoxView owner)
{
_lineProperties = owner.GetLineProperties();
_textRunCache = new TextRunCache();
}
internal LineProperties LineProperties
{
get { return _lineProperties; }
}
internal TextRunCache TextRunCache
{
get { return _textRunCache; }
}
// Cached TextFormatter for this thread.
internal TextFormatter TextFormatter
{
get
{
if (_textFormatter == null)
{
_textFormatter = TextFormatter.FromCurrentDispatcher();
}
return _textFormatter;
}
}
private readonly LineProperties _lineProperties;
private readonly TextRunCache _textRunCache;
private TextFormatter _textFormatter;
}
// Line metrics array entry.
private class LineRecord
{
internal LineRecord(int offset, TextBoxLine line)
{
_offset = offset;
_length = line.Length;
_contentLength = line.ContentLength;
_width = line.Width;
}
internal int Offset
{
get { return _offset; }
set { _offset = value; }
}
internal int Length { get { return _length; } }
internal int ContentLength { get { return _contentLength; } }
internal double Width { get { return _width; } }
internal int EndOffset { get { return _offset + _length; } }
private int _offset;
private readonly int _length; //
private readonly int _contentLength;
private readonly double _width;
}
#endregion Private Types
//------------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
#region Private Fields
// TextBox that owns this TextBoxView.
private readonly ITextBoxViewHost _host;
// Bounding box of the content, up to the point reached by background layout.
private Size _contentSize;
// The most recent constraint passed to MeasureOverride.
// this.PreviousConstraint cannot be used because it can be affected
// by Margin, Width/Min/MaxWidth propreties and ClipToBounds.
private Size _previousConstraint;
// Caches state used across a measure/arrange calculation.
// In addition to performance benefits, this ensures a consistent
// view of property values across measure/arrange.
private TextCache _cache;
// Height of any line, in pixels.
private double _lineHeight;
// Visuals tracked by GetVisualChild/VisualChilrenCount overrides.
private VisualCollection _visualChildren;
// Array of cached line metrics.
private List _lineMetrics;
// Array of cached line Visuals for the current viewport.
private List _viewportLineVisuals;
// Index of first line in the _viewportLineVisuals array.
private int _viewportLineVisualsIndex;
// IScrollInfo state/code.
private ScrollData _scrollData;
// List of invalidated regions created by TextContainer changes.
private DtrList _dirtyList;
// Timer used to disable background layout during user interaction.
private DispatcherTimer _throttleBackgroundTimer;
// Boolean flags, set with Flags enum.
private Flags _flags;
// Updated event listeners.
private EventHandler UpdatedEvent;
// Max time slice to run FullMeasureTick.
private const uint _maxMeasureTimeMs = 200;
// Number of seconds to disable background layout after receiving
// user input.
private const int _throttleBackgroundSeconds = 2;
#endregion Private Fields
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- MenuItemStyleCollection.cs
- RIPEMD160Managed.cs
- FunctionQuery.cs
- SingleSelectRootGridEntry.cs
- CharacterShapingProperties.cs
- TypeInformation.cs
- TextEndOfSegment.cs
- XsdValidatingReader.cs
- mediaeventshelper.cs
- Repeater.cs
- CachedResourceDictionaryExtension.cs
- BitmapEffectState.cs
- EntityDataSourceChangingEventArgs.cs
- RelOps.cs
- SpecularMaterial.cs
- SecurityKeyIdentifier.cs
- SmiContext.cs
- URLString.cs
- IPGlobalProperties.cs
- ComponentDispatcherThread.cs
- SQLMoneyStorage.cs
- RegexWriter.cs
- ProxyElement.cs
- FacetDescription.cs
- Registry.cs
- BamlResourceDeserializer.cs
- File.cs
- WmpBitmapEncoder.cs
- DodSequenceMerge.cs
- DataGridViewColumnHeaderCell.cs
- PerspectiveCamera.cs
- AppDomainFactory.cs
- SafeNativeMethodsCLR.cs
- JavaScriptSerializer.cs
- SerTrace.cs
- SpellerInterop.cs
- GradientBrush.cs
- DefaultPropertiesToSend.cs
- XmlSchemaComplexContent.cs
- RecordManager.cs
- EntityCommandExecutionException.cs
- PipeStream.cs
- TextServicesCompartment.cs
- ColorAnimationBase.cs
- SBCSCodePageEncoding.cs
- ObjectDataSourceDisposingEventArgs.cs
- Exceptions.cs
- ManagedFilter.cs
- ByteAnimationUsingKeyFrames.cs
- HttpCacheVaryByContentEncodings.cs
- RepeaterDesigner.cs
- SoapElementAttribute.cs
- DataServiceQueryContinuation.cs
- AsyncOperation.cs
- CoreSwitches.cs
- XmlProcessingInstruction.cs
- Descriptor.cs
- ExportOptions.cs
- ButtonBaseAdapter.cs
- DynamicResourceExtensionConverter.cs
- CreateUserWizardStep.cs
- DataGridViewCellContextMenuStripNeededEventArgs.cs
- XmlWriterTraceListener.cs
- FunctionImportMapping.cs
- XmlChildNodes.cs
- EntityDataSourceStatementEditor.cs
- ColumnResizeUndoUnit.cs
- WebPartConnectVerb.cs
- GenericsNotImplementedException.cs
- XmlEntity.cs
- CaseStatementSlot.cs
- AmbientProperties.cs
- mda.cs
- DataAccessException.cs
- PriorityBinding.cs
- DataBoundControlAdapter.cs
- CookieHandler.cs
- ImageListUtils.cs
- QueryHandler.cs
- LineGeometry.cs
- SingleObjectCollection.cs
- BindingSource.cs
- CustomWebEventKey.cs
- ToolboxDataAttribute.cs
- ListParagraph.cs
- columnmapkeybuilder.cs
- Symbol.cs
- COM2TypeInfoProcessor.cs
- ConstraintStruct.cs
- ApplicationSettingsBase.cs
- TableSectionStyle.cs
- ToolStripManager.cs
- XmlCollation.cs
- COM2PropertyDescriptor.cs
- COM2FontConverter.cs
- ButtonStandardAdapter.cs
- DtrList.cs
- ChannelDispatcherCollection.cs
- EventSetterHandlerConverter.cs
- RegexWriter.cs