Bind datatable to listview wpf mvvm - wpf

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

Related

An ObservableCollection of StackPanel as ItemsSource of ComboBox in WPF

I am having problem binding ObservableCollection as ItemsSource to a combo box (this combobox is in a listview of having some rows).
I followed A collection of StackPanel as ItemsSource of ComboBox but I did not get any clues for resolving my problem.
Problem:
I was able to add items to a combobox which is at the top on the form.
I have created a listview containing 3 text blocks and 1 combobox.
I am successful in populating data for the text blocks in listview.
But the problem lies with Combobox. First time, it shows all the items for each row in ListView. Once I select item or click on the combobox again to see the items, my list disappears. Only one combobox in the listview row shows all items. Other comboboxes shows blank.
Also, I was trying to save the index of the selected item and show the selected panel next time. But I did not get how to bind the stackpanel with selecteditem and selecteditemvalue.
I tried many ways of directly binding the items to the combobox in listview. But nothing worked. Request someone to help me on this.
Details of the code is given below:
I have XAML like below:
<Grid>
<Grid Height="40">
<ComboBox x:Name="cbList" />
</Grid>
<Grid Margin="0,56,0,168"></Grid>
<ListView x:Name="lvFirst" Margin="0,195,0,12">
<ListView.View>
<GridView >
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Width="50" x:Uid="tbListView1" Text="{Binding FirstName}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Width="50" x:Uid="tbListView2" Text="{Binding LastName}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Width="50" x:Uid="tbListView1" Text="{Binding ID}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox Width="100" x:Uid="cbListView" ItemsSource="{Binding Path=SPForComboBox}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
In the code behind I have a Contact class as below:
public class Contact : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void Notify(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
private string _fn;
private string _ln;
public string FirstName
{
get
{ return _fn; }
set
{ _fn = value; Notify("FirstName"); }
}
public string LastName
{
get
{ return _ln; }
set
{ _ln = value; Notify("LastName"); }
}
private int _id;
public int ID
{
get { return _id; }
set { _id = value; Notify("ID"); }
}
public StackPanel sp;
public override string ToString()
{
return FirstName + " " + LastName;
}
private ObservableCollection<StackPanel> _sp;
public ObservableCollection<StackPanel> SPForComboBox
{
get { return _sp; }
set { _sp = value; Notify("SPForComboBox"); }
}
}
To populate cbList Items, I am repeatedly calling below function after Initialization() function:
private void AddColours(string name, byte hexcolor)
{
//Add this ShiftType to Combo box
SolidColorBrush rectangleBrush = new SolidColorBrush();
Color color = new Color();
color.A = hexcolor;
rectangleBrush.Color = color;
System.Windows.Controls.StackPanel stkPanel = new StackPanel(); //stack panel to hold rectangle + text
stkPanel.Orientation = Orientation.Horizontal;
cbList.Items.Add(stkPanel);
Rectangle colorRect = new Rectangle(); //rectangle showing colour for shift
colorRect.Height = 12;
colorRect.Width = colorRect.Height;
colorRect.Fill = rectangleBrush;
stkPanel.Children.Add(colorRect);
System.Windows.Controls.TextBlock cboText = new TextBlock(); //Name of shift
cboText.Text = name;
cboText.Margin = new Thickness(5, 5, 5, 5);
stkPanel.Children.Add(cboText);
}
In the main window class, I have a created an ObservableCollection object as public static (object name is contacts).
public static ObservableCollection<Contact> contacts = new ObservableCollection<Contact>();
After the Initialization() function, I am populating contacts as below:
AddColours("First", 100);
AddColours("Second", 50);
AddColours("Third", 20);
AddColours("Fourth", 0);
AddColours("Fifth", 80);
Contact c1 = new Contact();
c1.FirstName = "Digo";
c1.LastName = "Maradona";
c1.ID = 0;
c1.SPForComboBox = new ObservableCollection<StackPanel>();
foreach (StackPanel sp in cbList.Items)
{
c1.SPForComboBox.Add(sp);
}
Contact c2 = new Contact();
c2.FirstName = "Brian";
c2.LastName = "Lara";
c2.ID = 1;
c2.SPForComboBox = new ObservableCollection<StackPanel>();
foreach (StackPanel sp in cbList.Items)
{
c2.SPForComboBox.Add(sp);
}
Contact c3 = new Contact();
c3.FirstName = "Sachin";
c3.LastName = "Tendulkar";
c3.ID = 2;
c3.SPForComboBox = new ObservableCollection<StackPanel>();
foreach (StackPanel sp in cbList.Items)
{
c3.SPForComboBox.Add(sp);
}
contacts.Add(c1);
contacts.Add(c2);
contacts.Add(c3);
lvFirst.ItemsSource = contacts;
HighCore, Thank you very much for the links. I have existing implementation and adding combobox to that.
I too felt that the method followed is not good. I shall certainly look at the alternatives provided by you and suggested by pushpraj.
Answer:
I thought referring objects in other combobox will work till the items exist in that combobox. But I need to create rectangle and textblock for reach combobox I am creating and for each entry in that combobox.
So certainly I need to do it in foreach loop.
Also, once I do this I can use SelectedIndex referring to the integer and SelectedItem to sp (individual stackpanel in that class).
This method is not good to follow but might be helpful for somebody.
Thanks.

