C# can't binding to list view using observable collection - c#

I have a problem when I try to bind a collection of type observable to listView.
I am sure that the collection is updating correctly but nothing happening in the list view.
I am retrieving JSON objects and convert them to a collection of Observable.
public partial class MainWindow : Window
{
public ObservableCollection<post> mproducts = new ObservableCollection<post>();
public MainWindow()
{
InitializeComponent();
DataContext = mproducts;
}
private static readonly HttpClient Client = new HttpClient();
async void OnLoad(object sender, RoutedEventArgs e)
{
HttpResponseMessage response = await Client.GetAsync("https://jsonplaceholder.typicode.com/posts");
response.EnsureSuccessStatusCode();
string data = await response.Content.ReadAsStringAsync();
mproducts = JsonConvert.DeserializeObject<ObservableCollection<post>>(data);
MessageBox.Show(mproducts.Count.ToString());
}
public class post
{
public string UserId { get; set; }
public string Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
}
}
this is the XAML class:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Loaded="OnLoad"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListView x:Name="products" Margin="400,0,0,0" Width="600" SelectionChanged="ListView_SelectionChanged" ItemsSource="{Binding mproducts}"; >
<ListView.View>
<GridView>
<GridViewColumn Width="160" Header="Description" DisplayMemberBinding="{Binding UserId}"/>
<GridViewColumn Width="160" Header="Date Filed" DisplayMemberBinding="{Binding Id}"/>
<GridViewColumn Width="160" Header="Filed By" DisplayMemberBinding="{Binding Title}"/>
<GridViewColumn Width="150" Header="Page" DisplayMemberBinding="{Binding Body}"/>
<GridViewColumn Width="Auto" Header="" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
when the app loads it shows in the Message box number 100, which means the collection is updated.

The binding of the ListBox should look like this
ItemsSource="{Binding}"
because you are binding to the collection itself which is stored in the DataContext. Also in your code you should do DataContext = mproducts; in the OnLoad event after you assign the collection to mproducts like this:
mproducts = JsonConvert.DeserializeObject<ObservableCollection<post>>(data);
DataContext = mproducts;

Related

WPF ListView SelectedItem Not Updating With Expander in DataTemplate

