Cannot bind to button events using x:Bind in DataTemplates - generates XAML error - xaml

With the {x:Bind} markup syntax you can bind to events provided the method meets the following requirements:
Match the signature of the event.
OR have no parameters.
OR have the same number of parameters of types that are assignable from the types of the event parameters.
This works perfectly fine outside of a DataTemplate. Once the binding happens inside the DataTemplate the compiler generates the following error:
Xaml Internal Error error WMC9999: Object reference not set to an
instance of an object.
What is the fix for binding to events inside DataTemplates?
Full example code here.
Snippet of the example code below - note the first button (line 2) is fine and the second button (line 6) is also fine. If you comment out line 6 and and comment in line 7, the error occurs.
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Tapped="{x:Bind Click}" Content="WORKING"/>
<ListView ItemsSource="{x:Bind Names}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Customer">
<Button Content="{x:Bind Title}"/>
<!--<Button Tapped="{x:Bind Clicky}" Content="{x:Bind Title}"/>-->
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>

I was able to get it to work with the following code:
<DataTemplate x:DataType="local:Customer">
<StackPanel Orientation="Vertical">
<Button Tapped="{x:Bind Clicky}" Content="{x:Bind Title}" />
</StackPanel>
</DataTemplate>
It seems as though you need to have it inside a container for it work. I have no idea why I am guessing magic.

The parser cannot find Clicky from the datacontext of the button while in the template. Because the object that is being handed to the button in the template (from the Names on the ItemSource of the parent) is not the same as outside the template which has a Clicky. You will need to bind Clicky to the page's datacontext to get it to work.
Otherwise turn off any design time operations by setting Tapped="{x:Bind Clicky, IsDesignTimeCreatable=False}.

Related

Using x:Bind inside the GridView's ItemTemplate layout User Control in UWP

