I try to get two borders in one grid with c# code.
I have one Grid in xaml that looks like this:
<Grid Name="GridKalkAuswahl" ShowGridLines="False" HorizontalAlignment="Left"
Height="Auto" VerticalAlignment="Top" Width="463">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="38px"/>
<ColumnDefinition Width="16px"/>
<ColumnDefinition Width="40px"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
Now I want to insert two Borders into the Grid.
Border myborder = new Border();
myborder.BorderBrush = new SolidColorBrush(Colors.DarkGray);
myborder.BorderThickness = new Thickness(1);
GridKalkAuswahl.Children.Add(myborder);
Grid.SetRowSpan(myborder, noStaffel.Count);
Grid.SetColumnSpan(myborder, 4);
But this ist just one Border. How could i get a second one into the Grid? If I do that the same way the 2 Border are on the same place.
Thank you.
set Margin to inner border. make that Margin equal to outer border Thickness
Border myborder = new Border
{
BorderBrush = Brushes.DarkGray,
BorderThickness = new Thickness(1);
};
GridKalkAuswahl.Children.Add(myborder);
Grid.SetRowSpan(myborder, noStaffel.Count);
Grid.SetColumnSpan(myborder, 4);
Border myborder2 = new Border
{
BorderBrush = Brushes.Orange,
BorderThickness = new Thickness(1),
Margin = myborder.BorderThickness
};
GridKalkAuswahl.Children.Add(myborder2);
Grid.SetRowSpan(myborder2, noStaffel.Count);
Grid.SetColumnSpan(myborder2, 4);
"But this ist just one Border. How could i get a second one into the Grid? If I do that the same way the 2 Border are on the same place." Ofcourse they're on the same place. Everything works as expected with your code. If you will create two borders, add them to the grid as it's children it's fine but if you will set the same columnspan and rowspan they will simply be on the same place. Please take a look at the example below:
Border myborder = new Border();
myborder.BorderBrush = new SolidColorBrush(Colors.DarkGray);
myborder.BorderThickness = new Thickness(1);
GridKalkAuswahl.Children.Add(myborder);
Grid.SetRowSpan(myborder, 1);
Grid.SetColumnSpan(myborder, 4);
Border myborder2 = new Border();
myborder2.BorderBrush = new SolidColorBrush(Colors.Crimson);
myborder2.BorderThickness = new Thickness(1);
GridKalkAuswahl.Children.Add(myborder2);
Grid.SetRowSpan(myborder2, 1);
Grid.SetColumnSpan(myborder2, 3);
Grid.SetColumnSpan and Grid.SetRowSpan is the key to your problem.
So I'm trying to build out a form that will automatically scale proportionally up and down based on the available width of the parent container, and the same column percentage ratios, like this:
There will be other surrounding content that needs to scale as well, like images and buttons (which will not be in the same grid), and from what I've read so far, using a Viewbox is the way to go.
However, when I wrap my grid in a Viewbox with Stretch="Uniform", the Textbox controls each collapse down to their minimum width, which looks like this:
If I increase the container width, everything scales as expected (good), but the textboxes are still collapsed to their minimum-possible width (bad):
If I type any content into the Textboxes, they will increase their width to contain the text:
...but I don't want that behavior - I want the Textbox element widths to be tied to the grid column widths, NOT to be dependent on the content.
Now, I've looked at a variety of SO questions, and this one comes closest to what I'm after:
How to automatically scale font size for a group of controls?
...but it still didn't really deal with the textbox width behavior specifically (when it interacts with the Viewbox beahvior), which seems to be the primary problem.
I've tried a variety of things - different HorizontalAlignment="Stretch" settings and so on, but nothing has worked so far. Here is my XAML:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<StackPanel.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Silver" Offset="0"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</StackPanel.Background>
<Viewbox Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Content="Field A" Grid.Column="0" Grid.Row="0" />
<TextBox Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch"></TextBox>
<Label Content="Field B" Grid.Column="2" Grid.Row="0" />
<TextBox Grid.Column="3" Grid.Row="0" HorizontalAlignment="Stretch"></TextBox>
</Grid>
</Viewbox>
<Label Content="Other Stuff"/>
</StackPanel>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" Height="100" Width="5"/>
<StackPanel Grid.Column="2">
<Label Content="Body"/>
</StackPanel>
</Grid>
</Window>
The reason for this behavior is that a Viewbox child is given infinite space to measure its desired size. Stretching the TextBoxes to infinite Width wouldn't make much sense, as that couldn't be rendered anyway, so their default size is returned.
You can use a converter to achieve the desired effect.
public class ToWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double gridWidth = (double)value;
return gridWidth * 2/6;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can hook everything up adding these resources.
<Viewbox.Resources>
<local:ToWidthConverter x:Key="ToWidthConverter"/>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Width"
Value="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType={x:Type Grid}},
Converter={StaticResource ToWidthConverter}}"/>
</Style>
</Viewbox.Resources>
UPDATE
I'm having trouble understanding the original problem of the infinite
grid width.
The infinite space approach is often used to determine the DesiredSize of a UIElement. In short, you give the control all the space it could possibly need (no constraints) and then measure it to retrieve its desired size.
Viewbox uses this approach to measure its child, but our Grid is dynamic in size (no Height or Width are set in code), so the Viewbox goes down another level at the grids children to see if it can determine a size by taking the sum of the components.
However, you can run in to problems when this sum of components exceeds the total available size, as shown below.
I replaced the textboxes with labels Foo and Bar and set their backgroundcolor to gray. Now we can see Bar is invading Body territory, this is clearly not something we meant to happen.
Again, the root of the problem comes from Viewbox not knowing how to divide infinity in to 6 equal shares (to map to columnwidths 1*, 2*, 1*,2*), so all we need to do is restore the link with the grids width. In ToWidthConverter the aim was to map the TextBox' Width to the Grids ColumnWidth of 2*, so I used gridWidth * 2/6. Now Viewbox is able to solve the equation again: each TextBox gets one third of gridwidth, and each Label one half of that (1* vs 2*).
Of course, when you scramble things up, by introducing new columns, you'll have to take care to keep the sum of the components in sync with the total available width. In other words, the equation needs to be solvable. Put in math, the sum of the desired sizes (of the controls you haven't constrained, labels in our example) and the converted sizes (as parts of gridWidth, textboxes in our example) needs to be less than or equal to the available size (gridWidth in our example).
I found the scaling to behave well if you use the converted sizes for TextBoxes, and let the star sized ColumnWidths handle most others. Keeping in mind to stay within the total available size.
One way to add some flexibility is to add a ConverterParameter to the mix.
public class PercentageToWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double gridWidth = (double)value;
double percentage = ParsePercentage(parameter);
return gridWidth * percentage;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private double ParsePercentage(object parameter)
{
// I chose to let it fail if parameter isn't in right format
string[] s = ((string)parameter).Split('/');
double percentage = Double.Parse(s[0]) / Double.Parse(s[1]);
return percentage;
}
}
An example that divides gridWidth over 10 equal shares, and assigns these shares to the components accordingly.
<Viewbox Stretch="Uniform">
<Viewbox.Resources>
<local:PercentageToWidthConverter x:Key="PercentageToWidthConverter"/>
</Viewbox.Resources>
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Label Content="Field A" Grid.Column="0" />
<TextBox Grid.Column="1"
Width="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType={x:Type Grid}},
Converter={StaticResource PercentageToWidthConverter},
ConverterParameter=2/10}" />
<Label Content="Field B" Grid.Column="2" />
<TextBox Grid.Column="3"
Width="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType={x:Type Grid}},
Converter={StaticResource PercentageToWidthConverter},
ConverterParameter=3/10}" />
<Button Content="Ok" Grid.Column="4"
Width="{Binding ActualWidth,
RelativeSource={RelativeSource AncestorType={x:Type Grid}},
Converter={StaticResource PercentageToWidthConverter},
ConverterParameter=1/10}" />
</Grid>
</Viewbox>
Note the shares for each control, grouped as 2 - 2 - 2 - 3 - 1 (with 1 for buttonwidth).
Finally, depending on the reusability you're after, some other ways to handle this:
Set fixed size(s) on your root Grid. Downsides:
Needs to be finetuned each time you change components (to achieve the
desired horizontal / vertical / fontsize ratio)
This ratio might break on different themes, Windows versions,...
Add a Behavior. As done in one of the answers in your linked FontSize post, but instead implemented to map the column widths to parts of gridWidth.
Create a custom panel, as proposed by #Grx70.
The problem with your approach is that Grid does not work exactly how we intuitively think it does. Namely, the star size works as expected only if these conditions are met:
The Grid has its horizontal alignment set to Stretch
The Grid is contained in a finite size container, i.e. its Measure method receives a constraint with finite Width (not double.PositiveInfinity)
This pertains to column sizing; row sizing is symmetrical. In your case the second condition is not met. I am not aware of any simple tricks to make Grid work as you expect, so my solution would be to create custom Panel that would do the job. That way you are in full control of how the controls are laid out. It's not really that hard to accomplish, although it requires some level of understanding how WPF layout system works.
Here's an example implementation that does your bidding. For the sake of brevity it only works horizontally, but it's not difficult to extend it to also work vertically.
public class ProportionalPanel : Panel
{
protected override Size MeasureOverride(Size constraint)
{
/* Here we measure all children and return minimal size this panel
* needs to be to show all children without clipping while maintaining
* the desired proportions between them. We should try, but are not
* obliged to, fit into the given constraint (available size) */
var desiredSize = new Size();
if (Children.Count > 0)
{
var children = Children.Cast<UIElement>().ToList();
var weights = children.Select(GetWeight).ToList();
var totalWeight = weights.Sum();
var unitWidth = 0d;
if (totalWeight == 0)
{
//We should handle the situation when all children have weights set
//to 0. One option is to measure them with 0 available space. To do
//so we simply set totalWeight to something other than 0 to avoid
//division by 0 later on.
totalWeight = children.Count;
//We could also assume they are to be arranged uniformly, so we
//simply coerce their weights to 1
for (var i = 0; i < weights.Count; i++)
weights[i] = 1;
}
for (var i = 0; i < children.Count; i++)
{
var child = children[i];
child.Measure(new Size
{
Width = constraint.Width * weights[i] / totalWeight,
Height = constraint.Height
});
desiredSize.Width += child.DesiredSize.Width;
desiredSize.Height =
Math.Max(desiredSize.Height, child.DesiredSize.Height);
if (weights[i] != 0)
unitWidth =
Math.Max(unitWidth, child.DesiredSize.Width / weights[i]);
}
if (double.IsPositiveInfinity(constraint.Width))
{
//If there's unlimited space (e.g. when the panel is nested in a Viewbox
//or a StackPanel) we need to adjust the desired width so that no child
//is given less than desired space while maintaining the desired
//proportions between them
desiredSize.Width = totalWeight * unitWidth;
}
}
return desiredSize;
}
protected override Size ArrangeOverride(Size constraint)
{
/* Here we arrange all children into their places and return the
* actual size this panel is. The constraint will never be smaller
* than the value of DesiredSize property, which is determined in
* the MeasureOverride method. If the desired size is larger than
* the size of parent element, the panel will simply be clipped
* or appear "outside" of the parent element */
var size = new Size();
if (Children.Count > 0)
{
var children = Children.Cast<UIElement>().ToList();
var weights = children.Select(GetWeight).ToList();
var totalWeight = weights.Sum();
if (totalWeight == 0)
{
//We perform same routine as in MeasureOverride
totalWeight = children.Count;
for (var i = 0; i < weights.Count; i++)
weights[i] = 1;
}
var offset = 0d;
for (var i = 0; i < children.Count; i++)
{
var width = constraint.Width * weights[i] / totalWeight;
children[i].Arrange(new Rect
{
X = offset,
Width = width,
Height = constraint.Height,
});
offset += width;
size.Width += children[i].RenderSize.Width;
size.Height = Math.Max(size.Height, children[i].RenderSize.Height);
}
}
return size;
}
public static readonly DependencyProperty WeightProperty =
DependencyProperty.RegisterAttached(
name: "Weight",
propertyType: typeof(double),
ownerType: typeof(ProportionalPanel),
defaultMetadata: new FrameworkPropertyMetadata
{
AffectsParentArrange = true, //because it's set on children and is used
//in parent panel's ArrageOverride method
AffectsParentMeasure = true, //because it's set on children and is used
//in parent panel's MeasuerOverride method
DefaultValue = 1d,
},
validateValueCallback: ValidateWeight);
private static bool ValidateWeight(object value)
{
//We want the value to be not less than 0 and finite
return value is double d
&& d >= 0 //this excludes double.NaN and double.NegativeInfinity
&& !double.IsPositiveInfinity(d);
}
public static double GetWeight(UIElement d)
=> (double)d.GetValue(WeightProperty);
public static void SetWeight(UIElement d, double value)
=> d.SetValue(WeightProperty, value);
}
And the usage looks like this:
<local:ProportionalPanel>
<Label Content="Field A" local:ProportionalPanel.Weight="1" />
<TextBox local:ProportionalPanel.Weight="2" />
<Label Content="Field B" local:ProportionalPanel.Weight="1" />
<TextBox local:ProportionalPanel.Weight="2" />
</local:ProportionalPanel>
I want to set the border of a grid in a way that it's only on the top of it (this is what I've managed to do) and that it's only in the center of the top (I mean it starts about 30px from the left edge and ends also 30px from the end)
The Code I use for generating Grid (in the .cs file):
Grid NewGrid = new Grid()
{
Height = 90,
Padding = new Thickness { Left = 0, Right = 0, Top = 0, Bottom = 0 },
BorderThickness = new Thickness { Left = 0, Top = 1, Right = 0, Bottom = 0 },
BorderBrush = new SolidColorBrush(Colors.Black),
};
The image of what I want to achieve:
where the gray color is no border and the black is a visible solid border...
You can't do this with Grid.BorderBrush and Grid.BorderThickness; the border always extends to the corners of the element.
Just use a Rectangle in the first row of the Grid to achieve the border you want:
<Grid>
<Grid.RowDefinitions>
<!-- The first row is for the border only -->
<RowDefinition Height="Auto"/>
<!-- Use additional rows as your layout dictates -->
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- The border element in the first row -->
<Rectangle Margin="50,0" Fill="Black" Height="2"/>
<!-- The main content of the Grid in subsequent rows -->
<Button Grid.Row="1" Content="Example 1"/>
<Button Grid.Row="2" Content="Example 2"/>
</Grid>
i think the best approach would be set no border on the Grid, but overlay a Line on top of the grid with the computed width like...
Line newLine = new Line();
{
line1.X2 = NewGrid.Width -60;
}
I have a grid (which also could be a rectangle) which I want to draw from it's bottom to the top. It's a long lineair bar.
<Grid Grid.Row="2" Background="AliceBlue" x:Name="SkillBar11">
<TextBlock Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,40" FontSize="16">IV</TextBlock>
</Grid>
and I can make it draw from the middle to top and bottom with this code in C#:
Storyboard s = new Storyboard();
DoubleAnimation doubleAni = new DoubleAnimation();
doubleAni.To = SkillBar11.ActualHeight;
doubleAni.From = 0;
doubleAni.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTarget(doubleAni, SkillBar11);
doubleAni.EnableDependentAnimation = true;
s.Children.Add(doubleAni);
Storyboard.SetTargetProperty(doubleAni, "Height");
s.Begin();
But I can't get it to be drawn from the bottom to the top, instead of the middle to bottom and top. Can someone help me out? :)
I have found the anwser. You can orientate how the bar grows by setting a Horizontal or Verticalalignment in the grid properties. This way it will start at the position where you aligned it!
I want to add an array of grids to my WPF window:
Grid[] Tiles = new Grid[20];
public void LoadTile()
{
for (int X = 0; X < Tiles.Length; X++)
{
Tiles[X] = new Grid();
Tiles[X].Height = (TileData[X].SizeY * 90) - 10;
Tiles[X].Width = (TileData[X].SizeY * 90) - 10;
Tiles[X].Margin = new Thickness(0 + (TileData[X].PositionX * 90), 216 + (TileData[X].PositionY * 90), 0, 0);
Tiles[X].HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
Tiles[X].VerticalAlignment = System.Windows.VerticalAlignment.Center;
Tiles[X].Visibility = System.Windows.Visibility.Visible;
SolidColorBrush Brush1 = new SolidColorBrush(Colors.Black);
Brush1.Opacity = 0.2;
Tiles[X].Background = Brush1;
}
}
That's what I have.
(BTW: I do have a method calling that one I just didn't include it here)
I added:
Nine_Window.Content = Tiles[X];
But it made it so all I could display was one of them, because each time the loop did that piece of code again it overwrote the last one
Your usual use for a grid (let's assume 3x3) will look something along the following in the XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
</Grid>
Regarding your problem with setting the Content, You are setting it to a specific tile instead of to the array. But Again, it'll be easy to do from the XAML I believe, and simply initialize it from code if you need to.
I think what you are actually looking for is row and column definitions of a grid. Add as many as you need by executing:
Grid Tile = new Grid()
// create new columns
ColumDefintion columnDefinition = new ColumnDefinition()
columnDefinition.Height = ... // set height here
Tile.ColumnDefinitions.Add(columnDefinition);
// create a row
Tile.RowDefinitions.Add(new RowDefinition());
Otherwise your changes will affect the whole grid object.
Well I donot second your approach but if you want to continue with it, do not add your Grids like this
Nine_Window.Content = Tiles[X];
instead add a stackPanel to NineWindow.Content
<Window ....>
<Grid>
<StackPanel x:Name="myStackPanel"></StackPanel>
</Grid>
and then in code behind
myStackPanel.Children.Add(Tile[X]);
Okay, Muds nearly got my answer but I'm gonna use a Canvas instead of a Stack Panel.
If you didn't get what I meant, it's simple, I wanted to create a multiple grid controls in an array and add them to my window.