How to get the Current Line Index in WinUI UWP TextBox? - c#

I know that SelectionStart property of WinUI UWP TextBox will return the CaretIndex. But, I want to get the exact Column and Line Position of Text. In WPF, GetLineFromCharacterIndex(CaretIndex) and TextBox.Lines[LineIndex].Length could be used to find the Current Line Index and Column number respectively. How can I achieve the same in WinUI UWP Textbox ?

Try this method:
public static int GetCurrentLineIndex(TextBox textBox)
{
int caretIndex = textBox.SelectionStart;
if (caretIndex == 0)
return 0;
string[] lines = textBox.Text?.Split('\r') ?? Array.Empty<string>();
int offset = 0;
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
offset += line.Length;
if (caretIndex <= offset)
return i;
offset++;
}
return 0;
}
It may need some slight improvement but it should give you the idea how you could determine the current line of the cursor.
You can call it from wherever you want to get the index, e.g.:
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
int index = GetCurrentLineIndex(sender as TextBox);
//...
}

Maybe you could do something like this:
var text = Textbox.Text;
var lines = text.Split('\r');
...
This has worked for me in the past using WPF but I have never tried UWP.
This also seems like a workaround so there might be a better, more practical, solution.

This example uses MVVM structure but you can apply the same concepts with a temp variable which stores the previous value.
<TextBox Height="600" Width="600"
Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" AcceptsReturn="True"/>
Then I added this to the constructor:
this.DataContext = this;
This isnt best practice and if you were using MVVM you would set up a ViewModel and use that (I did this for testing purposes).
Then I created my properties like this:
private int _line;
public int Line
{
get { return _line; }
set
{
_line = value;
tb1.Text = value.ToString();
}
}
private int _column;
public int Column
{
get { return _column; }
set
{
_column = value;
tb2.Text = value.ToString();
}
}
private string _text;
public string Text
{
get { return _text; }
set
{
if (_text + '\r' != value)
{
Line = GetLine(_text, value);
Column = GetColumn(_text, value, Line);
}
else
{
Line++;
Column = 0;
}
_text = value;
}
}
Then added my functions:
public int GetLine(string original, string newText)
{
var oLines = GenArray(original);
var nLines = GenArray(newText);
//set this to -1 if you want 0-based indexing
int count = 0;
foreach (var line in nLines)
{
count++;
if (oLines.Length < count || line != oLines[count - 1])
{
break;
}
}
return count;
}
public int GetColumn(string original, string newText, int lineChanged)
{
var oLine = GenArray(original)[lineChanged - 1];
var nLine = GenArray(newText)[lineChanged - 1];
//set this to -1 if you want 0-based indexing
int count = 0;
foreach (var c in nLine)
{
count++;
if (oLine.Length < count || c != oLine[count - 1])
{
}
}
return count;
}
private string[] GenArray(string text)
{
string[] lines;
if (text == null)
{
lines = new string[1] { "" };
}
else if (text.Contains('\r'))
{
lines = text.Split('\r');
}
else
{
lines = new string[1] { text };
}
return lines;
}
If you don't use MVVM just do this:
public string[] TempLines { get; set; }
...
//after the calculation code has finished
TempLines = TextBox.Split('\r');
Then you can substitute TempLines for value

Related

C# StringBuilder - Wrapping Text to Another Line

