How could I put a border on my grid control in WPF?

C#Wpf

C# Problem Overview


How do I put a border on my grid in C#/WPF?

This is what I would like it to be, but puts a border around the whole thing instead of the grid control I put in my application.

<Grid>
    <Border BorderBrush="Black" BorderThickness="2">
        <Grid Height="166" HorizontalAlignment="Left" Margin="12,12,0,0" Name="grid1" VerticalAlignment="Top" Width="479" Background="#FFF2F2F2" />
    </Border>
... and so on ...

C# Solutions


Solution 1 - C#

If you just want an outer border, the easiest way is to put it in a Border control:

<Border BorderBrush="Black" BorderThickness="2">
    <Grid>
       <!-- Grid contents here -->
    </Grid>
</Border>

The reason you're seeing the border completely fill your control is that, by default, it's HorizontalAlignment and VerticalAlignment are set to Stretch. Try the following:

<Grid>
    <Border  HorizontalAlignment="Left" VerticalAlignment="Top"  BorderBrush="Black" BorderThickness="2">
        <Grid Height="166" HorizontalAlignment="Left" Margin="12,12,0,0" Name="grid1" VerticalAlignment="Top" Width="479" Background="#FFF2F2F2" />
    </Border>
</Grid>

This should get you what you're after (though you may want to put a margin on all 4 sides, not just 2...)

Solution 2 - C#

If nesting your grid in a border control

<Border>
    <Grid>
    </Grid>
</Border>

does not do what you want, then you are going to have to make your own control template for the grid (or border) that DOES do what you want.

Solution 3 - C#

I think your problem is that the margin should be specified in the border tag and not in the grid.

Solution 4 - C#

This is a later answer that works for me, if it may be of use to anyone in the future. I wanted a simple border around all four sides of the grid and I achieved it like so...

<DataGrid x:Name="dgDisplay" Margin="5" BorderBrush="#1266a7" BorderThickness="1"...

Solution 5 - C#

<Grid x:Name="outerGrid">
    <Grid x:Name="innerGrid">
        <Border BorderBrush="#FF179AC8" BorderThickness="2" />
        <other stuff></other stuff>
        <other stuff></other stuff>
    </Grid>
</Grid>

This code Wrap a border inside the "innerGrid"

Solution 6 - C#

If someone is interested in the similar problem, but is not working with XAML, here's my solution:

var B1 = new Border();
B1.BorderBrush = Brushes.Black;
B1.BorderThickness = new Thickness(0, 1, 0, 0); // You can specify here which borders do you want
YourPanel.Children.Add(B1);

Solution 7 - C#

This is my solution, wish useful for you:

