WPF ListView/GridView Binding - c#

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>

Related

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

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;

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;
}

Trying to get data to display in my listview

I've been trying to practice with data binding and file IO concepts and for that I wrote this simple application that reads a 2 column, 5 row .csv file and displays the contents to a Listview in WPF with the feature that I can change the values of the 2nd column in my Listview (like a 2 way binding).
I have not been able to get any information to display in my window. I only get the column headers that I define in my MainWindow.xaml but none of the data binding is working.
Here is my code for the View Model and reading the file
namespace WpfPreview
{
public class LoadMovieData : BindableObject // My Data Context?
{
public string MovieName { get; set; }
private double year; public double Year { get { return year; } set { year = value; RaisePropertyChanged("Year"); } }
}
class ViewModel : BindableObject
{
private List<LoadMovieData> obsMovies = new List<LoadMovieData>();
public List<LoadMovieData> ObsMovies
{
get { return obsMovies; }
set { obsMovies = value; RaisePropertyChanged("ObsMovies"); }
}
public void ReadFile()
{
string filepath = System.IO.Path.Combine("C:\\Users\\Param\\Desktop", "excel.csv"); // Get filepath
using (var csvReader = new StreamReader(filepath)) // using this filepath
{
csvReader.ReadLine(); // read first line (headers)
csvReader.ReadLine(); // read first line of row data
while (!csvReader.EndOfStream) // while not end of file
{
var words = csvReader.ReadLine().Split(',').ToList(); // read line to list of columns
var x = new LoadMovieData() // new instance of data class
{
MovieName = words[0],
Year = Convert.ToDouble(words[1])
};
ObsMovies.Add(x); // add instance of data class to list variable
}
}
}
}
}
I'm not sure if my terms are correct. I am trying to follow the MVVM pattern. My codebehind for the window is this:
namespace WpfPreview
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
}
and here is my XAML part:
<Window x:Class="WpfPreview.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:WpfPreview"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Background="White">
<ListView x:Name="MovieListView" ItemsSource="{Binding Path=ObsMovies}" VirtualizingStackPanel.IsVirtualizing="True" Background="Transparent">
<ListView.View>
<GridView>
<GridViewColumn Header="Movie Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ObsMovies.MovieName}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Year" Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding ObsMovies.Year}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Border>
</Grid>
I am very new to working with file IO and data binding/data context. I am sure there is an obvious mistake somewhere and that what I'm trying to do could be done in a much easier/less complicated way. Please feel free to give me suggestions to restructure my code.
I generally put my data loading code in my ViewModel constructor. Also, without an access modifier, your ViewModel class is private I believe, so you will not be able to call anything from outside the class. Consider making it public.
It looks like your obsMovies list should be an ObservableCollection. The value of obsMovies implements PropertyChanged notification, but if you add an item to it, the collection does not notify the UI that its collection has changed.
Change this:
private List<LoadMovieData> obsMovies = new List<LoadMovieData>();
public List<LoadMovieData> ObsMovies
{
get { return obsMovies; }
set { obsMovies = value; RaisePropertyChanged("ObsMovies"); }
}
To this:
private ObservableCollection<LoadMovieData> obsMovies = new ObservableCollection<LoadMovieData>();
public ObservableCollection<LoadMovieData> ObsMovies
{
get { return obsMovies; }
set { obsMovies = value; RaisePropertyChanged("ObsMovies"); }
}
You will have to import System.Collections.ObjectModel to make use of it.
Also, it looks like your bindings may not be quite right. Try using the following instead:
<ListView.View>
<GridView>
<GridViewColumn Header="Movie Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding ObsMovies.MovieName}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Year" Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding ObsMovies.Year}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
The above bindings omit the ObsMovies in the TextBlock bindings. Since each rows DataContext is one of the items in the collection, there is no need to have the collection referenced in the binding. Just have the binding path start at the datacontext level (in this case ObsMovies).
Lastly, as promised, a sample implementation of DataGrid:
<DataGrid HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding ObsMovies}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Year}" ClipboardContentBinding="{x:Null}" Header="Year"/>
<DataGridTextColumn Binding="{Binding MovieName}" ClipboardContentBinding="{x:Null}" Header="Movie Name"/>
</DataGrid.Columns>
</DataGrid>
To have textboxes to allow for editing of items, in the listView example, replace the TextBlocks with TextBoxes, and for the DataGrid, specify a DataGridTemplateColumn and put a TextBox in the template:
<DataGridTemplateColumn ClipboardContentBinding="{x:Null}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Property}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

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.

