How do I build a DataTemplate in c# code? - c#

I am trying to build a dropdown list for a winform interop, and I am creating the dropdown in code. However, I have a problem getting the data to bind based on the DataTemplate I specify.
What am I missing?
drpCreditCardNumberWpf = new ComboBox();
DataTemplate cardLayout = new DataTemplate {DataType = typeof (CreditCardPayment)};
StackPanel sp = new StackPanel
{
Orientation = System.Windows.Controls.Orientation.Vertical
};
TextBlock cardHolder = new TextBlock {ToolTip = "Card Holder Name"};
cardHolder.SetBinding(TextBlock.TextProperty, "BillToName");
sp.Children.Add(cardHolder);
TextBlock cardNumber = new TextBlock {ToolTip = "Credit Card Number"};
cardNumber.SetBinding(TextBlock.TextProperty, "SafeNumber");
sp.Children.Add(cardNumber);
TextBlock notes = new TextBlock {ToolTip = "Notes"};
notes.SetBinding(TextBlock.TextProperty, "Notes");
sp.Children.Add(notes);
cardLayout.Resources.Add(sp, null);
drpCreditCardNumberWpf.ItemTemplate = cardLayout;

Assuming that you've already set up the ItemsSource etc for drpCreditCardNumberWpf...
//create the data template
DataTemplate cardLayout = new DataTemplate();
cardLayout.DataType = typeof(CreditCardPayment);
//set up the stack panel
FrameworkElementFactory spFactory = new FrameworkElementFactory(typeof(StackPanel));
spFactory.Name = "myComboFactory";
spFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
//set up the card holder textblock
FrameworkElementFactory cardHolder = new FrameworkElementFactory(typeof(TextBlock));
cardHolder.SetBinding(TextBlock.TextProperty, new Binding("BillToName"));
cardHolder.SetValue(TextBlock.ToolTipProperty, "Card Holder Name");
spFactory.AppendChild(cardHolder);
//set up the card number textblock
FrameworkElementFactory cardNumber = new FrameworkElementFactory(typeof(TextBlock));
cardNumber.SetBinding(TextBlock.TextProperty, new Binding("SafeNumber"));
cardNumber.SetValue(TextBlock.ToolTipProperty, "Credit Card Number");
spFactory.AppendChild(cardNumber);
//set up the notes textblock
FrameworkElementFactory notes = new FrameworkElementFactory(typeof(TextBlock));
notes.SetBinding(TextBlock.TextProperty, new Binding("Notes"));
notes.SetValue(TextBlock.ToolTipProperty, "Notes");
spFactory.AppendChild(notes);
//set the visual tree of the data template
cardLayout.VisualTree = spFactory;
//set the item template to be our shiny new data template
drpCreditCardNumberWpf.ItemTemplate = cardLayout;
You can use the same way I have set the ToolTip on the TextBlocks to set other properties such as margins.

