[WPF]解决ListView在没有Items时,水平滚动条不出现的问题

上一篇Blog中指出了ListView在没有Items时,即使Header超出了控件范围,水平滚动条也不会出现的问题。由于篇幅和时间所限,没有给出解决方案。下面就介绍一种解决方案。具体问题请参考上篇文章,这里就不赘述了。

解决这个问题分两个步骤:

1.       Header放到ScrollViewer中可以Scroll的部分里。

2.       Header在拖动垂直滚动条时不动。(难点,明明在ScrollViewer里,却不能动。)

第一个步骤很好做。把GridViewHeaderRowPresenterItemsPresenter一起放在ListViewScrollViewer里就是了。(原先只有ItemsPresenterListViewScrollViewer里。)

这里要感谢一下ListView的优秀设计——真正做到了LogicVisual的分离。只要把GridViewHeaderRowPresenterXAML剪切到另一个位置就可以了。什么都不用改。我们在实现自己的控件时,也应该能够达到这种易用度。

但是第二个步骤就不那么容易了。直到我在WPF Toolkit里看到了一个振奋人心的类名——SelectiveScrollingGrid。第一感觉就是这正是我想要的功能。今天终于有空研究研究了。

结果发现被忽悠了。这个名字很能蛊惑人心,看名字会以为是,在这个Grid中,可以指定哪些东西会Scroll,哪些东西不会Scroll。其实我应该猜得到了,微软也不傻,做个功能还是很倾向于用简单的方法的。这个SeletiveScrollingGrid没有控制内部的东西ScrollScroll,全都Scroll,只是把一部分的Child,用RenderTransform拉回原位,看起来象是没有动一样。

看它的实现方式,这个Grid的名字应该叫RenderBackOnScrollGrid。用RenderTransform和真正的SelectiveScrolling有什么区别呢?请参考错位的RenderTransform动画。我猜在这里也会有潜在的问题的。

好了言归正传,我们来着手解决第二个问题。解决方案如下画所示:

      

左图是原ListViewTemplate,右图是修正后的ListView Template。文字描述就省了。其中SelectiveScrollingGrid的代码是。

 

ExpandedBlockStart.gif SelectiveScrollingPart
< local:SelectiveScrollingGrid >
    
< local:SelectiveScrollingGrid.RowDefinitions >
        
< RowDefinition  Height ="Auto" />
        
< RowDefinition />
    
</ local:SelectiveScrollingGrid.RowDefinitions >
    
< ItemsPresenter  SnapsToDevicePixels =" {TemplateBinding SnapsToDevicePixels} "  Grid.Row ="1" />
    
< ScrollViewer  HorizontalScrollBarVisibility ="Hidden"
                  VerticalScrollBarVisibility
="Hidden"                      
              local:SelectiveScrollingGrid.SelectiveScrollingOrientation
="Horizontal" >
        
< GridViewHeaderRowPresenter
            
Margin ="2,0,2,0"
            SnapsToDevicePixels
=" {TemplateBinding SnapsToDevicePixels} "
            AllowsColumnReorder
=" {Binding View.AllowsColumnReorder, RelativeSource={RelativeSource TemplatedParent}} "
            ColumnHeaderContainerStyle
=" {Binding View.ColumnHeaderContainerStyle, RelativeSource={RelativeSource TemplatedParent}} "
            ColumnHeaderContextMenu
=" {Binding View.ColumnHeaderContextMenu, RelativeSource={RelativeSource TemplatedParent}} "
            ColumnHeaderStringFormat
=" {Binding View.ColumnHeaderStringFormat, RelativeSource={RelativeSource TemplatedParent}} "
            ColumnHeaderTemplate
=" {Binding View.ColumnHeaderTemplate, RelativeSource={RelativeSource TemplatedParent}} "
            ColumnHeaderTemplateSelector
=" {Binding View.ColumnHeaderTemplateSelector, RelativeSource={RelativeSource TemplatedParent}} "
            ColumnHeaderToolTip
=" {Binding View.ColumnHeaderToolTip, RelativeSource={RelativeSource TemplatedParent}} "
            Columns
=" {Binding View.Columns, RelativeSource={RelativeSource TemplatedParent}} " />
    
</ ScrollViewer >
</ local:SelectiveScrollingGrid >

 

 

注意到SelectiveScrollingGrid所在的Namespace了么?叫local,没有错,我没有用WPF Toolkit里的SelectiveScrollingGrid,而是自己又写了一个。

原因很简单,WPF Toolkit里的SelectiveScrollingGridBugBug代码如下。

ScrollViewer viewer = DataGridHelper.FindVisualParent<ScrollViewer>(element);

其中FindVisualParent的代码,没有考虑ParentTemplateParent。其代码如下:

 
 
ExpandedBlockStart.gif FindVisualParent in WPF Toolkit
public   static  T FindVisualParent < T > (UIElement element)  where  T: UIElement
{
    
for  (UIElement element2  =  element; element2  !=   null ; element2  =  VisualTreeHelper.GetParent(element2)  as  UIElement)
    {
        T local 
=  element2  as  T;
        
if  (local  !=   null )
        {
            
return  local;
        }
    }
    
return   default (T);
}

在这个FindVisualParent中,只用VisualTreeHelper去向上找Parent。但是有时这个VisualTreeHelper会莫名其妙地返回Null,而Parent属性却不是Null

所以我做的只不过是想重写一下FindVisualParent的逻辑成。

 

ExpandedBlockStart.gif My FindAncestor
public   static  T FindAncestor < T > ( this  DependencyObject element)
    
where  T : DependencyObject
{
    
if  (element  ==   null )
    {
        
return   null ;
    }
 
    DependencyObject ancestor 
=  VisualTreeHelper.GetParent(element);

    
if  (element  is  FrameworkElement)
    {
        ancestor 
=  ancestor  ??  ((element  as  FrameworkElement).Parent  ??  (element  as  FrameworkElement).TemplatedParent);
    }

    
// While, I expend CSharp compiler could handle 尾递归 sometime.
     return  ancestor  is  T  ?  ancestor  as  T : FindAncestor < T > (ancestor);
}

 

 

但是不幸的是,所有的相关函数都是static的。虽然SelectiveScrollingGrid不是sealed,但是继承他也改变不了什么。所以只能自己重新写一个。

我承认没有什么产品没有Bug,但是我希望MS能让产品的设计更灵活一点。比如少用Static做工具方法,多用策略模式或模版函数。这样就不必在想要改变一下默认行为的时候,非把整个类甚至Assembly重写了。

又扯远了,看下结果:

水平滚动条正常。再加些数据。测试一下垂直滚动。

一切正常。其实还有一个小Bug,看看大家能发现不?

这个Fix的源代码可以从这里下载。希望对你有所帮助。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值