WPF Hide Grid Column - wpf

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

Related

WPF: Dynamically hiding a grid column doesn't always work correctly using Styles or IValueConverter

I have a Grid with three columns - left, right, and a grid splitter in between. I need to hide one of the panels. What I need is to set Width=0 for two Splitter columns, and for the Panel column. However, this approach works well only from code. When I use styles or value converters, it works only in some cases.
Everything works as expeted until I move the splitter, panels hide but leave white space (for the case #2/Styles), or doesn't hide one of the panels (for case #3/IValueConverter). Only the "code behind" way works correctly in all cases. The GIF image below illustrates the behavior.
The code is shown below. The main idea is to set Width and MaxWidth properties for grid colums to 0, and then back to * for the panel, and to Auto for Splitter.
1. The way it works (code behind):
private void TogglePanelVisibility(bool isVisible)
{
if (isVisible)
{
// Restore saved parameters:
_mainWindow.ColumnPanel.Width = new GridLength(_columnPanelWidth, GridUnitType.Star);
_mainWindow.ColumnPanel.MaxWidth = double.PositiveInfinity;
_mainWindow.ColumnSplitter.Width = new GridLength(_columnSplitterWidth, GridUnitType.Auto);
_mainWindow.ColumnSplitter.MaxWidth = double.PositiveInfinity;
return;
}
// Save parameters:
_columnSplitterWidth = _mainWindow.ColumnSplitter.Width.Value;
_columnPanelWidth = _mainWindow.ColumnPanel.Width.Value;
// Hide panel:
_mainWindow.ColumnPanel.Width = new GridLength(0);
_mainWindow.ColumnPanel.MaxWidth = 0;
_mainWindow.ColumnSplitter.Width = new GridLength(0);
_mainWindow.ColumnSplitter.MaxWidth = 0;
}
2. The way it doesn't work (XAML Style)
<Window.Resources>
<Style x:Key="showColumnStar" TargetType="{x:Type ColumnDefinition}">
<Style.Setters>
<Setter Property="Width" Value="*" />
<Setter Property="MaxWidth" Value="{x:Static system:Double.PositiveInfinity}" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding IsPanelVisible}" Value="False">
<DataTrigger.Setters>
<Setter Property="Width" Value="0" />
<Setter Property="MaxWidth" Value="0" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="showColumnAuto" TargetType="{x:Type ColumnDefinition}">
<Style.Setters>
<Setter Property="Width" Value="Auto" />
<Setter Property="MaxWidth" Value="{x:Static system:Double.PositiveInfinity}" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding IsPanelVisible}" Value="False">
<DataTrigger.Setters>
<Setter Property="Width" Value="0" />
<Setter Property="MaxWidth" Value="0" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- ... -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" Style="{StaticResource showColumnAuto}" />
<ColumnDefinition Width="*" Style="{StaticResource showColumnStar}" />
</Grid.ColumnDefinitions>
<!-- ... -->
</Grid>
3. And the last scenario using value converters
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="{Binding IsPanelVisible, Converter={StaticResource BoolToGridSizeConverter}, ConverterParameter='Auto'}" />
<ColumnDefinition Width="{Binding IsPanelVisible, Converter={StaticResource BoolToGridSizeConverter}, ConverterParameter='*'}" />
</Grid.ColumnDefinitions>
<!-- ... -->
</Grid>
C#:
internal class BoolToGridRowColumnSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var param = parameter as string;
var unitType = GridUnitType.Star;
if (param != null && string.Compare(param, "Auto", StringComparison.InvariantCultureIgnoreCase) == 0)
{
unitType = GridUnitType.Auto;
}
return ((bool)value == true) ? new GridLength(1, unitType) : new GridLength(0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
There is one more thing I learning from debugging the case #3 - on an attempt to show or hide the panel after moving the splitter, the Converted is calling only once.
Could you please tell me what is happening, why the cases #2 and #3 do not work properly?
The full source code of the demo project are on GitHub.
Thank you in advance!
Could you please tell me what is happening, why the cases #2 and #3 do not work properly?
Because the GridSplitter sets the local value of the Width property of the ColumnDefinition and local values always take precedence over style setter values as explained in the docs.
So you have to set the local value of the Width property like you do in the code-behind.
I cloned your Code:
as for #2:
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" Style="{StaticResource showColumnAuto}" />
<ColumnDefinition Width="Auto" Style="{StaticResource showColumnStar}" />
</Grid.ColumnDefinitions>
setting the 3rd column to "Auto" did the trick
for #3:
if you debug your solution and set a breakpoint in your converter, you will see, that after moving your Splitter in the 3rd block, the converter is only reached once, so for the column with the splitter. Moving the splitter destroys your Width-binding on the 3rd Column (as #mm8 pointed out in his answer).
so either you manage to redefine your Binding after the splitter moved (e.g. PreviewMouseLeftButtonUp event) or just let this approach go.

Why is my Fluent Window title staying centered?

I'm using the latest version of Fluent.Ribbon. I've been doing some styling, most of which requires completely replacing the Styles and ControlTemplates, but I've hit a snag. The title of my app is centered in the header bar and I can't get it to move to the left.
My visual tree looks like this:
MainWindow
Grid
Adorner
Grid
DockPanel
PART_Icon
PART_RibbonTitleBar
Grid
PART_HeaderHolder [ContentPresenter]
TextBlock
PART_ItemsContainer
PART_QuickAccessToolbarHolder
I copied the current version of the Fluent:RibbonTitleBar ControlTemplate and Style into my override xaml for modification, but nothing I do makes any difference (yes it is loading my overriding styles.)
When I use the inspector tool in the app, the only elements I can highlight are the innermost TextBlock, which fits the text exactly with no stretch, and the DockPanel several levels above, which stretches the full window width. In the original window ControlTemplate, which you can see here, The RibbonTitleBar is the last element of the DockPanel which has LastChildFill set. The RibbonTitleBar does have a RenderSize of the full width, but then the Grid below it has a RenderSize of 0,0. Then PART_HeaderHolder inside that has a RenderSize that exactly covers the title text.
It doesn't seem to matter if I set HorizontalAlignment on various elements to Left or Stretch. I also tried changing the innermost Grid to other container types such as DockPanel and StackPanel. Nothing changes anything about the layout.
Here's my style overrides for the RibbonTitleBar. The only change I've made is that I moved the QuickAccessToolbar to the end and permanently collapsed it (if I try deleting it, the app crashes looking for it) and I tried defining some columns on the inner Grid to no avail.
<Style TargetType="{x:Type Fluent:RibbonTitleBar}">
<Setter Property="Template"
Value="{DynamicResource RibbonTitleBarControlOverride}" />
<Setter Property="Focusable"
Value="False" />
<Setter Property="VerticalAlignment"
Value="Top" />
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Margin="-2,0"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Text="{Binding}"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="RibbonTitleBarControlOverride"
TargetType="{x:Type Fluent:RibbonTitleBar}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0" x:Name="PART_HeaderHolder"
HorizontalAlignment="Left"
ContentSource="Header"
IsHitTestVisible="False" />
<Fluent:RibbonContextualGroupsContainer Grid.Column="1" x:Name="PART_ItemsContainer"
IsItemsHost="True" />
<ContentPresenter x:Name="PART_QuickAccessToolbarHolder"
ContentSource="QuickAccessToolBar" Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsCollapsed"
Value="True">
<Setter Property="Visibility"
Value="Collapsed"
TargetName="PART_ItemsContainer" />
</Trigger>
<Trigger Property="HideContextTabs"
Value="True">
<Setter Property="Visibility"
Value="Collapsed"
TargetName="PART_ItemsContainer" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>

wpf toggling visibility collapse only works once

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" />

Change ListView.ItemTemplate on subelement change

let's say we have simple data class:
public class Ex {
public string Prop1 {...} // notify property
public string Prop2 {...} // notify property
}
and an ObservableCollection of objects of this class. I want to have this collection displayed in a ListView with seperated DataTemplated which is distinguished by Ex.Prop2 (if it is null or empty then template01 is used, otherwise template02). This property can be changed in runtime so simple "trick" with ListView.ItemTemplateSelector does not work :(
How to achieve this functionality? Is it possible to achieve it any other way than listening to NotifyPropertyChanged on each object of the collection and than changing manually the template?
Thanks for your help.
Below piece of code which I already have:
<ListView x:Name="lstTerms"
ItemsSource="{Binding Game.Words}"
HorizontalContentAlignment="Stretch"
Grid.IsSharedSizeScope="True">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Control.Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<!-- checks if element is null or its Prop2 is null or empty. If so, uses NullTemplate -->
<ListView.ItemTemplateSelector>
<local:MySelectTemplate
NormalTemplate="{StaticResource NormalItemTemplate}"
NullTemplate="{StaticResource NullItemTemplate}" />
</ListView.ItemTemplateSelector>
</ListView>
Instead of using a TemplateSelector, you can have a single DataTemplate containing two Grids, which switch visibility dependent on the property values.
Here is an example:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Background="LightBlue" Name="normalGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Prop1}" Value="{x:Null}">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop1}"></TextBlock>
</Grid>
<Grid Background="Green" Name="nullGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=normalGrid, Path=Visibility}" Value="Visible">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop2}"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Obviously you could replace the TextBlock elements with UserControls representing your two DataTemplates.
If you want, you can also remove the need for the bulky Styles by binding Grid.Visibility to a property (named, for example, IsVisible) on your ViewModel and using a VisibilityConverter.
I usually just use a ContentControl which changes its ContentTemplate based on a DataTrigger. DataTriggers respond to the value getting changed, while DataTemplateSelectors do not
<Style x:Key="SomeStyleKey" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Prop2}" Value="{x:Null}">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Prop2}" Value="">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource SomeStyleKey}" />
</DataTemplate>
</ListView.ItemTemplate>
You could also use a Converter that returns String.IsNullOrEmpty(value) if you wanted a single DataTrigger

