Forum Xamarin.Forms

Announcement:

The Xamarin Forums have officially moved to the new Microsoft Q&A experience. Microsoft Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

To create new threads and ask questions head over to Microsoft Q&A for .NET and get involved today.

x:Reference not working?

KeithRomeKeithRome USUniversity ✭✭

I'm trying to use a view-to-view binding using x:Reference, but am having no luck. Specifically, I am binding the command/parameter of a TextCell's context actions menu. I wish to bind the command itself to an ICommand property of the main view model, while keeping the command parameter bound to the current item of the ListView that contains the cells. So this means using x:Reference as the Source of the command binding. Yet this doesn't seem to work.

I tried digging through the Xamarin Forms code using dotPeek to see if I could figure out what was going wrong - and I don't even see a ReferenceExtension class in XF 1.3 (Stable)?? Was this removed from the platform, or am I just looking in the wrong place?

My code looks very similar to this, however the binding's Source is totally being ignored:

<ListView ItemsSource="{Binding EventsList}"
          x:Name="EventsList">
  <ListView.ItemTemplate>
    <DataTemplate>
      <TextCell Text="{Binding Name}">
        <TextCell.ContextActions>
          <MenuItem Text="Delete" 
                    IsDestructive="True"
                    Command="{Binding Path=BindingContext.DeleteEventCommand, Source={x:Reference Name=EventsList}}"
                    CommandParameter="{Binding}"/>
        </TextCell.ContextActions>
      </TextCell>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

The ListView's BindingContext is a viewmodel class that has an ObservableCollection of items named EventsList, as well as an ICommand named DeleteEventCommand.

«1

