WPF XAML Template from code fails if it contains eventhandler - wpf

I am loading datatemplates to my TabControl dynamicly at runtime. For each tab im displaying the proper datagrid for that type using ContentTemplateSelector="{StaticResource DetailsDataGridTemplateSelector}"
I need to register KeyDown on the datagrid within the template. This works if used in xaml of cource. But when trying to parse xaml in run-time it fails for:
KeyDown=""DataGrid_KeyDown""
public DataTemplate CreateTemplate(IEnumerable<string> model)
{
string xamlTemplate = $#"<DataTemplate>
<DataGrid ItemsSource=""{{Binding Items}}"" AutoGenerateColumns=""False""
SelectionMode=""Extended"" SelectionUnit=""Cell"" KeyDown=""DataGrid_KeyDown"">
<DataGrid.Columns> ";
foreach (var prop in model)
{
xamlTemplate += $#"<DataGridTextColumn Header=""{prop}"" Binding=""{{Binding {prop},
UpdateSourceTrigger=PropertyChanged}}""/>";
}
xamlTemplate += #"</DataGrid.Columns>
</DataGrid>
</DataTemplate>";
var context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
var template = (DataTemplate)XamlReader.Parse(xamlTemplate, context);
return template;
}
System.Windows.Markup.XamlParseException: ''Failed to create a 'KeyDown' from the text 'DataGrid_KeyDown'.' Line number '2' and line position '127'.'
ArgumentException: Cannot bind to the target method because its signature is not compatible with that of the delegate type.
How can I attach a keydown event handler for the control inside the template when parsing at runtime?

Related

WPF. Access binding collection at runtime

I have a wpf window with some controls binded to different collections.
<controls:CustomTextBox ItemsSource="{Binding Countries}" />
<controls:CustomTextBox ItemsSource="{Binding Localities}" />
The "ItemsSource" is a Custom DependencyProperty for link with the Collection.
I want to get the collection at runtime in PreviewLostKeyboardFocus for validate if the text exists in the collection.
PreviewLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
CustomTextBox textBox = (CustomTextBox)sender;
var bindingExpression = textBox.GetBindingExpression(textBox.ItemsSourceDependencyProperty);
...
}
I get the bindingExpression, but I don't know how to seek the text in the Collection.
Thanks.
Why don't you simply access the CLR wrapper for the dependency property?
CustomTextBox textBox = (CustomTextBox)sender;
var collection = textBox.ItemsSource;
For you to be able to "seek the text" in the collection, you may have to cast it to an appropriate type such as for example an IEnumerable<string> or whatever type Countries or Localities is;
var collection = textBox.ItemsSource as IEnumerable<string>;
if (collection != null)
{
//...
}

Access WPF user Control properties

I've included a WPF control in my Winform C# application, now I would like to access some of the properties of the control. It is a Treeview and I'd like to get this:
Here's the code in XAML:
<TreeView Grid.Row="1" Grid.Column="0" Margin="5,5,5,5" Name="mytreeview"
BorderThickness="0" FontSize="12" FontFamily="Courier New"/>
And what I'm trying to acomplish:
//host3d is the integration object
host3d.Controls["mytreeview"].Items.Add("test");
I'm getting an error, saying controls doesn't contain that definition "Items" which makes sense, I'm sure there's a way to access the methods of the treeview...
I have created a WPF user control and using ElementHost control to host my wpf control in winform application. Then I can use any property from my user control as below
public Form1()
{
InitializeComponent();
UserControl1 control = new UserControl1();
elementHost1.Child = control;
}
private void button1_Click(object sender, EventArgs e)
{
UserControl1 control = elementHost1.Child as UserControl1;
String names = string.Empty; ;
foreach (var item in control.Patients1)
{
names += item.Name + "\n";
}
MessageBox.Show("Name: \n" + names);
}
If you are using WPF you can bind your treeView to a collection and access the collection items from anywhere. I have binding to Patients list and I have direct access to it.

How can I set a binding to a Combox in a UserControl?

