Can we manipulate (subtract) the value of a property while template bidning? - wpf

I am currently defining few grids as following:
<Grid.RowDefinitions>
<RowDefinition Height="{TemplateBinding Height-Height/5}"/>
<RowDefinition Height="{TemplateBinding Height/15}"/>
<RowDefinition Height="{TemplateBinding Height/20}"/>
<RowDefinition Height="{TemplateBinding Height/6}"/>
</Grid.RowDefinitions>
While the division works fine , the subtraction isn't yielding the output.
Ialso tried like following:
<RowDefinition Height="{TemplateBinding Height-(Height/5)}"/>
Still no result. Any suggestions plz.
Thanks,
Subhen
**
Update
**
Now In My XAML I tried implementing the IvalueConverter like :
<RowDefinition Height="{TemplateBinding Height, Converter={StaticResource heightConverter}}"/>
Added the reference as
<local:medieElementHeight x:Key="heightConverter"/>
In side generic.cs I have coded as following:
public class medieElementHeight : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//customVideoControl objVdeoCntrl=new customVideoControl();
double custoMediaElementHeight = (double)value;//objVdeoCntrl.customMediaPlayer.Height;
double mediaElementHeight = custoMediaElementHeight - (custoMediaElementHeight / 5);
return mediaElementHeight;
}
#region IValueConverter Members
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
But getting the exception Unknown Element Height in the element RowDefination.
Updating Code #Tony
<Style TargetType="local:customVideoControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:customVideoControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{TemplateBinding Height-Height/5}"/>
<RowDefinition Height="{TemplateBinding Height/15}"/>
<RowDefinition Height="{TemplateBinding Height/20}"/>
<RowDefinition Height="{TemplateBinding Height/6}"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<MediaElement x:Name="customMediaPlayer"
Source="{TemplateBinding CustomMediaSource}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"
Grid.Row="0" Grid.ColumnSpan="3"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now , My implementing XAML file contains is as follows:
<customMediaElement:customVideoControl x:Name="custMediaElement" Width="400" Height="300" nextBtnEvent="custMediaElement_nextBtnEvent" prevBtnEvent="custMediaElement_prevBtnEvent" Visibility="Collapsed"/>
Now, I want to substract or divide the values depending on the height of custMediaElement.