In the Universal Windows Platform API, how do I use x:Bind inside of a User Control (intended to be the layout for a GridView's ItemTemplate) to bind to instance properties of a GridView's ItemSource?
Background
I'm trying to re-create the layout found in Windows 10 stock apps like Sports, News, Money, etc.
I'm using a two GridViews for the main area of the app; one for "featured articles" (2 large photos w/ headlines) and one for all the other articles (smaller photos w/ headlines).
I'm able to bind to a data source that I supply in the code behind (a List where NewsItem is a POCO with a Image and Headline property) Here's the pertinent parts of the MainPage.xaml:
<Page ...
xmlns:data="using:NewsApp.Models" />
....
<GridView Name="FeaturedItems" Grid.Row="0">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:NewsItem">
<Grid Name="mainPanel" HorizontalAlignment="Stretch" Width="500" >
<Image Source="{x:Bind Image}" HorizontalAlignment="Stretch" />
<TextBlock Text="{x:Bind Headline}" />
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
....
The Image and Headline bind just fine (even though they've not been styled correctly). However, instead I think I need to bind to a User Control to get the styling options I want, control over resizing esp. when using Visual State Triggers and to simplify the XAML in general (at least, this was the technique suggested to me.)
So, I added a new User Control to the project (FeaturedItemControl.xaml), and copied in the DataTemplate's child Grid:
<UserControl ... >
<Grid Name="mainPanel" HorizontalAlignment="Stretch" Width="500" >
<Image Source="{x:Bind Image}" HorizontalAlignment="Stretch" />
<TextBlock Text="{x:Bind Headline}" />
</Grid>
</UserControl>
And then back in the MainPage.xaml, I change the DataTemplate to reference the new FeaturedItemControl:
<GridView Name="FeaturedItems" Grid.Row="0">
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:NewsItem">
<local:FeaturedItemControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
However, I get the error message for both Image and Headline properties: Invalid binding path 'Headline': Property 'Headline' can't be found on type 'FeaturedItemControl'.
I've tried a few things but am flailing just throwing code at the problem without understanding what I'm doing. Any help would be greatly appreciated.
Thank you for your kind attention.
Using Depechie's answer, I formulated this little cheat cheat for posterity:
Do note that you MUST use this technique to utilize the VisualStateManager with items inside your data bound controls' (GridView, ListView) data templates.
1) Create a User Control.
2) Cut the content of the DataTemplate in your page and paste it into the User Control replacing the template's Grid.
3) Reference the User Control from inside the Data Template:
4) Modify the contents of the User Control changing x:Bind statements to utilize object.property notation:
<UserControl>
<StackPanel>
<Image Source="{x:Bind NewsItem.LeadPhoto}" />
<TextBlock Text="{x:Bind NewsItem.Headline}" />
<TextBlock Text="{x:Bind NewsItem.Subhead}" />
</StackPanel>
</UserControl>
5) Add this in the User Control's Code Behind:
public Models.NewsItem NewsItem { get { return this.DataContext as Models.NewsItem; } }
public ContactTemplate()
{
this.InitializeComponent();
this.DataContextChanged += (s, e) => Bindings.Update();
}
Well it's possible to use x:Bind in user controls, but you'll need to add some extra code behind.
I encountered the same problem in my project, you can see the result here : https://github.com/AppCreativity/Kliva/tree/master/src/Kliva/Controls
So what you need to do is, create a property in the code behind of your user control that points to the correct DataContext.
If you do that, you can use properties of that DataContext in the xaml of your control: for example:
Do note that in the constructor of your control you do need to add: DataContextChanged += (sender, args) => this.Bindings.Update(); because the datacontext will change depending on the page where your control is used!
Then on the page where you are placing this control, you'll also need to do the same to enable the x:bind to work.
You'll see this in my example on the MainPage.DeviceFamily-Mobile.xaml and MainPage.xaml.cs files.
Hope this helps.
x:Bind isn't really hierarchical like Binding/DataContext is. Additionally when you're not directly inside a DataTemplate (such as inside your user control) the object that x:Bind tries to use is 'this' rather than 'this.DataContext'. My current line of thinking on how to solve this sort of issue is to try not to use UserControls anywhere. Instead preferring DataTemplates contained within a ResourceDictionary. There are some pretty strong caveats to this approach though, you will for example crash the xaml compiler if you use x:Bind inside a data template that was created from the ResourceDictionary item template (add new item). you can find a pretty complete example here https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/XamlBind its important to note in the sample where they show the ResourceDictionary being used that its not actually just a ResourceDictionary.xaml its also a ResourceDictionary.xaml.cs (this is where the generated code from x:Bind ends up)
Another option is to add Headline and Image as properties on your user control and x:Bind them from the template, then inside the user control x:Bind as you are currently doing, but now the x:Bind generated path 'this.Headline' will exist. Unfortunately the order things are actually bound means that the x:Bind's you have inside your user control will have to be OneWay rather than the default OneTime. this is because x:Bind OneTime does the bind inside the InitializeComponent call, and any set of properties/DataContext stuff doesn't get done until after that has already run.
So to sum this up, you have two options, use data templates everywhere, or bind to properties that are directly on the user control.

{Binding ElementName=...} does not work from any CompositeTransform property

I have the following code in which I hide a WebView just under the main Grid (LayoutRoot) so I can do a sliding animation later:
<Page...>
<Grid x:Name="LayoutRoot">
...
<Grid x:Name="ContentRoot">
...
</Grid>
<WebView...>
<WebView.RenderTransform>
<CompositeTransform TranslateY="{Binding ElementName=LayoutRoot,
Path=ActualHeight}"/> <!--Does not work-->
</WebView.RenderTransform>
</WebView>
</Grid>
</Page>
When I first type the {Binding ElementName=...} line into the designer, the WebView appears just below the Grid like it should. However, when I rebuild the solution or run the app, the WebView simply obscures the whole LayoutRoot.
This will happen regardless of what I am binding to/whatever the control is; however, binding to the exact same expression will show up properly in the designer and in the phone. To demonstrate what I am saying:
<Button Width="{Binding ElementName=LayoutRoot, Path=ActualHeight}"> <!--Works perfectly, both on designer and phone-->
<Button.RenderTransform>
<CompositeTransform SomeProperty={Binding ElementName=SomeElement, Path=SomePath}"/> <!--This does not work-->
</Button.RenderTransform>
</Button>
Is there any way to bind to LayoutRoot.ActualHeight short of writing C# code for this?
One problem you have is you are trying to bind to ActualHeight which is not a dependency property nor an observable (INotifyPropertyChanged) property, so the binding is only evaluated once when it's first created.

Why is this XAML failing with "The property "Content" can only be set once."?

