ColumnDefinition doesn't expand until resize - wpf

I have a grid where one columnwidth is defined as *.
The other columns are defined as Auto.
The column with the *-definition contains a usercontrol derived from Panel that also implements IScrollInfo.
During this control's MeasureOverride visibility is set to visible on a RepeatButton in another column (the RepeatButton's visibility is otherwise set to collapsed).
This does not cause the column to expand. This will only occur when I resize my window.
A simplified example:
<DockPanel LastChildFill="True">
<Grid DockPanel.Dock="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="TabItemColumn"/>
<ColumnDefinition x:Name="ScrollRightColumn" Width="Auto"/>
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ScrollViewer"
Grid.Column="0"
Margin="-1,0,0,0"
Height="32"
CanContentScroll="True"
VerticalScrollBarVisibility="Hidden"
HorizontalScrollBarVisibility="Hidden"
HorizontalAlignment="Left">
<local:TabPanel
x:Name="tabPanel"
HorizontalAlignment="Left"
IsItemsHost="True" />
</ScrollViewer>
<RepeatButton Style="{StaticResource RepeatButtonScrollRight}"
Visibility="{Binding ElementName=tabPanel, Path=CanScrollRight, Converter={StaticResource _localBooleanConverter}}"
Grid.Column="1">
</RepeatButton>
The visibility of the RepeatButton is triggered correctly, and as far as I can tell it is actually rendered, but the ActualWidth of the containing column is zero until resize.
Any ideas?

You'll need to trigger a layout change after changing the column width. Use InvalidateArrange() on the proper parent element. Take care to avoid infinite cycles.

InvalidateArrange is a good answer, but unfortunately it is silently ignored during the time a control is actually being arranged. So the trick is to call it after the arrange is complete.
This may work (I haven't tried it):
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
grid.InvalidateArrange();
}));
If this doesn't work, you might try calling InvalidateArrange and/or InvalidateMeasure on the RepeatButton, also within a Dispatcher.BeginInvoke callback.

Related

Listbox height binding not working properly & Image control proportional resizing

During developement of some program I've encountered some problems. The first is that I've bound the 'Height' property of listbox control to 'ActualHeight' of my stackPanel. Here's some XAML code I have:
<ListBox Name="listQuotes" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Width="{Binding ElementName=stackPanelQuotes, Path=ActualWidth}"
Height="{Binding ElementName=stackPanelQuotes, Path=ActualHeight}"
ItemsSource="{Binding}" ItemTemplate="{StaticResource quotesFeedTemplate}"
Background="Transparent" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
</ListBox>
When I'm expanding my window, size of listbox changes, but when I make it smaller again, the listbox itself doesn't change back, in fact even scrollbar remains the same range...
So how am I supposed to get this work properly?
Second thing is that my program consists of two parts - header and the main part
Header must remain static, while the the main part (two listboxes in two colums) must resize with the window. By resizing I mean that width should affect both, the header and main part, and height should only affect the main part
Normal view
Corrupted view
And the third thing. How can I make my image resize propotionally (lets say 3:4) whe I'm resing the window (no matter wich - width or height)
You couldget the effect you want by removing the height and width bindings and leaving them as automatic.
<Grid Name="Quotes">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" MinWidth="250" Name="listQuotes" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding QuotesList}"
Background="Transparent" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
</ListBox>
<ListBox Grid.Column ="1" MinWidth="250" Name="listQuotes2" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding QuotesList}"
Background="Transparent" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
</ListBox>
</Grid>
You can get the proportional sizing you want by using a grid with the Columnn definitions I posted. Those will stay in proportion as you resize.

How to make text wrap in a WPF TreeViewItem?

