Non-regular Grid-like structure with flexible sizing of items - wpf

I have a Template that is a List<List<Location>>. Each List<Location> is a row and each Location is a column. Each row is centered, with the Locations at a fixed width (i.e., the locations themselves don't stretch). Since the rows have different numbers of columns (not to mention the list having a variable number of rows), This is not a simple actual "grid".
The empty spots are where some cells have been "turned off". You'll also notice that cells can be portrait or landscape.
Currently the style of the cells has them at a fixed size; I'm also including the rows/columns template here:
<Style TargetType="ContentControl" x:Key="imageLocationStyle">
<!--Default styling (Portrait, Active)-->
<Setter Property="Height" Value="{StaticResource fullLong}"/>
<Setter Property="Width" Value="{StaticResource fullShort}"/>
<Setter Property="BorderBrush" Value="Black"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Landscape}" Value="True">
<Setter Property="Height" Value="{StaticResource fullShort}"/>
<Setter Property="Width" Value="{StaticResource fullLong}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Active}" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
<DataTemplate DataType="{x:Type data:ImageTemplate}">
<!--Rows-->
<ItemsControl ItemsSource="{Binding ImageLocations}" VerticalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--Columns-->
<ItemsControl ItemsSource="{Binding}" HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!--Single cell-->
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--Each presenter of a Template will provide its own
DataTemplate for TemplateImageLocation, which will
determine how each cell is rendered.-->
<ContentControl Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
I want the cells (and thus the template) to be of variable, flexible size, relative to the size of the Template's container element. The example above is cut from a UniformGrid that contains Buttons, which as you can see by the selection, fill their container. I've set the xaml properties fullLong and fullShort to a set number which is untenable, not only because any other time I want to use a template in another context I'd have to define a new style with another relatively arbitrary fixed number, but because screen sizes are different not to mention template sizes are different.
I've tried using a UniformGrid for the template (1 column) and each row (1 row), but then all rows are the same width, which is not what I want.
I've thought about padding all the rows with the max number of cells (happens to be 7), but as far as I can imagine, that wouldn't account for the difference between portrait and landscape. A row with 7 portrait cells should take up less horizontal space than a row with 7 landscape cells.
I believe if I simply had a way to determine the long and short dimensions as a percentage of their container, everything else would work fine. If I wanted a template to stretch to fill all or most available space depending on the size of the template itself (number of cells), I'd need to figure out the length of the longest row and/or column to determine what the percentage should even be.
How can this be done? Do I need to do all the calculations in a viewmodel and bind styles to that? How would I put all of it together?

