I can add column in ListView, but it is so difficult to add an item in ListView. Here is my code:
Dim myGridView As New GridView
myGridView.AllowsColumnReorder = True
myGridView.ColumnHeaderToolTip = "Employee Information"
Dim gvc1 As New GridViewColumn
gvc1.DisplayMemberBinding = New Binding("FirstName")
gvc1.Header = "FirstName"
gvc1.Width = 100
myGridView.Columns.Add(gvc1)
Dim gvc2 As New GridViewColumn
gvc2.DisplayMemberBinding = New Binding("LastName")
gvc2.Header = "Last Name"
gvc2.Width = 100
myGridView.Columns.Add(gvc2)
Dim gvc3 As New GridViewColumn()
gvc3.DisplayMemberBinding = New Binding("EmployeeNumber")
gvc3.Header = "Employee No."
gvc3.Width = 100
listview.View = myGridView
I just created ListViewItem.add and then used the subitem to add more items in a row. But now it's different. How can I add item in listview in WPF WITHOUT creating a new class, because the number of the columns of data is dynamic.
EDIT:
I had been surfing the internet for hours but found nothing of great help.
Everywhere, it is done using class with predefined numbers of column, while I want to add column item based on database which should have different Ipnumber of columns.
You should initialize the object collection in your code, for example Employees.
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Public Class EmployeeInformation
Public Property FirstName As String
Public Property LastName As String
Public Property EmployeeNumber As Integer
End Class
Class MainWindow
Public Property Employees As ObservableCollection(Of EmployeeInformation)
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
InitEmploeesCollection(10)
DataContext = Me
End Sub
Private Sub InitEmploeesCollection(count As Integer)
Employees = New ObservableCollection(Of EmployeeInformation)()
For index = 1 To count
Employees.Add(New EmployeeInformation() With {
.FirstName = "FirstName" & index,
.LastName = "LastName" & index,
.EmployeeNumber = index})
Next
End Sub
End Class
And then you can simply bind it to the ListView:
<ListView ItemsSource="{Binding Path=Employees}">
<ListView.View>
<GridView AllowsColumnReorder="True" ColumnHeaderToolTip="Employee Information">
<GridViewColumn Header="First Name" Width="100" DisplayMemberBinding="{Binding Path=FirstName}"/>
<GridViewColumn Header="Last Name" Width="100" DisplayMemberBinding="{Binding Path=LastName}"/>
<GridViewColumn Header="Employee No." Width="100" DisplayMemberBinding="{Binding Path=EmployeeNumber}"/>
</GridView>
</ListView.View>
</ListView>
To initialize columns dynamically, you can add the code from your question. It doesn't make difference if you are taking data from database. Just fill in Employees collection and bind it to the ListView.
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Class MainWindow
Public Property Employees As ObservableCollection(Of EmployeeInformation)
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
InitEmploeesCollection(10)
SetGridViewDynamically()
DataContext = Me
End Sub
Private Sub InitEmploeesCollection(count As Integer)
Employees = New ObservableCollection(Of EmployeeInformation)()
For index = 1 To count
Employees.Add(New EmployeeInformation() With {
.FirstName = "FirstName" & index,
.LastName = "LastName" & index,
.EmployeeNumber = index})
Next
End Sub
Private Sub SetGridViewDynamically()
Dim myGridView As New GridView
myGridView.AllowsColumnReorder = True
myGridView.ColumnHeaderToolTip = "Employee Information"
Dim gvc1 As New GridViewColumn
gvc1.DisplayMemberBinding = New Binding("FirstName")
gvc1.Header = "FirstName"
gvc1.Width = 100
myGridView.Columns.Add(gvc1)
Dim gvc2 As New GridViewColumn
gvc2.DisplayMemberBinding = New Binding("LastName")
gvc2.Header = "Last Name"
gvc2.Width = 100
myGridView.Columns.Add(gvc2)
Dim gvc3 As New GridViewColumn()
gvc3.DisplayMemberBinding = New Binding("EmployeeNumber")
gvc3.Header = "Employee No."
gvc3.Width = 100
myGridView.Columns.Add(gvc3)
ListView1.View = myGridView
End Sub
End Class
Public Class EmployeeInformation
Public Property FirstName As String
Public Property LastName As String
Public Property EmployeeNumber As Integer
End Class
This way XAML will look like this.
<ListView Name="ListView1" ItemsSource="{Binding Path=Employees}"/>
Dim row As String() = New String() {"John", "Doe", "1"}
myGridView.Rows.Add(row)
Is this what you're looking for?
XAML part of your program must be like be this:
<ListView x:Name="lvPencere" HorizontalAlignment="Left" Height="156" Grid.Row="1" VerticalAlignment="Top" Width="309">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="PencereSN" Width="0" DisplayMemberBinding="{Binding PencereSN}"/>
<GridViewColumn Header="Pencere Adı" Width="300" DisplayMemberBinding="{Binding PencereAD}"/>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
And your VB part must be like this:
Listview.Items.Add(New With {Key .PencereSN = "some string", Key .PencereAD = "some string"})
Related
I have a databound ComboBox that is linked to an Collection of custom objects...
Public Property printerlist As New ObservableCollection(Of Printers)
[..]
Dim PrintersList = New List(Of Printers)
'WMI Stuff
Dim objMS As System.Management.ManagementScope = New System.Management.ManagementScope(ManagementPath.DefaultPath)
objMS.Connect()
'Query Printers
Dim objquery As SelectQuery = New SelectQuery("SELECT * FROM Win32_Printer")
Dim objMOS As ManagementObjectSearcher = New ManagementObjectSearcher(objMS, objquery)
Dim objMOC As System.Management.ManagementObjectCollection = objMOS.Get()
Try
For Each Printers As ManagementObject In objMOC
If CBool(Printers("Local")) Then
PrintersList.Add(New Printers With {.DeviceName = Printers("Name"), .Type = "Local"})
End If
If CBool(Printers("Network")) Then
PrintersList.Add(New Printers With {.DeviceName = Printers("Name"), .Type = "Network"})
End If
Next
Catch ex As Exception
Debug.Print(ex.Message)
End Try
Dim LCV As ListCollectionView = New ListCollectionView(PrintersList)
Printer_Select.ItemsSource = LCV
[..]
Public Class Printers
Public Property DeviceName As String
Public Property Type As String
End Class
<ComboBox x:Name="Printer_Select" Background="{x:Null}" Padding="4,5,4,3" BorderBrush="Gainsboro" >
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="11" FontFamily="Segoe UI Semibold"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DeviceName}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
When I try to retrieve the SelectedItem of the ComboBox 'Printer_Select', I either get the name of the element or the error 'Conversion from type 'Printers' to type 'String' is not valid.'
How do I get the DeviceName of the ComboBoxItem when it's selected in the dropdown?
Cast the SelectedItem to a Printers object:
Dim selectedPrinter As Printers = TryCast(Printer_Select.SelectedItem, Printers)
If selectedPrinter IsNot Nothing Then
Dim deviceName As String = selectedPrinter.DeviceName
'...
End If
I'm new to WPF/MVVM and have been attempting to bind an observable collection class to a list view, no code behind, just MVVM.
The XAML:
<Window.DataContext>
<local:acLengthCalcVM/>
</Window.DataContext>
<StackPanel>
<GroupBox Header="Section(s) Selected" Margin="3,10,3,10">
<ListView x:Name="lvSections" Margin="0,6,0,2" Height="150" ItemsSource="{Binding SectionsSelected}">
<ListView.View>
<GridView>
<GridViewColumn Header="#" Width="30"/>
<GridViewColumn Header="Reference Tag #" Width="100"/>
<GridViewColumn Header="Section Type" Width="150"/>
<GridViewColumn Header="Section Length" Width="100"/>
</GridView>
</ListView.View>
</ListView>
</GroupBox>
</StackPanel>
The View Model:
Public Class acLengthCalcVM
Implements INotifyPropertyChanged
Dim _sectionsSelected As ObservableCollection(Of acLengthCalcModel) = New ObservableCollection(Of acLengthCalcModel)
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property SectionsSelected As ObservableCollection(Of acLengthCalcModel)
Get
Return _sectionsSelected
End Get
Set(value As ObservableCollection(Of acLengthCalcModel))
_sectionsSelected = value
NotifyPropertyChanged("SectionsSelected")
End Set
End Property
Private Sub NotifyPropertyChanged(Optional propertyName As String = "")
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
The Model:
Public Class acLengthCalcModel
Inherits ObservableCollection(Of acLengthCalcData)
Public Sub New()
Add(New acLengthCalcData(1, "n/a", "VTU Frame", 8))
Add(New acLengthCalcData(2, "1.1.01", "Air Conveyor Straight", 118))
End Sub
End Class
Public Class acLengthCalcData
' Constructor for this Class
Public Sub New(serialNum As Integer, refTagNum As String, sectionType As String, sectionLength As Double)
_refTagNum = refTagNum
_sectionLength = sectionLength
_sectionType = sectionType
_serialNum = serialNum
End Sub
' Reference Tag #
Private _refTagNum As String
Public Property RefTagNum As String
Get
Return _refTagNum
End Get
Set(value As String)
_refTagNum = value
End Set
End Property
' Section Length
Private _sectionLength As Double
Public Property SectionLength As Double
Get
Return _sectionLength
End Get
Set(value As Double)
_sectionLength = value
End Set
End Property
' Section Type
Private _sectionType As String
Public Property SectionType As String
Get
Return _sectionType
End Get
Set(value As String)
_sectionType = value
End Set
End Property
' Serial #
Private _serialNum As Integer
Public Property SerialNum As Integer
Get
Return _serialNum
End Get
Set(value As Integer)
_serialNum = value
End Set
End Property
End Class
When I run the code, the list view is created with only its headers, none of the data I initialized in the model constructor shows up.
Any pointers? Thanks.
You should create and return an instance of a acLengthCalcModel in your view model class:
Public Class acLengthCalcVM
Implements INotifyPropertyChanged
Dim _sectionsSelected = New acLengthCalcModel
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property SectionsSelected As acLengthCalcModel
Get
Return _sectionsSelected
End Get
Set(value As acLengthCalcModel)
_sectionsSelected = value
NotifyPropertyChanged("SectionsSelected")
End Set
End Property
Private Sub NotifyPropertyChanged(Optional propertyName As String = "")
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
You also need to bind each of the columns to a property of the acLengthCalcData class using the DisplayMemberBinding property:
<ListView x:Name="lvSections" Margin="0,6,0,2" Height="150" ItemsSource="{Binding SectionsSelected}">
<ListView.View>
<GridView>
<GridViewColumn Header="#" DisplayMemberBinding="{Binding SerialNum}" Width="30"/>
<GridViewColumn Header="Reference Tag #" DisplayMemberBinding="{Binding RefTagNum}" Width="100"/>
<GridViewColumn Header="Section Type" DisplayMemberBinding="{Binding SectionType}" Width="150"/>
<GridViewColumn Header="Section Length" DisplayMemberBinding="{Binding SectionLength}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
I've tried almost all the soulutions provided on stackoverflow and can't seem to get it to work. Am new to wpf and mvvm and was trying to bind a datatable to a listview and below is my code.
//code for viewmodel
public DataTable RetrieveDetails
{
get
{
DataTable users = new DataTable();
string dataBaseName = "name.db3";
using (SQLiteConnection connection = new SQLiteConnection("Data Source=" + dataBaseName + "; Version=3;"))
{
var details = "SELECT * FROM users";
connection.Open();
SQLiteCommand cmd = new SQLiteCommand(details, connection);
cmd.ExecuteNonQuery();
SQLiteDataAdapter adapter = new SQLiteDataAdapter(details, connection);
adapter.SelectCommand.CommandTimeout = 120;
DataSet ds = new DataSet();
adapter.Fill(ds);
return ds.Tables[0];
}
}
}
And below is how i bind the datatable to my list view
<ListView x:Name="FormOneView" ItemsSource="{Binding Path=RetrieveDetails}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding FirstName}" Header="First Name" />
</GridView>
</ListView.View>
</ListView>
I know this is supposed to be simple but am having a hard time displaying the data.
Try this approach:
<ListView x:Name="FormOneView" ItemsSource="{Binding}" DataContext="{Binding Path=allData}" HorizontalAlignment="Left" Height="100" Margin="243,289,0,0" VerticalAlignment="Top" Width="191">
<ListView.View>
<GridView>
<GridViewColumn Header="Time" Width="50" DisplayMemberBinding="{Binding Path=Tid}"/>
<GridViewColumn Header="Acceleration" Width="70" DisplayMemberBinding="{Binding Path=Acceleration}"/>
</GridView>
</ListView.View>
</ListView>
in this XAML I'm giving you an example of a datatable with two columns. I'm binding my ListView columns to my sql table column headers using DisplayMemberBinding.
Your backgournd code is actually fine, but you need to bind the DataContext of your ListView using propertyChangedEventHandler. Below is an example of how to do such a binding:
Add following two lines in your method:
adapter.Fill(ds, "users");
allData = ds.Tables["users"].DefaultView;
Then add following methods in your class. Your class should inherit INotifyPeropertyChanged
(yourClassName : INotifyPeropertyChanged) otherwise it won't work.
private DataView _allData;
public DataView allData
{
get { return _allData; }
set
{
if (value != _allData)
{
_allData = value;
OnPropertyChanged("allData");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
the result:
in order for the above code to work, need to add the following line of code
listview.datacontext = class holding alldata.allData
I currently have a Listview box with 3 comboboxes. I am populating them with from a sql database. For each row, I want to have the 3rd combobox change it's contents based on the selected values of the 2nd combobox.
The comboboxes will be: cmbx1 (employee[jack, jill, tom, lisa]), cmbx2(products[pen, pencil, stapler]), cmbx3(color - will be dynamic based on what color is available for the product)
product and color options: pen[red, blue, black]; pencil[black, orange, red]; stapler[pink, teal, purple, brown]
If for Row1, the user selects a pen, then only the available colors for that product will be listed in the color combobox for that row. The next row could have a different color option based on the product selected.
Is this possible or should i find another way to achieve the results?
here's what a currently have...
<ListView.View>
<GridView>
<GridViewColumn Header="Employee" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding lStrEmployee}" Width="120" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Product" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding lStrProduct}" Width="120" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Color" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding lStrColor}" Width="120" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</ListView.View>
code behind
List<Int32> liEmployee = new List<Int32>();
List<string> lsEmployee = new List<string>();
List<Int32> liProduct = new List<Int32>();
List<string> lsProduct = new List<string>();
List<Int32> liColor = new List<Int32>();
List<string> lsColor = new List<string>();
SqlConnection conn = new SqlConnection("Data Source=localhost\\SQLEXPRESS;Initial Catalog=testDB;Persist Security Info=True;User ID=USER;Password=PASSWORD;");//Connect Timeout=900
SqlCommand cmd1 = new SqlCommand("select id,employee from testDB.dbo.dmEmployee where inactive=0", conn);
SqlCommand cmd2 = new SqlCommand("select id,Product from testDB.dbo.tblProductList where inactive=0", conn);
SqlCommand cmd3 = new SqlCommand("select id,Color from testDB.dbo.Color where inactive=0", conn);
conn.Open();
SqlDataReader dr1 = cmd1.ExecuteReader();
while (dr1.Read())
{
liEmployee.Add(dr1.GetInt32(dr1.GetOrdinal("id")));
lsEmployee.Add(dr1.GetString(dr1.GetOrdinal("employee")));
}
conn.Close();
conn.Open();
SqlDataReader dr2 = cmd2.ExecuteReader();
while (dr2.Read())
{
liProduct.Add(dr2.GetInt32(dr2.GetOrdinal("id")));
lsProduct.Add(dr2.GetString(dr2.GetOrdinal("Product")));
}
conn.Close();
conn.Open();
SqlDataReader dr3 = cmd3.ExecuteReader();
while (dr3.Read())
{
liColor.Add(dr3.GetInt32(dr3.GetOrdinal("id")));
lsColor.Add(dr3.GetString(dr3.GetOrdinal("Color")));
}
conn.Close();
List<lvItem> itemFound = new List<lvItem>();
itemFound.Clear();
lvItem puzzlePieces;
for (int cnt = 0; cnt < 10; cnt++)
{
puzzlePieces = new lvItem();
puzzlePieces.lStrEmployee = lsEmployee;
puzzlePieces.lStrDatabase = lsDatabase;
puzzlePieces.lStrProvider = lsProvider;
itemFound.Add(puzzlePieces);
}
list1.ItemsSource = itemFound;
Thanks!
I'm surprised that you didn't get any answers to your question. Maybe it's because you don't seem to be doing things the WPF way, or maybe because you're asking for so much?
First things first... you need to create a data type class that implements the INotifyPropertyChanged interface and contains all of the properties required for display in each row of the ListView. In your case, you need three collections and three selected item values. As an example, you could do something like this (implementing the INotifyPropertyChanged interface yourself):
public class RowData : INotifyPropertyChanged
{
public ObservableCollection<Employee> Employees { get; set; }
public Employee SelectedEmployee { get; set; }
public ObservableCollection<Product> Products { get; set; }
public Product SelectedProduct { get; set; }
public ObservableCollection<Brush> Colours { get; set; }
public Brush SelectedColour { get; set; }
}
Note the use of the Brush class rather than the Color struct, this is because Brush is a class, which means that we can bind to it and also because it is more predominantly used in WPF.
However, it is not optimal having the same collections in every object in every row, except for the Colours collection, which could be different for each row. Having said that, that is exactly what I'm going to do because it will be quicker for me to explain and you can improve your code yourself at a later stage:
So now you have your data type class, we need to add a property of that type to bind to your ListView control. If you are using the code behind of your MainWindow, then let's create a DependencyProperty for it:
public static readonly DependencyProperty RowDataProperty = DependencyProperty.
Register("RowData", typeof(ObservableCollection<RowData>), typeof(MainWindow),
new UIPropertyMetadata(new ObservableCollection<RowData>()));
public ObservableCollection<RowData> RowData
{
get { return (ObservableCollection<RowData>)GetValue(RowDataProperty); }
set { SetValue(RowDataProperty, value); }
}
After filling your collection, you can now bind it to the ListView control:
xmlns:Local="clr-namespace:YourWpfApplicationName"
...
<ListView ItemsSource="{Binding RowData, RelativeSource={RelativeSource AncestorType={
x:Type Local:MainWindow}}}">
...
</ListView>
In short, the RelativeSource Binding is simply looking for the property you defined in the code behind. Now, how to define that a ComboBox should appear in each GridViewColumn? You need to define the GridViewColumn.CellTemplate:
<GridViewColumn Header="Employees">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Employees}" SelectedItem="{Binding
SelectedEmployee}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
You'll need to define the other columns from this example. So the final part of this puzzle is how to update the content of the Colours ComboBox dependent on the selected values of the other ComboBoxes? The answer lies in your selected value properties in your RowData class:
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
selectedEmployee = value;
NotifyPropertyChanged(SelectedEmployee);
Colours = GetColours();
}
}
private ObservableCollection<Brush> GetColours()
{
ObservableCollection<Brush> newColours = new ObservableCollection<Brush>();
if (SelectedEmployee.Name == "Some Name" && SelectedProduct.Name ==
"Some Product") newColours.AddRange( new List<Brush>() { Brushes.Red,
Brushes.White, Brushes.Blue } );
else ...
}
There are many ways to do this and I'll leave that up to you. You should now have a working example and I now realise why nobody had answered your question... far too much for anyone sane to type! After spending so long on this, I would appreciate it if you try to solve any minor problems you find with this on your own and hope it helps you.
I have the following in my dgridtext.xaml file.
XAML
<Grid x:Name="grid1">
<StackPanel>
<ListView Name="listview1" IsTextSearchEnabled="True" TextSearch.TextPath="Enquiry_Number">
<ListView.View>
<GridView ColumnHeaderToolTip="Multiple Category Information">
<GridViewColumn DisplayMemberBinding="{Binding Path=Enquiry_Number}" Header="Enquiry number"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Consignee_Ref}" Header="Consignee reference"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Booking_Reference}" Header="Booking reference"/>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Grid>
Something like this.
dgridtest.xaml.cs
for (int i = 0; i < listview1.Items.Count; i++)
{
MessageBox.Show(listview1.Items[i].ToString());
}
But all that returns is System.Data.DataRowView
Please help.
Update:
In My dgridtextxaml.cs file, I call the DataManager.cs class passing a dataset object which is the source of my listview(listview1).
DataManager.BindFilteredData(dts);
listview1.ItemsSource = dts.Tables[0].DefaultView;
And this is what I have in my DataManager.cs class
public static void BindFilteredData(DataSet dts)
{
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ConString"].ConnectionString))
{
string sql = "SELECT Enquiry_Number, Consignee_Ref, Booking_Reference FROM ConsHead";
using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connection))
{
adapter.Fill(dts);
}
}
}
You can cast the item to whatever class you use in the ListView and then use any property you need from it. Something like:
var item = listView1.Items[i] as YourClassHere;
Edit
In your case, since you bind the ItemsSource to a DataSet directly you probably can use DataRowView class, and then use the properties from it:
var firstItem = listview1.Items[0] as DataRowView;
var firstCellValue = firstItem.Row[0];