Using Events/Commands with XamlReader - c#

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;

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.
}

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

Related

How to generate event handler automatically in WPF, C#?

I'm currently learning to code WPF application after having used forms for a while.
I have a simple question, but can't seem to find an anwser anywhere.
Is it possible to automatically generate the 'private void eventName(...' code when creating an event ?
For example, if I create a WPF Form with a simple button and code :
Button x:Name = "mButton" Content = "Hello" Click = "mClick" /
Is there a trick to have the private void event handler create itself ? Cause right now, I either write it manually or double click in the event handler properties tab. In widowsForm, I could just double click and it would create itself.
This isn't a big issue for 1 or 2 but if I want to create a dozen buttons, it can become tedious.
I apologize if the question can seem obvious
Of-course, the more automated and lazier the better.
So a few tips:
You can generate a new event handler with an automated name like this:
Assign the x:Name before creating or assigning the event handler
Pick the default <New Event Handler> from the list of options IDE gives you for your event handler. it will generate something like:
MouseDoubleClick="mButton_MouseDoubleClick"
or Click="mButton_Click"
If the name is already taken, it will be prefixed with _1
If the x:Name is not assigned, it will be prefixed with Button_ instead of x:Name
You can generate any already-written event handler like this:
Right click on handler's name in XAML code ("mClick") and choose Go To Definition (The default shortkey is F12)
F12 does the same thing as double-clicking on an event handler value in properties window in WinForms. In case of default event (like Button's Click, it does the same as double-clicking directly on the control)
If you don't want the control to contain any code for event handler like:
<Button /> // handles the click event magically
Then you can add this to the container of all the buttons:
<Container.Resources>
<Style TargetType="Button">
<EventSetter Event="Click" Handler="mClick"/>
</Style>
</Container.Resources>
(obviously, I supposed the name of the container is Container. In your case it might be Window or Menu etc.)
Now every button inside this container has its Click handled by the same handler, in which you can redirect your logic to the right method:
Dictionary<string, Action> dic;
private void mClick(object sender, RoutedEventArgs e)
{
dic[(sender as Button).Name]();
}
These all still so tedious compared to MVVM pattern:
<ItemsControl ItemsSource="{Binding myButtons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ButtonText}" Command="{Binding ButtonAction}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you want to do this rather than use a databinding/command pattern, you can use the XAML designer in Visual Studio. If you start typing Click=" you should be prompted with a list of possible event handlers or a new one - selecting one and pressing tab will create the event handler in the code behind for you (you might want to rename it, or make sure you name the button in XAML first).

How do I ensure that onApplyTemplate gets called before anything else

I have a wpf Custom Control on which I have been working. It has a shared New like this:
Shared Sub New()
'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
'This style is defined in themes\generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(GetType(VtlDataNavigator_24)))
ItemsSourceProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(Nothing, AddressOf OnItemsSourceHasChanged))
End Sub
If an Items source has been set for the custom control this shared sub then invokes the overrideMetadata for the itemssource (as shown below)
Private Shared Sub OnItemsSourceHasChanged(ByVal d As DependencyObject, ByVal baseValue As Object)
Dim vdn As VtlDataNavigator_24 = DirectCast(d, VtlDataNavigator_24)
vdn.RecordCount = vdn.Items.SourceCollection.Cast(Of Object)().Count()
vdn.MyBaseCollection = DirectCast(vdn.ItemsSource, ICollectionView)
vdn.MyBaseEditableCollection = DirectCast(vdn.ItemsSource, IEditableCollectionView)
vdn.MyBaseCollection.MoveCurrentToFirst
vdn.RecordIndex = vdn.MyBaseCollection.CurrentPosition + 1
If Not IsNothing(vdn.FindButton) Then
If vdn.FindButton.Visibility = Visibility.Visible Then
vdn.RecordIndexTextBox.IsReadOnly = False
Else
vdn.RecordIndexTextBox.IsReadOnly = True
End If
End If
vdn.ResetTheNavigationButtons
vdn.SetupInitialStatesForNonNavigationButtons
End Sub
This then fails because buttons referred to in the code (and routines called from it) have not yet been instantiated because the override for OnApplyTemplate (shown below) has not been called.
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
RecordIndexTextBox = CType(GetTemplateChild("PART_RecordIndexTextBox"), TextBox)
RecordCountTextBox = CType(GetTemplateChild(RecordCountTextBoxPart), TextBox)
RecordTextBlock = CType(GetTemplateChild(RecordTextBlockPart), TextBlock)
OfTextBlock = CType(GetTemplateChild(OfTextBlockPart), TextBlock)
FirstButton = CType(GetTemplateChild(FirstButtonPart), Button)
PreviousButton = CType(GetTemplateChild(PreviousButtonPart), RepeatButton)
NextButton = CType(GetTemplateChild(NextButtonPart), RepeatButton)
LastButton = CType(GetTemplateChild(LastButtonPart), Button)
AddButton = CType(GetTemplateChild(AddButtonPart), Button)
CancelNewRecordButton = CType(GetTemplateChild(CancelNewButtonPart), Button)
EditButton = CType(GetTemplateChild(EditButtonPart), button)
CancelButton = CType(GetTemplateChild(CancelButtonPart), Button)
RefreshButton = CType(GetTemplateChild(RefreshButtonPart), Button)
SaveButton = CType(GetTemplateChild(SaveButtonPart), Button)
DeleteButton = CType(GetTemplateChild(DeleteButtonPart), Button)
FindButton = CType(GetTemplateChild(FindButtonPart), Button)
End Sub
If I add something along the lines of:
vdn.OnApplyTemplate
to OnItemsSourceHasChanged, OnApplyTemplate is called but nothing is resolved (see illustration below).
BUT if I don't set an itemssource on my control, then OnApplyTemplate gets called and the items resolve (see below)
Has anyone encountered this sort of behaviour before and found a way to correct it such that OnApplyTemplate is always the first thing to get called before anything that might require access to controls that have yet to be resolved.
Edit
The curious thing about this issue is that (and doesn't this always seem to be the case!) this was working until obviously I did something or set some property. What I am left with is a project that runs if I do not set an Items source on my custom control, and one which doesn't if I do because the custom handler I have in place to handle when the items source is changed on my custom control is running before OnApplyTemplate gets called.
Well I have at last been able to determine that my custom controls Itemssource property is being changed before the control is being drawn and rendered and therefore the code I have in place to set things up following the ItemsSource change raises null reference exceptions because the main control has yet to be rendered.
Given that it did work it must be something I've done but I'm now out od ideas as to how to delve into this further and actually find the reason. I'd welcome any suggestions you might have or potential work rounds.
Edit in relation to comments below: typical part of control template.
<!-- First Button -->
<Button Style="{StaticResource vtlNavButtonStyle}"
x:Name="PART_FirstButton"
Tag="First_Button"
Visibility="{Binding Path=NavigationButtonVisibility,Converter={StaticResource booltovis}, RelativeSource={RelativeSource TemplatedParent}}"
ToolTipService.ShowOnDisabled="False"
ToolTipService.ShowDuration="3000"
ToolTipService.InitialShowDelay="500">
<Button.ToolTip>
<Binding Path="FirstButtonToolTip"
RelativeSource="{RelativeSource TemplatedParent}"
TargetNullValue="{x:Static p:Resources.FirstText}">
</Binding>
</Button.ToolTip>
<StackPanel>
<Image Style="{StaticResource vtlImageStyle}">
<Image.Source>
<Binding Path="FirstImage"
RelativeSource="{RelativeSource TemplatedParent}">
<Binding.TargetNullValue>
<ImageSource>/VtlWpfControls;component/Images/16/first.png</ImageSource>
</Binding.TargetNullValue>
</Binding>
</Image.Source>
</Image>
</StackPanel>
</Button>
Calling OnApplyTemplate yourself isn't going to help; the framework will call it when the template has actually been applied. That said, the order in which things happen is not deterministic -- the template may or may not be applied before the ItemsSource is set. I'm working with UWP apps for Windows 10, which is a slightly different beast, but we've solved a similar issue doing something like this:
private TextBlock textBlock;
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Grab the template controls, e.g.:
textBlock = GetTemplateChild("MyTextBlock") as TextBlock;
InitializeDataContext();
DataContextChanged += (sender, args) => InitializeDataContext();
}
private void InitializeDataContext()
{
ViewModel ViewModel = DataContext as ViewModel;
if (viewModel != null)
{
// Here we know that both conditions are satisfied
textBlock.Text = ViewModel.Name;
}
}
The key is to not start listening for DataContextChanged until the template has been applied. If the data context has already been set, the first call to initializeDataContext takes care of things; if not, the callback takes care of things.
(In your case, replace our data context listening with items source listening, I suppose.)
This isn't an answer to your question, but instead expands on some things you mentioned in the comments.
I really think that it would benefit you to look into WPF commands as they pertain to custom controls. Your data navigator control sounds like it essentially supports a number of actions (go to first/previous/next/last; add; edit; cancel; etc) that you invoke using Button controls in the control template. Rather than looking for the buttons in OnApplyTemplate (at which point you store references to them so that you can presumably hook into their Click event later) you should support commands in your control: the buttons in the template would then bind to these commands.
An example would probably make this a bit clearer. The following is code for a custom control that supports two actions: go-to-first-page, and go-to-last-page. In the static constructor I register two command bindings, one for each action. These work by calling into a helper method that takes the command to "bind" to, plus a pair of delegates that get called when the action is invoked.
The commands I am using here are provided by the WPF framework, and are static properties contained in the static NavigationCommands class. (There are a bunch of other similar classes containing commands, just follow the links in the "See Also" section of that MSDN page).
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace StackOverflow
{
public class TestControl : Control
{
static TestControl()
{
RegisterCommandBinding<TestControl>(NavigationCommands.FirstPage,
x => x.GoToFirstPage());
RegisterCommandBinding<TestControl>(NavigationCommands.LastPage,
x => x.GoToLastPage(), x => x.CanGoToLastPage());
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl),
new FrameworkPropertyMetadata(typeof(TestControl)));
}
void GoToFirstPage()
{
Console.WriteLine("first page");
}
void GoToLastPage()
{
Console.WriteLine("last page");
}
bool CanGoToLastPage()
{
return true; // Would put your own logic here obviously
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute) where TControl : class
{
RegisterCommandBinding<TControl>(command, execute, target => true);
}
public static void RegisterCommandBinding<TControl>(
ICommand command, Action<TControl> execute, Func<TControl, bool> canExecute)
where TControl : class
{
var commandBinding = new CommandBinding(command,
(target, e) => execute((TControl) target),
(target, e) => e.CanExecute = canExecute((TControl) target));
CommandManager.RegisterClassCommandBinding(typeof(TControl), commandBinding);
}
}
}
The following is the control's default template. As you can see there are simply two Button controls, each one of which binds to the relevant command via its Command property (note this is not a data binding, ie. you're not using the {Binding} markup extension).
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<Style TargetType="{x:Type local:TestControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<StackPanel Orientation="Horizontal">
<Button Command="NavigationCommands.FirstPage" Content="First" />
<Button Command="NavigationCommands.LastPage" Content="Last" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Finally, here's the custom control in a Window. As you click the "First" and "Last" buttons you can see the actions being invoked by watching the relevant text appear in the debug console window.
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow">
<local:TestControl VerticalAlignment="Top" />
</Window>
If you use commands in this way then you should be able to simplify your control's code significantly.
I had a similar issue - a custom control (specifically, a class derived from Control) would show binding errors whenever a new instance of the control was instantiated. This was because the control template was being created before the bindings were setup. Once the bindings took effect, then the control would start to work.
To "fix" this (or work around it anyway) I just added a call to ApplyTemplate() to the control's constructor. So it ends up looking like this:
public CustomControl()
{
InitializeComponent();
ApplyTemplate();
}
Then there were no more binding errors.

