Trying to get data to display in my listview - c#

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>

Related

WPF ObservableCollection containing List<T>

for some time I am struggling with ObservableCollection containing List. I am not able to get it working in a way, that each List is in its own expander.
You can imagine input data as a list of records per day and each list contains orders for that day.
Here is some code:
My version 1:
public class Order
{
public string OrderId { get; private set}
}
public ObservableCollection<Order> ObservableResults
{
get
{
return new ObservableCollection<Order>
{
new Order("Foo"),
new Order("Bar")
};
}
}
XAML file:
<ScrollViewer>
<ListView Background="Transparent" ItemsSource="{Binding ObservableResults}">
<ListView.View>
<GridView>
<GridViewColumn Header="Order Id" Width="650" DisplayMemberBinding="{Binding OrderId, UpdateSourceTrigger=PropertyChanged}" />
</GridView>
</ListView.View>
/ListView>
</ScrollViewer>
This one worked just fine, but the problem was, that I had all the orders in a single grid view and that's something I did not really like so I wanted to modify it, that I will get List of orders and fill the ObservableCollection with these lists. Pretty much each list represents one day worth of orders. So I have modified the code like this:
Code behind
public class Order
{
public string OrderId { get; private set}
}
public ObservableCollection<List<Order>> ObservableResults
{
get
{
return new ObservableCollection<Order>
{
new List<Order>
{
new Order("Foo")
},
new List<Order>
{
new Order("Bar")
}
};
}
}
But I do have a problem with the XAML part right now... I am not really sure, how to achieve the following:
It will be a single scroll view, where each List<Order> will be encapsulated in its own Expander (so I can open close each day individually). So far I have this:
XAML version 2:
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Background="Red">
<ListView Background="Transparent" ItemsSource="{Binding ObservableResults}">
<ListView.ItemTemplate>
<DataTemplate>
<Expander>
<Expander.Header>
<TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource AncestorType=ListViewItem}" />
</Expander.Header>
<ListView ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="Order ID" Width="650" DisplayMemberBinding="{Binding OrderId, UpdateSourceTrigger=PropertyChanged}" />
</GridView>
</ListView.View>
</ListView>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
But in this case no data are present in the view and the ReSharper says, that it:
Cannot resolve symbol 'OrderId'
Can anyone help me, what's wrong in here?

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 - how to do binding the right way for a particular scenario?

