Grid inside a StackPanel: why do auto and * behave strangely? - wpf

My google and stackoverflow search-fu have failed me, so I present to the community this question.
(This is all generated using VS2010 and .NET 4.0, in a blank default WPF Solution)
Consider the following XAML:
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Name="aborder" Grid.Column="0" Grid.ColumnSpan="2"
Background="Red" Width="200"/>
<Border Name="aborder2" Background="Green"/>
</Grid>
</StackPanel>
What would you predict the width of "aborder2" to be?
If you guessed "20 pixels", you would be wrong. The correct answer is 110 pixels.
Consider this XAML:
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Name="aborder" Grid.Column="0" Grid.ColumnSpan="2"
Background="Red" Width="200"/>
<Border Name="aborder2" Background="Green"/>
</Grid>
</StackPanel>
What would you predict the width of "aborder2" to be?
If you guessed either 20 pixels or 110 pixels, you would be wrong. The correct answer is 200 pixels.
I cannot figure this out and it's driving me insane. It seems like the answer should be obvious; clearly there's some interaction between an auto-filling grid column and the stackpanel that causes the grid to freak out. But it just doesn't seem to make sense - whatever rules are governing this behavior seem to be arbitrary. Why 110 pixels? Why not 109 pixels or 100 pixels? I would understand if the auto-sized column failed to expand fully or something, but to have the fixed-width column randomly ignore its width has left me a burnt out shell of a developer.
Any help or guiding lights would be much appreciated!

I have no idea why the first example isn't rendering correctly
The 2nd is because Auto means "the same size as the contents", but you have nothing in Column2 so Column2 is getting rendered at 0px. You have something in Column1 which spans 2 cells, but since Column2 is rendered at 0 px it means Column1 is stretched to 200 px. By default, Grid's expand their children to fill all available space in the Cell, so this is making aborder2 stretch to 200px instead of 20.
I think the first example might be a similar situation, where Column2 is rendering at 0px because it has no content, however I am not sure why it is setting aborder2 to a width of 110. The 110 seems to come from (GridWidth / TotalColumns) + (1stColumnWidth / TotalColumns * NumberOfStarColumns), so I think it's a bug.
As a side note, you can set a Column1's MaxWidth="20" to force Column1 to always render as 20px

No answer for you, but it seems to happen in Silverlight too. I'd assume there's some bug in the arrange and measure passes. If you put a custom control in there instead of a border and override the measure and arrange methods you would probably get a better picture of what's going on.
To try to solve the mystery of where 110 comes from, I'm guessing (200 - 20) / 2 + 20
EDIT: I tried a few other formulas and it didn't hold up, looks more like:
(200 + 20) / 2

Related

Disable Auto Grid Resizing in XAML Designer using VS 2019

This very frustrating problem keeps happening (now in vs 2019 and before in vs 2017) and after finding zilch looking through the XAML options and searching for similar issues I was hoping someone can help with this:
Selecting elements in the designer (particularly grid elements) will sometimes alter the * grid definitions to crazy values that will LOOK the same visually but then when I look at them markup at some point the ratios have gotten HUGE or even it will add extra rows/cols and then automatically change the spans of what is actually there its nerveracking!
Any ideas what this awful setting is or what I'm clicking to make this happen?
For example it will change this:
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="7*" />
To this:
<ColumnDefinition Width="37*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="513*" />
And it never affects the view in the designer just the markup. That's as much explanation I can give offhand, any help would be great!
The ratio of rows to columns in a Grid is very precise. For your first ColumnDefinition, means dividing the width of the whole Grid into ten parts,the first column is 20% of the width of Grid, second is 10%,third is 30%.This is desgined by your need.
When you do a little modifity in the designer, all column widths maybe distributed proportionally if you choose Star for the ColumnDefinition in the designer as the below picture shown:

Keep same layout when orientation changes