I've got this XAML:
<AppBarButton Icon="Protected" Label="Privacy Policy" >
<AppBarButton.Flyout>
<StackPanel>
<Flyout>
<TextBlock Text="Photrax extracts information from images you load into it. The information it extracts includes location information (where the photos were taken, when that is available) and the date and time the photo was taken. This data is stored in a local/internal/embedded (SQLite) database. This data is not stored in the cloud but only on your local device." TextWrapping="Wrap" FontSize="26" FontFamily="Verdana">
</TextBlock>
<TextBlock Text="To reiterate: Your data is not shared with anyone else. It is stored only on the device from which you use Photrax." TextWrapping="Wrap" FontSize="26" FontFamily="Verdana">
</TextBlock>
</Flyout>
</StackPanel>
</AppBarButton.Flyout>
</AppBarButton>
...which fails with, "The property "Content" can only be set once."
Why is there a problem with this, when the following XAML, which is basically the same, compiles fine:
<AppBarButton Icon="MapPin" Label="Colors In Use" Tapped="appbarbtnPhotosetColorMapping_Tapped">
<AppBarButton.Flyout>
<Flyout>
<StackPanel Background="Azure">
<TextBlock Text="Photoset:Pushpin Color Legend" TextWrapping="Wrap" FontSize="26" FontFamily="Verdana"></TextBlock>
<TextBlock x:Name="textblock0" Text="Unused" Foreground="Red" FontFamily="Segoe UI" FontSize="13" Margin="4" />
. . .
?
I get other err msgs with that xaml, too (about the following types being expected: flyoutbase and uielement), but I think it's the Content business that is giving me the business.
I pasted the problem code into kaxaml, but it doesn't even know what an AppBarButton is, and complained about that being an invalid element.
UPDATE
I can't test it yet, but I'm thinking that what Abdallah means is that I need to change this:
<StackPanel>
<Flyout>
. . .
</Flyout>
</StackPanel>
...to this:
<Flyout>
<StackPanel>
. . .
</StackPanel>
</Flyout>
You have two problems here :
1) The property "Content" can only be set once. :
You can't set the content for <Flyout> </Flyout> more than once and you set the content twice (Two TextBlocks) in first XAML code and in the working XAML once(One StackPanel), According to MSDN :
<Flyout>
singleUIElement
</Flyout>
singleUIElement :
A single object element that declares the content. This must be an
object that has UIElement in its hierarchy (plain strings don't work).
This can be a container, such as a Panel derived class, so that
multiple content items within the Flyout can be arranged in layout.
2) The following type was expected: "FlyoutBase".
You can't set Flyout property to any class that not derived from FlyoutBase class (i.e only Flyout and MenuFlyout ). you set the Flyout property in first XAML code to StackPanel and in the working XAML to Flyout .

Get ListBox DataContext from UserControl

