How to control button layout in Xaml - wpf

Im having trouble controlling the exact layout of a button control with XAML.
It seems that whatever i do the button is of a minimum width.
I have a simple button with only a textblock inside the button. But the button has a lot of margin and padding that i cant seem to get rid of (i know of negative margins and padding).
The things i want to know is:
1. Why in the world was it designed this way.
2. what are the groundrules for controlling the exact layout of a button?
My code is as follows:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0"></StackPanel>
<Pivot Grid.Row="1">
<Pivot.Title>
<StackPanel Orientation="Horizontal" Margin="-15,-3,0,0" Background="red" Width="480">
<Button Background="Blue" x:Name="btnStudies" Click="btnMenuItem_Click" Width="20">
<TextBlock Text="Title" Foreground="White"></TextBlock>
</Button>
<Button Background="Green">
<TextBlock Text="Title" Foreground="White"></TextBlock>
</Button>
<Button Background="Blue" Click="btnMenuItem_Click">
<TextBlock Text="Title" Foreground="White"></TextBlock>
</Button>
<Button Background="Blue" Click="btnMenuItem_Click">
<TextBlock Text="Title" Foreground="White"></TextBlock>
</Button>
<Button Background="Blue" Click="btnMenuItem_Click">
<TextBlock Text="Title" Foreground="White"></TextBlock>
</Button>
</StackPanel>
</Pivot.Title>
</Pivot>
</Grid>
I want five buttons in a row but these are already too wide for the screen (windows phone). Changing the width doesnt seem to have any effect (why is it there).
The textBlock control within the button the button is as wide as the text on it, but i dont seem to have any control on the width of the button. In HTML you only have padding or margin when you define it but in xaml it just seems to be there and for me its unclear how to undo that.
*****EDIT*****
After reading Rachel's reply i decided to start from the ground up.
Using the code below i still have no control over how wide the button is because it uses a certain amount of padding that i cant seem to remove. The button has a width of about 110 when i define a width lower than that it doesnt change. Margins and paddings of 0 have no effect at all (dont want to use negative values just yet because that doesnt seem very intuitive). So the code below is very simple but still the button takes up an amount of space that i dont have any control over. I cant imagine a reason why it was designed this way.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400" />
</Grid.ColumnDefinitions>
<StackPanel Width="300" Background="Red" HorizontalAlignment="Left">
<Button Background="Blue" HorizontalAlignment="Left" Width="100" Margin="0" Padding="0">
<TextBlock Text="Title" Width="Auto" HorizontalAlignment="Left" />
</Button>
</StackPanel>
</Grid>

The type and size of the parent panel containing the control affects the size/layout of the child controls.
In your case, you have a Grid as your parent panel, and a Grid defaults to taking up all available space. In addition, children placed inside the grid default to taking up all available space as well unless you specify otherwise.
So your <Pivot> is being assigned a width equal to Grid.Width, and Pivot.Title sounds like it's being assigned a width equal to Pivot.Width, and StackPanel is being assigned a width equal to Pivot.Title.Width... you get the picture.
To specify that a control should not take up all available space, specify a HorizontalAlignment or VerticalAlignment property to tell it what side of the parent panel to dock the item on.
For example
<Pivot Grid.Row="1" HorizontalAlignment="Left">
or
<StackPanel OWidth="480" HorizontalAlignment="Left" ...>
If you're new to WPF's layout system, I would recommend reading through the codeproject article WPF Layouts: A Quick Visual Start to quickly learn what the main layout panels are for WPF.

Related

Having issues with the scroll bar in WPF