Posts

  • MikeEEEMikeEEE USMember ✭✭✭

    Thanks for posting this. It would be interesting/helpful/useful to know if x:Reference is planned for XF. Maybe file a bug report?

  • KeithRomeKeithRome USUniversity ✭✭

    Good point Michael. I've submitted it as a bug: https://bugzilla.xamarin.com/show_bug.cgi?id=27532

  • TiborEbnerTiborEbner HUMember

    @TheRealJasonSmith @KeithRome
    x:Reference works as expected in other scenarios, e.g. I use bindings in ListView headers like this (set on container element inside header):
    BindingContext="{Binding Source={x:Reference root}, Path=BindingContext}"
    where 'root' points to the ContentPage element.

    It seems to me that x:Reference only breaks when used inside DataTemplate. I try to use that for cell ContextAction command binding (like Keith) and experience the same behavior. Since there was no mention whatsoever in the reference documentation that DataTemplate had been intended to have this effect on x:Reference, I think it is safe to say that there is a major bug there.

    By the way, without command binding there is no way to properly implement enabled/disabled context menu actions other than hacking in the code-behind file, or setting DataTemplate's BindingContext explicitly from code-behind, both of which workarounds are rather inelegant.

  • NMackayNMackay GBInsider, University admin

    You can use Xamarin.Forms.Behaviours, it supports relativecontext binding

    https://www.nuget.org/packages/Xamarin.Forms.Behaviors/

    <ListView Grid.Row="1" ItemsSource="{Binding Customer}" VerticalOptions="Fill" x:Name="ListviewCustomer"> <ListView.ItemTemplate> <DataTemplate> <local:CustomImageCell Text="{Binding CustomerName}" Detail="{Binding AccountNo}"> <b:Interaction.Behaviors> <b:BehaviorCollection> <b:EventToCommand CommandNameContext="{b:RelativeContext CustDetails}" EventName="Tapped" CommandName="SelectCommand" CommandParameter="{Binding .}" /> </b:BehaviorCollection> </b:Interaction.Behaviors> </local:CustomImageCell> </DataTemplate> </ListView.ItemTemplate> </ListView>

  • GrishaVinevichGrishaVinevich ILMember ✭✭

    @NMackay
    I couldn't find a way to attach behaviors to ContextAction's MenuItem. But this approach just works.
    @KeithRome - Thanks for sharing this.

  • NestorLedonNestorLedon USMember ✭✭

    @KeithRome you're a hero. Thanks for sharing.

  • StephaneDelcroixStephaneDelcroix USInsider, Beta ✭✭✭✭

    @KeithRome nice to see your workaround working for you. just know that it would return wrong values if you try to reference object by name in the DataTemplate itself, because you're using the wrong NameScope.

  • KeithRomeKeithRome USUniversity ✭✭

    Thanks - I didn't think to try out that scenario. My use case was in a quick prototype for a client, so supporting data templates wasn't an issue. Do you have a way to solve that case easily?

  • KeirLavelleKeirLavelle USMember

    That utility class to set the binding context worked great for me thanks!

  • FranciscoGGFranciscoGG ESMember ✭✭

    @KeithRome, your approach works fine, but if I move the DataTemplate to <Application.Resources>....</Application.Resources> (in order to reuse the DataTemplate in more than one views) it does not work.

    Do you know why?

    This is the source code of the ListView:

      <ListView x:Name="ItemsListView" ItemsSource="{Binding OrderProposalLines}"
                ItemTemplate="{StaticResource AdjustableItemsListViewTemplate}"/>
    

    And this is the source code of the app resources:

    <?xml version="1.0" encoding="utf-8" ?>
    <Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:utils="clr-namespace:Elfo.VisionMobile.Core.Utils;assembly=Elfo.VisionMobile.Core"
             x:Class="Elfo.VisionMobile.Core.App">
    <Application.Resources>
    <ResourceDictionary>
    
      <DataTemplate x:Key="AdjustableItemsListViewTemplate">
        <ViewCell>
          <Grid>
            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="*"/>
              <ColumnDefinition Width="auto"/>
              <ColumnDefinition Width="auto"/>
            </Grid.ColumnDefinitions>
    
            <StackLayout Grid.Column="0">
              <Label Text="{Binding Description}"/>
              <Label Text="{Binding VendorItemNo}" />
            </StackLayout>
    
            <StackLayout Grid.Column="1" Orientation="Horizontal">
              <Button Command="{Binding Path=BindingContext.DecreaseQtyCommand, Source={utils:ElementSource ItemsListView}}" CommandParameter="{Binding}" Text="-" />
              <Button Command="{Binding Path=BindingContext.IncreaseQtyCommand, Source={utils:ElementSource ItemsListView}}" CommandParameter="{Binding}" Text="+"/>
            </StackLayout>
    
            <Image Grid.Column="2" Style="{StaticResource ListViewCellImage}"/>
          </Grid>
        </ViewCell>
      </DataTemplate>
    
    </ResourceDictionary>
    </Application.Resources>
    

    Please, can you help me?

  • MichaelRumplerMichaelRumpler ATMember ✭✭✭✭✭

    @Francisco.7655

    The problem is, that the rootProvider is not the root of the whole page, but only of the current file. If you use it in a DataTemplate, then the ElementSource won't find the element you defined the x:Name on.

    @StephaneDelcroix said above, that the NameScope is wrong, but neither Keith nor I know how to do it better.

  • StephaneDelcroixStephaneDelcroix USInsider, Beta ✭✭✭✭
    edited July 2015

    @MichaelRumpler I do :). This should be fixed in 1.4.3.

    see https://bugzilla.xamarin.com/show_bug.cgi?id=30684

  • FranciscoGGFranciscoGG ESMember ✭✭

    @StephaneDelcroix, I am working with 1.4.3 and I get this error.

    Did you get it to work?

  • StephaneDelcroixStephaneDelcroix USInsider, Beta ✭✭✭✭

    @Francisco.7655 could you please share your xaml, and the error you are seeing ?

  • FranciscoGGFranciscoGG ESMember ✭✭

    I am using just the same ElementSource posted markup posted in this topic:

    [ContentProperty("ElementName")]
    public class ElementSource : IMarkupExtension
    {
        public string ElementName { get; set; }
    
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            var rootProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
            if (rootProvider == null)
                return null;
            var root = rootProvider.RootObject as Element;
            if (root == null)
                return null;
            return root.FindByName<Element>(ElementName);
        }
    }
    
  • FranciscoGGFranciscoGG ESMember ✭✭
    edited July 2015

    @StephaneDelcroix, I am not getting any error, but the button I had referenced now is no working.

    As you can see in my xaml code, I have two commands binded using ElementSource:

    <Button Command="{Binding Path=BindingContext.DecreaseQtyCommand, Source={utils:ElementSource ItemsListView}}" CommandParameter="{Binding}" Text="-" />
    <Button Command="{Binding Path=BindingContext.IncreaseQtyCommand, Source={utils:ElementSource ItemsListView}}" CommandParameter="{Binding}" Text="+"/>
    

    When I was defining the DataTemplate in the related view, this was working fine. But it left to work once I moved the template to a ResourceDictionary (in order to share this template between two very similar screens).

  • StephaneDelcroixStephaneDelcroix USInsider, Beta ✭✭✭✭

    @Francisco.7655: x:Reference is fixed in 1.4.3. I have no idea what's the status of ElementSource

  • FranciscoGGFranciscoGG ESMember ✭✭

    Ah, ok, I wil try with x:Reference.

    How should I use it? Just like below?

    <Button Grid.Column="0" Command="{Binding Path=BindingContext.ChosenItemCommand, Source={x:Reference Name=ItemsListView}}"
                      CommandParameter="{Binding No}" Text="+" Style="{StaticResource InsideListButton}"/>
    
  • FranciscoGGFranciscoGG ESMember ✭✭

    @StephaneDelcroix, how are you using x:Reference?

    Thanks!!

  • FranciscoGGFranciscoGG ESMember ✭✭

    Please, somebody can help me? :)

  • KeithRomeKeithRome USUniversity ✭✭

    @Francisco.7655 : you shouldn't need to use ElementSource code above any longer. ReferenceExtension now appears in the current build of Xamarin Forms. ElementSource was just a temporary workaround written for a time when ReferenceExtension was missing.

  • FranciscoGGFranciscoGG ESMember ✭✭
    edited July 2015

    @KeithRome, ok, so I am trying to use x:Reference but it isn't working for me.

    I have the following template inside ResourceDictionary in my App.xaml (because this template will be shared in some views).

    I am using x:Reference in order to do the binding for + and - buttons commands.

    <ResourceDictionary>
      <DataTemplate x:Key="AdjustableItemsListViewTemplate">
        <ViewCell>
          <Grid>
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="auto"/>
              </Grid.ColumnDefinitions>
    
              <StackLayout Grid.Column="1" Orientation="Horizontal">
                <Button Command="{Binding Path=BindingContext.DecreaseQtyCommand, Source={x:Reference Name=ItemsListView}}"
                        CommandParameter="{Binding}" Text="-"/>
                <Label Text="{Binding Quantity}" VerticalOptions="Center" />
                <Button Command="{Binding Path=BindingContext.IncreaseQtyCommand, Source={x:Reference Name=ItemsListView}}"
                        CommandParameter="{Binding}" Text="+"/>
              </StackLayout>
    
              <Image Grid.Column="2" Style="{StaticResource ListViewCellImage}"/>
          </Grid>
        </ViewCell>
      </DataTemplate>
    </ResourceDictionary>
    

    And in my ItemsSearchView, I am using the SelectableItemsListViewTemplate template and the name of the list is ItemsListView as I declared in the template.

      <ListView x:Name="ItemsListView" Style="{StaticResource AppListView}" ItemsSource="{Binding Items}"
                ItemTemplate="{StaticResource SelectableItemsListViewTemplate}"/>
    

    However, the binding is not working, because when I tap on the buttons, nothing is happening.

    Am I using correctly x:Reference?

  • NMackayNMackay GBInsider, University admin

    @Francisco.7655

    Does the data template work if you just put it in your page? I've seen a few issues with people trying to use shared data templates in this way.

  • StephaneDelcroixStephaneDelcroix USInsider, Beta ✭✭✭✭

    @Franceso.7655: this is very unlikely to work. In a resource dictionary, you shouldn't x:Reference anything outside of the ResourceDictionary.

  • KeithRomeKeithRome USUniversity ✭✭

    Yeah, the "ItemsListView" you are trying to reference needs to be reachable. What you really want is something that works like {RelativeSource TemplatedParent} from WPF/Silverlight. But that doesn't exist in Xamarin Forms markup.

  • FranciscoGGFranciscoGG ESMember ✭✭

    Ok, so....no templates in ResourceDictionary :(

    -1 for Xamarin Forms....

  • KeithRomeKeithRome USUniversity ✭✭

    Templates are fine in ResourceDictionary. What you were specifically trying to do (access an external element) from within a template in a ResourceDictionary is what doesn't work.

  • AndrewHukinAndrewHukin AUMember

    I am having bit of issues setting the binding programatically.
    I have a page with a listview and the data template for the listview is in a different cs file. Does anyone know how to bind the button command to the page bindingcontext not the list view item bindingcontext.
    I tried the below code as shown in the OPs post programatically

    cameraButton.SetBinding( Button.CommandProperty, new Binding("CameraCommand", BindingMode.Default, null, null, null, new ReferenceExtension { Name = "listView"}) );

    and this

    cameraButton.SetBinding( Button.CommandProperty, new Binding("BindingContext.CameraCommand", BindingMode.Default, null, null, null, new ReferenceExtension { Name = "listView"}) );

    I get

    Binding: 'CameraCommand' property not found on 'Xamarin.Forms.Xaml.ReferenceExtension', target property: 'Xamarin.Forms.Button.Command'

  • KeithRomeKeithRome USUniversity ✭✭

    @LeonardoSuryana: you wouldn't instantiate a ReferenceExtension in that case. Markup Extensions are only needed to convert string input from the source XAML to .NET objects... sort of like a smart parser.

    Instead of passing new ReferenceExtension { Name = "listView"}), just pass in a reference to the listView object directly.

  • TomSoderlingTomSoderling USUniversity ✭✭✭

    I was able to get this to work using the fix for x:Reference inside a DataTemplate supplied in Xamarin.Forms v1.4.4
    For a code example, see my post here: http://forums.xamarin.com/discussion/comment/144204/#Comment_144204

  • AmitGobare23AmitGobare23 USMember ✭✭

    hi FranciscoCG.....are u getting solution to Button inside Listview.....can u plz share the solution if u have....thanks in advance

  • BrookGillespieBrookGillespie USMember

    If a view model is set to the BindingContext in XAML like this:

    <ContentPage.BindingContext>
        <viewModels:PCXWordSwapHomeViewModel /> // Your view model
    </ContentPage.BindingContext>
    

    I found I could implement IMarkupExtension.ProviderValue(IServiceProvider) like this:

        public object ProvideValue( IServiceProvider oServiceProvider )
        {
            IRootObjectProvider oRootProvider = oServiceProvider.GetService( typeof( IRootObjectProvider ) ) as IRootObjectProvider;
    
            Element oElement = oRootProvider?.RootObject as Element;
    
            return oElement?.BindingContext;
        }
    

    This approach returns my view model from within XAML:

        ...
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <ViewCell.View>
                        ...
                    </ViewCell.View>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
        ...
    

    This means that the value in ElementName is ignored.

  • BrookGillespieBrookGillespie USMember

    Here is the command in the XAML:
    ...
    <ListView.ItemTemplate>


    <ViewCell.View>
    ...



    ...
    </ViewCell.View>


    </ListView.ItemTemplate>
    ...
    The command parameter binding is on my object associated with my list, while the command is coming from my view mode class, which is found in the BindingContext of the page XAML as shown previously.

  • MarkFeldmanMarkFeldman USMember ✭✭
    edited November 2016

    For anyone else that stumbles upon this thread I've found that you can get around the problem of x:Relative not working in a DataTemplate by using a BindingProxy, as explained by Thomas Levesque at thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/. The Xamarin Forms version of the BindingProxy class is as follows:

    public class BindingProxy : BindableObject
    {
        public static readonly BindableProperty DataProperty =
            BindableProperty.Create("Data", typeof(object), typeof(BindingProxy), null, BindingMode.OneWay);
    
        public object Data
        {
            get { return (object)GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }
    }
    

    To use it you need to give your content page a name:

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 ...
                 x:Name="_this">
    

    Then in your resource dictionary (say) declare a proxy that binds to _this, and use StaticResource in your Command bindings to bind to the proxy:

    <behaviors:BindingProxy x:Key="Proxy" Data="{Binding Source={x:Reference _this}, Path=BindingContext}"/>
    
    <DataTemplate x:Key="MyReuseableDataTemplate">
        <Label Text="This is some clickable text" BackgroundColor="Transparent">
            <Label.Behaviors>
                <behaviors:CommandBehavior Command="{Binding Source={StaticResource Proxy}, Path=Data.ClickedCommand}" />
            </Label.Behaviors>
        </Label>
    </DataTemplate>
    

    This particular example uses a DataTemplate to add a behavior called CommandBehavior and binds its command handler to a property in the ContentPage's view model called ClickedCommand, the use of StaticResource is what allows you to side-step x:Relative altogether.

  • 14skywalker14skywalker NLMember ✭✭

    Fantastic, @MarkFeldman, you posted this yesterday. Today I can use it to change our bindings (getting rid of behaviour:RelativeContext) and enable XamlCompilation! Thanks!

  • wend0rlinwend0rlin DEMember ✭✭

    @MarkFeldman said:
    For anyone else that stumbles upon this thread I've found that you can get around the problem of x:Relative not working in a DataTemplate by using a BindingProxy, as explained by Thomas Levesque at thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/. The Xamarin Forms version of the BindingProxy class is as follows:

    public class BindingProxy : BindableObject
    {
    public static readonly BindableProperty DataProperty =
    BindableProperty.Create("Data", typeof(object), typeof(BindingProxy), null, BindingMode.OneWay);

      public object Data
      {
          get { return (object)GetValue(DataProperty); }
          set { SetValue(DataProperty, value); }
      }
    

    }

    To use it you need to give your content page a name:

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
               ...
               x:Name="_this">
    

    Then in your resource dictionary (say) declare a proxy that binds to _this, and use StaticResource in your Command bindings to bind to the proxy:





    This particular example uses a DataTemplate to add a behavior called CommandBehavior and binds its command handler to a property in the ContentPage's view model called ClickedCommand, the use of StaticResource is what allows you to side-step x:Relative altogether.

    hello @MarkFeldman and @14skywalker , why is this solution necessary when you can use 'RelativeSource' as shown by TomSonderling?

    I was able to get this to work using the fix for x:Reference inside a DataTemplate supplied in Xamarin.Forms v1.4.4
    For a code example, see my post here: http://forums.xamarin.com/discussion/comment/144204/#Comment_144204

    Am I right, when I assume that the data template has to reside in the content page you named '_this' and it does not work, when the data template resides in App.xaml ?

  • geskillgeskill DEMember

    I liked the idea of the markup extension and fixed it for XamlCompilation... However, keep in mind: This will only work als long as the Xamarin Team will keep the functions and properties used alive. (Xamarin.Forms.2.3.4.247 works)

        public class BindingExtension : IMarkupExtension
        {
            public object ProvideValue(IServiceProvider serviceProvider)
            {
                if (serviceProvider == null) throw new ArgumentNullException("serviceProvider");
    
                IRootObjectProvider rootObjectProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
                SimpleValueTargetProvider valueTargetProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as SimpleValueTargetProvider;
    
                // if XamlCompilation is active, IRootObjectProvider is not available, but SimpleValueTargetProvider is available
                // if XamlCompilation is inactive, IRootObjectProvider is available, but SimpleValueTargetProvider is not available
    
                if (rootObjectProvider == null && valueTargetProvider == null) throw new ArgumentException("serviceProvider does not provide an IRootObjectProvider or SimpleValueTargetProvider");
    
                if (rootObjectProvider == null)
                {
                    PropertyInfo propertyInfo = valueTargetProvider.GetType().GetTypeInfo().DeclaredProperties.Where(dp => dp.Name.Contains("ParentObjects")).FirstOrDefault();
                    if (propertyInfo == null) throw new ArgumentNullException("ParentObjects");
    
                    var parentObjects = propertyInfo.GetValue(valueTargetProvider) as IEnumerable<object>;
                    var parentObject = parentObjects.Where(pO => pO.GetType().GetTypeInfo().IsSubclassOf(typeof(Element))).FirstOrDefault();
                    if (parentObject == null) throw new ArgumentNullException("parentObject");
    
                    return parentObject as Element ?? new object();
                }
                else
                {
                    return rootObjectProvider.RootObject as Element ?? new object();
                }
            }
        }
    
  • I'm try get a text and another functions that i have in bindingcontext in my view and use inside of listview. Used this method that @KeithRome show us, but not work.
    I want that property MenuItem show text that exist in class inside BindingContext. Other bind works.
    I'm using Xamarin Forms.

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="APPChamadaMVVM.Views.CallsDataView"
                 Title="{Binding NewCallTitle}"
                 NavigationPage.HasBackButton="True"
                 NavigationPage.HasNavigationBar="False"
                 NavigationPage.BackButtonTitle="{Binding BackButtonTitle}"
                 xmlns:behaviors="clr-namespace:Behaviors"
                 xmlns:customobjects="clr-namespace:CustomObjects">
    
        <ContentPage.Content>
            <StackLayout>
                <StackLayout VerticalOptions="CenterAndExpand">
                    <ListView x:Name="ListStudents" ItemsSource="{Binding ListStudents}">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <ViewCell>
                                    <ViewCell.ContextActions>
                                        <MenuItem Clicked="CallClicked" Text="{Binding Path=BindingContext.DeleteText, Source={customobjects:ElementSource ListStudents}}" IsDestructive="True" CommandParameter="{Binding IDRegistration}"/>
                                    </ViewCell.ContextActions>
    
                                    <StackLayout BackgroundColor="AliceBlue" Orientation="Vertical">
                                        <StackLayout Orientation="Horizontal">
                                            <Label Text="{Binding IDRegistration}"/>
                                            <Label Text=" - "/>
                                            <Label Text="{Binding Name}"/>
                                        </StackLayout>
                                    </StackLayout>
                                </ViewCell>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </StackLayout>
            </StackLayout>
        </ContentPage.Content>
    </ContentPage>
    
  • Inside Path Custom Objects i have a class exacly he show.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Xamarin.Forms.Xaml;
    using Xamarin.Forms;
    
    namespace CustomObjects
    {
        [ContentProperty("ElementName")]
        public class ElementSource : IMarkupExtension
        {
            public string ElementName { get; set; }
    
            public object ProvideValue(IServiceProvider serviceProvider)
            {
                var rootProvider = serviceProvider.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
                if (rootProvider == null)
                    return null;
                var root = rootProvider.RootObject as Element;
                if (root == null)
                    return null;
                return root.FindByName<Element>(ElementName);
            }
        }
    }
    
Sign In or Register to comment.