Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Framework / System / Windows / Documents / TextRange.cs / 1 / TextRange.cs
//---------------------------------------------------------------------------- // // File: TextRange.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: a high-level tool for editing text based FrameworkElements. // //--------------------------------------------------------------------------- using MS.Internal; using System.Collections.Generic; using System.Windows.Threading; using System.Globalization; using System.Xml; using System.IO; using System.Windows.Markup; // Parser #pragma warning disable 1634, 1691 // suppressing PreSharp warnings namespace System.Windows.Documents { ////// TextRange is a high-level tool for editing text based FrameworkElements /// such as Text, TextFlow, or RichTextBox. /// public class TextRange : ITextRange { #region Constructors //----------------------------------------------------- // // Constructors // //----------------------------------------------------- ////// Creates a new TextRange instance. /// /// /// TextPointer specifying the static end of the new TextRange. /// /// /// TextPointer specifying the dynamic end of the new TextRange. /// ////// Throws an ArgumentException if position1 and position2 /// are not positioned within the same document, or if either /// TextPointer is null. /// ////// position1 represents the origin of the TextRange, as opposed /// to position2, which represents the UI active end of a TextRange. /// The distinction is important in applying selection heuristics to /// the exact placement of a new TextRange. /// /// Note that the parameters to this method will not always match /// the Start and End properties of the new TextRange. The exact /// positioning of a new TextRange is subject to hueristics specific /// to document types -- hueristics that match text selection behavior. /// public TextRange(TextPointer position1, TextPointer position2) : this((ITextPointer)position1, (ITextPointer)position2) { } internal TextRange(ITextPointer position1, ITextPointer position2) : this(position1, position2, false /* ignoreTextUnitBoundaries */) { } // ignoreTextUnitBoundaries - true if normalization should ignore text // normalization (surrogates, combining marks, etc). // Used for fine-grained control by IMEs. internal TextRange(ITextPointer position1, ITextPointer position2, bool ignoreTextUnitBoundaries) { if (position1 == null) { throw new ArgumentNullException("position1"); } if (position2 == null) { throw new ArgumentNullException("position2"); } SetFlags(ignoreTextUnitBoundaries, Flags.IgnoreTextUnitBoundaries); ValidationHelper.VerifyPosition(position1.TextContainer, position1, "position1"); ValidationHelper.VerifyPosition(position1.TextContainer, position2, "position2"); TextRangeBase.Select(this, position1, position2); } #endregion Constructors // ****************************************************** // ***************************************************** // ****************************************************** // // Abstract TextRange Implementation // // ****************************************************** // ***************************************************** // ****************************************************** //----------------------------------------------------- // // ITextRange implementation // //----------------------------------------------------- #region ITextRange Implementation //...................................................... // // Selection Building // //...................................................... ////// bool ITextRange.Contains(ITextPointer position) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.Contains(this, position); } ////// /// void ITextRange.Select(ITextPointer position1, ITextPointer position2) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.Select(this, position1, position2); } ////// /// void ITextRange.SelectWord(ITextPointer position) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.SelectWord(this, position); } ////// /// void ITextRange.SelectParagraph(ITextPointer position) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.SelectParagraph(this, position); } ////// /// void ITextRange.ApplyTypingHeuristics(bool overType) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.ApplyTypingHeuristics(this, overType); } object ITextRange.GetPropertyValue(DependencyProperty formattingProperty) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetPropertyValue(this, formattingProperty); } UIElement ITextRange.GetUIElementSelected() { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetUIElementSelected(this); } //...................................................... // // Range Content Serialization // //...................................................... bool ITextRange.CanSave(string dataFormat) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.CanSave(this, dataFormat); } void ITextRange.Save(Stream stream, string dataFormat) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.Save(this, stream, dataFormat, false); } void ITextRange.Save(Stream stream, string dataFormat, bool preserveTextElements) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.Save(this, stream, dataFormat, preserveTextElements); } //...................................................... // // Change Notifications // //...................................................... ////// /// void ITextRange.BeginChange() { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.BeginChange(this); } ////// /// void ITextRange.BeginChangeNoUndo() { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.BeginChangeNoUndo(this); } ////// /// void ITextRange.EndChange() { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.EndChange(this, false /* disableScroll */, false /* skipEvents */); } ////// /// void ITextRange.EndChange(bool disableScroll, bool skipEvents) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.EndChange(this, disableScroll, skipEvents); } ////// /// IDisposable ITextRange.DeclareChangeBlock() { return new ChangeBlock(this, false /* disableScroll */); } ////// /// IDisposable ITextRange.DeclareChangeBlock(bool disableScroll) { return new ChangeBlock(this, disableScroll); } ////// /// void ITextRange.NotifyChanged(bool disableScroll, bool skipEvents) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.NotifyChanged(this, disableScroll); } //----------------------------------------------------- // // ITextRange Properties // //------------------------------------------------------ // Set in the ctor. If true, normalization will ignore text normalization // (surrogates, combining marks, etc). // Used for fine-grained control by IMEs. bool ITextRange.IgnoreTextUnitBoundaries { get { return CheckFlags(Flags.IgnoreTextUnitBoundaries); } } //...................................................... // // Boundary Positions // //...................................................... ////// ITextPointer ITextRange.Start { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetStart(this); } } ////// /// ITextPointer ITextRange.End { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetEnd(this); } } ////// /// bool ITextRange.IsEmpty { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetIsEmpty(this); } } List/// ITextRange.TextSegments { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetTextSegments(this); } } //...................................................... // // Content - rich and plain // //...................................................... /// /// bool ITextRange.HasConcreteTextContainer { get { Invariant.Assert(_textSegments != null, "_textSegments must not be null"); Invariant.Assert(_textSegments.Count > 0, "_textSegments.Count must be > 0"); return _textSegments[0].Start is TextPointer; } } ////// string ITextRange.Text { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetText(this); } set { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.SetText(this, value); } } ////// /// string ITextRange.Xml { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetXml(this); } } ////// /// Ref count of open change blocks -- incremented/decremented /// around BeginChange/EndChange calls. /// int ITextRange.ChangeBlockLevel { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetChangeBlockLevel(this); } } //...................................................... // // Table Selection Properties // //...................................................... bool ITextRange.IsTableCellRange { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetIsTableCellRange(this); } } //----------------------------------------------------- // // ITextRange Events // //------------------------------------------------------ ////// event EventHandler ITextRange.Changed { add { this.Changed += value; } remove { this.Changed -= value; } } void ITextRange.FireChanged() { if (this.Changed != null) { this.Changed(this, EventArgs.Empty); } } //------------------------------------------------------ // // ITextRange Private Fields Accessors // //----------------------------------------------------- // Defines a state of this text range bool ITextRange._IsTableCellRange { get { return CheckFlags(Flags.IsTableCellRange); } set { SetFlags(value, Flags.IsTableCellRange); } } // A collection of TextSegments. Contains at least one segment. List/// ITextRange._TextSegments { get { return _textSegments; } set { _textSegments = value; } } // Count of nested move sequences. int ITextRange._ChangeBlockLevel { get { return _changeBlockLevel; } set { _changeBlockLevel = value; } } /// /// ChangeBlockUndoRecord ITextRange._ChangeBlockUndoRecord { get { return _changeBlockUndoRecord; } set { _changeBlockUndoRecord = value; } } // Set true if a Changed event is pending. bool ITextRange._IsChanged { get { return _IsChanged; } set { _IsChanged = value; } } // ContentGeneration counter storage implementation uint ITextRange._ContentGeneration { get { return _ContentGeneration; } set { _ContentGeneration = value; } } #endregion ITextRange Implementation // ****************************************************** // ***************************************************** // ***************************************************** // // Concrete TextRange Implementation // // ***************************************************** // ****************************************************** // ***************************************************** //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods //...................................................... // // Selection Building // //...................................................... ////// /// Determines if a TextPointer is within this TextRange. /// /// /// The TextPointer to test. /// ////// Throws an ArgumentException if textPointer /// are not positioned within the same document. /// ////// Returns true if textPosition is contained within this TextRange, false /// otherwise. /// ////// Note TextRanges may be disjoint (for instance, when a column in a /// Table is selected), so calling this method is not always equivalent /// to testing for inclusion against the raw Start and End properties. /// /// If textPointer is located at the TextRange Start or End position, /// it is contained by the TextRange. /// // public bool Contains(TextPointer textPointer) { return ((ITextRange)this).Contains(textPointer); } ////// Repositions this TextRange to cover specified content. /// /// /// TextPointer specifying the static end of the new TextRange. /// /// /// TextPointer specifying the dynamic end of the new TextRange. /// ////// Throws an ArgumentException if position1 and position2 /// are not positioned within the same document, or if either /// TextPointer is null. /// ////// position1 represents the origin of the TextRange, as opposed /// to position2, which represents the UI active end of a TextRange. /// The distinction is important in applying selection heuristics to /// the exact placement of the TextRange. /// /// Note that the parameters to this method will not always match /// the Start and End properties of the repositioned TextRange. The /// exact positioning of a TextRange is subject to hueristics /// specific to document types -- hueristics that match text selection /// behavior. /// public void Select(TextPointer position1, TextPointer position2) { ((ITextRange)this).Select(position1, position2); } ////// Selects the word containing a TextPointer. /// /// /// A TextPointer containing a word to select. /// ////// Throws an ArgumentException if textPointer is not positioned within the /// same document. /// internal void SelectWord(TextPointer textPointer) { ((ITextRange)this).SelectWord(textPointer); } ////// Selects a paragraph around the given position. /// /// /// A position identifying a paragraph to select. /// internal void SelectParagraph(ITextPointer position) { ((ITextRange)this).SelectParagraph(position); } //...................................................... // // Plain Text Modification // //...................................................... //...................................................... // // Rich Text Formatting // //...................................................... ////// Applies a formatting property to this TextRange. /// /// /// Property to apply. /// /// /// Specifies a value for the property. /// ////// This method applies the specificed property directly to the /// TextRange's content through the application of Inline elements. /// public void ApplyPropertyValue(DependencyProperty formattingProperty, object value) { this.ApplyPropertyValue(formattingProperty, value, /*applyToParagraphs*/false, PropertyValueAction.SetValue); } ////// Applies a formatting property to this TextRange. /// /// /// Property to apply. /// /// /// Specifies a value for the property. /// /// /// This parameter is used to resolve the ambiguity for overlapping inherited properties /// that apply to both inline and paragraph elements. /// ////// This method applies the specificed property directly to the /// TextRange's content through the application of Inline elements. /// internal void ApplyPropertyValue(DependencyProperty formattingProperty, object value, bool applyToParagraphs) { this.ApplyPropertyValue(formattingProperty, value, applyToParagraphs, PropertyValueAction.SetValue); } ////// Applies a formatting property to this TextRange. /// /// /// Property to apply. /// /// /// Specifies a value for the property. /// /// /// This parameter is used to resolve the ambiguity for overlapping inherited properties /// that apply to both inline and paragraph elements. /// /// /// Specifies how to apply the given value - use it for setting, /// for increasing or for decreasing existing values. /// This parameter must have PropertyValueAction.SetValue for all properties that /// cannot be incremented or decremented by their type. /// internal void ApplyPropertyValue(DependencyProperty formattingProperty, object value, bool applyToParagraphs, PropertyValueAction propertyValueAction) { Invariant.Assert(this.HasConcreteTextContainer, "Can't apply property to non-TextContainer range!"); if (formattingProperty == null) { throw new ArgumentNullException("formattingProperty"); } if (!TextSchema.IsCharacterProperty(formattingProperty) && !TextSchema.IsParagraphProperty(formattingProperty)) { #pragma warning suppress 6506 // formattingProperty is obviously not null throw new ArgumentException(SR.Get(SRID.TextEditorPropertyIsNotApplicableForTextFormatting, formattingProperty.Name)); } // Convert property value from a string to object if needed if ((value is string) && formattingProperty.PropertyType != typeof(string)) { System.ComponentModel.TypeConverter typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(formattingProperty.PropertyType); Invariant.Assert(typeConverter != null); value = typeConverter.ConvertFromString((string)value); } // Check if the value is appropriate for the property if (!formattingProperty.IsValidValue(value) && !(formattingProperty.PropertyType == typeof(Thickness) && (value is Thickness))) { // We exclude checking thcickness values because we have special treatment for negative values // in TextRangeEdit.SetParagraphProperty - negative values mean: "leave the value as is". throw new ArgumentException(SR.Get(SRID.TextEditorTypeOfParameterIsNotAppropriateForFormattingProperty, value == null ? "null" : value.GetType().Name, formattingProperty.Name), "value"); } // Check propertyValueAction validity if (propertyValueAction != PropertyValueAction.SetValue && propertyValueAction != PropertyValueAction.IncreaseByAbsoluteValue && propertyValueAction != PropertyValueAction.DecreaseByAbsoluteValue && propertyValueAction != PropertyValueAction.IncreaseByPercentageValue && propertyValueAction != PropertyValueAction.DecreaseByPercentageValue) { throw new ArgumentException(SR.Get(SRID.TextRange_InvalidParameterValue), "propertyValueAction"); } // Check if propertyValueAction is applicable to this property if (propertyValueAction != PropertyValueAction.SetValue && !TextSchema.IsPropertyIncremental(formattingProperty)) { throw new ArgumentException(SR.Get(SRID.TextRange_PropertyCannotBeIncrementedOrDecremented, formattingProperty.Name), "propertyValueAction"); } ApplyPropertyToTextVirtual(formattingProperty, value, applyToParagraphs, propertyValueAction); } ////// Removes all Inline formatting properties from this range. /// Affects only Inline elements: splits the on range borders /// and deletes all Inlines inside the range. /// Properties set on Paragraphs and other enclosing Block elements /// remain intact. /// public void ClearAllProperties() { Invariant.Assert(this.HasConcreteTextContainer, "Can't clear properties in non-TextContainer range"); ClearAllPropertiesVirtual(); } ////// Gets the value of the given formatting property on this range. /// /// /// Property value to get. /// ////// Value of the requested property. /// public object GetPropertyValue(DependencyProperty formattingProperty) { if (formattingProperty == null) { throw new ArgumentNullException("formattingProperty"); } if (!TextSchema.IsCharacterProperty(formattingProperty) && !TextSchema.IsParagraphProperty(formattingProperty)) { #pragma warning suppress 6506 // formattingProperty is obviously not null throw new ArgumentException(SR.Get(SRID.TextEditorPropertyIsNotApplicableForTextFormatting, formattingProperty.Name)); } // Redirect to virtual implementation - to allow extensiblity on TextSelection level return ((ITextRange)this).GetPropertyValue(formattingProperty); } ////// Returns a UIElement if it is selected by this range as its /// only content. If there is no UIElement in the range or /// if there is any other printable content (charaters, other /// UIElements, structural boundaries crossed), the method returns /// null. /// ///internal UIElement GetUIElementSelected() { return ((ITextRange)this).GetUIElementSelected(); } //...................................................... // // Data Conversion Support // //...................................................... /// /// Detects whether the content of a range can be converted /// to a requested format. /// /// /// A string indicatinng a requested format. /// ////// True if the given format is supported; false otherwise. /// public bool CanSave(string dataFormat) { return ((ITextRange)this).CanSave(dataFormat); } ////// Detects whether the content of a range can be converted /// from a requested format. /// /// /// A string indicatinng a requested format. /// ////// True if the given format is supported; false otherwise. /// public bool CanLoad(string dataFormat) { return TextRangeBase.CanLoad(this, dataFormat); } ////// Writes the contents of the range into a stream /// in a requested format. /// /// /// Writeable Stream - a destination for the serialized content. /// Must be empty on entry. Will contain a data converted from the range /// in a requested format. /// After saving the stream remains opened. /// The stream position after the saving operation /// is undefined. /// /// /// A string denoting one of supported data conversions: /// DataFormats.Text, DataFormats.Xaml, DataFormats.XamlPackage, /// DataFormats.Rtf. /// ////// When dataFormat requested is not supported /// the method will throw an exception. /// To detect whether the given format is supported /// call CanSave method. /// public void Save(Stream stream, string dataFormat) { ((ITextRange)this).Save(stream, dataFormat); } ////// Writes the contents of the range into a stream /// in a requested format. /// /// /// Writeable Stream - a destination for the serialized content. /// Must be empty on entry. Will contain a data converted from the range /// in a requested format. /// After saving the stream remains opened. /// The stream position after the saving operation /// is undefined. /// /// /// A string denoting one of supported data conversions: /// DataFormats.Text, DataFormats.Xaml, DataFormats.XamlPackage, /// DataFormats.Rtf. /// /// /// If TRUE, TextElements are saved as-is. If FALSE, they are upcast /// to their base type. Non-complex custom properties are also saved if this parameter /// is true. This parameter is only used for DataFormats.Xaml and /// DataFormats.XamlPackage and is ignored by other formats. /// ////// When dataFormat requested is not supported /// the method will throw an exception. /// To detect whether the given format is supported /// call CanSave method. /// public void Save(Stream stream, string dataFormat, bool preserveTextElements) { ((ITextRange)this).Save(stream, dataFormat, preserveTextElements); } ////// Reads the contents of the range from the stream /// in a requested format. /// /// /// Readable Stream - a source for the serialized content. /// Expected to contain data in the specified dataFormat. /// The content will be read from the beginning of the stream /// if the stream is Seekable (CanSeek=true), /// otherwize the content will be read from the current /// position of the stream. /// After loading the stream remains opened. /// The stream position after the loading operation /// is undefined. /// /// /// A string denoting one of supported data conversions: /// DataFormats.Text, DataFormats.Xaml, DataFormats.XamlPackage, /// DataFormats.Rtf /// ////// When dataFormat requested is not supported /// the method will throw an exception. /// To detect whether the given format is supported /// call CanLoad method. /// public void Load(Stream stream, string dataFormat) { LoadVirtual(stream, dataFormat); } //...................................................... // // Image support // //...................................................... // Implements smart insertion logic for embedded elements: // when inserted into an empty paragraph uses BlockUIContainer wrapper, // otherwise uses InlineUIContainer wrapper. internal void InsertEmbeddedUIElement(FrameworkElement embeddedElement) { Invariant.Assert(embeddedElement != null); this.InsertEmbeddedUIElementVirtual(embeddedElement); } // Inserts an image maintaining its size within a hard-coded predefined limit, // and keeping its aspect ratio. Uses a smart insertion logic - same // as in InsertEmbeddedUIElement method. // This method is called from Lexicon to insert the image. internal void InsertImage(System.Windows.Controls.Image image) { // System.Windows.Media.Imaging.BitmapSource bitmapSource = (System.Windows.Media.Imaging.BitmapSource)image.Source; Invariant.Assert(bitmapSource != null); const double MaxImageHeight = 300.0; if (double.IsNaN(image.Height)) { // Define image dimenstions maintaining its native aspect ratio, // but not bigger than a predefined maximum. if (bitmapSource.PixelHeight < MaxImageHeight) { image.Height = bitmapSource.PixelHeight; } else { image.Height = MaxImageHeight; } } if (double.IsNaN(image.Width)) { // Define image dimenstions maintaining its native aspect ratio, // but not bigger than a predefined maximum. if (bitmapSource.PixelHeight < MaxImageHeight) { image.Width = bitmapSource.PixelWidth; } else { image.Width = (MaxImageHeight / bitmapSource.PixelHeight) * bitmapSource.PixelWidth; } } this.InsertEmbeddedUIElement(image); } //...................................................... // // Range Serialization // //...................................................... // Worker for Xml property setter; enables extensibility for TextSelection internal virtual void SetXmlVirtual(TextElement fragment) { if (!this.IsTableCellRange) { TextRangeSerialization.PasteXml(this, fragment); } } // Worker for Load public method; enables extensibility for TextSelection internal virtual void LoadVirtual(Stream stream, string dataFormat) { TextRangeBase.Load(this, stream, dataFormat); } //...................................................... // // Table Editing // //...................................................... ////// Inserts a table with a given number of rows and columns. /// /// /// A number of rows generated in a table. /// /// /// A number of columns generated in each row of a table /// ////// Table element innserted. /// internal Table InsertTable(int rowCount, int columnCount) { Invariant.Assert(this.HasConcreteTextContainer, "InsertTable: TextRange must belong to non-abstract TextContainer"); return InsertTableVirtual(rowCount, columnCount); } ////// Inserts several table rows before or after the selection depending on /// sign of rowCount parameter. /// /// /// Absolute value of a parameter specifies number of rows inserted, /// the sign indicates before (negative) or after (positive) the range /// new rows must be inserted. /// ////// TextRange spanning all insereted rows. /// ////// Candidate for public method - when Table editing exposed /// internal TextRange InsertRows(int rowCount) { Invariant.Assert(this.HasConcreteTextContainer, "InsertRows: TextRange must belong to non-abstract TextContainer"); return InsertRowsVirtual(rowCount); } ////// Deletes rows identified by this range. /// ////// True if row range was selected and successfully deleted. /// False if a source range did not contain rows; no actions done in this case. /// ////// Candidate for public method - when Table editing exposed /// internal bool DeleteRows() { Invariant.Assert(this.HasConcreteTextContainer, "DeleteRows: TextRange must belong to non-abstract TextContainer"); return DeleteRowsVirtual(); } ////// Inserts several table columns before or after the selection depending on /// sign of rowCount parameter. /// /// /// Absolute value of a parameter specifies number of columns inserted, /// the sign indicates before (negative) or after (positive) the range /// new columns must be inserted. /// ////// TextRange spanning all insereted columns. /// ////// Candidate for public method - when Table editing exposed /// internal TextRange InsertColumns(int columnCount) { Invariant.Assert(this.HasConcreteTextContainer, "InsertColumns: TextRange must belong to non-abstract TextContainer"); return InsertColumnsVirtual(columnCount); } ////// Deletes columns identified by this range. /// ////// True if cell range was selected and columns were successfully deleted. /// False if a source range did not contain cells; no actions done in this case. /// ////// Candidate for public method - when Table editing exposed /// internal bool DeleteColumns() { Invariant.Assert(this.HasConcreteTextContainer, "DeleteColumns: TextRange must belong to non-abstract TextContainer"); return DeleteColumnsVirtual(); } ////// Merges all cells in a given range into one cell. /// ////// TextRange containing the resulting merged cell. /// ////// Candidate for public method - when Table editing exposed /// internal TextRange MergeCells() { Invariant.Assert(this.HasConcreteTextContainer, "MergeCells: TextRange must belong to non-abstract TextContainer"); return MergeCellsVirtual(); } ////// Splits a merged cell in vertical and in horizontal directions /// /// /// Number of cells created to the right of the current cell. /// Must be less than current cell's ColumnSpan property value. /// /// /// Number of cells created below the current cell. /// Must be less than current cell's RowSpan property value. /// ////// Table range spanning initial cell and all split cells to the left and below it. /// ////// Candidate for public method - when Table editing exposed /// internal TextRange SplitCell(int splitCountHorizontal, int splitCountVertical) { Invariant.Assert(this.HasConcreteTextContainer, "SplitCells: TextRange must belong to non-abstract TextContainer"); return SplitCellVirtual(splitCountHorizontal, splitCountVertical); } #endregion Public Methods //----------------------------------------------------- // // Public Properties // //------------------------------------------------------ #region Public Properties //...................................................... // // Boundary Positions // //...................................................... ////// TextPointer preceding all content. /// ////// The TextPointer returned always has its IsFrozen property set true. /// public TextPointer Start { get { return (TextPointer)((ITextRange)this).Start; } } ////// TextPointer following all content. /// ////// The TextPointer returned always has its IsFrozen property set true. /// public TextPointer End { get { return (TextPointer)((ITextRange)this).End; } } ////// Returns true if this TextRange spans no content. /// public bool IsEmpty { get { return ((ITextRange)this).IsEmpty; } } //...................................................... // // Content - rich and plain // //...................................................... internal bool HasConcreteTextContainer { get { return ((ITextRange)this).HasConcreteTextContainer; } } internal FrameworkElement ContainingFrameworkElement { get { if (this.HasConcreteTextContainer) { return ((TextPointer)this.Start).ContainingFrameworkElement; } else { return null; } } } ////// Get and set the text spanned by this text range. /// New line characters and paragraph breaks are /// considered as equivalent from plain text perspective, /// so all kinds of breaks are converted into new lines /// on get, and converted into paragraph breaks /// on set (if back-end store allows that, or /// remain new line characters otherwise). /// ////// The selected content is collapsed before setting text. /// Collapse assumes mering all block elements crossed by /// this range - from the two neighboring block the preceding /// one survives. /// Character formatting elements are not merged. /// They are eliminated only if they become empty. /// public string Text { get { return ((ITextRange)this).Text; } set { ((ITextRange)this).Text = value; } } ////// Returns the serialized content of this TextRange, in xml format. /// internal string Xml { get { return ((ITextRange)this).Xml; } set { // Note that setter for this property is not in ITextRange // so we use virtual mechanism for extensibility in TextSelection TextRangeBase.BeginChange(this); try { // Parse the fragment into a separate subtree object xamlObject = XamlReader.Load(new XmlTextReader(new System.IO.StringReader(value))); TextElement fragment = xamlObject as TextElement; if (fragment != null) { this.SetXmlVirtual(fragment); } } finally { TextRangeBase.EndChange(this); } } } //...................................................... // // Table Selection Properties // //...................................................... internal bool IsTableCellRange { get { return ((ITextRange)this).IsTableCellRange; } } #endregion Public Properties #region Public Events //----------------------------------------------------- // // Public Events // //----------------------------------------------------- ////// The Changed event is fired when the range is repositioned /// to cover a new span of text. /// /// The EventHandler delegate is called with this TextRange /// as the sender, and EventArgs.Empty as the argument. /// public event EventHandler Changed; #endregion Public Events #region Internal methods //...................................................... // // Change Notifications // //...................................................... ////// Begins a change block. /// ////// This method, along with EndChange or DeclareChangeBlock, provides /// an optional means of controlling the timing of events fired off this /// TextRange and its underlying document. /// /// Events are fired whenever a TextRange is moved, or its content /// modified. Normally, this happens immediately, before the instigating /// method (Select, set_Text, etc.) returns. This can cause trouble if /// all event listeners are not coordinated -- in particular because /// reentrant edits are possible a caller has no guarantees about the /// state of the TextRange or document after making any state changes. /// /// Calling BeginChange declares a "change block", a scope during which /// all state changes are recorded but no events are raised. Only when /// a matching EndChange call is made will events be raised. /// /// The pattern becomes: /// /// range.BeginChange(); /// try // Use a try/finally to ensure the EndChange is always called. /// { /// .. // Reposition the range, or modify the content. /// } /// finally /// { /// range.EndChange(); // events are raised. /// } /// /// Callers must take care to always match every call to BeginChange /// with a matching EndChange, or else the TextRange will stop raising /// events. The DeclareChangeBlock method provides a handy usage pattern /// for C# developers taking advantage of the "using" statement. /// /// Begin/EndChange calls are reference counted and may be nested. Only /// when the outermost EndChange call is made will events be raised. /// internal void BeginChange() { ((ITextRange)this).BeginChange(); } ////// Closes a change block. /// ////// internal void EndChange() { ((ITextRange)this).EndChange(); } ////// /// Each call the BeginChange must be followed by a call to this method, /// which raises public events for any editing operations performed /// within the block. /// /// Begins a change block. /// ////// This method is an alternative to the BeginChange/EndChange usage /// pattern. /// /// Calling this method is equivalent to calling the BeginChange method, /// except additionally an IDisposable is returned. Disposing the /// object is equivalent to calling EndChange. /// /// This method is intended for C# users taking advantage of the "using" /// statement. Instead of writing /// /// range.BeginChange(); /// try // Use a try/finally to ensure the EndChange is always called. /// { /// .. // Reposition the range, or modify the content. /// } /// finally /// { /// range.EndChange(); // events are raised. /// } /// /// a more concise (and exactly equivalent) /// /// using (new range.DeclareChangeBlock()) /// { /// .. // Reposition the range, or modify the content. /// } // Events are raised the Dispose takes place. /// /// is possible. /// internal IDisposable DeclareChangeBlock() { return ((ITextRange)this).DeclareChangeBlock(); } ////// Begins a change block. /// ////// When disableScroll == true, the caret will not automatically scroll into view. /// internal IDisposable DeclareChangeBlock(bool disableScroll) { return ((ITextRange)this).DeclareChangeBlock(disableScroll); } // Set true if a Changed event is pending. // This method only intended for use by derived classes // (but may not be declared "protected" without public // exposure). internal bool _IsChanged { get { return CheckFlags(Flags.IsChanged); } set { SetFlags(value, Flags.IsChanged); } } #endregion Internal methods //----------------------------------------------------- // // Internal Virtual Methods - TextSelection Extensibility // //------------------------------------------------------ #region Internal Virtual Methods //...................................................... // // Formatting // //...................................................... // Worker for AppendEmbeddedElement; enabled extensibility for TextSelection internal virtual void InsertEmbeddedUIElementVirtual(FrameworkElement embeddedElement) { Invariant.Assert(this.HasConcreteTextContainer, "Can't insert embedded object to non-TextContainer range!"); Invariant.Assert(embeddedElement != null); TextRangeBase.BeginChange(this); try { // Delete existing selected content this.Text = String.Empty; // Calling EnsureInsertionPosition has the effect of inserting a paragraph // before insert the embedded UIElement at BlockUIContainer or InlineUIConatiner. TextPointer startPosition = TextRangeEditTables.EnsureInsertionPosition(this.Start); // Choose what wrapper to use - BlockUIContainer or InlineUIContainer - // depending on the current paragraph emptiness Paragraph paragraph = startPosition.Paragraph; if (paragraph != null) { if (Paragraph.HasNoTextContent(paragraph)) { // Use BlockUIContainer as a replacement of the current paragraph BlockUIContainer blockUIContainer = new BlockUIContainer(embeddedElement); // Translate embedded element's horizontal alignment property to the BlockUIContainer's text alignment blockUIContainer.TextAlignment = TextRangeEdit.GetTextAlignmentFromHorizontalAlignment(embeddedElement.HorizontalAlignment); // Replace paragraph with BlockUIContainer paragraph.SiblingBlocks.InsertAfter(paragraph, blockUIContainer); paragraph.SiblingBlocks.Remove(paragraph); this.Select(blockUIContainer.ContentStart, blockUIContainer.ContentEnd); } else { // Use InlineUIContainer InlineUIContainer inlineUIContainer = new InlineUIContainer(embeddedElement); TextPointer insertionPosition = TextRangeEdit.SplitFormattingElements(this.Start, /*keepEmptyFormatting:*/false); insertionPosition.InsertTextElement(inlineUIContainer); this.Select(inlineUIContainer.ElementStart, inlineUIContainer.ElementEnd); } } } finally { TextRangeBase.EndChange(this); } } // Worker for ApplyProperty; enables extensibility for TextSelection internal virtual void ApplyPropertyToTextVirtual(DependencyProperty formattingProperty, object value, bool applyToParagraphs, PropertyValueAction propertyValueAction) { TextRangeBase.BeginChange(this); try { for (int i = 0; i < _textSegments.Count; i++) { TextSegment textSegment = _textSegments[i]; if (formattingProperty == FrameworkElement.FlowDirectionProperty) { // FlowDirection is an overlapping inheritable property that needs special handling. // We apply it as a paragraph property when: // 1. applyToParagraphs = true or // 2. range is empty or // 3. range crossed paragraph boundary // Otherwise, apply as inline property. if (applyToParagraphs || this.IsEmpty || TextRangeBase.IsParagraphBoundaryCrossed(this)) { TextRangeEdit.SetParagraphProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction); } else { TextRangeEdit.SetInlineProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction); } } else if (TextSchema.IsCharacterProperty(formattingProperty)) { TextRangeEdit.SetInlineProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction); } else if (TextSchema.IsParagraphProperty(formattingProperty)) { // We must check for paragraph properties after character ones, // to account for overlapping inheritable properties. // Thinkness properties (Margin, Padding, BorderThickness) have special treatment // in SetParagraphProperty method: it swaps Left and Right values for paragraphs // with RightToLeft flow direction. So we need to set them appropriatly - // depending on the FlowDirection of the first paragraph. if (formattingProperty.PropertyType == typeof(Thickness) && (FlowDirection)textSegment.Start.GetValue(Paragraph.FlowDirectionProperty) == FlowDirection.RightToLeft) { value = new Thickness( ((Thickness)value).Right, ((Thickness)value).Top, ((Thickness)value).Left, ((Thickness)value).Bottom); } TextRangeEdit.SetParagraphProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction); } } } finally { TextRangeBase.EndChange(this); } } // Worker for ClearAllProperties method; enables extensibility for TextSelection internal virtual void ClearAllPropertiesVirtual() { TextRangeBase.BeginChange(this); try { // Clear all inline formattings TextRangeEdit.CharacterResetFormatting((TextPointer)this.Start, (TextPointer)this.End); } finally { TextRangeBase.EndChange(this); } } //----------------------------------------------------- // // Table Editing // //------------------------------------------------------ // Worker for InsertTable; enables extensibility for TextSelection internal virtual Table InsertTableVirtual(int rowCount, int columnCount) { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.InsertTable((TextPointer)this.End, rowCount, columnCount); } finally { TextRangeBase.EndChange(this); } } // Worker for InsertRows; enables extensibility for TextSelection internal virtual TextRange InsertRowsVirtual(int rowCount) { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.InsertRows(this, rowCount); } finally { TextRangeBase.EndChange(this); } } // Worker for DeleteRows; enables extensibility for TextSelection internal virtual bool DeleteRowsVirtual() { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.DeleteRows(this); } finally { TextRangeBase.EndChange(this); } } // Worker for InsertColumns; enables extensibility for TextSelection internal virtual TextRange InsertColumnsVirtual(int columnCount) { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.InsertColumns(this, columnCount); } finally { TextRangeBase.EndChange(this); } } // Worker for DeleteColumns; enables extensibility for TextSelection internal virtual bool DeleteColumnsVirtual() { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.DeleteColumns(this); } finally { TextRangeBase.EndChange(this); } } // Worker for MergeCells; enables extensibility for TextSelection internal virtual TextRange MergeCellsVirtual() { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.MergeCells(this); } finally { TextRangeBase.EndChange(this); } } // Worker for SplitCells; enables extensibility for TextSelection internal virtual TextRange SplitCellVirtual(int splitCountHorizontal, int splitCountVertical) { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.SplitCell(this, splitCountHorizontal, splitCountVertical); } finally { TextRangeBase.EndChange(this); } } #endregion Internal Methods //------------------------------------------------------ // // Internal Properties // //----------------------------------------------------- #region Internal Properties internal int ChangeBlockLevel { get { return _changeBlockLevel; } } #endregion Internal Properties //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods // 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); } #endregion Private Methods //----------------------------------------------------- // // Private Types // //----------------------------------------------------- #region Private Types private class ChangeBlock : IDisposable { internal ChangeBlock(ITextRange range, bool disableScroll) { _range = range; _disableScroll = disableScroll; _range.BeginChange(); } void IDisposable.Dispose() { _range.EndChange(_disableScroll, false /* skipEvents */); } private readonly ITextRange _range; private readonly bool _disableScroll; } // Booleans for the _flags field. [System.Flags] private enum Flags { // True if normalization should ignore text normalization (surrogates, combining marks, etc). // Used for fine-grained control by IMEs. IgnoreTextUnitBoundaries = 0x1, // True if a Changed event is pending. IsChanged = 0x2, // True if this range covers a TableCell. IsTableCellRange = 0x4, } #endregion Private Types #region Private Fields //------------------------------------------------------ // // Private Fields // //----------------------------------------------------- // A collection of TextSegments. Contains at least one segment. private List_textSegments; // Count of nested move sequences. private int _changeBlockLevel; // Undo unit associated with the current change block, if any. private ChangeBlockUndoRecord _changeBlockUndoRecord; // Generation id associated with this range. Remembers the state of TextContainer // at the moment of the last range building/normalization private uint _ContentGeneration; // Boolean flags, set with Flags enum. private Flags _flags; #endregion Private Fields } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // File: TextRange.cs // // Copyright (C) Microsoft Corporation. All rights reserved. // // Description: a high-level tool for editing text based FrameworkElements. // //--------------------------------------------------------------------------- using MS.Internal; using System.Collections.Generic; using System.Windows.Threading; using System.Globalization; using System.Xml; using System.IO; using System.Windows.Markup; // Parser #pragma warning disable 1634, 1691 // suppressing PreSharp warnings namespace System.Windows.Documents { /// /// TextRange is a high-level tool for editing text based FrameworkElements /// such as Text, TextFlow, or RichTextBox. /// public class TextRange : ITextRange { #region Constructors //----------------------------------------------------- // // Constructors // //----------------------------------------------------- ////// Creates a new TextRange instance. /// /// /// TextPointer specifying the static end of the new TextRange. /// /// /// TextPointer specifying the dynamic end of the new TextRange. /// ////// Throws an ArgumentException if position1 and position2 /// are not positioned within the same document, or if either /// TextPointer is null. /// ////// position1 represents the origin of the TextRange, as opposed /// to position2, which represents the UI active end of a TextRange. /// The distinction is important in applying selection heuristics to /// the exact placement of a new TextRange. /// /// Note that the parameters to this method will not always match /// the Start and End properties of the new TextRange. The exact /// positioning of a new TextRange is subject to hueristics specific /// to document types -- hueristics that match text selection behavior. /// public TextRange(TextPointer position1, TextPointer position2) : this((ITextPointer)position1, (ITextPointer)position2) { } internal TextRange(ITextPointer position1, ITextPointer position2) : this(position1, position2, false /* ignoreTextUnitBoundaries */) { } // ignoreTextUnitBoundaries - true if normalization should ignore text // normalization (surrogates, combining marks, etc). // Used for fine-grained control by IMEs. internal TextRange(ITextPointer position1, ITextPointer position2, bool ignoreTextUnitBoundaries) { if (position1 == null) { throw new ArgumentNullException("position1"); } if (position2 == null) { throw new ArgumentNullException("position2"); } SetFlags(ignoreTextUnitBoundaries, Flags.IgnoreTextUnitBoundaries); ValidationHelper.VerifyPosition(position1.TextContainer, position1, "position1"); ValidationHelper.VerifyPosition(position1.TextContainer, position2, "position2"); TextRangeBase.Select(this, position1, position2); } #endregion Constructors // ****************************************************** // ***************************************************** // ****************************************************** // // Abstract TextRange Implementation // // ****************************************************** // ***************************************************** // ****************************************************** //----------------------------------------------------- // // ITextRange implementation // //----------------------------------------------------- #region ITextRange Implementation //...................................................... // // Selection Building // //...................................................... ////// bool ITextRange.Contains(ITextPointer position) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.Contains(this, position); } ////// /// void ITextRange.Select(ITextPointer position1, ITextPointer position2) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.Select(this, position1, position2); } ////// /// void ITextRange.SelectWord(ITextPointer position) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.SelectWord(this, position); } ////// /// void ITextRange.SelectParagraph(ITextPointer position) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.SelectParagraph(this, position); } ////// /// void ITextRange.ApplyTypingHeuristics(bool overType) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.ApplyTypingHeuristics(this, overType); } object ITextRange.GetPropertyValue(DependencyProperty formattingProperty) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetPropertyValue(this, formattingProperty); } UIElement ITextRange.GetUIElementSelected() { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetUIElementSelected(this); } //...................................................... // // Range Content Serialization // //...................................................... bool ITextRange.CanSave(string dataFormat) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.CanSave(this, dataFormat); } void ITextRange.Save(Stream stream, string dataFormat) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.Save(this, stream, dataFormat, false); } void ITextRange.Save(Stream stream, string dataFormat, bool preserveTextElements) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.Save(this, stream, dataFormat, preserveTextElements); } //...................................................... // // Change Notifications // //...................................................... ////// /// void ITextRange.BeginChange() { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.BeginChange(this); } ////// /// void ITextRange.BeginChangeNoUndo() { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.BeginChangeNoUndo(this); } ////// /// void ITextRange.EndChange() { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.EndChange(this, false /* disableScroll */, false /* skipEvents */); } ////// /// void ITextRange.EndChange(bool disableScroll, bool skipEvents) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.EndChange(this, disableScroll, skipEvents); } ////// /// IDisposable ITextRange.DeclareChangeBlock() { return new ChangeBlock(this, false /* disableScroll */); } ////// /// IDisposable ITextRange.DeclareChangeBlock(bool disableScroll) { return new ChangeBlock(this, disableScroll); } ////// /// void ITextRange.NotifyChanged(bool disableScroll, bool skipEvents) { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.NotifyChanged(this, disableScroll); } //----------------------------------------------------- // // ITextRange Properties // //------------------------------------------------------ // Set in the ctor. If true, normalization will ignore text normalization // (surrogates, combining marks, etc). // Used for fine-grained control by IMEs. bool ITextRange.IgnoreTextUnitBoundaries { get { return CheckFlags(Flags.IgnoreTextUnitBoundaries); } } //...................................................... // // Boundary Positions // //...................................................... ////// ITextPointer ITextRange.Start { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetStart(this); } } ////// /// ITextPointer ITextRange.End { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetEnd(this); } } ////// /// bool ITextRange.IsEmpty { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetIsEmpty(this); } } List/// ITextRange.TextSegments { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetTextSegments(this); } } //...................................................... // // Content - rich and plain // //...................................................... /// /// bool ITextRange.HasConcreteTextContainer { get { Invariant.Assert(_textSegments != null, "_textSegments must not be null"); Invariant.Assert(_textSegments.Count > 0, "_textSegments.Count must be > 0"); return _textSegments[0].Start is TextPointer; } } ////// string ITextRange.Text { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetText(this); } set { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! TextRangeBase.SetText(this, value); } } ////// /// string ITextRange.Xml { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetXml(this); } } ////// /// Ref count of open change blocks -- incremented/decremented /// around BeginChange/EndChange calls. /// int ITextRange.ChangeBlockLevel { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetChangeBlockLevel(this); } } //...................................................... // // Table Selection Properties // //...................................................... bool ITextRange.IsTableCellRange { get { // ATTENTION: This implementation *must* be pure redirect to TextRangeBase. Otherwise TextSelection extensibility is broken // DO NOT ANY CODE IN THIS METHOD! return TextRangeBase.GetIsTableCellRange(this); } } //----------------------------------------------------- // // ITextRange Events // //------------------------------------------------------ ////// event EventHandler ITextRange.Changed { add { this.Changed += value; } remove { this.Changed -= value; } } void ITextRange.FireChanged() { if (this.Changed != null) { this.Changed(this, EventArgs.Empty); } } //------------------------------------------------------ // // ITextRange Private Fields Accessors // //----------------------------------------------------- // Defines a state of this text range bool ITextRange._IsTableCellRange { get { return CheckFlags(Flags.IsTableCellRange); } set { SetFlags(value, Flags.IsTableCellRange); } } // A collection of TextSegments. Contains at least one segment. List/// ITextRange._TextSegments { get { return _textSegments; } set { _textSegments = value; } } // Count of nested move sequences. int ITextRange._ChangeBlockLevel { get { return _changeBlockLevel; } set { _changeBlockLevel = value; } } /// /// ChangeBlockUndoRecord ITextRange._ChangeBlockUndoRecord { get { return _changeBlockUndoRecord; } set { _changeBlockUndoRecord = value; } } // Set true if a Changed event is pending. bool ITextRange._IsChanged { get { return _IsChanged; } set { _IsChanged = value; } } // ContentGeneration counter storage implementation uint ITextRange._ContentGeneration { get { return _ContentGeneration; } set { _ContentGeneration = value; } } #endregion ITextRange Implementation // ****************************************************** // ***************************************************** // ***************************************************** // // Concrete TextRange Implementation // // ***************************************************** // ****************************************************** // ***************************************************** //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods //...................................................... // // Selection Building // //...................................................... ////// /// Determines if a TextPointer is within this TextRange. /// /// /// The TextPointer to test. /// ////// Throws an ArgumentException if textPointer /// are not positioned within the same document. /// ////// Returns true if textPosition is contained within this TextRange, false /// otherwise. /// ////// Note TextRanges may be disjoint (for instance, when a column in a /// Table is selected), so calling this method is not always equivalent /// to testing for inclusion against the raw Start and End properties. /// /// If textPointer is located at the TextRange Start or End position, /// it is contained by the TextRange. /// // public bool Contains(TextPointer textPointer) { return ((ITextRange)this).Contains(textPointer); } ////// Repositions this TextRange to cover specified content. /// /// /// TextPointer specifying the static end of the new TextRange. /// /// /// TextPointer specifying the dynamic end of the new TextRange. /// ////// Throws an ArgumentException if position1 and position2 /// are not positioned within the same document, or if either /// TextPointer is null. /// ////// position1 represents the origin of the TextRange, as opposed /// to position2, which represents the UI active end of a TextRange. /// The distinction is important in applying selection heuristics to /// the exact placement of the TextRange. /// /// Note that the parameters to this method will not always match /// the Start and End properties of the repositioned TextRange. The /// exact positioning of a TextRange is subject to hueristics /// specific to document types -- hueristics that match text selection /// behavior. /// public void Select(TextPointer position1, TextPointer position2) { ((ITextRange)this).Select(position1, position2); } ////// Selects the word containing a TextPointer. /// /// /// A TextPointer containing a word to select. /// ////// Throws an ArgumentException if textPointer is not positioned within the /// same document. /// internal void SelectWord(TextPointer textPointer) { ((ITextRange)this).SelectWord(textPointer); } ////// Selects a paragraph around the given position. /// /// /// A position identifying a paragraph to select. /// internal void SelectParagraph(ITextPointer position) { ((ITextRange)this).SelectParagraph(position); } //...................................................... // // Plain Text Modification // //...................................................... //...................................................... // // Rich Text Formatting // //...................................................... ////// Applies a formatting property to this TextRange. /// /// /// Property to apply. /// /// /// Specifies a value for the property. /// ////// This method applies the specificed property directly to the /// TextRange's content through the application of Inline elements. /// public void ApplyPropertyValue(DependencyProperty formattingProperty, object value) { this.ApplyPropertyValue(formattingProperty, value, /*applyToParagraphs*/false, PropertyValueAction.SetValue); } ////// Applies a formatting property to this TextRange. /// /// /// Property to apply. /// /// /// Specifies a value for the property. /// /// /// This parameter is used to resolve the ambiguity for overlapping inherited properties /// that apply to both inline and paragraph elements. /// ////// This method applies the specificed property directly to the /// TextRange's content through the application of Inline elements. /// internal void ApplyPropertyValue(DependencyProperty formattingProperty, object value, bool applyToParagraphs) { this.ApplyPropertyValue(formattingProperty, value, applyToParagraphs, PropertyValueAction.SetValue); } ////// Applies a formatting property to this TextRange. /// /// /// Property to apply. /// /// /// Specifies a value for the property. /// /// /// This parameter is used to resolve the ambiguity for overlapping inherited properties /// that apply to both inline and paragraph elements. /// /// /// Specifies how to apply the given value - use it for setting, /// for increasing or for decreasing existing values. /// This parameter must have PropertyValueAction.SetValue for all properties that /// cannot be incremented or decremented by their type. /// internal void ApplyPropertyValue(DependencyProperty formattingProperty, object value, bool applyToParagraphs, PropertyValueAction propertyValueAction) { Invariant.Assert(this.HasConcreteTextContainer, "Can't apply property to non-TextContainer range!"); if (formattingProperty == null) { throw new ArgumentNullException("formattingProperty"); } if (!TextSchema.IsCharacterProperty(formattingProperty) && !TextSchema.IsParagraphProperty(formattingProperty)) { #pragma warning suppress 6506 // formattingProperty is obviously not null throw new ArgumentException(SR.Get(SRID.TextEditorPropertyIsNotApplicableForTextFormatting, formattingProperty.Name)); } // Convert property value from a string to object if needed if ((value is string) && formattingProperty.PropertyType != typeof(string)) { System.ComponentModel.TypeConverter typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(formattingProperty.PropertyType); Invariant.Assert(typeConverter != null); value = typeConverter.ConvertFromString((string)value); } // Check if the value is appropriate for the property if (!formattingProperty.IsValidValue(value) && !(formattingProperty.PropertyType == typeof(Thickness) && (value is Thickness))) { // We exclude checking thcickness values because we have special treatment for negative values // in TextRangeEdit.SetParagraphProperty - negative values mean: "leave the value as is". throw new ArgumentException(SR.Get(SRID.TextEditorTypeOfParameterIsNotAppropriateForFormattingProperty, value == null ? "null" : value.GetType().Name, formattingProperty.Name), "value"); } // Check propertyValueAction validity if (propertyValueAction != PropertyValueAction.SetValue && propertyValueAction != PropertyValueAction.IncreaseByAbsoluteValue && propertyValueAction != PropertyValueAction.DecreaseByAbsoluteValue && propertyValueAction != PropertyValueAction.IncreaseByPercentageValue && propertyValueAction != PropertyValueAction.DecreaseByPercentageValue) { throw new ArgumentException(SR.Get(SRID.TextRange_InvalidParameterValue), "propertyValueAction"); } // Check if propertyValueAction is applicable to this property if (propertyValueAction != PropertyValueAction.SetValue && !TextSchema.IsPropertyIncremental(formattingProperty)) { throw new ArgumentException(SR.Get(SRID.TextRange_PropertyCannotBeIncrementedOrDecremented, formattingProperty.Name), "propertyValueAction"); } ApplyPropertyToTextVirtual(formattingProperty, value, applyToParagraphs, propertyValueAction); } ////// Removes all Inline formatting properties from this range. /// Affects only Inline elements: splits the on range borders /// and deletes all Inlines inside the range. /// Properties set on Paragraphs and other enclosing Block elements /// remain intact. /// public void ClearAllProperties() { Invariant.Assert(this.HasConcreteTextContainer, "Can't clear properties in non-TextContainer range"); ClearAllPropertiesVirtual(); } ////// Gets the value of the given formatting property on this range. /// /// /// Property value to get. /// ////// Value of the requested property. /// public object GetPropertyValue(DependencyProperty formattingProperty) { if (formattingProperty == null) { throw new ArgumentNullException("formattingProperty"); } if (!TextSchema.IsCharacterProperty(formattingProperty) && !TextSchema.IsParagraphProperty(formattingProperty)) { #pragma warning suppress 6506 // formattingProperty is obviously not null throw new ArgumentException(SR.Get(SRID.TextEditorPropertyIsNotApplicableForTextFormatting, formattingProperty.Name)); } // Redirect to virtual implementation - to allow extensiblity on TextSelection level return ((ITextRange)this).GetPropertyValue(formattingProperty); } ////// Returns a UIElement if it is selected by this range as its /// only content. If there is no UIElement in the range or /// if there is any other printable content (charaters, other /// UIElements, structural boundaries crossed), the method returns /// null. /// ///internal UIElement GetUIElementSelected() { return ((ITextRange)this).GetUIElementSelected(); } //...................................................... // // Data Conversion Support // //...................................................... /// /// Detects whether the content of a range can be converted /// to a requested format. /// /// /// A string indicatinng a requested format. /// ////// True if the given format is supported; false otherwise. /// public bool CanSave(string dataFormat) { return ((ITextRange)this).CanSave(dataFormat); } ////// Detects whether the content of a range can be converted /// from a requested format. /// /// /// A string indicatinng a requested format. /// ////// True if the given format is supported; false otherwise. /// public bool CanLoad(string dataFormat) { return TextRangeBase.CanLoad(this, dataFormat); } ////// Writes the contents of the range into a stream /// in a requested format. /// /// /// Writeable Stream - a destination for the serialized content. /// Must be empty on entry. Will contain a data converted from the range /// in a requested format. /// After saving the stream remains opened. /// The stream position after the saving operation /// is undefined. /// /// /// A string denoting one of supported data conversions: /// DataFormats.Text, DataFormats.Xaml, DataFormats.XamlPackage, /// DataFormats.Rtf. /// ////// When dataFormat requested is not supported /// the method will throw an exception. /// To detect whether the given format is supported /// call CanSave method. /// public void Save(Stream stream, string dataFormat) { ((ITextRange)this).Save(stream, dataFormat); } ////// Writes the contents of the range into a stream /// in a requested format. /// /// /// Writeable Stream - a destination for the serialized content. /// Must be empty on entry. Will contain a data converted from the range /// in a requested format. /// After saving the stream remains opened. /// The stream position after the saving operation /// is undefined. /// /// /// A string denoting one of supported data conversions: /// DataFormats.Text, DataFormats.Xaml, DataFormats.XamlPackage, /// DataFormats.Rtf. /// /// /// If TRUE, TextElements are saved as-is. If FALSE, they are upcast /// to their base type. Non-complex custom properties are also saved if this parameter /// is true. This parameter is only used for DataFormats.Xaml and /// DataFormats.XamlPackage and is ignored by other formats. /// ////// When dataFormat requested is not supported /// the method will throw an exception. /// To detect whether the given format is supported /// call CanSave method. /// public void Save(Stream stream, string dataFormat, bool preserveTextElements) { ((ITextRange)this).Save(stream, dataFormat, preserveTextElements); } ////// Reads the contents of the range from the stream /// in a requested format. /// /// /// Readable Stream - a source for the serialized content. /// Expected to contain data in the specified dataFormat. /// The content will be read from the beginning of the stream /// if the stream is Seekable (CanSeek=true), /// otherwize the content will be read from the current /// position of the stream. /// After loading the stream remains opened. /// The stream position after the loading operation /// is undefined. /// /// /// A string denoting one of supported data conversions: /// DataFormats.Text, DataFormats.Xaml, DataFormats.XamlPackage, /// DataFormats.Rtf /// ////// When dataFormat requested is not supported /// the method will throw an exception. /// To detect whether the given format is supported /// call CanLoad method. /// public void Load(Stream stream, string dataFormat) { LoadVirtual(stream, dataFormat); } //...................................................... // // Image support // //...................................................... // Implements smart insertion logic for embedded elements: // when inserted into an empty paragraph uses BlockUIContainer wrapper, // otherwise uses InlineUIContainer wrapper. internal void InsertEmbeddedUIElement(FrameworkElement embeddedElement) { Invariant.Assert(embeddedElement != null); this.InsertEmbeddedUIElementVirtual(embeddedElement); } // Inserts an image maintaining its size within a hard-coded predefined limit, // and keeping its aspect ratio. Uses a smart insertion logic - same // as in InsertEmbeddedUIElement method. // This method is called from Lexicon to insert the image. internal void InsertImage(System.Windows.Controls.Image image) { // System.Windows.Media.Imaging.BitmapSource bitmapSource = (System.Windows.Media.Imaging.BitmapSource)image.Source; Invariant.Assert(bitmapSource != null); const double MaxImageHeight = 300.0; if (double.IsNaN(image.Height)) { // Define image dimenstions maintaining its native aspect ratio, // but not bigger than a predefined maximum. if (bitmapSource.PixelHeight < MaxImageHeight) { image.Height = bitmapSource.PixelHeight; } else { image.Height = MaxImageHeight; } } if (double.IsNaN(image.Width)) { // Define image dimenstions maintaining its native aspect ratio, // but not bigger than a predefined maximum. if (bitmapSource.PixelHeight < MaxImageHeight) { image.Width = bitmapSource.PixelWidth; } else { image.Width = (MaxImageHeight / bitmapSource.PixelHeight) * bitmapSource.PixelWidth; } } this.InsertEmbeddedUIElement(image); } //...................................................... // // Range Serialization // //...................................................... // Worker for Xml property setter; enables extensibility for TextSelection internal virtual void SetXmlVirtual(TextElement fragment) { if (!this.IsTableCellRange) { TextRangeSerialization.PasteXml(this, fragment); } } // Worker for Load public method; enables extensibility for TextSelection internal virtual void LoadVirtual(Stream stream, string dataFormat) { TextRangeBase.Load(this, stream, dataFormat); } //...................................................... // // Table Editing // //...................................................... ////// Inserts a table with a given number of rows and columns. /// /// /// A number of rows generated in a table. /// /// /// A number of columns generated in each row of a table /// ////// Table element innserted. /// internal Table InsertTable(int rowCount, int columnCount) { Invariant.Assert(this.HasConcreteTextContainer, "InsertTable: TextRange must belong to non-abstract TextContainer"); return InsertTableVirtual(rowCount, columnCount); } ////// Inserts several table rows before or after the selection depending on /// sign of rowCount parameter. /// /// /// Absolute value of a parameter specifies number of rows inserted, /// the sign indicates before (negative) or after (positive) the range /// new rows must be inserted. /// ////// TextRange spanning all insereted rows. /// ////// Candidate for public method - when Table editing exposed /// internal TextRange InsertRows(int rowCount) { Invariant.Assert(this.HasConcreteTextContainer, "InsertRows: TextRange must belong to non-abstract TextContainer"); return InsertRowsVirtual(rowCount); } ////// Deletes rows identified by this range. /// ////// True if row range was selected and successfully deleted. /// False if a source range did not contain rows; no actions done in this case. /// ////// Candidate for public method - when Table editing exposed /// internal bool DeleteRows() { Invariant.Assert(this.HasConcreteTextContainer, "DeleteRows: TextRange must belong to non-abstract TextContainer"); return DeleteRowsVirtual(); } ////// Inserts several table columns before or after the selection depending on /// sign of rowCount parameter. /// /// /// Absolute value of a parameter specifies number of columns inserted, /// the sign indicates before (negative) or after (positive) the range /// new columns must be inserted. /// ////// TextRange spanning all insereted columns. /// ////// Candidate for public method - when Table editing exposed /// internal TextRange InsertColumns(int columnCount) { Invariant.Assert(this.HasConcreteTextContainer, "InsertColumns: TextRange must belong to non-abstract TextContainer"); return InsertColumnsVirtual(columnCount); } ////// Deletes columns identified by this range. /// ////// True if cell range was selected and columns were successfully deleted. /// False if a source range did not contain cells; no actions done in this case. /// ////// Candidate for public method - when Table editing exposed /// internal bool DeleteColumns() { Invariant.Assert(this.HasConcreteTextContainer, "DeleteColumns: TextRange must belong to non-abstract TextContainer"); return DeleteColumnsVirtual(); } ////// Merges all cells in a given range into one cell. /// ////// TextRange containing the resulting merged cell. /// ////// Candidate for public method - when Table editing exposed /// internal TextRange MergeCells() { Invariant.Assert(this.HasConcreteTextContainer, "MergeCells: TextRange must belong to non-abstract TextContainer"); return MergeCellsVirtual(); } ////// Splits a merged cell in vertical and in horizontal directions /// /// /// Number of cells created to the right of the current cell. /// Must be less than current cell's ColumnSpan property value. /// /// /// Number of cells created below the current cell. /// Must be less than current cell's RowSpan property value. /// ////// Table range spanning initial cell and all split cells to the left and below it. /// ////// Candidate for public method - when Table editing exposed /// internal TextRange SplitCell(int splitCountHorizontal, int splitCountVertical) { Invariant.Assert(this.HasConcreteTextContainer, "SplitCells: TextRange must belong to non-abstract TextContainer"); return SplitCellVirtual(splitCountHorizontal, splitCountVertical); } #endregion Public Methods //----------------------------------------------------- // // Public Properties // //------------------------------------------------------ #region Public Properties //...................................................... // // Boundary Positions // //...................................................... ////// TextPointer preceding all content. /// ////// The TextPointer returned always has its IsFrozen property set true. /// public TextPointer Start { get { return (TextPointer)((ITextRange)this).Start; } } ////// TextPointer following all content. /// ////// The TextPointer returned always has its IsFrozen property set true. /// public TextPointer End { get { return (TextPointer)((ITextRange)this).End; } } ////// Returns true if this TextRange spans no content. /// public bool IsEmpty { get { return ((ITextRange)this).IsEmpty; } } //...................................................... // // Content - rich and plain // //...................................................... internal bool HasConcreteTextContainer { get { return ((ITextRange)this).HasConcreteTextContainer; } } internal FrameworkElement ContainingFrameworkElement { get { if (this.HasConcreteTextContainer) { return ((TextPointer)this.Start).ContainingFrameworkElement; } else { return null; } } } ////// Get and set the text spanned by this text range. /// New line characters and paragraph breaks are /// considered as equivalent from plain text perspective, /// so all kinds of breaks are converted into new lines /// on get, and converted into paragraph breaks /// on set (if back-end store allows that, or /// remain new line characters otherwise). /// ////// The selected content is collapsed before setting text. /// Collapse assumes mering all block elements crossed by /// this range - from the two neighboring block the preceding /// one survives. /// Character formatting elements are not merged. /// They are eliminated only if they become empty. /// public string Text { get { return ((ITextRange)this).Text; } set { ((ITextRange)this).Text = value; } } ////// Returns the serialized content of this TextRange, in xml format. /// internal string Xml { get { return ((ITextRange)this).Xml; } set { // Note that setter for this property is not in ITextRange // so we use virtual mechanism for extensibility in TextSelection TextRangeBase.BeginChange(this); try { // Parse the fragment into a separate subtree object xamlObject = XamlReader.Load(new XmlTextReader(new System.IO.StringReader(value))); TextElement fragment = xamlObject as TextElement; if (fragment != null) { this.SetXmlVirtual(fragment); } } finally { TextRangeBase.EndChange(this); } } } //...................................................... // // Table Selection Properties // //...................................................... internal bool IsTableCellRange { get { return ((ITextRange)this).IsTableCellRange; } } #endregion Public Properties #region Public Events //----------------------------------------------------- // // Public Events // //----------------------------------------------------- ////// The Changed event is fired when the range is repositioned /// to cover a new span of text. /// /// The EventHandler delegate is called with this TextRange /// as the sender, and EventArgs.Empty as the argument. /// public event EventHandler Changed; #endregion Public Events #region Internal methods //...................................................... // // Change Notifications // //...................................................... ////// Begins a change block. /// ////// This method, along with EndChange or DeclareChangeBlock, provides /// an optional means of controlling the timing of events fired off this /// TextRange and its underlying document. /// /// Events are fired whenever a TextRange is moved, or its content /// modified. Normally, this happens immediately, before the instigating /// method (Select, set_Text, etc.) returns. This can cause trouble if /// all event listeners are not coordinated -- in particular because /// reentrant edits are possible a caller has no guarantees about the /// state of the TextRange or document after making any state changes. /// /// Calling BeginChange declares a "change block", a scope during which /// all state changes are recorded but no events are raised. Only when /// a matching EndChange call is made will events be raised. /// /// The pattern becomes: /// /// range.BeginChange(); /// try // Use a try/finally to ensure the EndChange is always called. /// { /// .. // Reposition the range, or modify the content. /// } /// finally /// { /// range.EndChange(); // events are raised. /// } /// /// Callers must take care to always match every call to BeginChange /// with a matching EndChange, or else the TextRange will stop raising /// events. The DeclareChangeBlock method provides a handy usage pattern /// for C# developers taking advantage of the "using" statement. /// /// Begin/EndChange calls are reference counted and may be nested. Only /// when the outermost EndChange call is made will events be raised. /// internal void BeginChange() { ((ITextRange)this).BeginChange(); } ////// Closes a change block. /// ////// internal void EndChange() { ((ITextRange)this).EndChange(); } ////// /// Each call the BeginChange must be followed by a call to this method, /// which raises public events for any editing operations performed /// within the block. /// /// Begins a change block. /// ////// This method is an alternative to the BeginChange/EndChange usage /// pattern. /// /// Calling this method is equivalent to calling the BeginChange method, /// except additionally an IDisposable is returned. Disposing the /// object is equivalent to calling EndChange. /// /// This method is intended for C# users taking advantage of the "using" /// statement. Instead of writing /// /// range.BeginChange(); /// try // Use a try/finally to ensure the EndChange is always called. /// { /// .. // Reposition the range, or modify the content. /// } /// finally /// { /// range.EndChange(); // events are raised. /// } /// /// a more concise (and exactly equivalent) /// /// using (new range.DeclareChangeBlock()) /// { /// .. // Reposition the range, or modify the content. /// } // Events are raised the Dispose takes place. /// /// is possible. /// internal IDisposable DeclareChangeBlock() { return ((ITextRange)this).DeclareChangeBlock(); } ////// Begins a change block. /// ////// When disableScroll == true, the caret will not automatically scroll into view. /// internal IDisposable DeclareChangeBlock(bool disableScroll) { return ((ITextRange)this).DeclareChangeBlock(disableScroll); } // Set true if a Changed event is pending. // This method only intended for use by derived classes // (but may not be declared "protected" without public // exposure). internal bool _IsChanged { get { return CheckFlags(Flags.IsChanged); } set { SetFlags(value, Flags.IsChanged); } } #endregion Internal methods //----------------------------------------------------- // // Internal Virtual Methods - TextSelection Extensibility // //------------------------------------------------------ #region Internal Virtual Methods //...................................................... // // Formatting // //...................................................... // Worker for AppendEmbeddedElement; enabled extensibility for TextSelection internal virtual void InsertEmbeddedUIElementVirtual(FrameworkElement embeddedElement) { Invariant.Assert(this.HasConcreteTextContainer, "Can't insert embedded object to non-TextContainer range!"); Invariant.Assert(embeddedElement != null); TextRangeBase.BeginChange(this); try { // Delete existing selected content this.Text = String.Empty; // Calling EnsureInsertionPosition has the effect of inserting a paragraph // before insert the embedded UIElement at BlockUIContainer or InlineUIConatiner. TextPointer startPosition = TextRangeEditTables.EnsureInsertionPosition(this.Start); // Choose what wrapper to use - BlockUIContainer or InlineUIContainer - // depending on the current paragraph emptiness Paragraph paragraph = startPosition.Paragraph; if (paragraph != null) { if (Paragraph.HasNoTextContent(paragraph)) { // Use BlockUIContainer as a replacement of the current paragraph BlockUIContainer blockUIContainer = new BlockUIContainer(embeddedElement); // Translate embedded element's horizontal alignment property to the BlockUIContainer's text alignment blockUIContainer.TextAlignment = TextRangeEdit.GetTextAlignmentFromHorizontalAlignment(embeddedElement.HorizontalAlignment); // Replace paragraph with BlockUIContainer paragraph.SiblingBlocks.InsertAfter(paragraph, blockUIContainer); paragraph.SiblingBlocks.Remove(paragraph); this.Select(blockUIContainer.ContentStart, blockUIContainer.ContentEnd); } else { // Use InlineUIContainer InlineUIContainer inlineUIContainer = new InlineUIContainer(embeddedElement); TextPointer insertionPosition = TextRangeEdit.SplitFormattingElements(this.Start, /*keepEmptyFormatting:*/false); insertionPosition.InsertTextElement(inlineUIContainer); this.Select(inlineUIContainer.ElementStart, inlineUIContainer.ElementEnd); } } } finally { TextRangeBase.EndChange(this); } } // Worker for ApplyProperty; enables extensibility for TextSelection internal virtual void ApplyPropertyToTextVirtual(DependencyProperty formattingProperty, object value, bool applyToParagraphs, PropertyValueAction propertyValueAction) { TextRangeBase.BeginChange(this); try { for (int i = 0; i < _textSegments.Count; i++) { TextSegment textSegment = _textSegments[i]; if (formattingProperty == FrameworkElement.FlowDirectionProperty) { // FlowDirection is an overlapping inheritable property that needs special handling. // We apply it as a paragraph property when: // 1. applyToParagraphs = true or // 2. range is empty or // 3. range crossed paragraph boundary // Otherwise, apply as inline property. if (applyToParagraphs || this.IsEmpty || TextRangeBase.IsParagraphBoundaryCrossed(this)) { TextRangeEdit.SetParagraphProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction); } else { TextRangeEdit.SetInlineProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction); } } else if (TextSchema.IsCharacterProperty(formattingProperty)) { TextRangeEdit.SetInlineProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction); } else if (TextSchema.IsParagraphProperty(formattingProperty)) { // We must check for paragraph properties after character ones, // to account for overlapping inheritable properties. // Thinkness properties (Margin, Padding, BorderThickness) have special treatment // in SetParagraphProperty method: it swaps Left and Right values for paragraphs // with RightToLeft flow direction. So we need to set them appropriatly - // depending on the FlowDirection of the first paragraph. if (formattingProperty.PropertyType == typeof(Thickness) && (FlowDirection)textSegment.Start.GetValue(Paragraph.FlowDirectionProperty) == FlowDirection.RightToLeft) { value = new Thickness( ((Thickness)value).Right, ((Thickness)value).Top, ((Thickness)value).Left, ((Thickness)value).Bottom); } TextRangeEdit.SetParagraphProperty((TextPointer)textSegment.Start, (TextPointer)textSegment.End, formattingProperty, value, propertyValueAction); } } } finally { TextRangeBase.EndChange(this); } } // Worker for ClearAllProperties method; enables extensibility for TextSelection internal virtual void ClearAllPropertiesVirtual() { TextRangeBase.BeginChange(this); try { // Clear all inline formattings TextRangeEdit.CharacterResetFormatting((TextPointer)this.Start, (TextPointer)this.End); } finally { TextRangeBase.EndChange(this); } } //----------------------------------------------------- // // Table Editing // //------------------------------------------------------ // Worker for InsertTable; enables extensibility for TextSelection internal virtual Table InsertTableVirtual(int rowCount, int columnCount) { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.InsertTable((TextPointer)this.End, rowCount, columnCount); } finally { TextRangeBase.EndChange(this); } } // Worker for InsertRows; enables extensibility for TextSelection internal virtual TextRange InsertRowsVirtual(int rowCount) { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.InsertRows(this, rowCount); } finally { TextRangeBase.EndChange(this); } } // Worker for DeleteRows; enables extensibility for TextSelection internal virtual bool DeleteRowsVirtual() { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.DeleteRows(this); } finally { TextRangeBase.EndChange(this); } } // Worker for InsertColumns; enables extensibility for TextSelection internal virtual TextRange InsertColumnsVirtual(int columnCount) { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.InsertColumns(this, columnCount); } finally { TextRangeBase.EndChange(this); } } // Worker for DeleteColumns; enables extensibility for TextSelection internal virtual bool DeleteColumnsVirtual() { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.DeleteColumns(this); } finally { TextRangeBase.EndChange(this); } } // Worker for MergeCells; enables extensibility for TextSelection internal virtual TextRange MergeCellsVirtual() { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.MergeCells(this); } finally { TextRangeBase.EndChange(this); } } // Worker for SplitCells; enables extensibility for TextSelection internal virtual TextRange SplitCellVirtual(int splitCountHorizontal, int splitCountVertical) { TextRangeBase.BeginChange(this); try { return TextRangeEditTables.SplitCell(this, splitCountHorizontal, splitCountVertical); } finally { TextRangeBase.EndChange(this); } } #endregion Internal Methods //------------------------------------------------------ // // Internal Properties // //----------------------------------------------------- #region Internal Properties internal int ChangeBlockLevel { get { return _changeBlockLevel; } } #endregion Internal Properties //------------------------------------------------------ // // Private Methods // //----------------------------------------------------- #region Private Methods // 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); } #endregion Private Methods //----------------------------------------------------- // // Private Types // //----------------------------------------------------- #region Private Types private class ChangeBlock : IDisposable { internal ChangeBlock(ITextRange range, bool disableScroll) { _range = range; _disableScroll = disableScroll; _range.BeginChange(); } void IDisposable.Dispose() { _range.EndChange(_disableScroll, false /* skipEvents */); } private readonly ITextRange _range; private readonly bool _disableScroll; } // Booleans for the _flags field. [System.Flags] private enum Flags { // True if normalization should ignore text normalization (surrogates, combining marks, etc). // Used for fine-grained control by IMEs. IgnoreTextUnitBoundaries = 0x1, // True if a Changed event is pending. IsChanged = 0x2, // True if this range covers a TableCell. IsTableCellRange = 0x4, } #endregion Private Types #region Private Fields //------------------------------------------------------ // // Private Fields // //----------------------------------------------------- // A collection of TextSegments. Contains at least one segment. private List_textSegments; // Count of nested move sequences. private int _changeBlockLevel; // Undo unit associated with the current change block, if any. private ChangeBlockUndoRecord _changeBlockUndoRecord; // Generation id associated with this range. Remembers the state of TextContainer // at the moment of the last range building/normalization private uint _ContentGeneration; // Boolean flags, set with Flags enum. private Flags _flags; #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
- EdgeModeValidation.cs
- Utils.cs
- CompiledQueryCacheKey.cs
- CroppedBitmap.cs
- MemoryMappedView.cs
- XmlSchemaAttributeGroupRef.cs
- SecurityAlgorithmSuiteConverter.cs
- EndpointAddressAugust2004.cs
- BrowserDefinition.cs
- BlurBitmapEffect.cs
- XPathNode.cs
- QueryInterceptorAttribute.cs
- FileAuthorizationModule.cs
- PcmConverter.cs
- CheckBoxList.cs
- InternalTypeHelper.cs
- EmbeddedMailObjectsCollection.cs
- DataServiceException.cs
- AsyncPostBackTrigger.cs
- DataSet.cs
- InvalidateEvent.cs
- SamlSecurityToken.cs
- DispatcherOperation.cs
- WebFormsRootDesigner.cs
- BitmapEffectState.cs
- GridViewRowEventArgs.cs
- EntityDataSourceDataSelection.cs
- SelectedPathEditor.cs
- DbSetClause.cs
- DataGridViewRowDividerDoubleClickEventArgs.cs
- ArrayEditor.cs
- TemplateParser.cs
- __FastResourceComparer.cs
- SmtpNtlmAuthenticationModule.cs
- Int32Rect.cs
- CollectionChange.cs
- HashSetDebugView.cs
- TransactionManager.cs
- Window.cs
- InlineUIContainer.cs
- HttpDebugHandler.cs
- MultiView.cs
- EventHandlersStore.cs
- CharacterMetricsDictionary.cs
- CodeAttachEventStatement.cs
- sortedlist.cs
- PropertyManager.cs
- MonthChangedEventArgs.cs
- AnnotationMap.cs
- XPathAncestorIterator.cs
- AsymmetricSignatureDeformatter.cs
- TextDecorationLocationValidation.cs
- Listbox.cs
- VBIdentifierDesigner.xaml.cs
- DynamicMetaObjectBinder.cs
- Exceptions.cs
- NumberSubstitution.cs
- SemanticAnalyzer.cs
- CryptographicAttribute.cs
- DataSourceSelectArguments.cs
- VirtualDirectoryMappingCollection.cs
- MouseEvent.cs
- SrgsItemList.cs
- SystemThemeKey.cs
- URLIdentityPermission.cs
- HandlerBase.cs
- XmlBinaryWriter.cs
- CqlWriter.cs
- XmlIncludeAttribute.cs
- OledbConnectionStringbuilder.cs
- PassportAuthenticationEventArgs.cs
- SafeNativeMethods.cs
- XmlEncoding.cs
- PeerNameRegistration.cs
- RoleManagerSection.cs
- WindowsRebar.cs
- ClientApiGenerator.cs
- ParallelTimeline.cs
- OleDbEnumerator.cs
- ServiceContractDetailViewControl.cs
- SignatureTargetIdManager.cs
- TemplateModeChangedEventArgs.cs
- StorageEntityTypeMapping.cs
- StreamAsIStream.cs
- IsolatedStoragePermission.cs
- Utils.cs
- SqlInfoMessageEvent.cs
- ServiceEndpoint.cs
- MsmqProcessProtocolHandler.cs
- SerializationStore.cs
- BaseInfoTable.cs
- NotificationContext.cs
- RtfControls.cs
- TreeNodeStyle.cs
- KernelTypeValidation.cs
- Compilation.cs
- DataControlLinkButton.cs
- OwnerDrawPropertyBag.cs
- SqlTransaction.cs
- CompatibleIComparer.cs