WPF Grid Elements not resizing when another one is set to collapsed - c#

I am using a Grid to balance 3 parts. The first two shall take up each 50% of the remaining space, the last two shall stay the same height at the bottom, as it is a bar with buttons.
I use a GridSplitter to allow dynamic resizing between the first two elements, but removing it doesnt change anything either.
Here is a minimal working example:
XAML:
<Window x:Class="TestWPFApplication.MainWindow"
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"
xmlns:local="clr-namespace:TestWPFApplication"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid x:Name="rootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox
x:Name="firstBox"
Grid.Row="0"
AcceptsReturn="True"
AcceptsTab="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"/>
<GridSplitter x:Name="splitter" Grid.Row="1" Height="5" HorizontalAlignment="Stretch" />
<TextBox
x:Name="secondBox"
Grid.Row="2"
AcceptsReturn="True"
AcceptsTab="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"/>
<Button Grid.Row="3" Content="Collapse" Click="Button_Click"/>
</Grid>
</Window>
CS:
namespace TestWPFApplication
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
splitter.Visibility = Visibility.Collapsed;
secondBox.Visibility = Visibility.Collapsed;
}
}
}
After clicking the button, the second textbox and the gridsplitter collapse, so I cannot use them anymore (desired), but the remaining textbox does not take up the rest of the space (undesired).
In my understanding as the row has a star for height, it should automatically adjust its space. Do I need to call a function like updateUI or something so that the first textbox gets automatically resized?

You observe such behaviour, cause the heights of first and third grid row is set to '*'. After setting 2nd textBoxVisibility to collapsed 3rd grid row is not disappears. So if you write in Button_Click event handler something like this, all should work like expected.
rootGrid.RowDefinitions[2].MaxHeight = 0;
I can suggest to use DataBinding for it property to achieve this. You can bind second TextBox Visibility property to third RowDefinition MaxHeight property with ValueConverter. But I don't know the restrictions of your project.

Related

WPF Grid change ColumnSpan on MinWidth

let's say I have a grid with 4 columns. Every column takes the same amount of width
-> 100% / 4cols = 25%.
The 3rd column has a TextBlock or any other control in it. Now, when I resize the Width of the Window and the column gets to a minimum value, I want the TextBlock to use ColumnSpan="2" else the TextBlock would be too small. I hope you guys understand what I mean. I tried so many things but I didn't even come close to what I want because I'm pretty new to WPF. For example setting the minWidth so it doesn't get too small doesn't work because at some point the controls will still be hidden inside the grid column. I don't have any useful xaml for you, unless you just want the grid layout. Maybe a Grid is not the right thing i want? The other layouts didn't seem right for this purpose. Any help is appreciated.
You could handle the SizeChanged event of the Grid or the window and programmaticlly set the Grid.ColumnSpan attached property of the TextBlock whenever the grid/window shrinks below a pre-defined minimum width. Please refer to the following sample code.
private void Grid_SizeChanged(object sender, SizeChangedEventArgs e)
{
const double MinWidth = 200;
Grid grid = (Grid)sender;
if (grid.ActualWidth < MinWidth)
Grid.SetColumnSpan(textBlock, 2);
else
Grid.SetColumnSpan(textBlock, 1);
}
XAML:
<Window x:Class="WpfApp1.MainWindow"
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" Height="300" Width="300">
<Grid SizeChanged="Grid_SizeChanged">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*" />
<ColumnDefinition Width="25*" />
<ColumnDefinition Width="25*" />
<ColumnDefinition Width="25*" />
</Grid.ColumnDefinitions>
<TextBlock Background="Yellow" Text="1" />
<TextBlock Background="Green" Text="2" Grid.Column="1" />
<TextBlock Background="Red" Text="4" Grid.Column="3" />
<TextBlock x:Name="textBlock" Background="Blue" Text="3" Grid.Column="2" Grid.ColumnSpan="2" />
</Grid>
</Window>
If you decrease the width of the window, the blue TextBlock streches across the third and fourth columns.

Making a WPF TextBlock be only grow and not shrink?

