Notes on Software

Posts Tagged ‘ScrollChanged’

HeaderedScrollViewer in WPF

Posted by K. M. on November 7, 2009

I needed a scrollviewer with a header area on the top that does not scroll vertically but does scroll horizontally. It was pretty simple to derive from ScrollViewer, define a TopHeader property (and a LeftHeader property for good measure), modify the control template to contain ScrollViewers for the headers with hidden scrollbars and synchronize the horizontal and vertical offsets in code. This worked well until I put a ComboBox in the header. Suddenly the scroll position started getting reset to zero when the ComboBox was opened. I looked on the internet for a solution but didn’t find one. A little debugging revealed that when the ComboBox is opened, the ScrollViewer in the popup raises a ScrollChanged event which routes up to the ScrollViewer containing the header. To fix this problem all I needed to do was to ignore events not originating directly from the header ScrollViewers.

Here is the code

    [TemplatePart(Name = "PART_TopHeaderScrollViewer", Type = typeof(ScrollViewer))]
    [
TemplatePart(Name = "PART_LeftHeaderScrollViewer", Type = typeof(ScrollViewer))]
    
public class HeaderedScrollViewer : ScrollViewer
    {
        
static HeaderedScrollViewer()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
typeof(HeaderedScrollViewer), new FrameworkPropertyMetadata(typeof(HeaderedScrollViewer)));
        }

        public object TopHeader
        {
            
get { return (object)GetValue(TopHeaderProperty); }
            
set { SetValue(TopHeaderProperty, value); }
        }
        
public static readonly DependencyProperty TopHeaderProperty =
            
DependencyProperty.Register("TopHeader", typeof(object), typeof(HeaderedScrollViewer), new UIPropertyMetadata(TopHeader_PropertyChanged));
        
private static void TopHeader_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            
HeaderedScrollViewer instance = (HeaderedScrollViewer)d;
            
if (e.OldValue != null)
                instance.RemoveLogicalChild(e.OldValue);
            
if (e.NewValue != null)
                instance.AddLogicalChild(e.NewValue);
        }

        public DataTemplate TopHeaderTemplate
        {
            
get { return (DataTemplate)GetValue(TopHeaderTemplateProperty); }
            
set { SetValue(TopHeaderTemplateProperty, value); }
        }
        
public static readonly DependencyProperty TopHeaderTemplateProperty =
            
DependencyProperty.Register("TopHeaderTemplate", typeof(DataTemplate), typeof(HeaderedScrollViewer), new UIPropertyMetadata());

        public object LeftHeader
        {
            
get { return (object)GetValue(LeftHeaderProperty); }
            
set { SetValue(LeftHeaderProperty, value); }
        }
        
public static readonly DependencyProperty LeftHeaderProperty =
            
DependencyProperty.Register("LeftHeader", typeof(object), typeof(HeaderedScrollViewer), new UIPropertyMetadata(LeftHeader_PropertyChanged));
        
private static void LeftHeader_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            
HeaderedScrollViewer instance = (HeaderedScrollViewer)d;
            
if (e.OldValue != null)
                instance.RemoveLogicalChild(e.OldValue);
            
if (e.NewValue != null)
                instance.AddLogicalChild(e.NewValue);
        }

        public DataTemplate LeftHeaderTemplate
        {
            
get { return (DataTemplate)GetValue(LeftHeaderTemplateProperty); }
            
set { SetValue(LeftHeaderTemplateProperty, value); }
        }
        
public static readonly DependencyProperty LeftHeaderTemplateProperty =
            
DependencyProperty.Register("LeftHeaderTemplate", typeof(DataTemplate), typeof(HeaderedScrollViewer), new UIPropertyMetadata());

        protected override System.Collections.IEnumerator LogicalChildren
        {
            
get
            {
                
if (TopHeader != null || LeftHeader != null)
                {

                    System.Collections.ArrayList children = new System.Collections.ArrayList();
                    System.Collections.
IEnumerator baseEnumerator = base.LogicalChildren;
                    
while (baseEnumerator.MoveNext())
                    {
                        children.Add(baseEnumerator.Current);
                    }
                    
if (TopHeader != null)
                        children.Add(TopHeader);
                    
if (LeftHeader != null)
                        children.Add(LeftHeader);
                    
return children.GetEnumerator();
                }
                
return base.LogicalChildren;
            }
        }

        private ScrollViewer topHeaderScrollViewer;

        private ScrollViewer leftHeaderScrollViewer;

        public override void OnApplyTemplate()
        {
            
base.OnApplyTemplate();
            topHeaderScrollViewer =
base.GetTemplateChild("PART_TopHeaderScrollViewer") as ScrollViewer;
            
if (topHeaderScrollViewer != null)
                topHeaderScrollViewer.ScrollChanged += topHeaderScrollViewer_ScrollChanged;
            leftHeaderScrollViewer =
base.GetTemplateChild("PART_LeftHeaderScrollViewer") as ScrollViewer;
            
if (leftHeaderScrollViewer != null)
                leftHeaderScrollViewer.ScrollChanged += leftHeaderScrollViewer_ScrollChanged;
        }

        private void leftHeaderScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            