The full version
var ms = new MemoryStream(Encoding.UTF8.GetBytes(#"<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml""
xmlns:c=""clr-namespace:MyApp.Converters;assembly=MyApp"">
<DataTemplate.Resources>
<c:MyConverter x:Key=""MyConverter""/>
</DataTemplate.Resources>
<TextBlock Text=""{Binding ., Converter={StaticResource MyConverter}}""/>
</DataTemplate>"));
var template = (DataTemplate)XamlReader.Load(ms);
var cb = new ComboBox { };
//Set the data template
cb.ItemTemplate = template;

Well, indeed we still have another way, you will really like it if you dislike those FrameworkElementFactory things.
And I think it just makes minor changes to the natural code, that is, declare a UserControl and put your control into it, and then, use just one FrameworkElementFactory to call the UserControl.
Simple demo code (in F#):
let buildView()=StackPanel()
//Build it with natural code
type MyView()=inherit UserControl(Content=buildView())
let factory=FrameworkElementFactory(typeof<MyView>)
let template=DataTemplate(VisualTree=factory)
let list=ItemsControl(ItemsSource=makeData(),ItemTemplate=template)

Related

Converter not working when DataTemplate is loaded in code behind

I loaded the datatemplate for image column in code behind . Please refer the below code snippet,
FrameworkElementFactory fef = new FrameworkElementFactory(typeof(Image));
Binding bind = new Binding() { Path=new PropertyPath(imagecolumn.MappingName),Converter = new StringToImageConverter(),Mode=BindingMode.TwoWay };
fef.SetBinding(Image.SourceProperty,new Binding(imagecolumn.MappingName));
DataTemplate template = new DataTemplate() { VisualTree = fef };
this.imagecolumn.CellItemTemplate = template;
But my converter is not invoked. I need to load different images in each row of the column . Am i missed anything ? Please share any idea
You instantiate a new Binding but you never use it. Do this:
FrameworkElementFactory fef = new FrameworkElementFactory(typeof(Image));
Binding bind = new Binding() { Path=new PropertyPath("MappingName"),Converter = new StringToImageConverter(),Mode=BindingMode.TwoWay,Source=imagecolumn };
fef.SetBinding(Image.SourceProperty, bind); // here you just created
//another instance of Binding instead of using your bind variable
DataTemplate template = new DataTemplate() { VisualTree = fef };
this.imagecolumn.CellItemTemplate = template;
EDIT:
Have a look at FrameworkElementFactory. In the remarks it sais:
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.
Maybe you should do it the recommended way.

WPF CheckBox checked event on scrolling thought datagrid

I have a datagrid with a DataGridTemplateColumn containing a CheckBox. I create the column like this:
DataGridTemplateColumn cTemplateColumn = new DataGridTemplateColumn();
cTemplateColumn.Header = "Auswahl";
FrameworkElementFactory cFactory = new FrameworkElementFactory(typeof(CheckBox));
Binding b1 = new Binding("[__intern_cv__]");
//b1.IsAsync = true;
b1.Converter = new StringToBoolConverter(this);
b1.Mode = BindingMode.TwoWay;
b1.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
cFactory.SetValue(CheckBox.IsCheckedProperty, b1);
cFactory.SetValue(CheckBox.HorizontalAlignmentProperty, HorizontalAlignment.Center);
cFactory.AddHandler(CheckBox.CheckedEvent, new RoutedEventHandler(CheckedEvent));
cFactory.AddHandler(CheckBox.UncheckedEvent, new RoutedEventHandler(CheckedEvent));
cFactory.AddHandler(CheckBox.PreviewMouseDownEvent, new MouseButtonEventHandler(checkBoxMouseDown));
DataTemplate cCellTemplate = new DataTemplate();
cCellTemplate.VisualTree = cFactory;
cTemplateColumn.CellTemplate = cCellTemplate;
Columns.Add(cTemplateColumn);
Now i have the problem, that during scrolling throught the DataGrid, the CheckedEvent is called, with Checked == False.
The vents a subscripted here:
cFactory.AddHandler(CheckBox.CheckedEvent, new RoutedEventHandler(CheckedEvent));
cFactory.AddHandler(CheckBox.UncheckedEvent, new RoutedEventHandler(CheckedEvent));
How can scrolling call the event?
maybe someone has an idea, thank you!
By default the DataGrid virtualizes its rows. This means that during scrolling old rows are re-used with new data. If the new data has "[__intern_cv__]" set to false, while the previous data had it set to true an Unchecked event will be raised.

Can't find element that should exist?

I am adding tabs to my tab control through code:
TabItem tab = new TabItem();
var stack = new StackPanel() { Orientation = Orientation.Horizontal };
stack.Children.Add(new TextBlock() { Text = header });
stack.Children.Add(new TextBlock() { Name = "extra" });
tab.Header = stack;
tabControl.Items.Add(tab);
As you can see, it creates the header of the tabItem with a stack panel. It adds two text blocks; one of which is empty, but I've assigned the name "extra". What I would like to do is, later in the code, edit the textBlock named "extra" and add some new text to it.
How would I find and edit this element? I have tried the following code, but its producing an error saying the element can not be found:
object test = Application.Current.FindResource("extra");
FindName is what you are looking for but your TextBlock is not in the correct WPF Namescope.
MSDN states:
If you add an object to an object tree at a point in time after the XAML that produced that tree was parsed, a Name or x:Name value on the new object does not automatically update the information in a XAML namescope. To add a name for an object into a WPF XAML namescope after XAML is loaded, must call the appropriate implementation of RegisterName on the object that defines the XAML namescope.
For example:
var textBlock = new TextBlock() { Name = "extra" };
stack.Children.Add(textBlock );
RegisterName(textBlock);
...
TextBlock textBlock = FindName("extra") as TextBlock;
Finally, Application.Current.FindResource("extra") is returning null because the element does not exist when project resources are created. More on FindResource.
Just use FrameworkElement.FindName method:
var control = tab.FindName("extra");
if(control is TextBlock){
// your logic here
}
You don't need Application.Current.Resource dictionary here because it's different collection. If you want to use it then you should put user controls within Resource dictionary beforehand.
Because you are trying to find resource with the key "extra". It's wrong.
Try this:
TabItem tab = new TabItem();
var stack = new StackPanel() { Orientation = Orientation.Horizontal };
var textBlock = new TextBlock() { Name = "extra" }
stack.Children.Add(new TextBlock() { Text = header });
stack.Children.Add(textBlock);
tab.Header = stack;
tabControl.Items.Add(tab);
Now you can reach it with textBlock instance.
Here's some VB WPF code for what you need
Dim test As TextBlock
test = DirectCast(FindName("extra"), TextBlock)
I have no idea if it will work like that in C# WPF although if that doesn't work try looking up CType

Creating a custom styled WPF Checkbox purely in the code behind

I'm trying to create a custom CheckBox control which will ideally be represented by a blue cross, which turns a lighter shade of blue when the mouse Mouse-Over event occurs, and fires a Click event when clicked.
I've seen ways of doing this in the XAML code using Control Templates and Styles, but not for purely in the code behind. I've created custom Styles in the code before and applied them well enough, but I'm having trouble with the amount of customization required this time around e.g replacing the whole checkbox with an image of a blue cross.
Does anybody know a standard way of a doing this? Can you create a full templated style in the XAML code, and then reference that template when setting the Properties on your new checkbox object in the code behind?
Thanks in advance.
I hate to do this, as it ruins the declaration approach with XAML, but there are times that you need to do it.
That being said, take a look at the FrameworkElementFactory class to build your XAML. It's a pretty neat pattern. The following snippet shows where I created a DataTemplate for a ListView in code. I needed to dynamically add elements based on the number of days in a month for a time reporting application.
GridView gv = new GridView();
int i = 0;
foreach (string s in vm.DateList)
{
string column = string.Format("DisplayTime[{0}].Hours", i);
DataTemplate dt = new DataTemplate();
DateTime date = DateTime.Parse(s);
bool isWeekday = true;
if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)
{
isWeekday = false;
}
Binding binding = new Binding();
binding.Path = new PropertyPath(column);
binding.Mode = BindingMode.TwoWay;
FrameworkElementFactory gridElement = new FrameworkElementFactory(typeof(Grid));
gridElement.SetValue(Grid.WidthProperty, 60.0);
gridElement.SetValue(Grid.HeightProperty, 94.0);
gridElement.SetValue(Grid.MarginProperty, new Thickness(0.0, 0.0, 0.0, 4.0));
if (!isWeekday)
{
gridElement.SetValue(Grid.BackgroundProperty, new SolidColorBrush(Color.FromRgb(65, 65, 65)));
}
FrameworkElementFactory txtelement = new FrameworkElementFactory(typeof(TextBox));
txtelement.SetBinding(TextBox.TextProperty, binding);
txtelement.SetValue(TextBox.WidthProperty, 40.0);
txtelement.SetValue(TextBox.HeightProperty, 20.0);
txtelement.SetValue(TextBox.VerticalAlignmentProperty, VerticalAlignment.Center);
txtelement.SetValue(TextBox.HorizontalAlignmentProperty, HorizontalAlignment.Center);
txtelement.SetValue(TextBox.TextAlignmentProperty, TextAlignment.Right);
gridElement.AppendChild(txtelement);
dt.VisualTree = gridElement;
gv.Columns.Add(new GridViewColumn()
{
Header = s,
// DisplayMemberBinding = new Binding(column),
CellTemplate = dt
});
i++;
}
ETCListView.View = gv;

Get Binding from a FrameworkElementFactory

In a GirdView the text of some columns sould be aligned to the right.
To do that I create a DataTemplate, which contains a TextBlock.
Binding bd = new Binding("path");
FrameworkElementFactory tbContent = new FrameworkElementFactory(typeof(TextBlock));
tbContent.SetBinding(TextBlock.TextProperty, bd);
tbContent.SetValue(TextBlock.TextAlignmentProperty, TextAlignment.Right);
DataTemplate dataTemplate = new DataTemplate();
dataTemplate.VisualTree = tbContent;
myGridViewColumn.CellTemplate = dataTemplate;
In an other class I have to access the Bindings of my GridViewColumns. How can I access the Binding of this column?
I had the same issue, so I exposed the TextAlignmentProperty as a public property.

Categories