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.
Related
I am trying to bind a value "MaxLines" to the TextBlock's Height property in WP7 app. There is a converter to the binding which is supposed to multiple the LineHeight with the MaxLines and return the expected height. What I am trying to say is I want to control the number of lines being shown in the TextBlock. How will I be able to access the TextBlock's LineHeight property from the converter.
To make this generic I did not want maintain the LineHeights separately or access them from viewModel
Check out this article, Silverlight data binding and value converters, where he explains how to Databind in Silverlight. In the example he uses a ValueConverter with parametervalue.
I think that is what you need, just bind your LineHeight to the parameter. (You can use Blend for that)
You can use the ConverterParameter:
<TextBlock x:Name="MyTextBlock" Height="{Binding ConverterParameter=Height, ElementName=MyTextBlock, Converter={StaticResource SomeConverter}}" Text="{Binding SomeLongText}" />
or pass the whole textblock:
<TextBlock x:Name="MyTextBlock" Height="{Binding Converter={StaticResource ImageFileConverter}, ElementName=DropdownImage}" Text="{Binding SomeLongText}" />
Then inside the controller:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var image = value as TextBlock;
/*do your magic here*/
}
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
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
I am creating a custom WPF control that let's say for simplicity sake has a vertical stack panel with a "title" TextBlock, followed by a ContentPresenter. I want the font size for the "title" to be 5 Points LARGER than the size used in the content, which is inherited by whatever container the user places this control in.
How can I specify a font size in the control template for the header element using a relative value without exposing a property like "TitleFontSize" to the user? I want do "add 5".
I tried using a ScaleTransform on the header text block with mixed results (the text block scaled fine but the orientation was modified - I had the text right-justified and it moved "off the control" area when scaled). Also, I am not sure if scale transform would be approprite here.
A more generic way
Value converter
public class MathConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
{
return (double)value + double.Parse( parameter.ToString() );
}
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
{
return null;
}
}
Converter Resource
<my:MathConverter x:Key="MathConverter" />
XAML
<TextBlock FontSize="{Binding
RelativeSource={RelativeSource AncestorType={x:Type Window}},
Path=FontSize,
Converter={StaticResource MathConverter},
ConverterParameter=2}" />
I did it with an IValueConverter as follows:
Created a class FontSizeConverter that derives from IValueConverter. The Convert method adds 10 to the value, and the ConvertBack method subtracts 10.
public class FontSizeConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (double)value + 12.0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (double)value - 12.0;
}
#endregion
}
Next, I declaried an instance of this class in the XAML template for the control:
<Style.Resources>
<local:FontSizeConverter x:Key="fontSizeConverter"/>
</Style.Resources>
And Finnaly, the FontSize binding uses this converter applied to the inherited FontSize property:
<TextBlock FontSize="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=FontSize, Converter={StaticResource fontSizeConverter}}"
Grid.Row="0" Text="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Date.Day}" HorizontalAlignment="Right" VerticalAlignment="Top" Padding="2" Margin="2" >
</TextBlock>
This works. But I still do not know if this is the correct answer. Let me know if there is a better way, or if this is appropriate.
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.