DataTemplate in listview - wpf

I use a listview to display data like a data matrix (columns and rows). My problem is : my items are typed : MatrixCellVM.
I tried everything I found on the net to apply a DataTemplate on this items but nothing worked.
Here is the latest technique I'm using
foreach (var col in dataMatrix.Columns)
{
//create the data template
DataTemplate cellLayout = new DataTemplate();
cellLayout.DataType = typeof(MatrixCellVM);
//set up the stack panel
FrameworkElementFactory spFactory = new FrameworkElementFactory(typeof(Grid));
spFactory.Name = "myComboFactory";
//set up value textblock
FrameworkElementFactory cellValue = new FrameworkElementFactory(typeof(TextBlock));
cellValue.SetBinding(TextBlock.TextProperty, new Binding("Value"));
cellValue.SetValue(TextBlock.ToolTipProperty, "Value");
spFactory.AppendChild(cellValue);
//set the visual tree of the data template
cellLayout.VisualTree = spFactory;
gridView.Columns.Add(new GridViewColumn{
Header = col.Name,
DisplayMemberBinding = new Binding(string.Format("[{0}]", count)),
CellTemplate = cellLayout
});
count++;
}
One more thing, when I override ToString() in MatrixCellVM, the value is displayed (but with this technique I add a context menu or give any color to my value).

I solved the problem but have another one.
So what I did is pretty simple. DisplayMemberBinding and CellTemplate can't be put together.
So I deleted the whole c# code creating the datatemplate.
The only thing that remains is :
gridView.Columns.Add(
new GridViewColumn
{
Header = col.Name,
CellTemplate = (DataTemplate)listView.Resources["MatrixCellVMTemplate"]
});
Thus, I added in my xaml code defining the datatemplate.
My new problem is that my rows are arrays of MatrixCellVM (as it is a matrix, I don't have properties and I don't know by advance how many columns I will have).
That was the point of the DisplayMemberBinding = new Binding(string.Format("[{0}]",count)) code in my top post.
How could I reproduce this behavior in my datatemplate (what is the binding in my textblock).
If anything is not clear, let me know.
Thanks

Related

WPF edit autogenerated column header text

I'm using a WPF DataGrid to display DataTable's.
I need to be able to edit this bound DataTables (Two-Way Binding).
I'm using the DataGrid as followed:
<DataGrid SelectionUnit="CellOrRowHeader" IsReadOnly="False" AutoGenerateColumns="True" ItemsSource="{Binding Path=SelectedItem.BindableContent, FallbackValue={x:Null}}" />
The Problem I have, the user can't edit the ColumnHeader's like cell content or rows.
The Screenshot below illustrates that porblem. The only thing I can do is sort the columns.
Is there a way to edit the column headers too, for example when the user clicks twice, or presses F2.
Maybe some Style' or a HeaderTemplate will do the job? I have already tried some styles and control templates I've found around the internet, but without any success.
EDIT:
I managed to display the column headers in a TextBox (and not in a TextBlock) within the AutogeneratingTextcolumn event handler:
private void _editor_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) {
// First: create and add the data template to the parent control
DataTemplate dt = new DataTemplate(typeof(TextBox));
e.Column.HeaderTemplate = dt;
// Second: create and add the text box to the data template
FrameworkElementFactory txtElement =
new FrameworkElementFactory(typeof(TextBox));
dt.VisualTree = txtElement;
// Create binding
Binding bind = new Binding();
bind.Path = new PropertyPath("Text");
bind.Mode = BindingMode.TwoWay;
// Third: set the binding in the text box
txtElement.SetBinding(TextBox.TextProperty, bind);
txtElement.SetValue(TextBox.TextProperty, e.Column.Header);
}
But I couldn't manage to set the binding correctly, if i edit the Text in the TextBoxes, it does not change the text in the Column.Header-Property (which is auto-generated by a binding to a DataTable like explained above).
You forgot to set the source of your binding and you mustn't set the value after the registration of the binding. The correct code would be the following:
private void asdf_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataTemplate dt = new DataTemplate(typeof(TextBox));
e.Column.HeaderTemplate = dt;
FrameworkElementFactory txtElement =
new FrameworkElementFactory(typeof(TextBox));
dt.VisualTree = txtElement;
Binding bind = new Binding();
bind.Path = new PropertyPath("Header");
bind.Mode = BindingMode.TwoWay;
// set source here
bind.Source = e.Column;
txtElement.SetBinding(TextBox.TextProperty, bind);
// You mustn't set the value here, otherwise the binding doesn't work
// txtElement.SetValue(TextBox.TextProperty, e.Column.Header);
}
Additionally you must change the binding property to Header, because you are adding the binding to the text property of the TextBox.

