/// <include file='doc\RichTextBox.uex' path='docs/doc[@for="RichTextBox.Find6"]/*' /> /// <devdoc> /// Searches the text in a RichTextBox control for the given characters. /// </devdoc> public int Find(char[] characterSet, int start, int end) { // Code used to support ability to search backwards and negate character sets. // The API no longer supports this, but in case we change our mind, I'm leaving // the ability in here. bool forward = true; bool negate = false; int textLength = TextLength; if (characterSet == null) throw new ArgumentNullException("characterSet"); if (start < 0 || start > textLength) throw new ArgumentOutOfRangeException("start", SR.GetString(SR.InvalidBoundArgument, "start", start, 0, textLength)); if (end < start && end != -1) throw new ArgumentOutOfRangeException("end", SR.GetString(SR.InvalidLowBoundArgumentEx, "end", end, "start")); // Don't do anything if we get nothing to look for if (characterSet.Length == 0) return -1; int textLen = SafeNativeMethods.GetWindowTextLength(new HandleRef(this, Handle)); if (start == end) { start = 0; end = textLen; } if (end == -1) { end = textLen; } NativeMethods.CHARRANGE chrg = new NativeMethods.CHARRANGE(); // The range of characters we have searched chrg.cpMax = chrg.cpMin = start; // Use the TEXTRANGE to move our text buffer forward // or backwards within the main text NativeMethods.TEXTRANGE txrg = new NativeMethods.TEXTRANGE(); // Characters we have slurped into memory in order to search txrg.chrg = new NativeMethods.CHARRANGE(); txrg.chrg.cpMin = chrg.cpMin; txrg.chrg.cpMax = chrg.cpMax; UnsafeNativeMethods.CharBuffer charBuffer; charBuffer = UnsafeNativeMethods.CharBuffer.CreateBuffer(CHAR_BUFFER_LEN + 1); txrg.lpstrText = charBuffer.AllocCoTaskMem(); if (txrg.lpstrText == IntPtr.Zero) throw new OutOfMemoryException(); try { bool done = false; // We want to loop as long as it takes. This loop will grab a // chunk of text out from the control as directed by txrg.chrg; while (!done) { if (forward) { // Move forward by starting at the end of the // previous text window and extending by the // size of our buffer txrg.chrg.cpMin = chrg.cpMax; txrg.chrg.cpMax += CHAR_BUFFER_LEN; } else { // Move backwards by anchoring at the start // of the previous buffer window, and backing // up by the desired size of our buffer txrg.chrg.cpMax = chrg.cpMin; txrg.chrg.cpMin -= CHAR_BUFFER_LEN; // We need to keep our request within the // lower bound of zero if (txrg.chrg.cpMin < 0) txrg.chrg.cpMin = 0; } if (end != -1) txrg.chrg.cpMax = Math.Min(txrg.chrg.cpMax, end); // go get the text in this range, if we didn't get any text then punt int len; len = (int)UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_GETTEXTRANGE, 0, txrg); if (len == 0) { chrg.cpMax = chrg.cpMin = -1; // Hit end of control without finding what we wanted break; } // get the data from RichTextBoxConstants into a string for us to use. charBuffer.PutCoTaskMem(txrg.lpstrText); string str = charBuffer.GetString(); // Loop through our text if (forward) { // Start at the begining of the buffer for (int x = 0; x < len; x++) { // Is it in char set? bool found = GetCharInCharSet(str[x], characterSet, negate); if (found) { done = true; break; } // Advance the buffer chrg.cpMax++; } } else { // Span reverse. int x = len; while (x-- != 0) { // Is it in char set? bool found = GetCharInCharSet(str[x], characterSet, negate); if (found) { done = true; break; } // Bring the selection back while keeping it anchored chrg.cpMin--; } } } } finally { // release the resources we got for our GETTEXTRANGE operation. if (txrg.lpstrText != IntPtr.Zero) Marshal.FreeCoTaskMem(txrg.lpstrText); } int index = (forward) ? chrg.cpMax : chrg.cpMin; return index; }
private void StreamIn(Stream data, int flags) { // clear out the selection only if we are replacing all the text // if ((flags & RichTextBoxConstants.SFF_SELECTION) == 0) { NativeMethods.CHARRANGE cr = new NativeMethods.CHARRANGE(); UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_EXSETSEL, 0, cr); } try { editStream = data; Debug.Assert(data != null, "StreamIn passed a null stream"); // If SF_RTF is requested then check for the RTF tag at the start // of the file. We don't load if the tag is not there // if ((flags & RichTextBoxConstants.SF_RTF) != 0) { long streamStart = editStream.Position; byte[] bytes = new byte[SZ_RTF_TAG.Length]; editStream.Read(bytes, (int)streamStart, SZ_RTF_TAG.Length); string str = Encoding.Default.GetString(bytes); if (!SZ_RTF_TAG.Equals(str)) throw new ArgumentException(SR.GetString(SR.InvalidFileFormat)); // put us back at the start of the file editStream.Position = streamStart; } int cookieVal = 0; // set up structure to do stream operation NativeMethods.EDITSTREAM es = new NativeMethods.EDITSTREAM(); if ((flags & RichTextBoxConstants.SF_UNICODE) != 0) { cookieVal = INPUT | UNICODE; } else { cookieVal = INPUT | ANSI; } if ((flags & RichTextBoxConstants.SF_RTF) != 0) { cookieVal |= RTF; } else { cookieVal |= TEXTLF; } es.dwCookie = (IntPtr) cookieVal; es.pfnCallback = new NativeMethods.EditStreamCallback(this.EditStreamProc); // gives us TextBox compatible behavior, programatic text change shouldn't // be limited... // SendMessage(RichTextBoxConstants.EM_EXLIMITTEXT, 0, Int32.MaxValue); // go get the text for the control //Weird hack needed for 64-bit if (IntPtr.Size == 8) { NativeMethods.EDITSTREAM64 es64 = ConvertToEDITSTREAM64(es); UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_STREAMIN, flags, es64); //Assign back dwError value es.dwError = GetErrorValue64(es64); } else { UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_STREAMIN, flags, es); } UpdateMaxLength(); // If we failed to load because of protected // text then return protect event was fired so no // exception is required for the the error if (GetProtectedError()) return; if (es.dwError != 0) throw new InvalidOperationException(SR.GetString(SR.LoadTextError)); // set the modify tag on the control SendMessage(NativeMethods.EM_SETMODIFY, -1, 0); // EM_GETLINECOUNT will cause the RichTextBoxConstants to recalculate its line indexes SendMessage(NativeMethods.EM_GETLINECOUNT, 0, 0); } finally { // release any storage space held. editStream = null; } }
/// <include file='doc\RichTextBox.uex' path='docs/doc[@for="RichTextBox.Find3"]/*' /> /// <devdoc> /// Searches the text in a RichTextBox control for a given string. /// </devdoc> public int Find(string str, int start, int end, RichTextBoxFinds options) { int textLen = TextLength; if (str == null) throw new ArgumentNullException("str"); if (start < 0 || start > textLen) throw new ArgumentOutOfRangeException("start", SR.GetString(SR.InvalidBoundArgument, "start", start, 0, textLen)); if (end < -1) throw new ArgumentOutOfRangeException("end", SR.GetString(SR.RichTextFindEndInvalid, end)); bool selectWord = true; NativeMethods.FINDTEXT ft = new NativeMethods.FINDTEXT(); ft.chrg = new NativeMethods.CHARRANGE(); // set up the default values for the FINDTEXT structure, that is // the given string and the whole range of the text stream ft.lpstrText = str; if (end == -1) { end = textLen; } if (start > end) { throw new ArgumentException(SR.GetString(SR.RichTextFindEndInvalid, end)); } if ((options & RichTextBoxFinds.Reverse) != RichTextBoxFinds.Reverse) { // normal // ft.chrg.cpMin = start; ft.chrg.cpMax = end; } else { // reverse // ft.chrg.cpMin = end; ft.chrg.cpMax = start; } // force complete search if we ended up with a zero length search if (ft.chrg.cpMin == ft.chrg.cpMax) { if ((options & RichTextBoxFinds.Reverse) != RichTextBoxFinds.Reverse) { ft.chrg.cpMin = 0; ft.chrg.cpMax = -1; } else { ft.chrg.cpMin = textLen; ft.chrg.cpMax = 0; } } // set up the options for the search int findOptions = 0; if ((options & RichTextBoxFinds.WholeWord) == RichTextBoxFinds.WholeWord) findOptions |= RichTextBoxConstants.FR_WHOLEWORD; if ((options & RichTextBoxFinds.MatchCase) == RichTextBoxFinds.MatchCase) findOptions |= RichTextBoxConstants.FR_MATCHCASE; if ((options & RichTextBoxFinds.NoHighlight) == RichTextBoxFinds.NoHighlight) selectWord = false; if ((options & RichTextBoxFinds.Reverse) != RichTextBoxFinds.Reverse) { // The default for RichEdit 2.0 is to search in reverse findOptions |= RichTextBoxConstants.FR_DOWN; } // Perform the find, will return ubyte position int position; position = (int)UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_FINDTEXT, findOptions, ft); // if we didn't find anything, or we don't have to select what was found, // we're done if (position != -1 && selectWord) { // Select the string found, this is done in ubyte units NativeMethods.CHARRANGE chrg = new NativeMethods.CHARRANGE(); chrg.cpMin = position; //Look for kashidas in the string. A kashida is an arabic visual justification character //that's not semantically meaningful. Searching for ABC might find AB_C (where A,B, and C //represent Arabic characters and _ represents a kashida). We should highlight the text //including the kashida (VSWhidbey 94809). char kashida = (char)0x640; string text = this.Text; string foundString = text.Substring(position, str.Length); int startIndex = foundString.IndexOf(kashida); if (startIndex == -1) { //No kashida in the string chrg.cpMax = position + str.Length; } else { //There's at least one kashida int searchingCursor; //index into search string int foundCursor; //index into Text for (searchingCursor = startIndex, foundCursor = position + startIndex; searchingCursor < str.Length; searchingCursor++, foundCursor++) { while (text[foundCursor] == kashida && str[searchingCursor] != kashida) { foundCursor++; } } chrg.cpMax = foundCursor; } UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_EXSETSEL, 0, chrg); SendMessage(NativeMethods.EM_SCROLLCARET, 0, 0); } return position; }