WPF ListView Binding + Error - c#

I am getting an exception "Object reference not set to an instance of an object." on the "songs.DataContext =" line. If I add songs = new ListView(); before it my listview is empty even though the list of audiofiles is not
XAML:
<ListView Height="Auto" HorizontalAlignment="Center" ItemsSource="{Binding}"
VerticalAlignment="Center" Name="songList" Width="Auto" MinHeight="300" MinWidth="600">
<ListView.View>
<GridView>
<GridViewColumn Width="Auto" Header="Title" DisplayMemberBinding="{Binding Path=Title}" />
<GridViewColumn Width="Auto" Header="Artist" DisplayMemberBinding="{Binding Path=Artist}" />
<GridViewColumn Width="Auto" Header="Album" />
<GridViewColumn Width="Auto" Header="Length" />
</GridView>
</ListView.View>
</ListView>
C#
public struct AudioFile
{
public String Artist;
public String Title;
public String Album;
public String fileLocation;
public String Length;
}
//...
private List<AudioFile> songs = new List<AudioFile>();
//code that adds to array
songList.DataContext = songs;

I suspect your code to be in the constructor, in a place where songList is not yet created.
//...
private List<AudioFile> songs = new List<AudioFile>();
//code that adds to array
songList.DataContext = songs;
Try to move it in the Loaded event instead.

Your songs are clearly instatiated, but what about the songList?

I think you may be trying to set songList.ItemsSource = list in your constructor and obviously the object is not built yet.
In your UI classes, best place to initialize will be only in Loaded event or after InitializeComponent method.
Even better approach will be to use MVVM.

Are you writing this code -
//...
private List<AudioFile> songs = new List<AudioFile>();
//code that adds to array
songList.DataContext = songs;
before IniitializeComponent() method is called for a view?? Can you provide a bit more of insight regarding your code placement that will help in understanding the situation better.
And just a suggestion, not related though. I would say use class instead of structure objects because WPF data binding considers only properties, not fields. Definitely, this is not a cause of an error though.

Related

Binding confusion in xaml

The source code can be found here : https://www.codeproject.com/Articles/24973/TreeListView
The way the original author has set it up, is the data is filled in the xaml itself. I need to create the TreeViewList inside of my ViewModel, but I can't figure out how to bind my own TreeViewList within the xaml to display it properly.
Here's an example of me creating a tree in the code behind and calling the window.
public TreeListView TreeList { get; set; } = new TreeListView();
private void generateTree()
{
TreeList.Columns.Add(new GridViewColumn() { Header = "Col1" });
TreeList.Columns.Add(new GridViewColumn() { Header = "Col2" });
TreeList.Columns.Add(new GridViewColumn() { Header = "Col3" });
}
public ICommand AssemblyTreeCommand => new RelayCommand(AssemblyTree, p => CanAssemblyTree);
public bool CanAssemblyTree { get; set; } = true;
private void AssemblyTree(object parameter)
{
generateTree();
AssemblyTreeDialogWindow dialog = new AssemblyTreeDialogWindow()
{
DataContext = this,
Topmost = true
};
dialog.ShowDialog();
}
AssemblyTreeDialog Window class looks like this:
<local:TreeListView AllowsColumnReorder="True" ItemsSource="{Binding TreeList}">
<!--Create an item template to specify the ItemsSource-->
<local:TreeListView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" />
</local:TreeListView.ItemTemplate>
<local:TreeListView.Columns>
<!--Create the first column containing the expand button and the type name.-->
<GridViewColumn Header="Name" Width="200">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--The Expander Button (can be used in any column (typically the first one))-->
<local:TreeListViewExpander/>
<!--Display the name of the DataElement-->
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!--Create a second column containing the number of children.-->
<GridViewColumn Header="Children" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<!--Display the size of the DataElement-->
<TextBlock Text="{Binding Children.Count}" HorizontalAlignment="Right"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!--Create a third column containing the brush of the material.-->
<GridViewColumn Header="Brush" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--Border showing the actual color-->
<Border Background="{Binding Brush}" CornerRadius="2"
Width="16" Height="16"
BorderThickness="1" BorderBrush="DarkGray"/>
<!--Display the brush-->
<TextBlock Text="{Binding Brush}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</local:TreeListView.Columns>
<!--Create some sample data-->
<MaterialGroup>
<MaterialGroup>
<DiffuseMaterial Brush="Blue"/>
<DiffuseMaterial Brush="Red"/>
<SpecularMaterial Brush="Orange"/>
</MaterialGroup>
<EmissiveMaterial Brush="AliceBlue"/>
</MaterialGroup>
</local:TreeListView>
Interestingly if I bind the line <GridViewColumn Header="Name" Width="200"> so that it reads <GridViewColumn Header="{Binding TreeList}" Width="200">it gives me this:
I'll explain my end goal as best as possible.
The System is a giant list of parts. A main table displays all of the parts while a subtable displays all of the parts which make up that part. All parts (including those which are used to create other parts) exist within the MainTable. So a Parent part might have a set of children parts, which each individually have children parts which they are made up of. This is the relationship i'm trying to model using this tool.
The code that I've written maps the parts list out into a list of class objects which contain the data. I'll post it below. It's working to map to a TreeView right now.
A datastructure I've based it off is here : Treeview with multiple columns
private void generateTree(string PN)
{
Proces selectedRow = new Proces() { procesId = (int)Vwr.Table.SelectedRow.Row["PID"], procesName = (string)Vwr.Table.SelectedRow.Row["PN"], subProcesses = generateSubtable(PN) };
processes.Add(selectedRow);
}
public List<Proces> generateSubtable(string PN)
{
List<Proces> subTable = new List<Proces>();
foreach (DataRow mplrow in Vwr.Table.Tbl.Rows) if (mplrow["PN"].ToString() == PN)
MainVM.Modules.AllModules[0].SubVwr.Tables[0].LoadTableQuery.Prms[0].Val = mplrow[0];
MainVM.Modules.AllModules[0].SubVwr.Tables[0].Tbl = Db.GetTable(MainVM.Modules.AllModules[0].SubVwr.Tables[0].LoadTableQuery);
foreach (DataRow sub in MainVM.Modules.AllModules[0].SubVwr.Tables[0].Tbl.Rows)
{
Proces subItem = new Proces() { procesId = (int)sub["ItemNo"], procesName = sub["PN"].ToString(), subProcesses = generateSubtable(sub["PN"].ToString()) };
subTable.Add(subItem);
}
return subTable;
}
Found the answer! After some pretty extensive searching, and trying many different solutions. Thought i'd post incase someone else might also be trying to do the same.
credit:
http://dlaa.me/blog/post/9898803