THE PROBLEM
The problem I am running into is trying to get text to wrap to another line properly through a function. What I would like it to do is to wrap like it would in a word editor to the next line cleanly.
THE CODE
The code for the function and all other relevant information required:
// Internal variables that store values.
// Should NOT be called upon.
private int width;
private int height;
private int x;
private int y;
// Getter/Setter for window Width.
public int Width
{
get
{
width = Console.WindowWidth;
return width;
}
private set
{
if (value <= 0)
{
throw new Exception("Width setter: Invalid Width inputted");
}
else
{
width = value;
}
}
}
// Getter/Setter for window Height.
public int Height
{
get
{
height = Console.WindowHeight;
return height;
}
private set
{
if (value <= 0)
{
throw new Exception("Height setter: Invalid height inputted");
}
else
{
height = value;
}
}
}
// Getter/Setter for cursor X position.
public int X
{
get
{
x = Console.CursorLeft;
return x;
}
private set
{
if ( value < 0 || value > Width )
{
throw new Exception("X Setter: Invalid X position.");
}
else
{
x = value;
}
}
}
// Getter/Setter for cursor Y position.
public int Y
{
get
{
y = Console.CursorTop;
return y;
}
private set
{
if (value < 0 || value > Height)
{
throw new Exception("Y Setter: Invalid Y position.");
}
else
{
y = value;
}
}
}
// SetCursorPosition is a method to, well, change the cursor position.
public void SetCursorPosition(int newX, int newY)
{
this.X = newX;
this.Y = newY;
Console.SetCursorPosition(newX, newY);
}
// WriteLine writes a line to the console.
// It also sanity checks the length of the string doesn't exceed the width
// of the window, and changes the string to be among two or even three lines
// if needed.
public void WriteLine( int yPos, string String )
{
int stringLength = String.Length;
if (stringLength > Width)
{
string[] textToSplit = String.Split();
StringBuilder splitText = new StringBuilder();
int currentLineLength = 0;
for ( int i = 0; i < textToSplit.Length; i++ )
{
if ( currentLineLength > Width )
{
if ( textToSplit[i].Length > Width - currentLineLength )
{
splitText.Append("\n");
splitText.Append(textToSplit[i]);
currentLineLength = 0;
}
else
{
splitText.Append(textToSplit[i]);
splitText.Append("\n");
currentLineLength = 0;
}
}
else
{
splitText.Append(textToSplit[i]);
splitText.Append(" ");
currentLineLength = currentLineLength + textToSplit[i].Length + 1;
}
}
Console.Write(splitText);
}
// The string fits on one line, so just print it out.
else
{
SetCursorPosition(0, yPos);
this.Y = yPos;
Console.Write(String);
}
}
THE RESULTS
What I expect the text to look like after inputting it is:
This is a really long string intended to demonstrate how words should wrap to the next line of the console. If I extending the string longer, it should wrap.
What it actually looks like:
You can use the following: to record a line in your view, example:
var new text = "text"
var cmd = #"what wish
show {change}";
If you want to change or collate something in this text you can use it that way.
cmd = cmd.Replace ("{change}", new text);
If the item you want to change is a list of objects you can use:
List <int> numbers = new List <int> (new int [] {2, 3, 5});
cmd = cmd.Replace ("{change}", string.Join (",", numbers));

Highlighting words in RichEditBox

It is needed to highlight by fiven colour a substring in the document RichEditBox. For this purpose I wrote a method:
private async Task ChangeTextColor(string text, Color color)
{
string textStr;
bool theEnd = false;
int startTextPos = 0;
myRichEdit.Document.GetText(TextGetOptions.None, out textStr);
while (theEnd == false)
{
myRichEdit.Document.GetRange(startTextPos, textStr.Length).GetText(TextGetOptions.None, out textStr);
var isFinded = myRichEdit.Document.GetRange(startTextPos, textStr.Length).FindText(text, textStr.Length, FindOptions.None);
if (isFinded != 0)
{
string textStr2;
textStr2 = myRichEdit.Document.Selection.Text;
var dialog = new MessageDialog(textStr2);
await dialog.ShowAsync();
myRichEdit.Document.Selection.CharacterFormat.BackgroundColor = color;
startTextPos = myRichEdit.Document.Selection.EndPosition;
myRichEdit.Document.ApplyDisplayUpdates();
}
else
{
theEnd = true;
}
}
}
In the debugger you can see that there is a substring and isFinded is equal with the number of signs (or symbols) in the found substring. It means the fragment is found and judging by the description of the method FindText should be highlighted but it isn't. In textStr2 an empty line returns and, correspondingly, the colour doesn't change. I cannot identify the reasons of the error.
The code you postted did not set the selection, so the myRichEdit.Document.Selection is null. You can use ITextRange.SetRange to set the selection. And you can use ITextRange.FindText method to find the string in the selection.
For example:
private void ChangeTextColor(string text, Color color)
{
string textStr;
myRichEdit.Document.GetText(TextGetOptions.None, out textStr);
var myRichEditLength = textStr.Length;
myRichEdit.Document.Selection.SetRange(0, myRichEditLength);
int i = 1;
while (i > 0)
{
i = myRichEdit.Document.Selection.FindText(text, myRichEditLength, FindOptions.Case);
ITextSelection selectedText = myRichEdit.Document.Selection;
if (selectedText != null)
{
selectedText.CharacterFormat.BackgroundColor = color;
}
}
}