You have posed several good questions; however, we often get caught up in finding a solution while overlooking the original problem. Before attempting to answer your questions I would like to explorer you layout expectations.
Your provided code suggests that you have a custom control (customVideoControl) whose instance is a height of 300px. The ControlTemplate applied to this control has 4 rows, which have calculated heights based on the instance height. Based on these settings your 4 rows would have the following values:
Row 0: 240
Row 1: 20
Row 2: 60
Row 3: 50
These total to 370px, which is 70px larger than the control. This means that Row 3 would be completely hidden from view, and Row 2 would be clipped down to the top 40px. I assume that this is not your intention. If this is your intention, then the answers below will hopefully help you on your path. If your intention is to scale the row heights based on a ratio, then you can use star sizing. Your suggested ratio would use the following settings:
<Grid.RowDefinitions>
<RowDefinition Height="240*"/>
<RowDefinition Height="20*"/>
<RowDefinition Height="60*"/>
<RowDefinition Height="50*"/>
</Grid.RowDefinitions>
If you would still like to measure the height of the rows then there are a few corrections that you need to make.
Mathematical operations cannot be performed within markup extensions (the curly braces). Your division approach may not be throwing a xaml parse exception, but I doubt that it is working correctly. A value converter is necessary to accomplish what you want.
TemplateBinding is really just a light-weight version of a RelativeSource binding. Since TemplateBinding is light-weight it doesn't allow for Converters.
To get your expected behavior you need to use a Binding with a RelativeSource. Therefore the code you want looks something like this:
<RowDefinition Height="{Binding Path=Height,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource DivisionConverter},
ConverterParameter=15}"
/>
Where DivisionConverter is the key of a custom converter. The ConverterParameter in this example allows the developer to pass in a denominator, instead of having to create a separate converter for each number.
Here's an example of the custom DivisionConverter you will need to create:
public class DivisionConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Default to 0. You may want to handle divide by zero
// and other issues differently than this.
double result = 0;
// Not the best code ever, but you get the idea.
if (value != null && parameter != null)
{
try
{
double numerator = (double)value;
double denominator = double.Parse(parameter.ToString());
if (denominator != 0)
{
result = numerator / denominator;
}
else
{
// TODO: Handle divide by zero senario.
}
}
catch (Exception e)
{
// TODO: Handle casting exceptions.
}
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
If you wanted to incorporate both division and subtraction in the same Binding you would need to either create a special converter, or use a MultiBinding (which would also require you to create a special MultiBindingConverter).

I think you have to use Converter for this purpose

Instead of subtracting one-fifth, why don't you just multiply by .8?

Related

WPF binding to an observable collection with hash key as index

The following code:
<UserControl x:Class="MyProgram.Views.MyControlContainer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyProgram.Views"
xmlns:common_model="clr-namespace:MyProgram.Models;assembly=CommonTitan"
mc:Ignorable="d"
>
<Grid Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<local:MyControl x:Name="MyControl1" DataContext="{Binding MyControlCollection[8]}" Grid.Row="0" Grid.Column="0"/>
<local:MyControl x:Name="MyControl2" DataContext="{Binding MyControlCollection[9]}" Grid.Row="0" Grid.Column="1"/>
<local:MyControl x:Name="MyControl3" DataContext="{Binding MyControlCollection[10]}" Grid.Row="0" Grid.Column="2"/>
</Grid>
</UserControl>
Appropriately (as far as i can tell) binds me to the items that i specify in a collection. In real use it will use an observable dictionary and those indexes are hash values that i won't be able to hand code in. Is it possible to pass in the 2 known parameters into some sort of converter in order to get the hash? or even pass in the 2 values to the converter which will end up returning the item out of the observable dictionary?
You can pass 2 known value as converter parameters, for example:
<local:MyControl x:Name="MyControl1" DataContext="{Binding MyControlCollection, Converter={StaticResource CollectionConverter}, ConverterParameters='8.9'}" />
And in your converter class you can use it here, parse the parameter as string:
public class CollectionConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter != null)
{
string[] param = (parameter as string).split('.');
if (int.Parse(param[0]) == 8)
return (value as ObservableCollection)[8];
else
return (value as ObservableCollection)[0];
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new InvalidOperationException("cannot cast the value");
}
}

How can I have only a * notation wpf grid column resize when the containing control is resized and not clip other columns?

I have a grid with certain column settings:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".25*" MinWidth="200"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="240" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="100*" />
</Grid.RowDefinitions>
....Omitting content to keep this simple....
</grid>
When I resize the width of the containing control, the center column resizes as expected, to a point. Then it will start to clip the third column for no apparent reason (there's still room for the center column to shrink). How can I force WPF to only resize the center column, and only clip the third column if the center column's width is at 0?
EDIT:
Try using a MultiValueConverter to minimize the MaxWidth of the second column to the available space in the grid. The code below should do the trick:
Xaml:
<Grid.ColumnDefinitions>
<ColumnDefinition Name="col1" Width=".25*" MinWidth="200"/>
<ColumnDefinition Name="col2" Width="*">
<ColumnDefinition.MaxWidth>
<MultiBinding Converter="{StaticResource GridWidthConverter}">
<Binding ElementName="col1" Path="MinWidth"/>
<Binding ElementName="col3" Path="Width"/>
<Binding ElementName="Control" Path="ActualWidth"/>
</MultiBinding>
</ColumnDefinition.MaxWidth>
</ColumnDefinition>
<ColumnDefinition Name="col3" Width="240" />
</Grid.ColumnDefinitions>
Converter:
public class GridWidthConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var col1 = System.Convert.ToDouble(values[0]);
var col2 = System.Convert.ToDouble(((GridLength)values[1]).Value);
var control = System.Convert.ToDouble(values[2]);
var maxWidth = control - (col1 + col2);
return maxWidth > 0 ? maxWidth : 0;
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
#endregion
}
You may want to add some error checking to the converter but it should give you the idea.
I ended up figuring out the problem. Richard E led me in the right direction. Apparently the act of setting a * notation on a column and a minimum width caused the behavior I was experiencing. Specifically, when column 1 stopped shrinking due to hitting the minimum width, column 2 only continued to shrink at a rate that it would have if column 1 continued to shrink as well. This version of the xaml works correctly:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="240" />
</Grid.ColumnDefinitions>
I don't know if the behavior I encountered was intentional or not.
Thanks for the help!