So im trying to get my scroll bar to A only show up as needed and B show up only around my description text
Right now the scroll view is going from the top of the window to the bottom
<Window x:Class="WpfApplication3.DataWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataWindow" Height="300" Width="300">
<Grid>
<Label x:Name="lblTitle" Content="Label" HorizontalAlignment="Left" Margin="96,25,0,0" VerticalAlignment="Top" Width="186"/>
<Label x:Name="lblPublishDate" Content="Label" HorizontalAlignment="Left" Margin="96,53,0,0" VerticalAlignment="Top" Width="186"/>
<Image x:Name="imgPic" HorizontalAlignment="Left" Height="81" Margin="10,10,0,0" VerticalAlignment="Top" Width="81"/>
<ScrollViewer>
<TextBlock x:Name="tbDesc" HorizontalAlignment="Left" Margin="10,96,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="167" Width="272" Text="TextBlock" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" />
</ScrollViewer>
</Grid>
A grid tries to let it's children take up all availble space.
Your ScrollViewer is one of the children, so it will fill all available space by default.
There are a number of ways around this.
You could use a different panel type, one that doesn't try to stretch it's children to fill all available space. Based on what you're doing with excessively large margins, a Canvas might be suitable.
I would suggest reading this for a quick understanding of WPF's available Layout Panels : WPF Layouts - A Visual Quick Start
Another alternative is to give your Grid some Row Definitions, and specify that the row containing the ScrollViewer should be of a fixed size, or should be sized so it fits whatever size the child object wants (Height="Auto")
Or you could give your ScrollViewer a fixed height, and set it's VerticalAlignment property so it gets docked to either the top or bottom of the Grid.
Personally I would recommend the first option - reviewing WPF's layout system and determining a more approrpiate panel type for your layout. And if the most appropriate panel type is a Grid, then I would highly recommend using the RowDefinitions and ColumnDefinitions to give your Grid some structure, rather than trying to use excessively large Margins to position your controls.
You're pretty close, the problem appears to be an issue of layout. Because the controls are arranged in the grid without row and column definitions the scrollviewer is attempting to resize to the full size of the grid while the textblock is adhereing to its fixed size and margin. Try the following starting point and see if it helps:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="150"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<Label x:Name="lblTitle" Content="Label" HorizontalAlignment="Left" Width="186"/>
<Label x:Name="lblPublishDate" Content="Label" HorizontalAlignment="Left" Width="186"/>
<Image x:Name="imgPic" HorizontalAlignment="Left" Height="81" Width="81"/>
</StackPanel>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<TextBlock x:Name="tbDesc" HorizontalAlignment="Left" TextWrapping="Wrap" Text="TextBlock"/>
</ScrollViewer>
</Grid>

Why ZIndex is not considered in arrange override of a stackpanel

Here is what I tried to to:
Added a stackpanel to my window (Orientation: Horizontal)
Added a set of buttons to it
Set the first button's ZIndex to be higher than the second one
Increased the width of the first button
What I expected:
I expected the first button to be on top of the second button (atlest overlay)
StackPanel's width should not change unless the width of the first button is no more sufficient
What is happening actually:
First button's width increases and the second button moves towards the right accordingly. They stay on the same plane
StackPanel's width increases with increase in the first button's width
Here is my question:
I know that stackpanel has not considered ZIndex while arranging the items within itself, BUT WHY?? Shouldn't it consider the ZIndex of its children while arranging them???
The Stackpanel 'stacks' its children based on their widths, i.e. if you increase the width of an item (or increase its margin), the stackpanel will simply expand to accomodate this. If you want to force items within a stackpanel to overlap, you will have to change their location after the layout has been computed. You can perform this using a RenderTransform. See the example below:
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Button Content="One" Canvas.ZIndex="10">
<Button.RenderTransform>
<TranslateTransform X="10"/>
</Button.RenderTransform>
</Button>
<Button Content="One"/>
<Button Content="One"/>
<Button Content="One"/>
</StackPanel>
And yes, the ZIndex is respected. This is an attached proepry of Canvas, however, it seems to be used by the rendering engine directly rather than by Canvas, hence it works in the above code.
I tried to find some relevant info about how to set the z index of wpf layout elements and panels. Using a Canvas comes with a different set of positioning issues which I simply hadn't the time to investigate. Here is a simple solution using the Panel.ZIndex property in xaml.
<Grid>
<Border Width="100" Height="100" Margin="0,0,50,50" Panel.ZIndex="1" Background="Navy" Opacity="0.3"
VerticalAlignment="Top" HorizontalAlignment="Left">
</Border>
<Border Width="100" Height="100" Margin="50,50,0,0" Background="Fuchsia" Opacity="0.3">
</Border>
The resulting two square border elements will overlap. One can use stackpanels instead of borders and use this logic to overlap anything easily.
Here is the same code adapted to the button problem:
<Grid>
<StackPanel Panel.ZIndex="10" Margin="20,20,0,0" HorizontalAlignment="Left">
<Button Content="One" Width="50" Height="40">
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="50,0,0,0" >
<Button Content="Two" Width="50" Height="40"/>
<Button Content="Three" Width="50" Height="40"/>
<Button Content="Four" Width="50" Height="40"/>
</StackPanel>
</Grid>