Insert a String into the GridView

I'm still learning WPF and have searched for a way to do this that is not beyond my level of learning, but have not found an answer. Hopefully someone can help me out!
The purpose of my application was to be a simple check-in/check-out program. In my main application I list employees by name along with 2 buttons (in and out) as well as a comment field.
So far I've managed to open a new window when the 'In' or 'Out' buttons are clicked and prompt the user for a comment. I've managed to pass that comment back to my MainWindow, but now I am at a loss as to how to display that in the GridView.
Here is what the MainWindow.cs looks like:
private void menuIn_Click(object sender, RoutedEventArgs e)
{
var item = (sender as FrameworkElement).DataContext;
int rowNumber = lvUsers.Items.IndexOf(item) + 1;
// MessageBox.Show(String.Format("Button row is {0}", rowNumber));
string userCommentString = "";
SubWindow subWindow = new SubWindow(userCommentString, rowNumber);
subWindow.Show();
}
So the 'rowNumber' is the row that contains the comment field I want to change. The 'userCommentString' is what I want to go into that row. Is there not a simple way to insert a string into the grid if I know the row and column number?
Here is a small example how you could do that. Beside this, I would also recommend to learn MVVM.
XAML:
<ListView x:Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="Comment" Width="150" DisplayMemberBinding="{Binding Path=Comment}" />
<GridViewColumn Header="Button">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<Button Content="Click" Click="Button_Click" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Add items to ListView
lvUsers.Items.Add(new Test() { Name = "User A" });
lvUsers.Items.Add(new Test() { Name = "User B" });
lvUsers.Items.Add(new Test() { Name = "User C" });
Manipulate row on button click:
private void Button_Click(object sender, RoutedEventArgs e)
{
//Get row number
var item = (sender as FrameworkElement).DataContext;
int rowNumber = lvUsers.Items.IndexOf(item);
//Use row number to manipulated the right row
//This should be done after comment is passed back to MainWindow
Test t = (Test)lvUsers.Items[rowNumber];
t.Comment = "new comment";
lvUsers.Items.Refresh();
}
Test class:
public class Test
{
public string Name { get; set; }
public string Comment { get; set; }
}

C# Listview DisplayMemberBinding shows "wrong" value

In my WPF app I have a Listview with a couple columns and one column with a checkbox inside.
The xaml is as follows:
<ListView x:Name="listviewImported">
<ListView.View>
<GridView>
<GridViewColumn Header="Check" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
/*This line may be the prob.*/ <CheckBox IsChecked="{Binding MarkedForCheck}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Pos." Width="75" DisplayMemberBinding="{Binding PositionAsString}" />
<GridViewColumn Header="Value" Width="175" DisplayMemberBinding="{Binding ImportValue}" />
<GridViewColumn Header="Type" Width="175" DisplayMemberBinding="{Binding ImportValueTypeDescription}" />
</GridView>
</ListView.View>
</ListView>
The ItemSource is assigned like this:
listviewImported.ItemsSource = _catalog.ImportedList;
The ImportedList in the _catalog is of Type ImportedElem:
private List<ImportedElem> _importedList = new List<ImportedElem>();
This is the ImportedElem class:
public class ImportedElem : BaseElem {
public ImportedElem(int Position, string ImportValue) : base(Position, ImportValue) {
}
//MARKED FOR CHECK
public bool MarkedForCheck = true;
}
This is the problem:
When I add an ImportedElem to the List, then the Listview will update and the columns "Pos.", "Value" and "Type" contain the correct Data. However, the Column with the checkbox ("Check") always shows a nonchecked Checkbox after adding it to the List. The Data is correct, the column just shows the wrong Data. I can modify the checkbox in the list and it will update the Data, that is good. But the List should contain the correct Data right after adding the Element to the list, wich it is not doing. It would be very interesting to know why 3 columns are correct and the checkbox doesn't work.
change public bool MarkedForCheck = true; TO public bool MarkedForCheck {get;set;}