I'm pretty new to WPF (moving from WinForms). I'm trying to transfer some scenario from a WinForms application to a WPF one:
A window has a ListView control with 3 columns.
There is a button there to add new rows to that ListView.
The first and the second columns contain the ComboBox control.
The third column must contain different controls but just one at a time is visible. Which one is visible, it depends on the selected value of the ComboBox at the first column.
The content of the ComboBox at the second column changes every time a user selects a value from the ComboBox at the first column.
The general scenario is: a user selects a type from the list of types from the first ComboBox, after that the second ComboBox changes its content to a list of supported operations for the selected type and the third column at that time must change its content to display a control that supports the input for that type.
I know how to implement it using WinForms but I have no idea yet how to do it using WPF. Can someone help me to implement it or can anyone help with the information that facilitate implementing that?
I have the code so far:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
if (PropertyChanged != null) PropertyChanged(this, args);
}
}
public class RecordFilter : ViewModelBase
{
private static readonly ObservableCollection<KeyValuePair<PropertyInfo, string>> ColumnAliases =
new ObservableCollection<KeyValuePair<PropertyInfo, string>>(Card.ColumnAliases);
private KeyValuePair<PropertyInfo, string> _currentSelectedProperty;
public IEnumerable<OperationInfo> Operations
{
get
{
return Operations.GetOperationInfosForType(GetTypeUnwrapNullable(SelectedProperty.Key.PropertyType));
}
}
public OperationInfo SelectedOperation { get; set; }
public KeyValuePair<PropertyInfo, string> SelectedProperty
{
get { return _currentSelectedProperty; }
set
{
_currentSelectedProperty = value;
OnPropertyChanged("Operations");
}
}
public ObservableCollection<KeyValuePair<PropertyInfo, string>> Properties
{
get { return ColumnAliases; }
}
//DateTime or int or float, depends on the selected property type
//public object PropertyValue { get; set; }
}
Here is the XAML code:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converters="clr-namespace:App.Converters" x:Class="App.DialogWindows.CardFilterWindow"
Title="Search filters" Height="347" Width="628" x:Name="wdw" ShowInTaskbar="False" WindowStartupLocation="CenterScreen">
<Window.Resources>
<Converters:NotNullObjectToEnabledConverter x:Key="NotNullObjectToEnabledConverter"/>
</Window.Resources>
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Center" Height="Auto">
<Button x:Name="bnOK" Margin="5" Width="41" Content="OK" IsDefault="True" Click="bnOK_Click"/>
<Button x:Name="bnCancel" Margin="5" Content="Отмена" IsCancel="True"/>
</StackPanel>
<ListView ItemsSource="{Binding Filters, ElementName=wdw}" Name="LvExpr" DataContext="{Binding Filters, ElementName=wdw}">
<ListView.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Header="Alias" Width="210">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox VerticalAlignment="Center"
ItemsSource="{Binding Properties}"
DisplayMemberPath="Value"
SelectedValue="{Binding SelectedProperty, Mode=TwoWay}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Operation" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox VerticalAlignment="Center"
ItemsSource="{Binding Operations}"
DisplayMemberPath="OperationAlias"
SelectedValue="{Binding SelectedOperation, Mode=TwoWay}"
/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Value" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Text="ValidatesOnDataErrors=True}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="33">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Tag="{Binding Mode=OneWay}" Click="BnDelete_Click" ToolTip="Delete filter">
<Image Source="delete.ico" Height="16" Width="16"/>
</Button>
</DataTemplate>
</GridViewColumn.CellTemplate>
<GridViewColumnHeader>
<DataGridCell>
<Button Click="ButtonAdd_Click" Height="22" Padding="0" ToolTip="Add filter">
<Image Source="plus.ico" Focusable="False"/>
</Button>
</DataGridCell>
</GridViewColumnHeader>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>
In your view-model, set up the list properties, and filter them out accordingly when the selected value changes (via the INotifyPropertyChanged.PropertyChanged event).
See this post for a comprehensive example. It uses a technique called MVVM that is used extensively with WPF and stands for ModelViewViewModel. I highly recommend you to learn this technique and utilize it in your XAML-related projects.
Here is one quick start tutorial, out of the many on the net.

MVVM ViewModel creating and binding

