Wpf GridSplitter replaces binding on row.height property - wpf

I'm having a hard time with grid splitter. I've bound the RowDefinition.Height dependency property to the clr property of the model as presented below.
<Grid.RowDefinitions>
<RowDefinition Height='{Binding Path=Height, Mode=OneWay}' />
<RowDefinition Height='*' />
</Grid.RowDefinitions>
This works fine just until the GridSplitter is used. When the height of the row is changed manually with GridSplitter, it replaces the binding with the new fixed size (and removes the binding).
Have you got any ideas or workarounds how to create two rows that would be resizable with GridSplitter but still change their height according to the clr property/binding?

I think the problem is that your source Property Height is of type double and RowDefinition.Height is of type GridLength. Use a converter and it'll work TwoWay
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Path=Height,
Mode=TwoWay,
Converter={StaticResource DoubleGridLengthConverter}}"/>
<!--...-->
</Grid.RowDefinitions>
DoubleGridLengthConverter
public class DoubleGridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new GridLength((double)value);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
GridLength gridLength = (GridLength)value;
return gridLength.Value;
}
}
Update
Uploaded my sample application here: http://www.mediafire.com/download.php?pgibb205d65596q
Set the RowDefinition.Height by entering a value in the lower TextBox and resize the RowDefinition.Height with the GridSplitter

Related

How to make two controls exactly the same height

