How does WPF decide how to render the textblock textwrap? - c#

I'm trying to get my head around how WPF makes decisions when it renders a textblock with wrap enabled.
I have the following code:
<Window x:Class="WpfWrapTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="200" Height="200">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="5"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border Background="Yellow" Grid.Column="0"></Border>
<GridSplitter ResizeDirection="Columns" Grid.Column="1" Width="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></GridSplitter>
<Grid Grid.Column="2" MinWidth="40">
<TextBlock TextWrapping="Wrap">asdflk;jsadlkfjlaskdjflasdkjflk laskdjfl;askjd l;kasjdf l;kjsadf ;lkajsdfl k</TextBlock>
</Grid>
</Grid>
When starting WPF decides to make my textblock larger than the screen and not take this into account for wrapping
Then when I drag the gridsplitter it somehow makes a different decision (probably because the gridsplitter is setting a width of a neighbour control?)
A third weird behaviour in this sample is when you try to drag the gridsplitter more left than it can go (minwidth on column 1 is 5). Then it decides to re-enlarge the textblock outside the visual screenspace.
What is making WPF do one or the other?

Try setting the 3rd column with to "*" instead. "Auto" means that the TextBlock will use as much space as it needs, effectively meaning it doesn't need to wrap.
When you drag the splitter, you are giving the grid column an explicit size, so TextBlock will wrap to fit to that size.

<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" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="5"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border Background="Yellow" Grid.Column="0"></Border>
<GridSplitter ResizeDirection="Columns" Grid.Column="1" Width="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></GridSplitter>
<Grid Grid.Column="2" MinWidth="40">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock TextWrapping="Wrap">asdflk;jsadlkfjlaskdjflasdkjflk laskdjfl;askjd l;kasjdf l;kjsadf ;lkajsdfl k</TextBlock>
</Grid>
</Grid>
</Window>

It's quite simple really... probably too simple for an answer even.
If the TextBlock or a parent of the TextBlock is given a Width that the TextBlock.Text value is longer than, the text will be wrapped. Otherwise, it will not be wrapped.
It really is that simple. As for the other 'weird' symptoms that you spoke about, I can't really help you there... your code didn't show me them. For example, I'm not sure how you can drag a GridSplitter more left than it can go.

Related

Listbox looks different in xaml viewer than in the program window

I'm working on a WPF Application, and i encountered the following problem,
ignored it until now though.
I want to display a Listbox and a Label on top of it.
For that I have the following XAML code:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="1" Content="Packs" Margin="60, 65, 60, 0"/>
<ListBox Grid.Column="1" Height="200" Width="150"/>
</Grid>
This gives the following output in the design window
You can ignore the buttons on the left.
As you can see the Label is right on top of the Listbox.
Now when I open my program, the window makes it seem like the Listbox is on top
of the Label.
I tried a lot rearranging the XAML-Code to display it differently, but this problem stay the same, and although I could bypass it by using values that are simply too big for the designer, it still bugs me that I can't find a solution.
Also please keep in mind that my program will not be resizeable, so dont worry about that.
Thank you for your answers!
You can put Label and ListBox in another Panel (StackPanel) and align that Panel in outer Grid:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1"
Width="150"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Label Content="Packs"/>
<ListBox Height="200"/>
</StackPanel>
</Grid>
this way both elements will be in the center of middle column even if window is resized.

Expand Window to LEFT & RIGHT but keep the main content at the same position

