【问题标题】:How to execute command from GridViewItem Tap event (XAML)如何从 GridViewItem Tap 事件 (XAML) 执行命令
【发布时间】:2013-11-25 06:04:34
【问题描述】:

我正在尝试在我的 Windows 8.1 商店应用程序 (XAML) 中遵循 MVVM 模式。

当在 UI 中单击/点击 GridViewItem 时,我想导航到新视图。我想在没有事件背后的代码的情况下执行此操作以提高可测试性(使用 MVVM Light)。

为了让我的 UI 绑定到视图模型命令,我一直在查看通过添加引用 -> Windows -> 扩展添加的 Microsoft Behaviors SDK (XAML)。

我的视图中的以下代码编译但当我点击网格视图项时爆炸。不幸的是,它提供的帮助很少,只是抛出一个未处理的 win32 异常 [3476]。

有人可以帮忙解释一下这个问题吗?

使用的命名空间是;

xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"


<GridView x:Name="itemGridView"
                      AutomationProperties.AutomationId="ItemGridView"
                      AutomationProperties.Name="Grouped Items"            
                      ItemsSource="{Binding Source={StaticResource GroupedSource}}"                         
                      IsSwipeEnabled="True"
                      IsTapEnabled="True">

                <GridView.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="0"
                              Height="230">
                            <StackPanel Orientation="Vertical"
                                        HorizontalAlignment="Stretch">
                                <Image Source="{Binding Image}"                                  
                                       Stretch="UniformToFill"
                                       HorizontalAlignment="Center"
                                       VerticalAlignment="Center"
                                       />
                                <StackPanel VerticalAlignment="Bottom"
                                            Height="45"
                                            Margin="0,-45,0,0">
                                    <StackPanel.Background>
                                        <SolidColorBrush Color="Black" 
                                                         Opacity="0.75" 
                                                         />
                                    </StackPanel.Background>
                                    <TextBlock FontSize="16"
                                               Margin="2"
                                               Text="{Binding Name}"
                                               TextWrapping="Wrap"
                                               VerticalAlignment="Bottom"
                                               />
                                </StackPanel>
                            </StackPanel>

                            <interactivity:Interaction.Behaviors>
                                <core:EventTriggerBehavior EventName="Tapped">
                                    <core:InvokeCommandAction Command="{Binding DataContext.SummaryCatagorySelectedCommand, ElementName=LayoutRoot}" />
                                </core:EventTriggerBehavior>                                                                        
                            </interactivity:Interaction.Behaviors>                                                                                             
                        </Grid>
                    </DataTemplate>
                </GridView.ItemTemplate>

编辑。根据要求,我添加了视图模型,特别包含我想从我的行为中触发的命令。

public class ViewModel : ViewModelBase
{
    public RelayCommand<string> SummaryCatagorySelectedCommand { get; set; }

    public ViewModel()
    {
        //
    }
}

【问题讨论】:

  • 那么什么是viewModel,你在哪里绑定了viewmodel with view?

标签: xaml winrt-xaml windows-8.1


【解决方案1】:

我猜,当您点击任何项目时,您会更改 SelectedItem。您可以绑定 (Mode=TwoWay) SelectedItem 并在属性的 Set() 中提出所需的操作。

或者您可以使用类似的东西并用作 GridView 的依赖属性。

public class GridViewItemClickCommand
{
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.RegisterAttached("Command", typeof(ICommand),
        typeof(GridViewItemClickCommand), new PropertyMetadata
(null, CommandPropertyChanged));


    public static void SetCommand(DependencyObject attached, ICommand value)
    {
        attached.SetValue(CommandProperty, value);
    }


    public static ICommand GetCommand(DependencyObject attached)
    {
        return (ICommand)attached.GetValue(CommandProperty);
    }


    private static void CommandPropertyChanged(DependencyObject d,
                                    DependencyPropertyChangedEventArgs e)
    {
        // Attach click handler
        (d as GridView).ItemClick += gridView_ItemClick;
    }


    private static void gridView_ItemClick(object sender,
                                           ItemClickEventArgs e)
    {
        // Get GridView
        var gridView = (sender as GridView);


        // Get command
        ICommand command = GetCommand(gridView);


        // Execute command
        command.Execute(e.ClickedItem);
    }
}

如果你有任何问题,请询问:)

【讨论】:

  • 感谢您的回答。我想我可以一直走这条路。如果它更容易的话,也许我应该这样做。如果可能的话,我真的很想了解我对行为的使用有什么问题。
【解决方案2】:

我有同样的问题,但我用另一种方式解决了它。我没有将行为放在数据模板中,而是在 GridView 中进行:

 <GridView x:Uid="Flow" 
                x:Name="PlacesGridView"
                Grid.Column="1" Grid.Row="2"
                ItemTemplate="{StaticResource YourDataTemplate}" 
                ItemsSource="{Binding YourSource}" >
                <Interactivity:Interaction.Behaviors>
                    <Behaviors:GoToDestinationOnSelected/>
                </Interactivity:Interaction.Behaviors>
            </GridView>

这是 GoToDestinationOnSelected 的样子:

 public class GoToDestinationOnSelected : DependencyObject, IBehavior
{
   .....

    void GoToDestinationOnGridViewItemSelected_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        object obj = (sender as ListViewBase).SelectedItem;

        if (obj == null)
            return;

        if (obj is YourClass)
        {
                App.RootFrame.Navigate(typeof(CountryPlacesPage));
                return;


        ((AssociatedObject) as ListViewBase).SelectedIndex = -1;
    }