Why is my Popup showing opposite the Placement property on some machines?

I have a simple WPF Popup that I am showing when the user clicks a Button.
<Button
x:Name="aButton"
Content="Up/Down"
Width="75"
Height="30"
Click="aButton_Click"
/>
<Popup
PlacementTarget="{Binding ElementName=aButton}"
Placement="Right"
VerticalOffset="-31"
StaysOpen="False"
AllowsTransparency="True"
>
<StackPanel>
<Button Width="45" Height="45" Margin="2,0,2,2" Content="+"/>
<Button Width="45" Height="45" Margin="2,0,2,0" Content="-"/>
</StackPanel>
</Popup>
What is extremely weird ... is that this code works differently depending on what machine it runs on.
I run this code on my main desktop and everything works just fine ... and as it should. I run it on my PDC09 netbook ... and the Popup shows opposite (on the left instead of the right as I told it to with the Placement property).
Why is this? And what can I do about it?
I couldn't find anything via Google ... but a lucky search in the WPF forum, quickly found this post. Note to self: don't forget to search the WPF forums if Google can't find anything.
The answer is that my PDC09 netbook is a Tablet PC at heart, and apparently, Microsoft thought it was a good idea to show the Popup opposite to the Placement property on a Tablet PC that is configured for right-handed people ... such that the Popup doesn't appear under the user's hand.
The solution is to revert to custom Popup placement ... if you don't want this behavior.
I would love to hear about any other ways around this problem.
I fixed this issue by adding a border in the same grid col/row as the desired placement target. Then set this as the placement target instead. By binding the width of this border to the popup content it will adjust it's width automatically therefore the alignment (left or right) is irrelevant. If you want to still control alignment, you can do that by aligning the placement target border.
Hope that makes sense, if not, here is a quick example.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Popup x:Name="StartMenuPopup" Placement="Top" PlacementTarget="{Binding ElementName=PopupTarget}" >
<Border x:Name="PopupBorder">
</Border>
</Popup>
<Border x:Name="PopupTarget" Grid.Row="1" Width="{Binding ActualWidth, Mode=OneWay, ElementName=PopupBorder}"
BorderThickness="0" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<startmenu:TaskBar Grid.Row="1">
<startmenu:TaskBar.StartButton>
<startmenu:ToggleMenu Width="36" x:Name="StartButton"
ImageData="{Binding StartButtonImageData}"
AssociatedPopup="{Binding ElementName=StartMenuPopup}"
IsOpen="{Binding StartMenuOpen, Mode=TwoWay}"/>
</startmenu:TaskBar.StartButton>
</startmenu:TaskBar>
</Grid>
The popup PlacementTarget binds to the PopupTarget border, and the PopupTarget border width binds back to the PopupBorder element. This makes the PopupTarget border the same width as the popup therefore negating the alignment issue.

how to extend WPF GridRow Contents into next row without expanding the current row