would like two controls to take up all the available space and be of exact same size. Also, when changing one controls visibility to collapsed I want the other control to take up the space. How is it possible?
The following xaml will make sure the controls are of same height and fill the space. But the space is taken by the two rows regardless of the visibility of the controls.
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Name="t1" Grid.Row="0" Text="first" />
<TextBlock Name="t2" Grid.Row="1" Text="second" />
</Grid>
Any help is much appreciated
Hard to do with Silverlight stock panels. Think about implementing a StretchPanel.
About SL StretchPanel implementations:
SO question and answer and blog post linked from there
If you have small fixed amount of this controls then you can also use alternative solution, which is to bind row height to controls visibility.
To do that we can modify your example like this:
<Grid>
<Grid.Resources>
<local:VisibilityToRowHeightConverter x:Key="visibilityToRowHeightConverter" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Visibility, ElementName=t1, Converter={StaticResource visibilityToRowHeightConverter}}" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Name="t1" Text="first" />
<TextBlock Name="t2" Grid.Row="1" Text="second" />
</Grid>
And the converter code:
public class VisibilityToRowHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Visibility)
return ((Visibility) value) == Visibility.Visible
? new GridLength(1, GridUnitType.Star)
: new GridLength(0);
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
So every time t1 collapses, corresponding RowDefinition will set it`s value to 0 and return in to one star when t1 is shown again.
I can't think of a way to do what you want, using any of the built-in panel controls.
A simple hack would be to update the number of row definitions each you update the visibility:
int numberOfRows = grid.Children.Count(item => item.Visibility == Visibility.Visible);
grid.RowDefinitions.Clear();
for (int i=0 ; i<numberOfRows ; i++)
grid.RowDefinitions.Add(new RowDefinition());
This will ensure that the visible children share the vertical space equally.
A more robust approach would be to build your own Panel control -- might be worth the effort if you use this kind of layout a lot.

ContentPresenter Visibility binding inside Grid not working?

I have a following grid:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
...
<ContentPresenter Grid.Row="1" Content="{Binding Path=PredictiveWorkspace}"
Visibility="{Binding Path=ShowPredictiveWorkspace,
Converter={StaticResource boolToVisibility}}"/>
<ContentPresenter Grid.Row="1" Content="{Binding Path=M2Workspace}"
Visibility="{Binding Path=ShowStandardWorkspace,
Converter={StaticResource boolToVisibility}}"/>
...
</Grid>
Those two ContentPresenters has the same Grid.Row definded because only one of them should be visible at once.
I have following boolToVisibility converter:
[ValueConversion(typeof(bool), typeof(System.Windows.Visibility))]
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
{
return System.Windows.Visibility.Visible;
}
else
return System.Windows.Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
And there's the problem: both ContentPresenters are visible! I noticed also that only ShowPredictiveWorkspace property is being read by a app. Breakpoint set on ShowStandardWorkspace getter is never called.
I guess it some stupid mistake but I really can't find it.
EDIT:
public bool ShowStandardWorkspace
{
get { return this._showStandardWorkspace; }
set
{
this._showStandardWorkspace = value;
this.OnPropertyChanged(() => this.ShowStandardWorkspace);
}
}
This is because it does not work to bind visibility with a converter on the ContentPresenter element.
If you change the ContentPresenter to a ContentControl it will work to bind the visibility property with a converter, and then you don't have to nest it within another element.
This is apparently because ContentPresenter is a light weight element that is meant to be used within a ControlTemplate.
From MSDN (with my highlighting):
You typically use the ContentPresenter in the ControlTemplate of a
ContentControl to specify where the content is to be added. Every
ContentControl type has a ContentPresenter in its default
ControlTemplate.
When a ContentPresenter object is in a
ControlTemplate of a ContentControl, the Content, ContentTemplate, and
ContentTemplateSelector properties get their values from the
properties of the same names of the ContentControl. You can have the
ContentPresenter property get the values of these properties from
other properties of the templated parent by setting the ContentSource
property or binding to them.
I have been searching a lot plus I made some tests and I'm pretty sure you cannot control the visibility of a contentpresenter. Plus - if the ViewModel that is going to be presented by the ContentPresenter is null when view is being showed - it does not even read the property using by boolToVisibilityConverter.
I made a simple workaround - I put ContentPresenter inside a Grid (you can use other type of container obviously) and bound Visibility of the Grid to the boolean properties. It works perfectly.
You should use the AncestorType. The DataContext isn't the same, when you use the ContentPresenter, but you can navigate up in the Visual Tree to find it. In Your case:
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type Grid}}, Path=ShowStandardWorkspace}"
Where Grid is the first ancestor by default, and its DataContext is used. If you need a second, third etc. ancestor, use the AncestorLevel property with an int value.
The converter is fine, I think.
possible sources of error:
spelling error ShowStandardWorkspace
OnPropertyChanged("ShowStandardWorkspace") not raised in property setter
ShowStandardWorkspace property simply not set to false
maybe wrong DataContext for the 2nd ContentPresenter

Set a property in XAML to a function

I need to set the property of cotrol which is a dependent of another property of its parent.
I try to explain better my problem with an example. I want to create a toggle switch button that animates a "slider" element into it. The dimensions of the toggle switch is being defined when the usercontrol is inserted into the application window. I want the slider be sized half larger than the switch case. So if the control is large 100, the slider should be 50, or if large 250, the slider should be 125. Then I need a sort of call to a function or something similar:
<UserControl>
<Border Name="switchCase" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Border Name="slider" Width="**Container.Width/2**" ></Border>
</Border>
</UserControl>
Is there any possibilities to achieve this ??
Thanks in advance
Paolo
Yes you need databinding with a converter, such as the following example
xmlns:conv="clr-namespace:MyConverters.Converters"
.......
<UserControl.Resources>
<conv:WidthConvertercs x:Key="widthConv"></conv:WidthConvertercs>
</UserControl.Resources>
<Border Name="switchCase" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Border Name="slider" Width="{Binding ElementName=switchCase, Path=ActualWidth, Converter={StaticResource widthConv}}" Background="DarkMagenta"></Border>
</Border>
Your converter class would be
[ValueConversion(typeof(double), typeof(double))]
class WidthConvertercs : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double withPar = (double)value;
return withPar/2.0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I hope this helps
Out of the box it's not supported by XAML. You can only bind to Properties.
You can write a converter which do the calculation (or you can use
MathConverter)
You can do the calculation in the code behind in event
handlers
If you are following the MVVM pattern you can do the
calculation in the ViewModel (altough it will introduce view
related concepts to the ViewModels which is not always good...)
You can write your own Binding extension

Rounded rectangle in Silverlight

I'm trying to create a rectangle in Silverlight where the corners are rounded. However, I do not explicitly specify the width and the height of the rectangle, which means it adapts to the size of the Grid which contains it (the size of the grid depends on the screen resolution amongst other things, and is not known before hand).
I'd like the RadiusX and RadiusY properties to be percentages of the rectangle's width and height respectively. What would be the cleanest way of doing this? Is there a XAML-only way of doing it (without resorting to code-behind)?
Two files below to download I used for testing this http://dl.dropbox.com/u/8679840/SilverlightApplication1.zip
Best way for reuse is to create a Type converter like
<Grid x:Name="LayoutRoot" Background="White">
<Rectangle x:Name="rectangle"
Width="200" Height="200"
RadiusX="{Binding Width, ElementName=rectangle, Converter={StaticResource myConverter}, ConverterParameter=.1}"
RadiusY="{Binding Height, ElementName=rectangle, Converter={StaticResource myConverter}, ConverterParameter=.1}"
/>
</Grid>
and the code behind
namespace SilverlightApplication1
{
public class PercentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return System.Convert.ToDouble(value) * System.Convert.ToDouble(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
While Justin King's answer works if the Width and Height are known before hand, it doesn't work if they're not set, and the parent control dynamically lays the rectangle out. Unfortunately, in Silverlight, you cannot use Binding with Converters on ActualWidth and ActualHeight, as they are calculated properties. What this means is that when ActualWidth and ActualHeight change, a property changed event is not raised internally, so the binding wouldn't propagate the changes to the source.
Essentially, at this point, the only option is to subscribe to the LayoutUpdated event and calculate and set the RadiusX and RadiusY properties in code-behind.

How do I databind a ColumnDefinition's Width or RowDefinition's Height?

Under the View-Model-ViewModel pattern for WPF, I am trying to databind the Heights and Widths of various definitions for grid controls, so I can store the values the user sets them to after using a GridSplitter. However, the normal pattern doesn't seem to work for these particular properties.
Note: I'm posting this as a reference question that I'm posting as Google failed me and I had to work this out myself. My own answer to follow.
Create a IValueConverter as follows:
public class GridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double val = (double)value;
GridLength gridLength = new GridLength(val);
return gridLength;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
GridLength val = (GridLength)value;
return val.Value;
}
}
You can then utilize the converter in your Binding:
<UserControl.Resources>
<local:GridLengthConverter x:Key="gridLengthConverter" />
</UserControl.Resources>
...
<ColumnDefinition Width="{Binding Path=LeftPanelWidth,
Mode=TwoWay,
Converter={StaticResource gridLengthConverter}}" />
There were a number of gotchas I discovered:
Although it may appear like a double in XAML, the actual value for a *Definition's Height or Width is a 'GridLength' struct.
All the properties of GridLength are readonly, you have to create a new one each time you change it.
Unlike every other property in WPF, Width and Height don't default their databinding mode to 'TwoWay', you have to manually set this.
Thusly, I used the following code:
private GridLength myHorizontalInputRegionSize = new GridLength(0, GridUnitType.Auto)
public GridLength HorizontalInputRegionSize
{
get
{
// If not yet set, get the starting value from the DataModel
if (myHorizontalInputRegionSize.IsAuto)
myHorizontalInputRegionSize = new GridLength(ConnectionTabDefaultUIOptions.HorizontalInputRegionSize, GridUnitType.Pixel);
return myHorizontalInputRegionSize;
}
set
{
myHorizontalInputRegionSize = value;
if (ConnectionTabDefaultUIOptions.HorizontalInputRegionSize != myHorizontalInputRegionSize.Value)
{
// Set the value in the DataModel
ConnectionTabDefaultUIOptions.HorizontalInputRegionSize = value.Value;
}
OnPropertyChanged("HorizontalInputRegionSize");
}
}
And the XAML:
<Grid.RowDefinitions>
<RowDefinition Height="*" MinHeight="100" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{Binding Path=HorizontalInputRegionSize,Mode=TwoWay}" MinHeight="50" />
</Grid.RowDefinitions>
The easiest solution is to simply use string settings for these properties so that WPF will automatically support them using GridLengthConverter without any extra work.
Another possibility, since you brought up converting between GridLength and int, is to create an IValueConverter and use it when binding to Width. IValueConverters also handle two-way binding because they have both ConvertTo() and ConvertBack() methods.

Resources