WPF custom control - specific how to get the correct width ratio

I'm in the process of creating a control to represent a employee work shift. The shift can be of different lengths and with none or more breaks.
Mockup prototype GUI of a 9 hour work shift with an 1 hour break
The following questions refer to the blue bar in the prototype:
Since the control need to resize perfectly, then a fixed size approach is not an option. My first thought was to use a grid with columns that has the same width ratio as the time spans. So if you look at the prototype above there would be 3 columns with a width of: 240*, 60*, 240*. These numbers are equal to the total minutes of each time span.
If I add a dependency property that hold, lets call them TimeSpanItems (TSI). Each TSI has a TimeSpan property. Is it then possible to bind this to the grid and its column definitions? The number of columns must change as TSI are added and also each column must change its width ratio to match the number of minutes.
Am I thinking about this the wrong way? Is it doable? Or is it a items control that I need that resizes its items when the control is resized?
At the moment I have different questions that I yet haven't found the answer to... and probably a lot of questions that I don't know yet what they are. Any help would be most welcome.
I did something that could be of help to you, the final result is this control:
Basically lists tasks, inside these tasks it's showing specific alocation by the "entities" that are listed on top.
The Task list is an ItemsControl that looks like this:
<ItemsControl ItemsSource="{Binding Path=Tarefas}" Grid.Column="4">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition />
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="SlateGray" Margin="5 5 5 5">
<TextBlock Grid.ColumnSpan="100" Text="Cronograma" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" />
</Border>
<ItemsPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Style="{StaticResource CronogramaGrid}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Converter={StaticResource InicioPerc}}" />
<ColumnDefinition Width="{Binding Converter={StaticResource MeioPerc}}" />
<ColumnDefinition Width="{Binding Converter={StaticResource FimPerc}}" />
</Grid.ColumnDefinitions>
<Border Background="SlateGray" Grid.Column="1">
<Border Background="White" Margin="2 2 2 2">
<Grid Margin="2 2 2 2">
<Border Background="SlateGray" />
<ItemsControl ItemsSource="{Binding Path=PercentagensParticipacao}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="{Binding Path=GridHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Path=Actual, Converter={StaticResource GridLengthStarConverter}}" />
<ColumnDefinition Width="{Binding Path=Restante, Converter={StaticResource GridLengthStarConverter}}" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{Binding Path=Index, Converter={StaticResource IndexColorConverter}}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Border>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The column width's are given by that converter. At the time this control was built we didn't had the ability to extend the Model, so a converter was written, if this was done now, the Model would have been extended and a Getter would have been writen.
The Converters look like this:
public class DataInicioPercentagemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var tarefa = (Tarefa)value;
var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;
double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
double diasTarefaInicio = tarefa.Inicio.Subtract(candidatura.DataInicio).Days;
return new GridLength((diasTarefaInicio / totalDias * 100), GridUnitType.Star);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
public class DataMeioPercentagemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var tarefa = (Tarefa)value;
var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;
double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
double diasTarefa = tarefa.Fim.Subtract(tarefa.Inicio).Days;
return new GridLength((diasTarefa / totalDias * 100), GridUnitType.Star);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
public class DataFimPercentagemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var tarefa = (Tarefa)value;
var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;
double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
double diasTarefaFim = candidatura.DataFim.Subtract(tarefa.Fim).Days;
return new GridLength((diasTarefaFim / totalDias * 100), GridUnitType.Star);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
I'm going to answer the question: Is it doable? My initial thought was that I didn't want the calculate the resizing. I wanted a grid to solve this for me. So I am going to ignore all the other questions regarding if this should be another control type or if this the "right" way to do it.
Here goes... first I use a control template where the uxMainContentGrid is the grid that is going to be modified when items are added.
<Style TargetType="{x:Type wpflib:RatioPresenterControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type wpflib:RatioPresenterControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}">
<Grid x:Name="uxMainContentGrid">
<Grid.ColumnDefinitions>
<!-- Columns and ratio is set in code behind -->
</Grid.ColumnDefinitions>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
The items that I used to set the width ratio look like this.
Public Class RatioItem
Public Property Value As Double
Public Property Brush As Brush
End Class
And the RatioItems are added through this dependency property with a default value of an empty list of RatioItems.
Public Shared ReadOnly RatioItemsProperty As DependencyProperty = _
DependencyProperty.Register("RatioItems", GetType(IEnumerable(Of RatioItem)),
GetType(RatioPresenterControl),
New FrameworkPropertyMetadata(New List(Of RatioItem), AddressOf OnRatioItemsPropertyChanged))
The dependency property that has the following value changed callback method. When you bind to the dependency property, then this callback fires before the template has been applied. This callback only reconstructs the grid columns if the control template has been set.
Public Shared Sub OnRatioItemsPropertyChanged(sender As Object, e As DependencyPropertyChangedEventArgs)
If (_MainContentGrid IsNot Nothing) Then
Dim ratioItems As IEnumerable(Of RatioItem) = TryCast(e.NewValue, IEnumerable(Of RatioItem))
ReconstructGridColumns(ratioItems, _MainContentGrid)
End If
End Sub
The following code retrieves the grid when the control template is applied. The thing here is to store the grid so that it can be accessed later if the RatioItems property change. When the template is applied the binding is already in affect so the grid columns are constructed.
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
_MainContentGrid = TryCast(Me.Template.FindName("uxMainContentGrid", Me), Grid)
ReconstructGridColumns(Me.RatioItems, _MainContentGrid)
End Sub
This method does all the "constructing"...
Private Shared Sub ReconstructGridColumns(ByVal ratioItems As IEnumerable(Of RatioItem), ByVal mainContentGrid As Grid)
Dim newContent As Rectangle
Dim columnCount As Integer = 0
mainContentGrid.ColumnDefinitions.Clear()
For Each item In ratioItems
mainContentGrid.ColumnDefinitions.Add(New ColumnDefinition() With {.Width = New GridLength(item.Value, GridUnitType.Star)})
newContent = New Rectangle() With {.Name = "item" & columnCount, .Fill = item.Brush}
mainContentGrid.Children.Add(newContent)
Grid.SetColumn(newContent, columnCount)
columnCount += 1
Next
End Sub
And there you have it. It can be done. Now to the discussion whether it is the "right" way to do it... :)