if (e.OriginalSource == leftHeaderScrollViewer)
                ScrollToVerticalOffset(e.VerticalOffset);
        }

        private void topHeaderScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            
if (e.OriginalSource == topHeaderScrollViewer)
                ScrollToHorizontalOffset(e.HorizontalOffset);
        }

        protected override void OnScrollChanged(ScrollChangedEventArgs e)
        {
            
base.OnScrollChanged(e);
            
if (topHeaderScrollViewer != null)
                topHeaderScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
            
if (leftHeaderScrollViewer != null)
                leftHeaderScrollViewer.ScrollToVerticalOffset(e.VerticalOffset);
        }
    }

<Style TargetType="{x:Type local:HeaderedScrollViewer}">
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Focusable" Value="False"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:HeaderedScrollViewer}">
                <Grid Margin="{TemplateBinding Padding}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <ScrollViewer Name="PART_TopHeaderScrollViewer" Grid.Column="1"
                                     HorizontalScrollBarVisibility="Hidden"
                                     VerticalScrollBarVisibility="Hidden"
                                     Focusable="false">
                        <ContentPresenter Content="{TemplateBinding TopHeader}" MinWidth="{TemplateBinding ExtentWidth}"
                                         ContentTemplate="{TemplateBinding TopHeaderTemplate}"/>
                    </ScrollViewer>
                    <ScrollViewer Name="PART_LeftHeaderScrollViewer" Grid.Row="1"
                                     HorizontalScrollBarVisibility="Hidden"
                                     VerticalScrollBarVisibility="Hidden"
                                     Focusable="false">
                        <ContentPresenter Content="{TemplateBinding LeftHeader}" MinHeight="{TemplateBinding ExtentHeight}"
                                         ContentTemplate="{TemplateBinding LeftHeaderTemplate}"/>
                    </ScrollViewer>
                    <AdornerDecorator x:Name="test" Grid.Row="1" Grid.Column="1">
                        <ScrollContentPresenter Name="PART_ScrollContentPresenter"
                                               KeyboardNavigation.DirectionalNavigation="Local"
                                               Content="{TemplateBinding Content}"
                                               ContentTemplate="{TemplateBinding ContentTemplate}"
                                               CanContentScroll="{TemplateBinding CanContentScroll}"
                                               SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </AdornerDecorator>
                    <ScrollBar Name="PART_HorizontalScrollBar"
                               Orientation="Horizontal"
                               Grid.Row="2" Grid.ColumnSpan="2"
                               Minimum="0.0"
                               Maximum="{TemplateBinding ScrollableWidth}"
                               ViewportSize="{TemplateBinding ViewportWidth}"
                               Value="{Binding Path=HorizontalOffset,RelativeSource={RelativeSource TemplatedParent},Mode=OneWay}"
                               Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                               Cursor="Arrow"/>
                    <ScrollBar Name="PART_VerticalScrollBar"
                               Orientation="Vertical"
                               Grid.Column="2" Grid.RowSpan="2"
                               Minimum="0.0"
                               Maximum="{TemplateBinding ScrollableHeight}"
                               ViewportSize="{TemplateBinding ViewportHeight}"
                               Value="{Binding Path=VerticalOffset,RelativeSource={RelativeSource TemplatedParent},Mode=OneWay}"
                               Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
                               Cursor="Arrow"/>
                    <DockPanel Grid.Column="2"
                               Grid.Row="2"
                               Background="{Binding Path=Background,ElementName=PART_VerticalScrollBar}"
                               LastChildFill="false">
                        <Rectangle DockPanel.Dock="Left"
                                   Width="1"
                                   Fill="White"
                                   Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>
                        <Rectangle DockPanel.Dock="Top"
                                   Height="1"
                                   Fill="White"
                                   Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
                    </DockPanel>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Posted in WPF | Tagged: , , , | 5 Comments »