TextBox inside a listview cell

I have a listview that I would like to add a textbox inside each gridview column cell so I can type data into it and then fetch that data.
I'm creating a datatemplate and passing it to a cell template for the GridViewColumn but when I look at the listview I can't add anything to the cell. It doesn't look like the textbox was even created.
GridViewColumn conceptColumn = new GridViewColumn();
conceptColumn.Header = conceptName;
conceptColumn.CellTemplate = this.GetDataTemplate();
this.TestModeler.Columns.Add(conceptColumn);
conceptColumn.DisplayMemberBinding = new Binding(conceptName);
private DataTemplate GetDataTemplate()
{
DataTemplate dt = new DataTemplate(typeof(TextBox));
FrameworkElementFactory txtElement = new FrameworkElementFactory(typeof(TextBox));
dt.VisualTree = txtElement;
Binding bind = new Binding();
bind.Path = new PropertyPath("Text");
bind.Mode = BindingMode.TwoWay;
txtElement.SetBinding(TextBox.TextProperty, bind);
txtElement.SetValue(TextBox.TextProperty, "test");
return dt;
}
Please take a look at the ListView Class page at MSDN where you can find a XAML example and plenty of link on how to do various things with a WPF ListView.
Of particular interest to you, please take a look at the How to: Use Templates to Style a ListView That Uses GridView page there which explains what you are trying to do (but in XAML) with examples.
MSDN should always be your first place to look as it is full of information just waiting to be read.

Convert DataGrid cell content

I have a DataGrid whose ItemsSource is set to a DataTable. The DataTable has a column of type DateTime, and I would like to display informational text (ie. "N/A") if the date in a particular cell is a certain value.
My first thought was to somehow bind the cell content to itself, and use a converter, but I can't seem to get it working correctly, and it seems as though there should be a better way.
Additionally, both the DataGrid and DataTable are dynamically generated, so this has to be done in the code behind.
Here's the code I tried initially:
// Create a new DataGridCellStyle
Style myStyle = new Style();
myStyle.TargetType = typeof(DataGridCell);
// Create the binding
Binding myBinding = new Binding();
myBinding.RelativeSource = RelativeSource.Self;
myBinding.Converter = new DateTimeToStringConverter();
// Add the Content setter
Setter mySetter = new Setter();
mySetter.Property = ContentProperty;
mySetter.Value = myBinding;
myStyle.Setters.Add(setter);
// Set the Style and ItemsSource
myDataGrid.CellStyle = myStyle ;
myDataGrid.ItemsSource = myDataTable.DefaultView;
DateTimeToStringConverter does implement IValueConverter, but I'm guessing the problem lies somewhere with the binding, since DateTimeToStringConverter is never actually called when the DataGrid is displayed.
At first, you Add the variable with the name setter to the Setters collection, but you are define the variable with the name mySetter. It may be a reason, why your Converter is not actually called.
Also the solution for your problem will be a bit more complicated.
Actually the Convert get a value of type RowDataView which contains a data for whole row. There is not an information in the converter about a Column or a Cell that is actually binded.
Better will be skip AutoGenerateColumns and generate them programmatically.
Here is example:
myDataGrid.ItemsSource = myDataTable.DefaultView;
myDataGrid.AutoGenerateColumns = false;
foreach (DataColumn column in myDataTable.Columns)
{
Binding binding = new Binding(column.ColumnName)
{
Converter = new DateTimeToStringConverter()
};
DataGridColumn gridColumn = new DataGridTextColumn
{
SortMemberPath = column.ColumnName,
Header = column.ColumnName,
Binding = binding
};
myDataGrid.Columns.Add(gridColumn);
}
Of course, for a performance will be better use the converter only in a DateTime columns.

