Roman Pylypets
Roman Pylypets

Reputation: 171

Windows Phone 8.1 - Menu flyout item click command c#

I am writing an application for Windows Phone 8.1, and I wanted to use a flyout on listView item. Because I am doing my best to write nice app, I am trying to use MVVM pattern and resource dictionaries with templates insead of all xaml in one page. However, I can't bind my MenuFlyoutItem Command - it seems like it doesn't see the datacontext of the page, or it has some other dataContext. Here is some code:

1) My template in a separate resource dictionary:

<Grid Margin="0, 0, 0, 10">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="4*" />
        </Grid.ColumnDefinitions>

        <Grid.Resources>
            <converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
            <converters:EmptyDateConverter x:Key="EmptyDateConverter" />
        </Grid.Resources>

        <i:Interaction.Behaviors>
            <icore:EventTriggerBehavior EventName="Holding">
                <converters:OpenMenuFlyoutAction />
            </icore:EventTriggerBehavior>
        </i:Interaction.Behaviors>

        <FlyoutBase.AttachedFlyout>
            <MenuFlyout>
                <MenuFlyoutItem x:Uid="AddToCalendarMenuItem" Command="{Binding AddToCalendar}" />
            </MenuFlyout>
        </FlyoutBase.AttachedFlyout>

        <Image Grid.Column="0" Source="{Binding ThumbnailUri}"/>
        <StackPanel Grid.Column="1" Orientation="Vertical" Margin="10,0,0,0">
            <TextBlock Text="{Binding Title}" Style="{StaticResource ListItemTitle}"/>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
                <TextBlock x:Uid="DvdReleaseDate" />
                <TextBlock Text="{Binding DvdRelease, Converter={StaticResource EmptyDateConverter}}" />
            </StackPanel>
        </StackPanel>
    </Grid>

2) And here is the list view:

<ListView Grid.Row="1" x:Name="SearchListView"
                  ItemsSource="{Binding SearchList}"
                  ItemTemplate="{StaticResource SearchListTemplate}" SelectionChanged="NavigateToMovieDetails"  />

My ViewModel is a static kind of singleton in the app.xaml.cs

I've tried to create a new instance of the VM in xaml, but it didn't work - maybe I was doing smth wrong.

I would really appreciate Your help! Thanks in advance.

Best regards, Roman

Upvotes: 2

Views: 3971

Answers (2)

Abdul Mateen Mohammed
Abdul Mateen Mohammed

Reputation: 1894

@Chubosaurus Software following your approach I came up with this.

Here is a ListView bound to a list of todo items placed inside a DataTemplate which contains a TextBlock having a MenuFlyout to show edit, delete context menu kind of thing.

The key to bind the commands in the view model to the MenuFlyoutItem is to give the ListView a name and do element binding using the ElementName property in the Command to point to the ListView's name. To access the commands in our view model we've to go through the ListView's DataContext and bind it to a command on it because the DataContext of the MenuFlyoutItem is an item in the ItemsSource

The MainPage.xaml

<Page
    x:Class="UWA.MenuFlyout.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UWA.MenuFlyout"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="using:UWA.MenuFlyout.ViewModels"
    xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
    xmlns:core="using:Microsoft.Xaml.Interactions.Core"
    xmlns:common="using:UWA.MenuFlyout.Core"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid Margin="24,24">
        <ListView x:Name="Todos" ItemsSource="{Binding Todos}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Action}">
                        <FlyoutBase.AttachedFlyout>
                            <MenuFlyout>
                                <MenuFlyoutItem Text="edit" 
                                                Command="{Binding ElementName=Todos, Path=DataContext.EditTodo}"
                                                CommandParameter="{Binding}"/>
                                <MenuFlyoutItem Text="delete" 
                                                Command="{Binding ElementName=Todos, Path=DataContext.DeleteTodo}"
                                                CommandParameter="{Binding}"/>
                            </MenuFlyout>
                        </FlyoutBase.AttachedFlyout>

                        <interactivity:Interaction.Behaviors>
                            <core:EventTriggerBehavior EventName="Holding">
                                <common:OpenMenuFlyoutAction/>
                            </core:EventTriggerBehavior>
                            <core:EventTriggerBehavior EventName="RightTapped">
                                <common:OpenMenuFlyoutAction/>
                            </core:EventTriggerBehavior>
                        </interactivity:Interaction.Behaviors>
                    </TextBlock>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Page>

