简体   繁体   中英

Set focus to an item in ItemsControl WPF

I am showing a list of images horizontally inside a scrolviewer and i am using the below line of code for doing it

<ScrollViewer Name="lviewThumbnails" Height="230"   >
<ItemsControl ItemsSource="{Binding ThumbsCollection}"  MouseWheel="ItemsControl_MouseWheel" > 
    <ItemsControl.ItemTemplate >
        <DataTemplate>
            <DockPanel Height="230">
                <Button Name="pageThumbnail" DockPanel.Dock="Top" Tag="{Binding}" VerticalAlignment="Top"  Margin="5,2">
                    <Grid>
                        <Image MaxWidth="140" Height="200" Source="{Binding ThubnailPath,IsAsync=True}" Stretch="None"></Image>                             
                    </Grid>
                </Button>
                <Label  HorizontalAlignment="Center"  FontSize="14" FontWeight="Bold" Content="text" Foreground="White" Padding="5"></Label>
            </DockPanel>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel  Orientation="Horizontal"  />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

I want to set the focus to a specific image item in this list when i opens the view For example if there are 150 images in the list and i want to set the focus to image 75 for instance while opening the list

What i am doing is on button click i am setting this code

  lviewThumbnails.Visibility = Visibility.Visible; 

to make the images visible , but very first item is the default selected one every time. I am using a trigger to detect the selected image as well like this

  <DataTrigger Binding="{Binding IsSelected}" Value="True">                                            
                                        <Setter TargetName="pageThumbnail" Property="BorderBrush" Value="Yellow"/>
                                        <Setter TargetName="pageThumbnail" Property="BorderThickness" Value="3"/>
 </DataTrigger>

But the problem is if image 75 is the selected one i can see yellow border around image 75 , but focus is still on image 1 when i click the button.I have to previous , next buttons of scroll Viewer to reach to the image 75. I am using my scroll viewer with specific style like column 1 a button column 2 contents and column 3 again button ( Hope it doesn't affect default behavior) and i am preventing the mouse scroll on scroll viewer everywhere

 <ControlTemplate TargetType="{x:Type ScrollViewer}" x:Key="ButtonOnlyScrollViewer">
        <ControlTemplate.Resources>
        </ControlTemplate.Resources>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="20" />
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="20"/>
            </Grid.ColumnDefinitions>
            <Button Style="{StaticResource ResourceKey=ViewPreviousButton}" Grid.Column="0"   Command="ScrollBar.PageUpCommand" MouseWheel="ItemsControl_MouseWheel" 
                      Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"   HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></Button>

            <ScrollContentPresenter
            CanContentScroll="{TemplateBinding CanContentScroll}"
            Grid.Column="1" 
            Content="{TemplateBinding Content}"  
            Width="{TemplateBinding Width}"
            Height="{TemplateBinding Height}" 
            Margin="{TemplateBinding Margin}"/>
            <Button Style="{StaticResource ResourceKey=ViewNextButton}" Grid.Column="2"   Command="ScrollBar.PageDownCommand"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MouseWheel="ItemsControl_MouseWheel"
                     ></Button>

        </Grid>
    </ControlTemplate>

You can use a trigger to do this and having a separate template for selected item or whatever the way that suits you.

Something like this, assuming you have image ids as a property;

<DataTrigger Binding="{Binding ThumbsCollection.ImageId}" Value="75">
   <Setter Property="ContentTemplate" Value="{StaticResource SelectedTemplate}" />
</DataTrigger>

you can use attached property FocusManager.FocusedElement in this case

<DataTrigger Binding="{Binding IsSelected}" Value="True">                                            
    <Setter TargetName="pageThumbnail" Property="BorderBrush" Value="Yellow"/>
    <Setter TargetName="pageThumbnail" Property="BorderThickness" Value="3"/>
    <Setter TargetName="pageThumbnail" Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</DataTrigger>

or

<DataTrigger Binding="{Binding IsSelected}" Value="True">                                            
    <Setter TargetName="pageThumbnail" Property="BorderBrush" Value="Yellow"/>
    <Setter TargetName="pageThumbnail" Property="BorderThickness" Value="3"/>
    <Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=pageThumbnail}"/>
</DataTrigger>

if above does not focus correctly then try focusing the container first then focus the pageThumbnail

<DataTrigger Binding="{Binding IsSelected}" Value="True">                                            
    <Setter TargetName="pageThumbnail" Property="BorderBrush" Value="Yellow"/>
    <Setter TargetName="pageThumbnail" Property="BorderThickness" Value="3"/>
    <Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"/>
    <Setter TargetName="pageThumbnail" Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</DataTrigger>

