I am attempting to form some xaml for a dataform programmatically using a string. I can get the combo box to appear. but when I attempt to use the code specifying the "MouseLeftButtonUp" or the "Loaded" event handler in the string; the page will turn white (no noticeable error) out after going into it. Please see relevant code below.
StringBuilder editTemplate = new StringBuilder("");
editTemplate.Append("<DataTemplate ");
editTemplate.Append("xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
editTemplate.Append("xmlns:toolkit='http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit' ");
editTemplate.Append("xmlns:navigation='clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation' ");
editTemplate.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' >");
editTemplate.Append("<StackPanel>");
editTemplate.Append(@" <toolkit:DataField Label='" + GetFieldWithoutNumber(theInfo, theDataContext) + "'>");
/* Won't Work */ editTemplate.Append(@" <ComboBox MouseLeftButtonUp='ComboBox_MouseLeftButtonUp' />");
/* Will Work */ editTemplate.Append(@" <ComboBox />");
editTemplate.Append(@" </toolkit:DataField>");
editTemplate.Append("</StackPanel></DataTemplate>");
dynamicDataForm.EditTemplate = XamlReader.Load(editTemplate.ToString()) as DataTemplate;
Event handlers hooked up in XAML are required to be declared in the code-behind connected to the XAML file. In the case of a ResourceDictionary or anything loaded from XamlReader.Load there can't be any code-behind, so event handlers can't be set in the XAML. The easiest way to get around this restriction would be to not build your template from strings and just declare it in the Resources section of your XAML file which you can then do like:
Resources["MyTemplate"] as DataTemplate
to get the template and assign it in code like you're doing here, or just use StaticResource in XAML. As long as it stays in the same XAML file connected to this code the event handlers you have in it currently should work fine. The dynamic part of the strings would also need to be changed to use Bindings.
If you want to stick with the XamlReader method you have 2 problems to solve.
To find the ComboBox you need to first give it an x:Name attribute in the template text (you can just replace the event code currently there). Next you need to be able to locate an item in the visual tree by name. This is fairly straightforward and you can find an example here to do that.
To call this code at the right time you either need to override OnApplyTemplate, which unfortunately won't work if you're in something like a UserControl, or use another trick to keep it from running until all the controls are rendered. Here's a full example that could go in a constructor and uses the find method linked from above:
DataTemplate template = Resources["MyTemplate"] as DataTemplate;
dynamicDataForm.ContentTemplate = template;
Dispatcher.BeginInvoke(() =>
{
ComboBox button = FindVisualChildByName<ComboBox>(this, "MyControl");
if (button != null)
button.MouseLeftButtonUp += (s, _) => MessageBox.Show("Click");
});
In your case it looks like your template might need to wait to switch to an edit state before it renders in which case you'd need to hold off on connecting the event and find some other event on your dataform that happens when that state is changed.
One solution is to handle the BeginningEdit
event of the DataForm and use that to subscribe your event handler to the ComboBox's MouseLeftButtonUp
event.
To do this, add to your code-behind a private field named isEventWiredUp
. We'll use this field to keep track of whether we've subscribed to the event and prevent the event from being subscribed to more than once.
Next, add an x:Name="..."
attribute to your ComboBox
. We use this name to get at the combobox.
Once that is done, add the following two methods, which should do what you want. Replace yourComboBoxName
with the x:Name
you gave to your combobox:
private void dynamicDataForm_BeginningEdit(object sender, CancelEventArgs e)
{
Dispatcher.BeginInvoke(OnBeginEdit);
}
private void OnBeginEdit()
{
if (!isEventWiredUp)
{
var combobox = dynamicDataForm.FindNameInContent("yourComboBoxName") as ComboBox;
if (combobox != null)
{
combobox.MouseLeftButtonUp += combobox_MouseLeftButtonUp;
isEventWiredUp = true;
}
}
}
Subscribe the first of these two methods to the DataForm's BeginningEdit
event.
I have to admit that I was unable to get the MouseLeftButtonUp
event to fire on the ComboBox. I'm not sure why this happens, but it seems to be a general problem with the ComboBox as opposed to something that happens because of the way you're constructing XAML. I was able to get an event handler for the ComboBox's SelectionChanged
event to work, however.
I also tried replacing the Dispatcher.BeginInvoke
line with a direct call to the OnBeginEdit
method, but I found that this approach didn't work. The events weren't quite wired up correctly; again, I'm not sure why.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With