1

I am making a winodws 8 phone application and trying to have a context menu on it from the Windows Phone tool kit.

I been following this tutorial but instead of list box I am using a long list selector that is built into WP8

<DataTemplate x:Key="GroceryListItemTemplate">
    <StackPanel Grid.Column="1" Grid.Row="1">
        <TextBlock x:Name="tbName" TextWrapping="Wrap" Text="{Binding Name}" FontSize="32"/>
        <TextBlock x:Name="tbProductInfo" TextWrapping="Wrap" Text="{Binding ProductInfoLabel}" HorizontalAlignment="Left"/>
    </StackPanel>
    <toolkit:ContextMenuService.ContextMenu>
        <toolkit:ContextMenu>
            <toolkit:MenuItem Header="Edit" 
                    Command="{Binding GroceryItemsVm.EditGroceryItemCmd, Source={StaticResource Locator}}"
                    CommandParameter="{Binding}"/>
            <toolkit:MenuItem Header="Delete" Command="{Binding GroceryItemsVm.DeleteGroceryItemCmd, Source={StaticResource Locator}}"
                    CommandParameter="{Binding}"/>
        </toolkit:ContextMenu>
    </toolkit:ContextMenuService.ContextMenu>
</DataTemplate>

above is a stripped down of what my code looks like

here is my list selector

<phone:LongListSelector IsGroupingEnabled="True" ItemsSource="{Binding GroceryItems}"    HideEmptyGroups="True" LayoutMode="List" Grid.Row="1">     
            <phone:LongListSelector.ItemTemplate>
                <StaticResource ResourceKey="GroceryListItemTemplate"/>
            </phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>

Here is my mvvm code I have

public class GroceryItemsVm : ViewModelBase
{

    public GroceryItemsVm()
    {


        if (IsInDesignMode)
        {


        }
        else
        {

            EditGroceryItemCmd = new RelayCommand<GroceryItem>(this.Edit);
            DeleteGroceryItemCmd = new RelayCommand<GroceryItem>(this.Delete);

            GroceryItems = // method that gets all items back as grouped.



        }
    }

      private List<Group<GroceryItem>> groceryItems = null;

            /// <summary>
            /// Sets and gets the GroceryItems property.
            /// Changes to that property's value raise the PropertyChanged event. 
            /// </summary>
            public List<Group<GroceryItem>> GroceryItems
            {
                get
                {
                    return groceryItems;
                }

                set
                {
                    if (groceryItems == value)
                    {
                        return;
                    }

                    RaisePropertyChanging(() => GroceryItems);
                    groceryItems = value;
                    RaisePropertyChanged(() => GroceryItems);
                }
            }




    private async void Delete(GroceryItem obj)
    {
       // trigged on context delete
    }

    private void Edit(GroceryItem obj)
    {
        // triggered on context edit
    }



    public RelayCommand<GroceryItem> EditGroceryItemCmd
    {
        get;
        private set;
    }

    public RelayCommand<GroceryItem> DeleteGroceryItemCmd
    {
        get;
        private set;
    }

}


    public class GroceryItem : ObservableObject
        {
            /// <summary>
            /// The <see cref="Name" /> property's name.
            /// </summary>
            public const string NamePropertyName = "Name";

            private string name = "";

            /// <summary>
            /// Sets and gets the Name property.
            /// Changes to that property's value raise the PropertyChanged event. 
            /// </summary>
            public string Name
            {
                get
                {
                    return name;
                }

                set
                {
                    if (name == value)
                    {
                        return;
                    }

                    RaisePropertyChanging(() => Name);
                    name = value;
                    RaisePropertyChanged(() => Name);
                }
            }
        }

Now when I run it, It works for the first time, whatever item I choose to edit it, will get the right object for it. However the next object will always be the same. It never changes it choice after the selection is done.

Edit

Here is an example.

https://onedrive.live.com/redir?resid=FAE864D71B4770C6!19080&authkey=!ACUC2xXmZLVD7fE&ithint=file%2c.zip

  1. Run it
  2. Trigger Context Menu to show over "1"
  3. Hit Edit - note dialog message (will say 1)
  4. Hit "Go back button"
  5. Trigger Context Menu to show over "3"
  6. Hit Edit - note dialog message (will say 3)

The only thing I can think of is override the back button for the pages I am going to and just do a Navigate to the page. It is kinda stupid but that's all I can think off.

  public partial class MvvmView1 : PhoneApplicationPage
    {
        // Constructor
        public MvvmView1()
        {
            InitializeComponent();

            // Sample code to localize the ApplicationBar
            //BuildLocalizedApplicationBar();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            NavigationService.GoBack();
        }


        protected override void OnBackKeyPress(CancelEventArgs e)
        {
            NavigationService.Navigate(new Uri("/MainPage.xaml", UriKind.Relative));
        }


    }
chobo2
  • 83,322
  • 195
  • 530
  • 832
  • GroceryItemsVm.EditGroceryItemCmd? Why isnt the command in root of the GroceryItemViewModel? If you do that, you do not need to use a long path, just {Binding Edit}. – Mikael Dúi Bolinder Mar 19 '14 at 16:14
  • How would it know which Grocery Item it would need to send to the view model if it was at the root? Each Grocery Item in the long list selector essentially needs it's own context menu so it knows which row is being edited. – chobo2 Mar 19 '14 at 16:19
  • 1
    Your ContextMenu is binding to the property `GroceryItemsVm`. Where does this property reside? Remember that Binding for commands is no different than regular binding. If `EditGroceryItemCmd` lives in the same class as `Name` and `ProductInfoLabel` then you need to remove the `GroceryItemsVm` prefix for your command binding – Shawn Kendrot Mar 19 '14 at 16:31
  • GroceryItemsVm is my View Model, It has a The EditGroceryItemCmd in it, Name and ProductInfoLabel belong to a Model class and come directly from that. – chobo2 Mar 19 '14 at 16:50
  • 1
    As I mentioned, remove that prefix from your command bindings. The DataContext of the ContextMenu (and MenuItem) is the same as the rest of your DataTemplate (as instance of GroceryItemsVm). So you will bind your command directly to `EditGroceryItemCmd` and `DeleteGroceryItemCmd`. that also means to remove the Source from your binding. – Shawn Kendrot Mar 19 '14 at 17:44

