要想直接在WPF中给ListView加上横竖线条,是一件很费劲的事情,不过我们可以通过其他的办法,来绕过去,具体是什么办法呢,就看下面的步骤吧!
1. 建立一个WPF程序
2. 添加一个类文件,命名为GridLineDecorator.cs,写入如下内容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Threading;
namespace ListViewWithLines
{
[ContentProperty("Target")]
public class GridLineDecorator : FrameworkElement
{
private ListView _target;
private DrawingVisual _gridLinesVisual = new DrawingVisual();
private GridViewHeaderRowPresenter _headerRowPresenter = null;
public GridLineDecorator()
{
this.AddVisualChild(_gridLinesVisual);
this.AddHandler(ScrollViewer.ScrollChangedEvent, new RoutedEventHandler(OnScrollChanged));
}
#region GridLineBrush
/// <summary>
/// GridLineBrush Dependency Property
/// </summary>
public static readonly DependencyProperty GridLineBrushProperty =
DependencyProperty.Register("GridLineBrush", typeof(Brush), typeof(GridLineDecorator),
new FrameworkPropertyMetadata(Brushes.LightGray,
new PropertyChangedCallback(OnGridLineBrushChanged)));
/// <summary>
/// Gets or sets the GridLineBrush property. This dependency property
/// indicates ....
/// </summary>
public Brush GridLineBrush
{
get { return (Brush)GetValue(GridLineBrushProperty); }
set { SetValue(GridLineBrushProperty, value); }
}
/// <summary>
/// Handles changes to the GridLineBrush property.
/// </summary>
private static void OnGridLineBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((GridLineDecorator)d).OnGridLineBrushChanged(e);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the GridLineBrush property.
/// </summary>
protected virtual void OnGridLineBrushChanged(DependencyPropertyChangedEventArgs e)
{
DrawGridLines();
}
#endregion
#region Target
public ListView Target
{
get { return _target; }
set
{
if (_target != value)
{
if (_target != null) Detach();
RemoveVisualChild(_target);
RemoveLogicalChild(_target);
_target = value;
AddVisualChild(_target);
AddLogicalChild(_target);
if (_target != null) Attach();
InvalidateMeasure();
}
}
}
private void GetGridViewHeaderPresenter()
{
if (Target == null)
{
_headerRowPresenter = null;
return;
}
_headerRowPresenter = Target.GetDesendentChild<GridViewHeaderRowPresenter>();
}
#endregion
#region DrawGridLines
private void DrawGridLines()
{
if (Target == null) return;
if (_headerRowPresenter == null) return;
var itemCount = Target.Items.Count;
if (itemCount == 0) return;
var gridView = Target.View as GridView;
if (gridView == null) return;
// 获取drawingContext
var drawingContext = _gridLinesVisual.RenderOpen();
var startPoint = new Point(0, 0);
// 为了对齐到像素的计算参数,否则就会看到有些线是模糊的
var dpiFactor = this.GetDpiFactor();
var pen = new Pen(this.GridLineBrush, 1 * dpiFactor);
var halfPenWidth = pen.Thickness / 2;
var guidelines = new GuidelineSet();
// 计算表头的偏移量和大小
var headerOffset = _headerRowPresenter.TranslatePoint(startPoint, this);
var headerSize = _headerRowPresenter.RenderSize;
var headerBottomY = headerOffset.Y + headerSize.Height;
// 计算ScrollViewer的可视区域大小
var item0 = _target.ItemContainerGenerator.ContainerFromIndex(0);
if (item0 == null) return;
var scrollViewer = item0.GetAncestor<ScrollViewer>();
if (scrollViewer == null) return;
var contentElement = scrollViewer.Content as UIElement;
var maxLineX = scrollViewer.ViewportWidth;
var maxLineY = headerBottomY + contentElement.RenderSize.Height;
var vLineY = 0.0;
// 画横线
for (int i = 0; i < itemCount; i++)
{
var item = Target.ItemContainerGenerator.ContainerFromIndex(i) as ListViewItem;
if (item != null)
{
var renderSize = item.RenderSize;
var offset = item.TranslatePoint(startPoint, this);
var hLineX1 = offset.X;
var hLineX2 = offset.X + renderSize.Width;
var hLineY = offset.Y + renderSize.Height;
vLineY = hLineY;
// 小于视图起始位置的不绘制
if (hLineY <= headerBottomY) continue;
// 大于视图结束位置之后的不绘制
if (hLineY > maxLineY) break;
// 如果大于横向宽度,取横向宽度
if (hLineX2 > maxLineX) hLineX2 = maxLineX;
// 加入参考线,对齐到像素
guidelines.GuidelinesY.Add(hLineY + halfPenWidth);
drawingContext.PushGuidelineSet(guidelines);
drawingContext.DrawLine(pen, new Point(hLineX1, hLineY), new Point(hLineX2, hLineY));
drawingContext.Pop();
}
}
// 画竖线
var columns = gridView.Columns;
var vLineX = headerOffset.X;
if (vLineY > maxLineY) vLineY = maxLineY;
foreach (var column in columns)
{
var columnWidth = column.GetColumnWidth();
vLineX += columnWidth;
if (vLineX > maxLineX) break;
// 加入参考线,对齐到像素
guidelines.GuidelinesX.Add(vLineX + halfPenWidth);
drawingContext.PushGuidelineSet(guidelines);
drawingContext.DrawLine(pen, new Point(vLineX, headerBottomY), new Point(vLineX, vLineY));
drawingContext.Pop();
}
drawingContext.Close();
}
#endregion
#region Overrides to show Target and grid lines
protected override int VisualChildrenCount
{
get { return Target == null ? 1 : 2; }
}
protected override System.Collections.IEnumerator LogicalChildren
{
get { yield return Target; }
}
protected override Visual GetVisualChild(int index)
{
if (index == 0) return _target;
if (index == 1) return _gridLinesVisual;
throw new IndexOutOfRangeException(string.Format("Index of visual child '{0}' is out of range", index));
}
protected override Size MeasureOverride(Size availableSize)
{
if (Target != null)
{
Target.Measure(availableSize);
return Target.DesiredSize;
}
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
if (Target != null)
Target.Arrange(new Rect(new Point(0, 0), finalSize));
return base.ArrangeOverride(finalSize);
}
#endregion
#region Handle Events
private void Attach()
{
_target.Loaded += OnTargetLoaded;
_target.Unloaded += OnTargetUnloaded;
_target.SizeChanged += OnTargetSizeChanged;
}
private void Detach()
{
_target.Loaded -= OnTargetLoaded;
_target.Unloaded -= OnTargetUnloaded;
_target.SizeChanged -= OnTargetSizeChanged;
}
private void OnTargetLoaded(object sender, RoutedEventArgs e)
{
if (_headerRowPresenter == null)
GetGridViewHeaderPresenter();
DrawGridLines();
}
private void OnTargetUnloaded(object sender, RoutedEventArgs e)
{
DrawGridLines();
}
private void OnTargetSizeChanged(object sender, SizeChangedEventArgs e)
{
DrawGridLines();
}
private void OnScrollChanged(object sender, RoutedEventArgs e)
{
DrawGridLines();
}
#endregion
}
}
3. 添加一个类文件,命名为GridViewColumnHelper.cs,写入如下内容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Controls;
namespace ListViewWithLines
{
internal static class GridViewColumnHelper
{
private static PropertyInfo DesiredWidthProperty =
typeof(GridViewColumn).GetProperty("DesiredWidth", BindingFlags.NonPublic | BindingFlags.Instance);
public static double GetColumnWidth(this GridViewColumn column)
{
return (double.IsNaN(column.Width)) ? (double)DesiredWidthProperty.GetValue(column, null) : column.Width;
}
}
}
4. 添加一个类文件,命名为VisualService.cs,写入如下内容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows;
namespace ListViewWithLines
{
public static class VisualService
{
public static double GetDpiFactor(this Visual target)
{
var source = PresentationSource.FromVisual(target);
return source == null ? 1.0 : 1 / source.CompositionTarget.TransformToDevice.M11;
}
public static T GetAncestor<T>(this DependencyObject target)
where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(target);
if (parent is T)
return (T)parent;
if (parent != null)
return parent.GetAncestor<T>();
return null;
}
public static T GetDesendentChild<T>(this DependencyObject target)
where T : DependencyObject
{
var childCount = VisualTreeHelper.GetChildrenCount(target);
if (childCount == 0) return null;
for (int i = 0; i < childCount; i++)
{
var current = VisualTreeHelper.GetChild(target, i);
if (current is T)
return (T)current;
var desendent = current.GetDesendentChild<T>();
if (desendent != null)
return desendent;
}
return null;
}
}
}
5. 在界面主XAML文件中,加入以下内容
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="TreeViewDesign.MainWindow"
xmlns:l="clr-namespace:ListViewWithLines"
x:Name="Window"
Title="开发测试工具"
Width="900" Height="600" Background="White" WindowState="Maximized">
<Grid>
<l:GridLineDecorator GridLineBrush="{Binding ElementName=cb_GridLineBrush, Path=SelectedItem.Content}">
<ListView Background="White" FocusVisualStyle="{x:Null}" Name="FileInfo">
<ListView.View>
<GridView>
<GridViewColumn Header="工程路径" Width="600" DisplayMemberBinding="{Binding XPath=@PATH}"/>
<GridViewColumn Header="修改日期" Width="200" DisplayMemberBinding="{Binding XPath=@MODIFY}"/>
</GridView>
</ListView.View>
<ListView.ContextMenu>
<ContextMenu Name="ContextMenu">
<MenuItem Header="创建工程" />
<MenuItem Header="添加工程" />
<MenuItem Header="打开工程" />
<MenuItem Header="删除工程" />
</ContextMenu>
</ListView.ContextMenu>
</ListView>
</l:GridLineDecorator>
</Grid>
</window>
6. 在界面主XAML文件对应的cs文件的构造函数中,加入以下内容
public MainWindow()
{
this.InitializeComponent();
// 在此点下面插入创建对象所需的代码。
string xmlpath = Directory.GetCurrentDirectory() + "\\context.xml";
XmlDocument xml = new XmlDocument();
xml.Load(xmlpath);
XmlDataProvider xdp = new XmlDataProvider();
xdp.Document = xml;
xdp.XPath = @"/Context/URInfo";
this.FileInfo.DataContext = xdp;
this.FileInfo.SetBinding(ComboBox.ItemsSourceProperty, new Binding());
}
7. 自己建立一个xml文件,里面填上相应的数据(有几列就写几列)
8. 编译WPF程序,大功告成了!