Binding problem when creating custom ProgressBar - wpf

<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();
}
}

Related

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 ?

Run-time error: InverseBooleanConverter not found

I have a problem attempting to follow the advice in:
How to bind inverse boolean properties in WPF?
When I use with ResourceDictionary, it give run-time error. InverseBooleanConverter not found.
XMAL as follows:
<UserControl x:Class="SMTF.MasterDataView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SMTF" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="466" d:DesignWidth="483">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../AppResource.xaml" />
<ResourceDictionary Source="../DefaultStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<StackPanel HorizontalAlignment="Left" Margin="200,12,0,0" Name="stkMain" VerticalAlignment="Top" >
<Grid Margin="4">
<ContentControl Visibility="{Binding IsChecked, ElementName=VisibilityToggle, Converter={StaticResource InverseBooleanConverter}}" >
<Border Grid.Column="2" Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl Content="{Binding Path=WorkspaceView}" ContentTemplate="{StaticResource WorkspacesTemplate}" Header="View" Style="{StaticResource MainHCCStyle}"/>
</Border>
</ContentControl>
</Grid>
<Grid DockPanel.Dock="Bottom" Margin="0,2,4,2">
<TextBlock HorizontalAlignment="Right">
<ToggleButton x:Name="VisibilityToggle" Focusable="False" Style="{StaticResource SMToggle}" Command ="{Binding ShowNew}" >
</ToggleButton>
<!--<ToggleButton x:Name="VisibilityToggle" Background="Transparent" Command ="{Binding ShowNew}" >
<Image Source="/Image/Add.png" Width="24" />
</ToggleButton>-->
</TextBlock>
</Grid>
<Grid Margin="4">
<ContentControl Visibility="{Binding IsChecked, ElementName=VisibilityToggle, Converter={StaticResource BoolToVisibility}}" >
<Border Grid.Column="2" Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl Content="{Binding Path=WorkspaceEdit}" ContentTemplate="{StaticResource WorkspacesTemplate}" Header="Configure" Style="{StaticResource MainHCCStyle}"/>
</Border>
</ContentControl>
</Grid>
</StackPanel>
</Grid>
</UserControl>
I'm using the same code provided in the link.
ie:
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBooleanConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
in the AppResource XML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SMTF">
<vm:InverseBooleanConverter x:Key="InverseBoolToVisibility" />
.....
.....
</ResourceDictionary>
Thanks in advance
NS
An alternative to converters for style related binding is to use Style.Triggers, the following shows a canvas when checkbox IsChecked = false, which would otherwise require an InverseBooleanConverter.
<Canvas x:Name="Overlay">
<Canvas.Style>
<Style TargetType="Canvas">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=MyCheckbox}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked, ElementName=MyCheckbox}" Value="False">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Style>
<Rectangle Canvas.ZIndex="3" Fill="#99333333" Height="25" Stroke="Transparent" Width="293" Canvas.Left="10" Canvas.Top="-25"/>
</Canvas>
The key you are using is not correct. You resource key is InverseBoolToVisibility, while you have used InverseBooleanConverter as key.
Change the resource key to refer to correct resource as
<ContentControl Visibility="{Binding IsChecked, ElementName=VisibilityToggle, Converter={StaticResource InverseBoolToVisibility}}" >
Also your implementation for convereter is wrong. If you want to change the Visibility based on Boolean inverse value update your converter code as:
public class InverseBooleanConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(Visibility))
throw new InvalidOperationException("The target must be a boolean");
if(!(bool)value)
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}

Binding to element position does not work?

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.

Binding GradientStop works but reports error

The following code binds a GradientStop to the Background.Color property of TemplatedParent. Everything works but I am getting a binding error in the output window:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Background.Color; DataItem=null; target element is 'GradientStop' (HashCode=6944299); target property is 'Color' (type 'Color')
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfBindingTest.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="100" Height="100">
<Window.Resources>
<ControlTemplate x:Key="GradientTemplate" TargetType="{x:Type ContentControl}">
<Border BorderThickness="1" BorderBrush="{TemplateBinding Background}">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="{Binding Path=Background.Color,
RelativeSource={RelativeSource TemplatedParent}}" Offset="1"/>
<GradientStop Color="White" Offset="0"/>
</LinearGradientBrush>
</Border.Background>
<ContentPresenter/>
</Border>
</ControlTemplate>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<ContentControl Background="Green" Template="{StaticResource GradientTemplate}" >
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="X" />
</ContentControl>
</Grid>
</Window>
I also had the same error in the Visual Studio console output.
A possible explanation and workaround for this is reported here
Basically if you use a Converter that returns a LinearGradientBrush then you don't get the error
The code is something like this
[ValueConversion(typeof(System.Windows.Media.Color), typeof(LinearGradientBrush))]
class GradientConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var brush = new LinearGradientBrush();
var color = (Color)value;
brush.StartPoint = new Point(0.5, 0);
brush.EndPoint = new Point(0.5, 1);
brush.GradientStops.Add(new GradientStop(Colors.White, 0));
brush.GradientStops.Add(new GradientStop((Color)value, 1));
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in the XAML
<Border BorderThickness="1" BorderBrush="{TemplateBinding Background}" Background="{Binding Path=Background.Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource gradConv}}">

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