I have a Silverlight page that I would like to have below appearance in portrait and landscape mode. Essentially, there are three images arranged in a grid. The big image spans two columns. When the phone rotates, the images rotate, but the overall layout does not. The small images remain close to the back/windows/search button and the large image remains towards the top of the phone.
I have tried a number of methods to achieve this effect, but they have all proved unsatisfactory in one way or another. I'm hoping that someone can point out something that I'm missing or, at the very least, prevent someone else from having to waste 4 or 5 days coming to the same conclusion that I did. Questions in Bold:
The first thing I tried was applying a RotateTransform to the LayoutRoot element and rotating it -90 degrees whenever the phone rotation changed to landscape. I had to hard code the height and width of the layout root to 800 and 400 instead of "Auto" or it gets drawn squished. This solution almost worked, but the RotateTransform gets applied after the page is drawn. Because it is drawn as an 400x800 image on an 800x400 screen, the top 200 and bottom 200 pixels aren't drawn. This becomes obvious after it's rotated and the (now)left and right portions are missing. Is there a way to force the layout engine to draw off the screen so that all the pixels are there after the RotateTransform is applied?
The next thing I considered (but did not try) was to set the page SupportedOrientations to "PortraitOnly" and then use the accelerometer to generate my own "OnOrientationChanged" event and then selectively rotate the images by 90 degrees when the phone is tilted to landscape. I determined this is a bad idea because I would probably get this wrong in some subtle way resulting in confusion when rotation didn't work quite the same way in my app as in every other app. Is there a way to have the OnOrientationChanged event fire without also automatically updating the layout of the grid that contains my elements? Alternatively, is there some other hook that can be used to detect the phone orientation?
The last thing I tried was similar to the advice offered here: Windows Phone 7 applications - Orientation Change and here: http://blogs.msdn.com/b/ptorr/archive/2010/03/27/strategies-for-dealing-with-orientation-changes.aspx. This solution seems a bit brittle to me because it forces me to change the relative sizes of my grid rows and columns in the OnOrientationChanged event handler as well as in the xaml code. In the portrait mode, the first row is set to 5* and the 2nd row is set to 2*. Then when I switch to landscape, the rows need to be each set to 1* and the columns need to be set to 5* and 2* respectively. Alternatively, I could hard-code the size of the small images and set the rows and columns to Auto, but then I'm still stuck hard coding something. Since I've exhausted all of my other options, I think this is the solution that I'm stuck with.
Am I missing anything, or is this the way to do it?
You don't really have to hard code anything. What you need is clever designing. To develop good applications with multiple orientation support you should come up with a clever grid layout which lets you reposition objects the way you want without creating a mess.
For your situation consider the layout:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3.5*" />
<ColumnDefinition Width="1.5*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="2.5*"/>
<RowDefinition Height="1.5*" />
<RowDefinition Height="1.5*" />
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Margin="12,17,0,28" Grid.ColumnSpan="3" Grid.RowSpan="3"></StackPanel>
<Image Name="bigSmiley" Margin="8" Grid.RowSpan="3" Grid.ColumnSpan="3" Source="big.jpg"
Stretch="Fill"/>
<Image Name="smallSmiley1" Grid.Row="3" Source="smiley.jpg" Stretch="Fill" Margin="8"/>
<Image Name="smallSmiley2" Grid.Row="3" Grid.Column="1"
Source="smiley.jpg" Stretch="Fill" Margin="8"
Grid.ColumnSpan="2" />
<!--ContentPanel - place additional content here-->
</Grid>
And your orientation changed method should be like:
private void PhoneApplicationPage_OrientationChanged(object sender, OrientationChangedEventArgs e)
{
if ((e.Orientation & PageOrientation.Portrait) == PageOrientation.Portrait)
{
Grid.SetRow(bigSmiley, 0);
Grid.SetColumn(bigSmiley, 0);
Grid.SetColumnSpan(bigSmiley, 3);
Grid.SetRowSpan(bigSmiley, 3);
Grid.SetRow(smallSmiley1, 3);
Grid.SetColumn(smallSmiley1, 0);
Grid.SetColumnSpan(smallSmiley1, 1);
Grid.SetRowSpan(smallSmiley1, 1);
Grid.SetRow(smallSmiley2, 3);
Grid.SetColumn(smallSmiley2, 1);
Grid.SetColumnSpan(smallSmiley2, 2);
Grid.SetRowSpan(smallSmiley2, 1);
}
else
{
Grid.SetRow(bigSmiley, 0);
Grid.SetColumn(bigSmiley, 0);
Grid.SetColumnSpan(bigSmiley, 2);
Grid.SetRowSpan(bigSmiley, 4);
Grid.SetRow(smallSmiley1, 0);
Grid.SetColumn(smallSmiley1, 2);
Grid.SetColumnSpan(smallSmiley1, 1);
Grid.SetRowSpan(smallSmiley1, 2);
Grid.SetRow(smallSmiley2, 2);
Grid.SetColumn(smallSmiley2, 2);
Grid.SetColumnSpan(smallSmiley2, 1);
Grid.SetRowSpan(smallSmiley2, 2);
}
}
Result:
I hope that solves your issue. Remember, there's always a better design which makes your problems go away! :)

