如何绘制可扩展的网格线?

时间:2016-05-03 10:32:57

标签: c# .net wpf user-interface

我有一个画布,我想给它网格线作为背景,但我希望有一个恒定数量的网格线将画布分成相同大小的部分,而不是只有等间距网格-lines。我希望在用户调整画布大小时保留它。

我该怎么做?

2 个答案:

答案 0 :(得分:2)

这是一个基于画布后面的两个wpf ListView控件的解决方案(一个用于行,第二个用于列)。与ListView控件相关的列的内容是一个矩形。

更新版本 - 托管网格线控制。您可以在此管理网格线的数量及其可见性。

Xaml代码 - 网格线控制:

<UserControl x:Class="CAnvasWithGrid.GridLineControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:canvasWithGrid="clr-namespace:CAnvasWithGrid"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300" x:Name="This">

<Grid x:Name="LayoutRoot">
    <Grid.Resources>
        <Style TargetType="ListView">
            <Setter Property="Background" Value="Transparent"/>
        </Style>
        <Style x:Key="ListViewItemStyle" TargetType="ListViewItem">
            <Setter Property="Background" Value="Transparent"/>
        </Style>
        <DataTemplate x:Key="InnerListviewDataTemplate" DataType="{x:Type canvasWithGrid:CellModel}">
            <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                       Margin="0" StrokeDashArray="4" Stroke="Black" StrokeThickness="0.5" Fill="Transparent"/>
        </DataTemplate>
        <DataTemplate x:Key="ListviewDataTemplate" DataType="{x:Type canvasWithGrid:RowModel}">
            <ListView ItemsSource="{Binding CellModels}" BorderBrush="#00FFFFFF" BorderThickness="0" Margin="0"
                                      HorizontalContentAlignment="Stretch"
                                      VerticalContentAlignment="Stretch"
                                      VerticalAlignment="Stretch" 
                                      HorizontalAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
                <ListView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid Columns="{Binding CellModels, Converter={canvasWithGrid:CollectionLength2NumberConverter}}"></UniformGrid>
                    </ItemsPanelTemplate>
                </ListView.ItemsPanel>
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemStyle}">
                        <Setter Property="Margin" Value="0"></Setter>
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="ListViewItem">
                                    <ContentPresenter Content="{TemplateBinding Content}" Margin="0"
                                              ContentTemplate="{StaticResource InnerListviewDataTemplate}" />
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                        <Setter Property="ContentTemplate" Value="{StaticResource InnerListviewDataTemplate}"/>
                    </Style>
                </ListView.ItemContainerStyle>
            </ListView>
        </DataTemplate>
    </Grid.Resources>
    <ListView ItemsSource="{Binding ElementName=This, Path=RowModels}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Rows="{Binding ElementName=This, Path=RowModels, Converter={canvasWithGrid:CollectionLength2NumberConverter}}"/>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem" BasedOn="{StaticResource ListViewItemStyle}">
                <Setter Property="Margin" Value="0"></Setter>
                <Setter Property="HorizontalAlignment" Value="Stretch"/>
                <Setter Property="VerticalAlignment" Value="Stretch"/>
                <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                <Setter Property="VerticalContentAlignment" Value="Stretch"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ListViewItem">
                            <ContentPresenter Content="{TemplateBinding Content}" Margin="-1"
                                              ContentTemplate="{StaticResource ListviewDataTemplate}" />
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
                <Setter Property="ContentTemplate" Value="{StaticResource ListviewDataTemplate}"/>
            </Style>
        </ListView.ItemContainerStyle>
    </ListView>
</Grid>

网格线控制 - 代码隐藏

    /// <summary>
/// Interaction logic for GridLineControl.xaml
/// </summary>
public partial class GridLineControl : UserControl
{
    public GridLineControl()
    {
        InitializeComponent();
    }

    public static readonly DependencyProperty NumberOfColumnsProperty = DependencyProperty.Register(
        "NumberOfColumns", typeof (int), typeof (GridLineControl), new PropertyMetadata(default(int), NumberOfColumnsChangedCallback));

    private static void NumberOfColumnsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
    {
        var numberOfRows = (int)dependencyObject.GetValue(NumberOfRowsProperty);
        var numberOfColumns = (int)args.NewValue;
        if (numberOfColumns == 0 || numberOfRows == 0) return;
        var rowModelsCollection = GetRowModelsCollection(numberOfRows, numberOfColumns);
        dependencyObject.SetValue(RowModelsProperty, rowModelsCollection);
    }

    public int NumberOfColumns
    {
        get { return (int) GetValue(NumberOfColumnsProperty); }
        set { SetValue(NumberOfColumnsProperty, value); }
    }

    public static readonly DependencyProperty NumberOfRowsProperty = DependencyProperty.Register(
        "NumberOfRows", typeof (int), typeof (GridLineControl), new PropertyMetadata(default(int), NumberOfRowsChangedCallback));