I'm new to WPF and I'm trying to build a dropdown menu using the expander. Page layout is being handled with a Grid.
The extender sits inside the first row of the grid and I would I would like the contents of the expander to expand over top of the contents of everything below when it's clicked. Unfortunately, right now, the entire row is expanded to accommodate the height of the expanded control.
I tried playing around with the ZIndex of the Expander but it doesn't seem to have any effect. No matter what, the row always expands forcing everything else on the page to move.
<Expander FontSize="18" Name="moduleSelect" Width="100" Header=" Goto -> "
Background="#000033" MouseEnter="moduleSelect_MouseEnter"
MouseLeave="moduleSelect_MouseLeave" Foreground="White"
Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" HorizontalAlignment="Left">
<StackPanel>
<Button Name="btnTasks" Width="100" Foreground="White"
Background="#000033">Tasks</Button>
<Button Name="btnNotes" Width="100" Foreground="White"
Background="#000033">Notes</Button>
</StackPanel>
</Expander>
How can I make this expand 'above' the subsequent rows without distorting the grid?
What would happen if you set the Grid.RowSpan of the Expander to 2 (or how ever many rows you'd like it to span when expanded)?
So, for a two-row grid, you'd have something like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" /> <!--set this to the height of the expander's header area-->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<WhateverGoesInRow2 Grid.Row="1" />
<Expander FontSize="18" Name="moduleSelect" Width="100" Header=" Goto -> "
Background="#000033" MouseEnter="moduleSelect_MouseEnter"
MouseLeave="moduleSelect_MouseLeave" Foreground="White"
Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Left"
Grid.Row=0 Grid.RowSpan="2">
<StackPanel>
<Button Name="btnTasks" Width="100" Foreground="White" Background="#000033">Tasks</Button>
<Button Name="btnNotes" Width="100" Foreground="White" Background="#000033">Notes</Button>
</StackPanel>
</Expander>
</Grid>
You may need to adjust your RowDefinition section for your particular situation, but if I'm understanding your problem correctly, I think this will work.
You want something that pops up over the grid, not expands within the grid. A ComboBox, say, or - this being a menu, after all - a ContextMenu.
You could also build some combination of a ToggleButton and a Popup, but that's essentially the same thing as a ComboBox with IsEditable turned off.
The built-in drop-down control makes use of a Popup control in its default control template to do a similar thing.

Align items in a stack panel?

I was wondering if I can have 2 controls in a horizontal-oriented StackPanel so that the right item should be docked to the right side of the StackPanel.
I tried the following but it didn't work:
<StackPanel Orientation="Horizontal">
<TextBlock>Left</TextBlock>
<Button Width="30" HorizontalAlignment="Right">Right<Button>
</StackPanel>
In the snippet above I want the Button to be docked to the right side of the StackPanel.
Note: I need it to be done with StackPanel, not Grid etc.
You can achieve this with a DockPanel:
<DockPanel Width="300">
<TextBlock>Left</TextBlock>
<Button HorizontalAlignment="Right">Right</Button>
</DockPanel>
The difference is that a StackPanel will arrange child elements into single line (either vertical or horizontally) whereas a DockPanel defines an area where you can arrange child elements either horizontally or vertically, relative to each other (the Dock property changes the position of an element relative to other elements within the same container. Alignment properties, such as HorizontalAlignment, change the position of an element relative to its parent element).
Update
As pointed out in the comments you can also use the FlowDirection property of a StackPanel. See #D_Bester's answer.
Yo can set FlowDirection of Stack panel to RightToLeft, and then all items will be aligned to the right side.
For those who stumble upon this question, here's how to achieve this layout with a Grid:
<Grid>
<TextBlock Text="Server:"/>
<TextBlock Text="http://127.0.0.1" HorizontalAlignment="Right"/>
</Grid>
creates
Server: http://127.0.0.1
Could not get this working using a DockPanel quite the way I wanted and reversing the flow direction of a StackPanel is troublesome. Using a grid is not an option as items inside of it may be hidden at runtime and thus I do not know the total number of columns at design time. The best and simplest solution I could come up with is:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<!-- Right aligned controls go here -->
</StackPanel>
</Grid>
This will result in controls inside of the StackPanel being aligned to the right side of the available space regardless of the number of controls - both at design and runtime. Yay! :)
This works perfectly for me. Just put the button first since you're starting on the right. If FlowDirection becomes a problem just add a StackPanel around it and specify FlowDirection="LeftToRight" for that portion. Or simply specify FlowDirection="LeftToRight" for the relevant control.
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" FlowDirection="RightToLeft">
<Button Width="40" HorizontalAlignment="Right" Margin="3">Right</Button>
<TextBlock Margin="5">Left</TextBlock>
<StackPanel FlowDirection="LeftToRight">
<my:DatePicker Height="24" Name="DatePicker1" Width="113" xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit" />
</StackPanel>
<my:DatePicker FlowDirection="LeftToRight" Height="24" Name="DatePicker1" Width="113" xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit" />
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Left" />
<Button Width="30" Grid.Column="1" >Right</Button>
</Grid>
If you are having a problem like the one I had where labels were centered in my vertical stack panel, make sure you use full width controls. Delete the Width property, or put your button in a full-width container that allows internal alignment. WPF is all about using containers to control the layout.
<StackPanel Orientation="Vertical">
<TextBlock>Left</TextBlock>
<DockPanel>
<Button HorizontalAlignment="Right">Right</Button>
</DockPanel>
</StackPanel>
Vertical StackPanel with Left Label followed by Right Button
I hope this helps.
for windows 10
use relativePanel instead of stack panel, and use
relativepanel.alignrightwithpanel="true"
for the contained elements.
Maybe not what you want if you need to avoid hard-coding size values, but sometimes I use a "shim" (Separator) for this:
<Separator Width="42"></Separator>

Resources