I have a UserControl with a button inside it. The UserControl its DataContext is one of my models so I can bind to it. However, for the button I want to be able to call a method from my viewmodel. The DataContext of the ListBox is this ViewModel.
Because my ContextMenu also needs the same DataContext I've bound them like this:
Command="{Binding Path=DataContext.AttendEventCommand, ElementName=EventListBox}"
Calling the EventListBox element and using its DataContext to call the AttendEventCommand. However I would like to call the AttendEventCommand from a button on the UserControl. I tried doing it the same way but sadly it doesn't work.
My data context is set like this:
DataContext="{Binding Path=EventList, Source={StaticResource Locator}}
My listbox code:
<ListBox x:Name="EventListBox" ItemsSource="{Binding Occurrences}" Margin="0,50,0,0" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<uctl:EventListItem HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="auto" Height="auto">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu IsZoomEnabled="True" x:Name="ContextMenu">
<toolkit:MenuItem x:Name="Going" Header="{Binding AttendingText}" Command="{Binding Path=DataContext.AttendEventCommand, ElementName=EventListBox}" CommandParameter="{Binding}"/>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</uctl:EventListItem>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And my UserControl's button:
<Button Grid.RowSpan="3" Grid.Column="5" Opacity="0" Command="{Binding Path=DataContext.AttendEventCommand, ElementName=EventListBox}" CommandParameter="{Binding}"/>
I believe your problem is not that what you want to do is not working; instead, your design appears to be wrong.
What you have now is like this:
You have a Window that has a DataContext and a ListBox. The ListBox has an ItemsSource, which we'll assume is some IEnumerable<Occurrence>.
The appearance of each item in your ListBox is an EventListItem, which is a UserControl that contains at least one Button.
You want this Button's Command to call a method on your Window's DataContext.
This last sentence is wrong. The fact that the item has the button implies that it does something that is related to the item, not the window contents. If this is not true, then the visual design of the window and listbox items should probably be reconsidered.
If the button is in fact affecting the item, then you should not call a method on your Window's DataContext, but instead call a method on your item's DataContext.
The solution is to wrap your model object Occurrence in a view model class of its own. Let's call it OccurrenceViewModel. Your ListBox's ItemsSource would be some form of IEnumerable<OccurrenceViewModel>. Because it's a view model, it's allowed to implement Command methods, which can then in one way or another manipulate the Occurrence, either directly or preferably by passing it to some class that implements the use case.
The DataContext of your EventListItem will be a Model of your ItemsSource because it is part of the DataTemplate. So you have to set it explicitly.
Refer to How to implement a navigation button for some of the code I'll be using as a solution.
Lets assume your custom UserControlis very basic like so:
<UserControl>
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<Button Command="{Binding DataContext.SimpleCommand}" CommandParameter="1"></Button>
</Grid>
</UserControl>
Where SimpleCommand is the Command you want to call in the Top View Model.
Then you have to change your DataTemplate to
<DataTemplate>
<Grid>
<uctl:EventListItem
DataContext="{Binding ElementName=myListBox}"/>
<!-- more xaml -->
</Grid>
</DataTemplate>
Your <toolkit:ContextMenu> should work as is.
Put a break point at the Execute function of the ICommand and you will see it will get
hit there everytime.
public void Execute(object parameter)
{
// logic to execute when user hits the command
int debug_var = 0; // put a break point here
}

Binding events to buttons in an ItemsControl

I have a windows phone 7 app with some xaml that looks like:
<Grid x:Name="ContentGrid" Grid.Row="1">
<ItemsControl x:Name="MyView" ItemTemplate="{StaticResource MyInner}"/>
</Grid>
The item template here looks like:
<DataTemplate x:Key="MyInner">
<ItemsControl ItemsSource="{Binding}" ItemTemplate="{StaticResource MyInner_Item}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
And finally, the MyInner_Item template looks like:
<DataTemplate x:Key="MyInner_Item">
<Button x:Name="MyButton">
<Button.Template>
<ControlTemplate>
<Border HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="myborder">
<Image Source="{Binding Path=ImageUri}" Width="{Binding Path=Width}" Height="{Binding Path=Height}" Stretch="Fill" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
So, it's an ItemsControl, which contains an ItemsControl, which contains buttons. This essentially creates a 2D array of buttons.
What I want to do is add an event handler to the Click event of the buttons.
Here's the catch: the code that sits behind this is written in F#. I can't, to the best of my knowledge, specify my event handler in the XAML, as F# doesn't talk to WPF in any nice way. So I need to add my event handler(s) manually in code.
Is there an easy way of doing this?
Currently, I have some F# which looks like:
let myView : ItemsControl = this?MyView
do myView.ItemsSource <- viewModel.BoardData
Here, the BoardData is a list of lists.
I was wondering if it's possible to loop through the controls in the ItemsControl, to add my event handlers? I'm having a bit of trouble doing this though. For example, in the following:
let container = myView.ItemContainerGenerator.ContainerFromItem(myView.Items.[0])
...sets container to null. In fact, all the methods I've tried from myView.ItemContainerGenerator returns null.
So, how should I go about attaching my event handler, so that I can respond to the buttons being clicked?
I have not done any Windows 7 Phone development, but I have done plenty of XAML + Silverlight development with C# and now I'm getting into doing some F# development. The approach I would take is by not using event handler's at all. Since you are using a button, make a class that derives from ICommand and add that type as a public property on your ViewModel so you could bind it to the Command property of the button. The benefits of using the ICommand interface over event handlers is that you could also have a condition on when the button is enabled to do the action.
Also, take notice that when you are doing binding expressions within (i.e. ItemTemplate) items in an ItemsControl control, the scope of what properties you can bind to are reduced to the properties of the current item. So all of the properties of the ViewModel are out of scope, unless you specify it fully i.e. <Button Command={Binding Source=ViewModel, Path=Property1.Property2.etc} />. Let me know if this helped.