Visible listview items in winforms listview? - c#

How can I get the visible items from a winforms listview? There doesn't seem to be a straightforward way, and I am reluctant to query items by control.clientrectangle or other hacks similar to the following:
https://stackoverflow.com/questions/372011/how-do-i-get-the-start-index-and-number-of-visible-items-in-a-listview)
When I say visible I mean the items are visible on the screen.

You can iterate from ListView.TopItem and check ListViewItem.Bounds property of each item whether it is located within the client area.
Better ListView Express is a freeware component that have also BottomItem property, so you can easily go through the visible items with a for loop (if both TopItem and BottomItem are not null):
for (int i = betterListView.TopItem.Index; i < betterListView.BottomItem.Index; i++)
{
// your code here
}
You can try this - it have the same interface as ListView and have many improvements over .NET ListView.

Sample code using GetItemAt
Looking at #Hans Passants comment I made a stab at actually creating code.
This code gets the top/bottom items. To get a Collection of visible items should be easy by out from Items where index is between index of top/bottom.
For me this worked much better than using Bounds, the bounds of the ListView appeared to have been higher then the visible part.
/// <summary>
/// Finds top/bottom visible items
/// </summary>
public static (ListViewItem, ListViewItem) GetTopBottomVisible(ListView listView)
{
ListViewItem topItem = listView.TopItem;
int lstTop = listView.Top;
int lstHeight = lstTop + listView.Height;
int lstBottom = lstHeight;
int step = lstHeight/2;
int x = listView.Left + listView.Width/2;
int y = lstTop + step;
ListViewItem bottomCandidate=null;
// iterate by interval halving
while ( step > 0 )
{
step /= 2; // halv interval
ListViewItem itm = listView.GetItemAt(x, y);
if ( itm == null )
{
// below last, move up
y -= step;
}
else if ( itm == bottomCandidate )
{
// Moving still in same item, stop here
break;
}
else
{
// above last, move down, storing candidate
bottomCandidate = itm;
y += step;
}
}
return (topItem, bottomCandidate);
}

If you are looking for a function that gives you only the visible item list, there is no such thing. You can go foreach item and check if its visible or not.
(If i understood your question right? Please give much clear explanation)

Related

How to add a specific number of labels to a label array?