This is actually an interesting problem. I think using nested ItemsControls was the right approach- it was my first thought before I saw your code- but I think your requirements are a bit too complicated for that. You're going to need to go custom; a custom control.
It's a bit of work, but I think it's the "right" way to do things, plus it's good experience in my opinion.
Base Type
I thought maybe a custom panel would be the right fit. Panel is the base class for "containers" which "position and arrange child objects". And that is exactly what you need, a custom arrangement of child objects. Implementing a custom Panel also gives you access everything you need, specifically the MeasureOverride method, which gives you availableSize, telling you how much space you have to work with. It also lets you dictate a size for each child element, which you can calculate based off availableSize and the number of children.
The problem is Panels generally support adding any kind of visual content, and the content you want to display is restricted to a specific data structure. I also thought of a custom ItemsControl, but we get the same problem. ItemsControls are designed to be independent of the ItemsPanel that they use.
For this reason, I think you should just inherit from FrameworkElement. It's one level down from Panel and still includes MeasureOverride.
Data Source Property
For your data, you need to end up with an IList<IList<Location>>. I recommend using IList<T> over List<T> so as not to restrict the input type unnecessarily. This will allow for List<T>, but also other collections like ObservableCollection<T>.
Since you will only be expecting input of that type, there's no real need to implement a flexible input property like ItemsControl.ItemsSource. You should, however, do the following:
Include a PropertyChangedCallback so you will be notified when the data source changes. This way you know to invalidate the current layout of the control.
In the PropertyChangedCallback, check if the IList<T> provided also implements INotifyCollectionChanged (such as ObservableCollection<T>). If so, attach a handler to the CollectionChanged for the same reason as above. Make sure to remove the existing handler if the data source property is given a new value.
Item Elements
Each Location needs a visual item to represent it. I would recommend sticking with ContentControl (this is what the base ItemsControl uses). You would create the ContentControl in code-behind whenever a new item is added, and dispose of the control if an item is removed.
You can even add an ItemTemplate property to the class to allow for a dynamic DataTemplate- or you build the template in code-behind if you want to restrict it.
You would keep track of which Location belongs to which ContentControl using a Dictionary<Location, ContentControl>. If you need two-way lookup, you can have a second Dictionary<ContentControl, Location> or just use a single List<(Location, ContentControl)>.
You will need to override GetVisualChild and VisualChildrenCount as shown in the linked documentation. You will add/remove the ContentControls to/from the custom control by calling AddVisualChild and RemoveVisualChild (an example can be seen in the linked documentation).
Layout Methods
The full rundown on the WPF layout system can be found here: MSDN - Layout
Now we get to the important bit, actually laying out the Location items. When WPF is ready to show your control, or when the available space changes, it will call your MeasureOverride and ArrangeOverride methods.
In MeasureOverride, you'll need to do the following:
Find the longest row and the longest column to determine "base size"
You'll need to loop through each item for this. We're look for longest as in visual size, not number of items.
At this stage, we don't know the scale of the items yet, so we measure in terms of aspect ratio. The items in your question appear to be 4:3 when landscape and 3:4 when portrait, so I'll use that. This means when calculating the length of a row, you add a 4 for every landscape item, and a 3 for every portrait item. For columns it's the reverse, since we're measuring height instead of width.
Create a Size variable where Width is the the length of the longest row and Height is the length of the longest column. This will be called baseSize- its the size of the control before any scaling.
Compare baseSize with availableSize to determine the scaling factor
You want to get the ratio of the available width to your base width, and the availalbe height to your base height. This gets you your scaling factor. Since you want to scale uniformly and never exceed availableSize, you take whichever of these ratios is smallest.
//I hope this math is right, it's getting late
double scale = Math.Min(availableSize.Width / baseSize.Width, availableSize.Height / baseSize.Height);
Calculate the size for a scaled item
Since all items are the same size, there's no scene recalculating the size for every one. Do it once to save processing power.
//This will probably be some private constant somewhere in the class
Size baseLandscape = new Size(4, 3);
Size basePortrat = new Size(baseLandscape.Height, baseLandscape.Width);
//Scaling the items to available space
Size scaledLandscape = new Size(baseLandscape.Width * scale, baseLandscape.Height * scale);
Size scaledPortrait = new Size(basePortrat.Width * scale, basePortrat.Height * scale);
Call Measure for each child item
Loop through the Dictionary or List from the Item Elements section again. Take each ContentControl and call Measure. Pass scaledLandscape if the item is to be displayed landscape, or scaledPortrait if it is to be displayed portrait. This tells each item how big it is allowed to be.
Return the desired size of the overall control
The last thing to do is to let WPF know how big our control wants to be. This is easy enough to get. It's just out baseSize from earilier, multiplied by our scale:
return new Size(baseSize.Width * scale, baseSize.Height * scale);
Next is ArrangeOverride, where you actually position your newly-measured items internally. In this method, you'll need to loop through all your items one final time, in row-then-column order. The size of each item will be new be available via ContentControl.DesiredSize.
Declare a double YOffset = 0, you'll need it later.
For each row:
Take the height if the tallest item, that will be the row's height.
Add up the width of every item, that will be the row's width.
Take finalSize.Width and subtract the width of the row. This gives you the amount of extra space. Since you want to center reach row, take this number and divide it by 2. We'll call this final number XOffset.
Loop through each ContentControl in the row and call Arrange. Pass it a Rect with X = XOffset, Y = YOffset, and Height and Width equal the ContentControl's DesiredSize. After each item, increment XOffset by the width of that item.
This row is done. Increment YOffset by the height of the row (from step 1) and move to the next row.
Conclusion
That's most of what you'll need. I've written more than I expected, but I like these kinds of challenges, so it was fun. I hope you give it a try instead of just using my second answer. Feel free to ask questions.