WPF datagrid empty row at bottom

I bind my datagrid using
//fill datagrid
public DataTable GameData
{
get
{
DataSet ds = new DataSet();
FileStream fs = new FileStream(IMDB.WebPage.Class.Config.XMLPath,
FileMode.Open, FileAccess.Read);
StreamReader reader = new StreamReader(fs, Encoding.Default);
ds.ReadXml(reader);
fs.Close();
DataTable temp = ds.Tables[0];
return ds.Tables[0];
}
}
For some reason I get an empty row at the bottom. And sometimes after clicking on some buttons and checkboxes in the grid, more empty rows are added.
Why is this? And how do I block this?
Sounds like you probably have CanUserAddRows set to true for the DataGrid. Just add
CanUserAddRows="false"
to the XAML.
It also works with the attribute:
IsReadOnly="true"
If your backing collection that implements IEditableCollectionView returns true from its CanAddNew method, the DataGrid will assume that is what you want to do.
There's a good overview here:Overview of the editing features in the WPF DataGrid
If you're creating DataGrid on-the-fly through Source Code...
DataGrid grid = new DataGrid();
grid.CanUserAddRows = false;
//...
grid.AutoGenerateColumns = false;
grid.Margin = new Thickness(10,20,10,10);
grid.VerticalAlignment = VerticalAlignment.Top;
grid.ItemsSource = //... and so on
Though the OP was asking how to REMOVE the empty row, the title isn't specific, and this article appeared in my search while trying to figure out how to ADD the empty row. I found that, for the empty row to appear, it not only needs to have CanUserAddRows="True" but the ItemsSource needs to have a default constructor public MyClass () { }.

Create WPF ItemTemplate DYNAMICALLY at runtime

