Sorry the title isn't really describing the issue, but it's a very weird one.
To make sure I wasn't doing any idiotic mistakes, I use breakpoints to track everything that was happening..
Basically, I have this code in a class which inherits from ObservableCollection<T>:
var n = new MyClass();
int startIndex = 0; // parameter
int length = 2; // parameter
for (int i = 0; i < length; i++)
{
n.Text += this[startIndex].Text;
this.RemoveAt(startIndex);
}
this.Insert(n);
When executing the code, my collection has 3 items ; the loop goes like this:
n.Text += "some string successfully gotten from this[startIndex]"
this.RemoveAt(startIndex)
n.Text += "some other string successfully gotten from this[startIndex]"
Exception: IndexOutOfRange.
I'm successfully getting the item, and yet there is an error when I'm trying to delete it. I'm lost here.
Any help would be very appreciated!
Thank you in advance.
EDIT 1
I've tried this, and had the same result.
var toRemove = this.Skip(startIndex).Take(length).ToList();
foreach (var b in toRemove)
{
this.Remove(b);
n.Text += b.Text;
}
Once again, I have an IndexOutOfRange Exception when Removing an item.
Whilst debugging, my Collection has 2 items, and RemoveAt(0) still throws this Exception.
EDIT 2
I tried to manually call OnCollectionChanged when modifying this.Items. The IndexOutOfRange Exception is triggered when calling OnCollectionChanged, but not when removing the item from this.Items.
for (int i = 0; i < length; i++)
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, Items[startIndex], startIndex));
Items.RemoveAt(startIndex);
}
After calling this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)), I also have a problem.
Looks like the whole problem comes from the ListBox. I'll try playing with the Bindings, and other things, and report.
I guess the problem is about you´re referecing a removed index here:
var toRemove = this.Skip(startIndex).Take(length).ToList();
foreach (var b in toRemove)
{
this.Remove(b); <<< removed
n.Text += b.Text; <<< referencing the removed
}
This way, obviously you have the error described. Invert the order from n.text and the remove.
I wouldn't recommend to write this.RemoveAt(startIndex); in the loop, sometimes it's not ok when observable collection enumeration changes inside the loop, so I would get it out of the loop definitely
Seems like you've realized your own realization of Insert for Obs.Coll. maybe the issue is there
UPD
I think your problem in bad design. you said your class inherits from observable collection, what is this then ? try better design and define method outside of collection class, as it pretends that your collection is 3 elems at least ALL THE TIME
UPD 2
Design is still ugly, but in case you want to stick with it, here what I did and it worked :
Your collection definition (NOT RECOMMEND DOING IT THIS WAY)
public class MyObs : ObservableCollection<MyClass> {
public void Fun() {
var n = new MyClass();
int startIndex = 0; // parameter
int length = 2; // parameter
for (int i = 0; i < length; i++) {
n.Text += this [startIndex].Text;
this.RemoveAt(startIndex);
}
this.Insert(0,n); // PAY ATTENTION THAT I INSERT AT 0 !
}
}
public class MyClass {
public string Text { get; set; }
public override string ToString() {
return Text;
}
}
Then your XAML:
<ListBox ItemsSource="{Binding MyCollection}"/>
Then your declaration code:
public MyObs MyCollection { get; set; }
Then initialization and processing:
MyCollection = new MyObs();
MyCollection.Add(new MyClass() {Text = "Item 1"});
MyCollection.Add(new MyClass() { Text = "Item 2" });
MyCollection.Add(new MyClass() { Text = "Item 3" });
MyCollection.Fun();
seems like your problem in your Insert
I found the solution. I feel bad.
Basically, SelectionChanged was triggered everytime the ObservableCollection changed, trying to do some stuff and encountering an error. The thing is, nothing in the Stacktrace led to that idea.
Sorry for wasting your time (considering how the comments / answers were good), and keep going.
Related
Consider this simple example:
MainWindow.xaml
<Window x:Class="WPF_Sandbox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
x:Name="ThisControl">
<StackPanel>
<ComboBox ItemsSource="{Binding Collection, ElementName=ThisControl}" SelectedItem="a" />
<Button x:Name="SortButton">Sort</Button>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace WPF_Sandbox
{
public partial class MainWindow
{
public ObservableCollection<string> Collection { get; } = new ObservableCollection<string>(new [] { "b", "a", "c" });
public MainWindow()
{
InitializeComponent();
SortButton.Click += (s, e) => Sort(Collection);
}
public static void Sort<T>(ObservableCollection<T> collection)
{
var sortableList = new List<T>(collection);
sortableList.Sort();
for (var i = 0; i < sortableList.Count; i++)
collection.Move(collection.IndexOf(sortableList[i]), i);
}
}
}
When starting the program, a is selected. On pressing Sort the selection doesn't change but the list gets sorted (still as expected).
If you a) press Sort again or b) select b or c before sorting, the ComboBox loses its selection and SelectedItem becomes null.
I pinpointed the issue down to the ObservableCollection.Move method. It appears that whenever you call Move(i, i) (so you do not actually move anything) with i being the SelectedItem, the selection goes to hell.
I'm not looking for a solution. Obvious workaround would be to not sort the ObservableCollection at all and use a CollectionViewSource or adjusting the Sort method to only call Move when the two indices actually differ.
The question I have is, why is this happening in the first place? There is no indication in documentation for the Move method that you must not pass the same parameter twice. Also there is no hint why this would not work in the documentation for the CollectionChanged event or the CollectionChangedEventArgs class. Is this a bug in WPF?
I believe this to be a bug in the implementation of the ItemControl's event handling. Take a look here:
case NotifyCollectionChangedAction.Move:
// items between New and Old have moved. The direction and
// exact endpoints depends on whether New comes before Old.
int left, right, delta;
if (e.OldStartingIndex < e.NewStartingIndex)
{
left = e.OldStartingIndex + 1;
right = e.NewStartingIndex;
delta = -1;
}
else
{
left = e.NewStartingIndex;
right = e.OldStartingIndex - 1;
delta = 1;
}
foreach (ItemInfo info in list)
{
int index = info.Index;
if (index == e.OldStartingIndex)
{
info.Index = e.NewStartingIndex;
}
else if (left <= index && index <= right)
{
info.Index = index + delta;
}
}
break;
Source
The if statement does not seem to expect e.OldStartingIndex and e.NewStartingIndex to be of the same value which results in delta being 1 which then causes some unintended index manipulation inside of the foreach loop. I'm surprised it "only" deselects the item and not completely ruins the whole collection.
I have a general question, concerning performance and best practice.
When working with a List (or any other datatype) from a different Class, which is better practice? Copying it at the beginning, working with the local and then re-copying it to the original, or always access the original?
An Example:
access the original:
public class A
{
public static List<int> list = new List<int>();
}
public class B
{
public static void insertString(int i)
{
// insert at right place
int count = A.list.Count;
if (count == 0)
{
A.list.Add(i);
}
else
{
for (int j = 0; j < count; j++)
{
if (A.list[j] >= i)
{
A.list.Insert(j, i);
break;
}
if (j == count - 1)
{
A.list.Add(i);
}
}
}
}
}
As you see I access the original List A.list several times. Here the alternative:
Copying:
public class A
{
public static List<int> list = new List<int>();
}
public class B
{
public static void insertString(int i)
{
List<int> localList = A.list;
// insert at right place
int count = localList.Count;
if (count == 0)
{
localList.Add(i);
}
else
{
for (int j = 0; j < count; j++)
{
if (localList[j] >= i)
{
localList.Insert(j, i);
break;
}
if (j == count - 1)
{
localList.Add(i);
}
}
}
A.list = localList;
}
}
Here I access the the list in the other class only twice (getting it at the beginning and setting it at the end). Which would be better.
Please note that this is a general question and that the algorithm is only an example.
I won't bother thinking about performance here and instead focus on best practice:
Giving out the whole List violates encapsulation. B can modify the List and all its elements without A noticing (This is not a problem if A never uses the List itself but then A wouldn't even need to store it).
A simple example: A creates the List and immediately adds one element. Subsequently, A never bothers to check List.Count, because it knows that the List cannot be empty. Now B comes along and empties the List...
So any time B is changed, you need to also check A to see if all the assumptions of A are still correct. This is enough of a headache if you have full control over the code. If another programmer uses your class A, he may do something unexpected with the List and never check if that's ok.
Solution(s):
If B only needs to iterate over the elements, write an IEnumerable accessor. If B mustn't modify the elements, make the accessor deliver copies.
If B needs to modify the List (add/remove elements), either give B a copy of the List (containing copies of the elements if they needn't be modified) and accept a new List from B or use an accessor as before and implement the necessary List operations. In both cases, A will know if B modifies the List and can react accordingly.
Example:
class A
{
private List<ItemType> internalList;
public IEnumerable<ItemType> Items()
{
foreach (var item in internalList)
yield return item;
// or maybe item.Copy();
// new ItemType(item);
// depending on ItemType
}
public RemoveFromList(ItemType toRemove)
{
internalList.Remove(toRemove);
// do other things necessary to keep A in a consistent state
}
}
Suppose I have this code:
public sealed class MyStruct {
// ... snip
public uint[] ItemStatValue { get; set; }
// ... snip
}
// MainForm.cs
// ...
Generator.GenerateColumns(this.ContentListView, structure, true);
ContentListView.SetObjects(_records);
// ...
Is there a way to instruct GenerateColumns to treat each element of the ItemStateValue property as a column on its own, and appropriately name them ? ("Item Stat Value 1", "Item Stat Value 2", etc) Currently, it just calls ToString() on the object, thus returning System.Type.UInt32[]. Not exactly what I'd want.
I'm using http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView
Cheers!
No, that is not possible. The OLV uses reflection on the type of the specified item to gather the required information. If you supply an IEnumerable it just looks at the type of the first item.
The Generator has no clue and does not care about the actual instances of your items. Also, it wouldn't make much sense in your proposed case, since the number of elements could differ between each struct.
How many items are going to be in the array? ListViews have a fixed number of columns, i.e. each row has the same number of columns.
If you know there is going to be, say, 20 possible stats, just generate the columns.
const int numberOfColumns = 20;
for (int i = 0; i < numberOfColumns; i++) {
var statIndex = i;
var column = new OLVColumn();
column.Name = "Stat" + i;
column.AspectGetter = delegate(object x) {
MyStruct myStruct = (MyStruct)x;
return statIndex < myStruct.ItemStatValue.Length ? (uint?)myStruct.ItemStatValue[statIndex] : null;
};
column.AspectToStringConverter = delegate(object x) {
uint? value = (uint?)x;
return value.HasValue ? String.Format("Stat value: {0}", value.Value) : String.Empty;
};
this.ContentListView.AllColumns.Add(column);
}
this.ContentListView.RebuildColumns();
Call this after the GenerateColumns()
#Grammarian
So after looking around in the code, I noticed aspect getters for fields. However, Generator.GenerateColumns didn't seem to use its third boolean parameter, named allProperties.
So I threw together this quick code, and sure enough, it worked. It doesn't seem to cause any bug either, which is great.
Here is a gist:
https://gist.github.com/Warpten/c792ad66ad20cc69a918
Note: this also requires that you allow OLVColumnAttribute and OLVIgnoreAttribute to be applied to fields. That's easy enough ;)
Cheers!
I have a listbox, and a string array added to it
static sting[] demo = new string[] {"cat", "dog", "bird", "horse"};
public Form1()
{
InitializeComponent();
ListBox1.Items.AddRange(demo);
}
Then elsewhere I am trying to make a bool array based off of the listbox's selected items (it is multi select enabled)...
*the logic I am trying to do
//somewhere...
bool[] b = new bool[] { false, false, false, false};
//somewhere else
for(int i=0; i<4; i++)
{
b[i] = ListBox1.Items[i].IsSelected;
}
This does not work. I cannot access the .Items[i] properties and have only methods available (which for this case are pointless: Equals(), GetHashCode(), GetType(), ToString()).
Using a foreach loop for the selectedIndices will not work either as it will only give me the ones that are selected (and I need to iterate through each items value true|false).
I Tried adding the System.Windows.Controls.ListBoxItem but that did not work either...
System.Windows.Controls.ListBoxItem[] myItems = new System.Windows.Controls.ListBoxItem[4];
public Form1()
{
InitializeComponent();
for(int i = 0; i < 4; i++)
{
myItems[i] = new System.Windows.Controls.ListBoxItem();
myItems[i].Content = demo[i];
listbox1.Items.Add(myItems[i].Content);
}
}
This part goes through fine, but later when I try to cycle through the ListBoxItems it blows up throwing a string to ListBoxItem cast error (even though I added it as a ListBoxItem object???)
Again, logic I am trying* to do but not working
bool tmp;
foreach(System.Windows.Controls.ListBoxItem li in listbox1.Items)
{
tmp = li.IsSelected;
// do something
}
I assume that trying a for loop (which realistically I would need to use anyways to point to the bool array) would fail in the same manner. I don't need to use the ListBoxItem class (actually more references to add so I would rather not use it) but I thought it would work correctly. There has to be a more efficient way to do this. In addition I would also like to know why when I add a listboxitem object to the listbox, why it has a type conversion error when I try to cycle through it later.
You didn't tag your question, but it looks like you are mixing WPF classes with a WinForms project.
To mimic the IsSelected property, you can use something like this:
for (int i = 0; i < 4; i++) {
b[i] = listBox1.SelectedIndices.Contains(i);
}
have you tried something like this?
for(int i=0; i<4; i++)
{
if(ListBox1.Items[i].IsSelected)
b[i] = true;
}
I think the compiler is doesn't have a value for IsSelected() until it's run.
My problem is that I want to make a program that uses two lists, which is almost impossible for me to understand. Okay, so the deal is that I want to make a program where you first type in a city name and then the temperature for the city. This is where the relationship comes from.
I have started by making a "list class", which looks like this:
class citytemp
{
private string city;
private double temp;
public citytemp(string city, double temp)
{
this.city = city;
this.temp = temp;
}
public string City
{
get { return city; }
set { city = value; }
}
public double Temp
{
get { return temp; }
set { temp = value; }
}
}
Then I make the list in the program like this
List<citytemp> temps = new List<citytemp>();
Which all looks good to me. But when I'm trying to show the user the list nothing shows up. I use these lines to show it:
for (int i = 0; i > temps.Count; i++)
{
Console.WriteLine(temps[i].City, temps[i].Temp);
}
BTW: I add "things" to the list by these rows:
temps.Add(new citytemp(tempcity, temptemp));
...where tempcity and temptemp are temporary variables. They are only there to make it more simple for me to add them to the list, since I'm using a switch statement to add them to the list.
To make things more clear, my problem is that I don't know how I'm suppose to show the list to the user in the program.
Your problem is in the for loop. Change it to this
for (int i = 0; i < temps.Count; i++)
i.e. change the greater than > operator to a less than <
You have an error in your for loop.
for (int i = 0; i > temps.Count; i++)
it should be:
for (int i = 0; i < temps.Count; i++)
First of all, I'm not sure what you mean by "2 lists" as you only have one list in your code.
However, the problem you're having with "nothing shows up" is easy to fix.
This line of code:
for (int i = 0; i > temps.Count; i++)
Should be read as follows:
i = 0;
while (i > temps.Count)
{
... rest of your loop body here
i++;
}
if you read this, you'll notice that the second part of the for statement is not when to terminate but how long to keep going.
Change it to this, and you should be good:
for (int i = 0; i < temps.Count; i++)
^
+-- changed from >
I think a hashtable, specifically a dictionary would help you here:
var cityTemps = new Dictionary<string, double>();
cityTemps.Add("TestCity", 56.4);
foreach (var kvp in cityTemps)
Console.WriteLine("{0}, {1}", kvp.Key, kvp.Value);
In addition to the loop that has been mentioned, be careful withConsole.WriteLine because it takes in a String as first argument which it assumes is a format and object[] params as a second parameter. When you pass temps[i].City to it since its String it will think that its the format and temps[i].Temp is the parameter and won't display correctly.
What you want instead:
Console.WriteLine("City: {0} Temp: {1}", temps[i].City, temps[i].Temp);
Here I am using "City: {0} Temp: {1}" as the format for the string and the proper parameters.
This answer is to save you a headache later on wondering why only the city name is being displayed.