Dynamically update combobox itemsource in listview per row

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.

wpf xaml loop thru a gridview which is inside a listview and get the cell value

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

Making databound ListViewColumns resize when adding an item

I'm making an WPF-application that has a databound ListView. When loading the application, the width of the ListViewColumns will auto-resize, but when adding or changing an item it doesn't auto-resize. I've tried refreshing the listview, setting the width of the column to auto, -1 or -2 in both xaml and VB-code and I've tries changing the itemssource to nothing before refilling it with items. This is the xaml code:
<ListView x:Name="lsvPersons" Margin="5,5,5,35" ItemsSource="{Binding Persons}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Header="Name"/>
<GridViewColumn DisplayMemberBinding="{Binding Gender}" Header="Gender"/>
<GridViewColumn DisplayMemberBinding="{Binding Age}" Header="Age"/>
</GridView>
</ListView.View>
</ListView>
<Button x:Name="btnAddPerson" Content="Add" Height="25" Margin="0,0,200,5" Width="80"/>
The binding works with a controller, which gets the persons with Person.getPersons from a SQL-database:
Private oController As New MainController()
Public Sub New()
MyBase.New()
Me.InitializeComponent()
Me.DataContext = oController
End Sub
After pressing the button, a window will open to add a person. After the window is closed, the item is added to the listview with following code, which refreshes the items in the listview:
lsvPersons.ItemsSource = Person.getPersons()
So, what do I need to do to auto-resize the columns of the listview when an item is added or editted?
GridView gv = lvSrchResulsGrid.View as GridView;
if (gv != null)
{
int colNum = 0;
foreach (GridViewColumn c in gv.Columns)
{
// Code below was found in GridViewColumnHeader.OnGripperDoubleClicked() event handler (using Reflector)
// i.e. it is the same code that is executed when the gripper is double clicked
// if (adjustAllColumns || App.StaticGabeLib.FieldDefsGrid[colNum].DispGrid)
// if (adjustAllColumns || fdGridSorted[colNum].AppliedDispGrid)
// {
if (double.IsNaN(c.Width))
{
c.Width = c.ActualWidth;
}
c.Width = double.NaN;
// }
}
}

How can I bind Wpf DataGridColumn to an object?

