【问题标题】:How to select ListBoxItem upon clicking on button in Template?单击模板中的按钮时如何选择 ListBoxItem?
【发布时间】:2010-09-15 19:09:16
【问题描述】:

我将以下数据模板应用于列表框:

<DataTemplate x:Key="MyTemplate" DataType="{x:Type DAL:Person}">
    <StackPanel Orientation="Horizontal">
        <Button Content="X" Command="{x:Static cmd:MyCommands.Remove}"/>
        <TextBlock Text="{Binding Person.FullName}" />
    </StackPanel>
</DataTemplate>

当我点击按钮时,命令被触发,但 ListBoxItem 没有被选中。如何强制它被选中,以便我可以在我的“执行”方法中获取选定的项目?

谢谢

【问题讨论】:

  • 我可以通过命令传递按钮,然后找到另一边的按钮,但这有点像黑客,我觉得......
  • 好吧,我删除了我的答案(即添加一个触发器,当键盘焦点在其中时选择 ListBoxItem),因为再想一想,在大多数情况下它可能不是处理这个问题的最佳方法。跨度>
  • 这里也一样。我最初的解决方案是将“Self”绑定到CommandParameter,然后在“执行”方法上得到LisBoxItem,如下所示: ListBoxItem selectedListBoxItem = ((ListBoxItem) MyListBox.ContainerFromElement((DependencyObject) e.Parameter));但我不认为这很干净......

标签: wpf command


【解决方案1】:

更好的方法是,将项目本身作为 CommandParameter 传递给 Command,因为您对选择项目并不感兴趣(因为无论如何它都会很快被删除)。

或者,您可以使用代码隐藏或触发器以迂回的方式进行,但我认为这不是重点。例如:

您可以处理列表框上的 ButtonBase.Click 事件,例如

<ListBox ButtonBase.Click="lb_Click"
...

然后在你的代码后面,这样做:

private void lb_Click(object sender, RoutedEventArgs e)
{
    object clicked = (e.OriginalSource as FrameworkElement).DataContext;
    var lbi = lb.ItemContainerGenerator.ContainerFromItem(clicked) as ListBoxItem;
    lbi.IsSelected = true;
}

这会得到单击的绑定项,因为按钮的数据上下文是从它的模板项继承的,然后是从 ListBox 的 ItemContainerGenerator 中实际自动生成的 ListBoxItem,并将 IsSelected 属性设置为 true。我认为这是最快和最简单的方法之一。也适用于模板中的多个 ButtonBase 派生对象。

当然,您也可以更好地将所有这些(或多或少完全相同)封装为可重复使用的Behavior

public class SelectItemOnButtonClick : Behavior<ListBox>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(handler), true);
    }
    protected override void OnDetaching()
    {
        this.AssociatedObject.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(handler));
        base.OnDetaching();
    }
    private void handler(object s, RoutedEventArgs e)
    {
        object clicked = (e.OriginalSource as FrameworkElement).DataContext;
        var lbi = AssociatedObject.ItemContainerGenerator.ContainerFromItem(clicked) as ListBoxItem;
        lbi.IsSelected = true;
    }
}

你可以这样使用它:

<ListBox xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" ...>
    <i:Interaction.Behaviors>
        <local:SelectItemOnButtonClick />
    </i:Interaction.Behaviors>
</ListBox>

当然,添加错误处理代码,例如至少为空检查 - 不希望这样简单的事情轰炸您的应用程序。

为了理解这个问题,按钮将所有作用于它的鼠标事件(MouseDown/Click)的 Handled 属性设置为 true,因此 ListBoxItem 不会考虑它们。您还可以将 MouseDown 事件附加到 ListBox 并向上移动可视化树,直到到达父 ListBoxItem 但这要棘手得多……嗯,如果您好奇,可以阅读 this article 以了解原因,基本上是您' 还会遇到 FrameworkContentElements(它也响应 MouseDown),因此代码会变得更加复杂,好处是在数据模板内单击的任何内容都会触发 ListBoxItem 被选中,无论它是否将事件标记为已处理。

嘿,我也尝试只使用样式和触发器来完成它,但它很快就变得丑陋了,我失去了兴趣(并且忘记了所有......错误的事情)。我认为基本上它可以解决,但我认为这不值得费心。也许我忽略了一些明显的东西,不知道。

【讨论】:

  • 嗨,亚历克斯,感谢您的回答。在此示例中,我需要删除项目,所以是的,我可以将项目传递给命令。但是我想学习最简单的选择项目的方法。
  • 好答案。在 SL 中,ButtonBase 来自不同的基础,它没有 ButtonBase.ClickEvent。
  • 一个非常好的解决方案,它基于 ButtonBase.Click 事件处理程序,通过这种方法,我能够在 ListBox 中使用 RadioButton 并使其表现为响应 Toggle 和 Untoggle 操作的 CheckBox。非常感谢!!!
【解决方案2】:

使底层对象暴露一个RemoveCommand 属性,并将按钮的Command 属性绑定到它。这简化了数据模板;它还极大地简化了应用程序逻辑可能指示无法删除特定项目的情况。

【讨论】:

  • 此时,底层对象需要知道集合不是吗?这似乎很糟糕。
【解决方案3】:

亚历克斯,感谢您的回答。您的行为解决方案很棒。第一个解决方案不是很好,因为只有在您单击特定按钮时才会起作用。这是另一种解决方案,可以单击任意控件表单 ListBoxItem 模板:

<ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem" 
           BasedOn="{StaticResource {x:Type ListBoxItem}}">
        <Style.Triggers>
            <Trigger Property="IsKeyboardFocusWithin" Value="True">
                <Setter Property="IsSelected" Value="True"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</ListBox.ItemContainerStyle>

这是 XAML 唯一的方法。我还设置了 BasedOn 属性,只是为了确保不会覆盖当前的 ListBoxItem 样式。

【讨论】:

  • 如果焦点设置到另一个元素,这将导致项目被取消选择。如果我希望它保持选中状态,则不能使用此方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-30
  • 2014-04-09
  • 1970-01-01
  • 1970-01-01
  • 2019-09-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多