In my app I set the Text of the TextBlock named tbkStatus many times.
How can I make the TextBlock be just grow auto to fit the text but not shrink when the text changed?
The StatusText changes every few seconds, There are statuses with long text and short text.
I want my TextBlock to fit itself to the size of the longest text that was, and even when there is a short text the TextBlock should not shrink
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="200" Width="400"
WindowStartupLocation="CenterScreen"
ResizeMode="CanMinimize" Topmost="True">
<Window.Resources>
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="AUTO" />
<RowDefinition Height="AUTO" />
<RowDefinition Height="AUTO" />
<RowDefinition Height="AUTO" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Please wait ..." Grid.Row="1" Margin="6"/>
<TextBlock Name="tbkStatus" Grid.Row="2" Margin="6" TextWrapping="Wrap" Text="{Binding StatusText}"/>
<ProgressBar Grid.Row="3" Margin="6" Height="20"/>
<Button Grid.Row="4" HorizontalAlignment="Center" Padding="24,3" Margin="6" Content="Stop"/>
</Grid>
</Window>
Xaml only solution:
<TextBlock Text="{Binding StatusText}"
MinWidth="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth}"
HorizontalAlignment="Left"/>
This way whenever the Width growths, MinWidth growths too. Hence, the control can't shrink back.
I guess you could just listen to Layout Events like SizeChanged or LayoutUpdated or write some sort of behavior
In the below example, the basic premise is to listen to either of these events, and force your control to never shrink
Note this is totally untested and was just an idea, maybe you could set the MinWidth Property instead
Xaml
<TextBlock x:Name="tbkStatus" SizeChanged="OnSizeChanged"/>
Code Behind
private double _lastSize;
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
var textBlock = sender as TextBlock;
if (textBlock == null)
{
return;
}
if (e.WidthChanged)
{
if (textBlock.Width < _lastSize)
{
textBlock.Width = _lastSize;
}
_lastSize = textBlock.Width;
}
}
Also Note
The SizeChangedEventArgs Class has many properties that you might be able to take advantage of
You can do something like this:
<TextBlock Name="tbkStatus" Grid.Row="2" Margin="6" TextWrapping="Wrap" Text="{Binding StatusText}"/>
Since TextBlock doesn't have TextChange event this will do that job
DependencyPropertyDescriptor dp = DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
int textLength =0
dp.AddValueChanged(tbkStatus, (object a, EventArgs b) =>
{
if (textLength < tbkStatus.Text.Length)
{
textLength = tkbStatus.Text.Length;
tbkStatus.Width = textLength * SomeValue; //You have to play around and see what value suits you best since it depends on font and it's size
}
});
Alternatively, you can use a TextBox and make it read only and use the TextChanged event.

Displaying multiple controls at once

I'm developing a UI with the WPF framework and MVVM.
This is the grid while loading:
image of the grid while displaying
This is the UI after the rendering has finished:
image of the grid when rendering is finished
So, as an example, the 3rd column isn't active and will collapse. But the user can see the whole building off the UI in the mainwindow.
Every view I've got is depending on the viewmodel. But also the controls got their own viewmodel. When I take my information together in one control, WPF is getting the information and draws the whole control, for example my tablecontrol. It´s a selfmade control, that is taking data and displays the data in a grid. Because it's one control, the whole grid is loaded in the background and displayed at once. When I make my own grid, because my tablecontrol can't handle the data, every control in the grid is displayed one after one and the grid is slowly displayed on the mainwindow.
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
.
.
.
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
.
.
.
</Grid.RowDefinitions>
<unitControls:Label Grid.Row="1" Grid.Column="0" Text="{Binding xx}"/>
<unitControls:Label Grid.Row="1" Grid.Column="1" Text="{Binding xx}"/>
<unitControls:Label Grid.Row="1" Grid.Column="2" Text="{Binding xx}"/>
.
.
.
<unitControls:Label Grid.Row="7" Grid.Column="7" Text="{Binding xx}"/>
<unitControls:Label Grid.Row="7" Grid.Column="8" Text="{Binding xx}"/>
<unitControls:Label Grid.Row="7" Grid.Column="9" Text="{Binding xx}"/>
<Grid>
Is there a way to display the whole content in one view at once? I tried to manipulate the dispatcher in the viewmodel, but nothing helped.
Try something like this:
XAML
<Window x:Class="Example.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">
<DockPanel>
<Button Content="Collect data" Click="OnCollectData" DockPanel.Dock="Top"/>
<ContentControl x:Name="HostControl"/>
</DockPanel>
</Window>
CS
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void OnCollectData(object sender, RoutedEventArgs e)
{
var data = await CollectDataAsync();
HostControl.Content = new MyCustomGridControl(data);
}
private async Task<GridDataHolder> CollectDataAsync()
{
GridDataHolder result = null;
await Task.Run(() =>
{
// Collect the logical data on the thread pool
result = GetGridData();
});
return result;
}
}
You can put the HostControl with a ProgressBar in a Grid and play with the Visibility property of the ProgressBar to improve the user experience.