WPF unwanted grid splitter behaviour

I have a simple grid with 3 columns (one of which contains a grid splitter). When resizing the grid and the left column reaches its minimum width, instead of doing nothing it increases the width of the right column. Could anyone help me stop this?
I can't set the max width of the right column, because the grid itself also resizes.
Here's some sample code that shows the problem. While resizing, move the mouse over the red area:
XAML:
<Grid DockPanel.Dock="Top" Height="200">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="200" Width="*" />
<ColumnDefinition Width="3" />
<ColumnDefinition MinWidth="120" Width="240" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Fill="Red" Grid.Row="0" Grid.Column="0" />
<DockPanel LastChildFill="True" Grid.Row="0" Grid.Column="2" >
<Rectangle DockPanel.Dock="Right" Width="20" Fill="Blue" />
<Rectangle Fill="Green" />
</DockPanel>
<GridSplitter Background="LightGray" Grid.Row="0" Grid.Column="1" Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
I know this is a bit late, but I just run into this problem, and here is my solution. Unfortunately it is not general enough, it only works for a grid with two columns, but it can probably be adapted farther. However, it solves the described problem and my own, so here goes:
The solution consists in a hack or workaround, however you want to call it. Instead of declaring MinWidth for both the left and right column, you declare a MinWidth and a MaxWidth for the first column. This means that the GridSplitter won't move right of a defined location. So far, so good.
The next problem is that if we have a resizable container (the window in my case), this is not enough. It means that we cannot enlarge the left column as much as we want, even though there might be plenty of space for the second one. Fortunately, there is a solution: binding on the Grid ActualWidth and using an addition converter. The converter parameter will actually be the desired MinWidth for the right column, obviously the negative value, since we need to subtract it from the Grid Width. You could also use a SubtractConvertor, but that is up to you.
Here goes the xaml and code:
<Grid Background="{DynamicResource MainBackground}" x:Name="MainGrid" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" MinWidth="100" MaxWidth="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType=Grid}, Converter={Converters:AdditionConverter}, ConverterParameter=-250}" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<GridSplitter Width="3" VerticalAlignment="Stretch" Grid.Column="0"/>
<!-- your content goes here -->
</Grid>
and the converter:
[ValueConversion(typeof(double), typeof(double))]
public class AdditionConverter : MarkupExtension, IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double dParameter;
if (targetType != typeof(double) ||
!double.TryParse((string)parameter, NumberStyles.Any, CultureInfo.InvariantCulture, out dParameter))
{
throw new InvalidOperationException("Value and parameter passed must be of type double");
}
var dValue = (double)value;
return dValue + dParameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
#region Overrides of MarkupExtension
/// <summary>
/// When implemented in a derived class, returns an object that is set as the value of the target property for this markup extension.
/// </summary>
/// <returns>
/// The object value to set on the property where the extension is applied.
/// </returns>
/// <param name="serviceProvider">Object that can provide services for the markup extension.
/// </param>
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
#endregion
}
I hope this helps,
Mihai Drebot
This approach is a hack but can be used for the desired effect. Try putting an event handler on the Window.SizeChanged event to set the maxWidth of the first columndef. For example:
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
int borderBuffer = 9;
// AppWindow Size Splitter Width Far Column Min FudgeFactor
Col0.MaxWidth = e.NewSize.Width - Col1.Width.Value - Col2.MinWidth - borderBuffer;
}
I needed to use this method to prevent a third party control in the top row of the grid from having some undesirable wrapping resulting from when the gridSplitter disregards the min/max width of the column with width set to "Auto". The borderBuffer may need to be adjusted for your case. The borderBuffer in my case didn't make perfect sense based on the geometry/column/border widths - it was just the magic number that worked for my layout.
If someone can come up with a cleaner solution, I'd love to use it instead. This solution reminds me of painfully trying to force VB6 to resize controls - yuck. But for now, it beats having items on the top row of my grid from getting hidden due to unexpected wrapping.
I got this undesirable behaviour to stop by changing the * in the columndefinition to Auto and the 240 to *.

