Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Event handlers when using a string as a data template for dataform in Silverlight

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;
like image 624
Kulingar Avatar asked Jan 07 '13 01:01

Kulingar


2 Answers

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.

  1. Locate the ComboBox instance inside the rendered template
  2. Wait until the template is rendered to look for the ComboBox

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.

like image 188
John Bowen Avatar answered Nov 17 '22 19:11

John Bowen


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.

like image 45
Luke Woodward Avatar answered Nov 17 '22 19:11

Luke Woodward