2010-06-02 10 views
13

Tengo un escenario típico de MVVM: Tengo un ListBox enlazado a una lista de StepsViewModels. Defino un DataTemplate para que los StepViewModels se representen como StepViews. StepView UserControl tiene un conjunto de etiquetas y cuadros de texto.Establecer ListBoxItem.IsSelected cuando el niño TextBox está enfocado

Lo que quiero hacer es seleccionar el ListBoxItem que está envolviendo el StepView cuando se enfoca un cuadro de texto. He tratado de crear un estilo para mis TextBoxs con el siguiente trigger:

<Trigger Property="IsFocused" Value="true"> 
    <Setter TargetName="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}" Property="IsSelected" Value="True"/> 
</Trigger> 

Pero me sale un error que me dice que TextBoxs no tienen una propiedad IsSelected. Ahora que eso, pero el objetivo es un ListBoxItem. ¿Cómo puedo hacer que funcione?

+0

¿Puede dar el código XAML que describe la estructura completa (cuadro de texto, cuadro de lista) – Amsakanna

+0

I?' Acabo de publicar la solución que funcionó para mí: http://stackoverflow.com/questions/15366806/wpf-setting-isselected-for-listbox-when-textbox-has-focus-without-losing-selec/37942357#37942357 –

Respuesta

27

Hay una propiedad de solo lectura IsKeyboardFocusWithin que se establecerá en verdadero si hay un elemento secundario enfocado. Puede utilizar esto para establecer ListBoxItem.IsSelected en un disparador:

<ListBox ItemsSource="{Binding SomeCollection}" HorizontalAlignment="Left"> 
    <ListBox.ItemContainerStyle> 
     <Style TargetType="{x:Type ListBoxItem}"> 
      <Style.Triggers> 
       <Trigger Property="IsKeyboardFocusWithin" Value="True"> 
        <Setter Property="IsSelected" Value="True" /> 
       </Trigger> 
      </Style.Triggers> 
     </Style> 
    </ListBox.ItemContainerStyle> 
    <ListBox.ItemTemplate> 
     <DataTemplate> 
      <TextBox Width="100" Margin="5" Text="{Binding Name}"/> 
     </DataTemplate> 
    </ListBox.ItemTemplate> 
</ListBox> 
+0

¡Muchas gracias! eso era exactamente lo que estaba buscando. – jpsstavares

+12

Hay un gran "problema" con este enfoque: cuando la aplicación pierde el foco, IsSelected se configurará como falso. Es decir, hago clic en el cuadro de texto dentro de un elemento de la lista, pero cambio a otra aplicación (por ejemplo, para responder una pregunta de StackOverflow en mi navegador) y luego vuelvo a la aplicación ... la propiedad IsSelected se establecerá en verdadero, falso, verdadero, en lugar de permanecer fiel todo el tiempo. Esto puede plantear un problema muy grande si está alejando comportamientos de la propiedad SelectedItem de ListBox. – Jordan0Day

+0

@ Jordan0Day Sí, esto me llamó la atención también. Si algo más obtiene el foco (incluso algún otro control dentro de la aplicación WPF), el ListBoxItem se deselecciona. Esta respuesta lo resuelve: http://stackoverflow.com/a/15383435/466011 – epalm

2

Una forma de lograrlo es mediante la implementación de un comportamiento personalizado utilizando una propiedad adjunta. Básicamente, la propiedad adjunta se aplicaría al ListBoxItem usando un estilo, y se conectaría a su evento GotFocus. Incluso se dispara si algún descendiente del control obtiene el foco, por lo que es adecuado para esta tarea. En el controlador de eventos, IsSelected se establece en true.

escribí un pequeño ejemplo para usted:

la clase del comportamiento:

public class MyBehavior 
{ 
    public static bool GetSelectOnDescendantFocus(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(SelectOnDescendantFocusProperty); 
    } 

    public static void SetSelectOnDescendantFocus(
     DependencyObject obj, bool value) 
    { 
     obj.SetValue(SelectOnDescendantFocusProperty, value); 
    } 

    public static readonly DependencyProperty SelectOnDescendantFocusProperty = 
     DependencyProperty.RegisterAttached(
      "SelectOnDescendantFocus", 
      typeof(bool), 
      typeof(MyBehavior), 
      new UIPropertyMetadata(false, OnSelectOnDescendantFocusChanged)); 

    static void OnSelectOnDescendantFocusChanged(
     DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     ListBoxItem lbi = d as ListBoxItem; 
     if (lbi == null) return; 
     bool ov = (bool)e.OldValue; 
     bool nv = (bool)e.NewValue; 
     if (ov == nv) return; 
     if (nv) 
     { 
      lbi.GotFocus += lbi_GotFocus; 
     } 
     else 
     { 
      lbi.GotFocus -= lbi_GotFocus; 
     } 
    } 

    static void lbi_GotFocus(object sender, RoutedEventArgs e) 
    { 
     ListBoxItem lbi = sender as ListBoxItem; 
     lbi.IsSelected = true; 
    } 
} 