    public DependencyObject AssociatedObject
    {
        get;
        private set; 
    }

    public void Attach(DependencyObject associatedObject)
    {
        AssociatedObject = associatedObject;
                  (associatedObject as ListViewBase).SelectionChanged += GoToDestinationOnGridViewItemSelected_SelectionChanged;
    }

    public void Detach()
    {
        (AssociatedObject as ListViewBase).SelectionChanged -= GoToDestinationOnGridViewItemSelected_SelectionChanged;   
    }

    ~GoToDestinationOnSelected()
    {

    }

【讨论】:

    【解决方案3】:

    最简单的答案是告诉你在这种情况下你不应该使用命令。首先,命令的价值在于它既执行又将无法执行的情况反馈给交互式 XAML 控件。例如,当命令不可用时,按钮被禁用。

    但是由于您使用的是框架元素的轻击事件,因此您基本上只是将控件用作简单的方法,而不是命令。当然,您的视图模型可以同时具有命令和方法。并且行为可以调用命令和方法。

    为此,您的解决方案的最佳方案是更改您的方法,而不是在视图模型中调用命令。您的困难是 1. 命令超出了数据模板的范围,2. 命令参数在超出范围的线程上下文中传递。

    以下是我的建议,可以让您的生活更轻松,让您的应用更简单。

    不要附加到项目的点击事件。但改为附加到 gridview 的 itemclicked 事件。当然,这意味着您需要在gridview 上将IsItemClickEnabled 设置为true。然后不要调用命令,这是您不使用的开销,而是调用方法。

    这是该方法在您的视图模型中的样子:

    public async void ClickCommand(object sender, object parameter)
    {
        var arg = parameter as Windows.UI.Xaml.Controls.ItemClickEventArgs;
        var item = arg.ClickedItem as Models.Item;
        await new MessageDialog(item.Text).ShowAsync()
    }
    

    方法的名称无关紧要(我什至称它为命令来说明这一点),但签名很重要。行为框架正在寻找具有零参数或具有两个对象类型参数的方法。方便的是,两个参数版本将事件签名转发给它。在这种情况下,这意味着您可以使用包含单击项目的 ItemClickEventArgs。就这么简单。

    您的网格视图也被简化了。您可以简单地将 gridview 的自然范围引用到外部视图模型,而不是试图在数据上下文中强制范围。它看起来像这样:

    <GridView Margin="0,140,0,0" Padding="120,0,0,0" 
                SelectionMode="None" ItemsSource="{Binding Items}" 
                IsItemClickEnabled="True">
        <Interactivity:Interaction.Behaviors>
            <Core:EventTriggerBehavior EventName="ItemClick">
                <Core:CallMethodAction MethodName="ClickCommand" 
                    TargetObject="{Binding Mode=OneWay}" />
            </Core:EventTriggerBehavior>
        </Interactivity:Interaction.Behaviors>
    

    这是一个非常简单的解决方案,并且不会违反 MVVM 模式中的任何内容,因为它仍然会将逻辑推送到您分离且可测试的视图模型中。它使您可以有效地将行为用作事件到命令,但实际上使用更简单的事件到方法模式。由于该行为一开始并未将 CanExecute 值传递回控件,因此这实际上也简化了您的视图模型。

    如果事实证明您想要做的是重用已在其他地方利用的现有命令(这听起来像 1% 的边缘情况),您始终可以为此目的创建一个 shell 方法,在内部利用该命令你。

    作为警告,Windows 8.1 附带的 RelayCommand 没有正确实现 ICommand,因为它在调用 Execute 之前没有首先测试 CanExecute。此外,类型化 RelayCommand 中的 CanExecute 逻辑不会将 CommandParameter 传递给处理程序。这些都不重要,这取决于您首先使用的命令。不过这对我很重要。

    所以,这就是你的答案。更改为 GridView.ItemClicked 并从 ViewModel.Command 更改为 ViewModel.Method。如果您想重用数据模板,这将使您的生活更轻松、更轻松,并使您的 XAML 更具可移植性。

    祝你好运!

    【讨论】:

    • 抱歉回复延迟。我同意我认为这是有道理的。谢谢。
    • 当我试图找到一种将导航绑定到我的 ViewModel 而不是在 View 中的好方法时,这个答案极大地帮助了我。使用行为和绑定到方法正是我所需要的。谢谢!
    • @jerry-nixon-msft 这会导致尝试在页面之间导航时出现问题。在 page1 上启用缓存。使用命令导航到 page2。当您导航回第 1 页时,将两个方法调用附加到 ItemClick 事件而不是一个。下次点击一个项目时,这会导致 2 次导航调用。
    【解决方案4】:

    在 UWP(Window 10 及更新版本)移动应用程序中,应用以下代码 sn-p

    <Interactivity:Interaction.Behaviors>
                                        <Core:EventTriggerBehavior EventName="ItemClick">
                                            <Core:EventTriggerBehavior.Actions>
                                                <Core:`enter code here`InvokeCommandAction Command="{Binding itemclick}"/>
                                            </Core:EventTriggerBehavior.Actions>
                                        </Core:EventTriggerBehavior>
                                    </Interactivity:Interaction.Behaviors>
    

    【讨论】:

      猜你喜欢
      • 2014-10-08
      • 1970-01-01
      • 1970-01-01
      • 2011-08-03
      • 1970-01-01
      • 1970-01-01
      • 2010-10-16
      • 2011-03-11
      相关资源
      最近更新 更多