why content template of button does not have object?

i am just exploring windows phone runtime apps template. But i am seeing a weird thing.
I have Button defined in Xaml with ContentTemplate set in it. I wanted extract the Image control defined in the ContentTemplate of this button. But it is coming null.
Xaml code :-
<Button x:Name="PlayButton" Click="PlayButton_OnClick">
<Button.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="Panel">
<Image x:Name="ControlImg"
Width="100"
/>
<TextBlock Text="text block" />
</StackPanel>
</DataTemplate>
</Button.ContentTemplate>
Here is button Click event :-
private async void PlayButton_OnClick(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
var ct = btn.ContentTemplate; // this part is also not showing controls in it when expending ct at runtime.
var img = btn.FindName("ControlImg") as Image; // coming null
var stckpnl = btn.FindName("Panel") as StackPanel;// coming null
}
Can anybody check this out why is this happening ?
Edit :- I have broken my problem and reach this very bottom simple level and after seeing this i am just not getting why is this happening ?
That is strange behavior. It should have stack Panel and image in control template. As a work around you can use ContentTemplateRoot to get the image and stackpanel. I have test this, it is working.
((StackPanel)btn.ContentTemplateRoot).Children[0] // image
Hope this helps
Edit:
For Details about why FindName is not working see the the Remarks section on on MSDN
. Here is some relevant quotes
Important In order to use the FindName method effectively, you should understand the concept of a XAML namescope, and how a XAML namescope is created at XAML load time and then referenced and possibly modified at run time. For more info see XAML namescopes.
The most common usage of FindName in your Windows Runtime code will be from within the generated InitializeComponent call for a XAML page. In this situation, FindName is invoked only after the XAML page is loaded. InitializeComponent provides the infrastructure such that any object that was instantiated by XAML loading can conveniently be accessed by your code-behind code. You can then reference the objects as a variable that shares the same name as the markup-declared x:Name.
A run-time API such as FindName is working against a run-time object tree of the app as it exists in memory. When part of this object tree is created from templates or run-time loaded XAML, a XAML namescope is typically not contiguous within that object tree. The result is that there might be a named object in the object tree that a given FindName scope cannot find. The discontinuities between XAML namescopes that you might encounter in typical application scenarios are when objects are created by applying a template, or when objects are created by a call to XamlReader.Load and subsequently added to the object tree.
As you are using DataTemplate so the xaml object tree is not contiguous so that is why FindName is failed to find the control from the xaml tree.
hope this explains...