Grid ColumnDefinition.Width in DataTemplate

In my project, I've made a control that inherits from Control. It is called DialogHeader and, as its name stands, is for displaying a header on modal, non-resizable dialogs. In truth, it binds by default to its parent Window. The control has a property called IconLocation, i.e. whether the image should be displayed on the left or right side of the control's label:
[Image] [Label] -- or -- [Label] [Image]
The template used with DialogHeader is basically the following:
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="COLN_Left" Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition x:Name="COLN_Right" Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Name="PART_Image" />
<Separator Grid.Column="1"
Visibility="Collapsed" Width="{TemplateBinding SpacerWidth}" />
<TextBlock Grid.Column="2" Name="PART_Text" />
</Grid>
<ControlTemplare.Triggers>
<Trigger Property="ImageLocation" Value="Right">
<Setter TargetName="PART_Image" Property="Grid.Column" Value="2" />
<Setter TargetName="PART_Text" Property="Grid.Column" Value="0" />
<!-- The following doesn't work! Help! -->
<Setter TargetName="COLN_Left" Property="Width" Value="*" />
<Setter TargetName="COLN_Right" Property="Width" Value="auto" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Simply put, when the ImageLocation property is set to Location.Right, the widths of COLN_Left and COLN_Right should be exchanged. So instead of [auto][auto][*], I should have [*][auto][auto].
How can I make this work from the ControlTemplate? If not, is there are way that doesn't involve using C# code?
Thank you in advance.
That part does work, it possibly is just not what you want i presume. Try to remove it and the result should be different.

Resources