Hide grid row in WPF

I have a simple WPF form with a Grid declared on the form. This Grid has a bunch of rows:
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="30" />
<RowDefinition Height="Auto" Name="rowToHide" />
<RowDefinition Height="Auto" MinHeight="30" />
</Grid.RowDefinitions>
The row named rowToHide contains a few input fields and I want to hide this row after I detect I don't need these fields. It's simple enough to just set Visibility = Hidden to all items in the row, but the row still takes up space in the Grid. I tried setting Height = 0 to the items, but that didn't seem to work.
You can think of it like this: You have a form, in there you have a drop down saying "Payment Type", and if the person selects "Cash", you want to hide the row containing the Card details. It isn't an option to start the form with this hidden already.
Row does not have a Visibility property, so as others have said, you need to set the Height. Another option is to use a converter, in case you need this functionality in many views:
[ValueConversion(typeof(bool), typeof(GridLength))]
public class BoolToGridRowHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value == true) ? new GridLength(1, GridUnitType.Star) : new GridLength(0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ // Don't need any convert back
return null;
}
}
And then in the appropriate view <Grid.RowDefinition>:
<RowDefinition Height="{Binding IsHiddenRow, Converter={StaticResource BoolToGridRowHeightConverter}}"></RowDefinition>
The best and clean solution to collapse rows or columns is to use a DataTrigger so in your case:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="30" />
<RowDefinition Name="rowToHide">
<RowDefinition.Style>
<Style TargetType="{x:Type RowDefinition}">
<Setter Property="Height" Value="Auto" />
<Style.Triggers>
<DataTrigger Binding="{Binding SomeBoolProperty}" Value="True">
<Setter Property="Height" Value="0" />
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
<RowDefinition Height="Auto" MinHeight="30" />
</Grid.RowDefinitions>
</Grid>
You can also do this by referencing the Row in the Grid and then changing the Height of the row itself.
XAML
<Grid Grid.Column="2" Grid.Row="1" x:Name="Links">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
</Grid>
VB.NET
If LinksList.Items.Count > 0 Then
Links.RowDefinitions(2).Height = New GridLength(1, GridUnitType.Star)
Else
Links.RowDefinitions(2).Height = New GridLength(0)
End If
Whilst the Collapsing of the elements within the Grid also works, this is a bit simpler if you have many items in the Grid that does not have an enclosing element that can be collapsed. This would provide a good alternative.
For reference, Visibility is a three-state System.Windows.Visibility enumeration:
Visible - The element gets rendered and participates in layout.
Collapsed - The element is invisible and does not participate in layout. Effectively giving it a height and width of 0 and behaving as if it doesn't exist.
Hidden - The element is invisible but continues to participate in layout.
See this tip and other tips on the WPF Tips and Tricks thread.
Instead of fiddling with the Grid Row, you can set the Visibility property of the Controls (fields in the row) to "Collapsed". This will ensure that the controls don't take up any space and if you have Grid Row Height="Auto", then the row will be hidden as all the controls in the row have Visibility="Collapsed".
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" Name="rowToHide" />
</Grid.RowDefinitions>
<Button Grid.Row=0 Content="Click Me" Height="20">
<TextBlock Grid.Row=1
Visibility="{Binding Converter={StaticResource customVisibilityConverter}}" Name="controlToHide"/>
</Grid>
This method is better because the Visibility of the controls can be bound to some property with the help of a Converter.
Simply do this:rowToHide.Height = new GridLength(0);
if u will use visibility.Collapse then u have to set it for every member of the row.
Set the Row's content visibility to Visibility.Collapsed instead of Hidden. This will make the content stop taking up space, and the row will shrink appropriately.
I had a similar idea by inheriting RowDefinition (just for interest)
public class MyRowDefinition : RowDefinition
{
private GridLength _height;
public bool IsHidden
{
get { return (bool)GetValue(IsHiddenProperty); }
set { SetValue(IsHiddenProperty, value); }
}
// Using a DependencyProperty as the backing store for IsHidden. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsHiddenProperty =
DependencyProperty.Register("IsHidden", typeof(bool), typeof(MyRowDefinition), new PropertyMetadata(false, Changed));
public static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var o = d as MyRowDefinition;
o.Toggle((bool)e.NewValue);
}
public void Toggle(bool isHidden)
{
if (isHidden)
{
_height = this.Height;
this.Height = new GridLength(0, GridUnitType.Star);
}
else
this.Height = _height;
}
}
Now you can use it as following:
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<my:MyRowDefinition Height="4*" IsHidden="false" x:Name="RowToHide" />
<RowDefinition Height="*" />
<RowDefinition Height="60" />
</Grid.RowDefinitions>
and toggle with
RowToHide.IsHidden = !RowToHide.IsHidden;

Resources