Coming from Windows Forms to WPF is a bit of a challenge, this is my first WPF project so forgive me if I'm missing something obvious...
I am adding items to a ListView row by row, then would like to iterate through the items later and read the item and subitem values. I have tried several approaches but all return only the last row.
I am not using the ItemsSource property because I want to see the rows as they are added as it may take some time to process the data for each row before it is added. If I use the ItemsSource = data I have to wait until all the data is acquired then add it all at once and haven't found another way using that method.
I've looked at a lot of suggestions here, this one looks good but it uses the ItemsSource to add all the data at one time. https://stackoverflow.com/questions/42635953/c-sharp-wpf-iterate-through-listview-items
I have this code
<ListView x:Name="listViewResults" Margin="2,117,4,36" Grid.ColumnSpan="6" FontFamily="Consolas" >
<ListView.View>
<GridView>
<GridViewColumn Header="#" Width="40" DisplayMemberBinding ="{Binding Path=hop}"/>
<GridViewColumn Header="Host/IP" Width="350" DisplayMemberBinding ="{Binding Path=hostIP}"/>
<GridViewColumn Header="RTT1" Width="100" DisplayMemberBinding ="{Binding Path=R1}"/>
<GridViewColumn Header="RTT2" Width="100" DisplayMemberBinding ="{Binding Path=R2}"/>
<GridViewColumn Header="RTT3" Width="100" DisplayMemberBinding ="{Binding Path=R3}"/>
<GridViewColumn Header="Average" Width="100" DisplayMemberBinding ="{Binding Path=average}"/>
</GridView>
</ListView.View>
</ListView>
public class RowData
{
public string? hop { get; set; }
public string? hostIP { get; set; }
public string? R1 { get; set; }
public string? R2 { get; set; }
public string? R3 { get; set; }
public string? average { get; set; }
public string? tag { get; set; }
}
This is how I add it,row by row...
RowData row = new RowData();
ListView lv = listViewResults;
lv.Items.Clear();
foreach (var entry in MyMethod(ipToTrace))
{
row.hop = hopID;
row.hostIP = hostOrIPAddress;
row.R1 = roundTripTime[0];
//etc...
lv.Items.Add(row);
}
I've tried all these methods to iterate through the ListView and all return the only last row
// 1
foreach (RowData item in listViewResults.Items)
{
MessageBox.Show("Hop: " + item.hop + " IP: " + item.hostIP);
}
// 2
for (int i = 0; i < listViewResults.Items.Count; i++)
{
RowData item = listViewResults.Items[i] as RowData;
MessageBox.Show("Hop: " + item.hop + " IP: " + item.hostIP);
}
// 3
List<RowData> items = (List<RowData>)listViewResults.Items.Cast<RowData>().ToList();
foreach (RowData row in items)
{
MessageBox.Show(row.hostIP + " - " + row.ToString());
}
When I query the count property it always has the correct number of rows but I'm really confused as to why I only get the last record return.
All your assumptions about the ItemsControl.ItemsSoucre
property and the way it is used are wrong. You should never modify a collection directly by adding items to the ItemsSource
or Items
property. Instead define a collection that you work on and assign it to the ItemsSource
(preferably using data binding).
All you have to do is to create an ObservableCollection<T>
and assign it to the ItemsSource
property. Now when adding/removing items to this collection the ListView
will automatically update to show the changes. You use the same collection to iterate over the rows.
Note, the common C# naming convention requires that all public members are named using PascalCase and not camelCase. Only fields are named camelCase and fields are never public except when they are constants or events. If you decide to not use the common convention then you should at least be consistent and not mix naming rules (because some of your properties are PascalCase and some camelCase).
partial class MainWindow : Window
{
// ObservableCollection implements INotifyCollectionChanged which enables
// every ItemsControl (like the ListView) to track changes of the ItemsSource.
// Create a field or property.
public ObservableCollection<RowData> Rows { get; }
public MainWindow()
{
InitializeComponent();
this.Rows = new ObservableCollection<RowData>();
// Assign the collection to the ItemsSource property or use data binding
listViewResults.ItemsSource = this.Rows;
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
foreach (var entry in MyMethod(ipToTrace))
{
RowData row = new RowData()
{
Hop = hopID,
hostIP = hostOrIPAddress,
R1 = roundTripTime[0],
//etc...
}
this.Rows.Add(row);
}
}
private void ProcessRows()
{
// If you want to iterate over the rows, just use the source collection
foreach (RowData row in this.Rows)
{
MessageBox.Show("Hop: " + row.hop + " IP: " + row.hostIP);
}
}
}