I'm creating a WPF application using C# and MVVM, and I'm having some trouble with the SelectedItem of a ListView.
In the contrived example, the scenario: I have a PersonViewModel containing a List of Person objects. Each person object has its own List of Address objects. My goal is to load all of the Person objects into a ListView. Additionally, the ListView DataTemplate should include Expanders that show the available addresses for each person.
The problem: This all actually works decently enough. The only problem is that if a user selects the expander for a person object, it doesn't actually change the SelectedItem property of the ListView (which is bound to a SelectedPerson object in the view model).
I'm relatively confident that the code works otherwise because if I click on the ListView row (outside the boundaries of the contained expander), the SelectedPerson property is updated. Is there anyway to somehow bind when the expander is clicked to the SelectedPerson property? (I'm also open to other UI ideas that will get my information across in a cleaner, more easily implemented manner).
Person.cs
using System.Collections.Generic;
namespace ListViewExpanderTest
{
public class Person
{
public Person()
{
Addresses = new List<Address>();
}
public string Name { get; set; }
public List<Address> Addresses { get; set; }
}
}
Address.cs
namespace ListViewExpanderTest
{
public class Address
{
public int HouseNumber { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
}
}
PersonViewModel.cs
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ListViewExpanderTest
{
public class PersonViewModel : INotifyPropertyChanged
{
private Person _selectedPerson;
private Address _selectedAddress;
public List<Person> People { get; set; }
public Person SelectedPerson
{
get
{
return _selectedPerson;
}
set
{
_selectedPerson = value;
OnPropertyChanged();
}
}
public Address SelectedAddress
{
get
{
return _selectedAddress;
}
set
{
_selectedAddress = value;
OnPropertyChanged();
}
}
public PersonViewModel()
{
People = new List<Person>
{
new Person
{
Name = "Person 1",
Addresses = new List<Address>
{
new Address {HouseNumber = 1, StreetName = "Fake St", City = "Fake City" },
new Address {HouseNumber = 2, StreetName = "Super Fake St", City = "Super Fake City" }
}
},
new Person
{
Name = "Person 2",
Addresses = new List<Address>
{
new Address {HouseNumber = 10, StreetName = "Fake St", City = "Fake City" },
new Address {HouseNumber = 20, StreetName = "Super Fake St", City = "Super Fake City" }
}
}
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainWindow.xaml
<Window x:Class="ListViewExpanderTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ListViewExpanderTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:PersonViewModel />
</Window.DataContext>
<Grid>
<ListView ItemsSource="{Binding People, Mode=TwoWay}"
SelectedItem="{Binding SelectedPerson}">
<ListView.ItemTemplate>
<DataTemplate>
<Expander >
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</Expander.Header>
<Expander.Content>
<ListView ItemsSource="{Binding Addresses, Mode=TwoWay}"
SelectedItem="{Binding SelectedAddress}">
<ListView.View>
<GridView>
<GridViewColumn Width="50" Header="Number" DisplayMemberBinding="{Binding HouseNumber}" />
<GridViewColumn Width="100" Header="Street" DisplayMemberBinding="{Binding StreetName}" />
<GridViewColumn Width="100" Header="City" DisplayMemberBinding="{Binding City}" />
</GridView>
</ListView.View>
</ListView>
</Expander.Content>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
As your view model contains a single SelectedPerson property it doesn't make sense to me to include an expander in the datatemplate. If you can change your UI, the below example seems to make more sense to me. You can obviously place the address 'panel' anywhere you like.
Also, have you tried selecting an address from the sub listview? Does the binding work?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding People, Mode=TwoWay}"
SelectedItem="{Binding SelectedPerson}"
DisplayMemberPath="Name">
</ListView>
<ListView Grid.Column="1"
ItemsSource="{Binding SelectedPerson.Addresses, Mode=TwoWay}"
SelectedItem="{Binding SelectedAddress}">
<ListView.View>
<GridView>
<GridViewColumn Width="50" Header="Number" DisplayMemberBinding="{Binding HouseNumber}" />
<GridViewColumn Width="100" Header="Street" DisplayMemberBinding="{Binding StreetName}" />
<GridViewColumn Width="100" Header="City" DisplayMemberBinding="{Binding City}" />
</GridView>
</ListView.View>
</ListView>
</Grid>

ListView into list

I have a wpf window with a ListView controller with bunch of item.
<ListView Name="lvUsers" SelectionChanged="lvUsers_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Mail" Width="150" DisplayMemberBinding="{Binding Mail}" />
</GridView>
</ListView.View>
</ListView>
Can I convert this ListView to a List<T> so that I can serialize it in XML file?
I tried this:
foreach (ListViewItem item in lvUsers.SelectedItems)
{
foreach (ListViewItem.ListViewSubItem subItem in lvUsers.SubItems)
{
}
}
But this code doesn't work cause ListViw doesn't containe SubItem property.
To serialize your data, your data got to be serializable. Where and how do you define it? The best way, would be to define an ObservableCollection<YourClass> and use it as ItemsSource in your ListView. Than, if YourClass is serializable, you can get data from that data collection, and serialize it.
<Window
x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Button Click="ButtonBase_OnClick">Serialize</Button>
<ListView ItemsSource="{Binding AllMyData}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Age" Width="50" DisplayMemberBinding="{Binding Age}" />
<GridViewColumn Header="Mail" Width="150" DisplayMemberBinding="{Binding Mail}" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Xml;
using System.Xml.Serialization;
namespace WpfApp2
{
public partial class MainWindow : Window
{
public class YourClass
{
public string Name { get; set; }
public string Age { get; set; }
public string Mail { get; set; }
}
public ObservableCollection<YourClass> AllMyData { get; set; }
public MainWindow()
{
InitializeComponent();
AllMyData = new ObservableCollection<YourClass>();
for (var i = 0; i < 3; i++)
{
AllMyData.Add(new YourClass { Name = i.ToString() });
}
this.DataContext = this;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
XmlSerializer xsSubmit = new XmlSerializer(typeof(List<YourClass>));
var subReq = AllMyData.ToList();
var xml = "";
using (var sww = new StringWriter())
{
using (XmlWriter writer = XmlWriter.Create(sww))
{
xsSubmit.Serialize(writer, subReq);
xml = sww.ToString(); // Your XML
Debugger.Break();
}
}
}
}
}

wpf listView with ContextMenu not showing data using data-binding

I have a problem with my List View.
It shows all the elements that I add to the ObservableCollection binded to it, just how it's supposed to work, but when I right-click any of it's elements, the bindings won't work and it won't display the data as I intend it to do.
I created another WPF project to show you the problem more clearly.
Here's my wpf code:
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView x:Name="listViewWithContextMenu" ItemsSource="{Binding Path=CollectionOfThings}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListView.View>
<GridView>
<GridViewColumn Width="120" Header="Name" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Width="120" Header="Quantity" DisplayMemberBinding="{Binding Quantity}"/>
</GridView>
</ListView.View>
<ListView.ContextMenu>
<ContextMenu>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical" Margin="3">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: "></TextBlock>
<TextBlock Text="{Binding Name}"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Quantity: "></TextBlock>
<TextBlock Text="{Binding Quantity}"></TextBlock>
</StackPanel>
</StackPanel>
</StackPanel>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
</Grid>
and the c# code behind it:
using System.Windows;
using System.Collections.ObjectModel;
namespace WpfApp2
{
public partial class MainWindow : Window
{
public ObservableCollection<DataOfThing> CollectionOfThings = new ObservableCollection<DataOfThing>();
public MainWindow()
{
InitializeComponent();
CollectionOfThings.Add(new DataOfThing() { Name = "Some Name", Quantity = 2 });
CollectionOfThings.Add(new DataOfThing() { Name = "Some Other Name", Quantity = 3 });
CollectionOfThings.Add(new DataOfThing() { Name = "Strange Name", Quantity = 1 });
listViewWithContextMenu.ItemsSource = CollectionOfThings;
}
}
public class DataOfThing
{
public string Name { get; set; }
public int Quantity { get; set; }
}
}
And here's what I get:
What happens is that ContextMenu is not in the same visual tree of your ListView (or any other control). It is completely separated from your Window element tree and that's why it gets lost on binding.
I got a solution that might not be the most beautiful but works :)
Set a ContextMenuOpening event to your ListView:
<ListView x:Name="listViewWithContextMenu" ItemsSource="{Binding Path=CollectionOfThings}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ContextMenuOpening="listViewWithContextMenu_ContextMenuOpening">
And in your codebehind, do:
private void listViewWithContextMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
var list = sender as ListView;
list.ContextMenu.DataContext = list.SelectedItem;
}

WPF ListView/GridView Binding

I am attempting to make a simple VS 2017 Extension that is taking a object and displaying it. I have the data coming back and displaying the json in a text box, so I know the data is coming back correctly. But for some reason the gv is just showing the word "id" twice, as their are two records in the dataset. I have tried so many things I'm loosing track. Plus the documentation seems to be all over the place.
I believe there could be at least 2 issues here...
1) XAML the "Bindings"
2) Binding or adding the data to the LV?
Any help with this would be greatly appreciated!
XAML
<UserControl x:Class="DoWork.AllWorkVSControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
Background="{DynamicResource VsBrush.Window}"
Foreground="{DynamicResource VsBrush.WindowText}"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="MyToolWindow">
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Margin="10" HorizontalAlignment="Center">AllWorkVS</TextBlock>
<Button Content="Click me!" Click="button1_Click" Width="120" Height="80" Name="button1"/>
<TextBox Height="200" TextWrapping="Wrap" Name="txtJson"/>
<ListView x:Name="LvIssues">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Width="Auto" Header="Id" DisplayMemberBinding="{Binding Source='Id'}"></GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Grid>
</UserControl>
C#
public class People
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public partial class AllWorkVSControl : UserControl
{
public AllWorkVSControl()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
var t = Issues.GetPeopleById("2");
PopulateListView();
MessageBox.Show(t);
}
private void PopulateListView()
{
var l = GetPeople();
txtJson.Text = JsonConvert.SerializeObject(l);
foreach (var p in l)
{
LvIssues.Items.Add(p);
}
}
}
you need to set the ListView.ItemsSource.
private void PopulateListView()
{
var l = GetPeople();
txtJson.Text = JsonConvert.SerializeObject(l);
LvIssues.ItemsSource= l;
}
<ListView x:Name="LvIssues">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Width="Auto" Header="Id" DisplayMemberBinding="{Binding Id}"></GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>

