Binding to Transforms in a ControlTemplate - silverlight

I'm trying to create a custom control in Silverlight that dynamically scales an element in it's ControlTemplate. First attempt of the ControlTemplate looks something like this:
<ControlTemplate TargetType="controls:ProgressBar">
<Grid>
<Rectangle x:Name="TrackPart" Fill="{TemplateBinding Background}" HorizontalAlignment="Left" />
<Rectangle x:Name="ProgressPart" Fill="Blue" >
<Rectangle.RenderTransform>
<ScaleTransform ScaleX="{TemplateBinding Progress}" />
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
</ControlTemplate>
However, this forum thread states that TemplateBinding only works on derivatives of FrameworkElements. ScaleTransform is not a FrameworkElement. Is there a work around for this? Any best practices for this sort of situation out there?

Rather than binding the ScaleX and ScaleY properties of the RenderTransform, you can bind the RenderTransform itself.
The problem is that the source is a double value, and you need a Transform. So you need to be able to convert a double to a ScaleTransform. You can create an IValueConverter to do that:
public class TransformConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double)
{
double d = (double)value;
return new ScaleTransform { ScaleY = d, ScaleX = d };
}
else
{
return new ScaleTransform();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can't specify an IValueConverter to use in a TemplateBinding, so you can use a regular Binding with RelativeSource as TemplatedParent. Like this:
<Rectangle x:Name="ProgressPart" Fill="Blue"
RenderTransform="{Binding Path=Progress, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource converter1}}" >
and you need to place the IValueConverter in the resources of ControlTemplate's root, in scope of the Binding:
<ControlTemplate TargetType="controls:ProgressBar">
<Grid>
<Grid.Resources>
<local:TransformConverter x:Key="converter1" />
</Grid.Resources>

Assuming that you are always using simple items like a rectangle, you could bind the rectangle's height and width to the progress, and then use a binding converter to adjust the value accordingly

Related

Set Width based on other control Width

I am trying to set the Width of an image of 600px width, based on the Width of another control in the same Grid column (Grid.Column="0"). Is there a way where I can capture the Width of that element (MenuControl) and put that number in the image Width to get a lower value than 600px?
<DockPanel Grid.Row="1" Grid.Column="0">
<Image Source="/Assets/combanc.png" Margin="10" Width="{Binding ElementName=MenuControl, Path=ActualWidth}" />
</DockPanel>
If I understand your question correctly, you want to bind the size of another control, but the image should not exceed a maximum size of 600, regardless of the size of the other control. You can create a value converter that returns the minimum of the bound size and a parametrized maximum.
public class MaximumSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is double size) ||
!(parameter is string maximumText) ||
!double.TryParse(maximumText, out var maximum))
return Binding.DoNothing;
return Math.Min(size, maximum);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Then you can create an instance of the converter in a resource dictionary in scope, e.g. Grid.Resources and use it in the Width binding of Image. Specify the converter by referencing it in the Converter property and set the maximum as ConverterParameter.
<Grid>
<Grid.Resources>
<local:MaximumSizeConverter x:Key="MaximumSizeConverter"/>
</Grid.Resources>
<!-- ...your other markup. -->
<DockPanel Grid.Row="1" Grid.Column="0">
<Image Source="/Assets/combanc.png" Margin="10" Width="{Binding ElementName=MenuControl, Path=ActualWidth, Converter={StaticResource MaximumSizeConverter}, ConverterParameter=600}" />
</DockPanel>
</Grid>

Change a Path's Fill color depending on a bool property

