wpf toggling visibility collapse only works once - wpf

What i want to do is collapse the bottom section of the WPF ui based on the checkbox toggle. This mostly works as expected so far. Below as you step through the images you'll see the collapse stops working once the grid splitter is moved. I do not understand why or how to fix this.
Start of the application, appears correct.
Toggle the checkbox and the botttom section disappears as expected.
Toggle the checkbox once more and then move the splitter vertically, everything appears as expected.
Now toggle the checkbox one last time and you'll notice the top section doesn't fill the application like it once did before. This is where it appears broken! I would expect the yellow section to fill the UI like it did in the initial toggle.
<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="250" Width="525"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<CheckBox Name="ToggleVisibility" Margin="10" IsChecked="True" Content="Toggle Bottom Section"></CheckBox>
<StackPanel Background="#feca00" Grid.Row="1">
<TextBlock FontSize="20" Foreground="#58290A">Top Section</TextBlock>
</StackPanel>
<GridSplitter Grid.Row="2" Height="5" HorizontalAlignment="Stretch">
<GridSplitter.Style>
<Style TargetType="GridSplitter">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleVisibility, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</GridSplitter.Style>
</GridSplitter>
<StackPanel Grid.Row="3" Background="LightBlue">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleVisibility, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock FontSize="20" Foreground="black">Bottom Section</TextBlock>
</StackPanel>
</Grid>
</Window>

The reason why this happens is that when you move the splitter it overwrites the Height property of the last RowDefinition and sets it to actual height of whatever is presented in the according row of the Grid - the second StackPanel (bottom section) in your case. It no longer is set to Auto, so even if you collapse the bottom section the row maintains it's height - hence the empty space.
I sometimes find myself in a similar situation, and what I do then is to span the control, which I want to take up all the space, across the remaining rows using Grid.RowSpan attached property. In your case you could achieve it by applying the following style to your first StackPanel (top section):
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=ToggleVisibility}" Value="False">
<Setter Property="Grid.RowSpan" Value="3" />
</DataTrigger>
</Style.Triggers>
</Style>
Then the StackPanel will span to the bottom of the Grid regardless of the following rows' heights.
EDIT
Since you're not satisfied with spanning the control, here's another solution: derive from RowDefinition class and wire your own visibility logic. Here's an example:
public class MyRowDefinition : RowDefinition
{
static MyRowDefinition()
{
HeightProperty.OverrideMetadata(
typeof(MyRowDefinition),
new FrameworkPropertyMetadata
{
CoerceValueCallback = CoerceHeightPropertyValue
});
}
private static object CoerceHeightPropertyValue(DependencyObject d, object baseValue)
{
if (Equals(d.GetValue(IsVisibleProperty), false))
return new GridLength(0d);
else
return baseValue;
}
public bool IsVisible
{
get { return (bool)GetValue(IsVisibleProperty); }
set { SetValue(IsVisibleProperty, value); }
}
public static readonly DependencyProperty IsVisibleProperty =
DependencyProperty.Register(
"IsVisible",
typeof(bool),
typeof(MyRowDefinition),
new FrameworkPropertyMetadata
{
DefaultValue = true,
PropertyChangedCallback = IsVisiblePropertyChanged
});
private static void IsVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.InvalidateProperty(RowDefinition.HeightProperty);
}
}
Then use that class instead of RowDefinition in your grid and bind the IsVisible property to the check state of the checkbox.