How to restore controls in their correct places same as earlier when we maximize a window?

I am creating a wpf application.It contains a ListBox in which there are 3 buttons that are to be put on extreme right hand side one after another .The problem is that when I maximize that window ,the buttons does not take their correct places as used to be in earlier window i.e. They should be on the extreme right hand side of the form one after another.But now they are shown in the middle of the ListBox..Please guys help me to solve this problem ASAP..
With no code to go on, I can only assume you're using absolute positioning. That would explain why it only works when your Window is a certain size. If you instead use relative positioning, you can achieve your goals.
There are many ways to do this, and you should read up on WPF layout if you're unfamiliar with it. Here is one example:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListBox/>
<StackPanel Grid.Column="1">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
</StackPanel>
</Grid>

Looking for explanation for WPF Grid ColumnSpan behavior

I asked a question at http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5c7f5cdf-4351-4969-990f-29ce9ec84b87/ , but still lack a good explanation for a strange behavior.
Running the following XAML shows that the TextBlock in column 0 is width greater than 100 even though the column is set to width 100. I think that the strangeness may have something to do with it being wrapped in a ScrollViewer, but I don't know why. If I set a MaxWidth on the columns, it works fine, but setting Width does not.
Why is the width of column 0 not being honored?
Why does the column sizing behave differently when you remove the scroll viewer?
I appreciate any explanation! This is a real puzzle to me.
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Width="300">
<ScrollViewer HorizontalScrollBarVisibility="Auto" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock x:Name="textBlock" Text="{Binding ElementName=textBlock, Path=ActualWidth}" />
<TextBlock Text="column 1" Grid.Column="1" />
<TextBlock Grid.Row="1" Grid.ColumnSpan="3" Text="text here that is wider than the first two columns combined" />
</Grid>
</ScrollViewer>
</Window>
This is a very good question and tests the limits of our intuition. It reveals the implementation details of the Grid's layout logic.
The width of 100 is not being honored because:
There is nothing in the third column that causes the grid to give it width.
The long text in the second row is wider than can fit in the first two columns.
When the width of the Grid is not constrained or set by its parent its layout logic evidently stretches the first column instead of the last column.
By putting a MaxWidth on the first column, you are constraining the Grid's layout logic, so it moves on to the second column and stretches it. You'll note it will be wider than 100 in that scenario.
However, as soon as the Grid's width is set to a specific value or is constrained by its parent (e.g. when no ScrollViewer in the Window), the Grid's width has a specific value, and the third column gets a width set even though it is empty. Now the Grid's auto-size code is deactivated, and it no longer stretches any of your columns to try to squeeze in that text. You can see this by putting a specific width on the Grid, even though it is still in the ScrollViewer.
Edit: Now that I read the answer of the MSDN support in your original thread, I believe it is correct, meaning this is probably the result of the implementation of the attached property and not the grid itself. However, the principle is the same, and hopefully my explanation is clear enough to make sense of the subtlety here.
Short Answer:
Its because of the combination of:
1. Presence of ScrollViewer which allows grid (if it wishes) to take any desired size.
2. The grid not having explicit width.
3. A column (Column 2) whose width has not been specified, which sets it to 1*, leaving its final size dependant on size of grid and other columns.
4. TextBlock which has colspan over three columns.
If you:
1. Remove the scrollviewer, the grid is allowed to grow only till the client area of window (which comes to be about 278 in your example), and the long textblock has to fit within this width otherwise its trimmed.
2. Set explicit width of Grid, which again trims textblock to fit.
3. Set explicit width of Column 2, which provides a fixed width to grid (100+100+width_of_col2), which again trims textblock to fit.
4. Remove the colspan, the columns which do not contain it and have fixed width defined, will take that width.
Here's what's happening:
This is crude and not exact explanation of the measure and arrange passes, however, it should give a fair idea.
To start with col0 is happy with 100, col1 with 100 and col2 with 0. Based on this grid's size would be 100+100+0=200. When Grid requests its children (textblocks) to be measured, it sees that first two textblocks fit within the width of their columns. However, the third textblock needs 288. Since, grid isn't having any width defined and its within a scrollviewer, it can increase its size if one of its child needs it. The Grid has now to increase its size from 200 to 288 (i.e. by 88). This means each column to which that textblock spans (all three of them) will expand by 88/3~=29 pixels. This makes col0=100+29=129, col1=100+29=129, col2=0+29.
Try this:
Include a rectangle, put it in col2 and set width of rectangle to 20.
This is what's happening:
To start with col0 and col1 are happy with 100 each as their individual textblocks need less than that. col2 is happy with 20 as rectangle in it needs that. Based on this grid's width would be 100+100+20=220. However, because of the columnspanning textblock the Grid has to increase its size from 220 to 288 (i.e. by 68). This means each column to which that textblock spans (all three of them) will expand by 68/3~=23 pixels. This makes col0=100+23=123, col1=100+23=123, col2=20+23=43.
HTH.
Here is another example that shows the problem using a Canvas instead of a ScrollViewer:
<Canvas>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" x:Name="textBlock1" Text="{Binding ElementName=textBlock1, Path=ActualWidth}"/>
<TextBlock Grid.Column="1" x:Name="textBlock2" Text="{Binding ElementName=textBlock2, Path=ActualWidth}"/>
<TextBlock Grid.Row="1" Grid.ColumnSpan="3" Width="300"/>
</Grid>
</Canvas>
This example shows that when given unlimited space, the first two columns are incorrectly expanded by 33%. I do not have working reference source to debug this right now because SP1 broke .NET4 reference source but frankly pinpointing this to the line in the source file is not going to help you so let's not go that route.
Instead, we'll agree that this is definitely a bug and we can prove that it's a bug by setting Grid.MaxWidth to progressively larger values and the widths of two columns remain both at 100 no matter how large it gets. But if you leave Grid.MaxWidth unset and place the Grid inside of a Canvas then the value during measure will be double.PositiveInfinity and this value with produce column widths of 133. As a result we can speculate that some how the special case of a size constraint of positive infinity is not handled correctly during the column sizing calculations.
Luckily, that same experiment provides a simple workaround: simply supply an absurdly large value for Grid.MaxWidth when the Grid is used inside another control that allows it unlimited space, such as a ScrollViewer or a Canvas. I recommend something like:
<Grid MaxWidth="1000000">
This approach avoids the bug by preventing the size constraint from having the probematic value of positive infinity, while practically achieving the same effect.
But this:
<Grid MaxWidth="{x:Static sys:Double.PositiveInfinity}">
will trigger the bug.
I reported this issue as a bug:
https://connect.microsoft.com/VisualStudio/feedback/details/665448/wpf-grids-columns-width-property-not-honored-when-columnspan-row-forces-grid-to-grow
Please vote it up at that link if you agree that it is a bug.

