WPF edit autogenerated column header text - wpf

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.

Related

WPF Datagrid binding to DataTable and TextBox style in code behind

I have to show a set of data in a DataGrid having columns style as ComboBox or TextBlock.
The DataGrid is binded to a DataTable.
The number and position of each column in the DataTable is defined at runtime, so I create the DataGrid programmatically.
Everything is fine as long as I use DataGridTextColumn and leave the default style (TextBlock), while I get a type error if I try to change the DataGridTextColumn to TextBox style.
There is no problem for what concerns ComboBox, so I paste hereafter only the DataGridTextColumn part of my code (for a single DataGrid cell):
C#
// Create the DataTable that will contain real-time data
public DataTable CurrentTable { get; set; }
// Binding string
string stringA = "some_string_A";
// Create new binding
Binding b = new Binding(stringA);
b.Mode = BindingMode.TwoWay;
// Create a new TextColumn
DataGridTextColumn dgCol = new DataGridTextColumn();
//dgCol.ElementStyle = new Style(typeof(TextBox)); <- this row generates error
// Set the column binding for the new TextColumn
dgCol.Binding = b;
// Add the TextColumn to the DataGrid
datagrid.Columns.Add(dgCol);
// Create a new row in the DataTable
var colDataTable = CurrentTable.NewRow();
// Populate column "stringA" of the new row
colDataTable[stringA]="some_string_B";
// Add the row to DataTable
CurrentTable.Rows.Add(colDataTable);
// Finally bind DataGrid to DataTable
datagrid.ItemsSource = CurrentTable.AsDataView();
XAML
<DataGrid x:Name="datagrid" ItemsSource="{Binding CurrentTable}" CanUserAddRows="True" />
I tried to change the column style to TetBox in many ways, probably I misunderstood something, could anybody enlight me?
You should the EditingElementStyle property to your TextBox style:
dgCol.EditingElementStyle = new Style(typeof(TextBox));
A DataGridTextColumn has two styles. One for displaying and another one for editing.

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.

WPF Bind control to DataView

I am having a LOT of trouble trying to bind my controls to a data source. I tried binding to XML document. That worked, but lots of issues when I tried to refresh the XML document itself and have it update the UI.
My newest try is to bind my controls to a DataView, which seems simple. I have a sample app I found here on StackOverflow, which does this:
public MainWindow()
{
InitializeComponent();
DataTable dataTable = GetTable();
Binding dataTableBinding = new Binding();
dataTableBinding.Source = dataTable;
dataTableBinding.Path = new PropertyPath("Rows[0][MyTextColumn]");
txtMyTextColumnDataTable.SetBinding(TextBox.TextProperty, dataTableBinding);
DataView dataView = dataTable.DefaultView;
Binding dataViewBinding = new Binding();
dataViewBinding.Source = dataView;
dataViewBinding.Path = new PropertyPath("[0][MyTextColumn]");
txtMyTextColumnDataView.SetBinding(TextBox.TextProperty, dataViewBinding);
}
This works perfectly, right out of the box. I added a button whose code updates the value in the data table, and the textbox immediately reflects the new value when I click that button.
I tried this in my VB.Net project, like this:
dim plcData As DataTable = GetTable()
dim plcView As DataView = plcData.DefaultView
dim plcBinding As Binding = New Binding
plcBinding.Source = plcView
plcBinding.Path = New PropertyPath("(0)(conveyor_plc_data_Main_FeedCarousel_caroAngle)")
Me.tb.SetBinding(TextBlock.TextProperty, plcBinding)
And it doesn't work. It will not update my UI control.
In both cases, GetTable builds a 1-row DataTable with sample data. In my VB project, tb is a TextBlock on my MainWindow.
In the VB project, I can interrupt my code and query the particular data column in the Immediate window, and the proper value is there. It just won't update into my control.
This seems like a very simple thing to do. I am quite new to WPF, and can't see what is wrong with my code. Eventually I would like to define the binding in my XAML, but can't figure out how to do this. At this point, a code-behind setting of the binding would be ok. I will have many controls to be bound to many data columns.
Can anybody tell me what obvious thing I'm missing here?
According to the documentation, the syntax for the PropertyPath class only accepts C#-style indexers.
Single Indexer on the Immediate Object as Data Context:
<Binding Path="[key]" .../>
The class has no way to change its syntax based on the calling language.
EDIT
To set the binding in XAML when the DataView is created in the code-behind, expose the view as a property:
public static readonly DependencyProperty plcViewProperty
= DependencyProperty.Register("plcView", typeof(System.Data.DataView),
typeof(MainWindow), new PropertyMetadata(null));
public System.Data.DataView plcView
{
get { return (System.Data.DataView)GetValue(plcViewProperty); }
set { SetValue(plcViewProperty, value); }
}
private void MainWindow_Initialized(object sender, EventArgs eventArgs)
{
plcView = GetTable().DefaultView;
}
Then in your XAML:
<Window x:Name="TheWindow" ...>
...
Text="{Binding ElementName=TheWindow,
Path=plcView[0][conveyor_plc_data_Main_FeedCarousel_caroAngle]}"

How to force DataGrid to rebuild VisualTree for its columns

I have WPF form with DataGrid. New columns can be added to the datagrid manually by user via button. This is the code to add new column:
private void ColumnAdornerAddButton_MouseDown(object sender, MouseButtonEventArgs e)
{
DataGridTextAdornerColumn column = new DataGridTextAdornerColumn();
column.Header = "New column";
column.HeaderStyle = (Style)FindResource("columnHeader");
column.AdornerTemplate = (DataTemplate)FindResource("columnAdorner");
Binding binding = new Binding("Data");
binding.Mode = BindingMode.TwoWay;
column.Binding = binding;
grid.Columns.Insert(grid.Columns.Count - 1, column);
//Add adorner
DataGridColumnHeader header = GetColumnHeaderFromColumn(column);
AddAdorner(header, column.AdornerTemplate, column.IsReadOnly);
}
private DataGridColumnHeader GetColumnHeaderFromColumn(DataGridColumn column)
{
// dataGrid is the name of your DataGrid. In this case Name="dataGrid"
List<DataGridColumnHeader> columnHeaders = GetVisualChildCollection<DataGridColumnHeader>(grid);
foreach (DataGridColumnHeader columnHeader in columnHeaders)
{
if (columnHeader.Column == column)
{
return columnHeader;
}
}
return null;
}
The problem is that after I have added the column to the grid its header is not yet generated and it is not present in visual tree. Thus I cant get header for the new column and apply adorner to it.
I have tried to call ApplyTemplate recursively on visual tree of the grid without any luck.
Is there any way to force grid to generate DataGridColumnHeader for the new column in code?
Thank you in advance.
Hi after adding columns to datagrid call the method UpdateLayOut() of DataGrid.
datagrid.UpdateLayout();
I hope this will help.
I just want to enhance the solution,
The method
datagrid.Items.Refresh();
will helps to recreate the view (Datagrid).
thus you can see the updated value in datagrid

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