The MainPage.xaml.cs is where the DataContext of the MainPage is set.

namespace UWA.MenuFlyout
{
    using UWA.MenuFlyout.ViewModels;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Navigation;

    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            this.NavigationCacheMode = NavigationCacheMode.Required;
            this.DataContext = new MainViewModel();
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.
        /// This parameter is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // TODO: Prepare page for display here.

            // TODO: If your application contains multiple pages, ensure that you are
            // handling the hardware Back button by registering for the
            // Windows.Phone.UI.Input.HardwareButtons.BackPressed event.
            // If you are using the NavigationHelper provided by some templates,
            // this event is handled for you.
        }
    }
}

The MainViewModel.cs containing the Todos which is an ObservableCollection type and the EditTodo and DeleteTodo commands.

namespace UWA.MenuFlyout.ViewModels
{
    using System.Collections.ObjectModel;
    using System.Windows.Input;
    using UWA.MenuFlyout.Core;
    using UWA.MenuFlyout.Models;

    public class MainViewModel : BaseViewModel
    {
        private ICommand editTodo;

        private ICommand deleteTodo;

        public MainViewModel()
        {
            this.Todos = new ObservableCollection<TodoModel>
            {
                new TodoModel { Id = 1, Action = "Buy Milk", IsDone = true },
                new TodoModel { Id = 2, Action = "Buy Groceries", IsDone = false }
            };
        }

        public ObservableCollection<TodoModel> Todos { get; set; }

        public ICommand EditTodo
        {
            get
            {
                if (this.editTodo == null)
                {
                    this.editTodo = new RelayCommand(this.OnEditTodo);
                }

                return this.editTodo;
            }
        }

        public ICommand DeleteTodo
        {
            get
            {
                if (this.deleteTodo == null)
                {
                    this.deleteTodo = new RelayCommand(this.OnDeleteTodo);
                }

                return this.deleteTodo;
            }
        }

        public void OnEditTodo(object parameter)
        {
            // perform edit here
            var todo = parameter as TodoModel;
        }

        public void OnDeleteTodo(object parameter)
        {
            // perform delete here
            var todo = parameter as TodoModel;
        }
    }
}

The Model

namespace UWA.MenuFlyout.Models
{
    public class TodoModel
    {
        public int Id { get; set; }

        public string Action { get; set; }

        public bool IsDone { get; set; }
    }
}

The BaseViewModel which implements the INotifyPropertyChanged.

namespace UWA.MenuFlyout.Core
{
    using System.ComponentModel;
    using System.Runtime.CompilerServices;

    public class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

A simple ICommand implementation.

namespace UWA.MenuFlyout.Core
{
    using System;
    using System.Windows.Input;

    public class RelayCommand : ICommand
    {
        private Action<object> action;

        public RelayCommand(Action<object> action)
        {
            this.action = action;
        }

        public event EventHandler CanExecuteChanged;

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            this.action(parameter);
        }
    }
}

The OpenMenuFlyoutAction which implements DependencyObject and IAction to open the MenuFlyout by using the Execute method on the IAction interface.

namespace UWA.MenuFlyout.Core
{
    using Microsoft.Xaml.Interactivity;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls.Primitives;

    public class OpenMenuFlyoutAction : DependencyObject, IAction
    {
        public object Execute(object sender, object parameter)
        {
            var frameworkElement = sender as FrameworkElement;
            var flyoutBase = FlyoutBase.GetAttachedFlyout(frameworkElement);
            flyoutBase.ShowAt(frameworkElement);

            return null;
        }
    }
}

Upvotes: 0

Chubosaurus Software
Chubosaurus Software

Reputation: 8161

If you stick with that <ItemTemplate> you will have to have a Command per every Model in your ViewModel, which is not ideal.

To set a single Command and have a CommandParameter see this SO Tutorial I made it is too much code to type here:

Implement a ViewModel Single Command with CommandParamater

Upvotes: 1

Related Questions