Using Events/Commands with XamlReader

2020-04-10 02:30发布

问题:

I am dynamically building my datatemplate using XamlReader.Parse(string). The problem I have is that I can't put any events on any of the controls I create using the XamlReader. After doing some research online I've learned that this is a known limitation of XamlReader.

I don't know a lot about commands in WPF but could I somehow use them to gain the same result? If so how? If not is there any way I can handle an event in my code behind from a control created using Xaml Reader?

Below is an example of the datatemplate I create. I have the MenuItem_Click event handler defined in the the codebehind of the Window that will host this datatemplate.

I get the following error when trying to run it: System.Windows.Markup.XamlParseException was unhandled: Failed to create a 'Click' from the text 'MenuItem_Click'.

DataTemplate result = null;
        StringBuilder sb = new StringBuilder();

        sb.Append(@"<DataTemplate 
                        xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
                        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
                            <Grid Width=""Auto"" Height=""Auto"">

                            <TextBlock Text=""Hello"">
                                <TextBlock.ContextMenu>
                                    <ContextMenu>
                                         <MenuItem 
                                          Header=""World""
                                          Click=""MenuItem_Click""></MenuItem>
                                    </ContextMenu>
                                </TextBlock.ContextMenu>
                            </TextBlock>

                            </Grid>
                      </DataTemplate>");

        result = XamlReader.Parse(sb.ToString()) as DataTemplate;

回答1:

Hoping a late answer might help others:

I found that I needed to bind the events after the parsing, and had to remove the click event from the Xaml string.

In my scenario I applied the resulting DataTemplate to an ItemTemplate, wired up the ItemSource, and then added the handler. This does mean the click event would be the same for all the items, but in my case the header was the information needed and the method was the same.

//Set the datatemplate to the result of the xaml parsing.
myListView.ItemTemplate = (DataTemplate)result;

//Add the itemtemplate first, otherwise there will be a visual child error
myListView.ItemsSource = this.ItemsSource; 

//Attach click event.
myListView.AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(MenuItem_Click));

And then the click event needs to get back to the original source, the sender will be the ListView in my case that was using the DataTemplate.

internal void MenuItem_Click(object sender, RoutedEventArgs e){
    MenuItem mi = e.OriginalSource as MenuItem;
    //At this point you can access the menuitem's header or other information as needed.
}


回答2:

Take a look at this link. Most of the solutions there will apply with Parse as well. I'm not really a C# dev, so the only one I can really explain is the last one, which is something of an if-all-else-fails option:

First, you add ID's to your XAML instead of Click, etc attributes. Then you can use FindLogicalNode to get at nodes, and then wire up the events yourself.

For example, say you give your MenuItem ID="WorldMenuItem". Then in your code after calling parse, you can do this:

MenuItem worldMenuItem = (MenuItem)LogicalTreeHelper.FindLogicalNode(result, "WorldMenuItem");
worldMenuItem.Click += MenuItem_Click; // whatever your handler is