At run time I want to dynamically build grid columns (or another display layout) in a WPF ListView. I do not know the number and names of the columns before hand.
I want to be able to do:
MyListView.ItemSource = MyDataset;
MyListView.CreateColumns();
You can add columns dynamically to a ListView by using Attached Properties. Check out this article on the CodeProject it explains exactly that...
WPF DynamicListView - Binding to a DataMatrix
From MSDN:
MyListBox.ItemsSource = view;
ListView myListView = new ListView();
GridView myGridView = new GridView();
myGridView.AllowsColumnReorder = true;
myGridView.ColumnHeaderToolTip = "Employee Information";
GridViewColumn gvc1 = new GridViewColumn();
gvc1.DisplayMemberBinding = new Binding("FirstName");
gvc1.Header = "FirstName";
gvc1.Width = 100;
myGridView.Columns.Add(gvc1);
GridViewColumn gvc2 = new GridViewColumn();
gvc2.DisplayMemberBinding = new Binding("LastName");
gvc2.Header = "Last Name";
gvc2.Width = 100;
myGridView.Columns.Add(gvc2);
GridViewColumn gvc3 = new GridViewColumn();
gvc3.DisplayMemberBinding = new Binding("EmployeeNumber");
gvc3.Header = "Employee No.";
gvc3.Width = 100;
myGridView.Columns.Add(gvc3);
//ItemsSource is ObservableCollection of EmployeeInfo objects
myListView.ItemsSource = new myEmployees();
myListView.View = myGridView;
myStackPanel.Children.Add(myListView);
i'd try following approach:
A) you need to have the list box display grid view - i believe this you've done already
B) define a style for GridViewColumnHeader:
<Style TargetType="{x:Type GridViewColumnHeader}" x:Key="gridViewColumnStyle">
<EventSetter Event="Click" Handler="OnHeaderClicked"/>
<EventSetter Event="Loaded" Handler="OnHeaderLoaded"/>
</Style>
in my case, i had a whole bunch of other properties set, but in the basic scenario - you'd need Loaded event. Clicked - this is useful if you want to add sorting and filtering functionality.
C) in your listview code, bind the template with your gridview:
public MyListView()
{
InitializeComponent();
GridView gridViewHeader = this.listView.View as GridView;
System.Diagnostics.Debug.Assert(gridViewHeader != null, "Expected ListView.View should be GridView");
if (null != gridViewHeader)
{
gridViewHeader.ColumnHeaderContainerStyle = (Style)this.FindResource("gridViewColumnStyle");
}
}
D) then in you OnHeaderLoaded handler, you can set a proper template based on the column's data
void OnHeaderLoaded(object sender, RoutedEventArgs e)
{
GridViewColumnHeader header = (GridViewColumnHeader)sender;
GridViewColumn column = header.Column;
//select and apply your data template here.
e.Handled = true;
}
E) I guess you'd need also to acquire ownership of ItemsSource dependency property and handle it's changed event.
ListView.ItemsSourceProperty.AddOwner(typeof(MyListView), new PropertyMetadata(OnItemsSourceChanged));
static void OnItemsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyListView view = (MyListView)sender;
//do reflection to get column names and types
//and for each column, add it to your grid view:
GridViewColumn column = new GridViewColumn();
//set column properties here...
view.Columns.Add(column);
}
the GridViewColumn class itself doesn't have much properties, so you might want to add some information there using attached properties - i.e. like unique column tag - header most likely will be used for localization, and you will not relay on this one.
In general, this approach, even though quite complicated, will allow you to easily extend your list view functionality.
Have a DataTemplateselector to select one of the predefined templates(Of same DataType) and apply the selector on to the ListView. You can have as many DataTemplates with different columns.
You can use a DataTemplateSelector to return a DataTemplate that you have created dynamically in code. However, this is a bit tedious and more complicated than using a predefined one from XAML, but it is still possible.
Have a look at this example: http://dedjo.blogspot.com/2007/03/creating-datatemplates-from-code.html
From experience I can recommend steering clear of dynamic data templates if you can help it... rather use the advice given here to explictly create the ListView columns, rather than trying to create a DataTemplate dynamically.
Reason is that the FrameworkElementFactory (or whatever the class name is for producing DataTemplates at run time) is somewhat cludgey to use (and is deprecated in favor of using XAML for dynamic templates) - either way you take a performance hit.
This function will bind columns to a specified class and dynamically set header, binding, width, and string format.
private void AddListViewColumns<T>(GridView GvFOO)
{
foreach (System.Reflection.PropertyInfo property in typeof(T).GetProperties().Where(p => p.CanWrite)) //loop through the fields of the object
{
if (property.Name != "Id") //if you don't want to add the id in the list view
{
GridViewColumn gvc = new GridViewColumn(); //initialize the new column
gvc.DisplayMemberBinding = new Binding(property.Name); // bind the column to the field
if (property.PropertyType == typeof(DateTime)) { gvc.DisplayMemberBinding.StringFormat = "yyyy-MM-dd"; } //[optional] if you want to display dates only for DateTime data
gvc.Header = property.Name; //set header name like the field name
gvc.Width = (property.Name == "Description") ? 200 : 100; //set width dynamically
GvFOO.Columns.Add(gvc); //add new column to the Gridview
}
}
}
Let's say you have a GridView with Name="GvFoo" in your XAML, which you would like to bind to a class FOO.
then, you can call the function by passing your class "FOO and GridView "GvFoo" as arguments in your MainWindow.xaml.cs on Window loading
AddLvTodoColumns<FOO>(GvFoo);
your MainWindow.xaml file should include the following
<ListView x:Name="LvFOO">
<ListView.View>
<GridView x:Name="GvTodos"/>
</ListView.View>
</ListView>

Resources