the problem is probably simple, the post is longer than I wished, but I've tried providing as much info and detail as possible.
I didn't write this GUI app, nor designed, however like most of us I've inherited it.
It had a (regular) ListView, actually the app has several ListView(s), not sure if that matters yet.
Because the # of items arriving to this one ListView (screen/form) can get very large 10K+ I decided to convert it to virtual list, however I'm experiencing some early problems.
One of the biggest problems, is that the items are being populated asynchronously by hitting a button on the form.
When they arrive (from service/network/database) the items are built into ListViewItem(s) and added to someListItems which is an ArrayList.
In my RetrieveVirtualItem method I need to handle both cases when the list is empty and when I already have something (after the button was hit) and that's when I hit the wall (no pun intended)
with the following line of code:
if ( someListItems.Count > e.ItemIndex )
It basically causes (no idea why) a call to Dispose method on the main form which results in the entire application crashing hard. BUT!!, it only happens when I click on the form and list. If the form is just loaded and populated it is fine..the second you left click on the mouse, BOOM!
It took my couple of hours to figure out that the line above was the culprit, as the call stack wasn't very apparent to point that out, and another minute to find out that e.ItemIndex is the culprit. But WHY??? I
n msdn examples they access e.ItemIndex to perform tests and it seems fine.
The Virtual Mode is set in the constructor of the form:
myListView.VirtualMode = true;
VirtualListSize is set right after data arrives asynchronously:
myListView.VirtualListSize = someArrayList.Count;
This is my RetrieveVirtualItem implementation:
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
// someListItems is an ArrayList that is created when the object/class loads..and populated with ListViewItems.
// i.e. private ArrayList someListItems = new ArrayList();
// it is populated asynchronously by hitting a button on the form, hence it's empty when the form loads..
if ( someListItems.Count <= 0 )
{
e.Item = new ListViewItem( "" );
e.Item.SubItems.Add( "" );
e.Item.SubItems.Add( "" );
}
else
{
// the of code below is the problem, and more specifically - e.ItemIndex causes somehow to call Dispose on the main form..
// the reason I have this code is because if I take it out, all items will show up, no problem, but it will crash when I try to scroll down..
// with message like this:
// Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index
if ( someListItems.Count > e.ItemIndex )
{
// took out my code out to eliminate possibility that it's my code. :)
int x = e.ItemIndex * e.ItemIndex;
e.Item = new ListViewItem( x.ToString() );
// but I had something like that just for a test:
// ListViewItem item = ( ListViewItem )someListItems[e.ItemIndex];
// e.Item = item;
// remember that someListItems already has ListViewItems
}
}
}
The method that gets called asynchronously, creates ListViewItems and populates someListItems looks something like that:
private void ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
{
//Im only showing more essential code..
SomeArrayList.Items.Clear();
myListView.VirtualListSize = ar.Count;
foreach ( SomeObject o in ar )
{
ListViewItem lvi = new ListViewItem( SomeObject.somePropertyID, 0 );
// I've tried changing the above line to: lvi = new ListViewItem( SomeObject.somePropertyID, 0 ); // and having the ListViewItem lvi on the class level. i.e private ListViewItem lvi
// didn't help.. :(
lvi.SubItems.Add( o.someProperty1 );
lvi.SubItems.Add( o.someProperty2 );
// there's quite few of these subitems..2 is enough for this example...
}
// the orignal code, before I changed it to virtual list was adding the items somewhere here..after finished looping, now I'm just trying to reuse that array of ListViewItems.
}
There's also another problem that the items don't really show up at all unless I take out the:
if ( someListItems.Count > e.ItemIndex )
but then I experience the index of out of range issue when I try scrolling.
UPDATE:
I've noticed that if I set the size of virtual list, only after the loop is finished and therefore it is zero (0) at the beginning (I can always reset it to zero), then everything works and don't need to check for size, all I have to do is this:
After the loop in: private void ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
this.myListView.VirtualListSize = someListItems.Count;
which I would like to thank for Hans Passant for noticing the discrepancy.
So this is the complete, for now (I'm sure that I'll add some code or change as I would like to add some caching, but at least I have something...
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
e.Item = ( ListViewItem )someListItems[e.ItemIndex];
}
The only thing I'm not sure what Hans Passant mentioned is this: "it really isn't okay for this event handler to never allocate a ListViewItem. " which I'm not sure if I understand, because the ListViewItems are allocated and inserted into someListItems array. I do have a try catch around, and I did before as well.
Also, I was thinking and I would appreciate someone's input on this idea:
Create a separate object that would hold all the properies of SomeObject or insert SomeObject(s) into the List and create new ListViewItems as required?
e.g:
private void blah_RetrieveVirtualItem( object sender, RetrieveVirtualItemEventArgs e )
{
// that list would be build sometime during the loop iteration in
// (I'm using the original method name mentioned way above in this post)
// ExampleMethod_That_PopulatesSomeArrayList(ArrayList ar)
SomeObject o = listOfObjects[e.ItemIndex];
e.Item = new ListViewItem();
e.Item.SubItems.Add(o.prop1);
e.Item.SubItems.Add(o.prop2);
e.Item.SubItems.Add(o.prop3);
}
To answer this question. The virtual list was crashing because the VirtualListSize wasn't being set correctly.
Basically, to help others here, if you have a virtual list, always make sure that the VirtualListSize corresponds to the actual number of items you're trying to show. If not, all hell breaks loose. If you do update, remove, add, anything, you need to reset VirtualListSize to the correct number.
I ended up deriving from ListView and storing my listviewitems in an array.
Related
While I was trying to implement an infinite scroll page I've encountered a strange behaviour with the CurrentItemChanged event of Xamarin.Forms.CarouselView.
While the user scrolls, new items are added to the ItemSource and old items are removed (for low memory consumption).
After I encountered the strange behaviour I've debugged and narrowed down the problem.
So here are the steps to replicate the situation.
Create a CarouselView.
Create a ObservableCollection<T> in the code behind and assign it to the ItemSource.
Create a method and subscribe it to the CurrentItemChanged event of the CarouselView. This method must at some point remove an element from the ItemSource that has an index between 0 and the index of the CurrentItem.
Now deploy the app and swipe the CarouselView once. This will result in an endless loop of scrolls that will keep going untill all of the items are removed from the ItemSource.
The method from step 3 must look like below.
bool FirstTime = true;
private void StateChanged(object s, EventArgs e)
{
// Pass the first call which is made right after the Carousel is initialized.
if (FirstTime) { FirstTime = false; return; }
var currentItem = (Model)Carousel.CurrentItem; // For debug.
var index = Models.IndexOf(currentItem); // Same.
// Step 3's requirement
Models.RemoveAt(0);
}
When you instead for example add a button to the page and assign the method you've created at the step 3 to it's Clicked event, and continue with the 4th step and manually press button after each scroll the endless loop won't occur.
I don't know if this is a feature or a bug but this certainly was unexpected, at least for me. I would love to figure out how to overcome this problem and learn why it works like this.
Note: I'm aware that removing the current item will cause such problem but the described behaviour occurs either ways. Also CarouselView.CurrentItem is updated before the CurrentItemChanged event is fired.
This will result in an endless loop of scrolls that will keep going untill all of the items are removed from the ItemSource.
This is because ObservableCollection has CollectionChanged event which will be called when the data collection is changed. When you remove the first item, the index is refershed and the event will also be triggered.
For this function, you could detect if the current item is the last one to update the data collection. Check the code:
public partial class Page1 : ContentPage
{
CustomViewModel viewModel = new CustomViewModel();
ObservableCollection<CustomModel> collection;
public Page1()
{
InitializeComponent();
BindingContext = viewModel;
collection = viewmodel.DataCollection;
}
private void CarouselView_CurrentItemChanged(object sender, CurrentItemChangedEventArgs e)
{
var item = e.CurrentItem as CustomModel;
var index = collection.IndexOf(item);
if (collection.Count == (index + 1))
{
collection.RemoveAt(0);
collection.Add(new CustomModel { ... });
}
}
}
Good day all. I am trying to accomplish the following:
In a C# WinForm I am having a ComboBox.
In a local data-base I have some "groups" that after execution become folder in "D://" ( they are five )
After that in all the folders I have some files ( the number varies )
I do not know how to populate the ComboBox with the names of those files, and after that when pressing a button I need to interact with the name selected in the ComboBox.
I have absolutely no idea on how to do that. I do not beg for any code ( altho it will be well received ) I just want the guideline ( do "this" first they you can do "that" and at the end you do "that" ) and I will do all the rest. It is just I can not figure that out. Thank you all !
First get the names of the files that is something like this:
string[] files=Directory.GetFiles("//path");
Now you have an array of all file names in the specific folder given above. Now take this string and populate it to the combo box that is something like this.
foreach(string file in files){
comboBox1.Items.add(file);
}
After that you have to create the event behind the combo box. If you drag-drooped combo box, then you can make its event by going to properties. Then code something like this behind the item select event behind combo box.
protected void combobox(bla bla)
{
if(comboBox1.SelectedItem == "An item")
//Do whatever
//it maybe selectedItem or selectedText or something like this
}
I code roughly so it may contain some errors.
Based on the help given I have done:
public string seltest = null;
string group1 = GroupsDBForm.gone;
string[] tests1 =
Directory.GetFiles("D:\\Riddler\\groups\\" + group1).Select(path => Path.GetFileName(path)).ToArray();
foreach (string t1 in tests1)
{
test_list.Items.Add(group1+"\\"+t1);
}
private void begin_test_btn_Click(object sender, EventArgs e)
{
seltest = "D:\\Riddler\\groups\\" + test_list.Text;
Do_Test_Form DoTest = new Do_Test_Form();
DoTest.ShowPath = seltest;
DoTest.MdiParent = this.ParentForm;
DoTest.Show();
}
( Those are the parts of the project connected to the issue, and because they are connected to other parts is might be lessunderstandeble what are the other names mentioned )
I know it is far from the best code but it works. I post it if this help another person with a close to this issue !
Thank you again Jamil!
I have some strange behaviour I can't explain:
I have a List of Ellipsis in a WPF-Canvas, which I'd like to make blink.
For performances purpose, I add newly created stars to the Canvas (which holds more objects) and to a local list. This means I can work just with the local List and dont have to recheck the Children-Collection every time.
My code is looking like this:
private const Int32 STAR_COUNT = 500;
private readonly double STAR_MOVE_RATE = GameObject.OBJECTS_MOVE_RATE/2;
private readonly Timer _blinkingTimer = new Timer();
private readonly Canvas _gameField;
private readonly List<Ellipse> _stars = new List<Ellipse>();
public StarHandler(Canvas gameField)
{
_gameField = gameField;
_blinkingTimer.Interval = 70;
_blinkingTimer.Elapsed += _blinkingTimer_Elapsed;
for (int i = 0; i < STAR_COUNT; i++)
AddSetStar(StarFactory.CreateStar());
_blinkingTimer.Start();
}
private void AddSetStar(Ellipse star)
{
Canvas.SetTop(star, GameObject.Random.Next(GameObject.LEVEL_HEIGHT));
Canvas.SetLeft(star, GameObject.Random.Next(GameObject.LEVEL_WIDTH));
Panel.SetZIndex(star, 0);
_gameField.Children.Add(star);
_stars.Add(star);
}
private void _blinkingTimer_Elapsed(object sender, ElapsedEventArgs e)
{
ThreadingHelper.ThreadingAction(() =>
{
Ellipse star = _stars.ElementAt(GameObject.Random.Next(STAR_COUNT));
_gameField.Children.Remove(star);
_stars.Remove(star);
var newStar = StarFactory.CreateStar();
AddSetStar(newStar);
});
}
The StarFactory just creates such an Ellipse and returns it.
What I dont get: The
_gameField.Children.Remove(star);
works only for the Stars I add in the Constructor. The Stars I add on the Timer are not found on the Children-List.
I really have no idea, how this Elements can not be found since I add newly created Stars everytime on both lists.
P.S.: The ThreadingAction is just a helper to work on the UI-Thread:
internal static void ThreadingAction(Action action)
{
if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
action();
else
Application.Current.Dispatcher.Invoke(action);
}
Edit: The reason why I'm holding two lists is this:
If I want to work with the List, I use:
_stars.ForEach(f =>
Without this, I'd have to
_gameField.Children.OfType<Ellipse>().ToList().ForEach(f => )
This is 2 Enumerations, one for OfType and one for ToList.
And what If I add other ellipses, which are not Stars?
About the Construction of the Stars: I do this only on two positions: In the Timer-Elapsed Event and in the Constructor. Does the Children-List not point to the objects then?
So the Stars are the same references, since I add the same reference everytime in both Lists.
You have a number of problems with your code. I shall attempt to address each in turn. First, you said that you
add newly created stars to the Canvas (which holds more objects) and to a local list. This means I can work just with the local List and dont have to recheck the Children-Collection every time
However, there is no benefit to doing this. The Children collection points to a certain location in memory and your collection points to another... virtually no difference.
What I dont get: The Children.Remove(star) works only for the Stars I add in the Constructor
That is because you can only remove an item from the collection if that exact item is in the collection. Therefore, you cannot generate a new object with certain property values and expect that to equal another object with identical property values... they are different objects in memory. So, to get around this, you can use LinQ to do something like this:
_gameField.Children.Remove(_gameField.Children[0]);
However, I believe that your final error is that you shouldn't be removing and re-adding these objects into the Children collection as you are anyway. Instead, it makes more sense to leave the objects in the Children collection and merely change their Visiblity values from Visible to Hidden and back repeatedly... perhaps something like this:
foreach (UIElement element in Canvas.Children)
{
element.Visibility = element.Visibility == Visibility.Hidden ?
Visibility.Visible : Visibility.Hidden;
}
I have some items in a CheckedListBox, I want to disable the CheckBox of first item in it.
i.e. I want to disable the first item in the CheckedListBox, because I want to tell the user visually that option is not available.
Combining 2 of the above partial answers worked great for me.
Add your items to the list with:
myCheckedListBox.Items.Add(myItem, myState);
Where myState is CheckState.Indeterminate for items that should be disabled.
Then add an event handler to keep those items from being changed:
myCheckedListBox.ItemCheck += (s, e) => { if (e.CurrentValue == CheckState.Indeterminate) e.NewValue = CheckState.Indeterminate; };
This does not allow you to use 'Indeterminate' in this list for its normal purpose but it does give a look very similar to what one would expect for a disabled item and it provides the correct behavior!
Though this post is pretty old, the last added answer has been submitted in April this year,
and I hope this will help someone.
I was after something similar : a checked list box that behaves like
a lot of installers, which offer a list of options where some features are required and
thus are both checked and disabled.
Thanks to this post (Can I use a DrawItem event handler with a CheckedListBox?)
I managed to do that, subclassing a CheckedListBox control.
As the OP in the linked post states, in the CheckedListBox control the OnDrawItem event is never fired,
so subclassing is necessary.
It's very basic, but it works.
This is what it looks like (the CheckBox above is for comparison) :
NOTE: the disabled item is really disabled : clicking on it has no effects whatsoever (as far as I can tell).
And this is the code :
public class CheckedListBoxDisabledItems : CheckedListBox {
private List<string> _checkedAndDisabledItems = new List<string>();
private List<int> _checkedAndDisabledIndexes = new List<int>();
public void CheckAndDisable(string item) {
_checkedAndDisabledItems.Add(item);
this.Refresh();
}
public void CheckAndDisable(int index) {
_checkedAndDisabledIndexes.Add(index);
this.Refresh();
}
protected override void OnDrawItem(DrawItemEventArgs e) {
string s = Items[e.Index].ToString();
if (_checkedAndDisabledItems.Contains(s) || _checkedAndDisabledIndexes.Contains(e.Index)) {
System.Windows.Forms.VisualStyles.CheckBoxState state = System.Windows.Forms.VisualStyles.CheckBoxState.CheckedDisabled;
Size glyphSize = CheckBoxRenderer.GetGlyphSize(e.Graphics, state);
CheckBoxRenderer.DrawCheckBox(
e.Graphics,
new Point(e.Bounds.X + 1, e.Bounds.Y + 1), // add one pixel to align the check gliph properly
new Rectangle(
new Point(e.Bounds.X + glyphSize.Width + 3, e.Bounds.Y), // add three pixels to align text properly
new Size(e.Bounds.Width - glyphSize.Width, e.Bounds.Height)),
s,
this.Font,
TextFormatFlags.Left, // text is centered by default
false,
state);
}
else {
base.OnDrawItem(e);
}
}
public void ClearDisabledItems() {
_checkedAndDisabledIndexes.Clear();
_checkedAndDisabledItems.Clear();
this.Refresh();
}
}
Use it like this:
checkedListBox.Items.Add("Larry");
checkedListBox.Items.Add("Curly");
checkedListBox.Items.Add("Moe");
// these lines are equivalent
checkedListBox.CheckAndDisable("Larry");
checkedListBox.CheckAndDisable(0);
Hope this can help someone.
Disabling items isn't a great idea, the user will have no good feedback that click the check box won't have any effect. You cannot use custom drawing to make it obvious. Best thing to do is to simply omit the item.
You can however easily defeat the user with the ItemCheck event:
private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e) {
if (e.Index == 0) e.NewValue = e.CurrentValue;
}
To disable any particular item use following:
checkedListBox1.SetItemCheckState(0, CheckState.Indeterminate);
SetItemCheckState takes index of item and CheckState Enum
Indeterminate is used to show shaded appearance
I know it has been a while, but I found this in my search for a list box and thought I would add it to the discussion.
If you have a listbox and want to disable all of the checkboxes so they cannot be clicked, but not disable the control so the user can still scroll etc. you can do this:
listbox.SelectionMode = SelectionMode.None
The CheckedListBox will not work in this way. CheckedListBox.Items is a collection of strings so they cannot be "disabled" as such.
Here are some discussions about possible solutions that might help you: here and here.
This works for me:
checkedListBox1.SelectionMode = SelectionMode.None;
Which means no items can be selected
None: No items can be selected.
For more info, you can check it here: SelectionMode Enumeration.
The solution is to use the event ItemChecking:
_myCheckedListBox.ItemChecking += (s, e) => e.Cancel = true;
This will cancel all the checking on every item, but you can always do more refined solution but testing the current .SelectedItem
Here's how I did it in a helpdesk application I wrote:
First, I made it so the check box was greyed out as I added it to the list during form load:
private void frmMain_Load(object sender, EventArgs e)
{
List<string> grpList = new List<string>();
ADSI objADSI = new ADSI();
grpList = objADSI.fetchGroups();
foreach (string group in grpList)
{
if (group == "SpecificGroupName")
{
chkLst.Items.Add(group, CheckState.Indeterminate);
}
else
{
chkLst.Items.Add(group);
}
}
Then I used an event so that when clicked it ensures it stays clicked:
private void chkLst_SelectedIndexChanged(object sender, EventArgs e)
{
if (chkLst.SelectedItem.ToString() == "SpecificGroupName")
{
chkLst.SetItemCheckState(chkLst.SelectedIndex, CheckState.Indeterminate);
}
}
The idea here is that on my form it's set so that the box checks on item click/select. This way I could kill two birds with one stone. I could keep this event from causing problems when the item is first checked and added during form load. Plus making it check on select allows me to use this event instead of the item checked event. Ultimately the idea is to keep it from messing up during the load.
You'll also notice that it doesn't matter what the index number is, that variable is unknown because in my app it's grabbing a list of groups from AD that exist in a specific OU.
As to whether this is a good idea or not, that's dependent on the situation. I have another app where the item to disable is dependent on another setting. In this app I just want the helpdesk to see that this group is required so they don't go removing them from it.
Try Below Code:
Private Sub CheckedListBox1_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles CheckedListBox1.MouseUp
If (Condition) Then
Me.CheckedListBox1.SelectedIndex = -1
End If
End Sub
I think an alternative solution, is using Telerik components.
A RadListControl can give you that option:
I had a regular Forms.ListView and converted it to Virtual list. Implemented the RetrieveVirtualItem and everything was working fine.
Then I decided that I'll add cache and eventually I'll need sorting and who knows what else. Since I've inherited the code and it was already somewhat messy, I decided to yank my changes and move them to a separate class, namely: derive from ListView, e.g. class MyOwnListView : ListView
so I've moved it and also added CacheVirtualItems.
After implementing both of these methods, I replaced:
private System.Windows.Forms.ListView someListView;
with
private MyOwnListView someListView;
on the main form.
So far so good..it runs, doesn't crash, however (and I only have about 60 items for now..) when I move the scroll bar, a lot of them don't repaint, so you'll see empty white rows, and sometimes it will repaint/show after clicking on that row. I also get partially displayed and drawn row, such as the top of the row won't be displaying in its entirety.
I'm not sure what the problem is, I tried adding DoubleBuffered=true;
I also added to my constructor the following: (per some suggestion I've found somewhere here and/or googling..)
SetStyle( ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true );
SetStyle( ControlStyles.EnableNotifyMessage, true );
and this method:
protected override void OnNotifyMessage( Message m )
{
//Filter out the WM_ERASEBKGND message
if ( m.Msg != 0x14 )
{
base.OnNotifyMessage( m );
}
}
My code overall is very similar to this:..just to give you an idea:
public class MyListView: ListView
{
private ListViewItem[] cache;
private int firstItem;
public MyListView()
{
SetStyle( ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true );
SetStyle( ControlStyles.EnableNotifyMessage, true );
RetrieveVirtualItem += new RetrieveVirtualItemEventHandler( xxx_RetrieveVirtualItem );
CacheVirtualItems += new CacheVirtualItemsEventHandler( xxx_CacheVirtualItems );
}
private void xxx_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
if (cache != null && e.ItemIndex >= firstItem && e.ItemIndex < firstItem + cache.Length)
e.Item = cache[e.ItemIndex - firstItem];
else
e.Item = GetItem(e.ItemIndex);
}
private void xxx_CacheVirtualItems(object sender, CacheVirtualItemsEventArgs e)
{
if (cache != null && e.StartIndex >= firstItem && e.EndIndex <= firstItem + cache.Length)
return;
firstItem = e.StartIndex;
int length = e.EndIndex - e.StartIndex + 1;
cache = new ListViewItem[length];
for (int i = 0; i < cache.Length; i++)
cache[i] = GetItem(firstItem + i);
}
Now, GetItem, basically accesses a List<someobject>, gets the object out of list, and based on that
it creates new ListViewItem and returns it.
}
EDIT: I've added some debugging code to the xxx_CacheVirtualItems. And it appears that everytime I scroll it comes back that the item was not found and is adding it again to the cache. Not sure why yet. I would expect that after the first scroll all the way down it would keep them inside the cache. I'm still looking.
I also tried adding:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
and tried //Application.SetCompatibleTextRenderingDefault(true);
That application didn't have these at all..these two lines did something interesting. SetCompatibleTextRenderingDefault with false, did not scroll at all, while SetCompatibleTextRenderingDefault with true did scroll, but only down and once I've reach the bottom it stopped. but the screen didn't have any painting/refresh problems..
OK Folks, I figured it out. It's very weird, but
I inadvertently changed some code, and didn't think it was a big deal, let me paraphrase it, so in my GetItem(index); code
What I had was:
ListViewItem v = new ListViewItem(....,0);
v.SubItems.Add(...);
v.SubItems.Add(...);
v.SubItems.Add(...);
......
...
well, I wanted to be "elegant" I've changed that to:
ListViewItem v = new ListViewItem(....,0);
v.SubItems.AddRange( new string[] { prop1, prop2, prop3, , , ,....});
And that was the issue, I just changed it back and magically it started to work.