    private static void NumberOfRowsChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
    {
        var numberOfRows = (int)args.NewValue;
        var numberOfColumns = (int)dependencyObject.GetValue(NumberOfColumnsProperty);
        if(numberOfColumns == 0 || numberOfRows == 0) return;
        var rowModelsCollection = GetRowModelsCollection(numberOfRows, numberOfColumns);
        dependencyObject.SetValue(RowModelsProperty, rowModelsCollection);
    }

    private static ObservableCollection<RowModel> GetRowModelsCollection(int numberOfRows, int numberOfColumns)
    {
        var rowModelsCollection = new ObservableCollection<RowModel>();
        for (var i = 0; i < numberOfRows; i++)
        {
            rowModelsCollection.Add(new RowModel(numberOfColumns) {Position = (i + 1).ToString()});
        }
        return rowModelsCollection;
    }

    public int NumberOfRows
    {
        get { return (int) GetValue(NumberOfRowsProperty); }
        set { SetValue(NumberOfRowsProperty, value); }
    }

    public static readonly DependencyProperty RowModelsProperty = DependencyProperty.Register("RowModels",
        typeof(ObservableCollection<RowModel>), typeof(GridLineControl),
        new PropertyMetadata(default(ObservableCollection<RowModel>)));

    public ObservableCollection<RowModel> RowModels
    {
        get { return (ObservableCollection<RowModel>)GetValue(RowModelsProperty); }
        private set { SetValue(RowModelsProperty, value); }
    }
}

<强>型号:

public class RowModel:BaseGridMember
{
    public RowModel(int numberOfCellsInRow)
    {
        CellModels = new ObservableCollection<CellModel>();
        for (int i = 0; i < numberOfCellsInRow; i++)
        {
            CellModels.Add(new CellModel{Position = (i+1).ToString()});
        }
    }
    public ObservableCollection<CellModel>  CellModels { get; set; }
}

public class CellModel:BaseGridMember
{

}

public class BaseGridMember:BaseObservableObject
{
    private string _position;

    public string Position
    {
        get { return _position; }
        set
        {
            _position = value;
            OnPropertyChanged();
        }
    }
}

主窗口xaml代码 - 正如您在这里看到的是ImageContol而不是Canvas但您可以替换它:

<Window x:Class="CAnvasWithGrid.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:canvasWithGrid="clr-namespace:CAnvasWithGrid"
    Title="MainWindow" Height="525" Width="525" x:Name="This">

<Grid Tag="{Binding ElementName=This}">
    <Grid.Resources>
        <BooleanToVisibilityConverter x:Key="Bool2VisConvKey" />
    </Grid.Resources>
    <Grid.ContextMenu>
        <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
            <MenuItem Header="Show Grid Lines" Command="{Binding ShowGridLinesCommand}"/>
        </ContextMenu>
    </Grid.ContextMenu>
    <Image Source="Resources/Koala.jpg" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MouseDown="UIElement_OnMouseDown"/>
    <canvasWithGrid:GridLineControl NumberOfRows="50" NumberOfColumns="50" 
                                    IsHitTestVisible="False" Visibility="{Binding ElementName=This, Path=AreGridLineVisible, Converter={StaticResource Bool2VisConvKey}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>

背后的主窗口代码:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        ShowGridLinesCommand = new RelayCommand(ShowGridLineManageCommand);
        AreGridLineVisible = true;
    }

    private void ShowGridLineManageCommand()
    {
        AreGridLineVisible = !AreGridLineVisible;
    }

    public static readonly DependencyProperty AreGridLineVisibleProperty = DependencyProperty.Register(
        "AreGridLineVisible", typeof (bool), typeof (MainWindow), new PropertyMetadata(default(bool)));

    public bool AreGridLineVisible
    {
        get { return (bool) GetValue(AreGridLineVisibleProperty); }
        set { SetValue(AreGridLineVisibleProperty, value); }
    }

    public static readonly DependencyProperty ShowGridLinesCommandProperty = DependencyProperty.Register(
        "ShowGridLinesCommand", typeof (ICommand), typeof (MainWindow), new PropertyMetadata(default(ICommand)));

    public ICommand ShowGridLinesCommand
    {
        get { return (ICommand) GetValue(ShowGridLinesCommandProperty); }
        set { SetValue(ShowGridLinesCommandProperty, value); }
    }



    private void UIElement_OnMouseDown(object sender, MouseButtonEventArgs e)
    {

    }
}

看起来如何: Here.

答案 1 :(得分:0)

听起来像是自定义绘图的自定义控件的候选者。你真的不想使用多个FrameworkElements,比如&#34; Line&#34;如果由于性能原因你期望很多网格线。

因此,您要创建customControl GridLinesControl并覆盖OnRender方法。您可以使用ActualWidth和ActualHeight属性获取组件的实际宽度和高度,除以所需的网格线数,并使用drawingContext.DrawLine绘制线条。

最简单的方法是添加你在画布下面制作的GridLinesControl,占用相同的空间(所以它有正确的ActualWidth和ActualHeight),如下所示:

<Grid>
    <myControls:GridLinesControl/>
    <Canvas ... />
</Grid>

所以它总是在下面。