1

I have an existing UWP app to manage passwords for websites and accounts but when I wrote it about 3 years ago I did not know MVVM very well and so all the event handlers are in the View's code behind and now I'm trying to resolve this and make it more MVVM adherent by moving this code to the ViewModel.

One of the existing features I have is a flyout menu on each ListView item so the user can edit/delete the entry (and do a couple of other functions) but because I am defining a DataType for the ListView ItemTemplate data template it will now not recognise the binding to the Click event handler in my Viewmodel.

As you'd expect I have my ViewModel defined both in the namespaces and in the page data context as follows:

    <Page ....
      xmlns:vm="using:PassPort.ViewModels"
      ...>

    <Page.DataContext>
        <vm:MainViewModel/>
    </Page.DataContext>

And here is my ListView and it's ItemTemplate and DataTemplate. In each MenuFlyoutItem in the FlyOut I'm trying to tell it to use the handler in my ViewModel but it cannot resolve 'vm' - it shows the squiggly line and says "the property 'vm' was not found in type 'Account'".

    </ListView Name="lvwAccounts"  
               ItemsSource="{x:Bind vm.AccountsView}" 
               .....>
       <ListView.ItemTemplate>
        <DataTemplate x:DataType="model:Account">
            <Grid MinHeight="36" HorizontalAlignment="Left" RightTapped="AccountsList_RightTapped">

                <FlyoutBase.AttachedFlyout>
                    <MenuFlyout Placement="Bottom">
                        <MenuFlyoutItem x:Name="OpenWebsiteButton" Text="Open Website" Click="{x:Bind vm.FlyoutOpenWebsiteButton_Click}"/>
                        <MenuFlyoutItem x:Name="EditButton" Text="Edit Account" Click="{x:Bind vm.FlyoutEditButton_Click}"/>
                        <MenuFlyoutItem x:Name="AddButton" Text="Add Account" Click="{x:Bind vm. FlyoutAddButton_Click}"/>
                        <MenuFlyoutItem x:Name="DeleteButton" Text="Delete Account" Click="{x:Bind vm.FlyoutDeleteButton_Click}"/>
                    </MenuFlyout>
                </FlyoutBase.AttachedFlyout>

                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="200"/>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="200"/>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

                <TextBlock Grid.Row="0" Grid.Column="0" Text="{x:Bind AccountName}"  Foreground="{StaticResource Light}"
                           VerticalAlignment="Center" HorizontalAlignment="Stretch"/>

                <TextBlock Grid.Row="0" Grid.Column="1" Text="{x:Bind Category}"  Foreground="{StaticResource Light}"
                           VerticalAlignment="Center" HorizontalAlignment="Stretch" TextWrapping="Wrap" />

                <TextBlock Grid.Row="0" Grid.Column="2" Text="{x:Bind UserID}" Foreground="{StaticResource Light}"
                           VerticalAlignment="Center" HorizontalAlignment="Left"/>

                <TextBlock Grid.Row="0" Grid.Column="3" Text="{x:Bind Password}" Foreground="{StaticResource Light}"
                           VerticalAlignment="Center" HorizontalAlignment="Left" />

                <TextBlock Grid.Row="0" Grid.Column="4" Text="{x:Bind PasswordHint}" Foreground="{StaticResource Light}"/>
            </Grid>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Everywhere else in the code 'vm' is resolved without issue so it seems that because of the DataType it then can't/won't resolve my ViewModel reference.

I've also tried using 'Click="{Binding vm.[event handler]"} but it makes no difference - so does anyone know how I can resolve this?

Clemens
  • 123,504
  • 12
  • 155
  • 268
nzmike
  • 578
  • 1
  • 7
  • 25
  • I dont think you should bind ViewModel to MenuFlyout. – lindexi Aug 29 '18 at 06:19
  • You can bind to other Element to solve it. – lindexi Aug 29 '18 at 06:20
  • 1
    `x:Bind` does not use the current DataContext as default binding source. When you write `x:Bind vm.AccountsView`, there should be a `vm` property in the view's code behind that holds a view model instance. Better don't use `x:Bind` at all and write `ItemsSource="{Binding AccountsView}"`. – Clemens Aug 29 '18 at 06:31

1 Answers1

0

In DataTemplate and x:Bind the Microsoft say that:

Inside a DataTemplate (whether used as an item template, a content template, or a header template), the value of Path is not interpreted in the context of the page, but in the context of the data object being templated. So that its bindings can be validated (and efficient code generated for them) at compile-time, a DataTemplate needs to declare the type of its data object using x:DataType.

To bind the ViewModel in the DataTemplate, you need use binding instead of x:bind like this code.

<ListView Name="lvwAccounts"  
          ItemsSource="{x:Bind vm.AccountsView}" >
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="model:Account">
                <FlyoutBase.AttachedFlyout>
                    <MenuFlyout Placement="Bottom">
                        <MenuFlyoutItem x:Name="OpenWebsiteButton" Text="Open Website" Command="{Binding ElementName=lvwAccounts,Path=DataContext.FlyoutOpenWebsite}"/>

                    </MenuFlyout>
                </FlyoutBase.AttachedFlyout>
        </DataTemplate>
    </ListView.ItemTemplate>      
</ListView>

But binding in UWP cant bind the method and the binding can only bind the Command. You should change your method to command.

I can't find the Command property in MenuFlyoutItem that we may use Behavior to bind UI event to command in ViewModel see:WPF Binding UI events to commands in ViewModel

See: https://stackoverflow.com/a/40774956/6116637

Why can't I use {x:Bind {RelativeSource Self}} in a data template?

lindexi
  • 4,182
  • 3
  • 19
  • 65
  • Thanks lindexi - I did a bit more reading after posting my answer and realised I could use "Binding ElementName=..." but also realised that I need to change the event handler in the VM to a command. – nzmike Aug 30 '18 at 07:18
  • For things like just displaying the FlyOutMenu surely that can be done in the code-behind? Obviously the commands for each menu option have to implemented in the VM as it involves business logic but just for rendering the menu control can't I keep that in the View code-behind? If not, why not? – nzmike Aug 30 '18 at 07:26