Get the selected value

I am attempting to pull information from my listview but cannot seem to select a specific dataset. When pulling information from this I always get the entire contents back but can never read just one set.
I will be pulling the row data as:
chartViewList2.SelectedItem
I added information to the list view via
private void getServices(string ComputerName)
{
chartListView2.Items.Clear();
ServiceController[] services = ServiceController.GetServices(ComputerName);
foreach (ServiceController service in services)
{
chartListView2.Items.Add(new { Service = service.ServiceName, Status = service.Status, Desc = service.DisplayName });
}
}
List View XML:
ListView x:Name="chartListView2" Margin="12,59,46,6"
GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler2">
<ListView.View>
<GridView>
<GridViewColumn Header="Service" DisplayMemberBinding="{Binding Service}" Width="100"/>
<GridViewColumn Header="Status" DisplayMemberBinding="{Binding Status}" Width="60"/>
<GridViewColumn Header="Desc" DisplayMemberBinding="{Binding Desc}" Width="190"/>
</GridView>
</ListView.View>
</ListView>
All I want to get from this is the Service name field back.
Any help will be appreciated!
Reflection may help here.
var item = chartViewList2.SelectedItem;
var type = item.GetType();
var propertyInfo = type.GetProperty("Service");
var value = propertyInfo.GetValue(item, null);
Then you will have a value of Service field in 'value' variable.
Since .NET 4 and dynamic keyword even shorter way exists:
dynamic item = chartViewList2.SelectedItem;
dynamic propertyValue = item.Service;
If you are binding your ListView to a List<TypedObject> then you can do this:
var item = chartViewList2.SelectedItem as TypedObject
You can set the ListView SelectedvaluePath to Service and bind to your model or access using the ListView SelectedValue.
Example:
<ListView x:Name="chartListView2" SelectedValuePath="Service" SelectedValue="{Binding SelectedServiceName}" >

Dynamically Adding Items To a WPF List View

I am trying to programatically add items to a ListView in WPF. I have done a lot of reading (including some questions here) and thought I was doing it correctly but the items aren't showing up. As I understand it I create the ListViewe and bind it to a data source, in this case an ObservableCollection. I have verified the ObservableCollection is getting items added to it, but they aren't getting displayed on the ListView. If it matters, the ListView is already instantiated by the time I run the LINQ query and attempt to add items to it.
Here is the XAML that defines the list view:
<TabPanel Name="ResultsTab" Height="200" Width ="500" DockPanel.Dock="Top" HorizontalAlignment="Left">
<TabItem Name="Default_Tab" Header="Default">
<ListView Name="DefaultListView" ItemsSource="Binding FCPortCollection">
<ListView.View>
<GridView x:Name="DefaultGridView">
<GridViewColumn Width="Auto" Header="FC Port" DisplayMemberBinding="{Binding Path=FCPort}" />
<GridViewColumn Width="Auto" Header="WWPN" DisplayMemberBinding="{Binding Path=WWPN}"/>
<GridViewColumn Width="Auto" Header="FCID" DisplayMemberBinding="{Binding Path=FCID}" />
<GridViewColumn Width="Auto" Header="SwitchName" DisplayMemberBinding="{Binding Path=SwitchName}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</TabItem>
And here is the code that is supposed to load it.
public class PortResult
{
public string SwitchName;
public string FCPort;
public string FCID;
public string WWPN;
public PortResult(string name, FCPort port)
{
SwitchName = name;
FCPort = String.Format("fc{0}/{1}", port.SlotNum, port.PortNum);
WWPN = port.WWPNList[0].WWPNValue;
FCID = port.WWPNList[0].FCIDValue;
}
}
ObservableCollection<PortResult> FCPortCollection = new ObservableCollection<PortResult>();
// results is an IEnumerable collection of FCPort result from a LINQ query that has been turned into a Dictionary
foreach (KeyValuePair<string, List<FCPort>> resultspair in results)
{
foreach (FCPort port in resultspair.Value)
{
// create a new PortResult and add it to the ObservableCollection
PortResult pr = new PortResult(resultspair.Key, port);
FCPortCollection.Add(pr);
}
}
There are several problems in the code you posted:
The binding syntax for your ItemsSource is missing the {} braces - it needs to be ItemsSource="{Binding FCPortCollection}"
You can only bind to properties, however you only expose fields in your PortResult class. Change those fields to be properties.
Also make sure the DataContext of the ListView is set to the object which contains the FCPortCollection. Also make sure the collection is a property of the object and not a field (same reason as point 2. above).
This:
ItemsSource="Binding FCPortCollection"
Is not a binding, you forgot the braces {} and hence assigned a char[] as ItemsSource instead.

Categories

Resources