I've been trying to add a few candles to an array so I can use that array for the rest of my code, using the candles' properties more easily. However it doesn't seem as my code is correct and I would love someone to help me with this. (Color != DarkGoldenrod differentiates the candles from the other labels in my project, all of which have the same color)
private Label[] CandlesToMatrix()
{
Label[] candles = new Label[7];
foreach (Control ctrl in this.Controls)
{
if ((ctrl is Label) && (ctrl.BackColor != Color.DarkGoldenrod))
{
for (int i = 0; i < 7; i++)
{
candles[i] = (Label)ctrl;
}
}
}
return candles;
}
The problem you are facing is that you assign each element in the array the control that matches the criteria.
The code runs like: Enumerate all controls and check if it is a label and not some specific color. If he finds one, he will fill up the whole array with a reference to that control. If the array was already filled by the previous match, it will be overwritten.
So you end up with an array filled with either a null or the last matching control.
I think you would like to have the array filled with 'unique' controls. So every time you find a match, you have to increase the index to write it to.
For example:
private Label[] CandlesToMatrix()
{
Label[] candles = new Label[7];
// declare a variable to keep hold of the index
int currentIndex = 0;
foreach (Control ctrl in this.Controls)
{
if ((ctrl is Label label) && (label.BackColor != Color.DarkGoldenrod))
{
// check if the currentIndex is within the array. Never read/write outside the array.
if(currentIndex == candles.Length)
break;
candles[currentIndex] = label;
currentIndex++;
}
}
}
I'll add another example......
C# has so much more to offer. This is a little old c programming style. Fixed size arrays etc. In C# you have also a List which uses the type you use between the < and >. For example. List<Label>.
Here is an example which uses a List.
private Label[] CandlesToMatrix()
{
List<Label> candles = new List<Label>();
// int currentIndex = 0; You don't need to keep track of the index. Just add it.
foreach (Control ctrl in this.Controls)
{
if ((ctrl is Label label) && (label.BackColor != Color.DarkGoldenrod))
{
candles.Add(label);
}
}
return candles.ToArray(); // if you still want the array as result.
}
and... you could also use Linq (which is a next step)
private Label[] CandlesToMatrix()
{
return this.Controls
// check the type.
.OfType<Label>()
// matches it the creteria?
.Where(label => ((ctrl is Label label) && (label.BackColor != Color.DarkGoldenrod))
// fillup an array with the results
.ToArray();
}

C# Call an object from concatenated text

I was trying to call multiple labels with multiple names from a for loop, but the thing is that i dont want to use the "foreach" to loop trough all the controls.
I want to make a direct reference to it, for example :
for(ai = 2; ai < 11 ; ai ++)
{
this.Controls("label" + ai).Text = "SomeRandomText";
}
How can i do this?
I already tried to find this question on the net, but all i find are answers with "foreach" loops.
Thanks!!
Assuming that your labels are named "lable2" through "label10", then you can do it like this:
for(int ai = 2; ai < 11 ; ai++)
{
this.Controls["label" + ai].Text = "SomeRandomText";
}
Here is a solution that is not dependent on the control's name so you are free to change the name of the label at any point in time without breaking your code.
foreach (var control in this.Controls)
{
if (control is Label)
{
int index;
if (control.Tag != null && int.TryParse(control.Tag.ToString(), out index) && index >= 2 && index < 11)
{
((Label)control).Text = "SomeRandomText";
}
}
}
Then, all you need to do is assign a value between 2 and 11 to each control's Tag property that you want updated. You can set this property through code or set the property in the designer.
You are also free to change the values of the Tag property as you see fit. Just make sure the index checks in the code line up with the tag values you choose!

Creating a sudoku. Should I use a while statement for this code?

I'm making a sudoku in Windows Form Application.
I have 81 textboxes and I have named them all textBox1a, textBox1b... textBox2a, textBox2b...
I want to make it so that if any of the textboxes, in any of the rows, is equal to any other textbox in the same row, then both will get the background color red while the textboxes are equal.
I tried using this code just for test:
private void textBox1a_TextChanged_1(object sender, EventArgs e)
{
while (textBox1a.Text == textBox1b.Text)
{
textBox1a.BackColor = System.Drawing.Color.Red;
textBox1b.BackColor = System.Drawing.Color.Red;
}
It didn't work, and I don't know where I should put all this code, I know I shouldn't have it in the textboxes.
Should I use a code similar to this or is it totally wrong?
You want to iterate over the collection of text boxes just once, comparing it to those that haven't yet been compared against. If you have your textboxes in an array (let's call it textBoxes), and know which one was just changed (e.g. from the textChanged handler), you could do:
void highlightDuplicates(int i) // i is the index of the box that was changed
{
int iVal = textBoxes[i].Text;
for (int j = 0; j < 82; j++)
{
// don't compare to self
if (i == j) return;
if (textBoxes[j].Text == iVal)
{
textBoxes[i].BackgroundColor = System.Drawing.Color.Red;
textBoxes[j].BackgroundColor = System.Drawing.Color.Red;
}
}
}
If you wanted to get fancier, you could put your data in something like: Dictionary<int, TextBox>, where the key is the value and the TextBox is a reference to the text box with that value. Then you can quickly test for duplicate values with Dictionary.Contains() and color the matching text box by getting its value.
I think your current code would result in an infinite loop. The textboxes' values can't change while you are still in the event handler, so that loop would never exit.
If all of your boxes are named according to one convention, you could do something like this. More than one input can use the same handler, so you can just assign this handler to all the boxes.
The following code is not tested and may contain errors
private void textBox_TextChanged(object sender, EventArgs e){
var thisBox = sender as TextBox;
//given name like "textBox1a"
var boxNumber = thisBox.Name.SubString(7,1);
var boxLetter = thisBox.Name.SubString(8,1);
//numbers (horizontal?)
for(int i = 1; i<=9; i++){
if(i.ToString() == boxNumber)
continue; //don't compare to self
var otherBox = Page.FindControl("textBox" + i + boxLetter) as TextBox;
if (otherBox.Text == thisBox.Text)
{
thisBox.BackColor = System.Drawing.Color.Red;
otherBox.BackColor = System.Drawing.Color.Red;
}
}
//letters (vertical?)
for(int i = 1; i<=9; i++){
var j = ConvertNumberToLetter(i); //up to you how to do this
if(j == boxLetter)
continue; //don't compare to self
var otherBox = Page.FindControl("textBox" + boxNumber + j) as TextBox;
if (otherBox.Text == thisBox.Text)
{
thisBox.BackColor = System.Drawing.Color.Red;
otherBox.BackColor = System.Drawing.Color.Red;
}
}
}
I believe you will be more effective if create an Array (or a List) of Integers and compare them in memory, against compare them in UI (User Interface).
For instance, you could:
1) Create an Array of 81 integers.
2) Everytime the user input a new number, you search for it in that Array. If found, set the textbox as RED, otherwise, add the new value to that array.
3) The ENTER event may be allocated fot the entire Textboxes (utilize the Handles keyword with all Textboxes; like handles Text1.enter, Text2.enter, Text3.enter ... and so forth)
Something like:
int[] NumbersByUser = new int[81];
Private Sub Textbox1.Enter(sender as object, e as EventArgs) handles Textbox1.Enter, Textbox2.Enter, Textbox3.enter ...
int UserEntry = Convert.ToInt32(Sender.text);
int ValorSelecionado = Array.Find(NumbersByUser, Z => (Z == UserEntry));
if (ValorSelecionado > 0) {
Sender.forecolor = Red;
}
else
{
NumbersByUser(Index) = UserEntry;
}
You should have a 2 dimensional array of numbers (could be one dimensional, but 2 makes more sense) let's assume its called Values. I suggest that you have each textbox have a incrementing number (starting top left, going right, then next row). Now you can do the following:
All TextBox Changed events can point to the same function. The function then takes the tag to figure out the position in the 2dim array. (X coordinate is TAG % 9 and Y coordinate is TAG / 9)
In the callback you can loop over the textboxes and colorize all boxes as you like. First do the "check row" loop (pseudo code)
var currentTextBox = ((TextBox)sender)
var x = ((int)currentTextBox.Tag) % 9
var y = ((int)currentTextBox.Tag) / 9
// First assign the current value to the backing store
Values[currentTextBox] = int.parse(currentTextBox.Text)
// assuming variable x holding the column and y holding the row of current box
// Array to hold the status of a number (is it already used?)
bool isUsed[9] = {false, false, ...}
for(int col = 0; col <= 9; i++)
{
// do not compare with self
if(col == x) continue;
isUsed[textBox] = true;
}
// now we have the status of all other boxes
if( isUsed[Values[x,y]] ) currentTextBox.Background = Red else currentTextBox.Background = Green
// now repeat the procedure for the column iterating the rows and for the blocks
I would suggest a dynamic approach to this. Consider each board item as a cell (this would be it's own class). The class would contain a numeric value and other properties that could be useful (i.e. a list of possible values).
You would then create 3 collections of the cells, these would be:
A collection of rows of 9 cells (for tracking each row)
A collection of columns of 9 cells (for tracking each column)
A collection of 3x3 cells
These collections would share references - each cell object would appear once in each collection. Each cell could also contain a reference to each of the 3 collections.
Now, when a cell value is changed, you can get references to each of the 3 collections and then apply a standard set of Sudoku logic against any of those collections.
You then have some display logic that can walk the boards of cells and output to the display (your View) your values.
Enjoy - this is a fun project.

Change text of a listview

I have a quick question about listView's and how check if a ListView (which contains null items) has a certain string?
Here is the code which add to sepcific items (under column 5 in the listView). It basically checks if that item appears in Google search or not. If it does, it'll write yes to that specific row, if not it'll leave it blank:
string google2 = http.get("https://www.google.com/search?q=" + textBox1.Text + "");
string[] embedid = getBetweenAll(vid, "type='text/html' href='http://www.youtube.com/watch?v=", "&feature=youtube_gdata'/>");
for (int i = 0; i < embedid.Length; i++)
{
if (google2.Contains(embedid[i]))
{
listView1.Items[i].SubItems.Add("Yes");
}
}
Now what I am trying to do is check if that certain column contains items that say Yes. If it does color them Green if not don't.
Here's the code for that:
if (i.SubItems[5].Text.Contains("Yes"))
{
labelContainsVideo.ForeColor = System.Drawing.Color.Green;
}
My issue is I keep getting an error that says InvalidArgument=Value of '5' is not valid for 'index'.
My hunch is that there are null items in column 5 which might be messing it up but I dont know.
Any idea on how to fix this?
Check the Item to see if the SubItem Collection has the correct Number of values.
i.e.
int threshold = 5;
foreach (ListViewItem item in listView1.Items)
{
if (item.SubItems.Count > threshold)
{
if (item.SubItems[5].Text.Contains("Yes"))
{
// Do your work here
}
}
}

ListView moving items

I have another problem with ListView :( Now I need to move items in group (up, down, to the beginning, to the end), but ListView is displaying moved items always at the end.
Here is sample code for moving item to the beginning:
if (1 == listView1.SelectedItems.Count)
{
ListViewItem item = listView1.SelectedItems[0];
ListViewGroup gp = item.Group;
int index;
index = item.Index;
if (index < listView1.Items.Count)
{
index = 0;
listView1.Items.Remove(item);
item.Group = gp;
listView1.Items.Insert(index, item);
}
}
I tried google to find some solution, and I found someone else (http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/838f90cd-33d8-4c81-9ed9-85220b511afe) who had same problem like me, but his solution is not working :(
I considered using ObjectListView but I have modified ListView witch now supports drag & drop with WinAmp effect, onScroll events, scrolling synchronization etc.. and I don't want to lose this stuff :(
Try this:
/// <summary>
/// Move the given item to the given index in the given group
/// </summary>
/// <remarks>The item and group must belong to the same ListView</remarks>
public void MoveToGroup(ListViewItem lvi, ListViewGroup group, int indexInGroup) {
group.ListView.BeginUpdate();
ListViewItem[] items = new ListViewItem[group.Items.Count + 1];
group.Items.CopyTo(items, 0);
Array.Copy(items, indexInGroup, items, indexInGroup + 1, group.Items.Count - indexInGroup);
items[indexInGroup] = lvi;
for (int i = 0; i < items.Length; i++)
items[i].Group = null;
for (int i = 0; i < items.Length; i++)
group.Items.Add(items[i]);
group.ListView.EndUpdate();
}
My automatic response to this question would be to check and see if it works if you say
listView1.Items.Count - 1
because the list is zero-indexed so the count is 1 greater than the last index)
Looking at the code, it has a few problems which make me curious. It looks like what may be the problem is the intial if statement. If there is only 1 item in the itemlist, then removing it and re-adding it will not do anything regardless of where you stick it. I believe you want <=
Also, you're setting the index to 0 right after getting it from the item you're interested in. If the code is supposed to move it to the top, Index=0 should be moved inside the if statement.
Not sure if those will solve the problem though...

Categories