WPF shrink other conent when Expander is Expanded

Whenever Expander is expanded I would like to shrink grid cell above containing ListBox, so that you can always access every ListItem(if the listbox's grid cell would not shrink, lowest part would be inacessible). To illustrate:
item item *scrollbar*
item -> item *scrollbar*
item expanderItems
expander expander
I found bunch of threads for resizable expander, but none mentioning resizing other content. The problem is, grid containing listbox in 1st row and expander in 2nd with 2nd row Height set to Auto do not resize itself when expander is expanded.
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
I managed to code an ugly workaround using Expanded/Collapsed events:
private void Expander_Expanded(object sender, System.Windows.RoutedEventArgs e)
{
//grid1.Height is named content of expander
this.LayoutRoot.RowDefinitions[1].Height = new GridLength(this.LayoutRoot.RowDefinitions[1].ActualHeight + grid1.Height, GridUnitType.Pixel);
this.LayoutRoot.RowDefinitions[0].Height = new GridLength(1, GridUnitType.Star);
}
What would be the proper way? Preferably with no code behind and more "automated".
EDIT: xaml
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Expander Header="Expander" Margin="0" Grid.Row="1" VerticalAlignment="Bottom" Height="23" Panel.ZIndex="1" ExpandDirection="Up" Grid.RowSpan="2" Expanded="Expander_Expanded" Collapsed="Expander_Collapsed">
<Grid x:Name="grid1" Background="#FF762CF7" Height="100" Margin="0,-100,0,0"/>
</Expander>
<ListBox Margin="0" Background="#19FFFFFF">
<Button Height="150" Width="100"/>
<Button Height="150" Width="100"/>
<Button Height="150" Width="100"/>
</ListBox>
<Grid Margin="0" Grid.Row="1" Background="#FFAEFFAE"/>
<Grid Margin="0" Background="#FFFFD8D8"/>
</Grid>
The main problem you have is setting the Height property of the Expander. It changes its height when you expand it. But if you are setting it, it has to respect the height you gave it and cannot properly expand.
Instead of setting the Height, I would set the MinHeight (or completely remove height constraints, depending on what you want to do).
Additionally you should remove the Margin of the grid inside the expander.

wpf randomly highlighting text on vista / xp

This issue only happens on vista and xp. What is happening is if there is a textbox that has a lot of text and is partially off the screen and you click in it, wpf will scroll it into view and highlight text while it does this. In windows 7 it won't scroll it into view. I am using .net 4 and have tried clearing the selection on the textbox's received focus, got keyboard focus, and mouse capture events, but the scroll seems to take place after those. I have included some screen shots of what I am talking about as well as a test app that demonstrates the problem.
Before clicking on anything, In the next screen shot all I have done was click on line 6
After clicking on line 6 you can see everything is highlighted as it scrolled the textbox into view.
repro:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (int i = 1; i < 1000; i++)
{
textBox3.AppendText(string.Format("line {0}\r\n", i));
}
}
}
<Window x:Class="WpfApplication1.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" MaxHeight="350" MaxWidth="525">
<Grid>
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="this is some text"></TextBox>
<TextBox Grid.Row="1" Text="this is some text"></TextBox>
<TextBox Grid.Row="2" ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto" AcceptsReturn="True"
x:Name="textBox3"></TextBox>
</Grid>
</ScrollViewer>
</Grid>
</Window>
Turns out from Microsoft that this is a known issue in .net 3.5 and they fixed in .net 4.0. they did not have a work around.

Categories

Resources