Add value ticks into my Circular Slider - wpf

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 ?

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;
...
}

wpf specify multiple DataBindings in Groupbox IsEnabled Property

I have a GroupBox whose IsEnabled Property is set via a property on the ViewModel as under:-
<GroupBox>
<Canvas IsEnabled="{Binding CurrentRec.Current_Selected_Category.NoBonus,Converter={StaticResource TFC}}">
<Label Content="Amount:" Width="55" Canvas.Left="9" Canvas.Top="-2"/>
<TextBox x:Name="txtBonusAmount" Width="76" Canvas.Left="12" Canvas.Top="20" Text="Some text"/>
<Label Content="Bonus:" Canvas.Top="38" Width="54" Canvas.Left="10"/>
<TextBox x:Name="txtBonus" Width="76" Canvas.Left="13" Canvas.Top="58" Text="Some Text"/>
</Canvas>
<Groupbox>
There are more than one properties in my viewmodel affecting the IsEnabled property of Canvas.How do i specify those additional properties against the IsEnabled property of Canvas?
Use a MultiBinding with a converter:
<GroupBox>
<GroupBox.Resources>
<local:MultiConverter x:Key="conv" />
</GroupBox.Resources>
<Canvas>
<Canvas.IsEnabled>
<MultiBinding Converter="{StaticResource conv}">
<Binding Path="CurrentRec.Current_Selected_Category.NoBonus" />
<Binding Path="TheOtherProperty" />
</MultiBinding>
</Canvas.IsEnabled>
<Label Content="Amount:" Width="55" Canvas.Left="9" Canvas.Top="-2"/>
<TextBox x:Name="txtBonusAmount" Width="76" Canvas.Left="12" Canvas.Top="20" Text="Some text"/>
<Label Content="Bonus:" Canvas.Top="38" Width="54" Canvas.Left="10"/>
<TextBox x:Name="txtBonus" Width="76" Canvas.Left="13" Canvas.Top="58" Text="Some Text"/>
</Canvas>
</GroupBox>
The converter class should implement the IMultiValueConverter interface and return a bool from the Convert method:
public class MultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
bool noBonus = System.Convert.ToBoolean(values[0]);
bool theOtherSourceProperty = System.Convert.ToBoolean(values[1]);
//..
return noBonus && theOtherSourceProperty;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

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.

Always getting DependencyProperty.unsetvalue

So am testing multibinding in wpf and i have three text boxes which should get the year,month,day and my converter class should return a date with those inputs..pretty simple.
But in my convert method the values[0] is always unset that is i am always getting Dependencyproperty.UnsetValue even if get give it an initial value.
XAML
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:src="clr-namespace:WpfApplication2"
Title="MultiBinding Demo" Width="200" Height="200">
<Window.Resources>
<src:DateConverter x:Key="myConverter" />
</Window.Resources>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
</StackPanel.Resources>
<TextBox Name="tb1" Margin="10" Width="Auto" Height="20"></TextBox>
<TextBox Name="tb2" Margin="10" Width="20" Height="20" ></TextBox>
<TextBox Name="tb3" Width="20" Height="20" ></TextBox>
<Label Name="Date" Width="50" Height="25" Margin="5" >
<Label.Content>
<MultiBinding Converter="{StaticResource myConverter}" Mode="OneWay">
<Binding ElementName="tbl" Path="Text" />
<Binding ElementName="tb2" Path="Text" />
<Binding ElementName="tb3" Path="Text" />
</MultiBinding>
</Label.Content>
</Label>
</StackPanel>
DATECONVERTER CLASS
class DateConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue || values[2] == DependencyProperty.UnsetValue)
{
return "";
}
else
{
int year = (int)values[0];
int month = (int)values[1];
int day = (int)values[2];
DateTime date = new DateTime(year, month, day);
return date.ToShortDateString();
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'm looking at your XAML, and it looks like your first TextBox is named tb1 (number 1) but in the binding you're referencing element name tbl (letter L).

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

Resources