I was going to create this with windows forms but was told that wpf mvvm would be better. I am new to c# and have been researching mvvm and wpf.
I am now working on my viewmodel to work with both the view and the model. There is no database.
My problem:
how do I correctly bind the view to the viewmodel. I am missing a itemssource or localsource code somewhere in my xaml but I also do not understand how the itemsource works. Where in the viewmodel is the itemsource declared and how. I have been googling for a good answer but still have not found one that makes it click for me.
I also know there is an INotifyChange type property and i have seen some code examples but dont fully understand it, it just has not clicked for me.
Currently:
I have a view created in xaml which is the first code below. I then created a class for a scan which is the second group of code below in c# (i know the get set methods could be improved but i was following a tutorial).
The user with the scan gun is not going to be looking at the screen when they are scanning. I want to be able to go in order so first scan fills in the first text box, second scan fills the second text box and if needed they will fill in the count.
Extra info:
The bottom part (dataview) is a temp table for showing previous scans but I can figure that our later. Most important part is being able to get the scans and do something with them.
The scans will be keyboardwedge (sends characters like being typed w/ an enter key at the end) but later i am planning on making them serial com port so this program can run in the background.
Note: I know i gave a lot of detail that is probobly not needed for the small current problem but just wanted to be clear.
<Window x:Class="ScanningV2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="700">
<DockPanel LastChildFill="True">
<Grid x:Name="LayoutRoot" DockPanel.Dock="Top" Height="100" Background="#FFFFFF" Margin="2,2,2,2">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="Scan" Grid.ColumnSpan="1" Margin="2,2,2.2,2" />
<Label Content="Operator Barcode" Grid.Column="1" HorizontalAlignment="Left" Margin="50,20,0,0" VerticalAlignment="Top" Width="120" />
<Label Content="MO/Task Barcode" Grid.Column="1" HorizontalAlignment="Left" Margin="200,20,0,0" VerticalAlignment="Top" Width="120" />
<Label Content="Quantity" Grid.Column="1" HorizontalAlignment="Left" Margin="350,20,0,0" VerticalAlignment="Top" Width="120" />
<TextBox Grid.Column="1" HorizontalAlignment="Left" Margin="50,50,0,0" TextWrapping="Wrap" Text="Scan" VerticalAlignment="Top" Height="20" Width="120" />
<TextBox Grid.Column="1" HorizontalAlignment="Left" Margin="200,50,0,0" TextWrapping="Wrap" Text="Scan" VerticalAlignment="Top" Height="20" Width="120" />
<TextBox Grid.Column="1" HorizontalAlignment="Left" Margin="350,50,0,0" TextWrapping="Wrap" Text="Scan" VerticalAlignment="Top" Height="20" Width="120" />
<!-- <ListView Grid.Row="0" Grid.Column="1" x:Name="curScans" Background="Aqua" Grid.ColumnSpan="1" Margin="1.8,0,-0.4,0">
<ListView.View>
<GridView>
<GridViewColumn Header="Scanner" DisplayMemberBinding="{Binding Path=curScanNum}" Width="150" />
<GridViewColumn Header="Operator" DisplayMemberBinding="{Binding Path=curOperator}" Width="200" />
<GridViewColumn Header="Task" DisplayMemberBinding="{Binding Path=curTask}" Width="200"/>
</GridView>
</ListView.View>
</ListView> -->
</Grid>
<ListView x:Name="pastScans" Background="#2FFFFFFF" DockPanel.Dock="Bottom">
<ListView.View>
<GridView>
<GridViewColumn Header="Scanner" DisplayMemberBinding="{Binding Path=ScannerNum}" Width="100" />
<GridViewColumn Header="Operator barcode" DisplayMemberBinding="{Binding Path=Operator}" Width="150" />
<GridViewColumn Header="MO/Task barcode" DisplayMemberBinding="{Binding Path=Task}" Width="150" />
<GridViewColumn Header="Date" DisplayMemberBinding="{Binding Path=ScanDate}" Width="100" />
<GridViewColumn Header="Time" DisplayMemberBinding="{Binding Path=ScanTime}" Width="100" />
<GridViewColumn Header="Quantity" DisplayMemberBinding="{Binding Path=Quantity}" Width="100" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ScanningV2
{
class scan
{
//Member variables
private string operatorCode;
public string OperatorCode
{
get { return operatorCode; }
set { operatorCode = value; }
}
private string taskCode;
public string TaskCode
{
get { return taskCode; }
set { taskCode = value; }
}
private int count;
public int Count
{
get { return count; }
set { count = value; }
}
private DateTime scanDateTime;
public DateTime ScanDateTime
{
get { return scanDateTime; }
set { scanDateTime = value; }
}
//Default Constructor
public scan()
{
operatorCode = null;
taskCode = null;
count = 0;
}
//Overload Constructor
public scan(string OperCode, string TaskMOCode, int CountNum)
{
operatorCode = OperCode;
taskCode = TaskMOCode;
count = CountNum;
}
}
}
You'll have to set an instance of your view-model class as the DataContext of your view. I usually do this in the code-behind of a view, so in your MainWindow.xaml.cs you would do the following:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Scan();
}
}
Keep in mind that your view won't be able to detect changes unless you notify it. That's the point of the INotifyPropertyChanged interface:
class Scan : INotifyPropertyChanged
{
// Implementing the INotifyPropertyChanged interface:
public event PropertyChangedEventHandler PropertyChanged;
// A utility method to make raising the above event a little easier:
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
// Then, notify the view about changes whenever a property is set:
private string operatorCode;
public string OperatorCode
{
get { return operatorCode; }
set { operatorCode = value; RaisePropertyChanged("OperatorCode"); }
}
}
In your MainWindow.xaml, you can bind to that property:
<TextBlock Text="{Binding OperatorCode}" />
Now, whenever you set a new value for OperatorCode, your view will be notified so it can fetch and display the new value.
For ItemsSources, any IEnumerable will do - a List, an array... however, if you want the view to be notified whenever your collection changes, you'll have to use a class that implements INotifyCollectionChanged, such as ObservableCollection.
So, you create a bindable property in your view-model:
private ObservableCollection<string> names;
public ObservableCollection<string> Names
{
get { return names; }
set { names = value; RaisePropertyChanged("Names"); }
}
And you bind to that from within your view:
<ListView ItemsSource="{Binding Names}" />
Minor point: in C#, class names are usually written in CamelCase. Also, personally I prefer giving each view-model class a ViewModel postfix, so you can quickly see which classes are meant to be view-models. I try to match their name to the name of the view they belong to, so instead of 'scan', I would call this one 'MainWindowViewModel'.
You cannot bind any of that to any WPF UI elements because your code is too java-like.
You need to use Properties the C# way.
change all your get() and set() methods to real properties.

