2010-06-02 7 views
13

Tôi có một kịch bản MVVM điển hình: Tôi có một ListBox được liên kết với một Danh sách các StepsViewModels. Tôi định nghĩa một DataTemplate để StepViewModels được hiển thị dưới dạng StepViews. StepView UserControl có một bộ nhãn và TextBox.Đặt ListBoxItem.IsSelected khi TextBox con được tập trung

Điều tôi muốn làm là chọn ListBoxItem đang gói StepView khi một hộp văn bản được tập trung. Tôi đã cố gắng tạo kiểu cho TextBox của mình bằng trình kích hoạt sau:

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

Nhưng tôi gặp lỗi khi nói rằng TextBox không có thuộc tính được chọn. Tôi bây giờ mà mục tiêu là một ListBoxItem. Tôi làm cách nào để hoạt động?

+0

Bạn có thể cho mã XAML mô tả toàn bộ cấu trúc (textbox, listbox) – Amsakanna

+0

tôi?' vừa đăng giải pháp đã hoạt động cho tôi: http://stackoverflow.com/questions/15366806/wpf-setting-isselected-for-listbox-when-textbox-has-focus-without-losing-selec/37942357#37942357 –

Trả lời

27

Có thuộc tính chỉ đọc IsKeyboardFocusWithin sẽ được đặt thành true nếu có bất kỳ tập trung con nào. Bạn có thể sử dụng để thiết lập ListBoxItem.IsSelected trong một Trigger:

<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

Cảm ơn rất nhiều! đó là chính xác những gì tôi đang tìm kiếm. – jpsstavares

+12

Có một "dấu hiệu" thực sự lớn với cách tiếp cận này - khi bản thân ứng dụng của bạn mất tiêu điểm, IsSelected sẽ được đặt thành false. Tức là, tôi nhấp vào hộp văn bản bên trong một mục hộp danh sách nhưng chuyển sang ứng dụng khác (nói để trả lời câu hỏi StackOverflow trong trình duyệt của tôi), sau đó chuyển về ứng dụng của bạn ... thuộc tính IsSelected sẽ được đặt thành true, false, true, trái ngược với việc chỉ ở lại đúng toàn bộ thời gian. Điều này có thể đặt ra một vấn đề rất lớn nếu bạn đang lái xe hành vi ra khỏi tài sản SelectedItem của ListBox. – Jordan0Day

+0

@ Jordan0Day Yep, điều này cũng đã khiến tôi rất khó chịu. Nếu bất cứ điều gì khác được tập trung (ngay cả một số điều khiển khác trong ứng dụng WPF), ListBoxItem được bỏ chọn. Câu trả lời này giải quyết nó: http://stackoverflow.com/a/15383435/466011 – epalm

2

Một cách để đạt được điều đó là thực hiện hành vi tùy chỉnh bằng cách sử dụng thuộc tính được đính kèm. Về cơ bản, thuộc tính đính kèm sẽ được áp dụng cho ListBoxItem bằng cách sử dụng một kiểu và sẽ kết nối với sự kiện GotFocus của chúng. Điều đó thậm chí cháy nếu bất kỳ hậu duệ của điều khiển được tập trung, vì vậy nó phù hợp cho nhiệm vụ này. Trong trình xử lý sự kiện, IsSelected được đặt thành true.

Tôi đã viết lên một ví dụ nhỏ cho bạn:

Các hành vi Class:

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; 
    } 
} 

Window 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> 

Các User Control XAML:

<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

Cảm ơn cho câu trả lời của bạn nhưng câu trả lời của Bowen thực hiện công việc với mã ít hơn nhiều. Cảm ơn rất nhiều vì đã giúp đỡ! – jpsstavares

+0

Thật vậy, tôi đã không nhận thức được rằng tài sản, có rất nhiều :) 1 để trả lời của mình cũng –

1

Nếu bạn tạo một User Control và sau đó sử dụng nó như là DataTemplate Có vẻ như để làm việc sạch hơn. Sau đó, bạn không phải sử dụng Trình kích hoạt kiểu bẩn mà không hoạt động 100% thời gian.

5

Như Jordan0Được chỉ ra chính xác, có thể thực sự là vấn đề lớn khi sử dụng giải pháp IsKeyboardFocusWithin. Trong trường hợp của tôi, một Nút trong Thanh công cụ liên quan đến ListBox cũng không hoạt động nữa. Cùng một vấn đề với tiêu điểm. Khi nhấn vào nút ListBoxItem sẽ mất Focus và Button đã cập nhật phương thức CanExecute của nó, điều này dẫn đến việc vô hiệu hóa nút này chỉ một lúc trước khi lệnh click button được thực thi.

Đối với tôi một giải pháp tốt hơn là sử dụng một ItemContainerStyle EventSetter như đã mô tả trong bài viết này: 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> 

EventHandler trong mã phía sau của quan điểm:

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

Đây là cách chính xác để làm điều đó. Nên xem bài đăng social.MSDN được liên kết từ Dr.WPF. – Indy9000

1

Chỉnh sửa: Một người khác đã có cùng câu trả lời cho một câu hỏi khác: https://stackoverflow.com/a/7555852/2484737

Tiếp tục trên Maexs' câu trả lời, sử dụng một EventTrigger thay vì một EventSetter loại bỏ sự cần thiết của code-behind:

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