I have a window that expands to left and to the right.
Simplified example:
<Window x:Class="Application1.Windows.Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow"
SizeToContent="Width" Height="250">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <!--Expander LEFT-->
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="175" /> <!--Red Content -->
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="Auto"/> <!--Expander RIGHT-->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="*"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Expander Grid.Column="0"
Grid.Row="0"
Grid.RowSpan="3"
Margin="5"
ExpandDirection="Left">
<Grid Width="200">
</Grid>
</Expander>
<Grid Grid.Column="2"
Grid.RowSpan="3"
Background="Red"/>
<Expander Grid.Column="4"
Grid.Row="0"
Grid.RowSpan="3"
Margin="5"
ExpandDirection="Right">
<Grid Width="200">
</Grid>
</Expander>
</Grid>
Here you can see what the problem is:
I've already had a look on WPF - Expand Window to the Left.
The solution kind of works but that is only solving half of the problem.
How to do that for Left AND Right ?
UPDATE
I do not want the Expander columns to be fixed. They should just show the minimal space needed for the expander
One approach that will work is this:
(But I'm somehow unsatisfied with this solution)
Create an Enum to store the last Action in:
public enum ExpandActions
{
ExpandRight,
ExpandLeft,
CollapsRight,
CollapseLeft
}
Then I added the handlers for Expand & Collapse to my Expanders in xaml.
At last override SizeChanged on the Window as in WPF - Expand Window to the Left.
We only want this behavior, when expanding to left - otherwise we would kill our 'expand-to-right' behavior.
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
if (!sizeInfo.WidthChanged)
{
base.OnRenderSizeChanged(sizeInfo);
return;
}
Hide();
base.OnRenderSizeChanged(sizeInfo);
//Last action was on left expander
if(_lastAction == ExpandActions.CollapseLeft || _lastAction == ExpandActions.ExpandLeft)
{
Left -= (sizeInfo.NewSize.Width - sizeInfo.PreviousSize.Width);
}
Show();
}
Good point about this is that expansion out of screen could be handled.
It works but I guess it's not the best solution.
The result is this:

Increase font size when screen is smaller

I have this in my XAML
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.4*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="0.4*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="1" >
<TextBlock HorizontalAlignment="Center" TextWrapping="Wrap" Text="Hello there and welcome!" />
</Grid>
</Grid>
</Grid>
The problem I'm facing is that the TextBlock will remain the same FontSize on all screen sizes, so when the screen is small, its easy to read but as the screen gets bigger its harder to read.
How do I keep it at a nice FontSize so it's readable from all screen sizes? Is there a way of increasing the font size as the screen expands?
You can wrap it inside the ViewBox
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Viewbox>
<TextBlock HorizontalAlignment="Center" TextWrapping="Wrap" Text="Hello there and welcome!" />
</Viewbox>
</Window>
WPF fonts are DPI aware, so they'll be the same size regardless of resolution. A viewbox will allow you to scale everything down to fit, although in my experience it typically results in everything looking too small, especially if it's been designed on a desktop and then viewed on a laptop. What I do myself is create two themes and choose the appropriate one based on physical display size (i.e. product of DPI and resolution). Not sure if it's the best solution, but so far it seems to be working.

Bind Width property of elements which are horizontally stretched

I have a group box and grid splitter control in a column of the grid. Horizontal Alignment of group box is set to stretch so it occupies all the space when I drag the splitter. All works well.
Now I need to store the value of the group box in a property of the bound object but as soon as I bind the width property it gets stuck it is no longer stretching itself upon stretching the splitter.
I know the reason because now the binded property is responsible for its width and it is not getting changed. But don't know how to make it work. This is my XAML.
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="InnerGrid" HorizontalAlignment="Stretch" Height="{Binding ElementName=Control1,Path=ActualHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="200"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<GroupBox Header="{Binding TrackName}" VerticalAlignment="Stretch" Margin="3 0 3 0" HorizontalAlignment="Stretch" />
<GridSplitter Width="5" VerticalAlignment="Stretch" Focusable="False" Background="Gray"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
Perhaps you're actually interested in binding the ColumnDefinition width, as so:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Width}" MinWidth="200"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
As I understand you need read calculated width of the GroupBox. You can use ActualWidth property for this purpose.
Edit:
You can write custom GroupBox and make use of Dependency Properties:
public class MyGroupBox : GroupBox
{
public static readonly DependencyProperty CurrentWidthProperty =
DependencyProperty.Register("CurrentWidth", typeof(double),
typeof(MyGroupBox), new FrameworkPropertyMetadata(0d));
public double CurrentWidth
{
get { return this.ActualWidth; }
set { SetValue(CurrentWidthProperty, value); }
}
}
XAML:
<Window x:Class="FunWithWpfAndXP.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FunWithWp"
Title="Window1" Height="300" Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="200"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<local:MyGroupBox CurrentWidth="{Binding Path=myProp}" VerticalAlignment="Stretch" Margin="3 0 3 0" HorizontalAlignment="Stretch"/>
<GridSplitter Width="5" VerticalAlignment="Stretch" Focusable="False" Background="Gray"/>
</Grid>
</Window>
The problem is that your binding is resetting the width changed by the GridSplitter, setting the Mode of the GroupBox Width binding to OneWayToSource should (probably) help you, you'll probably get something like this:
<GroupBox Width="{Binding Path=MyGroupBoxWidth, Mode=OneWayToSource}"/>
MSDN:
OneWayToSource: Updates the source property when the target property changes.
Setting this will cause that the property in your code will be updated but not the other way around

