【问题标题】:How to make context menu work for windows phone?如何使上下文菜单适用于 windows phone?
【发布时间】:2013-01-04 23:47:08
【问题描述】:

我正在使用 Windows Phone 控制工具包中的 ContextMenu。想知道我如何知道列表中的哪个列表项被按下?似乎我可以知道选择了哪个上下文菜单,但我无法知道操作了哪个列表项。请帮忙。谢谢!

        <DataTemplate x:Key="ListItemTemplate">
            <StackPanel Grid.Column="1" VerticalAlignment="Top">
                <TextBlock Tag="{Binding Index}"  Text="{Binding SName}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}" />
              <toolkit:ContextMenuService.ContextMenu>
                <toolkit:ContextMenu>
                  <toolkit:MenuItem Header="Add to playlist" Click="Move_Click"/>
                </toolkit:ContextMenu>
              </toolkit:ContextMenuService.ContextMenu>
            </StackPanel>

        private void Move_Click(object sender, RoutedEventArgs e)
    {
        String name = (string)((MenuItem)sender).Header;
        // how to know which index of the item is targeted on
    }

【问题讨论】:

    标签: silverlight xaml contextmenu windows-phone-8 silverlight-toolkit


    【解决方案1】:

    我也推荐 MVVM,但它可以简化。无需为每个对象都拥有一个 ViewModel。关键是将命令绑定到 ItemsControl 的 DataContext(例如 ListBox)。

    假设您的 ItemTemplate 用于 ListBox,ListBox 的 ItemsSource 属性绑定到您的 ViewModel。您的 xaml 将如下所示:

    <ListBox x:Name="SongsListBox" ItemsSource="{Binding Songs}">
        <ListBox.ItemTemplate>
            <DataTemplate >
                <StackPanel >
                    <TextBlock Text="{Binding SName}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}" />
                    <toolkit:ContextMenuService.ContextMenu>
                       <toolkit:ContextMenu>
                           <toolkit:MenuItem Header="Add to playlist" Command="{Binding DataContext.AddToPlaylistCommand, ElementName=SongsListBox}" CommandParameter="{Binding}"/>
                       </toolkit:ContextMenu>
                    </toolkit:ContextMenuService.ContextMenu>
                </StackPanel>              
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    

    您的 ViewModel 将具有属性Songs,即您的模型对象的集合。它还有一个 ICommand AddToPlaylistCommand。正如我 said before 一样,我最喜欢的 ICommand 实现是 PNP 团队的 DelegateCommand。

    public class ViewModel : INotifyPropertyChanged
    {
        public ViewModel()
        {
            Songs = new ObservableCollection<Songs>();
            AddToPlaylistCommand = new DelegateCommand<Song>(AddToPlaylist);
        }
        public ICollection<Songs> Songs { get; set; }
        public ICommand AddToPlaylistCommand  { get; private set; }
    
        private void AddToPlaylist(Song song)
        {
            // I have full access to my model!
            // add the item to the playlist
        }
    
        // Other stuff for INotifyPropertyChanged
    }
    

    【讨论】:

    • 谢谢!这对我帮助很大!我可以根据需要复制和粘贴。
    • 这是吹毛求疵,但您应该将 Songs 的设置器设为私有。
    • 我不同意,在某些情况下您可能想从其他地方进行设置
    • 我使用的是 MVVM light,所以我的代码有点不同,但这对我不起作用stackoverflow.com/questions/22509401/…
    【解决方案2】:

    答案是:MVVM。不要在代码隐藏中使用事件注册,而是在 ViewModel 中调用命令。首先,不是绑定到 Data 对象,而是绑定到 ViewModel(或者如果您在列表中,则绑定到表示单个列表项的 ViewModel)。然后,不要使用 Click 事件,而是让您的 ViewModel 公开一个可以直接在数据绑定 VM 上调用的命令。

    这是来自我的United Nations News OSS WP7 应用程序的简化示例:(XAML, C#)

    <DataTemplate x:Key="ArticleItemDataTemplate">
        <StackPanel>
            <toolkit:ContextMenuService.ContextMenu>
                <toolkit:ContextMenu>
                    <toolkit:MenuItem Command="{Binding NavigateToArticle}" Header="read article"/>
                    <toolkit:MenuItem Command="{Binding ShareViaEmail}" Header="share via email"/>
                    <toolkit:MenuItem Command="{Binding ShareOnFacebook}" Header="share on facebook"/>
                    <toolkit:MenuItem Command="{Binding ShareOnTwitter}" Header="share on twitter"/>
                </toolkit:ContextMenu>
            </toolkit:ContextMenuService.ContextMenu>
            <TextBlockText="{Binding Title}">
        </StackPanel>
    </DataTemplate>
    
    public ICommand ShareOnTwitter
    {
        get
        {
            return new RelayCommand(() => 
                IoC.Get<ISocialShareService>().ShareOnTwitter(ShareableOnSocialNetwroks));
        }
    }
    
    public ICommand ShareOnFacebook
    {
        get
        {
            return new RelayCommand(() =>
                IoC.Get<ISocialShareService>().ShareOnFacebook(ShareableOnSocialNetwroks));
        }
    }
    
    public ICommand ShareViaEmail
    {
        get
        {
            return new RelayCommand(() =>
                IoC.Get<ISocialShareService>().ShareViaEmail(ShareableOnSocialNetwroks));
        }
    }
    

    这是我在Neurons WP7 OSS 项目中使用的相同想法的另一个简化示例:(XAMLC#

        <DataTemplate x:Key="YouTubeVideoItem">
            <Grid>
                <Button >
                    <toolkit:ContextMenuService.ContextMenu>
                        <toolkit:ContextMenu IsZoomEnabled="False">
                            <toolkit:MenuItem Command="{Binding NavigateToVideo}" Header="play video" />
                            <toolkit:MenuItem Command="{Binding ViewInBrowser}" Header="open in browser" />
                            <toolkit:MenuItem Command="{Binding SendInEmail}" Header="share via email" />
                            <toolkit:MenuItem Command="{Binding FacebookInBrowser}" Header="share on facebook" />
                            <toolkit:MenuItem Command="{Binding TweetInBrowser}" Header="share on twitter" />
                        </toolkit:ContextMenu>
                    </toolkit:ContextMenuService.ContextMenu>
                    <Custom:Interaction.Triggers>
                        <Custom:EventTrigger EventName="Click">
                            <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding NavigateToVideo}"/>
                        </Custom:EventTrigger>
                    </Custom:Interaction.Triggers>
                    <StackPanel Orientation="Horizontal">
                        <Image Height="90" Source="{Binding ImageUrl}" />
                        <TextBlock Width="271" Text="{Binding Title}" />
                    </StackPanel>
                </Button>
            </Grid>
        </DataTemplate>
    
        public ICommand ViewInBrowser
        {
            get
            {
                return new RelayCommand(() =>
                    TaskInvoker.OpenWebBrowser(this.ExternalLink.OriginalString)
                );
            }
        }
    
        public ICommand TweetInBrowser
        {
            get
            {
                return new RelayCommand(() =>
                    IoC.Get<IMessenger>().Send(new NavigateToMessage(PageSources.WebBrowser, TwitterUri)));
            }
        }
    
        public ICommand FacebookInBrowser
        {
            get
            {
                return new RelayCommand(() =>
                    IoC.Get<IMessenger>().Send(new NavigateToMessage(PageSources.WebBrowser, FacebookUri)));
            }
        }
    

    【讨论】:

    • 唯一的问题是Title 就像是绑定的几个项目之一的属性,而命令绑定通常在父视图模型上。使用您选择的模型,命令基本上会被重新定义几次。
    • 这不是问题,这是一项功能。它实际上是我使用的代码示例。而不是 DataBinding 到数据对象(例如 Cow),您应该将该对象包装在 Item ViewModle(例如 CowViewModel)中,并将您的 ItemsControl.ItemSource 数据绑定到 List 而不是 List。然后,CowViewModel 可以公开您想要的任何命令(例如 CowViewModel.SlaughterCowCommand)。如果您在 DataRow 中有一个带有 2 个按钮的 DataGrid,这也是相同的解决方案。看看我提供的示例。
    • 嗯,这是一种明显的代码味道,所以我个人不建议这样做。 (此外,您的 IoC 对象是一个服务定位器,与控制反转无关,但这是一个不同的讨论。)
    • 我不同意这是一种代码味道。当有一个项目列表并且每个项目可以执行 2 个或更多操作时,这是唯一合理的解决方案。赋予它显然不拥有的 PageViewModel 责任(并且可能与其他 PageViewModel 共享)是 IMO 的代码异味。考虑一个 DataGrid 示例,其中包含 List 和每个 DataRow 中的两个按钮。
    • 感谢各位宝贵的回答!阅读您的建议后,我已经开始工作了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多