Update

the issue was not only the focus but the position of the element too as it stays out side of the view

try this xaml

<ItemsControl ItemsSource="{Binding ThumbsCollection}"  MouseWheel="ItemsControl_MouseWheel" Name="singleThumbs">
    <ItemsControl.Resources>
        <Style TargetType="ContentPresenter" xmlns:l="clr-namespace:WPFPerfomanceTest">
            <Setter Property="l:ScrollProvider.ScrollIntoView"
                    Value="{Binding IsSelected}" />
        </Style>
    </ItemsControl.Resources>
    <ItemsControl.ItemTemplate >
        <DataTemplate>
            <DockPanel Height="230">
                <Button Name="pageThumbnail"  DockPanel.Dock="Top" Tag="{Binding}" VerticalAlignment="Top"  Margin="5,2" Width="140">
                    <Grid>
                        <Image Height="200" Source="{Binding ThubnailPath,IsAsync=True}" Stretch="None"></Image>                                        
                    </Grid>
                </Button>
                <Label   HorizontalAlignment="Center"  FontSize="14" FontWeight="Bold" Content="{Binding PageNo}" Foreground="White" Padding="5"></Label>
            </DockPanel>
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding IsSelected}" Value="True" >
                    <Setter TargetName="pageThumbnail" Property="BorderBrush" Value="Yellow"/>
                    <Setter TargetName="pageThumbnail" Property="BorderThickness" Value="3"/>
                    <Setter TargetName="pageThumbnail" Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"/>
                </DataTrigger>

            </DataTemplate.Triggers>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel  Orientation="Horizontal"  />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

note that I have added style for ContentPresenter and bind ScrollProvider.ScrollIntoView to IsSelected property

also removed max width from the image and specified Width="140" to the button. max width cause the button size to shrink until image is loaded which effectively voids the bring to view behavior

and to make it work properly

we need some attached behavior, add this class to your project

class ScrollProvider : DependencyObject
{
    public static bool GetScrollIntoView(DependencyObject obj)
    {
        return (bool)obj.GetValue(ScrollIntoViewProperty);
    }

    public static void SetScrollIntoView(DependencyObject obj, bool value)
    {
        obj.SetValue(ScrollIntoViewProperty, value);
    }

    // Using a DependencyProperty as the backing store for ScrollIntoView.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ScrollIntoViewProperty =
        DependencyProperty.RegisterAttached("ScrollIntoView", typeof(bool), typeof(ScrollProvider), new PropertyMetadata(false, OnScrollIntoViewChanged));

    private static void OnScrollIntoViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement element = d as FrameworkElement;
        if ((bool)e.NewValue)
        {
            element.BringIntoView();
            element.Focus();
        }
    }
}

if you want to set focus or show some effects ,you can binding IsFocus and use trigger

private void Button_Click(object sender, RoutedEventArgs e)
        {
            lviewThumbnails.Visibility = Visibility.Visible;

            int index = 75;
            ThumbsCollection[index].IsFocus = true;

            Button button = (Button)FindFirstVisualChild(lviewThumbnails, "pageThumbnail");

            lviewThumbnails.ScrollToHorizontalOffset((button.ActualWidth + 6.5) * index); // set it with pageThumbnail's location or margin
        }

        public object FindFirstVisualChild(DependencyObject obj, string childName)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child.GetValue(NameProperty).ToString() == childName)
                {
                    return child;
                }
                else
                {
                    object childOfChild = FindFirstVisualChild(child, childName);
                    if (childOfChild != null)
                    {
                        return childOfChild;
                    }
                }
            }
            return null;
        }

I think you want to show the 75th(the middle item) item in the screen ,so you can use ScrollToHorizontalOffset . Get the button's actualwith at first and take it's margin/padding in calculate and then you will get the offset.

All these answers left out that pesky little bit of key information about ItemsControls. They use virtualization, so only the items that are actually in the view (and a few of the surrounding ones) actually "exist". What the OP is trying to do is impossible using the above given answers because the 75th image doesn't actually exist yet, so you can't set focus to it.

You need to:

1) subscribe to the ItemsControl ItemContainerGenerator.StatusChanged event 2) set your scroll position so the 75th item is scrolled into view 3) in the StatusChanged event handler, wait for the status = ContainerGenerated for the 75th item, then unsubscribe and set focus

Yes, this is overly complicated and yes, the code will execute "instantly", but you have to wait until after the container was generated for the 75th item to be able to set focus to it. That's just how virtualization works.

You are trying to set focus to the 75th item when only items 1 - 10 have been created.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM