Binding to element position does not work? - wpf

I have an image with opacity mask and wish to add possibility to resize/reposition that opacity mask.
I'm a complete WPF newbie, so may be may thinking is completely wrong, feel free to throw any ideas at me :)
So I have this:
<Canvas Name="MainCanvas">
<Rectangle Width="197.016" Height="120.896" Canvas.Left="76.119" Canvas.Top="73.134" Name="SelectionRectangle"></Rectangle>
<Image Source="Chrysanthemum.jpg" Width="{Binding ActualWidth, ElementName=MainCanvas, Mode=OneWay}" Height="{Binding ActualHeight, ElementName=MainCanvas, Mode=OneWay}">
<Image.OpacityMask>
<RadialGradientBrush MappingMode="Absolute"
Center="{Binding ElementName=SelectionRectangle, Converter={StaticResource RectangleConverter}}"
GradientOrigin="{Binding ElementName=SelectionRectangle, Converter={StaticResource RectangleConverter}}"
RadiusY="{Binding ElementName=SelectionRectangle, Path=ActualHeight, Converter={StaticResource MathConverter}, ConverterParameter=#VALUE/2}"
RadiusX="{Binding ElementName=SelectionRectangle, Path=ActualWidth, Converter={StaticResource MathConverter}, ConverterParameter=#VALUE/2}">
<GradientStop Color="#7C15161F" Offset="1" />
<GradientStop Color="#FF40499E" Offset="0.999" />
<GradientStop Color="#FF182395" Offset="0" />
</RadialGradientBrush>
</Image.OpacityMask>
</Image>
</Canvas>
So there's image with RadialGradientBrush mask and I'm trying to bind RadialGradientBrush center to rectangles center using this converter:
public class RectangleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var rectangle = (Rectangle)value;
return new Point(Canvas.GetLeft(rectangle) + rectangle.ActualWidth / 2f, Canvas.GetTop(rectangle) + rectangle.ActualHeight / 2f);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Everything compiles and runs, but works incorrectly, I guess because when Rectangle position changes, RadialGradientBrush Center binding does not know about that (at least breaking point in converter is not hit by changing rectangle's position).
What would be the easiest/recommended way to fix this?

I've found, that Multibinding can solve this problem:
<Canvas Name="MainCanvas">
<Rectangle Width="197.016" Height="120.896" Canvas.Left="76.119" Canvas.Top="73.134" Name="SelectionRectangle"></Rectangle>
<Image Source="Chrysanthemum.jpg" Width="{Binding ActualWidth, ElementName=MainCanvas, Mode=OneWay}" Height="{Binding ActualHeight, ElementName=MainCanvas, Mode=OneWay}">
<Image.OpacityMask>
<RadialGradientBrush MappingMode="Absolute"
RadiusY="{Binding ElementName=SelectionRectangle, Path=ActualHeight, Converter={StaticResource MathConverter}, ConverterParameter=#VALUE/2}"
RadiusX="{Binding ElementName=SelectionRectangle, Path=ActualWidth, Converter={StaticResource MathConverter}, ConverterParameter=#VALUE/2}">
<RadialGradientBrush.Center>
<MultiBinding Converter="{StaticResource RectangleConverter}">
<Binding ElementName="SelectionRectangle" Path="(Canvas.Left)"></Binding>
<Binding ElementName="SelectionRectangle" Path="ActualWidth"></Binding>
<Binding ElementName="SelectionRectangle" Path="(Canvas.Top)"></Binding>
<Binding ElementName="SelectionRectangle" Path="ActualHeight"></Binding>
</MultiBinding>
</RadialGradientBrush.Center>
<RadialGradientBrush.GradientOrigin>
<MultiBinding Converter="{StaticResource RectangleConverter}">
<Binding ElementName="SelectionRectangle" Path="(Canvas.Left)"></Binding>
<Binding ElementName="SelectionRectangle" Path="ActualWidth"></Binding>
<Binding ElementName="SelectionRectangle" Path="(Canvas.Top)"></Binding>
<Binding ElementName="SelectionRectangle" Path="ActualHeight"></Binding>
</MultiBinding>
</RadialGradientBrush.GradientOrigin>
<GradientStop Color="#7C15161F" Offset="1" />
<GradientStop Color="#FF40499E" Offset="0.999" />
<GradientStop Color="#FF182395" Offset="0" />
</RadialGradientBrush>
</Image.OpacityMask>
</Image>
</Canvas>
And converter:
public class RectangleConverter : IMultiValueConverter
{
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return new Point((double)values[0] + (double)values[1] / 2d, (double)values[2] + (double)values[3] / 2d);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
Not sure if I'm right, but my explanation would be - in question I was binding to whole object, so it is not a dependency property, so mask was not notified about the changes.
Now multibinding let's to specify which exactly properties are interesting and value converter get's notification when any of them change.

Related

Drawing Ellipse Line/PolyLine with a double

I have been working with this example to draw bar graph. The bar part is done and it represents the volume/quantity in a transaction. It's an associated price in range 20 - 30. What I want now is to draw points to represent price associated with volumes and connect those points. Two changes I've made in EDIT part of the linked example (1) removed the TextBlock from the DataTemplate of ItemsControl and added an Ellipse instead and (2) edited the canvas to add price/volume axis label. Here's how it looks like now:
How to add those Ellipse in right position and connect those with Line/PolyLine?
EDIT
Here's what I've now in ItemsControl:
<ItemsControl ScrollViewer.CanContentScroll="True"
Height="135"
ItemsSource="{Binding RectCollection}"
Margin="50 0 50 0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Width="20">
<Canvas.LayoutTransform>
<ScaleTransform ScaleY="-1"/>
</Canvas.LayoutTransform>
<Rectangle Width="18"
Margin="0 0 2 0"
VerticalAlignment="Bottom"
Opacity=".5" Fill="LightGray">
<Rectangle.Height>
<MultiBinding Converter="{StaticResource VConverter}">
<Binding Path="ActualHeight"
RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
<Binding Path="DataContext.HighestPoint"
RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
<Binding Path="Volume"/>
</MultiBinding>
</Rectangle.Height>
</Rectangle>
<Line Stroke="DarkGreen" StrokeThickness="1"
X1="10" X2="30"
Y2="{Binding PreviousPrice, Converter={StaticResource PConverter}}"
Y1="{Binding CurrentPrice, Converter={StaticResource PConverter}}">
<Line.Style>
<Style TargetType="Line">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=PreviousPrice}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Line.Style>
</Line>
<Ellipse Fill="Red" Width="6" Height="6" Margin="-3" Canvas.Left="10"
Canvas.Top="{Binding CurrentPrice, Converter={StaticResource PConverter}}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer
VerticalScrollBarVisibility="Hidden"
Background="{TemplateBinding Panel.Background}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
oh! I forgot to add those ValueConverters, here're those:
public class VolumeConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var height = (double)values[0];
var higest = (double)values[1];
var value = (double)values[2];
return value * height / higest;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class PriceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is double)) return null;
var price = (double)value;
var remainingHeight = 90;
var priceRange = 30 - 20.0;
return 45 + ((price - 20) * remainingHeight / priceRange);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and here's how it looks like:
As suggested by #Clemens, I've to have another double?, in case where Insert(0, ... ) is used on ObservableCollection instead of Add(...) to add the last item in first place and removed the AternationCount/Index stuff.
The following example uses a vertically flipped Canvas to invert the y-axis order, so that it goes upwards. So PConverter should return positive y values.
Besides the Rectangle and Ellipse elements it draws a Line element from the previous data value to the current one by means of RelativeSource={RelativeSource PreviousData} in the value binding. It also uses a DataTrigger on the AlternationIndex to hide the first line.
<ItemsControl ... AlternationCount="2147483647">
...
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas Width="20">
<Canvas.LayoutTransform>
<ScaleTransform ScaleY="-1"/>
</Canvas.LayoutTransform>
<Rectangle Fill="LightGray" Margin="1" Width="18"
Height="{Binding Value1, Converter={StaticResource PConverter}}"/>
<Line Stroke="DarkGreen" StrokeThickness="3"
X1="-10" X2="10"
Y1="{Binding Price,
Converter={StaticResource PConverter},
RelativeSource={RelativeSource PreviousData}}"
Y2="{Binding Price,
Converter={StaticResource PConverter}}">
<Line.Style>
<Style TargetType="Line">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=(ItemsControl.AlternationIndex),
RelativeSource={RelativeSource
AncestorType=ContentPresenter}}"
Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Line.Style>
</Line>
<Ellipse Fill="Red" Width="6" Height="6" Margin="-3" Canvas.Left="10"
Canvas.Top="{Binding Price, Converter={StaticResource PConverter}}"/>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
Since the value converter is now also called for a non-existing value (for PreviousData of the first item), you have to make sure that it checks if the passed value is actually a double:
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is double)) return 0d;
...
}

Add value ticks into my Circular Slider

This is my Circular slider:
<Slider Name="knobSlider" Minimum="0" Maximum="50" Value="1" Height="99" Width="75"
HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1">
<Slider.Template>
<ControlTemplate>
<Viewbox>
<Canvas Width="300" Height="300" Margin="5">
<Ellipse Fill="Transparent" Width="300" Height="300" Canvas.Left="0" Canvas.Top="0"
Stroke="#FF878889" StrokeThickness="10"
MouseLeftButtonUp="Ellipse_MouseLeftButtonUp"
MouseMove="Ellipse_MouseMove"/>
<Ellipse Fill="Transparent" Width="60" Height="60" Canvas.Left="120" Canvas.Top="120"/>
<Canvas>
<Line Stroke="Transparent" StrokeThickness="5" X1="150" Y1="150" X2="150" Y2="10"
MouseLeftButtonUp="Ellipse_MouseLeftButtonUp"/>
<Ellipse Fill="White" Width="30" Height="30" Canvas.Left="140" Canvas.Top="0"
MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"
MouseLeftButtonUp="Ellipse_MouseLeftButtonUp">
<Ellipse.ToolTip>
<ToolTip>
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="Value" Converter="{StaticResource valueTextConverter}"/>
</ToolTip>
</Ellipse.ToolTip>
</Ellipse>
<Canvas.RenderTransform>
<RotateTransform CenterX="150" CenterY="150">
<RotateTransform.Angle>
<MultiBinding Converter="{StaticResource valueAngleConverter}">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum"/>
</MultiBinding>
</RotateTransform.Angle>
</RotateTransform>
</Canvas.RenderTransform>
</Canvas>
</Canvas>
</Viewbox>
</ControlTemplate>
</Slider.Template>
</Slider>
public class ValueAngleConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
double value = (double)values[0];
double minimum = (double)values[1];
double maximum = (double)values[2];
return MyHelper.GetAngle(value, maximum, minimum);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
public class ValueTextConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
double v = (double)value;
return String.Format("{0:F2}", v);
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
And the results:
Is it possible to change the look of this Circular Slider to something similar to this one:
And add value tick and show the current value of this controller ad the button of this control like in the example instead of put a textBlock inside the Circle ?

Multibinding using TemplatedParent always returns 0.0 for ActualHeight

I'm attempting to use Multibinding in a ControlTemplate to draw some Lines. My XAML:
<Line X1="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}" Y1="0"
X2="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}" Stroke="Red" StrokeThickness="1">
<Line.Y2>
<MultiBinding Converter="{StaticResource XAMLResourceAddConverter}">
<Binding Source="-15"></Binding>
<Binding Path="ActualHeight" RelativeSource="{RelativeSource TemplatedParent}"></Binding>
</MultiBinding>
</Line.Y2>
</Line>
And my convertor:
public class AddConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
int result =
Int32.Parse(values[0].ToString()) + Int32.Parse(values[1].ToString());
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
The Line is not actually drawing. Stepping through the Convertor, I found that the value[1] (which should be the ActualHeight) is always coming through as 0.0. How do I fix this?
You can use FindAncestor in this case as TemplatedParent is not being resolved in binding
<Binding Path="ActualHeight" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type MyType}}"></Binding>
secondly as you are using a hardcoded value -15 for first binding in the multibinding, perhaps you can bind the Y2 inline using converter parameter
so if you can change the converter XAMLResourceAddConverter to a IValueConveter then perhaps you can use it as
Y2="{Binding ActualHeight, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource XAMLResourceAddConverter}, ConverterParameter=-15}"

Binding problem when creating custom ProgressBar

<UserControl x:Class="WpfApplication2.ProgressBar"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ProgressBar Minimum="0" Maximum="1" Value="0.5" LargeChange="0.1" SmallChange="0.01" Margin="2,2,12,2" Height="22">
<ProgressBar.Template>
<ControlTemplate>
<Border BorderThickness="2" BorderBrush="Black">
<Rectangle>
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0">
<LinearGradientBrush.EndPoint>
<Point Y="0" X="{Binding RelativeSource={RelativeSource AncestorType={x:Type ProgressBar}}, Path=ProgressBar.Value}"/>
</LinearGradientBrush.EndPoint>
<GradientStop Color="Transparent" Offset="1.01"/>
<GradientStop Color="#FF0000" Offset="1.0"/>
<GradientStop Color="#FFFF00" Offset="0.50"/>
<GradientStop Color="#00FF00" Offset="0.0"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Border>
</ControlTemplate>
</ProgressBar.Template>
</ProgressBar>
<TextBlock Text="50%" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</UserControl>
I get error: "A 'Binding' cannot be set on the 'X' property of type 'Point'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject."
Is there any clean workaround?
Since Point.X isn't a Dependency Property you can't bind it to something. You could bind the EndPointProperty though, and use a Converter that creates the Point for you. It could take the Y value as parameter for example
Xaml
<LinearGradientBrush.EndPoint>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ProgressBar}}"
Path="Value"
Converter="{StaticResource PointXConverter}"
ConverterParameter="0"/>
</LinearGradientBrush.EndPoint>
PointXConverter
public class PointXConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double progressBarValue = (double)value;
double yValue = System.Convert.ToDouble(parameter);
return new Point(progressBarValue, yValue);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note: Probably not related to your question but if you would need to bind Y as well, you can use a MultiBinding like this
<LinearGradientBrush.EndPoint>
<MultiBinding Converter="{StaticResource PointConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ProgressBar}}"
Path="Value"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ProgressBar}}"
Path="Value"/>
</MultiBinding>
</LinearGradientBrush.EndPoint>
PointConverter
public class PointConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double xValue = (double)values[0];
double yValue = (double)values[1];
return new Point(xValue, yValue);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

WPF: Dynamic Actions in Template

I've defined a control Template for a progressbar to make it look like a thermometer ... now i want it to change its color when reaching a certain value (for example .. when the progressbar has the value 70, its color should change to yellow)
Currently the color of PART_Indicator is bound to the background color of the progressbar .. the backgroundcolor is changed in the ValueChanged Eventhandler, and so the color of the indicator changes too... is there a possibility to do this within the template only, so i dont need to use the ValueChanged Eventhandler?
<ControlTemplate x:Key="myThermometer" TargetType="{x:Type ProgressBar}">
<ControlTemplate.Resources>
<RadialGradientBrush x:Key="brushBowl" GradientOrigin="0.5 0.5">
<GradientStop Offset="0" Color="Pink" />
<GradientStop Offset="1" Color="Red" />
</RadialGradientBrush>
</ControlTemplate.Resources>
<Canvas>
<Path Name="PART_Track" Stroke="Black" StrokeThickness="5" Grid.Column="0">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<RectangleGeometry Rect="10,40,130,20" RadiusX="5" RadiusY="5"/>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry Center="10,50" RadiusX="20" RadiusY="20"/>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
<Path Fill="{TemplateBinding Background}">
<Path.Data>
<EllipseGeometry Center="10,50" RadiusX="17" RadiusY="17"/>
</Path.Data>
</Path>
<Path Name="PART_Indicator" Fill="{TemplateBinding Background}" Grid.Column="0">
<Path.Data>
<RectangleGeometry Rect="22,43,115,15" RadiusX="5" RadiusY="5"/>
</Path.Data>
</Path>
<Canvas Canvas.Top="35" Canvas.Right="375">
<Canvas.RenderTransform>
<RotateTransform CenterX="120" CenterY="120" Angle="-270" />
</Canvas.RenderTransform>
<TextBlock FontWeight="Bold" FontSize="16" Foreground="Black" Text="{TemplateBinding Tag}"/>
</Canvas>
</Canvas>
</ControlTemplate>
<ProgressBar x:Name="progressBar" Background="{Binding RelativeSource={RelativeSource Self},Path=Value,Converter={StaticResource IntToBrushConverter}}" />
public class IntToBrushConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double val = (double)value;
if (val > 50)
return Brushes.Blue;
else
return Brushes.Green;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("Not Implemented");
}
#endregion
}
more or less - I'd write a custom ValueConverter that converts your progress value to a Color and bind the fill of the progress bar using this converter.
XAML:
Fill="{TemplateBinding Progress,
Converter={StaticResource ProgressToColorConverter}}"
Code:
[ValueConversion(typeof(int), typeof(Color))]
public class ProgressToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int progress = (int)value;
if (progress < 60)
return Color.Green;
else
return Color.Red;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This code is just from the top of my head and hasn't been tested, but it should show the principles.

Resources