How to group an array of variables based on flags assigned to them with minimal code in C#

I have an array that has four elements
example :
int[] a = new int[5];
the value would be like : a[0] = 10,a[1]=5,a[2]=15,a[3]=10,a[4]=0;
I have flag ,
public bool[] flag = new bool[4]{false,false,false,false};
based on which I need to assign this above values to another variable named b[5].
If the flag is false,it will add to the existing values of b ,
else it will reset the values of a to b.
I have tried the following code which seems to be too lengthy
count[0]=20,count[1]=20;count[2]=20;count[3]=20;
flag[0]=true,flag[1]=true,flag[2]=true,flag[3]=false;
void display(int[] count,int[]flag)
{
if (flag[0] == true)
{
resetcount[0] = count[0];
}
if(flag[1]==true)
{
resetcount[1] = count[1];
}
if (flag[2] == true)
{
resetcount[2] = count[2];
}
if(flag[3]==true)
{
resetcount[3] = count[3];
}
if (flag[0] == false)
{
resetcount[0] += count[0];
}
if (flag[1] == false)
{
resetcount[1] += count[1];
}
if (flag[2] == false)
{
resetcount[2] += count[2];
}
if (flag[3] == false)
{
resetcount[3] += count[3];
}
}
here resetcount[0]=10,resetcount[1]=10,resetcount[2]=10,resetcount[3]=10;
if all flag is false the count value will be added and if it is true the count value will be set to reset count
so as above it will be 0,1,2
count[0]=20,count[1]=20;count[2]=20;count[3]=20;
flag[0]=true,flag[1]=true,flag[2]=true,flag[3]=false;
resetcount[0]=10,resetcount[1]=10,resetcount[2]=10,resetcount[3]=10;
so now resetcount would be :
resetcount[0]=20,resetcount[1]=20,resetcount[2]=20,resetcount[3]=30;
Like a loop?
count[0]=20,count[1]=20;count[2]=20;count[3]=20;
flag[0]=true,flag[1]=true,flag[2]=true,flag[3]=false;
void display(int[] count,int[]flag)
{
for (int i = 0 ; i<=3 ; i++)
{
if (flag[i])
{
resetcount[i] = count[i];
}
else
{
resetcount[i] += count[i];
}
}
public void Display(int[] resetCount, int[] count, bool[] flags)
{
for (int i = 0; i < resetCount.Count(); i++)
{
resetCount[i] = this.Calculate(resetCount[i], count[i], flags[i]);
}
}
public int Calculate(int resetCount, int count, bool flag)
{
if (flag)
{
resetCount = count;
}
else
{
resetCount += count;
}
return resetCount;
}

Creating a function to be reusable multiple times in my code

I want to be able to call the following function multiple times through out my code to fill different groups of 8 text boxes in my form.
Right now reference is being passed in "tbPlay" from where it is being called initially in the code.
Each time this function will be called it will be to fill different text box groups.
I am trying to think of a way of using the empty for loop to create the necessary variable names to replace tbPlay0-7 in my case statement, so it isn't only usable for one group of text boxes in my code. I am not sure it can be done.
Can anyone help.
private void convertBasetoDrawn(string numBase, string reference)
{
string baseNumber = numBase;
for (int i = 0; i < 8; i++)
{
//some code here to create variables to replace the text box names in the
//following case statement
}
switch (baseNumber)
{
case "000":
tbPlay0.Text = "000";
tbPlay0.ForeColor = Color.Red;
tbPlay1.Text = "500";
tbPlay2.Text = "050";
tbPlay3.Text = "005";
tbPlay4.Text = "550";
tbPlay5.Text = "505";
tbPlay6.Text = "055";
tbPlay7.Text = "555";
tbPlay7.ForeColor = Color.Red;
break;
}
}
Create a List<TextBox> for each group:
List<TextBox> list01 = new List<TextBox>() { tbPlay0, tbPlay1, ....};
List<TextBox> list02 = new List<TextBox>() { ..., ... , ....};
// ..
}
And pass such a group to the function:
private void convertBasetoDrawn(List<TextBox> list, string numBase, string reference)
{
string[] texts = new string[8]
{ "000", "500", "050", "005", "550", "505", "055", "555" };
for (int t = 0; t < list.Count; t++) list[t].Text = texts[t];
list[0].ForeColor = Color.Red;
list[7].ForeColor = Color.Red;
}
Assuming the texts will always look like that. If they depend on, maybe numbase you can construct them dynamically as well, as long as you know the rules.. Maybe even a simple replacement will do the job?
You didn't use reference, btw..
Now, I'm just guessig here, but maybe this is the pattern for your texts..:
string[] texts = new string[8]
{ "nnn", "dnn", "ndn", "nnd", "ddn", "dnd", "ndd", "ddd" };
for (int s = 0; s < texts.Length; s++)
texts[s] = texts[s].Replace("d", numBase).Replace("n", reference);
Now you can call it like this:
convertBasetoDrawn(list01, "0","5");
Update: For the rules as I understand them now you could do:
string newText = "";
for (int s = 0; s < texts.Length; s++)
{
newText = "";
for (int i = 0; i < 3; i++)
{
if (texts[s][i] == 'n') newText += numBase[i];
else newText += (Convert.ToByte(numBase[i].ToString()) +
Convert.ToByte(reference[0].ToString()) ).ToString("0");
}
texts[s] = newText;
}
and call it like this:
convertBasetoDrawn(list01, "001", "5");
or
convertBasetoDrawn(list02, "000", "1");
Note: no carry over here.. You'd have to define rules for that and code it yourself..
It's not clear how you plan to identify the specific group of eight. But let's assume you have somehow.
Then, if I were writing this code, I would use a UserControl to encapsulate the repeated pattern, exposing the eight TextBox controls — or rather, the properties of them that you want access to — as properties. E.g.
class TextBoxGroup : UserControl
{
public string Text1
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
public Color ForeColor1
{
get { return textBox1.ForeColor; }
set { textBox1.ForeColor = value; }
}
public string Text2
{
get { return textBox2.Text; }
set { textBox2.Text = value; }
}
public Color ForeColor2
{
get { return textBox2.ForeColor; }
set { textBox2.ForeColor = value; }
}
// ...
public string Text8
{
get { return textBox8.Text; }
set { textBox8.Text = value; }
}
public Color ForeColor8
{
get { return textBox8.ForeColor; }
set { textBox8.ForeColor = value; }
}
}
Then in your method, rather than whatever logic you planned on using to figure the starting index for your group, instead you just retrieve the appropriate TextBoxGroup instance and use it in the switch, like this:
case "000":
textBoxGroup.Text1 = "000";
textBoxGroup.ForeColor1 = Color.Red;
textBoxGroup.Text2 = "500";
textBoxGroup.Text3 = "050";
textBoxGroup.Text4 = "005";
textBoxGroup.Text5 = "550";
textBoxGroup.Text6 = "505";
textBoxGroup.Text7 = "055";
textBoxGroup.Text8 = "555";
textBoxGroup.ForeColor8 = Color.Red;
break;
A variation on the above would encapsulate the properties with setter methods taking an index. E.g.
class TextBoxGroup : UserControl
{
// Initialized in constructor to be the eight TextBoxes
private TextBox[] _textboxes;
public void SetText(int i, string text)
{
_textboxes[i].Text = text;
}
}
Of course, if you don't want to use a UserControl, you could just initialize a similar data structure in the main form instead, so that the controls can be accessed by index. But personally, I'd prefer the UserControl as it makes it easier to reuse and ensure consistency across all the groups of TextBox controls.