public class Sheet : Grid
{
	public static readonly DependencyProperty BorderBrushProperty = DependencyProperty.Register(nameof(BorderBrush), typeof(Brush), typeof(Sheet), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnBorderBrushChanged));

	public static readonly DependencyProperty BorderThicknessProperty = DependencyProperty.Register(nameof(BorderThickness), typeof(double), typeof(Sheet), new FrameworkPropertyMetadata(1D, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnBorderThicknessChanged, CoerceBorderThickness));
	public static readonly DependencyProperty CellSpacingProperty = DependencyProperty.Register(nameof(CellSpacing), typeof(double), typeof(Sheet), new FrameworkPropertyMetadata(0D, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnCellSpacingChanged, CoerceCellSpacing));

	public Brush BorderBrush
	{
		get => this.GetValue(BorderBrushProperty) as Brush;
		set => this.SetValue(BorderBrushProperty, value);
	}

	public double BorderThickness
	{
		get => (double)this.GetValue(BorderThicknessProperty);
		set => this.SetValue(BorderThicknessProperty, value);
	}

	public double CellSpacing
	{
		get => (double)this.GetValue(CellSpacingProperty);
		set => this.SetValue(CellSpacingProperty, value);
	}

	protected override Size ArrangeOverride(Size arrangeSize)
	{
		Size size = base.ArrangeOverride(arrangeSize);
		double border = this.BorderThickness;
		double doubleBorder = border * 2D;
		double spacing = this.CellSpacing;
		double halfSpacing = spacing * 0.5D;
		if (border > 0D || spacing > 0D)
		{
			foreach (UIElement child in this.InternalChildren)
			{
				this.GetChildBounds(child, out double left, out double top, out double width, out double height);

				left += halfSpacing + border;
				top += halfSpacing + border;
				height -= spacing + doubleBorder;
				width -= spacing + doubleBorder;

				if (width < 0D)
				{
					width = 0D;
				}

				if (height < 0D)
				{
					height = 0D;
				}

				left -= left % 0.5D;
				top -= top % 0.5D;
				width -= width % 0.5D;
				height -= height % 0.5D;

				child.Arrange(new Rect(left, top, width, height));
			}

			if (border > 0D && this.BorderBrush != null)
			{
				this.InvalidateVisual();
			}
		}

		return size;
	}
	
	protected override void OnRender(DrawingContext dc)
	{
		base.OnRender(dc);

		if (this.BorderThickness > 0D && this.BorderBrush != null)
		{
			if (this.CellSpacing == 0D)
			{
				this.DrawCollapsedBorder(dc);
			}
			else
			{
				this.DrawSeperatedBorder(dc);
			}
		}
	}

	private void DrawSeperatedBorder(DrawingContext dc)
	{
		double spacing = this.CellSpacing;
		double halfSpacing = spacing * 0.5D;


		#region draw border
		Pen pen = new Pen(this.BorderBrush, this.BorderThickness);
		UIElementCollection children = this.InternalChildren;
		foreach (UIElement child in children)
		{
			this.GetChildBounds(child, out double left, out double top, out double width, out double height);
			left += halfSpacing;
			top += halfSpacing;
			width -= spacing;
			height -= spacing;

			dc.DrawRectangle(null, pen, new Rect(left, top, width, height));
		}
		#endregion
	}

	private void DrawCollapsedBorder(DrawingContext dc)
	{
		RowDefinitionCollection rows = this.RowDefinitions;
		ColumnDefinitionCollection columns = this.ColumnDefinitions;
		int rowCount = rows.Count;
		int columnCount = columns.Count;
		const byte BORDER_LEFT = 0x08;
		const byte BORDER_TOP = 0x04;
		const byte BORDER_RIGHT = 0x02;
		const byte BORDER_BOTTOM = 0x01;
		byte[,] borderState = new byte[rowCount, columnCount];
		int column = columnCount - 1;
		int columnSpan;
		int row = rowCount - 1;
		int rowSpan;
		#region generate main border data
		for (int i = 0; i < rowCount; i++)
		{
			borderState[i, 0] = BORDER_LEFT;
			borderState[i, column] = BORDER_RIGHT;
		}

		for (int i = 0; i < columnCount; i++)
		{
			borderState[0, i] |= BORDER_TOP;
			borderState[row, i] |= BORDER_BOTTOM;
		}
		#endregion

		#region generate child border data
		UIElementCollection children = this.InternalChildren;
		foreach (UIElement child in children)
		{
			this.GetChildLayout(child, out row, out rowSpan, out column, out columnSpan);
			for (int i = 0; i < rowSpan; i++)
			{
				borderState[row + i, column] |= BORDER_LEFT;
				borderState[row + i, column + columnSpan - 1] |= BORDER_RIGHT;
			}
			for (int i = 0; i < columnSpan; i++)
			{
				borderState[row, column + i] |= BORDER_TOP;
				borderState[row + rowSpan - 1, column + i] |= BORDER_BOTTOM;
			}
		}
		#endregion

		#region draw border
		Pen pen = new Pen(this.BorderBrush, this.BorderThickness);
		double left;
		double top;
		double width, height;


		for (int r = 0; r < rowCount; r++)
		{
			RowDefinition v = rows[r];
			top = v.Offset;
			height = v.ActualHeight;
			for (int c = 0; c < columnCount; c++)
			{
				byte state = borderState[r, c];

				ColumnDefinition h = columns[c];
				left = h.Offset;
				width = h.ActualWidth;
				if ((state & BORDER_LEFT) == BORDER_LEFT)
				{
					dc.DrawLine(pen, new Point(left, top), new Point(left, top + height));
				}
				if ((state & BORDER_TOP) == BORDER_TOP)
				{
					dc.DrawLine(pen, new Point(left, top), new Point(left + width, top));
				}
				if ((state & BORDER_RIGHT) == BORDER_RIGHT && (c + 1 >= columnCount || (borderState[r, c + 1] & BORDER_LEFT) == 0))
				{
					dc.DrawLine(pen, new Point(left + width, top), new Point(left + width, top + height));
				}
				if ((state & BORDER_BOTTOM) == BORDER_BOTTOM && (r + 1 >= rowCount || (borderState[r + 1, c] & BORDER_TOP) == 0))
				{
					dc.DrawLine(pen, new Point(left, top + height), new Point(left + width, top + height));
				}
			}

		}
		#endregion
	}

	private void GetChildBounds(UIElement child, out double left, out double top, out double width, out double height)
	{
		ColumnDefinitionCollection columns = this.ColumnDefinitions;
		RowDefinitionCollection rows = this.RowDefinitions;
		int rowCount = rows.Count;

		int row = (int)child.GetValue(Grid.RowProperty);
		if (row >= rowCount)
		{
			row = rowCount - 1;
		}

		int rowSpan = (int)child.GetValue(Grid.RowSpanProperty);
		if (row + rowSpan > rowCount)
		{
			rowSpan = rowCount - row;
		}
		int columnCount = columns.Count;

		int column = (int)child.GetValue(Grid.ColumnProperty);
		if (column >= columnCount)
		{
			column = columnCount - 1;
		}

		int columnSpan = (int)child.GetValue(Grid.ColumnSpanProperty);
		if (column + columnSpan > columnCount)
		{
			columnSpan = columnCount - column;
		}

		left = columns[column].Offset;
		top = rows[row].Offset;
		ColumnDefinition right = columns[column + columnSpan - 1];
		width = right.Offset + right.ActualWidth - left;
		RowDefinition bottom = rows[row + rowSpan - 1];
		height = bottom.Offset + bottom.ActualHeight - top;
		if (width < 0D)
		{
			width = 0D;
		}

		if (height < 0D)
		{
			height = 0D;
		}

	}
	private void GetChildLayout(UIElement child, out int row, out int rowSpan, out int column, out int columnSpan)
	{
		int rowCount = this.RowDefinitions.Count;

		row = (int)child.GetValue(Grid.RowProperty);
		if (row >= rowCount)
		{
			row = rowCount - 1;
		}

		rowSpan = (int)child.GetValue(Grid.RowSpanProperty);
		if (row + rowSpan > rowCount)
		{
			rowSpan = rowCount - row;
		}
		int columnCount = this.ColumnDefinitions.Count;

		column = (int)child.GetValue(Grid.ColumnProperty);
		if (column >= columnCount)
		{
			column = columnCount - 1;
		}

		columnSpan = (int)child.GetValue(Grid.ColumnSpanProperty);
		if (column + columnSpan > columnCount)
		{
			columnSpan = columnCount - column;
		}
	}

	private static void OnBorderBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
	{
		if (d is UIElement element)
		{
			element.InvalidateVisual();
		}
	}

	private static void OnBorderThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
	{
		if (d is UIElement element)
		{
			element.InvalidateArrange();
		}
	}

	private static void OnCellSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
	{
		if (d is UIElement element)
		{
			element.InvalidateArrange();
		}
	}

	private static object CoerceBorderThickness(DependencyObject d, object baseValue)
	{
		if (baseValue is double value)
		{
			return value < 0D || double.IsNaN(value) || double.IsInfinity(value) ? 0D : value;
		}

		return 0D;
	}
	private static object CoerceCellSpacing(DependencyObject d, object baseValue)
	{
		if (baseValue is double value)
		{
			return value < 0D || double.IsNaN(value) || double.IsInfinity(value) ? 0D : value;
		}

		return 0D;
	}
}

a demo: demo of a table with collapsed border

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJason94View Question on Stackoverflow
Solution 1 - C#Reed CopseyView Answer on Stackoverflow
Solution 2 - C#Muad'DibView Answer on Stackoverflow
Solution 3 - C#suregaView Answer on Stackoverflow
Solution 4 - C#georgehatziView Answer on Stackoverflow
Solution 5 - C#PJ3View Answer on Stackoverflow
Solution 6 - C#TripleAccretionView Answer on Stackoverflow
Solution 7 - C#dexiangView Answer on Stackoverflow