ListView Binding to List of Custom class containing Dictionnary

I am learning WPF and I am trying to fill a ListView with a list of folders (as ListView Groups) and files for each folder(as ListView Items).
Using WPF/MVVM Quick Start Tutorial , I created the following classes (Business removed)
public class PatchGen
{
public PatchGen() { }
private string _folderName;
private Dictionary<string, string> _filesInfo = new Dictionary<string, string>();
public string FolderName
{
get { return _folderName; }
set { _folderName= value; }
}
public Dictionary<string, string> FilesInfo
{
get { return _filesInfo; }
set { _filesInfo = value; }
}
}
and the ViewModel:
public class PatchGenViewModel : ObservableObject
{
public PatchGenViewModel()
{
}
List<PatchGen> _folderList = new List<PatchGen>();
public List<PatchGen> Folders
{
get
{
return _folderList;
}
set { }
}
void AddFilesExecute()
{
//business here
}
bool CanAddFilesExecute()
{
return true;
}
public ICommand AddFiles { get { return new RelayCommand(AddFilesExecute, CanAddFilesExecute); } }
The xaml section includes the DataContextand the CollectionViewSource:
<Window.DataContext>
<local:PatchGenViewModel></local:PatchGenViewModel>
</Window.DataContext>
<Window.Resources>
<CollectionViewSource x:Key='groups'
Source="{Binding Path=Folders}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="FolderName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
and the ListView:
<ListView Grid.Row="1"
HorizontalAlignment="Stretch"
Name="lstViewServices"
ItemsSource='{Binding Source={StaticResource groups}}'>
<ListView.View>
<GridView>
<GridViewColumn Header="File Name"
DisplayMemberBinding="{Binding Path=??? }"
Width="100" />
<GridViewColumn Header="File Path"
DisplayMemberBinding="{Binding Path=??? }"
Width="Auto" />
</GridView>
</ListView.View>
</ListView>
The ListView Group is not showing the Folders Names. ?
How to have the File Name and the File Path that represents the FilesInfo (Dictionnary < string,string > ) information displayed?
Is there any way to do this through XAML and ViewModel Class without the Code behind of the Xaml file?
You need to just bind the File name to the File to the folder name property.
For the File Path, you need to bind it to the FilesInfo property. Why is it a dictionary? I guess I didn't understand why you are using a dictionary here? Maybe I am missing something but you should drop the dictionary and create your own little object.
public class FileInfo
{
public string FileName {get;set;}
public string FilePath {get;set;}
}
Then of course change your PatchGen object to use that instead of a Dictionary.
Maybe a screenshot of what you want it to look like would help. However, if you look at your XAML, you don't have anywhere to put your FolderName. You only have places for FileName and FilePath.
<ListView Grid.Row="1"
HorizontalAlignment="Stretch"
Name="lstViewServices"
ItemsSource='{Binding Source={StaticResource groups}}'>
<ListView.View>
<GridView>
<GridViewColumn Header="File Name"
DisplayMemberBinding="{Binding Path=FileName }"
Width="100" />
<GridViewColumn Header="File Path"
DisplayMemberBinding="{Binding Path=FilePath}"
Width="Auto" />
</GridView>
</ListView.View>
</ListView>
So you should add a place for FolderName. You have two lists it seems: the folder list and for each folder a the file list. But your view only has one level.
Here is an example that has two levels.
<ItemsControl ItemsSource='{Binding Folders}'>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding FolderName}" />
<ListView Grid.Row="1"
HorizontalAlignment="Stretch"
Name="lstViewServices"
ItemsSource="FileInfo">
<ListView.View>
<GridView>
<GridViewColumn Header="File Name"
DisplayMemberBinding="{Binding Path=FolderName}"
Width="100" />
<GridViewColumn Header="File Path"
DisplayMemberBinding="{Binding Path=FolderName }"
Width="Auto" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Categories