As an alternative to my complicated, "right way" answer (which I still hope you read). You might be able to get by by just wrapping your top ItemsControl in a ViewBox. You can use this control to visually scale the contents uniformly. I'm not sure how clean it will look, but it is definitly simpler than doing the math to calculate the correct size of each item.

Related

Get padding caused by stretch value Uniform

A WPF UI element has defined a width and height, the background is set to an image brush and pointed the image source to some random images on run-time. The stretch property is not set, hence I assume it is the default value "Fill" that comes into play. Since control size and image size are different, in run-time, the background image brush is filled with some aspect ratio, i.e with a padding (visually) inside the control. Is there a way to get this padded values?
Sample code :
<Grid Width="2000" Height="2000">
<ListBox>
<ListBox.Background>
<ImageBrush ImageSource="{Binding Source={x:Static local:MyModel.Instance},Path=ImageSource,Converter={StaticResource pathImageConverter}}" />
</ListBox.Background>
</ListBox>
</Grid>
Instead of image brush if I use a color, it fills the entire area.But if I use an image say 5000 * 4000 dimension, it stretches somewhat to fill inside the space (without skewing and cropping). That is not the real padding, visually we feel there is a padding from the list box boundaries and image boundaries. Let me try to get a snapshot of this.
There are multiple tools to get those values, and where they are assigned when running the application:
XAMLSpy
Snoop
And probably more, but these are the ones I use when encountering a problem like yours.
There is no API to get those values, I'm afraid you need to do the calculations yourself. Let me elaborate...
As indicated in the comments, the ImageBrush is not causing your padding. The brush will simply paint the area it gets. Of course, depending on the mode of the ImageBrush, it won't paint the entire area, but only a portion. Changing the brush to a SolidColorBrush will show you the entire area the brush will get. That should be identical to the area the image brush will fill, if it's put in... well, Fill mode.
However, I highly doubt it's the brush, but rather the ControlTemplate of the ListBox. If you're running in the Aero theme, by default, the template starts with a Border, which has its Background property template binded back to the ListBox, and it does the same with the BorderThickness (and to make things worse, it also has a fixed padding of 1, however, the background is drawn on top of that).
So to calculate the "padding" of the brush, you need to do the same as Border does: BorderThickness + Padding.
However, if you're running in a different theme, or if your controls have been restyled, then your control templates might be different, and the calculation might be different...

Creating a horizontal separator in xamDataGrid

I want some way of indicating a separation or line between two records whose dataitems are known, in a XamDataGrid. Currently I'm doing this by adding a background brush to the rows that contains a gradient (the top row has solid red near the bottom, the bottom row has solid red near the top), resulting in the appearance of a nice-looking red line between the two rows. A converter checks the dataitems against fields containing the special dataitems, and if they match, it applies the brush. However, now I need to add actual backgrounds to the rows.
So is there a way to apply two LinearGradientBrushes to the same row? Alternatively, I tried adding the StopCollections for the two border gradients to the normal background brushes, but this means for every normal background, there's 3 possible brushes (simple, red at top, red at bottom), which quickly adds up when you have 12 possible backgrounds.
On a different tangent, is it maybe possible to add a line or show a separator in a xamDataGrid in any other way than this background hack? Maybe a border hack or an actual way to insert visual elements into the grid at a location specified by row indexes?
You could add a style for the DataRecordCellArea to show the border below the cells. For example:
<Style TargetType="{x:Type igDP:DataRecordCellArea}">
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="0,0,0,2"/>
</Style>
Note that you would still need to add the logic that you have to use this conditionally.

Strategy for implementing a complex curve editor with XAML/WPF

