I'm trying to make 'find/find next' function in my windows store application.
Word which I want to search and select is in textBox named 'tboxFind'.
Textbox 'EditorWindow' contains all my text.
My function works good only if there is one line of text in 'editorWindow'.
Otherwise, selection is moved forwards by number of new lines.
How to fix it?
Is there any simple way to create find next function?
private void btnFind_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
if ((tmpPos) == pos && tmpWord == tboxFind.Text && !String.IsNullOrEmpty(editorWindow.Text))
{
string tmpString = editorWindow.Text.Substring(pos + tboxFind.Text.Length);
tmpPos = tmpString.ToLower().IndexOf(tboxFind.Text.ToLower());
if (tmpPos != -1)
{
editorWindow.Focus(Windows.UI.Xaml.FocusState.Keyboard);
editorWindow.SelectionStart = pos + tmpPos + tboxFind.Text.Length;
editorWindow.SelectionLength = tboxFind.Text.Length;
pos = pos + tmpPos + tboxFind.Text.Length;
}
}
tmpWord = tboxFind.Text;
tmpPos = pos;
}
// EDIT:
I found a different way to create that function. Here is my code:
private void btnFind_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
numOfNewLines = 0;
pos = (tmpWord == tboxFind.Text) ? editorWindow.Text.ToLower().IndexOf(tboxFind.Text, pos + tboxFind.Text.Length)
: editorWindow.Text.ToLower().IndexOf(tboxFind.Text);
if (pos != -1)
{
foreach (char s in editorWindow.Text.Substring(0, pos))
{
if (s == '\n')
{
numOfNewLines++;
}
}
pos -= numOfNewLines;
editorWindow.Focus(Windows.UI.Xaml.FocusState.Keyboard);
//tmpPos = editorWindow.Text.ToLower().IndexOf(tboxFind.Text);
editorWindow.Select(pos, tboxFind.Text.Length);
pos += numOfNewLines;
}
tmpWord = tboxFind.Text;
}
I'm not 100% sure what's wrong with your code because I can't fully replicate it, but consider the following SSCCE in a basic Windows application:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
foreach (var i in FindIndicies("text"))
{
this.textBox1.SelectionStart = i;
this.textBox1.SelectionLength = "text".Length;
var result = MessageBox.Show(
"Move to the next index?",
"Next?",
MessageBoxButtons.YesNo);
if (result == System.Windows.Forms.DialogResult.No) { break; }
}
}
private List<int> FindIndicies(string textToFind)
{
var indicies = new List<int>();
var offset = 0;
var i = 0;
while ((i = this.textBox1.Text.IndexOf(
textToFind,
offset,
StringComparison.CurrentCultureIgnoreCase)) > 0)
{
indicies.Add(i);
offset = (i + textToFind.Length);
}
return indicies;
}
}
given that textBox1 has a set text value of:
Here is a set of text
and I'm going to find the word text
Even when there are multiple lines of text.
It finds each index properly, and selects them properly.
I would consider finding all indicies up front with the method I wrote and then simply iterate through them on demand. In my case I'm using a message box to determine when I want to move to the next index, but you'll use something different.
Related
I am trying to make a Sudoku solver in C# WPF, using backtracking algorithm, everything was going smoothly, but just as it seemed like it was going to work, I started getting stack overflow errors.
I tried to fix them with separating the loops and calls, but I still am getting the same error.
Another thing I tried was to make certain methods separate threads, but that seems too complicated and I am not sure it will work.
I want to know if separating them with threads could work or if I have to do something else so I don't get the error.
The main part of the code is below, though I don't know if it can be of any help
// h is the current Label's index in the list MTLabels (Empty labels)
// a is the current number I am displaying
private void Calculate()
{
Label label = MTLabels[h];
label.Content = a;
Check(label);
return;
}
private void Check(Label label)
{
CommonLabels.Clear();
var p1 = VisualTreeHelper.GetParent(label) as UIElement;
int labelID = Convert.ToInt32(label.Name.Substring(5));
int labelY = GetLabelY(labelID);
int labelX = labelID % 9;
string labelParent = ((Grid)p1).Name;
foreach (Label lbl in Labels)
{
if (lbl != label)
{
var p2 = VisualTreeHelper.GetParent(lbl) as UIElement;
int lblID = Convert.ToInt32(lbl.Name.Substring(5));
int lblY = GetLabelY(lblID);
int lblX = lblID % 9;
string lblParent = ((Grid)p2).Name;
if (lblY == labelY || lblX == labelX || lblParent == labelParent)
{
CommonLabels.Add(lbl);
}
}
}
CheckInCommon();
}
private void CheckInCommon()
{
Label label = MTLabels[h];
foreach (Label lbl in CommonLabels)
{
if (lbl.Content.ToString() == label.Content.ToString())
{
if (a >= 9)
{
label.Content = "";
h--;
label = MTLabels[h];
a = (int)label.Content;
a++;
label.Content = a;
Check(label);
}
else if (a < 9)
{
a++;
label.Content = a;
CheckInCommon();
}
}
}
if (h + 1 < MTLabels.Count)
{
a = 1;
h++;
label = MTLabels[h];
label.Content = a;
Check(label);
}
else if (h + 1 == MTLabels.Count)
{
Console.WriteLine("Done");
}
}
I have been looking for a way to have a TextBox format while the user is inputting data.
I have made a hack that does what I want, but it is a bit finicky due things like when you accidentally press two keys at once which can screw with the if statements and when trying to delete the string via Delete Key by the user.
Anyway, that is not the issue and this is the code that I am using now:
Calling the methods from this class, The first method adds commas to the string and the second method checks if a comma is at the end when the user has finnished typing.
class BonusData
{
public class DoDataTextBox
{
public static void AutoComplete(TextBox textBox, int counter)
{
if (textBox.Text.Length - counter == 3)
{
textBox.Text = textBox.Text + ",";
textBox.SelectionStart = textBox.Text.Length;
}
else if (textBox.Text.Length - counter == 7)
{
textBox.Text = textBox.Text + ",";
textBox.SelectionStart = textBox.Text.Length;
}
else if (textBox.Text.Length - counter == 11)
{
textBox.Text = textBox.Text + ",";
textBox.SelectionStart = textBox.Text.Length;
}
}
public static void CheckLastCharacter(TextBox textBox)
{
if (textBox.Text.Length < 2) return;
if (textBox.Text.Substring(textBox.Text.Length - 1) != ",") return;
{
textBox.Text = textBox.Text.Substring(0, textBox.Text.Length - 1);
}
}
}
}
And the event handlers,
private void SecondMonthRowOne_OnKeyUp(object sender, KeyEventArgs e)
{
int counter = 0;
if (IsText)
{
counter = 1;
}
BonusData.DoDataTextBox.AutoComplete((TextBox)sender, counter);
}
private void SecondMonthRowOne_OnGotFocus(object sender, RoutedEventArgs e)
{
if (e.ToString() != String.Empty) return;
IsText = true;
}
private void SecondMonthRowOne_OnLostFocus(object sender, RoutedEventArgs e)
{
BonusData.DoDataTextBox.CheckLastCharacter((TextBox)sender);
IsText = false;
}
Which gives a result like this,
999,999,999
Like I said it works 98% of the time, but I was hoping there was another way to do this in WPF. I have searched far and wide, but I don't even know what something like this is called.
I suggest you to use "WPF TextBox AutoComplete" instead. It's a WPF attached behavior wrapped in a NuGet package.
You can simply add this package to your project by adding this using NuGet Package Manager for your WPF project.
This NuGet package is also open source, therefore you can customize it further to meet your needs/requirements.
The source code is hosted on this GitHub repo: https://github.com/Nimgoble/WPFTextBoxAutoComplete
NOTE: I haven't fully tested this WPF library against .NET 4.6. The library is compiled against .NET 4.5 as its target. But as far as I know, it's 100% compatible with WPF in .NET 4.6.
Without using any outside addins I found this to work really well,
This will add a Comma every third character and allow users to delete characters one at a time.
The TextBox format will look like this,
999,999,999,999
The code,
public class DoDataTextBox
{
public static void AutoCompleteStringBuild(TextBox textBox)
{
string endResult = null;
string replace = Reverse(textBox.Text).Replace(",", "");
int count = 1;
char[] value = replace.ToCharArray();
foreach (var car in value)
{
if (count % 3 == 0)
{
endResult = String.Concat(endResult, car);
if (value.Length != count)
{
endResult = String.Concat(endResult, ",");
}
count++;
}
else
{
endResult = String.Concat(endResult, car);
count++;
}
}
if (endResult == null) return;
string textBoxResult = Reverse(endResult);
textBox.Text = textBoxResult;
}
public static string Reverse(string stringVal)
{
char[] charArray = stringVal.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
public static void IsDeleteKey(TextBox textBox, KeyEventArgs e)
{
textBox.SelectionStart = e.Key == Key.Delete ? 0 : textBox.Text.Length;
}
}
And the event handler,
private void TextBoxOne_OnKeyUp(object sender, KeyEventArgs e)
{
BonusData.DoDataTextBox.AutoCompleteStringBuild((TextBox)sender);
BonusData.DoDataTextBox.IsDeleteKey((TextBox)sender, e);
}
At present this is the best way I have found to do this.
I am looking a way to create a HyperLink in a RichTextBox pointing to a line of the text of the same RichTextBox.
I just found how to do that with Internet Links but I don't find a way to do it with the same text inside of the control (It's like Hyperlinks in MS Word pointing to a header or bookmark).
Thanks in Advance. - CCB
No, this will not work unless you code the necessary stuff yourself.
Two suggestions:
A simple workaround with links always starting with www.
The nicer solution with arbitrary link text
Let's have a look at both options..:
Using the built-in functionality of recognizing an URL seems the right way to start, but the link will always have to look like a URL, not like a hyperlink to an anchor.. If you can live with a solution that has, say, links like this: www.goto.234 and anchors like this: #234# this is really rather simple..
A working example can be as simple as this:
private void richTextBox1_LinkClicked(object sender, LinkClickedEventArgs e)
{
var s = e.LinkText.Split('.');
string anchor = s[2];
int a = richTextBox1.Text.IndexOf("#" + anchor + "#" );
if (a >= 0) richTextBox1.SelectionStart = a; else return; // do add more checks!
richTextBox1.SelectionLength = 0;
Text = anchor + " # " + a;
//richTextBox1.ScrollToCaret(); <<--- this crashes on my machine!
// so I take the jump out of the click event and it works:
Timer ttt = new Timer() { Interval = 100 };
ttt.Tick += (ss, ee) => { richTextBox1.ScrollToCaret(); ttt.Stop(); };
}
Option two: If you'd rather have more choice of how the links should read you can do this:
Start by formatting each to
Start with a special character, say a tilde '~'
format it to look blue and underlined if you want to
Either make it one word or replace space by underlines and format those to have the forecolor equal to the backcolor
Now this can do the job:
public string delimiters = " ()[]{}!&?=/\\,;.\r\n";
private void richTextBox2_Click(object sender, EventArgs e)
{
int sstart = -1;
string s = getWordAt(richTextBox2.Text,
richTextBox2.SelectionStart, delimiters, out sstart);
if (s.Length < 3) return;
string char1 = s.Substring(0, 1);
if (char1 == "~")
{
int p = richTextBox2.Text.IndexOf("#" + s.Substring(1));
if (p >= 0) { richTextBox2.SelectionStart = p; richTextBox2.ScrollToCaret(); }
}
}
public static string getWordAt(string text, int cursorPos,
string delimiters, out int selStart)
{
int startPos = 0;
selStart = startPos;
if ((cursorPos < 0) | (cursorPos > text.Length) | (text.Length == 0)) return "";
if ((text.Length > cursorPos) & (delimiters.Contains(text[cursorPos]))) return "";
int endPos = text.Length - 1;
if (cursorPos == text.Length) endPos = text.Length - 1;
else { for (int i = cursorPos; i < text.Length; i++)
{ if (delimiters.Contains(text[i])) { endPos = i - 1; break; } } }
if (cursorPos == 0) startPos = 0;
else { for (int i = cursorPos; i > 0; i--)
{ if (delimiters.Contains(text[i])) { startPos = i + 1; break; } } }
selStart = startPos;
return text.Substring(startPos, endPos - startPos + 1);
}
Here are the two versions side by side, once at the top then after clicking on a link:
Both versions work fine, although both could do with some more checks.
Note that I was too lazy to format the pseudo-links in the second example, so they show their tildes and hashes..
Not hard to write a helper function that can insert the formatting; the search will still work as it searches in the Text, not the Rtf property..
I'm trying to find instances of a string in a WPF RichTextBox. What I have now almost works, but it highlights the wrong section of the document.
private int curSearchLocation;
private void FindNext_Click(object sender, RoutedEventArgs e)
{
TextRange text = new TextRange(RichEditor.Document.ContentStart, RichEditor.Document.ContentEnd);
var location = text.Text.IndexOf(SearchBox.Text, curSearchLocation, StringComparison.CurrentCultureIgnoreCase);
if (location < 0)
{
location = text.Text.IndexOf(SearchBox.Text, StringComparison.CurrentCultureIgnoreCase);
}
if (location >= 0)
{
curSearchLocation = location + 1;
RichEditor.Selection.Select(text.Start.GetPositionAtOffset(location), text.Start.GetPositionAtOffset(location + SearchBox.Text.Length));
}
else
{
curSearchLocation = 0;
MessageBox.Show("Not found");
}
RichEditor.Focus();
}
This is what happens when I search for "document":
This is because GetPositionAtOffset includes non-text elements such as opening and closing tags in its offset, which is not what I want. I couldn't find a way to ignore these elements, and I also couldn't find a way to directly get a TextPointer to the text I want, which would also solve the problem.
How can I get it to highlight the correct text?
Unfortunately the TextRange.Text strips out non-text characters, so in this case the offset computed by IndexOf will be slightly too low. That is the main problem.
I tried to solve your problem and found working solution that works fine even when we have formatted text in many paragraphs.
A lot of help is taken from this CodeProject Article. So also read that article.
int curSearchLocation;
private void FindNext_Click(object sender, RoutedEventArgs e)
{
TextRange text = new TextRange(RichEditor.Document.ContentStart, RichEditor.Document.ContentEnd);
var location = text.Text.IndexOf(SearchBox.Text, curSearchLocation, StringComparison.CurrentCultureIgnoreCase);
if (location < 0)
{
location = text.Text.IndexOf(SearchBox.Text, StringComparison.CurrentCultureIgnoreCase);
}
if (location >= 0)
{
curSearchLocation = location + 1;
Select(location, SearchBox.Text.Length);
}
else
{
curSearchLocation = 0;
MessageBox.Show("Not found");
}
RichEditor.Focus();
}
public void Select(int start, int length)
{
TextPointer tp = RichEditor.Document.ContentStart;
TextPointer tpLeft = GetPositionAtOffset(tp, start, LogicalDirection.Forward);
TextPointer tpRight = GetPositionAtOffset(tp, start + length, LogicalDirection.Forward);
RichEditor.Selection.Select(tpLeft, tpRight);
}
private TextPointer GetPositionAtOffset(TextPointer startingPoint, int offset, LogicalDirection direction)
{
TextPointer binarySearchPoint1 = null;
TextPointer binarySearchPoint2 = null;
// setup arguments appropriately
if (direction == LogicalDirection.Forward)
{
binarySearchPoint2 = this.RichEditor.Document.ContentEnd;
if (offset < 0)
{
offset = Math.Abs(offset);
}
}
if (direction == LogicalDirection.Backward)
{
binarySearchPoint2 = this.RichEditor.Document.ContentStart;
if (offset > 0)
{
offset = -offset;
}
}
// setup for binary search
bool isFound = false;
TextPointer resultTextPointer = null;
int offset2 = Math.Abs(GetOffsetInTextLength(startingPoint, binarySearchPoint2));
int halfOffset = direction == LogicalDirection.Backward ? -(offset2 / 2) : offset2 / 2;
binarySearchPoint1 = startingPoint.GetPositionAtOffset(halfOffset, direction);
int offset1 = Math.Abs(GetOffsetInTextLength(startingPoint, binarySearchPoint1));
// binary search loop
while (isFound == false)
{
if (Math.Abs(offset1) == Math.Abs(offset))
{
isFound = true;
resultTextPointer = binarySearchPoint1;
}
else
if (Math.Abs(offset2) == Math.Abs(offset))
{
isFound = true;
resultTextPointer = binarySearchPoint2;
}
else
{
if (Math.Abs(offset) < Math.Abs(offset1))
{
// this is simple case when we search in the 1st half
binarySearchPoint2 = binarySearchPoint1;
offset2 = offset1;
halfOffset = direction == LogicalDirection.Backward ? -(offset2 / 2) : offset2 / 2;
binarySearchPoint1 = startingPoint.GetPositionAtOffset(halfOffset, direction);
offset1 = Math.Abs(GetOffsetInTextLength(startingPoint, binarySearchPoint1));
}
else
{
// this is more complex case when we search in the 2nd half
int rtfOffset1 = startingPoint.GetOffsetToPosition(binarySearchPoint1);
int rtfOffset2 = startingPoint.GetOffsetToPosition(binarySearchPoint2);
int rtfOffsetMiddle = (Math.Abs(rtfOffset1) + Math.Abs(rtfOffset2)) / 2;
if (direction == LogicalDirection.Backward)
{
rtfOffsetMiddle = -rtfOffsetMiddle;
}
TextPointer binarySearchPointMiddle = startingPoint.GetPositionAtOffset(rtfOffsetMiddle, direction);
int offsetMiddle = GetOffsetInTextLength(startingPoint, binarySearchPointMiddle);
// two cases possible
if (Math.Abs(offset) < Math.Abs(offsetMiddle))
{
// 3rd quarter of search domain
binarySearchPoint2 = binarySearchPointMiddle;
offset2 = offsetMiddle;
}
else
{
// 4th quarter of the search domain
binarySearchPoint1 = binarySearchPointMiddle;
offset1 = offsetMiddle;
}
}
}
}
return resultTextPointer;
}
int GetOffsetInTextLength(TextPointer pointer1, TextPointer pointer2)
{
if (pointer1 == null || pointer2 == null)
return 0;
TextRange tr = new TextRange(pointer1, pointer2);
return tr.Text.Length;
}
Hope so this code will work for your case.
I'm working on storing last used settings and then when program starts retrieve them back. Everything works fine but I just don't know how to deal with ListBox items.
To point out one thing is that I'm already using one delimiter to store my settings. I'm getting quite confused when dealing with this problem.
This is how I store my settings:
private void btnStart_Click(object sender, EventArgs e)
{
int interval = 0;
int plusMinus = 0;
int pause = 0;
int delay = 0;
int randomLine = 0;
if (cbPause.Checked == true) pause = 1;
if (cbDelay.Checked == true) delay = 1;
if (cbRandomLine.Checked == true) randomLine = 1;
interval = int.Parse(nudInterval.Value.ToString());
plusMinus = int.Parse(nudPlusMinus.Value.ToString());
lastUsed.Text =
interval + splitString +
plusMinus + splitString +
pause + splitString +
delay + splitString +
randomLine;
if (nudPlusMinus.Value == 0)
{
tmrInterval.Interval = int.Parse(nudInterval.Value.ToString());
}
else
{
Random random = new Random();
tmrInterval.Interval = random.Next(int.Parse(nudInterval.Value.ToString()) - int.Parse(nudPlusMinus.Value.ToString()), int.Parse(nudInterval.Value.ToString()) + int.Parse(nudPlusMinus.Value.ToString()));
}
WhenStarted();
tmrInterval.Start();
}
This is how I retrieve them at the program start up:
public AutoTyper()
{
InitializeComponent();
tmrInterval.Tick += new EventHandler(Interval);
tmrDelay.Tick += new EventHandler(Delay);
tmrSpace.Tick += new EventHandler(Space);
lbMessage.SelectedIndexChanged += new EventHandler(lbMessage_SelectedIndexChanged);
txtMessage.TextChanged += new EventHandler(txtMessage_TextChanged);
SetInterval();
if (!lastUsed.EmptyFile())
{
string[] allSettings = lastUsed.Text.Split(splitChar, StringSplitOptions.None);
int settingCount = 0;
int settingNumber = 0;
foreach (string setting in allSettings) settingNumber++;
if (settingNumber == 5)
{
foreach (string setting in allSettings)
{
settingCount++;
if (settingCount == 1) nudInterval.Value = int.Parse(setting);
else if (settingCount == 2) nudPlusMinus.Value = int.Parse(setting);
else if (settingCount == 3) { if (setting == "1") cbPause.Checked = true; }
else if (settingCount == 4) { if (setting == "1") cbDelay.Checked = true; }
else if (settingCount == 5) { if (setting == "1") cbRandomLine.Checked = true; }
}
}
}
}
Just retrieve/set SelectedIndex after adding all values (unless that happens at design time already).
But in general, I'd rewrite that settings handling. You should store your settings using keys and values. Otherwise you'll run into tons issue if you ever want to add, remove or change the order of some settings.