I want to bind the columns of my WPF DataGrid to some objects in a Dictionary like this:
Binding Path=Objects[i]
where Objects is my Dictionary of objects, so that each cell will represent an Object element. How can I do that?
I suppose that I need to create a template for my cell, which I did, but how to get the result of column binding in my template? I know that by default the content of a DataGridCell is a TextBlock and it's Text property is set through column binding result, but if that result is an object I guess that I have to create a ContentTemplate. How do I do that, as the stuff I tried is not displaying anything.
Here it is what I tried:
<Style x:Key="CellStyle" TargetType="{x:Type dg:DataGridCell}">
<Setter Property="Template"> ---it should realy be ContentTemplate?
<Setter.Value>
<ControlTemplate>
<controls:DataGridCellControl CurrentObject="{Binding }"/> -- I would expect to get the object like this for this column path : Path=Objects[i] but is not working
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So, to make myself completly clear, i want to get in CurrentObject property of my DataGridCellControl the current object that should result if I set the column binding in my data grid like this Path=Objects[i].
Thank you for any suggestion,
John.
Try this:
<ListView x:Name="listViewUsers" SelectionMode="Single"
ItemsSource="{Binding ElementName=window1, Path=Users, Mode=TwoWay}" MouseDoubleClick="listViewUsers_MouseDoubleClick">
<ListView.View>
<GridView x:Name="gridViewUsers" AllowsColumnReorder="False">
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=IsAdministrator, Converter={StaticResource boolToImage}, ConverterParameter='Images/admin18.gif|Images/user18.gif'}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="User Name" DisplayMemberBinding="{Binding Path=UserName}" Width="140" />
<GridViewColumn Header="Full Name" DisplayMemberBinding="{Binding Path=FullName}" Width="140" />
<GridViewColumn Header="Phone Number" DisplayMemberBinding="{Binding Path=PhoneNumber}" Width="110" />
<GridViewColumn Header="Access Type" DisplayMemberBinding="{Binding Path=AccessType}" Width="110">
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Cursor="Hand" ToolTip="Delete User" Stretch="None" Source="Images/trash12.gif" MouseUp="DeleteUser" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Where in ItemsSource="{Binding ElementName=window1, Path=Users, Mode=TwoWay}"
ElementName is the name of the Window in XAML (just add x:Name="window1" to the Window tag as with any other ontrol.
Users is a List, should work the same with Dictionary
Mode=TwoWay means that if the grid gets modified, the list will get modified too, and vice versa (Two way binding)
EDIT:
Try this:
XAML:
<ListView x:Name="listViewTest" ItemsSource="{Binding}">
<ListView.View>
<GridView x:Name="gridViewTest">
</GridView>
</ListView.View>
</ListView>
C#:
public class TheClass
{
public int Col1, Col2, Col3;
public Dictionary<int, OtherColumns> otherColumns = new Dictionary<int,OtherColumns>();
}
public class OtherColumns
{
public string ColumnName;
public int Value;
}
And call this method under Window_Loaded:
private void PopulateListView()
{
TheClass c = new TheClass();
c.Col1 = 10;
c.Col2 = 20;
c.Col3 = 30;
c.otherColumns.Add(0, new OtherColumns() { ColumnName = "Col4", Value = 40 });
c.otherColumns.Add(1, new OtherColumns() { ColumnName = "Col5", Value = 50 });
c.otherColumns.Add(3, new OtherColumns() { ColumnName = "Col6", Value = 60 });
DataTable table = new DataTable();
// adding regular columns
table.Columns.Add("Col1", typeof(int));
table.Columns.Add("Col2", typeof(int));
table.Columns.Add("Col3", typeof(int));
// adding dynamic columns
foreach (KeyValuePair<int, OtherColumns> pair in c.otherColumns)
{
table.Columns.Add(pair.Value.ColumnName, typeof(int));
}
DataRow row = table.NewRow();
// adding regular column values to the DataTable
row["Col1"] = c.Col1;
row["Col2"] = c.Col2;
row["Col3"] = c.Col3;
// adding dynamic column values to the DataTable
foreach (KeyValuePair<int, OtherColumns> pair in c.otherColumns)
{
row[pair.Value.ColumnName] = pair.Value.Value;
}
table.Rows.Add(row);
// Start binding the table.
gridViewTest.Columns.Clear();
System.Windows.Controls.GridViewColumn gvc;
Binding binding;
foreach (DataColumn column in table.Columns)
{
gvc = new System.Windows.Controls.GridViewColumn();
binding = new System.Windows.Data.Binding();
binding.Path = new PropertyPath(column.ColumnName);
binding.Mode = BindingMode.OneWay;
gvc.Header = column.Caption;
gvc.DisplayMemberBinding = binding;
gridViewTest.Columns.Add(gvc);
}
listViewTest.DataContext = table;
}
I'm not saying it's the best solution, but it could help. Let me know.
I made some helper classes so I could use the DataGrid as a kind of DataTable. In other words, I wanted the formatting, sorting, and polished look of the DataGrid without having to pre-fab some classes beforehand. The main reason I wanted this was for a testing suite, I wanted to be able to create an arbitrary number of columns and at runtime. Here's what I got
public class DataRow
{
internal List<object> Items = new List<object>();
public object this[string value]
{
get { return Items[Convert.ToInt32(value)]; }
}
public string GetString(int index)
{
return Items[index].ToString();
}
public object GetObject(int index)
{
return Items[index];
}
public DataRow(params object[] values)
{
if (values == null || values.Length < 1)
throw new Exception("You must pass in some values");
Items.AddRange(values);
}
}
public class GridConstructor
{
public List<DataRow> Rows = new List<DataRow>();
private DataRow headers;
public GridConstructor(DataRow head)
{
headers = head;
}
public void BuildInto(DataGrid grid)
{
grid.AutoGenerateColumns = false;
grid.Columns.Clear();
int totalCols = 0;
Type headType = headers.GetType();
for (int i = 0; i < headers.Items.Count; i++)
{
grid.Columns.Add(GetCol(headers.GetString(i), String.Concat("[", i.ToString(),"]")));
totalCols++;
}
int finalWidth = totalCols * (int)grid.ColumnWidth.Value + 15;
grid.Width = finalWidth;
grid.ItemsSource = Rows;
}
private DataGridTextColumn GetCol(string header, string binding)
{
DataGridTextColumn col = new DataGridTextColumn();
col.IsReadOnly = true;
col.Header = header;
col.Binding = new Binding(binding);
return col;
}
public DataGrid Create(int colSize)
{
DataGrid grid = new DataGrid();
grid.ColumnWidth = colSize;
grid.CanUserAddRows = false;
grid.AlternationCount = 2;
BuildInto(grid);
return grid;
}
}
Putting this together, this is a sample use:
void SimpleTest_Loaded(object sender, RoutedEventArgs e)
{
DataRow headers = new DataRow("Level", "Weapon Type", "vs None", "vs Leather", "vs Studded", "vs Brigandine");
GridConstructor gridConstructor = new GridConstructor(headers);
var weaponType = "Slash";
for (int level = 1; level < 10; level++)
{
int damage = DiceCup.RollMulti(8, level);
int damCloth = damage - DiceCup.RollMulti(2, level);
int damLeather = damage - DiceCup.RollMulti(3, level);
int damStudded = damage - DiceCup.RollMulti(4, level);
int damBrigandine = damage - DiceCup.RollMulti(5, level);
DataRow row = new DataRow(level, weaponType, damage, damCloth, damLeather, damStudded, damBrigandine);
gridConstructor.Rows.Add(row);
}
//Create the grid.
var grid = gridConstructor.Create(100);
//Create a chart.
Chart chart = new Chart();
chart.Height = 200;
chart.LegendTitle = "Legend";
chart.Title = "Slash vs Armor Types";
chart.DataContext = gridConstructor.Rows;
//Create our series, or lines.
LineSeries slashVsNone = new LineSeries();
slashVsNone.Title = "vs None";
slashVsNone.DependentValueBinding = new Binding("[2]");
slashVsNone.IndependentValueBinding = new Binding("[0]");
slashVsNone.ItemsSource = gridConstructor.Rows;
chart.Series.Add(slashVsNone);
//Presentation is a stackpanel on the page.
presentation.Children.Add(grid);
presentation.Children.Add(chart);
}
And the output:
alt text http://quiteabnormal.com/images/codeSample.jpg
Please note that the grid coloring is from universal styles set on the page. If you use the GridConstructor.BuildInto() method you can specify a grid you've pre-formatted yourself in Blend or somesuch.
Just one thing, the GridConstructor makes some assumptions about the column's initial settings. You can change the class to make it more customizable if you like, but this is what I needed so I wanted to be able to make it without fuss.

Resources