Data sorting ANSI data from an array to a list c#

I have a problem that I really cannot get my head around. I know how to sort data in general but this one is taxing me!
I have a list of values in an array. The values look like this:
[03;02HTransactions
[03;16HPost Transactions
[04:02HDividends
[04;16HPostDividends
[01:01H-----------------------------------------------------
[05:01H-----------------------------------------------------
[02:16HDate: Today
[02:02HTrades
So its essentially ANSI formatting from a terminal screen which i'm trying to re-construct into a list so that I can print it on our test logs so it at least looks vaguely readable.
So this is how it works within the first 6 characters: [XX:YY where XX is the row number and YY is the column number. The H doesn't matter its just formatting.
Here is what I've got so far:
List<string> rows = new List<string>();
for (int i = 0; i <= filteredLines.Count - 1; i++)
{
int rowIndex = Convert.ToInt32(filteredLines[i].Substring(1, 2));
Dictionary<int, string> columns = new Dictionary<int, string>();
foreach (string row in filteredLines)
{
int innerRowIndex = Convert.ToInt32(row.Substring(1, 2));
if (innerRowIndex == rowIndex)
{
int columnIndex = Convert.ToInt32(filteredLines[i].Substring(4, 2));
string value = filteredLines[i].Remove(0, 7);
columns.Add(columnIndex, value);
}
}
string columnConcatenated = "";
for (int j = 0; j <= columns.Count; j ++ )
{
columnConcatenated = columnConcatenated + columns[j];
}
rows.Add(columnConcatenated);
}
What I essentially want to do is to to build up the lines and sort them into a list based on the row number so it looks like:
--------------------------------------------
Trades Date: Today
Transactions Post Transactions
Dividends Post Dividends
--------------------------------------------
my example isn't 100% accurate as its hard to count the exact columns, but you get the idea. They just need to be on the same line in the correct order.
I cant help but feel i'm probably not going about this the best way. So is there an ideal way for me to achieve this?
Okay, I would implement it like so:
Create a simple POCO/class to represent a log entry with the properties row, column and text
Implement the IComparable interface, so these items can be sorted, on row # first and on column # second
Parse every log line and create a simple POCO Entry object for each
Use a simple List<Entry> and sort it afterwards
Use a StringBuilder to build up the final output
Loop over every Entry in the list, checking it's row # and perhaps entering some newlines for our StringBuilder if there are gaps
If we get an Entry with a row number which is the same as a previous one (which you can use a temp variable for), don't output a newline, but append the Entry.text to this line instead, at the column you want
You already have code to parse each line, extracting its row, column, and displayed text. If the lines of text are not sparse, you could represent this as basically a 2D dynamic array of characters that automatically pads itself out with spaces or empty lines, like so:
public class StringBuilderList : IList<string>
{
readonly List<StringBuilder> list = new List<StringBuilder>();
readonly char pad = ' ';
const char DefaultPad = ' ';
public StringBuilderList(char pad)
{
this.pad = pad;
}
public StringBuilderList() : this(DefaultPad) {}
public void SetString(int iLine, int iChar, string text)
{
list.EnsureCount(iLine + 1);
if (list[iLine] == null)
list[iLine] = new StringBuilder(iChar + text.Length);
var sb = list[iLine];
sb.OverwriteAt(iChar, text, pad);
}
#region IList<string> Members
public int IndexOf(string item)
{
for (int i = 0; i < Count; i++)
if (this[i] == item) // this is not memory-efficient.
return i;
return -1;
}
public void Insert(int index, string item)
{
var sb = new StringBuilder(item);
list.Insert(index, sb);
}
public void RemoveAt(int index)
{
list.RemoveAt(index);
}
public string this[int index]
{
get
{
// Hide the nulls from the caller!
var sb = list[index];
if (sb == null)
return string.Empty;
return sb.ToString();
}
set
{
if (string.IsNullOrEmpty(value))
{
if (list[index] != null)
list[index].Length = 0;
}
else if (list[index] == null)
{
list[index] = new StringBuilder(value);
}
else
{
list[index].Length = 0;
list[index].Append(value);
}
}
}
#endregion
#region ICollection<string> Members
public void Add(string item)
{
list.Add(new StringBuilder(item));
}
public void Clear()
{
list.Clear();
}
public bool Contains(string item)
{
return IndexOf(item) >= 0;
}
public void CopyTo(string[] array, int arrayIndex)
{
foreach (var str in this)
array[arrayIndex++] = str;
}
public int Count
{
get { return list.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(string item)
{
int index = IndexOf(item);
if (index < 0)
return false;
RemoveAt(index);
return true;
}
#endregion
#region IEnumerable<string> Members
public IEnumerator<string> GetEnumerator()
{
foreach (var sb in list)
yield return (sb == null ? string.Empty : sb.ToString());
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
SetString is the method you would use, it copies the string into the 2d array of characters, expanding it as required with empty lines and/or space characters.
And helper methods:
public static class ListHelper
{
public static void Resize<T>(this List<T> list, int count)
{
if (list == null || count < 0)
throw new ArgumentException();
int oldCount = list.Count;
if (count > oldCount)
{
list.Capacity = count;
for (int i = oldCount; i < count; i++)
list.Add(default(T));
}
else if (count < oldCount)
{
for (int i = oldCount-1; i >= count; i--)
list.RemoveAt(i);
}
}
public static void EnsureCount<T>(this List<T> list, int count)
{
if (list == null || count < 0)
throw new ArgumentException();
if (count > list.Count)
list.Resize(count);
}
}
public static class StringBuilderHelper
{
public static void OverwriteAt(this StringBuilder sb, int index, string text, char pad)
{
var textLen = text.Length;
if (textLen + index > sb.Length)
{
for (int i = sb.Length, newLen = textLen + index; i < newLen; i++)
{
sb.Append(pad);
}
}
for (int i = 0; i < textLen; i++)
{
sb[index + i] = text[i];
}
}
}
So a few people have some solutions though they seemed a bit over complicated for what I needed. I managed to resolve the issue myself using a pen and paper then trying it in visual studio. Here is what I did:
First I created a list by looking through the original array and I sorted and removed duplicates:
List<int> rowList = new List<int>();
for (int i = 0; i <= filteredLines.Count - 1; i++)
{
int rowIndex = Convert.ToInt32(filteredLines[i].Substring(1, 2));
rowList.Add(rowIndex);
}
rowList = rowList.Distinct().ToList<int>();
rowList.Sort();
Next I created a container for my final list that would hold the values, then I ran my sorting routine which makes use of a SortedList in order to ensure the columns are sorted before I concatenate them:
foreach (int listRow in rowList)
{
SortedList<int, string> columnList = new SortedList<int, string>();
foreach(string row in filteredLines)
{
int rowIndex = Convert.ToInt32(row.Substring(1, 2));
if(rowIndex==listRow)
{
int columnIndex = Convert.ToInt32(row.Substring(4, 2));
string value = row.Remove(0, 7);
if(columnList.ContainsKey(columnIndex))
{
columnList[columnIndex] = columnList[columnIndex] + value;
}
else
{
columnList.Add(columnIndex, value);
}
}
}
string concatenatedColumns = "";
foreach(string col in columnList.Values)
{
concatenatedColumns = concatenatedColumns + col;
}
parsedAndSortedRows.Add(concatenatedColumns);
}
It does the job fine and puts all the columns in order on the correct row as I wanted. Thanks for the help to everyone though it was through the different answers I helped come up with this solution.

Categories

Resources