I have a DataGrid bound to a DataTable.DefaultView, which renders the grid using auto generation of columns. That part works fine. However, for some columns I would like to use a custom template. The problem is that columns in the table change on each load, so the solution needs to be generic.
I can hook into the AutoGeneratingColumn event as described here, but still have problem with defining the template binding:
<UserControl.Resources>
<DataTemplate x:Key="customCellTemplate">
<TextBlock Text="{Binding ???"></TextBlock>
</DataTemplate>
</UserControl.Resources>
(...)
<DataGrid ItemsSource="{Binding DefaultView}" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn">
</DataGrid>
And my code behind:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
string colName = e.PropertyName;
if (someCondition)
{
var templateColumn = new DataGridTemplateColumn();
templateColumn.Header = colName;
templateColumn.CellTemplate = (DataTemplate)Resources["customCellTemplate"];
templateColumn.SortMemberPath = colName;
e.Column = templateColumn;
}
As you can see I don't know how to define the binding in the column template, because the column name changes.
EDIT:
In addition to the accepted answer - sometimes it's easier to create the entire template programmatically as described here:
http://fczaja.blogspot.com/2013/12/wpf-datagrid-custom-template-for.html
Using a StaticResource forces you to keep it the same -- remember, static means there's just one instance, so if you change its binding for one column, you will change it for all of them. So it will have to be like this:
<DataTemplate x:Key="customCellTemplate">
<TextBlock Text="{Binding}"></TextBlock>
</DataTemplate>
I was thinking you could use this template in a dynamic way by wrapping it in another DataTemplate using a ContentControl. Set the Content property dynamically, and use the static template for the ContentTemplate:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
string colName = e.PropertyName;
if (someCondition)
{
string xaml = #"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""><ContentControl Content=""{0}"" ContentTemplate=""{1}"" /></DataTemplate>";
var tmpl = (DataTemplate)XamlReader.Load(string.Format(xaml, "{Binding " + colName + "}", "{StaticResource customCellTemplate}"));
var templateColumn = new DataGridTemplateColumn();
templateColumn.CellTemplate = tmpl;
templateColumn.Header = colName;
templateColumn.SortMemberPath = colName;
e.Column = templateColumn;
}
}
The only catch is that, with this setup, I believe "customCellTemplate" will have to be defined at the application level.
Related
I'm trying to change the template of a cell inside a DataGrid depending on a List<List<int>> which holds the type of the cell (I.e 1=bool, 2=int, 3=string, 4=custom, etc...). The custom types (types because they can be more than 1) have to be represented by a ComboBox. For numbers and strings, I need a normal TextBox and for boolean I need a CheckBox. The DataGrid is binded to a DataTable which I can resize and edit at runtime. Here it is some code:
<Grid>
<DataGrid ItemsSource="{Binding Path=DataTable}" Name="Grid" AutoGenerateColumns="True"
CanUserResizeRows="True" CanUserDeleteRows="False"
CanUserAddRows="False" AreRowDetailsFrozen="False"
SelectionUnit="Cell" LoadingRow="Grid_LoadingRow">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="AlternatingRowBackground" Value="LightYellow"/>
</Style>
</DataGrid.Style>
</DataGrid>
</Grid>
public partial class TableEditorWindow : Window
{
public string[] DebugNames = { "Bob", "Dan", "Pierre", "Mark", "Gary" };
// Stores the values of the Table
public ds_grid Table { get; set; }
// Stores the types of each cell in the Table
public ds_grid ValueTypesTable { get; set; }
// Used as wrapper between the Table variable and the DataGrid
public DataTable DataTable { get; set; }
public TableEditorWindow()
{
InitializeComponent();
Table = new ds_grid(5, 5);
// Fills the Table with 1s
for (int i = 0; i < 5; ++i)
{
for (int j = 0; j < Table.Width; ++j)
{
Table.Set(i, j, 1d);
}
}
DataTable = new DataTable();
// Add the columns
for (int i = 0; i < 5; ++i)
{
DataTable.Columns.Add(DebugNames[i]);
}
// Add the rows
for (int i = 0; i < Table.Height; ++i)
{
DataRow _row = DataTable.NewRow();
for (int j = 0; j < Table.Width; ++j)
{
_row[j] = Table.Get(j, i);
}
DataTable.Rows.Add(_row);
}
Grid.DataContext = this;
Grid.RowHeaderWidth = 50;
Grid.ColumnWidth = 100;
}
// Gives to each row the correct name
private void Grid_LoadingRow(object sender, DataGridRowEventArgs e)
{
int _id = e.Row.GetIndex();
e.Row.Header = DebugNames[_id];
}
}
ds_grid is basically a List<List<object>> with some utility methods around it.
I saw that there are some solutions, such as using DataTrigger, but I think that in that case I'd need to write in in the DataGrid in the XAML file, but I can't because AutoGenerateColumns is True. There is also the possibility to change the Type of each column of the DataTable but I don't want that every cell of that column is of that type, I want that only a single cell becomes of that type, at runtime.
Maybe there are better solutions, such as not using a DataGrid, or not using a DataTable, or there is a way to set AutoGenerateColumns to False and manually generating every column when needed, by code. Any suggestion is really appreciated.
Thanks in advance.
This is different enough from my original answer that I'm submitting it separately.
I also want to point out that is a very unconventional use of a DataGrid. Common data structure is that each column has a single type and I've hardly ever needed otherwise. My previous answer works if you stick with that convention. That being said, what you're asking for can be done.
If you really want to disregard common data structure and customize things on the cell level, you'll need a custom DataGridColumn:
public class DataTableBoundColumn : DataGridBoundColumn
{
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
if (dataItem == CollectionView.NewItemPlaceholder) { return null; }
DataRowView dataRow = (dataItem as DataRowView);
if (dataRow == null) { throw new ArgumentException(); }
object cellData = dataRow[cell.Column.DisplayIndex];
var contentHost = new ContentControl() { Content = cellData };
//Do some tests on cellData to determine the type and pick a DataTemplate
//Alternatively, you could build the actual content here in code-behind, but a DataTemplate would probably be cleaner
contentHost.ContentTemplate = (DataTemplate)SomeResourceDictionary["SomeResourceKey"];
return contentHost;
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
return GenerateElement(cell, dataItem);
}
}
The above is based off the example from this article. Using this column type, your AutoGeneratingColumn handler would be as follows:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
DataTableBoundColumn col = new DataTableBoundColumn();
e.Column = col;
e.Column.Header = "Whatever you want";
}
If you want to customize the auto-generated columns you will have to use the DataGrid.AutoGeneratingColumn event. I understand that you don't want the entity of a column to be the same, but you'll still need to use this event- you just need to use it a bit differently.
You were on the right track with thinking of Styles and DataTriggers to dynamically change the cell template. This would usually be done by declaring the columns in XAML, but you could get around that by using DataGrid.AutoGeneratingColumn and declaring your column as a resource.
Take something like this:
<DataGrid>
<DataGrid.Resources>
<DataGridTemplateColumn x:Key="TemplateColumn" x:Shared="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<!--Use DataTriggers to set the content to whatever you need-->
<DataTrigger>
<!--...-->
</DataTrigger>
<DataTrigger>
<!--...-->
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Resources>
</DataGrid>
The above is a DataTemplate that uses a ContentControl with DataTriggers to dynamically set the ContentTemplate. I defined it as resource of the DataGrid with an x:Key. x:Shared="False" means that WPF will create a new instance of this column whenever the resource is requested, instead of creating one and giving out references to that single instance so users can "share" it. This will let you add multiple instances of the column to the DataGrid.
Your AutoGeneratingColumn would be something like this:
private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
//Add an if statement if you only want to replace some of the columns with the dynamic template one
var DG = (DataGrid)sender;
DataGridTemplateColumn col = (DataGridTemplateColumn)DG.Resources["TemplateColumn"];
e.Column = col;
e.Column.Header = "Whatever you want";
}
This replaces your column with an insance of the TemplateColumn resource.
I have the following TabControl:
<TabControl ItemsSource="{Binding Tabs"}>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type vm:TabVM}">
<TextBox></TextBox>
<TextBox Text="{Binding SomeProperty}"></TextBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The unexpected behaviour is that first TextBox has Text property shared between all tabitems, while second TextBox effectively bind to ViewModel property.
My need is to make independent the first TextBox too, even without binding.
What can I do ?
** UPDATE **
After several tries I've decided to use the ikriv's TabContent.cs.
The only issue I've found with this is that calling the TabControl.Items.Refresh() (i.e. after removing a tabItem) cause the reset of the internal cache.
An unelegant but effective solution may be this:
public ContentManager(TabControl tabControl, Decorator border)
{
_tabControl = tabControl;
_border = border;
_tabControl.SelectionChanged += (sender, args) => { UpdateSelectedTab(); };
/* CUSTOM */
var view = CollectionViewSource.GetDefaultView(((TabControl)_tabControl).Items);
view.CollectionChanged += View_CollectionChanged;
}
/*
* This fix the internal cache content when calling items->Refresh() method
* */
private void View_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
{
/* Retrieve all tabitems cache and store to a temp list */
IList<ContentControl> cachedContents = new List<ContentControl>();
foreach (var item in _tabControl.Items)
{
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
var cachedContent = TabContent.GetInternalCachedContent(tabItem);
cachedContents.Add(cachedContent);
}
/* rebuild the view */
_tabControl.Items.Refresh();
/* Retrieve all cached content and store to the tabitems */
int idx = 0;
foreach (var item in _tabControl.Items)
{
var tabItem = _tabControl.ItemContainerGenerator.ContainerFromItem(item);
TabContent.SetInternalCachedContent(tabItem, cachedContents[idx++]);
}
}
}
You should use data binding since the same ContentTemplate will be applied for all items in your ItemsSource. Only the binding will be refreshed when you switch tabs basically. The TextBox isn't re-created nor reset.
What can I do ?
You could work around this in the view by handling the SelectionChanged event of the TabControl and reset the TextBox control yourself:
private void tabs_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TabControl tc = sender as TabControl;
ContentPresenter cp = tc.Template.FindName("PART_SelectedContentHost", tc) as ContentPresenter;
if(cp != null && VisualTreeHelper.GetChildrenCount(cp) > 0)
{
ContentPresenter cpp = VisualTreeHelper.GetChild(cp, 0) as ContentPresenter;
if(cpp != null)
{
TextBox textBox = cpp.FindName("txt") as TextBox;
if (textBox != null)
textBox.Text = string.Empty;
}
}
}
<TabControl x:Name="tabs" ItemsSource="{Binding Tabs}" SelectionChanged="tabs_SelectionChanged">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter>
<ContentPresenter.Content>
<StackPanel>
<TextBox x:Name="txt"></TextBox>
</StackPanel>
</ContentPresenter.Content>
</ContentPresenter>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
If you want to persist the text in the TextBox when you switch tabs you could use the attached behaviour from the following article and set its IsCached property to true: https://www.codeproject.com/articles/460989/wpf-tabcontrol-turning-off-tab-virtualization
<TabControl ItemsSource="{Binding Items}" behaviors:TabContent.IsCached="True">
<!-- Make sure that you don't set the TabControl's ContentTemplate property but the custom one here-->
<behaviors:TabContent.Template>
<DataTemplate>
<StackPanel>
<TextBox />
</StackPanel>
</DataTemplate>
</behaviors:TabContent.Template>
</TabControl>
Yet another approach would be to modify the ControlTemplate of the TabControl to include a ListBox as suggested by 'gekka' in the following thread on the MSDN forums: https://social.msdn.microsoft.com/Forums/en-US/4b71a43a-26f5-4fef-8dc5-55409262298e/using-uielements-on-datatemplate?forum=wpf
I want to add a button on every row of WPF grid which I am binding from code behind. I am very new to WPF any help is appreciated.
My current code for binding grid is:
DataGridTextColumn c1 = new DataGridTextColumn();
c1.Header = "Dummy column";
c1.Binding = new Binding("DummyColumn");
c1.IsReadOnly = true;
grdDummy.Columns.Add(c1);
foreach (DummyObject deal in AllDummyObjects)
{
ModelToBind dataModel = new ModelToBind()
//do some processing on dataModel
grdDummy.Items.Add(dataModel);
}
You can add another column with button like this:
DataGridTemplateColumn buttonColumn = new DataGridTemplateColumn();
DataTemplate buttonTemplate = new DataTemplate();
FrameworkElementFactory buttonFactory = new FrameworkElementFactory(typeof (Button));
buttonTemplate.VisualTree = buttonFactory;
//add handler or you can add binding to command if you want to handle click
buttonFactory.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(HandleClick));
buttonFactory.SetValue(ContentProperty, "Button");
buttonColumn.CellTemplate = buttonTemplate;
grdDummy.Columns.Add(buttonColumn);
Previously, an example was given of creating a Button using FrameworkElementFactory.
This class is not recommended.
Quote from the documentation:
This class is a deprecated way to programmatically create templates, which are subclasses of FrameworkTemplate such as ControlTemplate or DataTemplate; not all of the template functionality is available when you create a template using this class. The recommended way to programmatically create a template is to load XAML from a string or a memory stream using the Load method of the XamlReader class.
In this regard, I show the implementation code using the XamlReader.
The code is shown from the assumption that the ModelToBind class has a command-property named ButtonCommand and property ButtonTitle.
And this class is located in the local namespace "Febr20y" in the assembly of the same name.
DataGridTextColumn c1 = new DataGridTextColumn
{
Header = "Dummy column",
Binding = new Binding("DummyColumn"),
IsReadOnly = true
};
DataTemplate template = (DataTemplate)XamlReader.Parse(
#"<DataTemplate
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Febr20y;assembly=Febr20y'
DataType ='{x:Type local:ModelToBind}'>
<Button Content='{Binding ButtonTitle, Mode=OneWay}'
Command='{Binding ButtonCommand, Mode=OneWay}'/>
</DataTemplate>");
DataGridTemplateColumn c2 = new DataGridTemplateColumn()
{
Header = "Buttons",
IsReadOnly = true,
CellTemplate=template
};
grdDummy.Columns.Add(c1);
grdDummy.Columns.Add(c2);
var listSource = AllDummyObjects
.Select(deal => new ModelToBind() { ButtonTitle = deal.Title.ToString()})
.ToList();
grdDummy.ItemsSource = listSource;
This is equivalent to this XAML code:
<DataGrid x:Name="grdDummy" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding DummyColumn}"
IsReadOnly="True"
Header="Dummy column"/>
<DataGridTemplateColumn Header="Buttons"
IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:ModelToBind}">
<Button Content="{Binding ButtonTitle, Mode=OneWay}"
Command="{Binding ButtonCommand, Mode=OneWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I have a row in a grid with 5 textboxes, 2 of which are enabled by checkboxes. I am trying to dynamically add additional rows to the grid when a button is clicked. The eventhandler I added will only enable the textbox in the first row, but not in the current row (2nd). There is another eventhandler which handles the box in the first row, this is a new one. (BTW I only have part of the second row coded). Not sure if I should try making a template for the checkbox, and then use binding to the textbox? And if so, the instructions I've read on connecting the binding are vague and confusing. Or can I do the binding directly? Or ?
public partial class Window2 : Window
{
int currentColumn = 0;
int currentRow = 1;
int timesCalled = 1;
public Window2()
{
InitializeComponent();
}
private void AddLevelButton_Click(object sender, RoutedEventArgs e)
{
string level = this.Level.Content.ToString(); //label for the row
string[] splitLevel = level.Split(' ');
int levelNum = int.Parse(splitLevel[1]);
levelNum = timesCalled + 1;
int nextRow = currentRow + 1;
int nextColumn = currentColumn + 1;
Label levelLabel = new Label();
levelLabel.Content = "Level " + levelNum.ToString();
Grid.SetRow(levelLabel, nextRow);
Grid.SetColumn(levelLabel, currentColumn);
FlowGrid.Children.Add(levelLabel);
currentColumn++;
CheckBox antesBox = new CheckBox(); //the checkbox to enable the
antesBox.Name = "AntesBox"; //textbox which follows
antesBox.VerticalAlignment = VerticalAlignment.Bottom;
antesBox.HorizontalAlignment = HorizontalAlignment.Right;
antesBox.FontSize = 16;
antesBox.Width = 20;
antesBox.Height = 20;
antesBox.Checked += AntesBox_Checked1; //eventhandler
Grid.SetRow(antesBox, nextRow);
Grid.SetColumn(antesBox, currentColumn);
FlowGrid.Children.Add(antesBox);
nextColumn = ++currentColumn;
TextBox enterAntes = new TextBox(); //the textbox to be enabled
enterAntes.Name = "EnterAntes";
enterAntes.Margin = new Thickness(5, 0, 5, 0);
enterAntes.FontSize = 16;
enterAntes.FontFamily = new FontFamily("Verdana");
enterAntes.IsEnabled = false;
enterAntes.KeyDown += EnterAntes_KeyDown1; //tested; this works
Grid.SetRow(EnterAntes, nextRow);
Grid.SetColumn(EnterAntes, nextColumn);
FlowGrid.Children.Add(EnterAntes);
nextColumn = ++currentColumn;
}
private void enterAntes_KeyDown1(object sender, KeyEventArgs e)
{
int key = (int)e.Key;
e.Handled = !(key >= 34 && key <= 43 ||
key >= 74 && key <= 83 || key == 2);
}
private void AntesBox_Checked1(object sender, RoutedEventArgs e)
{
EnterAntes.IsEnabled = true;
}
You need to add following codes to enable text boxes.
Following is the xaml view of the datagrid.
<DataGrid x:Name="gvTest" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalAlignment="Left" Margin="86,204,0,0" VerticalAlignment="Top" Height="132" Width="436">
<DataGrid.Columns>
<DataGridTemplateColumn Header="TextBox 01">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txt01" Width="50" Text="{Binding TxtBox01}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 02">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox02" Width="50" Text="{Binding TxtBox02}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 03">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox03" Width="50" Text="{Binding TxtBox03}"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 04">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox04" Width="50" IsEnabled="False" Text="{Binding TxtBox04}" Loaded="txtbox04_Loaded"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="TextBox 05">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbox05" Text="{Binding TxtBox05}" Loaded="txtbox05_Loaded"></TextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Enable" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="chk01" Checked="chk01_Checked" IsChecked="{Binding IsActive}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
add the following codes to declare instance of required textboxes and declare observable collection.
TextBox txt04;
TextBox txt05;
ObservableCollection<TestItem> TestItemList = new ObservableCollection<TestItem>();
add the following codes to the loaded event of the required textboxes.
private void txtbox04_Loaded(object sender, RoutedEventArgs e)
{
txt04 = (sender as TextBox);
//txt04.IsEnabled = false;
}
private void txtbox05_Loaded(object sender, RoutedEventArgs e)
{
txt05 = (sender as TextBox);
}
Now, create a model class with following code segment in order to bind values to the datagrid.
public class TestItem
{
public string TxtBox01 { get; set; }
public string TxtBox02 { get; set; }
public string TxtBox03 { get; set; }
public string TxtBox04 { get; set; }
public string TxtBox05 { get; set; }
public bool IsActive { get; set; }
public TestItem()
{
IsActive = false;
}
}
I have used a button to add new rows to the datagrid. add the following codes to the button click to add rows.
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
TestItemList.Add(new TestItem());
gvTest.ItemsSource = TestItemList;
}
Finally, add the following codes to the checkbox checked event
CheckBox c = (sender as CheckBox);
if (c.IsChecked==true)
{
txt04.IsEnabled = true;
txt05.IsEnabled = true;
}
Hope this helps you to fulfill your requirement.
At the risk of perpetuating the wrong approach, it seems to me that the most direct way to address your specific need here is to fix your event handler so that it is always specific to the text box that corresponds to the checkbox in question. This is most easily done by moving the event handler subscription to below the declaration of the local variable enterAntes, and then use that variable in the event handler (i.e. so that it's capture by the anonymous method used as the event handler). For example:
TextBox enterAntes = new TextBox(); //the textbox to be enabled
antesBox.Checked += (sender, e) => enterAntes.IsEnabled = true;
Now, that said, I whole-heartedly agree with commenter Mark Feldman who suggests that the code you've written is not the right way to accomplish your goal in WPF.
I'm not sure I agree with the characterization "harder". That's such a loaded and subjective term, depending in no small part in what you find easy or hard. Being new to WPF, you almost certainly find the concepts of data binding and declarative XAML-based coding "hard", and direct, procedural code such as in your example "easy" (or at least "easier" :) ).
But he's absolutely right that in the long run, you will be better served by doing things "the WPF way". You may or may not wind up with much less code, but the WPF API is designed to be leveraged as much as possible from the XAML, and use code-behind minimally (and certainly not for the purpose to build the UI).
So what's all that mean for your code? Well, I ramble and it would be beyond the scope of a good, concise Stack Overflow answer for me to try to rewrite your entire code from scratch to suit the WPF paradigm. But I will offer some suggestions as to how I'd handle this.
First, forget the UI objects themselves for a moment. Write classes that describe the key characteristics of the UI as you want it to be, without being the UI itself. In this example, this could mean that you should have a list of rows. There should also be a class that defines what a single row looks like, e.g. with a bool property (to reflect the checkbox state) and a string property (to reflect the text box value). This is your "model"; i.e. each class is an individual model class, and at the same time you could consider the entire collection of classes as the model for your UI.
Now, go back to your UI and define it in XAML, not in code. There are several different ways to represent a list in the UI. Classes like ListBox, ListView, DataGrid, or even ItemsControl (the base class for many of the list-oriented controls). Bind the source of your list control to the model list you created in the previous step.
Define a DataTemplate (again, in XAML) for the type of class that is contained in the list. This will declare the UI for a single row in your list. Your template might look something like this:
<!-- Make sure you defined the "local" XML namespace for your project using the
xmlns declaration -->
<DataTemplate DataType="{x:Type local:MyRowModel}">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Text}" IsEnabled={Binding IsEnabled}"/>
<Checkbox Checked="{Binding IsEnabled}"/>
</StackPanel>
</DataTemplate>
All of the XAML inside the DataTemplate element tells WPF what you want a single row to look like, within the control that is presenting your row model. That control will set the DataContext for the list item defined by the template, such that the {Binding...} declarations can reference your row model's properties directly by name.
That row model in turn might look something like this:
class MyRowModel : INotifyPropertyChanged
{
private string _text;
private bool _isEnabled;
public string Text
{
get { return _text; }
set
{
if (_text != value)
{
_text = value;
OnPropertyChanged();
}
}
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (_isEnabled != value)
{
_isEnabled = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
When your button to add a new item is clicked, don't mess with the UI directly. Instead, add a new element to your list of rows. Let WPF do the work of updating the UI to match.
NOTES:
The above uses StackPanel for the data template for convenience. If you want things lined up in columns, you'll probably want to use a Grid and declare its columns using SharedSizeGroup.
Or better yet, maybe you can use DataGrid which, assuming its default presentation of the values is acceptable to you, offers simple and automatic handling of exactly this type of layout.
The above is not meant to be anything close to a complete explanation of how to use data templating. It's just supposed to get you pointed in the right direction. Templating is one of WPF's more powerful features, but with that it also has the potential to be fairly complex.
For all of this to work, your types need to provide notification when they change. In the case of the row model, you can see it implements INotifyPropertyChanged. There is also an interface INotifyCollectionChanged; usually you don't have to implement this yourself, as WPF has the type ObservableCollection<T> which you can use just like List<T>, to store lists of data but with a way for notifications of changes to be reported to WPF.
I know this is a lot to take it all at once. Unfortunately, it's not feasible to try to explain all in a single answer all the things you need to learn to get this right. Frankly, even the above is pushing the limits as to what's within the scope of a Stack Overflow answer. But I hope that I've hit just the right highlights to get you looking at the right parts of the WPF documentation, and to understand the underlying philosophy of the WPF API.
I'm trying to edit the contents of an StackPanel inside a templated DataGrid Column by Code. Unfortunately i cannot find the StackPanel from Code. Can anybody help me, please?
This is my DataTemplate:
<UserControl.Resources>
<DataTemplate x:Key="ReservationContainerTemplate">
<StackPanel Orientation="Horizontal" Background="Black" />
</DataTemplate>
</UserControl.Resources>
This is how I create this Column:
var colReservations = new DataGridTemplateColumn();
colReservations.Header = "Nordplatz";
DataTemplate dt = null;
dt = dataGrid1.FindResource("ReservationContainerTemplate") as DataTemplate;
colReservations.CellTemplate = dt;
dataGrid1.Columns.Add(colReservations);
What I need to do is, writing to this StackPanel inside DataTemplate.
I was able to get the Object by following Code:
DataRowView n = (DataRowView)dataGrid1.Items[i];
//var m = dataGrid1.SelectedItem.Cells[0].Text;
DataTemplate Template = dataGrid1.FindResource("ReservationContainerTemplate") as DataTemplate;
StackPanel stp = Template.LoadContent() as StackPanel;
Great, I've got my Object, but how can I modify it? Under this conditions i've only got a copy of the Object, changes are not reflected to the original one.
Does anybody have got an idea?