It looks like the StackPanel and the GridSplitter are collapsed, but that Grid.Row 3 is not. I'm not sure why the Auto height does no close it out. EDIT: I see that #Grx70 has explained it.
This: Hide grid row in WPF
suggests that setting all children to Visibility.Collapsed ought to work, but you have the option of setting the height of Grid.Row 3 to 0.
I think the Height="5" of your GridSplitter might also keep it taking up space, taking precedence over the style setting (it is collapsed and gets Height 0 from that, but gets Height 5 from the Height="5", which wins (but it is still invisible). It might be better to but that in the style too:
<Setter Property="Height" Value="5" />

Related

WPF ToggleButton style that changes text of TextBlock within a StackPanel based on IsChecked trigger

I have a WPF style for a toggle button that uses a stack panel to achieve stacked, vertical text. I want the button text to change based on the toggle button's IsChecked state.
Additionally, the number of characters changes so I need to hide one of the text blocks in the stack panel. I tried setting the Visibility property of the Letter4 text block to hidden but the text was not vertically centered.
The code below works but it's just a cheesy workaround — I change the font size to 1 so it seems to disappear. (I pulled out all the formatting to make it simpler.) What is the correct way to do what I need?
Thanks.
<Style x:Key="RunStopToggle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<StackPanel VerticalAlignment="Center">
<TextBlock x:Name="Letter1"/>
<TextBlock x:Name="Letter2"/>
<TextBlock x:Name="Letter3"/>
<TextBlock x:Name="Letter4"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="TextBlock.Text" TargetName="Letter1" Value="S"/>
<Setter Property="TextBlock.Text" TargetName="Letter2" Value="T"/>
<Setter Property="TextBlock.Text" TargetName="Letter3" Value="O"/>
<Setter Property="TextBlock.Text" TargetName="Letter4" Value="P"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="TextBlock.Text" TargetName="Letter1" Value="R"/>
<Setter Property="TextBlock.Text" TargetName="Letter2" Value="U"/>
<Setter Property="TextBlock.Text" TargetName="Letter3" Value="N"/>
<Setter Property="TextBlock.Text" TargetName="Letter4" Value=""/>
<Setter Property="TextBlock.FontSize" TargetName="Letter4" Value="1"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
IMHO, the biggest problem with the code is that you're trying to do everything in XAML instead of letting a view model mediate the changeable values. A smaller issue is how you're actually implementing the vertically-stacked text.
There is already another question with good advice about vertically-stacked text. See Vertical Text in Wpf TextBlock
We can combine the advice there, where they use ItemsControl to display the text vertically, along with a view model to provide the actual text, and a placeholder ItemsControl that is hidden, but not collapsed (so that it still takes up space), to display the toggle button much more simply than in the code you have now.
First, the view model:
class ViewModel : INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set => _UpdateField(ref _isChecked, value, _OnIsCheckedChanged);
}
private string _buttonText;
public string ButtonText
{
get => _buttonText;
set => _UpdateField(ref _buttonText, value);
}
public ViewModel()
{
ButtonText = _GetTextForButtonState();
}
public event PropertyChangedEventHandler PropertyChanged;
private void _OnIsCheckedChanged(bool previous)
{
ButtonText = _GetTextForButtonState();
}
private string _GetTextForButtonState()
{
return IsChecked ? "STOP" : "RUN";
}
private void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This view model just provides a property to receive the toggle button's state, as well as to provide the appropriate button text for that state.
Next, the XAML to use this view model:
<Window x:Class="TestSO68091382ToggleVerticalText.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:l="clr-namespace:TestSO68091382ToggleVerticalText"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:ViewModel/>
</Window.DataContext>
<Grid>
<ToggleButton IsChecked="{Binding IsChecked}"
HorizontalAlignment="Left" VerticalAlignment="Top">
<ToggleButton.Content>
<Grid>
<ItemsControl ItemsSource="STOP" Visibility="Hidden"/>
<ItemsControl ItemsSource="{Binding ButtonText}" VerticalAlignment="Center"/>
</Grid>
</ToggleButton.Content>
</ToggleButton>
</Grid>
</Window>
The ToggleButton.IsChecked property is bound to the IsChecked property in the view model, so that it can update the text as necessary. Then the content of the ToggleButton includes the ItemsControl that will display the text vertically.
Note that button's direct descendent is actually a Grid. This is so that two different ItemsControl elements can be provided: one shows the text itself, and is bound to the ButtonText property; the other has hard-coded the longer of the two strings that might be displayed. This ensures that the ToggleButton's size is always the same, large enough for that longer text. The bound ItemsControl is then centered vertically; you can of course use whatever aligment you like there, but the way your question is worded it sounds like you want the text vertically centered.
For what it's worth, if you really want to do everything in XAML, that's possible to. I personally prefer to avoid this kind of use for triggers, but I admit there's no hard and fast rule that says you can't. My preference mainly has to do with my desire to keep the XAML as simple as possible, as I find it a less readable language, and harder to mentally keep track of all the different related elements, which adding triggers tends to make more complex.
If you do prefer a XAML-only solution, it would look like this:
<Window x:Class="TestSO68091382ToggleVerticalText.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:l="clr-namespace:TestSO68091382ToggleVerticalText"
xmlns:s="clr-namespace:System;assembly=netstandard"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<s:String x:Key="runText">RUN</s:String>
<s:String x:Key="stopText">STOP</s:String>
</Window.Resources>
<Grid>
<ToggleButton HorizontalAlignment="Left" VerticalAlignment="Top">
<ToggleButton.Content>
<Grid>
<ItemsControl ItemsSource="STOP" Visibility="Hidden"/>
<ItemsControl VerticalAlignment="Center">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="ItemsSource" Value="{StaticResource runText}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ToggleButton}}" Value="True">
<Setter Property="ItemsSource" Value="{StaticResource stopText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
</ItemsControl>
</Grid>
</ToggleButton.Content>
</ToggleButton>
</Grid>
</Window>
Mechanically, this is very similar to the view model-based example above, just using a DataTrigger to respond to the changes in the ToggleButton.IsChecked state instead of doing it in the view model.
Note that you really only need one trigger. You can use a Setter to provide the unchecked state's value, and then use a single trigger to override that value for the checked state.
You need to change Visibility:
<Style x:Key="RunStopToggle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<StackPanel VerticalAlignment="Center">
<TextBlock x:Name="Letter1"/>
<TextBlock x:Name="Letter2"/>
<TextBlock x:Name="Letter3"/>
<TextBlock x:Name="Letter4" Text="P"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="TextBlock.Text" TargetName="Letter1" Value="S"/>
<Setter Property="TextBlock.Text" TargetName="Letter2" Value="T"/>
<Setter Property="TextBlock.Text" TargetName="Letter3" Value="O"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="TextBlock.Text" TargetName="Letter1" Value="R"/>
<Setter Property="TextBlock.Text" TargetName="Letter2" Value="U"/>
<Setter Property="TextBlock.Text" TargetName="Letter3" Value="N"/>
<Setter Property="TextBlock.Visibility" TargetName="Letter4" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But I agree with #Peter Duniho - you'd better use a different approach for vertical text.
For permanent text, to print it one letter on each line, it is enough to insert newline characters & # xA; between the letters.
Example:
<Style x:Key="RunStopToggle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<StackPanel VerticalAlignment="Center">
<TextBlock x:Name="PART_TextBlock" Text="R
U
N"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="TextBlock.Text" TargetName="PART_TextBlock" Value="S
T
O
P"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Also see my answer with a converter for vertical text: https://stackoverflow.com/a/68094601/13349759

