I'm trying to write a small method to loop through and find a GridView Column by its Index, since it can change position based on what might be visible.
Here is what I have so far:
private int GetColumnIndexByName(GridView grid, string name)
{
foreach (DataColumn col in grid.Columns)
{
if (col.ColumnName.ToLower().Trim() == name.ToLower().Trim()) return col.Ordinal;
}
return -1;
}
In this case, DataColumn doesn't appear to be the right type to use, but I'm kind of lost as to what I should be doing here.
I can only use .NET 2.0 / 3.5. I can't use 4.0.
I figured it out, I needed to be using DataControlField and slightly different syntax.
The working version:
private int GetColumnIndexByName(GridView grid, string name)
{
foreach (DataControlField col in grid.Columns)
{
if (col.HeaderText.ToLower().Trim() == name.ToLower().Trim())
{
return grid.Columns.IndexOf(col);
}
}
return -1;
}
I prefer collection iteration but why bother with the overhead of foreach and grid.Columns.IndexOf call in this case? Just iterate through array with an index.
private int GetColumnIndexByName(GridView grid, string name)
{
for(int i = 0; i < grid.Columns.Count; i++)
{
if (grid.Columns[i].HeaderText.ToLower().Trim() == name.ToLower().Trim())
{
return i;
}
}
return -1;
}
Better solution which works for Datafield, SortExpression and headerText.
public static int GetBoundFieldIndexByName(this GridView gv,string name)
{
int index = 0;
bool found = false;
foreach (DataControlField c in gv.Columns)
{
if (c is BoundField)
{
BoundField field = (BoundField)c;
if (name == field.DataField ||
name == field.SortExpression ||
name == field.HeaderText)
{
found = true;
break;
}
}
index++;
}
return found ? index : -1;
}
//Get index of column by header text.
int GetColumnIndexByName(GridViewRow row, string headerText)
{
int columnIndex = 0;
foreach (DataControlFieldCell cell in row.Cells)
{
if(cell.ContainingField is TemplateField){
if(((TemplateField)cell.ContainingField).HeaderText.Equals(headerText))
{
break;
}
}
if(cell.ContainingField is BoundField){
if (((BoundField)cell.ContainingField).HeaderText.Equals(headerText))
{
break;
}
}
columnIndex++;
}
return columnIndex;
}
In case if you need a column itself and not just its index you can use some Linq magic:
DataControlField col=GridView1.Columns.Cast<DataControlField>().First(c => c.HeaderText == "Column_header")
Here's a VB version
Protected Function GetColumnIndexByHeaderText(grid As GridView, findHeader As String) As Integer
Dim i As Integer = 0
For i = 0 To grid.Columns.Count - 1
If grid.Columns(i).HeaderText.ToLower().Trim() = findHeader.ToLower().Trim() Then
Return i
End If
Next
Return -1
End Function
This way, works for me (.NET Gridview):
private int GetColumnIndexByName(GridView grid, string name)
{
for (int i = 0; i < grid.HeaderRow.Cells.Count; i++)
{
if (grid.HeaderRow.Cells[i].Text.ToLower().Trim() == name.ToLower().Trim())
{
return i;
}
}
return -1;
}
Related
Value
ID
A
A
A
B
B
C
Desired output
Value
ID
A
1
A
1
A
1
B
2
B
2
C
3
I need to create IDs based on grouping the value column. A single ID for all A's and then B's respectively.
Thanks in advance!
You could simply use a loop like:
for(int i = 0; i < dataTable.Rows.Count; i++){
switch(dataTable.Rows[i][0].ToString()){
case "A" :
dataTable.Rows[i][1] = 1;
break;
case "B" :
dataTable.Rows[i][1] = 2;
break;
case "C" :
dataTable.Rows[i][1] = 3;
break;
// other cases
}
}
here dt is your datatable. Use a loop like this:
int id = 1;
for (int i = 0; i < dt.Rows.Count; i++)
{
if (i == 0)
{
dt.Rows[i]["ID"] = id;
if (i != dt.Rows.Count && dt.Rows[i + 1]["Value"] != dt.Rows[i]["Value"])
{
id++;
}
}
else
{
if (dt.Rows[i - 1]["Value"] == dt.Rows[i]["Value"])
{
dt.Rows[i]["ID"] = id;
}
else
{
id = id + 1;
dt.Rows[i]["ID"] = id;
}
}
}
If the values might fall between A-Z then consider the following done in a form but can be done where ever you want.
public class Replacement
{
public string Value { get; set; }
public int Index { get; set; }
}
Form code
private void SetIdButton_Click(object sender, EventArgs e)
{
var dataTable = MockedDataTable();
var items = ReplacementData;
for (int index = 0; index < dataTable.Rows.Count; index++)
{
dataTable.Rows[index].SetField("ID",
items.FirstOrDefault(replacement =>
replacement.Value == dataTable.Rows[index].Field<string>("Value")).Index);
}
foreach (DataRow row in dataTable.Rows)
{
Console.WriteLine($"{string.Join(",", row.ItemArray)}");
}
}
private static Replacement[] ReplacementData
=> "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
.ToCharArray().Select((value, index) => new Replacement
{
Value = value.ToString(),
Index = index + 1
})
.ToArray();
private static DataTable MockedDataTable()
{
DataTable dataTable = new DataTable();
dataTable.Columns.Add("Value", typeof(string));
dataTable.Columns.Add("ID", typeof(int));
dataTable.Rows.Add("A");
dataTable.Rows.Add("A");
dataTable.Rows.Add("A");
dataTable.Rows.Add("B");
dataTable.Rows.Add("B");
dataTable.Rows.Add("C");
dataTable.Rows.Add("D");
return dataTable;
}
Output
A,1
A,1
A,1
B,2
B,2
C,3
D,4
You could also use a dictionary:
dictionary<string,int> record_dic = new dictionary<string,int>{{"A",1},{"B",2}};
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
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.
I want to do something only if selected dgvCells are in the same Column:
foreach (DataGridViewCell c in dgvC.SelectedCells)
if (c.ColumnIndex is the same) // how to say this ?
Seen no reply for sometime, here is my solution, I don't think it optimized enough, but I think it will do the job
int columnIndex = dgvC.SelectedCells[0].ColumnIndex;
bool sameCol = true;
for(int i=0;i<dgvC.SelectedCells.Count;i++)
{
if(dgvC.SelectedCells[i].ColumnIndex != columnIndex)
{
sameCol = false;
break;
}
}
if (sameCol)
{
MessageBox.Show("Same Column");
}
else
{
MessageBox.Show("Not same column");
}
EDIT:
You can also try:
int columnIndex = dgvC.SelectedCells[0].ColumnIndex;
if (dgvC.SelectedCells.Cast<DataGridViewCell>().Any(r => r.ColumnIndex != columnIndex))
{
//Not same
}
else
{
//Same
}
You can use GroupBy to make sure that cells are from the same column
if(dgvC.SelectedCells.Cast<DataGridViewCell>()
.GroupBy(c => c.ColumnIndex).Count() == 1)
{
foreach (DataGridViewCell c in dgvC.SelectedCells)
//your code
}
Something basic like this should work:
Boolean allCells = true;
int colIndex = dgvC.SelectedCells[0].ColumnIndex;
foreach (DataGridViewCell c in dgvC.SelectedCells)
{
if(c.ColumnIndex != colIndex)
{
allCells = false;
}
}
if(allCells)
{
//do stuff here
}
Try this one.
for (int i=0; i < dgvC.SelectedCells.Count; i++ )
{
int currentCellColumnIndex = dgvC.SelectedCells[i].ColumnIndex;
for (int j=i+1; j < dgvC.SelectedCells.Count-1; j++)
{
if(currentCellColumnIndex == dgvC.SelectedCells[j])
{
//Same column
//dgvC.SelectedCells[i] and all dgvC.SelectedCells[j] have same column
}
}
}
I have been saving into the ComboBox a value out of the selected column in datagridview with below code.
My question is:How can I prevent duplicate records when I save the values into the ComboBox? How can I do that?
Code:
int ColumnIndex = dgUretimListesi.CurrentCell.ColumnIndex;
CmbAra.Text = "";
for (int i = 0; i < dgUretimListesi.Rows.Count; i++)
{
CmbAra.Items.Add(dgUretimListesi.Rows.Cells[ColumnIndex].Value.ToString());
}
Please try this
private void dgvServerList_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
try
{
if (e.ColumnIndex == 1)
{
string id = dgvServerList[e.ColumnIndex, e.RowIndex].Value.ToString();
int duplicaterow = 0;
for (int row = 0; row < dgvServerList.Rows.Count; row++)
{
if (row != e.RowIndex && id == dgvServerList[e.ColumnIndex, row].Value.ToString())
{
duplicaterow = row + 1;
MessageBox.Show("Duplicate found in the row: " + duplicaterow);
this.dgvServerList[e.ColumnIndex, e.RowIndex].Value = "";
break;
}
}
}
}
catch
{
}
}
you could first transfer your datagridview items to a dictionary (which guarantees uniqueness) and then transfer that dictionary content to the combobox. or you could check for uniqueness yourself using a 'Contains' method on the combobox. you could even tie the dictionary to the combobox as a source for the combobox items.
Dictionary<string,bool> d = new Dictionary<string,bool>();
int ColumnIndex = dgUretimListesi.CurrentCell.ColumnIndex;
CmbAra.Text = "";
for (int i = 0; i < dgUretimListesi.Rows.Count; i++)
{
d[dgUretimListesi.Rows.Cells[ColumnIndex].Value.ToString()] = true;
}
CmbAra.Items.AddRange(d.Keys);
Use a set:
int ColumnIndex = dgUretimListesi.CurrentCell.ColumnIndex;
CmbAra.Text = "";
HashSet<string> set = new HashSet<string>();
for (int i = 0; i < dgUretimListesi.Rows.Count; i++)
{
string s = dgUretimListesi.Rows.Cells[ColumnIndex].Value.ToString();
if(!set.Contains(s)) {
CmbAra.Items.Add(s);
set.Add(s);
}
}
by using the following check and then determine to add or not
if(CmbAra.Items.Contains(dgUretimListesi.Rows.Cells[ColumnIndex].Value.ToString()))
You can use the following code part.
if(!(CmbAra.Items.Contains(dgUretimListesi.Rows.Cells[ColumnIndex].Value.ToString())))
{
CmbAra.Items.Add(dgUretimListesi.Rows.Cells[ColumnIndex].Value.ToString());
}
else
{
MessageBox.Show("Value Already exists , not added");
}