WPF Facing App window size on different resolution

I have a WPF application. To make the full screen visible on all screen sizes, I have implemented MinHeight, MinWidth & HorizontalAlignment="Stretch" VerticalAlignment="Stretch" in Window & Containers too. I am facing some problems when the app runs on Lower Resolution screens. The window gets cut from the right side of the screen - this doesn't show Min, Max, Close btns also on top right.
If I add layout code in then the window is proper in all resolutions, but it makes blank space above the Menubar and below end. On removing , their is no space and all is well, but right side gets cut in Low Resolution screens. And with ViewBox, space above and below the layout. My XML code is like follows :
CODE UPDATED
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- MENU BAR -->
<Menu Grid.Row="0" x:Name="myMnus" VerticalAlignment="Top" Cursor="Hand" HorizontalAlignment="Stretch" IsMainMenu="True" Grid.ColumnSpan="2">
.............
</Menu>
<ToolBarTray HorizontalAlignment="Stretch" Background="White" Margin="0,19,114,0" VerticalAlignment="Top" Grid.ColumnSpan="2" >
..............
<ToolBarTray>
<TabControl Grid.Row="1" Name="tabControl1" HorizontalAlignment="Left" Margin="0,3,0,0" VerticalAlignment="Top"
TabStripPlacement="Bottom" Grid.RowSpan="2" BorderThickness="4,25,4,1" FontSize="13">
</TabControl>
<TabControl Grid.Row="2" Name="tabControl4" HorizontalAlignment="Left" Margin="0,323,0,0" VerticalAlignment="Stretch"
TabStripPlacement="Bottom" BorderThickness="4,25,4,1" FontSize="13" Background="White" Width="227">
</TabControl>
<TabControl TabStripPlacement="Bottom" MinHeight="415" MinWidth="480" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Row="1" Grid.Column="1" Name="tabChildContainer" Margin="227,3,207,0" BorderThickness="4,25,4,1" Grid.RowSpan="2" >
</TabControl>
</Grid>
I thought by using Stretch in HorizontalAlignment and VerticalAlignment along with MinWidth and MinHeight, that it would occupy all available space horizontally and Vertically. But tabChildContainer TabControl doesn't go to the right end corner which it should go based on the code.
This is where your problem starts:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="762.976"/>
<ColumnDefinition Width="751.024"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
Anytime that you set exact pixel sizes in your UI, you're asking for exactly these kinds of problems. Setting exact sizes for sections of your application was more of a WinForms thing... WPF has numerous controls that can resize your content for you... you're using one, the Grid... just incorrectly.
Secondly, it is very unusual to use a ViewBox on your whole UI... it is not going to help you. Your best bet is to simply remove it and all of your hard-coded dimensions and make full use of the "*" and "Auto" values in your Grid. When the controls resize themselves (or a Grid resizes them) in this way, it really doesn't matter what resolution a user is using.

Categories

Resources