La Ventana XAML:

<Window x:Class="q2960098.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:sys="clr-namespace:System;assembly=mscorlib" 
     Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:q2960098"> 
    <Window.Resources> 
     <DataTemplate x:Key="UserControlItemTemplate"> 
      <Border BorderBrush="Black" BorderThickness="5" Margin="10"> 
       <my:UserControl1/> 
      </Border> 
     </DataTemplate> 
     <XmlDataProvider x:Key="data"> 
      <x:XData> 
       <test xmlns=""> 
        <item a1="1" a2="2" a3="3" a4="4">a</item> 
        <item a1="a" a2="b" a3="c" a4="d">b</item> 
        <item a1="A" a2="B" a3="C" a4="D">c</item> 
       </test> 
      </x:XData> 
     </XmlDataProvider> 
     <Style x:Key="MyBehaviorStyle" TargetType="ListBoxItem"> 
      <Setter Property="my:MyBehavior.SelectOnDescendantFocus" Value="True"/> 
     </Style> 
    </Window.Resources> 
    <Grid> 
     <ListBox ItemTemplate="{StaticResource UserControlItemTemplate}" 
       ItemsSource="{Binding Source={StaticResource data}, XPath=//item}" 
       HorizontalContentAlignment="Stretch" 
       ItemContainerStyle="{StaticResource MyBehaviorStyle}"> 

     </ListBox> 
    </Grid> 
</Window> 

El XAML Control de Usuario:

<UserControl x:Class="q2960098.UserControl1" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"> 
    <UniformGrid> 
     <TextBox Margin="10" Text="{Binding [email protected]}"/> 
     <TextBox Margin="10" Text="{Binding [email protected]}"/> 
     <TextBox Margin="10" Text="{Binding [email protected]}"/> 
     <TextBox Margin="10" Text="{Binding [email protected]}"/> 
    </UniformGrid> 
</UserControl> 
+0

Gracias por su respuesta, pero la respuesta de Bowen hace el trabajo con mucho menos código. ¡Muchas gracias por la ayuda! – jpsstavares

+0

De hecho, yo no estaba al tanto de esa propiedad, hay tantos :) +1 a su respuesta también –

1

Si crea un control de usuario y luego usarlo como DataTemplate Parece que funciona más limpio. Entonces no tiene que usar los Triggers de Estilo sucios que no funcionan el 100% del tiempo.

5

Como Jordan0Day señaló correctamente, puede haber grandes problemas con la solución IsKeyboardFocusWithin. En mi caso, un botón en una barra de herramientas que se refiere al ListBox tampoco funcionaba. El mismo problema con el enfoque. Al hacer clic en el botón, ListBoxItem pierde el enfoque y el botón actualiza su método CanExecute, lo que dio como resultado la desactivación del botón solo un momento antes de que se ejecute el comando de clic de botón.

Para mí una solución mucho mejor era utilizar un ItemContainerStyle EventSetter como se describe en este post: ListboxItem selection when the controls inside are used

XAML:

<Style x:Key="MyItemContainer.Style" TargetType="{x:Type ListBoxItem}"> 
    <Setter Property="Background" Value="LightGray"/> 
    <EventSetter Event="GotKeyboardFocus" Handler="OnListBoxItemContainerFocused" /> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type ListBoxItem}"> 
       <Border x:Name="backgroundBorder" Background="White"> 
        <ContentPresenter Content="{TemplateBinding Content}"/> 
       </Border> 
      <ControlTemplate.Triggers> 
       <Trigger Property="IsSelected" Value="True"> 
        <Setter TargetName="backgroundBorder" Property="Background" Value="#FFD7E6FC"/> 
       </Trigger> 
      </ControlTemplate.Triggers> 
     </ControlTemplate> 
    </Setter.Value> 
</Setter> 
</Style> 

manejador de sucesos en el código detrás de la vista:

private void OnListBoxItemContainerFocused(object sender, RoutedEventArgs e) 
{ 
    (sender as ListBoxItem).IsSelected = true; 
} 
+0

Esta es la forma correcta de hacerlo. Debería ver también la publicación social.MSDN vinculada desde Dr.WPF. – Indy9000

1

Editar: Alguien más ya tenía la misma respuesta en una pregunta diferente: https://stackoverflow.com/a/7555852/2484737

Continuando Maexs' respuesta, utilizando un EventTrigger en lugar de un EventSetter elimina la necesidad de código subyacente:

<Style.Triggers> 
    <EventTrigger RoutedEvent="GotKeyboardFocus"> 
     <BeginStoryboard> 
      <Storyboard > 
       <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="IsSelected" > 
        <DiscreteBooleanKeyFrame Value="True" KeyTime="0:0:0"/> 
       </BooleanAnimationUsingKeyFrames> 
      </Storyboard> 
     </BeginStoryboard> 
    </EventTrigger> 
</Style.Triggers> 
Cuestiones relacionadas