I have a DataTemplate with a Path in a ResourceDictionary and I want to change the Fill color for the Path (would choose between two colors) depending on a bool property from the viewmodel.
<DataTemplate x:Key="FileIcon">
<Path Data="M20.8573547,8.0085467..." Fill="#F0F1F3" Width="30" Height="30"/>
</DataTemplate>
I presume, I need to use some converter but not sure how to write the XAML code for it. Something like this?
<Path Fill="{Binding MyBoolProperty, RelativeSource={RelativeSource FindAncestor, AncestorType=Path}, Converter={StaticResource BoolToColorConverter}}"/>
The Path isn't an ancestor of itself. If the viewmodel is the Path's DataContext, a conventional binding should suffice:
<Path
Fill="{Binding MyBoolProperty, Converter={StaticResource BoolToColorHiddenConverter}}"
/>
You could also skip the converter and use a Style trigger. Note that the default Fill is no longer set as an attribute in this version; if it is, then it'll override anything the Style does.
<Path
Data="M20.8573547,8.0085467..."
Width="30"
Height="30"
>
<Path.Style>
<Style TargetType="Path">
<Setter Property="Fill" Value="#F0F1F3" />
<Style.Triggers>
<DataTrigger
Binding="{Binding MyBoolProperty}"
Value="True"
>
<Setter Property="Fill" Value="FluorescentBeige" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
If you want to use a converter, you can follow this example code for making one:
1. Make a new class
2. Use the following namespaces:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
3. Inherit and implement the IValueConverter Interface
4. In the Convert function, evaluate the value parameter and return the corresponding color you want
Example Code
class BoolToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value == true)
{
// return the color you want
}
else
{
// return the color you want
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Placing a PopUp to the top of the mouse-cursor position

I am working on an interactive chart, where I am displaying a popup with more information, when the user clicks on a data-point.This works fine so far and this is the popup definition:
<Popup IsOpen="{Binding PopupViewModel.IsOpen}"
Placement="Mouse"
HorizontalOffset="-150">
<Popup.Resources>
<DataTemplate DataType="{x:Type ViewModels:DataPointPopUpContentViewModel}">
<Views:DataPointPopUpContentView/>
</DataTemplate>
</Popup.Resources>
<Border BorderThickness="1" BorderBrush="Black" Background="White">
<ContentPresenter Content="{Binding PopupViewModel}" />
</Border>
</Popup>
The default placement of the popup, when using Placement="Mouse" is at the bottom right of the mouse-cursor. However I want the popup to be placed just at the top edge the mouse-cursor. As you can see I have achieved the horizontal centering by setting HorizontalOffset="-150", which is have of the fixed popup-width (Width=300). For the vertical placement I have the problem, that the popup-height is not fixed, since I am displaying an image of variable size and aspect-ratio inside it. I have therefore tried to set VerticalOffset to the ActualHeight of the pupup by adding VerticalOffset="{Binding ActualHeight}". This does not work unfortunately. Any ideas on what I am doing wrong and how to achieve my goal?
First of all you need a converter:
public class MultiplyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is double && parameter is double)
{
return ((double)value) * ((double)parameter);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then try to bind the VerticalOffset property to the ActualHeight of the Popup's child:
<Window.Resources>
<local:MultiplyConverter x:Key="MultiplyConverter" />
<sys:Double x:Key="Factor">-.5</sys:Double>
</Window.Resources>
<Popup IsOpen="{Binding PopupViewModel.IsOpen}"
Placement="Mouse"
HorizontalOffset="-150">
<Popup.VerticalOffset>
<Binding Path="Child.ActualHeight" RelativeSource="{RelativeSource Self}"
Converter="{StaticResource MultiplyConverter}" ConverterParameter="{StaticResource Factor}" />
</Popup.VerticalOffset>
<!-- popup content -->
</Popup>
I hope it can help you.

How do I bind the RadiusX of a rectangle to the ActualHeight of the rectangle and multiply it by some number in Expression Blend 4 (or VS)?

Right now I am "cheating" and using the following:
<Rectangle x:Name="rectangle" Stroke="SlateGray"
Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding ActualHeight}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
SizeChanged="rectangle_SizeChanged">
</Rectangle>
<x:Code>
<![CDATA[ private void rectangle_SizeChanged(object sender, SizeChangedEventArgs e)
{
Rectangle r = sender as Rectangle;
r.RadiusX = r.Height / 2;
r.RadiusY = r.Height / 2;
}
]]>
</x:Code>
This x:Code works perfectly at run time and accomplishes what I want. but I really want it to change instantly on the Artboard by doing something like:
<Rectangle x:Name="rectangle" Stroke="SlateGray"
Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding ActualHeight}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
RadiusX=".5*({TemplateBinding ActualHeight})"
RadiusY=".5*({TemplateBinding ActualHeight})">
</Rectangle>
But there is no way to include this .5*(...) Is there another way to accomplish this?
To run code in a binding you use a converter class.
public class MultiplyConverter : IValueConverter
{
public double Multipler{ get; set; }
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
double candidate = (double)value;
return candidate * Multipler ;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then add the converter in a Resources section.
<Window.Resources>
<local:MultiplyConverter x:Key="MultiplyConverter" Multipler="5"/>
</Window.Resources>
And add the coverter to your binding.
<Rectangle x:Name="rectangle" Fill="#FFA4A4E4"
RadiusX="{Binding ActualHeight, Converter={StaticResource MultiplyConverter}, ElementName=rectangle}"
RadiusY="{Binding ActualWidth, Converter={StaticResource MultiplyConverter}, ElementName=rectangle,}" />
You can use the Blend binding windows to automatically add the resource and binding.

Show the validation error template on a different control in WPF

I have a UserControl that contains other controls and a TextBox. It has a Value property that is bound to the TextBox text and has ValidatesOnDataErrors set to True.
When a validation error occurs in the Value property binding, the error template (standard red border) is shown around the entire UserControl.
Is there a way to show it around the TextBox only?
I'd like to be able to use any error template so simply putting border around textbox and binding its color or something to Validation.HasError is not an option.
Here's my code:
<DataTemplate x:Key="TextFieldDataTemplate">
<c:TextField DisplayName="{Binding Name}" Value="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
</DataTemplate>
<controls:FieldBase x:Name="root">
<DockPanel DataContext="{Binding ElementName=root}">
<TextBlock Text="{Binding DisplayName}"/>
<TextBox x:Name="txtBox"
Text="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True}"
IsReadOnly="{Binding IsReadOnly}"/>
</DockPanel>
UserControl (FieldBase) is than bound to ModelView which performs validation.
to accomplish this task I've used this solution. It uses converter, that "hides" border by converting (Validation.Errors).CurrentItem to Thickness.
<Grid>
<Grid.Resources>
<data:ValidationBorderConverter
x:Key="ValidationBorderConverter" />
</Grid.Resources>
<Border
BorderBrush="#ff0000"
BorderThickness="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem,
onverter={StaticResource ValidationBorderConverter}}">
<TextBox
ToolTip="{Binding
ElementName=myControl,
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Border>
</Grid>
ValidationBorderConverter class is pretty simple:
[ValueConversion(typeof(object), typeof(ValidationError))]
public sealed class ValidationBorderConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return (value == null) ? new Thickness(0) : new Thickness(1);
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Resources