2 Answers2

1

Based on the comments, you have a GroceryItemsVm class that looks something like the following.

public class GroceryItemVm : INotifyPropertyChanged
{
    public string Name { get; set; }
    public string ProductInfoLabel{ get; set; }

    public ICommand EditGroceryItemCmd { get; private set; }
    public ICommand DeleteGroceryItemCmd { get; private set; }
}

And so the GroceryItems property that your LLS is binding to would be

public IEnumerable<GroceryItemVm> GroceryItems { get; set;}

If this is the case, then the DataContext of the items within your DataTemplate is an instance of GroceryItemsVm. All of your bindings within the DataTemplate should bind directly to that instance

<DataTemplate x:Key="GroceryListItemTemplate">
    <StackPanel Grid.Column="1" Grid.Row="1">
        <TextBlock x:Name="tbName" TextWrapping="Wrap" Text="{Binding Name}" FontSize="32"/>
        <TextBlock x:Name="tbProductInfo" TextWrapping="Wrap" Text="{Binding ProductInfoLabel}" HorizontalAlignment="Left"/>
    </StackPanel>
    <toolkit:ContextMenuService.ContextMenu>
        <toolkit:ContextMenu>
            <toolkit:MenuItem Header="Edit" Command="{Binding EditGroceryItemCmd}"/>
            <toolkit:MenuItem Header="Delete" Command="{Binding DeleteGroceryItemCmd}"/>
        </toolkit:ContextMenu>
    </toolkit:ContextMenuService.ContextMenu>
</DataTemplate>
Shawn Kendrot
  • 12,425
  • 1
  • 25
  • 41
  • I will post the class that I have when I get home but what you have is not what I have. I have only On GroceryItemVm Class(View Model). the IEnmberable is a ObservableCollection of GroceryItem which has the property of Name and ProductInfo. There is only 1 instance of EditGroceryItemCmd and DeleteGroceryItemCmd that is in the GroceryItemVm. – chobo2 Mar 19 '14 at 19:51
  • Got it. One option is to not put the DataTemplate in a resource, but directly under the LLS, and bind the command as my [other answer](http://stackoverflow.com/a/14168934/1054961). If not that, where is "Locator" defined? Is it a Static Resource that the template has access to? Does that object have a GroceryItemsVm that is the **same** instance as the one the LLS is binding to? – Shawn Kendrot Mar 19 '14 at 21:13
  • The Locator is a static resource using the Locator pattern, It is how the MVVM light toolkit is setup. It will generate one and only one GroceryItemsVm and use it through the application. – chobo2 Mar 19 '14 at 21:19
  • hmm, I tried to use the data context but like in your answer but I suffer from the same problem. – chobo2 Mar 20 '14 at 02:29
  • I think I know what is wrong but don't know why. In my Edit method, I navigate to a new page and when I go back then it goes bad. If I don't navigate everything is fine. – chobo2 Mar 20 '14 at 03:04
  • It seems like it is just phone hardware back button that is causing grief, if I navigate back to the page all is fine. – chobo2 Mar 20 '14 at 03:17
  • What are you doing to navigate back? Why would navigation mess up your DataContext? Sometime a great tool to use is a value converter for debugging bindings – Shawn Kendrot Mar 20 '14 at 18:48
1

This is common problem with ContextMenu. I have been trying for some time to think of a solution searching all around for something. You said after you click once it nevers gets it right.

Try the following:

Add the unloaded handler to you contextmenu as follows:

<DataTemplate x:Key="GroceryListItemTemplate">
    <StackPanel Grid.Column="1" Grid.Row="1">
        <TextBlock x:Name="tbName" TextWrapping="Wrap" Text="{Binding Name}" FontSize="32"/>
        <TextBlock x:Name="tbProductInfo" TextWrapping="Wrap" Text="{Binding ProductInfoLabel}" HorizontalAlignment="Left"/>
    </StackPanel>
    <toolkit:ContextMenuService.ContextMenu>
        <toolkit:ContextMenu ***Unloaded="ContextMenu_Unloaded"***>
            <toolkit:MenuItem Header="Edit" 
                    Command="{Binding GroceryItemsVm.EditGroceryItemCmd, Source={StaticResource Locator}}"
                    CommandParameter="{Binding}"/>
            <toolkit:MenuItem Header="Delete" Command="{Binding GroceryItemsVm.DeleteGroceryItemCmd, Source={StaticResource Locator}}"
                    CommandParameter="{Binding}"/>
        </toolkit:ContextMenu>
    </toolkit:ContextMenuService.ContextMenu>
</DataTemplate>

Remove the * I added them to emphasize the changes. And then the code behind for that handler would be:

private void ContextMenu_Unloaded(object sender, RoutedEventArgs e)
{
    var conmen = (sender as ContextMenu);
    if (conmen != null)
        conmen.ClearValue(DataContextProperty);
}

Let me know if this works.

George Nikolaides
  • 1,386
  • 12
  • 21
  • Initial testing looks good! I have to test it more though before I can say it is the solution. Do you know why this happen though? – chobo2 Mar 20 '14 at 06:14
  • I found this somewhere... not sure why they fields have to be invalidated in this way but it is something that has to do with binding. – George Nikolaides Mar 20 '14 at 09:12