Reputation: 20697
In some class, I've a constant. The constant is used by some other ViewModel to compute placements inside of a canvas (and some other stuff).
In a view I need to use those constants for 2 RowDefinition
Height.
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="{x:Static local:SomeModel.PART_A_SIZE}"></RowDefinition>
<RowDefinition Height="{x:Static local:SomeModel.PART_B_SIZE}"></RowDefinition>
</Grid.RowDefinitions>
<!-- ... -->
</Grid>
The issue is that the Height is a GridLength
and does not convert from double.
I first tought of using a converter, but they can only be used inside a binding. I cannot use a resource since the value has to be usable outside of this control (outside of WPF actually).
So how can I achieve this without repeating the size at 2 places?
Upvotes: 0
Views: 428
Reputation: 22079
A GridLength
can have different sizing options like Pixel
and Star
. Consequently, depending on your use-case, there might be a custom conversion needed. Here a few examples how to achieve that.
You can actually bind to a static property or field, by specifying it as Source
(with or without converter).
<Grid Margin="0">
<Grid.Resources>
<local:DoubleToGridLengthConverter x:Key="DoubleToGridLengthConverter"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Source={x:Static local:SomeModel.PART_A_SIZE}, Converter={StaticResource DoubleToGridLengthConverter}}"/>
<RowDefinition Height="{Binding Source={x:Static local:SomeModel.PART_B_SIZE}, Converter={StaticResource DoubleToGridLengthConverter}}"/>
</Grid.RowDefinitions>
<!-- ... -->
</Grid>
Then you can use a custom converter (if you need it) to convert your double
values to GridLength
, e.g.:
public class DoubleToGridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is double doubleValue))
return null;
return new GridLength(doubleValue, GridUnitType.Star);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("This conversion is not supported.");
}
}
I first tought of using a converter, but they can only be used inside a binding.
Indeed, value converters are used in bindings, but the conversion can be done in other ways, too, if you are not dealing with changing properties anyway or use constants only.
A rather elegant way could also be to create a custom markup extension with a custom conversion.
public class CustomGridLengthExtension : StaticExtension
{
public CustomGridLengthExtension()
{
}
public CustomGridLengthExtension(string member) : base(member)
{
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if(!(base.ProvideValue(serviceProvider) is double value))
return null;
return Convert(value);
}
private static GridLength Convert(double value)
{
return new GridLength(value, GridUnitType.Star);
}
}
Of course you can extend this to parametrize the extension with the grid unit type or anything else.
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="{local:CustomGridLength local:SomeModel.PART_A_SIZE}"/>
<RowDefinition Height="{local:CustomGridLength local:SomeModel.PART_B_SIZE}"/>
</Grid.RowDefinitions>
<!-- ... -->
</Grid>
Just for completeness, since you are using a static value anyway, you could also define the corresponding GridLength
pendants in XAML using an ObjectDataProvider
if you do not need a conversion that changes the double
values itself, but only the unit type (Auto
, Star
, Pixel
). You cannot define GridLength
s directly, as they require constructor parameters and do not expose any properties to set after creation.
<Grid Margin="0">
<Grid.Resources>
<ObjectDataProvider x:Key="PART_A_GRID_LENGTH"
ObjectType="{x:Type GridLength}">
<ObjectDataProvider.ConstructorParameters>
<x:Static MemberType="{x:Type local:SomeModel}" Member="PART_A_SIZE"/>
<x:Static MemberType="{x:Type GridUnitType}" Member="Star"/>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
<ObjectDataProvider x:Key="PART_B_GRID_LENGTH"
ObjectType="{x:Type GridLength}">
<ObjectDataProvider.ConstructorParameters>
<x:Static MemberType="{x:Type local:SomeModel}" Member="PART_B_SIZE"/>
<x:Static MemberType="{x:Type GridUnitType}" Member="Star"/>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Source={StaticResource PART_A_GRID_LENGTH}}"/>
<RowDefinition Height="{Binding Source={StaticResource PART_B_GRID_LENGTH}}"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="Red"/>
<Border Grid.Row="1" Background="Blue"/>
<!-- ... -->
</Grid>
Upvotes: 0
Reputation: 7908
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Path=(local:SomeModel.PART_A_SIZE)}"></RowDefinition>
<RowDefinition Height="{Binding Path=(local:SomeModel.PART_B_SIZE)}"></RowDefinition>
</Grid.RowDefinitions>
...
</Grid>
Auto-update of static property binding is also supported. To do this, you need to either implement the event of the same name with the endwith Changed. This is more typical for Forms, but works for WPF as well. Or a StaticPropertyChanged event with a property name.
public static double PART_A_SIZE
{
get => _part_a_size;
set
{
if(!Equals(_part_a_size, value))
{
_part_a_size = value;
PART_A_SIZEChanged?.Invoke(null, EventArgs.Empty);
}
}
}
private static double _part_a_size;
public static event EventHandler PART_A_SIZEChanged;
public static double PART_A_SIZE
{
get => _part_a_size;
set
{
if(!Equals(_part_a_size, value))
{
_part_a_size = value;
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(nameof(PART_A_SIZE)));
}
}
}
private static double _part_a_size;
public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
If you need bindings to Constants, then you need to pass them to the Source of the bindings.
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Source={x:Static local:SomeModel.PART_A_SIZE}}"></RowDefinition>
<RowDefinition Height="{Binding Source={x:Static local:SomeModel.PART_B_SIZE}}"></RowDefinition>
</Grid.RowDefinitions>
...
</Grid>
Upvotes: 0
Reputation: 128042
You could bind a constant
public class SomeModel
{
public const double PART_A_SIZE = 100;
}
or a static field
public class SomeModel
{
public static double PART_A_SIZE = 100;
}
like
<RowDefinition Height="{Binding Source={x:Static local:SomeModel.PART_A_SIZE}}"/>
and would thus benefit from built-in type conversion from double
to GridLength
.
Assuming you have a static property
public class SomeModel
{
public static double PART_A_SIZE { get; } = 100;
}
you could also bind it like
<RowDefinition Height="{Binding Path=(local:SomeModel.PART_A_SIZE)}"/>
Upvotes: 2