I want to implement a rather complex CurveEditor that has to support the usual requirements like:
freely scalable and moveable axis
different interpolation types per curve point (Linear, Cubic, Spline)
Tangents (joined and broken)
Selecting one or several points to edit (move, scale, delete) via Fence or Click
Only show handles and highlights for selected curve points
I don't want to manipulate actual WPF curves but an existing model with key/value/tangents sets and sample the precise shape of the curve from our implementation.
I already gathered some experience on implementing custom UserControls and Templates. But I want to make sure, I don't miss any apparent solution. I planned to have this general XAML-tree:
CurveEditor - Window holding all content
MainThumb : Enable dragging and scaling the editor range
XAxis : UserControl rending some scale on the left side
YAxis : UserControl rending some scale on the bottom
Curves : Canvas holding the curves
Curve : UserControl for a single curve
CurveName - Label of the curve
CurveLine - DrawingVisual that will render the actual curve by sampling the internal implementation of the spline function.
CurveEditPoints - Canvas that holds all edit points
CurveEditPoint - UserControl for a single edit point
LeftTangent - UserControl for the left tangent handle
LeftTangentThumb - For modifying the handle
RightTangent - UserControl for the right tangent handle
RightTangentThumb - For modifying the handle
CurvePointCenter - Visualisation of the actual point, select state and interpolation type.
CurvePointThumb - Thumb to select and drag point around
I know, this is quite a complex question and I am not asking for an actual implementation. I am interested in the following questions:
Can you recommend any tutorials or books that might help me (I already got Illustrated WPF, WPF Control Development Unleashed, and a couple of other)
Should minor elements like the Tangents be individual UserControls?
What container is best suited for hosting the individual "Curves", "EditPoints" and "Tangents". Right now, I use Canvas and Canvas.SetLeft/SetTop to position the children, but that feels "strange".
Should I use "Shapes" like Path or DrawingVisual-Classes to implement actual representation. Path is straight forward, but I am concerned about performance with hundreds of CurvePoints.
Should I use Transforms to rotate the tangents or is just fine to do some triangulation math in the code behind files?
Does the structure roughly make sense, or do you suggest a completely different approach?
you seem to have the right tools at hand, WPF Unleashed is excellent, but I guess you have that one already.
make individual UserControls in one of these cases:
you are using the same xaml all over the place (DRY)
you xaml file gets too big (get some components out)
you need to inherit from some class
this depends on how much codebehind you want.
you can, as you suggested in your comment, use an ItemsControl as a container for wherever you need selection between multiple items. so this could also be done on the level of Curves, not just on the level of points on the curve. Depending on how you want to handle drawing of the actual lines and curves you can even have an ItemsControl for those. (on a side note: you will not have virtualization out of the box though, as your items won't have a constant height)
Path is OK with hundreds of CurvePoints. If you have 10.000, I'd say you could get problems.
can't imagine how a transform should be used here, maybe inside an Adorner.
your structure looks good. you will be able to implement all of this. I will suggest though how I would do it:
first of all use MVVM.
CurveEditor
ListBox(Panel=Canvas)(ItemsSource=Curves)(ItemTemplate=CurveControl)
CurveControl
Canvas(Background=Transparent) <= I'm not sure if standard is white, but you don't want to overlap other Curves...
CurveName
ListBox(Panel=Canvas(Background=Transparent))(ItemsSource=CurveParts)
ListBox(Panel=Canvas(Background=Transparent))(ItemsSource=CurvePoints)(ItemTemplate=>EditPointControl)
EditPointControl
Canvas
Thumb(Template = Ellipse) (Name=CenterHandle) (with some Visualstates for Selection and hiding of Tangents)
Thumb(Template = Ellipse) (Name=LeftHandle)
Thumb(Template = Ellipse) (Name=RightHandle)
Line (Binding X/Y to Centerpoint and LeftHandlePoint)
Line (Binding X/Y to Centerpoint and RightHandlePoint)
I have stated to set ItemTemplate for the ListBox. You can however style the listbox however you want (I think the standard style includes a border, you might want to remove that or set bordersize=0) and set instead of ItemTemplate the ItemContainerStyle and bind to IsSelected so you have control over IsSelected from your ViewModel (look here for what I mean).
So the viewmodel looks like this:
- CurveEditorViewModel
- ObservableCollection<CurveViewModel> Curves
- CurveViewModel
- string Label
- (Point LabelPlacement)
- bool IsSelected
- ObservableCollection<CurvePointViewModel> CurvePoints
- ObservableCollection<CurvePartViewModel> CurveParts
- CurvePointViewModel
- Point Position
- bool IsSelected
- Point LeftHandle
- Point RightHandle
- CurvePartViewModel
- CurvePointViewModel StartPoint
- CurvePointViewModel EndPoint
- Path CurvePath
in here you can subscribe to CurvePointViewModel's PropertyChanged and recalculate the Path you're exposing.
I'd probably improve on it as I go but that'd be my first guess.
There are some details you might want to watch out for. eg: the style for the thumbs might be a visible circle in the middle and an invisible bigger one around that with background=transparent. that way you can have your visible circle small, but have the user use the tumb in an area around the small circle.
EDIT:
here is an Example for the Thumb:
<Thumb Width="8" Height="8" Cursor="Hand" Margin="-4">
<Thumb.Template>
<ControlTemplate TargetType="Thumb">
<Grid>
<Ellipse Fill="Transparent" Margin="-6"/>
<Ellipse Stroke="Red" StrokeThickness="2"/>
</Grid>
</ControlTemplate>
</Thumb.Template>
</Thumb>
as you want to position this at a specific point on a canvas setting the Margin to minus half the width and height will place the center of the circle on that point. Furthermore, having that inner ellipse with a transparent fill and Margin of -6 you will get a 6px bigger radius around the inner (smaller) circle where you can drag the thumb.

How can I line up WPF items in a Horizontal WrapPanel so they line up based on an arbitrary vertical point in each item?

I'm trying to create a View in WPF and having a hard time figuring out how to set it up. Here's what I'm trying to build:
My ViewModel exposes an IEnumerable property called Items
Each item is an event on a timeline, and each one implements ITimelineItem
The ViewModel for each item has it's own DataTemplate to to display it
I want to display all the items on the timeline connected by a line. I'm thinking a WrapPanel inside of a ListView would work well for this.
However, the height of each item will vary depending on the information it displays. Some items will have graphic objects right on the line (like a circle or a diamond, or whatever), and some have annotations above or below the line.
So it seems complicated to me. It seems that each item on the timeline has to render its own segment of the line. That's fine. But the distance between the top of the item to the line (and the bottom of the item to the line) could vary. If one item has the line 50 px down from the top and the next item has the line 100 px down from the top, then the first item needs 50 px of padding so that the line segments add up.
I think I could solve that problem, however, we only need to add padding if these two items are on the same line in the WrapPanel! Let's say there are 5 items and only room on the screen for 3 across... the WrapPanel will put the other two on the next line. That's ok, but that means only the first 3 need to pad together, and the last 2 need to pad together.
This is what's giving me a headache. Is there another approach I could look at?
EDIT: I'm leaning towards replacing the WrapPanel with a custom panel that inherits from Canvas and overrides the MeasureOverride and ArrangeOverride methods.
The ideal solution to this would probably be using Adorners. On the AdornerLayer you would have a custom Adorner for each item on the Panel (you define it in the DataTemplate). You will be able to retreive dimensions an positions of each item (Boundingbox of the Adorner). Also on the AdornerLayer you would then draw the lines between these boundingboxes.
Using this solution you can layout your items any way you want (using any Panel you want) and the items will still be connected with lines.
A long time ago I used this approach for a graph visualizer. The nodes where arbitary UIElements that could be layouted in any way. The items where connected with lines.
To make all items align perfectly you could use a Canvas as the root Panel in you ItemTemplate. The Canvas can be 0x0 units big. You would then arrange your elements around that Canvas. The Canvas becomes your point of reference. Everything on top of the line will get negative Canvas.Top values.
Another approach would be to use negative margins that are bound to the height of the top and bottom controls that surrond the line. Use a IValueConverter (Height*-1) to invert the Height.
This won't solve your problem, but I bet it will simplify it:
Try defining a DataTemplate that looks something like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" SharedSizeGroup="Top"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto" SharedSizeGroup="Bottom"/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Content="{Binding TopAnnotations}/>
<Rectangle Fill="Black" Grid.Row="1" Margin="0,4,0,4"/>
<ContentControl Grid.Row="1" Height="10" Content="{Binding GraphicObject}"/>
<ContentControl Grid.Row="2" Content="{Binding BottomAnnotations}"/>
</Grid>
Now create an ItemsControl whose ItemsPanelTemplate is a horizontal WrapPanel with the Grid.IsSharedSizeScope attached property set to True. You will of course also need to define data templates for whatever the annotation and graphic object properties contain.
You still have the problem that when the items in the WrapPanel wrap, the objects above and below the timeline have the same padding irrespective of how much they actually need to fit on their current row of the WrapPanel. So to get the effect that I think you're looking for, you still have to monkey around with measure and arrange - basically, instead of creating a single WrapPanel, you'll create something that puts these items into a StackPanel on each "row", each of which is its own shared-size scope. But actually measuring and arranging the heights of the timeline items is not something you need to worry about. You only need their widths (so that your logic can determine when to wrap). The Grid will take care of calculating the heights for you.

How to get controls in WPF to fill available space?

Some WPF controls (like the Button) seem to happily consume all the available space in its' container if you don't specify the height it is to have.
And some, like the ones I need to use right now, the (multiline) TextBox and the ListBox seem more worried about just taking the space necessary to fit their contents, and no more.
If you put these guys in a cell in a UniformGrid, they will expand to fit the available space. However, UniformGrid instances are not right for all situations. What if you have a grid with some rows set to a * height to divide the height between itself and other * rows? What if you have a StackPanel and you have a Label, a List and a Button, how can you get the list to take up all the space not eaten by the label and the button?
I would think this would really be a basic layout requirement, but I can't figure out how to get them to fill the space that they could (putting them in a DockPanel and setting it to fill also doesn't work, it seems, since the DockPanel only takes up the space needed by its' subcontrols).
A resizable GUI would be quite horrible if you had to play with Height, Width, MinHeight, MinWidth etc.
Can you bind your Height and Width properties to the grid cell you occupy? Or is there another way to do this?
There are also some properties you can set to force a control to fill its available space when it would otherwise not do so. For example, you can say:
HorizontalContentAlignment="Stretch"
... to force the contents of a control to stretch horizontally. Or you can say:
HorizontalAlignment="Stretch"
... to force the control itself to stretch horizontally to fill its parent.
Each control deriving from Panel implements distinct layout logic performed in Measure() and Arrange():
Measure() determines the size of the panel and each of its children
Arrange() determines the rectangle where each control renders
The last child of the DockPanel fills the remaining space. You can disable this behavior by setting the LastChild property to false.
The StackPanel asks each child for its desired size and then stacks them. The stack panel calls Measure() on each child, with an available size of Infinity and then uses the child's desired size.
A Grid occupies all available space, however, it will set each child to their desired size and then center them in the cell.
You can implement your own layout logic by deriving from Panel and then overriding MeasureOverride() and ArrangeOverride().
See this article for a simple example.
Well, I figured it out myself, right after posting, which is the most embarassing way. :)
It seems every member of a StackPanel will simply fill its minimum requested size.
In the DockPanel, I had docked things in the wrong order. If the TextBox or ListBox is the only docked item without an alignment, or if they are the last added, they WILL fill the remaining space as wanted.
I would love to see a more elegant method of handling this, but it will do.
Use the HorizontalAlignment and VerticalAlignment layout properties. They control how an element uses the space it has inside its parent when more room is available than it required by the element.
The width of a StackPanel, for example, will be as wide as the widest element it contains. So, all narrower elements have a bit of excess space. The alignment properties control what the child element does with the extra space.
The default value for both properties is Stretch, so the child element is stretched to fill all available space. Additional options include Left, Center and Right for HorizontalAlignment and Top, Center and Bottom for VerticalAlignment.
Use SizeChanged="OnSizeChanged" in your xaml and the set the sizes you want in the code behind.
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
TheScrollViewer.Height = MainWin.Height - 100;
}
Long term it will be better for you.
When your manager comes along and asks "make that a bit bigger" you won't to spend the afternoon messing about with layout controls trying to get it to work. Also you won't have to explain WHY you spent the afternoon trying to make it work.

Resources