I have spent several days on this issue and can't seem to get it to work.
I have a user control that is saved out to a xaml file with the following code:
StringBuilder outstr = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
XamlDesignerSerializationManager dsm = new
XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings));
dsm.XamlWriterMode = XamlWriterMode.Expression;
System.Windows.Markup.XamlWriter.Save(test1, dsm);
String saveCard = outstr.ToString();
File.WriteAllText("inputEnum.xaml", saveCard);
Xaml for the user control:
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding DescriptionWidth}" />
<ColumnDefinition Width="{Binding ValueWidth}" />
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="1" Background="White" FontSize="{Binding FontSizeValue}" Width="Auto"
Padding="10,0,5,0" ItemsSource="{Binding ComboItemsProperty}" SelectedIndex="{Binding EnumSelectedIndex}">
</ComboBox>
</Grid>
The ItemsSource in the combobox is what is giving me problems.
When I save an instance of this usercontrol out to a file, from my understanding, the {Binding ComboItemsProperty} is lost. So, in the constructor of my usercontrol I have:
public UserInputEnum()
{
InitializeComponent();
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBinding(ComboBox.ItemsSourceProperty, bind);
}
Here is my property and the changed method:
EnumItemsCollection ComboItems = new EnumItemsCollection();
public EnumItemsCollection ComboItemsProperty
{
get { return ComboItems; }
set
{
ComboItems = value;
OnPropertyChanged("ComboItemsProperty");
}
}
public void OnPropertyChanged(string propertyName)
{
getEnumItems(this.ComboItemsProperty, this.EnumSelectedIndex, this.ID, this.SubmodeID);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this.ComboItems, new PropertyChangedEventArgs(propertyName));
}
}
Just a note. EnumItemsCollection is a simple class that inherits off ObservableCollection. There is nothing else to this class. (not sure if this makes a difference).
I think this should work but when when I load the XAML file through the XAMLReader, my combobox items won't update.
EDIT:
I ran a little test on an instance of user control that wasn't loaded from XAML but is in the MainWindow.xaml.
Everything works fine. When I add to the ComboItemsProperty, the combobox updates.
So, I took away the {Binding ComboItemsProperty} and tried to set the binding in the code as above changing 'this' to the instance of the user control. Didn't work. This tells me it is the binding code that is not functioning correctly.
I'm fairly certain is the bind.Source line that is the issue. When it is in a UserControl I am unsure of what to put there.
EDIT:
Code that loads usercontrol from file:
FileStream stream = File.Open("usercontrol.xaml", FileMode.Open, FileAccess.Read);
ComboBox cmb = System.Windows.Markup.XamlReader.Load(stream) as ComboBox;
It loads perfectly fine. The Binding just isn't working(ItemsSource={Binding ComboItemsProperty}) because Bindings aren't saved out.
I load it from a file because this program will have many User Interfaces in a sense. Each one will be loaded by a different person using the program.
You need to the set context of the instance containing your property ComboItemsProperty. So instead of 'this' u should set it to this.DataContext or other class object instance containing the ItemSource property you have defined..
Try this,
Binding bind = new Binding();
bind.Mode = BindingMode.TwoWay;
bind.Source = this.DataContext;
bind.Path = new PropertyPath("ComboItemsProperty");
this.SetBinding(ComboBox.ItemsSourceProperty, bind);
Update
According to Serialization Limitations of XamlWriter.Save available on msdn,
Many design-time properties of the original XAML file may already be optimized or lost by the time that the XAML is loaded as in-memory objects, and are not preserved when you call Save to serialize.
Common references to objects made by various markup extension formats, such as StaticResource or Binding, will be dereferenced by the serialization process.
Conclusion, that I made out now is you cannot directly load the UserControl as whole by Serialization - Deserialization procedure of XAML. I think you can load the object instances by Serialization - Deserialization procedure on the DataContext of the UserControl i.e. the custom list(s) or object(s) you have databound.

Change Silverlight DataForm:DataField label value at runtime

