7

I am trying to do something that one would think should be very simple (at least it is in WPF). I have a page with a listbox and a datatemplate, now that datatemplate calls a user control with a button in it. Nothing fancy, but the buttons command is not part of the listboxsource, and I can’t find a simple way to tell the button where to look for the command. Here is the scenario

<Page x:Class="App1.MainPage"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:App1">
<Page.Resources>
    <DataTemplate x:Key="MyDataTemplate">
        <local:MyButton />
    </DataTemplate>
</Page.Resources>
<ListBox ItemTemplate="{StaticResource MyDataTemplate}" ItemsSource="{Binding Customers}" />
</Page>

<UserControl x:Class="App1.MyButton"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Button Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl, AncestorLevel=2}, Path=DataContext.DeleteCommand}" Content="Delete" />
</UserControl>

Please note this does not compile as in UWP there is no mode find ancestor? How should I do it, I keep looking at google but can’t find anything about it.

Thank you

adminSoftDK
  • 2,012
  • 1
  • 21
  • 41

2 Answers2

5

The answer is Dependency Property. I have had the same issue. First if you have no DataTemplate involved the solution is straight forward:

(this.Content as FrameworkElement).DataContext = this;

You set the DataContext of the UserControl in its constructor to its code behind.

If you are planning to us your Command inside a DataTemplate you will need a DependecyProperty too.

Example:

 <DataTemplate>
     <Button Command="{Binding DataContext.MyCommand, ElementName=ParentName}">
 </DataTemplate>

And to back it up you create a dependency property for that command:

 public ICommand MyCommand
    {
        get { return (ICommand)GetValue(MyCommandProperty); }
        set { SetValue(MyCommandProperty, value); }
    }

    // Using a DependencyProperty as the backing store for MyCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MyCommandProperty =
        DependencyProperty.Register("MyCommand", typeof(ICommand), typeof(ownerclass), new PropertyMetadata(0));

So now when you use your user control you will have a MyCommand property on it that you can bind to any command from your ViewModel as long as the templating parent matches the one that you provided and also the parameter is bound to the actual item that the control is part of.

<usercontrols:button MyCommand="{Binding MyCommandFromViewModel}" CommandParameter="{Binding}"/>

Simple example:

UserControl code behind

 public sealed partial class ListviewUserControl : UserControl
{
    public ListviewUserControl()
    {
        this.InitializeComponent();

        (this.Content as FrameworkElement).DataContext = this;
    }




    public ICommand ButtonCommand
    {
        get { return (ICommand)GetValue(ButtonCommandProperty); }
        set { SetValue(ButtonCommandProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ButtonCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ButtonCommandProperty =
        DependencyProperty.Register("ButtonCommand", typeof(ICommand), typeof(ListviewUserControl), new PropertyMetadata(null));




    public ObservableCollection<Item> ItemsSource
    {
        get { return (ObservableCollection<Item>)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ItemsSource.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(ObservableCollection<Item>), typeof(ListviewUserControl), new PropertyMetadata(new ObservableCollection<Item>()));



}

UserControl Xaml:

<Grid>
    <ListView ItemsSource="{Binding ItemSource}" x:Name="ListView">
        <ListView.ItemTemplate>
            <DataTemplate>
                <!--some item related content-->
                <AppBarButton Icon="Delete" Command="{Binding ButtonCommand, ElementName=ListView}" CommandParameter="{Binding}"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

Usage in Page.xaml:

<Controls:ListviewUserControl ItemsSource="{Binding ViewModelsItemsList}" ButtonCommand="{Binding ViewModelsCommand}"/>
Barptad
  • 174
  • 1
  • 8
  • I did not get the last part what the binding need to look like inside the usercontrols:button, button command={Binding MyCommand} does not do anything, the button would have to look at the templated parent or something to find the dependency property? – adminSoftDK Sep 30 '15 at 12:37
  • The dependency property is always in the usercontrols code behind. And its only there to allow binding between the object in the user control and outside of it. Some times I put the whole ListView into a user control and create a dependency property for the ItemSource too so in that way I can reuse the whole list in different views and I only have to bind the ItemSource and the different commands and their labels if they change – Barptad Sep 30 '15 at 12:52
  • I see, it makes sense now, I'll test it later :) Thank u very much :) – adminSoftDK Sep 30 '15 at 13:46
  • No worries, I'm glad that it helps... It's a bit confusing first, but once you get it... – Barptad Sep 30 '15 at 14:29
2

There is a concept called x:Bind in Windows 10 UWP. In x:Bind the code behind becomes the datacontext for the binding. So if you add a property in the user control's code behind, pointing to the view model, that can be used to bind the command.

public class MyButton
{
   public ViewModel ButtonViewModel 
   { 
      get
      { 
          return ButtonViewModelObject;
      }
   }
}    

In XAML -

<Button Command="{x:Bind ButtonViewModel.DeleteCommand}" Content="Delete" />

Refer - https://msdn.microsoft.com/en-us/library/windows/apps/mt204783.aspx

                                 OR

You can use ElementName with traditional binding to achieve the result.

<Button Command="{Binding DataContext.DeleteCommand, ElementName= UserControlName}" Content="Delete" />

Refer - Can't access datacontext of parent

Update: To access delete command from the page's datacontext, the following method can be used, assuming - the change of usercontrol's datacontext (from customer) to the page's datacontext doesn't affect anything else present inside the usercontrol.

<DataTemplate x:Key="MyDataTemplate">
        <local:MyButton DataContext="{Binding DataContext, ElementName = PageName}" />
 </DataTemplate>

<Button Command="{Binding DeleteCommand}" Content="Delete" />
Community
  • 1
  • 1
Bells
  • 1,465
  • 1
  • 19
  • 30
  • Thank you for the answer, but this is not a good solution for me, because I put that button into a separate user control so I can reuse it in many places. So the ViewModel will be different on a different screen. Relative source allowed to ignore which viewmodel it was, it would just a look at the parent’s datacontext. I've tried the ElementName, but it does not work in this case, it does not find name of a calling page. – adminSoftDK Sep 30 '15 at 09:54
  • @adminSoftDK: Is it like - you have the button in a usercontrol which is inside the datatemplate and the delete command is in the datacontext of the page with `ListBox`? – Bells Sep 30 '15 at 10:59
  • yes, deletecommand is in the datacontext in the page, but the current datacontext for the datatemplate is each customer. – adminSoftDK Sep 30 '15 at 11:01
  • I should probably do a better example, I also tried this. And this is nearly there, but I also pass an Id of the current item as command parameter. Which them belongs to the original datacontext, so by doing what you just said the command will be found, but now I have same situaction for the command parameter. – adminSoftDK Sep 30 '15 at 11:30