I'm having an issue trying to bind to a RectangleGeometry's Rect property. The basic idea here is that I'm attempting to bind a clip mask to control the visualized height of a pseudo-chart object. Here's the XAML:
<Path x:Name="_value" Fill="{DynamicResource PositiveColorBrush}" Data="F1 M10,55 C10,57.75 7.75,60 5,60 2.25,60 0,57.75 0,55 L0,5 C0,2.25 2.25,0 5,0 7.75,0 10,2.25 10,5 L10,55 z">
<Path.Clip>
<!-- SECOND NUMBER CONTROLS THE HEIGHT : SCALE OF 0-60 REVERSED -->
<!--<RectangleGeometry Rect="0,22.82,10,60"/>-->
<RectangleGeometry
Rect="{Binding Score, Converter={StaticResource ChartBarScoreConverter}}" />
</Path.Clip>
</Path>
Note the commented RectangleGeometry there. That works perfectly when I uncomment it and comment out the bound RectangleGeometry. Of course, it won't change size when the Score changes, though.
Now, if I place a breakpoint in the ChartBarScoreConverter, I get the proper value and return a new RectangleGeometry object of the exact same specs as the commented out one there. Here's the short code of the converter:
...
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
RectangleGeometry output = new RectangleGeometry();
double score = 60; //0
if (Common.IsNumeric(value))
{
score = System.Convert.ToDouble(value) * .60;//scale is 0-60
score = 60 - score;//reversed (=
}
output.Rect = new Rect(0, score, 10, 60);
return output;
}
...
When the app is run, it simply doesn't show the clip. As I said, I put a breakpoint in the converter and have verified that it's called and that an object of the correct size is returned... but it just doesn't appear in the view.
Any ideas?
Thanks,
Paul
Your converter is returning a RectangleGeometry which you're then trying to assign to the Rect property of type Rect on a RectangleGeometry. Get rid of the "output" object in the converter and just return the Rect itself.
Related
I need to create a Converter that takes some value and returns another. This will be used to set the textblock RotateTransform.Angle value in XAML.
If I hard-code the value to a static number the textblock gets rotated successfully. But when it goes through the converter it does not get rotated.
Any insight would be appreciated.
Thanks.
Hard-coded value (works):
<TextBlock ...
<RotateTransform CenterX="0.5" CenterY="0.5">
<RotateTransform.Angle>
10
</RotateTransform.Angle>
</RotateTransform>
Going through a Converter (does not work):
<TextBlock ...
<RotateTransform CenterX="0.5" CenterY="0.5">
<RotateTransform.Angle>
<MultiBinding Converter="{StaticResource RelativeToAbsoluteRotationConverter}">
<Binding Path="RelativeAngle" />
</MultiBinding>
</RotateTransform.Angle>
</RotateTransform>
Converter class:
public class RelativeToAbsoluteRotationConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Add logic here... Function is hit, but no rotation ever takes place.
return 10; // irrelevant
}
// ...
Output window:
System.Windows.Data Error: 5 : Value produced by BindingExpression is not valid for target property. Int32:'30' MultiBindingExpression:target element is 'RotateTransform' (HashCode=57454947); target property is 'Angle' (type 'Double')
The solution is to modify the object Convert() method and instead of returning an int (e.g. 10), we return a double value (e.g. 10d or 10.0).
return 10d; // This works
I have BudgetControlType Properties that has 1 .. 7 value
if(BudgetControlType ==1)
dataComboBox1.Visibility=Visibility.Visiblile;
dataComboBox2 to dataComboBox7 =Visibility.Hidden;
if(BudgetControlType ==2)
dataComboBox1.Visibility=Visibility.Visiblile;
dataComboBox2.Visibility=Visibility.Visiblile;
dataComboBox3 to dataComboBox7 =Visibility.Hidden;
and so on...
How to do this in xaml?
Here is another approach I have used in the past using WPFConverters.
<TabItem.Visibility>
<Binding Path="SomeObservableCollection.Count">
<Binding.Converter>
<converters:ConverterGroup>
<converters:ExpressionConverter Expression="{}{0} > 0" />
<BooleanToVisibilityConverter />
</converters:ConverterGroup>
</Binding.Converter>
</Binding>
</TabItem.Visibility>
The ConvertGroup allows for multiple converters to be run sequentially.
The ExpressionConverter lets you define an arbitrary expression. In my case I want the TabItem to be visible if the collection count is greater than zero. Being defined in xaml means escaping characters and a somewhat awkward syntax but it works well enough!
The BooleanToVisibilityConverter converts the boolean result from the expression to our desired visibility.
For Elham, BudgetControlType could be bound to as long as it implemented INotifyPropertyChanged. An equals expression is done like this (I'm returning true if the bound value equals 7):
<converters:ExpressionConverter Expression="{}{0} == 7" />
You can use 1,2,4,8,... and convert it to Visibility
for example if your int number is 6 (2+4) then Control with paramerter 2 and Control with parameter 4 is Visible!
public class IntToVisibilityConverter:IValueConverter
{
private int val;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int intParam = (int)parameter;
val = (int)value;
return ((intParam & val) != 0) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
And in xaml :
<ComboBox Visibility="{Binding Path=MyEnum,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=1}"/>
<ComboBox Visibility="{Binding Path=MyEnum,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=2}"/>
<ComboBox Visibility="{Binding Path=MyEnum,UpdateSourceTrigger=PropertyChanged, Converter={StaticResource IntToVisibilityConverter}, ConverterParameter=4}"/>
The best way I'd say would be to go with properties on your ViewModel, and bind to them.
example (you'll have to massage it a bit, but it's fairly simple from here) :
public Visibility dtcb1 { get; set; }
// all the rest till 7
// Somewhere in your logit / constructor :
dtcb1 = BudgetControlType == 1 ? Visible : Hidden;
// and so on
And on your xaml you'll bind your visibility to dtcb1
You can make the property boolean, and use a boolean to visibility converter as well (as per this answer for example, or just google yourself)
This is sample code to draw ellipse, with shadow enabled. I set both Fill and shadow color as same. But in view shadow color is different. This may be WPF feature but in my scenario i want to set desired shadow color for the object.
<Window x:Class="Test.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Canvas>
<Ellipse Width="200" Height="300" Fill="#7D00FE">
<Ellipse.Effect>
<DropShadowEffect
ShadowDepth="5"
Color="#7D00FE"/>
</Ellipse.Effect>
</Ellipse>
</Canvas>
</Grid>
</Window>
It seems like that DropShadowEffect somehow affects the Color when it renders itself. This problem seems to be non-existing for primary colors (so named Colors, like Red, Blue, Aqua, etc. - but you don't have to use the name, you can specify them through #AARRGGBB format as well.)
I could not figure out the exact modification it does, nor can I offer a workaround (except to use named colors...), but I thought maybe it's worth noting it in an answer.
See this other questions, which probably point to the same "bug" or undocumented feature of DropShadowEffect:
DropShadowEffect with DynamicResource as color has weak
visibility
WPF DropShadowEffect - Unexpected Color Difference
Update:
So, this is cheating, but for your specific question, it might solve the issue:
<Grid>
<Canvas>
<Ellipse Width="200" Height="300" Fill="#7D00FE">
<Ellipse.Effect>
<DropShadowEffect
ShadowDepth="5"
Color="#BA00FE"/>
</Ellipse.Effect>
</Ellipse>
</Canvas>
</Grid>
With a little invested work, one might be able to come up with a converter, that can convert a Color to an other Color, which will be the desired DropShadowEffect Color for the given Color. If I will have a little time I will come back to this.
My intuition suggests that the problem might be in the shader code for that particular effect, and that the output might differ on different hardware (and/or driver version), but currently I can not prove this.
Update:
I was wrong about named colors, it does not work for all of those, e.g.: Green is flawed, but the problem is not - solely - dependent on the green component of the Color. Intriguing.
Update 2:
So here is the converter I talked about earlier:
using System;
using System.Windows.Data;
using System.Windows.Media;
namespace MyCustomConverters
{
public class ColorToShadowColorConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Only touch the shadow color if it's a solid color, do not mess up other fancy effects
if (value is SolidColorBrush)
{
Color color = ((SolidColorBrush)value).Color;
var r = Transform(color.R);
var g = Transform(color.G);
var b = Transform(color.B);
// return with Color and not SolidColorBrush, otherwise it will not work
// This means that most likely the Color -> SolidBrushColor conversion does the RBG -> sRBG conversion somewhere...
return Color.FromArgb(color.A, r, g, b);
}
return value;
}
private byte Transform(byte source)
{
// see http://en.wikipedia.org/wiki/SRGB
return (byte)(Math.Pow(source / 255d, 1 / 2.2d) * 255);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("ColorToShadowColorConverter is a OneWay converter.");
}
#endregion
}
}
And here is how it should be used:
Resources part:
<namespaceDefinedByXmlnsProperty:ColorToShadowColorConverter x:Key="ColorConverter" />
Real usage:
<Ellipse Width="50" Height="100" Fill="#7D00FE">
<Ellipse.Effect>
<DropShadowEffect ShadowDepth="50"
Color="{Binding Fill, RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType={x:Type Ellipse}},
Converter={StaticResource ColorConverter}}"/>
</Ellipse.Effect>
</Ellipse>
Thanks for Michal Ciechan for his answer, as it guided me in the right direction.
Somewhere it is converting the DropShadowEffect into a specific Sc value.
The closer to 1 you are, the less the difference (hence FF/255/1 works absolutely fine) because nth root of 1 is 1
From looking into this and researching about on ScRGB, the gamma value of ScRGB is around 2.2. Therefore when converting from RGB to ScRGB, you may need to divide by 255, then nth(2.2) root of the value to come up with the final value.
E.g.
value 5E is 94
94 / 255 = 0.36862745098039215686274509803922
2.2root of 94/255 = 0.635322735100355
0.635322735100355 * 255 = ~162 = A2
Therefore when you set the Green of the foreground to 5E, you need to set the DropShadowEffect to A2.
This is just my observation and what i came up with from my research.
Why did MS implement it like this? I HAVE NO IDEA
Sources:
RGB/XYZ Matrices
Wikipedia sRGB
Therefore in your example to have the same colour you need to use #B800FE
As explained in Ciechan's answer(thanks to Mr Ciechan), Microsoft converts the DropShadowEffect into a specific Sc value.
So how to solve it?
Just let Microsoft do the calculation back by entering the RGB value into sRGB.
//Where the variable color is the expected color.
Color newColor = new Color();
newColor.ScR = color.R;
newColor.ScG = color.G;
newColor.ScB = color.B;
//the converted color value is in newColor.R, newColor.G, newColor.B
Refer to Update 2 in #qqbenq's answer, for technical details for the binding converter(thanks to #qqbenq).
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Only touch the shadow color if it's a solid color, do not mess up other fancy effects
if (value is SolidColorBrush)
{
Color color = ((SolidColorBrush)value).Color;
//Where the variable color is the expected color.
Color newColor = new Color();
newColor.ScR = (float)color.R / 255;
newColor.ScG = (float)color.G / 255;
newColor.ScB = (float)color.B / 255;
return newColor;
}
return value;
}
Here is a formula improved for #qqbenq 's answer.
The changes are in the Transform function. It is much more accurate and the difference is around 1 value.
Therefore in questioner example to have the same colour you need to use #BA00FF and you will get #7D00FF (questioner requested for #7D00FE).
Source of reference for the formula found in https://www.nayuki.io/page/srgb-transform-library
private byte Transform(byte source)
{
// see http://en.wikipedia.org/wiki/SRGB
return (byte)(Math.Pow(source / 255d, 1 / 2.2d) * 255);
double x = (double)source / 255;
if (x <= 0)
return 0;
else if (x >= 1)
return 1;
else if (x < 0.0031308f)
return (byte)(x * 12.92f * 255);
else
return (byte)((Math.Pow(x, 1 / 2.4) * 1.055 - 0.055) * 255);
}
Based on this question, but slightly different.
I'm not sure if this is possible. Is there any way to link an UI element's width to another element's width percentage?
For example:
<ScrollViewer x:Name="scrollviewerWrapper">
<TextBlock x:Name="textblock"
Width="{Binding Path=Width, [[[ % of ]]] ElementName=scrollviewerWrapper}" />
</ScrollViewer>
So if my ScrollViewer is 100px, I want my TextBlock to be 60% of that = 60px (the scrollviewer's width is dynamic).
So I ended up using a converter to pass a number. Only think, instead of a percentage I went for the remainder of the total minus a fixed width, because I had a number for that.
XAML:
Width="{Binding Path=ActualWidth, ElementName=exampleElement, Converter={StaticResource MyConverter}, ConverterParameter={StaticResource ResourceKey=actionAreaWidth}}">
Parameter conversion:
<clr:Double x:Key="actionAreaWidth">150</clr:Double>
And the converter itself:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double percentage = 100;
double.TryParse(parameter.ToString(), out percentage);
var actualWidth = (double)value;
return actualWidth * (percentage / 100);
}
I am working on an application that needs to be able to manipulate shapes and lines in WPF. My original thought was to databind a collection to ListBox and use Rectangles in the datatemplate, setting each of the fill properties to the image. This has worked well for the majority of shapes, except for circles and a few rectangles. Since re-sizing an image causes pixelation and the lines to change sizes, the result is less than stellar.
I have spent some time browsing SO and a few other sites regarding Path elements, but haven't found anything that really meets my needs. My guess is I will need to generate paths differently for each type of shape and databind them using a converter similar to Path drawing and data binding or use http://www.telerik.com/help/wpf/raddiagram-overview.html or similar rad tool.
My questions: Is there an easier way of accomplishing this or any other examples?
EDIT: I also need to be able to add text. Not sure how I can do that with a path...maybe a ContentControl?
You can draw all manner of shapes by databinding a Path.Data to a Geometry. You can generate the Geometry from a list of points. A converter is perfect for this adaptation.
For example, I draw spirals by databinding the Path.Data property to a StreamGeometry which I generate off of a list of points managed by the view model, and it works quite well for my needs:
// ViewModel ...
public class ViewModel
{
[Notify]
public IList<Point> Points { get; set; }
}
// Converter ...
public class GeometryConverter : IValueConverter
{
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
{
if (value == null || value == DependencyProperty.UnsetValue)
{
return value;
}
var points = (IList<Point>)value;
var i = 0;
var newPath = new StreamGeometry();
using (var context = newPath.Open())
{
var begun = false;
for (var i = 0; i < points.Count; i++)
{
var current = points[i];
if (!begun)
{
begun = true;
context.BeginFigure(current, true, false);
}
else
{
context.ArcTo(current, new Size(radius, radius), angle, false, SweepDirection.Counterclockwise, true, true);
}
}
}
newPath.Freeze();
return newPath.GetFlattenedPathGeometry();
}
}
XAML:
<Canvas>
<Path StrokeThickness="{Binding StrokeWidth}"
Canvas.Top="{Binding Top}"
Canvas.Left="{Binding Left}"
Data="{Binding Points, Converter={StaticResource GeometryConverter}}">
<Path.Stroke>
<SolidColorBrush Color="{Binding CurrentColor}" />
</Path.Stroke>
</Path>
</Canvas>
As for the text, wouldn't it be better to bind TextBlock elements and arrange those on a 'Canvas` as needed?