WPF : Binding to a listbox, special behavior when nothing is selected

I have a window with a listbox, and a usercontrol details 'view' that is bound to the selected item of the listbox. By default, nothing is selected in the listbox (this is default behavior as far as I know, nothing special here).
What I'd like to do is have some sort of message appear (instead of, but in the same place as the details view) when nothing is selected in the Listbox.
Here is the code, I've been trying to condense this as small as I can and so have removed most of the MVVM/ ViewModel /NotifyPropertyChanged stuff.
I'll start with the window :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" Name="ListList"></ListBox>
<view:BoundItem Grid.Column="1" DataContext="{Binding ElementName=ListList, Path=SelectedItem}" ></view:BoundItem>
</Grid>
The item that gets bound to does nothing more than echo some properties :
<Grid>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Foo, Mode=OneWay}"></TextBox>
<TextBox Text="{Binding Bar, Mode=OneWay}"></TextBox>
<TextBox Text="{Binding Baz, Mode=OneWay}"></TextBox>
</StackPanel>
</Grid>
And I'm setting populating the listbox and defining a dataitem all in one go :
/// <summary>
/// Interaction logic for ListBoxSelectedItemBinding.xaml
/// </summary>
public partial class ListBoxSelectedItemBinding : Window
{
public ListBoxSelectedItemBinding()
{
InitializeComponent();
this.ListList.Items.Add(new ListBoxData());
this.ListList.Items.Add(new ListBoxData());
this.ListList.Items.Add(new ListBoxData());
}
}
public class ListBoxData
{
public string Foo
{
get
{
return "Foo";
}
}
public string Bar
{
get
{
return "FooBar";
}
}
public string Baz
{
get
{
return "FooBarBaz";
}
}
}
When the app starts up, nothing is selected, and I see the listbox on the Left, and 3 empty textboxes on the right. Once I select something, the three textboxes get populated.
What I'd like is to just hide the completely until something gets selected. I think I can give a default item to bind to with Binding TargetNullValue or FallBackValue - That isn't quite what I am looking for. More so, I'm looking to entirely hide the and replace it with something else (suppose a button, just to give an idea).
I don't think there is anything at all complicated here. I've searched for answers, but am just swamped with slightly related subjects on listbox binding, selected item, etc.
You can deal with Visibility property and hide one Panel when SelectedItem equals null and simultaneously show latter.
Hide TextBoxes Grid:
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ListList, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBox/>
<TextBox/>
<TextBox/>
</Grid>
and Show your Buttons Grid
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ListList, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Button/>
<Button/>
<Button/>
</Grid>
Keep in mind they need to be place at the same place (for instance the same column and row). More sofisticated way out then dealing with Visibility is to use ContentControl which will automatically change View based on ViewModel change. More here.