Adding items to a listview row with columns

So I am trying to add a row of information to my listview but when I do it displays it weirdly. Like so:
I am using an for each loop like so:
foreach (Client c in clients)
{
ListViewItem i = new ListViewItem();
i.Content = new String[] { c.info.cid.ToString(), c.info.pc.ToString(),c.info.ip.ToString(), c.info.status.ToString() };
list.Items.Add(i);
}
My Client class is using a struct to store the info
public struct Info
{
public int cid;
public string pc;
public string ip;
public string status;
}
I am also adding values to it:
info = new Info();
info.ip = "192.168.1.100";
info.pc = "Duncan";
info.status = "idle";
info.cid = 1;
Why is it displaying it weirdly? Could anyone help?
My ListView XAML:
<ListView Height="247" HorizontalAlignment="Left" Margin="4,6,0,0" Name="list" VerticalAlignment="Top" Width="319" Background="#FF454545" ItemsSource="{Binding}" SelectionMode="Multiple" Grid.Column="0">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="ID" Width="30" />
<GridViewColumn Header="Computer" Width="100" />
<GridViewColumn Header="IP" Width="100" />
<GridViewColumn Header="Status" Width="100" />
</GridView>
</ListView.View>
</ListView>
There are some wrong things in this code. If you want to push data in a ListView using bindings, you have to have a valid ViewModel with properties to bind on. You have to define the bindings on you GridViewColumns.
Moreover, WPF doesnt know how to bind on fields, so you will need .NET properties for each data you want to display. Here is a very raw example for your case, it's not a realistic scenario but should help you get started :
Window.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ListView Height="247" HorizontalAlignment="Left" Margin="4,6,0,0" Name="list" VerticalAlignment="Top" Width="319" Background="#FF454545" ItemsSource="{Binding Clients}" SelectionMode="Multiple" Grid.Column="0">
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="ID" Width="30" DisplayMemberBinding="{Binding Id}" />
<GridViewColumn Header="Computer" Width="100" DisplayMemberBinding="{Binding Computer}" />
<GridViewColumn Header="IP" Width="100" DisplayMemberBinding="{Binding Ip}" />
<GridViewColumn Header="Status" Width="100" DisplayMemberBinding="{Binding Status}" />
</GridView>
</ListView.View>
</ListView>
</Window>
MainWindow.xaml.cs
/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public IEnumerable<DummyClient> Clients
{
get
{
for (int i = 0; i < 10; i++)
{
var info = new Info();
info.ip = "192.168.1.100";
info.pc = "Duncan";
info.status = "idle";
info.cid = 1;
yield return new DummyClient(info);
}
}
}
}
public class DummyClient
{
public DummyClient(Info info)
{
Info = info;
}
public string Ip { get { return Info.ip; } }
public string Computer { get { return Info.pc; } }
public string Status { get { return Info.status; } }
public int Id { get { return Info.cid; } }
public Info Info
{
get;
private set;
}
}
public struct Info
{
public int cid;
public string pc;
public string ip;
public string status;
}
Once again it's not really the way it should be done but this is a start. For exampe, DummyClient should implement INotifyPropertyChanged if you want two ways bindings to works.

Categories