resolution issue in silverlight

HI, i need to have a resolution independant UI in silverlight application. Will it support implicitly or should it be taken care in code behind doing ScaleTransform ?
will it support multiple browsers as well ?
Thanks in advance.
You can use The ViewBox control in the Silverlight Toolkit to do the scale transforming. It will work on all supported browsers.
You can also set the UserControl width and height to Auto (or remove them) and then have your UI stretch (but not resize) to rules that you set up (typically with a Grid control).
Okay, I figured I will outline all the methods that you can make use of the implicit methods that Silverlight allow for specifying sizing.
If you define anything using the Stretch setting for VerticalAlignment option in a control:
<TextBox Grid.Column="0" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
The UIElement will stretch to take up all the space available to it in its parent control. Another setting such as this is to do something like defining a grid column width or row height like this:
<ColumnDefinition Width="*"/>
This will take up all the space available on the screen.
You can grow columns and rows of the grid in a ratio form:
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
This will grow the height of the first row by 3px for each 2px that the second one grows.
Then you can have options such as Auto
<ColumnDefinition Width="Auto"/>
This will grow the UIElement according to size requirements. If a child of the element requires more size, the element will take up more screen space.
And finally:
<TextBox Grid.Column="1" Grid.Row="0" Height="100" MinWidth="200" MaxWidth="400" x:Name="text"/>
These are fixed values and ensures that given any resolution that the element will not take up more than 400px in width but no less than 200px. It also indicates that the height of the element should always be 100px. This is useful for elements such as buttons etc. which you do not want to grow or shrink as the resolution changes.
Finally, you will probably want to wrap a ScrollViewer around the whole thing, just to ensure that elements off the screen can be scrolled to. This can happen when your view requires more space than available on the screen or have elements set to Auto.

Resources