datatemplate not switching upon datatrigger

I have defined two datatemplates for my listBox for which I am using a DataTemplateSelector to decide which one to be rendered for an item. However, One of my templates has an embedded button which when clicked should display a form in place. So I have setup a boolean property and implemented INotifyPropertyChanged; so that when the boolean property is true, the current template is replaced by a third template.
This is the code for the button.
Activity activityItem = (Activity)listBox1.Items[listBox1.SelectedIndex];
activityItem.ShowFeedbackForm = 1;
This is my XAML:
<Grid.Resources>
<DataTemplate x:Key="completedActivityTemplate">
<Grid Name="templateGrid" >
...
<DataTemplate x:Key="activityTemplate">
<Grid Name="templateGrid" >
...
<DataTemplate x:Key="feedbackFormTemplate">
<Grid Name="templateGrid" >
...
<Style TargetType="ListBoxItem" x:Key="ContainerStyle">
<Setter Property="Height" Value="55" />
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
<DataTrigger Binding="{Binding showFeedbackForm}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource feedbackFormTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<ListBox Grid.Row="1" Name="listBox1" IsSynchronizedWithCurrentItem="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
VerticalContentAlignment="Stretch" Background="Transparent"
BorderThickness="0" ItemContainerStyle="{StaticResource ContainerStyle}"
HorizontalContentAlignment="Stretch" Margin="-3,0,0,0"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}">
</ListBox>
So when the user clicks the button, the value of 1 is set for the ShowFeedbackForm for that item which is when the template showing a feedback form is to be displayed but nothing is happening. My ObservableCollection(one way) bindings are working fine.
In the button click handler, I set the boolean value the bound item and then rebind the datatemplate selector for the listBox.
ListBoxItem currentItem = (ListBoxItem)(listBox1.ItemContainerGenerator.ContainerFromItem(listBox1.Items.CurrentItem));
Activity activityItem = (Activity)listBox1.Items[listBox1.SelectedIndex];
activityItem.ShowFeedbackForm = 1;
listBox1.ItemTemplateSelector = new ActivityFeedDataTemplateSelector();
My ActivityFeedDataTemplateSelector returns the desired template name if the boolean is set.

WPF GridSplitter after resize

