WPF Scroll & focus changing problem

2019-05-10 17:49发布

问题:

I am having a problem with scrolling for my WPF application.

Here is the deal. My UI is the following:

The role of my application is to act as a central hub for many applications and launch them. An admin can launch a dump recorded by another user. Therefore, I have a ListView, showing the application list, which is scrollable if needed. I defined a GroupStyle in order to show expanders and emulate a Windows Explorer view. Everything works fine, I just have a problem: when scrolling with the mouse wheel, the component in clear blue ("Launch mode") seems to be catching the focus and stop scrolling. This means especially that if my mouse is anywhere out of this control, the scrolling is okay. But whenever the mouse enters this control, I can't scroll anymore. I tried to modify the property Focusable and set it to False everywhere I could but nothing changed. I'd guess that it is finally not a focus problem. Anybody has an idea on how to avoid the scrolling to be caught by the element?

Here is some (simplified, removed some useless properties so as to make it as clear as possible) XAML for the expander's content:

<StackPanel Orientation="Vertical"  VerticalAlignment="Top" >

            <ToggleButton>
                <!-- ToggleButton Content... -->
            </ToggleButton>

            <!-- This is the custom component in which you can see "Launch mode" -->
            <my:UcReleaseChooser >
                <!-- Properties there. I tried to set Focusable to False, no impact... -->
            </my:UcReleaseChooser>

        </StackPanel>

And the code for UcReleaseChooser:

<StackPanel HorizontalAlignment="Stretch"
                Focusable="False" ScrollViewer.CanContentScroll="False">

        <ListBox ItemsSource="{Binding ListChosenReleases}" BorderBrush="LightGray" Background="AliceBlue"
                 HorizontalAlignment="Stretch" Focusable="False" ScrollViewer.CanContentScroll="False">

            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical" 
                                Focusable="False" ScrollViewer.CanContentScroll="False"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>

            <ListBox.ItemTemplate>
                <DataTemplate>
                    <DockPanel LastChildFill="True" HorizontalAlignment="Stretch"
                               Focusable="False" ScrollViewer.CanContentScroll="False">
                        <TextBlock DockPanel.Dock="Top"
                            HorizontalAlignment="Left" Text="{Binding Key}" 
                                   FontStyle="Italic"/>
                        <ListBox DockPanel.Dock="Bottom"
                            HorizontalAlignment="Right" ItemsSource="{Binding Value}"
                                 BorderBrush="{x:Null}" Background="AliceBlue"
                                 Focusable="False" ScrollViewer.CanContentScroll="False">
                            <ListBox.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <UniformGrid Focusable="False"/>
                                </ItemsPanelTemplate>
                            </ListBox.ItemsPanel>

                            <ListBox.ItemContainerStyle>
                                <Style TargetType="{x:Type ListBoxItem}">
                                    <-- Blah blah about style -->
                                </Style>
                            </ListBox.ItemContainerStyle>


                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <RadioButton Content="{Binding Key}" Margin="3"
                                            IsChecked="{Binding Path=IsSelected, Mode=TwoWay, 
                                                        RelativeSource={RelativeSource FindAncestor, 
                                                            AncestorType={x:Type ListBoxItem}}}" 
                                                 Focusable="False" ScrollViewer.CanContentScroll="False"/>
                                </DataTemplate>
                            </ListBox.ItemTemplate>
                        </ListBox>
                    </DockPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>

        </ListBox>

    </StackPanel>

As you can see, the UcReleaseChooser contains a list of RadioButton lists. I tried to set Focusable & CanContentScroll to False everywhere it seemed appropriate, but the control keeps preventing the main UI to scroll...

I guess I should change another property... Any idea?

Thanks!

回答1:

The problem is the ListBox, or more specifically, the ScrollViewer within the ListBox's template. This is getting your scroll events and consuming them before the outer ScrollViewer in the ListView even sees them.

I would advise replacing the ListBox with an ItemsControl if possible. However, that implies there will be no SelectedItem property. If you need that, I would suggest setting ScrollViewer.HorizontalScrollBarVisibility (or VerticalScrollBarVisibility) to Disabled. Failing that, I can only suggest re-templating ListBox to not contain a ScrollViewer at all.



回答2:

I had an issue with a listbox stealing focus inside a scrollviewer (I have multiple listboxes inside the scrollviewer). So I created an attached property which denies the listbox the ability to scroll. So therefore the scrollviewer which is housing the listbox can scroll

Your control is a listbox, so this should work as is, but there is no reason the Extension should be limited to a Listbox; it just is to match my exact purpose.

 public static class ListboxExtensions
{
    public static DependencyProperty IgnoreScrollProperty = DependencyProperty.RegisterAttached("IgnoreScroll", typeof(bool), typeof(ListboxExtensions), new UIPropertyMetadata(false, IgnoreScrollChanged));

    public static bool GetIgnoreScroll(DependencyObject dependencyObject)
    {
        return (bool)dependencyObject.GetValue(IgnoreScrollProperty);
    }

    public static void SetIgnoreScroll(DependencyObject dependencyObject, bool value)
    {
        dependencyObject.SetValue(IgnoreScrollProperty, value);
    }

    private static void IgnoreScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var newValue = (bool)e.NewValue;
        var oldValue = (bool)e.OldValue;

        var frameworkElement = d as FrameworkElement;
        if (frameworkElement == null) return;

        if (!newValue || oldValue || frameworkElement.IsFocused) return;

        var lb = frameworkElement as ListBox;
        if (lb == null) return;

        lb.PreviewMouseWheel += LbOnPreviewMouseWheel;
    }

    private static void LbOnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        if (!(sender is ListBox) || e.Handled) return;

        e.Handled = true;
        var eventArg = new MouseWheelEventArgs(e.MouseDevice, e.Timestamp, e.Delta)
            {
                RoutedEvent = UIElement.MouseWheelEvent,
                Source = sender
            };

        var parent = ((Control)sender).Parent as UIElement;
        if (parent != null) parent.RaiseEvent(eventArg);
    }
}

And then in your XAML, you just put this on the listbox:

 <ListBox extensions:ListboxExtensions.IgnoreScroll="True">

Of course, remembering to include the namespace to your extensions in the top of the XAML:

xmlns:extensions="clr-namespace:UI.Extensions"