WPF: Binding to commands in code behind

I have a WPF Microsoft Surface Application and I'm using MVVM-Pattern.
I have some buttons that are created in code behind and I would like to bind commands to them, but I only know how that works in the XAML
like this:
<Custom:SurfaceButton Command="{Binding SaveReservationCommandBinding, Mode=OneWay}"/>
But I cannot do it like this because my buttons do not exist in the XAML, only in the code behind.
So how would a command binding like that works in code behind?
The accepted answer will work great if the Button has access to the Command. However, in MVVM these are usually kept separate (the Button in the View and the Command in the View-Model). In XAML you'd normally use a data binding to hook it up (like the example in the question).
My program gave me an error when my dynamic Button couldn't find the Command (because it was in a totally different namespace). This is how I ended up solving this:
SurfaceButton.SetBinding (Button.CommandProperty, new Binding("SaveReservationCommand"));
Assuming that you have a named your SurfaceButton to "SurfaceButton1" and you have access to an instance of the command, you can use the following code:
SurfaceButton1.Command = SaveReservationCommand;
I took the code from the link posted by Anvaka as template. I use RadMenuItem of Telerik, but surely you can use any other component that expose Command property.
item = new RadMenuItem();
item.Header = "Hide Column";
DependencyProperty commProp = RadMenuItem.CommandProperty;
if (!BindingOperations.IsDataBound(item, commProp)) {
Binding binding = new Binding("HideColumnCommand");
BindingOperations.SetBinding(item, commProp, binding);
}
//this is optional, i found easier to pass the direct ref of the parameter instead of another binding (it would be a binding to ElementName).
item.CommandParameter = headerlCell.Column;
menu.Items.Add(item);
Hope it helps ... and if something is not clear, sorry, it's my first post :)
This works
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}, Path=SaveReservationCommand}"

Silverlight 4: How to find source UI element from contextmenu's menuitem_click?

I have a datagrid and I added silverlight 4 toolkit contextmenu to textbox in datagrid as follows. When users right click on the textbox, contextmenu is being displayed. When users click the menu item with Header "Test", "MenuItem_Click" is getting executed. Now I want to access the textbox from the MenuItem_Click and modify its properties like background etc. Is there anyway to find textbox element(which is contextmenu's parent) from MenuItem_Click event?
It appears to me that I am missing something very simple.
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding AcctId}"
Style="{StaticResource documentTextBoxStyle}"
ToolTipService.ToolTip="Right Click to modify parameters" >
<toolkit:ContextMenuService.ContextMenu >
<toolkit:ContextMenu >
<toolkit:MenuItem Header="Test" Click="MenuItem_Click"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</TextBox>
</DataTemplate>
There's really no need for a workaround, it's as simple as using the databinding:
(sender as MenuItem).DataContext as TextBox
Will give you the TextBox you're after. (Storing stuff in the Tag field is really not something you want to clutter your code with.)
Though I did not find a solution to this, I found couple of workarounds
Traverse the visual tree and findout the textbox
Modify the code in control toolkit sources to expose the internal member 'Owner' as a public Property which contains reference to the owner of the context menu, in my case, the textbox.
I wonder why SL toolkit guys made the owner to be internal not public. Probably their idea is to manage 'ContextMenu' only through 'ContextMenuService' but unfortunately ContextMenuService doesnt give the Owner. Hopefully SL toolkit guys will give us a way to get the owner of the context menu in future releases.
I'm not sure if this works in Silverlight, but I had a similar issue with WPF recently. If you use the ContextMenu's PlacementTarget property, it should return the element that was used to open the ContextMenu.
All I can suggest is giving your MenuItem a Tag with it's parent's TextBlock name like this:
EDIT: Can't figure out how to paste in Xaml, but I'm sure you know how to add this.
Then in your click event you find the TextBlock:
private void MenuItem_TextBlockClick(object sender, RoutedEventArgs e)
{
MenuItem menuItem = (MenuItem)sender;
TextBlock textBlock = this.FindName((string)menuItem.Tag) as TextBlock;
/// do something
}
The issue I found was the parent of the MenuItem is ContextMenu, which is fine. But once you try and get the Parent of the ContextMenu it just crashes.

Categories