Once the gridsplitter is used to resize a grid the row * will not reclaim the space when the other rows are collapsed.
I have the following grid in a master detail view with three rows. A data grid on top a splitter in the middle and a contentcontrol view in the last row. The splitter has a close button on it to collapse the detail. This all works with the exception that once the user resizes using the gridsplitter.
<Grid Margin="3,0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Style="{StaticResource CollapsableRow}"/><!-- Splitter Here -->
<RowDefinition Style="{StaticResource CollapsableRow}"/>
</Grid.RowDefinitions>
The GridSplitter style:
<Style x:Key="gridSplitterStyle" TargetType="{x:Type GridSplitter}">
<Setter Property="Visibility" Value="{Binding IsItemSelected, Converter={StaticResource BoolToShow},ConverterParameter='Visible|Collapsed'}" />
<Setter Property="Width" Value="Auto"/>
<Setter Property="Height" Value="14"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Border.BorderBrush" Value="#FF6593CF" />
<Setter Property="Border.BorderThickness" Value="0,1,0,0" />
<Setter Property="UIElement.SnapsToDevicePixels" Value="True" />
<Setter Property="UIElement.Focusable" Value="False" />
<Setter Property="Control.Padding" Value="7,7,7,7" />
<Setter Property="Cursor" Value="SizeNS" /></Style>
Like I said the collapse works correctly unless the gridsplitter is used to resize. After that the whitespace stays.
EDIT:
H.B. and codenaked had simple and consistant suggestions so and I attempted to implement them w/o success in a data trigger:
<Style x:Key="CollapsableRow" TargetType="{x:Type RowDefinition}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, Converter={StaticResource IsNullConverter}}" Value="True">
<Setter Property="RowDefinition.Height" Value="0"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem, Converter={StaticResource IsNullConverter}}" Value="False">
<Setter Property="RowDefinition.Height" Value="Auto"/>
</DataTrigger>
</Style.Triggers>
</Style>
Since the grid splitter and detail were already being hidden Visibility was the obvious choice to reset the next row definition height.
/// <summary>
/// Grid splitter that show or hides the following row when the visibility of the splitter is changed.
/// </summary>
public class HidableGridSplitter : GridSplitter {
GridLength height;
public HidableGridSplitter()
{
this.IsVisibleChanged += HideableGridSplitter_IsVisibleChanged;
this.Initialized += HideableGridSplitter_Initialized;
}
void HideableGridSplitter_Initialized(object sender, EventArgs e)
{
//Cache the initial RowDefinition height,
//so it is not always assumed to be "Auto"
Grid parent = base.Parent as Grid;
if (parent == null) return;
int rowIndex = Grid.GetRow(this);
if (rowIndex + 1 >= parent.RowDefinitions.Count) return;
var lastRow = parent.RowDefinitions[rowIndex + 1];
height = lastRow.Height;
}
void HideableGridSplitter_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Grid parent = base.Parent as Grid;
if (parent == null) return;
int rowIndex = Grid.GetRow(this);
if (rowIndex + 1 >= parent.RowDefinitions.Count) return;
var lastRow = parent.RowDefinitions[rowIndex + 1];
if (this.Visibility == Visibility.Visible)
{
lastRow.Height = height;
}
else
{
height = lastRow.Height;
lastRow.Height = new GridLength(0);
}
}
You can use animation to solve the row/column definition override by gridsplitter. See my answer to a similar question at GridSplitter overrides ColumnDefinition's style trigger?
If you use the GridSplitter the Heights are no longer Auto but concrete values. You need to manually change the values back either using a style or events and code behind, e.g. this resets a auto-size column on double-click:
private void ColumnSplitter_DoubleClick(object sender, MouseButtonEventArgs e)
{
if (!ColumnTreeView.Width.IsAuto) ColumnTreeView.Width = new GridLength();
}
Based on what you provided, the GridSplitter will resize the previous and next rows. You can see this in action with this code:
<Grid Margin="3,0">
<Grid.RowDefinitions>
<RowDefinition x:Name="row0" Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition x:Name="row2" Height="Auto" />
</Grid.RowDefinitions>
<Border Background="Red" >
<TextBlock Text="{Binding ElementName=row0, Path=Height}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<GridSplitter Grid.Row="1" Style="{StaticResource gridSplitterStyle}" HorizontalAlignment="Stretch" />
<Border Background="Blue" Grid.Row="2" MinHeight="50">
<TextBlock Text="{Binding ElementName=row2, Path=Height}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</Grid>
The last row's size will actually change from Auto to a fixed height. So even if you collapse the content in that row, it will still take up the specified space. You'd need to reset the row to Height="Auto" to truly collapse it with it's content.

WPF Hide Grid Column

I have a xaml grid defined as:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
The first column will contain a TextBlock and the second column a TextBox for data capture.
How can I toggle the visibility of first column?
The solution to my problem was to change the width of the first column to "Auto". Then I set up the bindings of my first textbox so that its Visibility property was set to Collapsed (not hidden) which results in the column not being rendered.
<ColumnDefinition>
<ColumnDefinition.Style>
<Style TargetType="ColumnDefinition">
<Setter Property="Width" Value="*" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsColumnVisible}" Value="False">
<Setter Property="Width" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
Please do implement INotifyPropertyChanged in your ViewModel

Resources