I'm using PixelShader effects for an image.
Image effects for adjusting Contrast, Brightness, CMY & RGB using SLIDERS
Blend mode Image effects which are predefined & loaded in a combo box and users can select their own choice.
This is the Image element and the image effect for adjusting Contrast, Brightness, CMY & RGB are applied in this way.
<Viewbox x:Name="cImage" Stretch="Uniform" Grid.Column="1" Grid.Row="1" Grid.RowSpan="2" Margin="0" >
<Image x:Name="ViewedPhoto" Source="IMG_0071.jpg"
Stretch="None" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="0,5,0,95" >
<Image.Effect>
<l:BrightContrastEffect
Brightness="{Binding Value, ElementName=bVal}"
Contrast="{Binding Value, ElementName=cVal}"
Red="{Binding Value, ElementName=rVal}"
Green="{Binding Value, ElementName=gVal}"
Blue="{Binding Value, ElementName=blVal}"
/>
</Image.Effect>
</Image>
</Viewbox>
Slider for brightness:
<Slider Maximum="1" Minimum="-1" x:Name="bVal" TickFrequency="1" TickPlacement="BottomRight" />
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Value, ElementName=bVal, UpdateSourceTrigger=PropertyChanged}" TextAlignment="Right" Width="30" Height="10" Margin="0" RenderTransformOrigin="-1.167,0.423" Visibility="Hidden"/>
</StackPanel>
From the above when i move the sliders for adjusting Contrast, Brightness, CMY & RGB, it's working.
I apply predefined Blend mode image effects using comboBox.
<ComboBox x:Name="cColorEffects" SelectionChanged="cColorEffects_SelectionChanged" />
To show the different effects i call ViewedPhoto.Effect = null; at first & then apply the selected effect.
if (cColorEffects.SelectedIndex == 0)
{
ViewedPhoto.Effect = null;
ViewedPhoto.Effect = new AverageEffect();
}
if (cColorEffects.SelectedIndex == 1)
{
ViewedPhoto.Effect = null;
ViewedPhoto.Effect = new ColorBurnEffect();
}
Problems:
I'm calling another image effects through comboBox so the image effects for adjusting Contrast, Brightness, CMY & RGB are not working.
I'm using ViewedPhoto.Effect = null; the image effects for adjusting Contrast, Brightness, CMY & RGB are not working.
I want to make the sliders working for adjusting Contrast, Brightness, CMY & RGB and apply the blend mode image effects simultaneously. How can i fix this? Or Give me an idea to fix this?
EDIT:
I have been thinking I could do make the slider binding pro-grammatically and the image effect. Does it make sense? if it's good; How can i apply this?
<Image x:Name="ViewedPhoto" >
<Image.Effect>
<l:BrightContrastEffect
Brightness="{Binding Value, ElementName=bVal}"
Contrast="{Binding Value, ElementName=cVal}"
Red="{Binding Value, ElementName=rVal}"
Green="{Binding Value, ElementName=gVal}"
Blue="{Binding Value, ElementName=blVal}"
/>
</Image.Effect>
</Image>
<Slider Maximum="1" Minimum="-1" x:Name="bVal" TickFrequency="1" TickPlacement="BottomRight" />
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Value, ElementName=bVal, UpdateSourceTrigger=PropertyChanged}" Visibility="Hidden"/>
<Button x:Name="bReset" Content="R" Height="10" Width="30" Margin="35,0,0,0" Click="bReset_Click"/>
</StackPanel>
<Slider Maximum="1" Minimum="-1" x:Name="cVal" TickFrequency="1" TickPlacement="BottomRight" />
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Value, ElementName=cVal, UpdateSourceTrigger=PropertyChanged}" Visibility="Hidden" />
<Button x:Name="cReset" Content="R" Height="10" Width="30" Margin="35,0,0,0" Click="cReset_Click"/>
</StackPanel>
Additional info with codes:
public class BlendModeEffect : ShaderEffect
{
public BlendModeEffect()
{
UpdateShaderValue(InputProperty);
UpdateShaderValue(TextureProperty);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty
(
"Input",
typeof(BlendModeEffect),
0
);
public Brush Texture
{
get { return (Brush)GetValue(TextureProperty); }
set { SetValue(TextureProperty, value); }
}
public static readonly DependencyProperty TextureProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty
(
"Texture",
typeof(BlendModeEffect),
1
);
}
//Contrast, Brightness, CMY & RGB Effect
public class BrightContrastEffect : ShaderEffect
{
private static PixelShader m_shader =
new PixelShader() { UriSource = MakePackUri("bricon.ps") };
public BrightContrastEffect()
{
PixelShader = m_shader;
UpdateShaderValue(InputProperty);
UpdateShaderValue(BrightnessProperty);
UpdateShaderValue(ContrastProperty);
UpdateShaderValue(RedProperty);
UpdateShaderValue(GreenProperty);
UpdateShaderValue(BlueProperty);
}
// MakePackUri is a utility method for computing a pack uri
// for the given resource.
public static Uri MakePackUri(string relativeFile)
{
Assembly a = typeof(BrightContrastEffect).Assembly;
// Extract the short name.
string assemblyShortName = a.ToString().Split(',')[0];
string uriString = "pack://application:,,,/" +
assemblyShortName +
";component/" +
relativeFile;
return new Uri(uriString);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(BrightContrastEffect), 0);
public float Brightness
{
get { return (float)GetValue(BrightnessProperty); }
set { SetValue(BrightnessProperty, value); }
}
public static readonly DependencyProperty BrightnessProperty = DependencyProperty.Register("Brightness", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(0)));
public float Contrast
{
get { return (float)GetValue(ContrastProperty); }
set { SetValue(ContrastProperty, value); }
}
public static readonly DependencyProperty ContrastProperty = DependencyProperty.Register("Contrast", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(1)));
public float Red
{
get { return (float)GetValue(RedProperty); }
set { SetValue(RedProperty, value); }
}
public static readonly DependencyProperty RedProperty = DependencyProperty.Register("Red", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(2)));
public float Green
{
get { return (float)GetValue(GreenProperty); }
set { SetValue(RedProperty, value); }
}
public static readonly DependencyProperty GreenProperty = DependencyProperty.Register("Green", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(3)));
public float Blue
{
get { return (float)GetValue(BlueProperty); }
set { SetValue(BlueProperty, value); }
}
public static readonly DependencyProperty BlueProperty = DependencyProperty.Register("Blue", typeof(double), typeof(BrightContrastEffect), new UIPropertyMetadata(0.0, PixelShaderConstantCallback(4)));
//private static PixelShader m_shader = new PixelShader() { UriSource = new Uri(#"pack://application:,,,/CustomPixelRender;component/bricon.ps") };
}
//Average Blend Mode Effect
public class AverageEffect : BlendModeEffect
{
public static Uri MakePackUri(string relativeFile)
{
Assembly a = typeof(ColorBurnEffect).Assembly;
// Extract the short name.
string assemblyShortName = a.ToString().Split(',')[0];
string uriString = "pack://application:,,,/" +
assemblyShortName +
";component/" +
relativeFile;
return new Uri(uriString);
}
static AverageEffect()
{
_pixelShader.UriSource = MakePackUri("AverageEffect.ps");
}
public AverageEffect()
{
this.PixelShader = _pixelShader;
}
private static PixelShader _pixelShader = new PixelShader();
}
//ColorBurn Effect
public class ColorDodgeEffect : BlendModeEffect
{
public static Uri MakePackUri(string relativeFile)
{
Assembly a = typeof(ColorBurnEffect).Assembly;
// Extract the short name.
string assemblyShortName = a.ToString().Split(',')[0];
string uriString = "pack://application:,,,/" +
assemblyShortName +
";component/" +
relativeFile;
return new Uri(uriString);
}
static ColorDodgeEffect()
{
_pixelShader.UriSource = MakePackUri("ColorDodgeEffect.ps");
}
public ColorDodgeEffect()
{
this.PixelShader = _pixelShader;
}
private static PixelShader _pixelShader = new PixelShader();
}
//BlendModeEffect
public class BlendModeEffect : ShaderEffect
{
public BlendModeEffect()
{
UpdateShaderValue(InputProperty);
UpdateShaderValue(TextureProperty);
}
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty
(
"Input",
typeof(BlendModeEffect),
0
);
public Brush Texture
{
get { return (Brush)GetValue(TextureProperty); }
set { SetValue(TextureProperty, value); }
}
public static readonly DependencyProperty TextureProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty
(
"Texture",
typeof(BlendModeEffect),
1
);
}
As per the SO chat uses suggestion. I have called BrightContrastEffect as new Effect to the image in the slider changed event Handler. I set the biding values pro-grammatically.
private void cVal_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
cColorEffects.SelectedIndex = 0;
BrightContrastEffect bce = new BrightContrastEffect();
BindingOperations.SetBinding(bce, BrightContrastEffect.ContrastProperty, new Binding("Value") { Source = cVal });
ViewedPhoto.Effect = bce;
}
I have applied the same approached for the other sliders.
Related
I have created two separate usercontrols, they are meant to work together.
The first one is a simple usercontrol with a thumb attached to it, the thumb makes the control move around by dragging, this is simple and working.
XAML:
<Canvas>
<Thumb x:Name="Thumb" Width="15" Height="15" DragDelta="Thumb_DragDelta"/>
</Canvas>
Code-Behind: A dependency property called Position, when Setter is called it updates the usercontrol's margin.
public partial class ThumbPoint : UserControl
{
public Point Position
{
get { return (Point)GetValue(PositionProperty); }
set { SetValue(PositionProperty, value); this.Margin = new Thickness(value.X, value.Y, 0, 0); }
}
// Using a DependencyProperty as the backing store for Position. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register("Position", typeof(Point), typeof(ThumbPoint), new PropertyMetadata(new Point()));
public ThumbPoint()
{
InitializeComponent();
Position = new Point(0, 0);
}
private void Thumb_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
Position = new Point(Position.X + e.HorizontalChange, Position.Y + e.VerticalChange);
}
}
The second UserControl is called StraightLine, its composed of a Line control
XAML:
<Canvas>
<Line x:Name="Line" Stroke="Gray" StrokeThickness="1"/>
</Canvas>
Code-Behind: A dependency property called StartPosition, when Setter is called it updates the Line X1 and Y1 (starting position of the line).
public partial class StraightLine : UserControl
{
public Point StartPosition
{
get { return (Point)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value); Line.X1 = value.X; Line.Y1 = value.Y; }
}
// Using a DependencyProperty as the backing store for StartPosition. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StartPositionProperty =
DependencyProperty.Register("StartPosition", typeof(Point), typeof(StraightLine), new PropertyMetadata(new Point()));
public StraightLine()
{
InitializeComponent();
Line.X1 = 0;
Line.Y1 = 0;
Line.X2 = 300;
Line.Y2 = 200;
}
}
Here I am trying to bind them together on the mainwindow.xaml:
<Canvas>
<local:ThumbPoint x:Name="ThumbPoint"/>
<local:StraightLine StartPosition="{Binding Position, ElementName=ThumbPoint}"/>
</Canvas>
Desired effect: DependencyProperty StartPosition of the StraightLine should be updated.
Whats happening: It's not being updated so only the ThumbPoint is moving.
binding doesn't use common property wrappers for DP (public Point StartPosition), it uses SetValue() directly, so code in setter isn't invoked.
What is needed is propertyChangedCallback:
public Point StartPosition
{
get { return (Point)GetValue(StartPositionProperty); }
set { SetValue(StartPositionProperty, value); }
}
public static readonly DependencyProperty StartPositionProperty =
DependencyProperty.Register("StartPosition", typeof(Point), typeof(StraightLine), new PropertyMetadata(new Point(), OnStartPositionChanged));
private static void OnStartPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StraightLine c = (StraightLine) d;
c.Line.X1 = c.StartPosition.X;
c.Line.Y1 = c.StartPosition.Y;
}
in ThumbPoint public Point Position property has the same issue but it works there because you use setter directly: Position = new Point()
Alternatively bind X1 and Y1 value in xaml:
<Canvas>
<Line x:Name="Line" Stroke="Gray" StrokeThickness="1"
X1="{Binding StartPosition.X, RelativeSource={RelativeSource AncestorType=StraightLine}}"
Y1="{Binding StartPosition.Y, RelativeSource={RelativeSource AncestorType=StraightLine}}"/>
</Canvas>
(or use ElementName instead of RelativeSource)
I want to Style a TextBox with decimal places like this:
How can I do that ?
You can extend the TextBox like this.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
public class DecimalTextBox : TextBox
{
public static readonly DependencyProperty FloatColorProperty = DependencyProperty.Register("FloatColor", typeof(Color), typeof(DecimalTextBox), new FrameworkPropertyMetadata(Colors.Red));
public Color FloatColor
{
get { return (Color)GetValue(FloatColorProperty); }
set { SetValue(FloatColorProperty, value); }
}
protected TextBlock _textBlock;
protected FrameworkElement _textBoxView;
public DecimalTextBox()
{
_textBlock = new TextBlock() { Margin = new Thickness(1, 0, 0, 0) };
Loaded += ExTextBox_Loaded;
}
private void ExTextBox_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= ExTextBox_Loaded;
// hide the original drawing visuals, by setting opacity on their parent
var visual = this.GetChildOfType<DrawingVisual>();
_textBoxView = (FrameworkElement)visual.Parent;
_textBoxView.Opacity = 0;
// add textblock to do the text drawing for us
var grid = this.GetChildOfType<Grid>();
if (grid.Children.Count >= 2)
grid.Children.Insert(1, _textBlock);
else
grid.Children.Add(_textBlock);
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
_textBoxView.Opacity = 0;
_textBlock.Visibility = Visibility.Visible;
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
_textBoxView.Opacity = 1;
_textBlock.Visibility = Visibility.Collapsed;
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
// making sure text on TextBlock is updated as per TextBox
var dotPos = Text.IndexOf('.');
var textPart1 = dotPos == -1 ? Text : Text.Substring(0, dotPos + 1);
var textPart2 = (dotPos == -1 || dotPos >= (Text.Length-1)) ? null : Text.Substring(dotPos + 1);
_textBlock.Inlines.Clear();
_textBlock.Inlines.Add(new Run {
Text = textPart1,
FontFamily = FontFamily,
FontSize = FontSize,
Foreground = Foreground });
if (textPart2 != null)
_textBlock.Inlines.Add(new Run {
Text = textPart2,
FontFamily = FontFamily,
TextDecorations = System.Windows.TextDecorations.Underline,
BaselineAlignment = BaselineAlignment.TextTop,
FontSize = FontSize * 5/6,
Foreground = new SolidColorBrush(FloatColor) });
}
}
public static class HelperExtensions
{
public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
XAML code usage
<local:DecimalTextBox FloatColor="Maroon" />
And your output should look like this:
Update 05/17
Explanation: As you can see from the image, the DecimalTextBox displays the text in formatted mode only when its not focused.
I had initially developed the control to support formatting during edit (which can still be done by commenting the methods OnLostKeyboardFocus, and OnGotKeyboardFocus) - but because of the font-size difference the cursor positioning was getting slightly skewed, which in turn would translate to bad user experience.
Therefore, implemented the swap logic during GotFocus and LostFocus to fix that.
You can't do that with a TextBox, because TextBox only accepts color changes to the entire text. You should try with RichTextBox, that allows loop throug TextRange's. Look at this sample of syntax highlighting with a RichTextBox.
I actually understood how it works and made it:
private void richTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
richTextBox.TextChanged -= this.richTextBox_TextChanged;
if (richTextBox.Document == null)
return;
TextRange documentRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
documentRange.ClearAllProperties();
int dotIndex = documentRange.Text.IndexOf(".");
if(dotIndex == -1)
{
richTextBox.TextChanged += this.richTextBox_TextChanged;
return;
}
TextPointer dotStart = GetPoint(richTextBox.Document.ContentStart, dotIndex);
TextPointer dotEnd = dotStart.GetPositionAtOffset(1, LogicalDirection.Forward);
TextRange initRange = new TextRange(richTextBox.Document.ContentStart, dotStart);
TextRange endRange = new TextRange(dotEnd, richTextBox.Document.ContentEnd);
endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
richTextBox.TextChanged += this.richTextBox_TextChanged;
}
Suscribe the textbox TextChanged event to this method. You can now set the styles you want to every part of the text like this:
To change the last part (after the dot char) endRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
To change the first part (before the dot char) is the same but for the initRange variable. If you want to change the 'dot' style, then create a new TextRange with dotStart and dotEnd TextPointers and apply styles to it. You can do other things like change font style, size, etc.
This code result looks like this:
All this is just for style. For checking that is a number is up to you.
I would define a custom control with this main properties:
FloatNumber: the original number, that should be a DependencyProperty to be Bind from the control.
NumberOfDecimalDigits: a number to choose how many decimal digits to represent, that should be a DependencyProperty to be Bind from the control.
FirstPart: a string that will contain the first part of the decimal number
Decimals: a string that will contain the decimal digits of FloatNumber
Of course this is just a scratch, those properties could be implemented better to extract FloatNumber parts.
public partial class DecimalDisplayControl : UserControl, INotifyPropertyChanged
{
public DecimalDisplayControl()
{
InitializeComponent();
(Content as FrameworkElement).DataContext = this;
}
public static readonly DependencyProperty NumberOfDecimalDigitsProperty =
DependencyProperty.Register(
"NumberOfDecimalDigits", typeof(string),
typeof(DecimalDisplayControl), new PropertyMetadata(default(string), OnFloatNumberChanged));
public static readonly DependencyProperty FloatNumberProperty =
DependencyProperty.Register(
"FloatNumber", typeof(string),
typeof(DecimalDisplayControl), new PropertyMetadata(default(string), OnFloatNumberChanged));
private static void OnFloatNumberChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as DecimalDisplayControl).OnFloatNumberChanged();
}
protected void OnFloatNumberChanged()
{
int numberOfDecimalDigits = Convert.ToInt32(NumberOfDecimalDigits);
float fullNumber = Convert.ToSingle(FloatNumber);
float firstPart = (float)Math.Truncate(fullNumber);
float fullDecimalPart = fullNumber - firstPart;
int desideredDecimalPart = (int)(fullDecimalPart * Math.Pow(10, numberOfDecimalDigits));
FirstPart = $"{firstPart}.";
Decimals = desideredDecimalPart.ToString();
}
public string FloatNumber
{
get => (string)GetValue(FloatNumberProperty);
set { SetValue(FloatNumberProperty, value); }
}
public string NumberOfDecimalDigits
{
get => (string)GetValue(NumberOfDecimalDigitsProperty);
set { SetValue(NumberOfDecimalDigitsProperty, value); }
}
private string _firstPart;
public string FirstPart
{
get => _firstPart;
set
{
if (_firstPart == value)
return;
_firstPart = value;
OnPropertyChanged();
}
}
private string _decimals;
public string Decimals
{
get => _decimals;
set
{
if (_decimals == value)
return;
_decimals = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Its XAML:
<UserControl
x:Class="WpfApp1.CustomControls.DecimalDisplayControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock
FontSize="20"
Foreground="Black"
Text="{Binding FirstPart}" />
<TextBlock
FontSize="10"
Foreground="Red"
Text="{Binding Decimals}"
TextDecorations="Underline" />
</StackPanel>
</UserControl>
Then you can use it in your page and bind a property to make it change dynamically:
<Grid>
<StackPanel VerticalAlignment="Center" Orientation="Vertical">
<customControls:DecimalDisplayControl
HorizontalAlignment="Center"
VerticalAlignment="Center"
NumberOfDecimalDigits="2"
FloatNumber="{Binding MyNumber}" />
<TextBox
Width="200"
VerticalAlignment="Center"
Text="{Binding MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>
The final result:
Using WPF - how can I create a graph that looks like the Windows Progress bar - doughnut chart?
https://guyterry.files.wordpress.com/2015/07/upgradingrelax.jpg
Try to make it as a user control using ring component from available drag and drop components. Add label and make some properties which you can modify to get certain result.
See this post.
Or try Tutorial
Try this:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace WpfApplication1
{
public class CircleProgress : Canvas
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(180d, OnValueChanged));
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(0d, OnValueChanged));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(360d, OnValueChanged));
public static readonly DependencyProperty BackgroundCircleStrokeProperty =
DependencyProperty.Register("BackgroundCircleStroke", typeof(Brush),
typeof(CircleProgress), new FrameworkPropertyMetadata(Brushes.Gray, OnStrokeChanged));
public static readonly DependencyProperty BackgroundCircleStrokeThicknessProperty =
DependencyProperty.Register("BackgroundCircleStrokeThickness", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(5d, OnStrokeChanged));
public static readonly DependencyProperty MainCircleStrokeProperty =
DependencyProperty.Register("MainCircleStroke", typeof(Brush),
typeof(CircleProgress), new FrameworkPropertyMetadata(Brushes.DeepSkyBlue, OnStrokeChanged));
public static readonly DependencyProperty MainCircleStrokeThicknessProperty =
DependencyProperty.Register("MainCircleStrokeThickness", typeof(double),
typeof(CircleProgress), new FrameworkPropertyMetadata(5d, OnStrokeChanged));
public static readonly DependencyProperty TextStrokeProperty =
DependencyProperty.Register("TextStroke", typeof(Brush),
typeof(CircleProgress), new FrameworkPropertyMetadata(Brushes.Black, OnStrokeChanged));
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public double Minimum
{
get { return (double)GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
public double Maximum
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public Brush BackgroundCircleStroke
{
get { return (Brush) GetValue(BackgroundCircleStrokeProperty); }
set { SetValue(BackgroundCircleStrokeProperty, value); }
}
public Brush MainCircleStroke
{
get { return (Brush)GetValue(MainCircleStrokeProperty); }
set { SetValue(MainCircleStrokeProperty, value); }
}
public Brush TextStroke
{
get { return (Brush)GetValue(MainCircleStrokeProperty); }
set { SetValue(MainCircleStrokeProperty, value); }
}
public double BackgroundCircleStrokeThickness
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
public double MainCircleStrokeThickness
{
get { return (double)GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
private readonly Path _backEllipse = new Path()
{
StrokeThickness = 5,
Stroke = Brushes.Gray
};
private readonly Path _mainEllipse = new Path()
{
StrokeThickness = 5,
Stroke = Brushes.DeepSkyBlue
};
private readonly Path _text = new Path()
{
StrokeThickness = 1,
Fill = Brushes.Black
};
private double _radius = 10;
private Point _center = new Point(0,0);
private Point _startPoint = new Point(0,0);
public CircleProgress()
{
_backEllipse.Data = new EllipseGeometry(new Point(Width/2, Height/2), _radius, _radius);
_mainEllipse.Data = new PathGeometry()
{
Figures = new PathFigureCollection()
{
new PathFigure(new Point(Width/2, 0), new PathSegmentCollection()
{
new ArcSegment(
(new RotateTransform(Value*(360/(Maximum - Minimum)), _center.X, _center.Y)).Transform(
_startPoint), new Size(_radius*2, _radius*2), 0, true, SweepDirection.Clockwise, false)
}, false)
}
};
var text = new FormattedText(Value.ToString(CultureInfo.CurrentCulture),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Palatino"),
0.8* _radius,
Brushes.Black);
_text.Data = text.BuildGeometry(new Point(_center.X - text.Width/2, _center.Y - text.Height/2));
SizeChanged += OnSizeChanged;
Children.Add(_backEllipse);
Children.Add(_mainEllipse);
Children.Add(_text);
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
var quadSize = e.NewSize.Height <= e.NewSize.Width ? e.NewSize.Height : e.NewSize.Width;
_radius = quadSize / 2;
_center = new Point(e.NewSize.Width/2, e.NewSize.Height / 2);
_startPoint = _center - new Vector(0, _radius);
UpdateCircle(this);
}
/// <summary>
/// Action when Value, Minimum or Maximum changed.
/// </summary>
/// <param name="d">Dependecy object.</param>
/// <param name="e">EventArgs.</param>
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var circle = d as CircleProgress;
UpdateCircle(circle);
}
private static void OnStrokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var circle = d as CircleProgress;
circle._backEllipse.Stroke = circle.BackgroundCircleStroke;
circle._backEllipse.StrokeThickness = circle.BackgroundCircleStrokeThickness;
circle._mainEllipse.Stroke = circle.BackgroundCircleStroke;
circle._mainEllipse.StrokeThickness = circle.BackgroundCircleStrokeThickness;
circle._text.Fill = circle.TextStroke;
}
/// <summary>
/// Update Background and Main circles.
/// </summary>
/// <param name="circle">Reference to CircleProgress control.</param>
private static void UpdateCircle(CircleProgress circle)
{
circle._backEllipse.Data = new EllipseGeometry(circle._center, circle._radius, circle._radius);
if (Math.Abs(circle.Value*(360/(circle.Maximum - circle.Minimum)) - 360) < 0.0001)
circle._mainEllipse.Data = new EllipseGeometry(circle._center, circle._radius, circle._radius);
else
{
circle._mainEllipse.Data = new PathGeometry()
{
Figures = new PathFigureCollection()
{
new PathFigure(circle._startPoint, new PathSegmentCollection()
{
new ArcSegment(
(new RotateTransform(circle.Value*(360/(circle.Maximum - circle.Minimum)),
circle._center.X, circle._center.Y)).Transform(
circle._startPoint), new Size(circle._radius, circle._radius), 0,
!(circle.Value*(360/(circle.Maximum - circle.Minimum)) <= 180), SweepDirection.Clockwise,
true)
}, false)
}
};
}
var text = new FormattedText($"{circle.Value:##}",
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Palatino"),
0.8 * circle._radius,
Brushes.Black);
circle._text.Data = text.BuildGeometry(new Point(circle._center.X - text.Width / 2, circle._center.Y - text.Height / 2));
}
}
}
XAML Usage:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" x:Name="Main">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="472*"/>
<ColumnDefinition Width="45*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="27"/>
</Grid.RowDefinitions>
<local:CircleProgress Grid.Row="0" Grid.Column="0" Margin="75" Minimum="{Binding ElementName=MinTextBox, Path=Text}" Maximum="{Binding ElementName=MaxTextBox, Path=Text}" Value="{Binding ElementName=slider, Path=Value}"/>
<Slider x:Name="slider" Grid.Column="1" Grid.Row="0" Orientation="Vertical" TickPlacement="BottomRight" Minimum="{Binding ElementName=MinTextBox, Path=Text}" Maximum="{Binding ElementName=MaxTextBox, Path=Text}"/>
<StackPanel Grid.Column="0" Grid.Row="1" Orientation="Horizontal">
<Label Content="Minimum" />
<TextBox Width="200" x:Name="MinTextBox"/>
<Label Content="Maximum"/>
<TextBox Width="150" x:Name="MaxTextBox"/>
</StackPanel>
</Grid>
</Window>
Here is i've got:
I have 2 TextBoxes in a Usercontrol (InfoControl) bound to a Point property in a VM implementing INotifyPropertyChanged.
I have another UserControl (DesignerControl) which contains a draggable rectangle.
The Rectangle has its Canvas.Left and Canvas.Bottom bound to a ConvPoint property of the same VM.
ConvPoint Property (0 => ActualWidth) is a converted version of the Point Property (0 => 1)
when i drag my rectangle, the ConvPoint VM Property and the values in the textboxes are instantly updated, but when i update my textboxes with new values, the VM Point Property is instantly updated but the Rectangle is positioned only when i drag the rectangle again and not instantly.
A bit of code to explain:
First, my ViewModel's Position Property
public class MyVM : ViewModelBase
{
private DependencyPoint position;
public DependencyPoint Position
{
get { return this.position; }
set
{
this.position = value;
RaisePropertyChanged("Position");
}
}
public DependencyPoint ConvPosition
{
get { return new Point(this.Position.X * MainVM.ActualWidth, this.Position.Y * MainVM.AcutalHeight);}
set
{
Point p = new Point(value.X/MainVM.ActualWidth,value.Y/MainVM.ActualHeight);
this.position = p;
RaisePropertyChanged("ConvPosition");
}
}
}
Edit:
I'm using for this a class DependencyPoint to have a notification on the X and Y properties:
public class DependencyPoint : DependencyObject
{
public enum PointOrder
{
isStartPoint,
isEndPoint,
isPoint1,
isPoint2
}
public DependencyPoint()
{
}
public DependencyPoint(Double x, Double y, PointOrder po)
{
this.X = x;
this.Y = y;
this.Order = po;
}
public DependencyPoint(DependencyPoint point)
{
this.X = point.X;
this.Y = point.Y;
this.Order = point.Order;
}
public DependencyPoint(Point point, PointOrder po)
{
this.X = point.X;
this.Y = point.Y;
this.Order = po;
}
public Point ToPoint()
{
return new Point(this.X, this.Y);
}
public PointOrder Order
{
get { return (PointOrder)GetValue(OrderProperty); }
set { SetValue(OrderProperty, value); }
}
// Using a DependencyProperty as the backing store for Order. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OrderProperty =
DependencyProperty.Register("Order", typeof(PointOrder), typeof(DependencyPoint), new UIPropertyMetadata(null));
public Double X
{
get { return (Double)GetValue(XProperty); }
set { SetValue(XProperty, value); }
}
// Using a DependencyProperty as the backing store for X. This enables animation, styling, binding, etc...
public static readonly DependencyProperty XProperty =
DependencyProperty.Register("X", typeof(Double), typeof(DependencyPoint), new UIPropertyMetadata((double)0.0));
public Double Y
{
get { return (Double)GetValue(YProperty); }
set { SetValue(YProperty, value); }
}
// Using a DependencyProperty as the backing store for Y. This enables animation, styling, binding, etc...
public static readonly DependencyProperty YProperty =
DependencyProperty.Register("Y", typeof(Double), typeof(DependencyPoint), new UIPropertyMetadata((double)0.0));
}
then in my InfoControl:
<Grid Grid.Row="0" DataContext="{Binding Position}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Margin="3,3,0,1" Grid.Row="0" LastChildFill="True">
<TextBlock Foreground="White" Padding="0,3,0,0" Margin="2,0,0,0" DockPanel.Dock="Left" Text="StartPoint.X :"/>
<TextBox FontWeight="DemiBold" Foreground="Black" Background="#efefef" Width="Auto" Margin="7,0,0,0" Text="{Binding X, Converter={StaticResource StDConverter}, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
</DockPanel>
<DockPanel Margin="3,3,0,1" Grid.Row="1" LastChildFill="True">
<TextBlock Foreground="White" Padding="0,3,0,0" Margin="2,0,0,0" DockPanel.Dock="Left" Text="StartPoint.Y :"/>
<TextBox FontWeight="DemiBold" Foreground="Black" Background="#efefef" Width="Auto" Margin="7,0,0,0" Text="{Binding Y, Converter={StaticResource StDConverter}, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
</DockPanel>
</Grid>
And in the DesignerControl:
<UserControl>
<Canvas>
<Rectangle x:Name="Point" Width="10" Height="10" Canvas.Bottom="{Binding ConvPosition.Y, Mode=TwoWay}" Canvas.Left="{Binding ConvPosition.X, Mode=TwoWay}" />
</Canvas>
</UserControl>
I have access to the actualWidth and my Rectangle is well positioned in the Canvas.
I know my properties in the VM or a bit dirty but i don't know an other way to do it properly and manage the conversion too.
any ideas?
Because ConvPosition depends from Position you should raise a NotificationChanged for ConvPosition in Position setter.
public Point Position
{
get { return this.position; }
set
{
this.position = value;
RaisePropertyChanged("Position");
RaisePropertyChanged("ConvPosition");
}
}
ConvPosition you can change to this
public Point ConvPosition
{
get { return new Point(this.Position.X * MainVM.ActualWidth, this.Position.Y * MainVM.AcutalHeight); }
set
{
this.Position = new Point(value.X/MainVM.ActualWidth, value.Y/MainVM.ActualHeight);
}
}
EDIT
I think two additional properties in MyVM would be the simpliest solution.
public double X
{
get { return Position.X; }
set
{
Position = new Point(value, Position.Y);
RaisePropertyChanged("X");
}
}
public double Y
{
get { return Position.Y; }
set
{
Position = new Point(Position.X, value);
RaisePropertyChanged("Y");
}
}
Only change in Grid:
<Grid DataContext="{Binding}">
I have a TextBlock that's on top of a Button in a Grid. I'd like to have then displayed thus:
"some very long text" <-- text
"that doesn't fit" <-- text wrapped
[my button text size] <-- button
However, what I've got is this:
"some very long text that doesn't fit" <-- text
[my button text size] <-- button
My issue is that the text in the Button is dynamically set through localized resource and therefore the width of the button changes dynamically.
The static solution that works for non-dynamic Button resize is:
<TextBlock
Margin="5"
TextWrapping="Wrap"
Width="{Binding ElementName=requestDemoButton, Path=RenderSize.Width}"
Text="{Binding Path=Resource.Text, Source={StaticResource LocalizedStrings }}"
/>
<Button
x:Name="requestDemoButton"
Margin="5"
Height="Auto"
Width="Auto"
HorizontalAlignment="Right"
Content="{Binding Path=Resource.Button, Source={StaticResource LocalizedStrings }}" />
Ideas, anyone? I'm currently thinking of sticking a Behavior class to the TextBlock that listens for the SizeChanged event on the Button. I'd like to have a built-in solution if it exists.
If anyone's interested, here's how I've done in in a behaviour.
I pass on Height or Width according to the property I want bound.
Here's the class:
public static class DynamicControlResizeBehavior
{
public static readonly DependencyProperty TargetProperty =
DependencyProperty.RegisterAttached("Target", typeof(FrameworkElement), typeof(DynamicControlResizeBehavior), new PropertyMetadata(OnTargetSetCallback));
public static readonly DependencyProperty PropertyNameProperty =
DependencyProperty.RegisterAttached("PropertyName", typeof(string), typeof(DynamicControlResizeBehavior), new PropertyMetadata(OnPropertyNameSetCallback));
public static string GetPropertyName(DependencyObject obj)
{
return (string)obj.GetValue(PropertyNameProperty);
}
public static void SetPropertyName(DependencyObject obj, string value)
{
obj.SetValue(PropertyNameProperty, value);
}
public static FrameworkElement GetTarget(DependencyObject obj)
{
return (FrameworkElement)obj.GetValue(TargetProperty);
}
public static void SetTarget(DependencyObject obj, FrameworkElement value)
{
obj.SetValue(TargetProperty, value);
}
private static void SynchronizeProperty(DependencyObject dependencyObject)
{
var target = GetTarget(dependencyObject);
if (target != null)
{
var propertyName = GetPropertyName(dependencyObject);
DependencyProperty dependencyToRead;
DependencyProperty dependencyToWrite;
if (string.Equals(propertyName, "Width", StringComparison.InvariantCulture))
{
dependencyToRead = FrameworkElement.ActualWidthProperty;
dependencyToWrite = FrameworkElement.WidthProperty;
}
else if (string.Equals(propertyName, "Height", StringComparison.InvariantCulture))
{
dependencyToRead = FrameworkElement.ActualHeightProperty;
dependencyToWrite = FrameworkElement.HeightProperty;
}
else
{
return;
}
var propertySize = (double)target.GetValue(dependencyToRead);
dependencyObject.SetValue(dependencyToWrite, propertySize);
}
}
private static void OnTargetSetCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var oldElement = e.OldValue as FrameworkElement;
if (oldElement != null)
{
oldElement.SizeChanged -= (o, s) => SynchronizeProperty(d);
}
var newElement = e.NewValue as FrameworkElement;
if (newElement != null)
{
newElement.SizeChanged += (o, s) => SynchronizeProperty(d);
}
}
private static void OnPropertyNameSetCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SynchronizeProperty(d);
}
}
And here's how it's used:
<TextBlock
Behaviors:DynamicControlResizeBehavior.Target="{Binding ElementName=submitButton}"
Behaviors:DynamicControlResizeBehavior.PropertyName="Width"
HorizontalAlignment="Right"
Margin="5,20,5,5"
TextWrapping="Wrap"
Text="{Binding Path=Resource.RequestDemoLoginText, Source={StaticResource LocalizedStrings }}"
/>
<Button
x:Name="submitButton"
Margin="5"
Height="Auto"
Width="Auto"
HorizontalAlignment="Right"
HorizontalContentAlignment="Left"
Content="{Binding Path=Resource.RequestDemoLogin, Source={StaticResource LocalizedStrings }}" />
Hope that might help someone else.