WPF Listview databinding to generic List, dynamically filled by WCF service

In WPF app I have a WCF service which dynamically fills a generic List object from a backend database.
How in this case (List created in runtime), I could bind List items to a ListView object items?
It is the Data contract for my Web service:
....
[DataContract]
public class MeetList
{
[DataMember]
public string MeetDate;
[DataMember]
public string MeetTime;
[DataMember]
public string MeetDescr;
.....
static internal List<MeetList> LoadMeetings(string dynamicsNavXml)
{
...// Loads XML stream into the WCF type
}
Here in this event handler I read the WCF service and Loop through a List object:
private void AllMeetings()
{
Customer_ServiceClient service = new Customer_ServiceClient();
foreach (MeetList meet in service.ReadMeetList())
{
?????? = meet.MeetDate; // it's here that I bumped into a problem
?????? = meet.MeetTime; //
?????? = meet.MeetDescr;//
}
}
My Listview XAML:
<Grid>
<ListView Height="100" Width="434" Margin="0,22,0,0" Name="lvItems" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" SelectionMode="Single">
<ListView.View>
<GridView>
<GridViewColumn Header="Date" Width="100" HeaderTemplate="{StaticResource DateHeader}" CellTemplate="{DynamicResource DateCell}"/>
<GridViewColumn Header="Time" Width="100" HeaderTemplate="{StaticResource TimeHeader}" CellTemplate="{DynamicResource TimeCell}"/>
<GridViewColumn Header="Description" Width="200" HeaderTemplate="{StaticResource DescriptionHeader}" CellTemplate="{DynamicResource DescriptionCell}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
And data templates for this ListView:
<Window.Resources>
<DataTemplate x:Key="DateHeader">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,0,0,0" Text="Date" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="DateCell" DataType="Profile">
<StackPanel Orientation="Horizontal">
<TextBlock>
<TextBlock.Text>
<Binding Path="MeetDate" />
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
......
How in this case (List created in runtime), I could bind my generic List items to a ListView object items?
I tried to use lvItems.ItemsSource = profiles; , but it doesn't work in my event handler
List doesn't have behaviour to notify that items count is changed. You should use a list with support INotifyCollectionChanged.. for example: ObservableCollection<T>. ObservableCollection<T> will inform your lvItems that items count is changed and it will be properly display.
Using intermediate ObservableCollection:
ObservableCollection<Meets> _MeetCollection =
new ObservableCollection<Meets>();
public ObservableCollection<Meets> MeetCollection
{ get { return _MeetCollection; } }
public class Meets
{
public string Date { get; set; }
public string Time { get; set; }
public string Description { get; set; }
}
In the event handler loop we put:
private void AllMeetings()
{
Customer_ServiceClient service = new Customer_ServiceClient();
_MeetCollection.Clear();
foreach (MeetList meet in service.ReadMeetList())
{
_MeetCollection.Add(new Meets
{
Date = meet.MeetDate,
Time = meet.MeetTime,
Description = meet.MeetDescr
});
}
}
And XAML binding is changed to:
<Grid>
<ListView Height="100" Width="434" Margin="0,22,0,0" Name="lvItems" ItemsSource="{Binding MeetCollection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Date" Width="100" DisplayMemberBinding="{Binding Date}"/>
<GridViewColumn Header="Time" Width="100" DisplayMemberBinding="{Binding Time}"/>
<GridViewColumn Header="Description" Width="200" DisplayMemberBinding="{Binding Description}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>

Categories