1

I'm extremely new to WPF and am trying to use Knockout JS as an analogy when creating bindings. I heard WPF's binding engine was even more powerful, but I'm struggling with something that is dog easy in Knockout.

Suppose I have a Person class which has a collection of Addresses.

public class Person
{
    public string PersonId {get;set;}
    public IList<Address> Addresses{get; set;}
    public string FormattedName{get;set;}
}

public class Address
{
    public string AddressId{get;set;}
    public string Address1{get;set;}
    public string City{get;set;}
    public string State{get;set;}
    public string Zip{get;set;}
}

Suppose on a page, I have a collection of people and for each person, I'd like to show all address and provide buttons to choose an address. So, my page view model looks like this:

public class AddressSelection
{
    public string PersonId{get;set;}
    public string AddressId{get;set;}
}

public class PersonAddressSelectionViewModel
{
    public IList<Person> People {get; set;}

    public Person SelectedPerson {get;set;}
    public Address SelectedAddress{get;set;}

    public void SelectAddress(string personId, string addressId)
    {
        this.SelectedPerson = this.People.FirstOrDefault(x => x.PersonId == personId);
        this.SelectedAddress = this.SelectedPerson?.Addresses.FirstOrDefault(x => x.AddressId == addressId);
    }

    public void SelectAddress(AddressSelection arg)
    {  SelectAddress(arg.PersonId, arg.AddressId); }
}

Now, I want to show a UI that shows a header for each passenger, then a button for each address. When the button is selected, the SelectAddress function should be triggered. However, in the XAML, I'm not sure how to assign the binding to use both the parent and current element properties; whether it can create an object from them or even just call a method and pass arguments fetch from both.

In knockout, you'd just bind the function and access the parent context like:

<!-- ko foreach: $data.people -->
<h2 data-bind="text: formattedName"></h2>
<ul data-bind="foreach: $data.addresses">
    <li>
        <button data-bind="click: function() { $parents[1].selectAddress($parent.personId, $data.addressId); }">Select Address</button>
    </li>
</ul>
<!-- /ko -->

I can't seem to figure out how to do the same thing in XAML.

<ItemsControl Grid.Row="2" ItemsSource="{x:Bind ViewModel.People, Mode=OneWay}"
    HorizontalAlignment="Center" Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Style="{StaticResource TextBlockSmallStyle}" VerticalAlignment="Center" Width="180" Text="{Binding FormattedName, Mode=OneWay}" />
                <ItemsControl ItemsSource="{Binding Addresses}" Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <VirtualizingStackPanel Orientation="Horizontal" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Button Width="180" Style="{StaticResource ButtonStyle}"
                                Content="{Binding Address1}"
                                Click="SelectAddressClicked" Tag="{Binding **what to put here**}" />
                            </StackPanel>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </StackPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

in code behind:

SelectAddressClicked(object sender, object args) {
    ((Button)sender).Tag as Address; // would prefer this to be something that has both the address id and person id
}
MPavlak
  • 2,133
  • 1
  • 23
  • 38

1 Answers1

0

you should do this with MVVM instead of code behind

Google RelayCommand class implementation

then in your view model add the following definitions

public RelayCommand SelectAddressCommand { get; private set; }

in constructor

SelectAddressCommand  = new RelayCommand( args => SelectAddress(args) );

in xaml

<Button Command={Binding SelectAddressCommand}
        CommandParameter={...}

But based on my understanding you can save yourself tons of trouble by using a ListBox instead of ItemsControl. It has SelectedItem property built in you can just bind it directly to your View Model

Steve
  • 11,696
  • 7
  • 43
  • 81
  • Even if i use relay instead of code behind, how to construct the arguments from the current context and the parent context? – MPavlak Mar 01 '17 at 15:37
  • @MPavlak look up RelativeResource Self/FindAncestor – Steve Mar 01 '17 at 15:42
  • even with relative resource, wouldn't that give me an either / or situation. I need something from both objects. If it makes a difference, this is a uwp app and I'm trying to use this question as a reference http://stackoverflow.com/questions/32861612/how-to-do-relativesource-mode-find-ancestor-or-equivalent-in-uwp – MPavlak Mar 01 '17 at 15:45
  • @MPavlak like I said in my last sentence. you should use MVVM design pattern and all the information you want to pass into the command should exist in the ViewModel already, which you can just call the command with empty parameter. But if you absolutely want to do it you can use MultiBinding to combine the two parameters with 2 relative resource bindings – Steve Mar 01 '17 at 15:51
  • that pattern is not MVVM. that is commanding. I am using MVVM, which there is no reason I cannot reach into a parent context for data. Loading up the button with all the parameters follows the commanding pattern which I personally do not care for since it makes the buttons know about parent models. – MPavlak Mar 01 '17 at 16:28
  • @MPavlak like I said, you should bind the selected item directly to the viewmodel. But if you absolutely want to do it the way you are doing right now multi binding and relative resource will get you there – Steve Mar 01 '17 at 16:41
  • I will check out the MultiBinding. we are binding the selected item directly, however, that item does not have enough information about it's parents to make a decision about what to do. At anyrate, if the multi binding works out, I'll give you the check mark :) – MPavlak Mar 01 '17 at 16:47
  • multibinding is not available in uwp :) Looks like I gotta go the commanding route. If I can get that working, I'll still give you the check. – MPavlak Mar 01 '17 at 17:19
  • Giving the check to this answer because it is simply too difficult to not follow Commanding with UWP given the limitations to the XAML. I would have preferred to not use commanding, but it is not worth the effort to avoid it. – MPavlak Mar 09 '17 at 18:21