This time, my question is as simple as it sounds... how do you get text to wrap in a WPF TreeViewItem?
I have a simple HierarchicalDataTemplate with just one TextBlock in it.
<TextBlock Text="{Binding Value}" TextWrapping="Wrap" />
The text does not wrap.
I tried binding the Width of the TextBlock to the ActualWidth of the TreeView, but although that makes the text wrap, the Width of the TreeViewItem does not fit in the TreeView because the TreeView has Padding. Binding to the ActualWidth of the TreeViewItem has (unsurprisingly) the same effect. Another downside to this is that even the items with little text stretch outside the TreeView bounds.
<TextBlock Text="{Binding Value}" TextWrapping="Wrap" Width="{Binding ActualWidth,
ElementName=TreeView}" />
Surely there must be a better way, like somehow informing the TreeViewItem of the TreeView's bounds... I can't believe it doesn't know automatically. But how can I do this?
UPDATE >>>
Thanks to H.B.'s answer, I managed to change the Grid.ColumnSpan to 2 on the Bd Border he mentioned in the ControlTemplate and it set the width so that the text now wraps nicely. The problem is that I am using this ControlTemplate for other TreeViewItems in other TreeViews where I don't want full width items.
The solution I came up with is simple. I have bound the TreeViewItem.Tag value to the Grid.ColumnSpan property in the ControlTemplate like so:
<Border Grid.ColumnSpan="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}"
Name="Bd" Grid.Column="1" ... />
This allows me to change the Grid.ColumnSpan and therefore the full width or ordinary width behaviour of the TreeViewItem by setting the TreeViewItem.Tag value to either 2 or 1 respectively.
If you look at the default template of TreeViewItems you will see a Grid like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19"
Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ... -->
As you can see the third column takes all available space while the others are on Auto, the header is placed in the second column inside a border:
<Border Name="Bd"
Grid.Column="1"
...
This means that the column will become as large as the header, there is no restriction on it. Thus the header just gets bigger than the TreeView itself.
If you add Grid.ColumnSpan="2" to this Border it will occupy the third column as well, which is restricted by how much space is left, hence the text will wrap; this will however extend the header across the whole width which might look a bit odd when selecting it.
Of course you will also need to disable horizontal scrolling:
<TreeView ScrollViewer.HorizontalScrollBarVisibility="Disabled" ...

Prevent parent from being resized by child

is there a possibility to prevent a textblock from resize it's parent?
I have a textblock with a lot of text and I want it to wrap, but not to enlarge the parents size. The size of the parent may be variable.
Greetings Thomas
Unfortunately there is no Property for this feature.
The only workaround that I'm aware of is to either use an existing control or place another hidden control in the same space and then bind Width of your TextBlock/TextBox to the ActualWidth of that control.
Here is an example when the TextBlock doesn't effect the Width of the ColumnDefinition but it will get wider if the ColumnDefinition is resized for another reason
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1"
TextWrapping="Wrap"
Text="{Binding ...}"
Width="{Binding ElementName=sizingControl, Path=ActualWidth}"/>
<Rectangle Name="sizingControl" Grid.Column="1" Visibility="Hidden" />
</Grid>
For this to work, you need to set a width on the parent or place another grid or some container inside the parent which holds the textblock. Then set a width on that. The textblock will not wrap on a flexible parent.
Or better yet, just set a width on the textblock.

TextBox expanding with surrounding Grid but not with text

A window has a Grid with two columns. The left column contains a control with a constant width but with a height that adapts. The right column contains a TextBox that takes up all remaining space in the Grid (and thereby in the Window).
The Grid is given a minimal width and height and is wrapped within a ScrollViewer. If the user resizes the window to be smaller than the minimal width/height of the Grid, scrollbars are displayed.
This is exactly how I want it to be. However, a problem occurs when the user starts typing text. If the text is to long to fit in one line in the TextBox, I want the text to wrap. Therefore I set TextWrapping="Wrap" on the TextBox. But since the TextBox has an automatic width and is wrapped in a ScrollViewer (its actually the whole Grid that is wrapped), the TextBox just keeps expanding to the right.
I do want the TextBox to expand if the window is expanded, but I don't want the TextBox to expand by the text. Rather the text should wrap inside the available TextBox. If the text don't fit within the TextBox height, a scrollbar should be displayed within the TextBox.
Is there a way to accomplish this?
Below is some code that shows my problem:
<Window x:Class="AdaptingTextBoxes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="400" Background="DarkCyan">
<Grid Margin="10" Name="LayoutRoot">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid MinWidth="300" MinHeight="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Margin="0,0,10,0" Content="Button" Width="100" />
<TextBox Grid.Column="1" AcceptsReturn="True" TextWrapping="Wrap" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" />
</Grid>
</ScrollViewer>
</Grid>
</Window>
You could use an invisible border (its hacky but it works - its how I tend to sort out dynamic textbox sizes in Xaml):
<Border BorderThickness="0" x:Name="border" Grid.Column="1" Margin="0.5" />
<TextBox Grid.Column="1" AcceptsReturn="True" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=border}" Height="{Binding ActualHeight, ElementName=border}" />
Have you tried setting the MaxWidth property on just the TextBox?
Edit after OP's comment
I would try getting rid of the ScrollViewer. The sizing used in the Grid's layout should take care of re-sizing and the scroll bar settings on the TextBox should take care of the rest.
The answer is based on Leom's answer.
The solution works great when you enlarge the window, but the resizing is not smooth when you make the window smaller. As the textbox participates in the grid's layout, it has to perform layout process multiple times. You can fix that by putting the texbox in the canvas, so the change of the size of the textbox no longer triggers the grid's re-layout.
The updated code:
<Border BorderThickness="0" x:Name="border" Grid.Column="1" Margin="0.5" />
<Canvas Grid.Column="1">
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=border}" Height="{Binding ActualHeight, ElementName=border}" />
</Canvas>

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