I'm having a dataform which is binded to a property in my view-model in a Silverlight application, I've created my entity classes with WCF RIA Services and every property has the attribute of DisplayName which is shown in the dataform datafield label. what I need to do is to add a ":" at the end of every label in the custom datafields that I create.
The reason I need this to happen is because I have a grid in my page which is binded to the list of current objects (e.g. Employees) and I don't want ":" at the end of the grid headers, but I also need ":" when I'm trying to edit or add a new employee.
This is what I've done so far, but it's not working.
public class CustomDataField : DataField
{
public CustomDataField()
{
}
public new object Label
{
get { return base.Label; }
set
{
base.Label = value;
if( value is string )
{
base.Label = (string)value + ":";
}
}
}
}
(1)
When you don't let the DataForm autogenerate the fields, you have more control over the fields and can set the labels manually:
<tkt:DataForm AutoGenerateFields="False" AutoEdit="True">
<tkt:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<tkt:DataField Label="SomeLabel:">
<TextBox Text="{Binding SomeProperty, Mode=TwoWay}" />
</tkt:DataField>
[...]
</StackPanel>
</DataTemplate>
</tkt:DataForm.EditTemplate>
</tkt:DataForm>
(2)
If you need the auto-generating functionality, but you also need more control over how fields are displayed, you could wrap the DataForm into your own custom control. You'll have to implement the auto-generation yourself to build your own EditTemplate, which you'd assign to the DataForm. This is the road that I took.
(3)
Another quick and dirty way would be to iterate through the visual tree after the DataForm has rendered to change the labels. That goes pretty straightforward with a little help from the toolkit:
// needs System.Windows.Controls.Toolkit.dll
using System.Linq;
using System.Windows.Controls.Primitives;
foreach (var field in myDataForm.GetVisualDescendents().OfType<DataField>())
{
field.Label = field.Label + ":";
}
(4)
Finally, I just saw that there is an AutoGeneratingField event on the DataForm that could work (untested):
myDataForm.AutoGeneratingField += (sender, e) => e.Field.Label = e.Field.Label + ":";

How do I load user controls dynamically?

How can I load a user control[s] in a window dynamically (using code at runtime)?
I'd highly recommend having a look at Prism, since composite user interfaces is what it's for. However, since this would require you refactoring your entire application, I'll also answer your question directly.
If you want a single user control in a container, put a ContentControl in your XAML and then set the Content property. If you are using a view model, you could bind Content to a FrameworkElement property on the view model:
contentControlInstance.Content = new CustomUserControl();
If you want multiple controls in a list, use an ItemsControl and assign an ObservableCollection<> to the ItemsSource property. If you are using a view model, you could bind ItemsSource to an ObservableCollection property on the View Model.
Then you can just add/remove views from that ObservableCollection:
private ObservableCollection<FrameworkElement> views =
new ObservableCollection<FrameworkElement>();
private void Initialize()
{
itemsControl.ItemsSource = views;
}
private void AddView(FrameworkElement frameworkElement)
{
views.Add(frameworkElement);
}
For adding multiple controls you need container.
Suppose you have a StackPanel container "myStack"
<Window ..>
<StackPanel Name="MyStack" />
</Window>
You can create control dynamically and add it to container. See code below
void AddButtons()
{
Button B1=new Button(),B2=new Button(), B3=new Button();
B1.Content="Hello";
B2.Content="First";
B3.content="Application";
// Now you can set more properties like height, width, margin etc...
MyStack.Children.Add(B1);
MyStack.Children.Add(B2);
MyStack.Children.Add(B2);
}
Or use binding. Here's a really crude example showing how different WPF controls can be shown in a single WPF window using ContentControl and binding (which is what a toolkit like Prism or Caliburn Micro does).
XAML:
<UserControl x:Class="ViewA">
...
<UserControl/>
<UserControl x:Class="ViewB">
...
<UserControl/>
Code:
void ShowViewModelDialog (object viewModel)
{
var host = new MyViewHost();
FrameworkElement control = null;
string viewModelName = viewModel.GetType().Name;
switch (viewModelName )
{
case ("ViewModelA"):
control = new ViewA();
break;
case ("ViewModelB"):
control = new ViewB();
break;
default:
control = new TextBlock {Text = String.Format ("No view for {0}", viewModelName);
break;
}
if (control!=null) control.DataContext = viewModel;
host.DataContext = control;
host.Show(); // Host window will show either ViewA, ViewB, or TextBlock.
}

Resources