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.
Related
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.
I have the following structure in my WPF application. (Using Prism and Regions)
Window---->
UserControl---->
DockPanel------>
Grid(2X2)------>Row0Col0--
Grid (2X1)------>
ItemsControl------>
UserControl(injected into ItemsControl with Prism)----->
DockPanel----->
DataGrid.(60 rows, 10 columns)
The behavior that I expect is that the DataGrid will size itself to fit the size of the grid cell and display both scrollbars because it is too big to fit. But it doesnt. It remains its maximum size and cuts out of the edges of the grid cell. All cells of both grids have no size specifications (Auto Sized). When I explicitly specify datagrid's height and width, I see the scrollbars, but of course I don't want to do that.
Please help!.
Thanks
I have saved the screenshots at the following link.
http://s1199.photobucket.com/albums/aa467/vikasgoyalgzs/
You say: "All cells of both grids have no size specifications (Auto Sized)" - this is where the problem is. When the grid cell is auto sized the grid gives the content in that cell as much space as it wants (doesn't matter if it fits in the window or not). To fix it you have to put your DataGrid into a star-sized cell.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0">
<!-- Content that will take as much space as it wants -->
</Border>
<Border Grid.Row="1">
<!-- Content that will take all the remaining space -->
</Border>
</Grid>
UPDATE: Based on the screenshots you provided...
First, get rid of the DockPanel in the top level control. DockPanel gives its child all the space it asks for. If it is not a "fill" child (LastChildFill="True"). Use grid instead of DockPanel (i.e. at the top level a grid with two rows - one auto-sized for the menu and the second star-size for the rest of the stuff, the in that star-size row put another grid for you items controls, etc.).
Remember, whenever you put the content either in an auto-size cell in a grid or in a DockPanel with dock type different than Fill, the content will take as much space as it required without showing a scroll bar (it will go beyond the window).
UPDATE 2: Looking at the new screenshots (see comments to this post)...
OK, I think I see the problem. The thing is that ItemsControl uses StackPanel to display its children, but StackPanel also gives its children all the space they want (your DataGrid thinks that it has enough space to render itself without scroll bars).
To fix that you need to put your items controls inside an ScrollViewer like this:
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ... />
</ScrollViewer>
I'm trying to do something which seems like it should be extremely simple and yet I can't see how. I have a very simple layout, a TextBox with an image next to it, similar to the way it might look adorned with an ErrorProvider in a WinForms application. The problem is, I want the image to be no higher than the TextBox it's next to. If I lay it out like this, say:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Row="0" Grid.Column="0" MinWidth="100"/>
<Image Grid.Row="0" Grid.Column="1" Source="error.png" />
</Grid>
the row will size to the height of the image if the image is taller than the TextBox. This also happens if I use a DockPanel or StackPanel.
The naive solution would be to bind the Height to the TextBox's ActualHeight. I'm sure this is wrong. But what's right?
Edit
Here's an example of what looks wrong to me: In both of these layouts (which are both horizontal StackPanels), the FontSize is the only variable:
You can see that the first TextBox is constrained to the height of the icon, and as a result has an unnecessary bottom padding under the text. And the icon next to the second is out of scale to the TextBox it's next to.
As it happens, I found a completely different (and much better) way to approach the problem - originally I was scaling my layout by changing the FontSize on the Window, but using a ScaleTransform is a whole lot easier and seems to work perfectly. But even so, it still seems odd to me that it's so hard to do this.
Name your TextBox, reference the TextBox from the Image as follows.
<TextBox Name="myTextBox"
Grid.Row="0" Grid.Column="0"
MinWidth="100"/>
<Image Grid.Row="0"
Grid.Column="1"
Source="error.png"
Height="{Binding ActualHeight, ElementName=myTextBox}"/>
You want a layout algorithm that measures the other elements with a height constraint equal to the desired height of a specific one. While several of the existing Panel implementations will reduce the available space for remaining elements based on the size used by previous ones, none of them will set the constraint the way you want. If you want the behavior you describe in a single layout pass, you will need to write your own layout algorithm.
For example, you can get close by overriding the behavior of StackPanel like this:
public class SizeToFirstChildStackPanel
: StackPanel
{
protected override Size MeasureOverride(Size constraint)
{
if (Children.Count > 0)
{
var firstChild = Children[0];
firstChild.Measure(constraint);
if (Orientation == Orientation.Horizontal)
{
constraint = new Size(
constraint.Width,
Math.Min(firstChild.DesiredSize.Height, constraint.Height));
}
else
{
constraint = new Size(
Math.Min(firstChild.DesiredSize.Width, constraint.Width),
constraint.Height);
}
}
return base.MeasureOverride(constraint);
}
}
This will constrain the height of all children of the StackPanel to the desired height of the first one (or width if the panel is oriented vertically).
It's still not ideal because the first child will get measured a second time by the base class MeasureOverride, and the second measure will have a constraint. This is extra work, and will cause odd behavior if the first child wants to get larger.
To do it right you would need to implement the entire MeasureOverride method method yourself. I'm not going to do that here because it would be a lot of code that isn't really relevant to the problem, and it depends on how exactly you want the layout to work. The important point is to measure the specific element first and use its DesiredSize to determine the availableSize when you call Measure on the others.
The UserControl I'm trying to work with is essentially laid out like so:
<Grid>
<Grid.RowDefinitions>
<Row Height="*"/>
<Row Height="*"/>
</Grid.RowDefinitions>
<wpftoolkit:DataGrid x:Name="PrimaryGrid"/> <!-- ~10 rows -->
<Border>
<Grid>
<Grid.RowDefinitions>
<Row Height="*"/>
<Row Height="*"/>
</Grid.RowDefinitions>
<wpftoolkit:DataGrid x:Name="SecondaryGrid"/> <!-- ~2 rows -->
<wpftoolkit:DataGrid x:Name="OtherGrid"/> <!-- ~50 rows -->
</Grid>
</Border>
</Grid>
This UserControl is then placed in my Window as the last member of a Grid, the only one with Height="*". I'm having problems with the way the DataGrids are sized.
If I set VerticalAlignment of the UserControl to Stretch in the Window, then PrimaryGrid gets 1/2 height of the UserControl, and each of the two inside the Border get 1/4. They are sized like this regardless of the number of rows each have, leaving OtherGrid with too little vertical space and the others with non-row whitespace inside the scrollview.
If I set VerticalAlignment to Top, the grids seem to size pretty well to their contents, except there is an inexplicable whitespace being left at the bottom of the UserControl. I used Snoop and the RowDefinition for the UserControl has the proper ActualHeight, but the UserControl only uses a portion of it - 80% or so.
I don't really mind whether I fix the Stretch case (How do I make the DataGrid not stretch larger than its number of rows?) or the Top case (How do I make the UserControl use all the space it has available to it?)
Summary: Use Stretch for the UserControl, but Auto (instead of *) for the row heights inside your UserControl.
Explanation: "Auto" means: as much space as needed (which is what you want), whereas "*" means: a proportional share of all available space (resulting in the 1/2, 1/4, 1/4-distribution).
Since you want the UserControl to use all available space, Stretch is the correct option (it means exactly that). Set one of the row heights inside the UserControl back to "*", if you want this row to take up the remaining available space.
This is a common problem where what you really want is 2 completely different layout behaviors: Auto sizing when there's room for all three, * sizing when there isn't. Some quick fixes you can try out with limitations:
Auto sizing (as already mentioned)
DockPanel with each set to Dock=Top - this will have a similar effect to VerticalAlignment=Top but the last DataGrid (only 1) will stretch out to fill the remaining space. Also bad if the first or second take up more space than exists because they'll push the others out.
Set MinHeight/MaxHeight in combination with one of the other 2 changes on your DataGrids to keep them from getting out of control. This gives up some of the auto-layout flexibility in exchange for making sure everything shows up.
Beyond those you can try something more complex like creating a custom Panel (or find one that someone else made already), or creating a MultiValueConverter that can calculate appropriate Height (or MinHeight, MaxHeight) settings for each DG or Row based on the height of the UC and each of the DGs.
Well, here's what I ended up with. It does what I want from a layout point of view, mostly. A bit more code behind than I'd like, but oh well.
In my DataContextChanged event handler:
//for each grid
_reportObserver = new PropertyObserver<ItemCollection>(PrimaryGrid.Items)
.RegisterHandler(c => c.Count, c => UpdateMaxHeight(PrimaryGrid));
UpdateMaxHeight(PrimaryGrid);
PropertyObserver is from http://joshsmithonwpf.wordpress.com/2009/07/11/one-way-to-avoid-messy-propertychanged-event-handling/
//Lots of ugly hard-coding
private void UpdateMaxHeight(DataGrid grid)
{
double header_height = grid.ColumnHeaderHeight;
if (double.IsNaN(header_height))
header_height = 22;
double margin_height = grid.Margin.Bottom + grid.Margin.Top;
grid.MaxHeight = header_height + margin_height + grid.Items.Count * (grid.RowHeight+2);
UpdateLayout(); //this is key for changes to number of items at runtime
}
Even after setting the DataGrid's MaxHeight, things were still ugly, so I had to set the max height on the RowDefinition's too. But that still wasn't right, causing the margin_height addition above.
<RowDefinition Height="*" MaxHeight="{Binding ElementName=PrimaryGrid, Path=MaxHeight}"/>
At some point, I'll take into account my optionally visible row details in my ugly max height code.
As far as Top vs Stretch, I ended up for other reasons having the usercontrol in a ListView. Everything sizes nicely now.
Thanks again for looking at my problem.
Just wanted to follow up on this problem. Over time, the solution I provided above just did not meet users expectations. I've now changed to a scheme like this:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid>
<!-- row definitions -->
<KentBoogart's Resizer ResizeDirection="South">
<DataGrid/>
</kb:Resizer>
<kb:Resizer ResizeDirection="South">
<DataGrid/>
</kb:Resizer>
</Grid>
</ScrollViewer>
In another place where I've used this idiom, I've set the Resizer to have a MaxHeight bound to the ScrollViewer's ActualHeight to keep it from going out of control. This design can be a little confusing with the overall scrollbar, plus scrollbars in the DataGrid, but with good borders and margins, it's not too bad.
So I'm coming at WPF from a HTML perspective.
I just want to put a TextBox on my Window like this:
<Grid>
<TextBox Name="theName" />
</Grid>
Turns out that the TextBox is then HUGE, covers the whole window. (!)
Ok, that's not what I want, but I don't want to define the EXACT size either since I know Height and Width should be flexible, so I try:
<TextBox Name="theName" Width="Auto" Height="Auto"/>
Same thing. So I try:
<TextBox Name="theName"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
Same thing. So I just hard code the sizes:
<TextBox Name="theName" Width="100" Height="20"/>
Which I know is not a good programming practice in WPF.
So, what how do you tell TextBox to "display default sizes for the font size being used"?
You can take Bryan's example even a bit further. By specifying a specific alignment that isn't stretch and further constrain the TextBox so that it won't expand beyond a certain size. eg:
<Grid x:Name="LayoutRoot">
<TextBox HorizontalAlignment="Left" VerticalAlignment="Top" Text="TextBox" TextWrapping="Wrap"
MinWidth="15" MinHeight="20" MaxWidth="500" MaxHeight="50"/>
</Grid>
You can take it even further by setting up rows/columns inside the Grid and constraining them in various fashions. As you're coming from an HTML background, think of it like using a table to control layout. Remember that you can also nest other container objects (i.e. StackPanels, WrapPanels, other Grids, etc...).
The challenge with XAML and the WPF/Silverlight controls is that they a very flexible, so you've got to get a handle on all the options and how they affect layout.
Good luck. I'm going through this exact same thing now.
Use a different container.
The Grid always streches its child controls to fill the grid cell.
You could use e.g. a stackpanel which only streches its controls in one direction.
In addition to using a different panel as Stefan mentioned you could just give the TextBox an alignment that isn't Stretch. e.g.
<TextBox Name="theName" HorizontalAlignment="Left" VerticalAlignment="Top"/>
The sizes in WPF aren't pixels, they are "device independent pixels" that are 1/96 of an inch - so in today's normal DPI setup they map 1:1 to pixels.
But, if you run the program in high DPI mode the TextBox will grow with the DPI (and the font).
So setting an hard-coded size isn't that bad.
Other than that you can only use HorizontalAlignment and VerticalAlignment that are not "Stretch", this will size the TextBox to content - but then an empty TextBox will be tiny.
You can set VerticalAlignment to "Center", "Top" or "Bottom" to get automatic height of about one line (maybe backed up by a MinHeight setting to avoid problems really tiny fonts) and then set the Width so the TextBox width does not change as the user types into it.