Databind RenderTransform Scaling in Silverlight 2 Beta 2 - silverlight

Anyone know if it's possible to databind the ScaleX and ScaleY of a render transform in Silverlight 2 Beta 2? Binding transforms is possible in WPF - But I'm getting an error when setting up my binding in Silverlight through XAML. Perhaps it's possible to do it through code?
<Image Height="60" HorizontalAlignment="Right"
Margin="0,122,11,0" VerticalAlignment="Top" Width="60"
Source="Images/Fish128x128.png" Stretch="Fill"
RenderTransformOrigin="0.5,0.5" x:Name="fishImage">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Image.RenderTransform>
</Image>
I want to bind the ScaleX and ScaleY of the ScaleTransform element.
I'm getting a runtime error when I try to bind against a double property on my data context:
Message="AG_E_PARSER_BAD_PROPERTY_VALUE [Line: 1570 Position: 108]"
My binding looks like this:
<ScaleTransform ScaleX="{Binding Path=SelectedDive.Visibility}"
ScaleY="{Binding Path=SelectedDive.Visibility}"/>
I have triple verified that the binding path is correct - I'm binding a slidebar against the same value and that works just fine...
Visibility is of type double and is a number between 0.0 and 30.0. I have a value converter that scales that number down to 0.5 and 1 - I want to scale the size of the fish depending on the clarity of the water. So I don't think it's a problem with the type I'm binding against...

ScaleTransform doesn't have a data context so most likely the binding is looking for SelectedDive.Visibility off it's self and not finding it. There is much in Silverlight xaml and databinding that is different from WPF...
Anyway to solve this you will want to set up the binding in code**, or manually listen for the PropertyChanged event of your data object and set the Scale in code behind.
I would choose the latter if you wanted to do an animation/storyboard for the scale change.
** i need to check but you may not be able to bind to it. as i recall if the RenderTransform is not part of an animation it gets turned into a matrix transform and all bets are off.

Is it a runtime error or compile-time, Jonas? Looking at the documentation, ScaleX and ScaleY are dependency properties, so you should be able to write
<ScaleTransform ScaleX="{Binding Foo}" ScaleY="{Binding Bar}" />
... where Foo and Bar are of the appropriate type.
Edit: Of course, that's the WPF documentation. I suppose it's possible that they've changed ScaleX and ScaleY to be standard properties rather than dependency properties in Silverlight. I'd love to hear more about the error you're seeing.

Ah I think I see your problem. You're attempting to bind a property of type Visibility (SelectedDive.Visibility) to a property of type Double (ScaleTransform.ScaleX). WPF/Silverlight can't convert between those two types.
What are you trying to accomplish? Maybe I can help you with the XAML. What is "SelectedDive" and what do you want to happen when its Visibility changes?

Sorry - was looking for the answer count to go up so I didn't realise you'd edited the question with more information.
OK, so Visibility is of type Double, so the binding should work in that regard.
As a workaround, could you try binding your ScaleX and ScaleY values directly to the slider control that SelectedDive.Visibility is bound to? Something like:
<ScaleTransform ScaleX="{Binding ElementName=slider1,Path=Value}" ... />
If that works then it'll at least get you going.
Edit: Ah, I just remembered that I read once that Silverlight doesn't support the ElementName syntax in bindings, so that might not work.

Yeah maybe the embedded render transforms aren't inheriting the DataContext from the object they apply to. Can you force the DataContext into them? For example, give the transform a name:
<ScaleTransform x:Name="myScaler" ... />
... and then in your code-behind:
myScaler.DataContext = fishImage.DataContext;
... so that the scaler definitely shares its DataContext with the Image.

Ok, is the Image itself picking up the DataContext properly?
Try adding this:
<Image Tooltip="{Binding SelectedDive.Visibility}" ... />
If that compiles and runs, hover over the image and see if it displays the right value.

I was hoping to solve this through XAML, but turns out Brian's suggestion was the way to go. I used Matt's suggestion to give the scale transform a name, so that I can access it from code. Then I hooked the value changed event of the slider, and manually updates the ScaleX and ScaleY property. I kept my value converter to convert from the visibility range (0-30m) to scale (0.5 to 1). The code looks like this:
private ScaleConverter converter;
public DiveLog()
{
InitializeComponent();
converter = new ScaleConverter();
visibilitySlider.ValueChanged += new
RoutedPropertyChangedEventHandler<double>(visibilitySlider_ValueChanged);
}
private void visibilitySlider_ValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
fishScale.ScaleX = (double)converter.Convert(e.NewValue,
typeof(double), null, CultureInfo.CurrentCulture);
fishScale.ScaleY = fishScale.ScaleX;
}

Related

How do I bind to the X position of a MouseDragElementBehavior?

My goal is to display the X position of my control in a TextBlock as I drag it around.
xmlns:mb="http://schemas.microsoft.com/xaml/behaviors"
<cc:CardControl Name="SevenOfSpades" Canvas.Left="350" Canvas.Top="124" Width="60" Height="80" Face="S7">
<mb:Interaction.Behaviors>
<mb:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</mb:Interaction.Behaviors>
</cc:CardControl>
<TextBlock Text="{Binding ElementName=SevenOfSpades, Path=(mb:Interaction.Behaviors)[0].X}"/>
I'm struggling with the syntax of the Binding Path. At runtime I get an exception:
InvalidOperationException: Property path is not valid. 'Interaction' does not have a public property named 'Behaviors'.
The property is there because the drag works when the TextBlock is removed. I've tried various combinations of parentheses, I even tried x:static. Any help?
Edit
Having reread WPF Attached Property Data Binding, it does not solve my problem. Path= is in the Xaml and parentheses are included. The error is not a binding error it's a runtime error that occurs inside InitializeComponent.
MouseDragElementBehavior is part of the Microsoft.Xaml.Behaviors.Wpf Nuget package installed into my project.
Ah, ok. In that case, the code for MouseDragElementBehavior is most certainly available, and even if it wasn't you could just open up the assembly with JustDecompile or something and browse it that way.
If you check the documentation for MouseDragElementBehavior you'll see this:
XProperty Dependency property for the X position of the dragged
element, relative to the left of the root element.
So basically you're trying to bind one dependency property (TextBlock.Text) to another (MouseDragElementBehavior.X), but in order for this to work they have to be part of the same visual or logical tree (which they aren't, MouseDragElementBehavior is a behavior). If one of them was an attached property then you could bind them directly, but in your case you have to link them together with either a property in your DataContext that supports INPC, or some kind of proxy object.
However, even if you do this, you're going to run into problems. If you click the "Go to Live Visual Tree" button while your application is running and look at the properties for your SevenOfSpades control you'll see this:
So far, so good. Now drag the control around a bit and repeat this process. Suddenly a RenderTransform field has appeared:
Looking back at the code for MouseDragElementBehavior reveals that sure enough, that behaviour does the drag by changing the render transform.
So basically you're trying to set the position with Canvas.Top/Canvas.Left, but the behaviour is setting it by applying a render transform offset. Pick one. I personally use MVVM where everything is implemented in the view model layer, so it's easy to bind Canvas.Top/Canvas.Left to properties there. If you want to continue using MouseDragElementBehavior then you'll need to bind both the position of your cards, as well as your TextBlock text, to the render transform instead:
<Canvas>
<Rectangle Name="SevenOfSpades" Width="60" Height="80" Fill="Blue">
<Rectangle.RenderTransform>
<TranslateTransform X="350" Y="124" />
</Rectangle.RenderTransform>
<mb:Interaction.Behaviors>
<mb:MouseDragElementBehavior ConstrainToParentBounds="True" />
</mb:Interaction.Behaviors>
</Rectangle>
<TextBlock Text="{Binding ElementName=SevenOfSpades, Path=RenderTransform.Value.OffsetX}" />
</Canvas>

Are WPF Inherited Attached Dependency properties "expensive"?

I've got a working app that draws shapes and images on a zoomable canvas. I'm wondering about the effects of changing my current approach from using a global, transient settings object to using inherited attached properties.
To explain: A few aspects of the drawing (e.g. line widths, font sizes, etc) are user-configurable but are also affected by zoom level. I quickly found that merely binding, say, the StrokeThickness of my shapes to a configured value caused problems. If the user zoomed the canvas way in or out, the thickness changed. I wanted it to stay constant.
So I chose up with a solution that instead bound my shapes to a global, transient set of "Live" settings derived from the configured settings and the current zoom scale. The code-behind changes these live settings as the user zooms my canvas in or out.
private void UpdateScaledSizesAfterZoom()
{
// Get the scale from the canvas' scale transform.
if (!(Scene.LayoutTransform is ScaleTransform st))
return;
var div = st.ScaleX > 0 ? st.ScaleX : 1;
// Update all live settings with the new scale.
LiveSettings.LineWidth = Settings.LineWidth/ div;
LiveSettings.FontSize = Settings.FontSize / div;
}
Binding:
<Path StrokeThickness="{Binding Source={x:Static LiveSettings.Default}, Path=LineWidth}" Data=... blah blah blah .../>
This all works well enough but something about tying all my objects to a global object just plain bothers me. I can stay with it if I must but I wouldn't mind something cleaner.
So I wondered about an approach that used WPF Property Inheritance instead; I could, alternately register properties like this as inherited attached properties on my canvas ("ShapeCanvas"). Then my shapes could bind to "ShapeCanvas.LineWidth" and not need to rely on the existence of some global settings object.
However I might have many, many shapes. So I am wondering about how this might affect performance. How expensive is it for WPF to propagate a property like this through containment inheritance?
I've already debugged some of how attached properties work and it appears that when an attached inherited property changes, literally every item in the inheritance context gets notified about it. So it seems like this could be quite expensive indeed. I wouldn't want to make my zooming laggy or anything.
Does anyone have any experience with such issues? Is this something to be concerned about.
Here is a very simple example of how to transform the Geometry of a Path instead of the Path element itself, which avoids the need to re-scale its StrokeThickness:
<Window.Resources>
<MatrixTransform x:Key="GeometryTransform"/>
</Window.Resources>
<Canvas Background="Transparent" MouseWheel="Canvas_MouseWheel">
<Path Fill="Yellow" Stroke="Blue" StrokeThickness="3">
<Path.Data>
<PathGeometry Figures="M100,50 L150,100 100,150 50,100Z"
Transform="{StaticResource GeometryTransform}"/>
</Path.Data>
</Path>
</Canvas>
with this MouseWheel handler:
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
var transform = (MatrixTransform)Resources["GeometryTransform"];
var matrix = transform.Matrix;
var scale = e.Delta > 0 ? 1.1 : 1 / 1.1;
var pos = e.GetPosition((IInputElement)sender);
matrix.ScaleAt(scale, scale, pos.X, pos.Y);
transform.Matrix = matrix;
}

Making a control hidden by default while its Visibility property is bound and context set after Loaded

I have a rectangle with its Visibility property bound to the view-model. For (apparent) performance reasons, I'm setting the DataContext in the page's Loaded event. This rectangle should be collapsed by default. Unfortunately, with this "late-context" pattern, the rectangle shows for a few fractions of a second.
Is there a no-code way to make it collapsed by default?
If no, I could simply set the property to Collapsed and bind it in the Loaded event, but I have many such rectangles in my app.
Could I implement a ContentControl that is collapsed until loaded? Is that second idea too far-fetched?
#Martin - have you tried setting the FallbackValue in your binding?
I don't have the VS at hand to check it, but I think that if your setup is like (sorry for 'errors', thats just a draft:
<rectangle visibility={Binding mydata.somthing.someVisibilityProperty} />
then, if binding at Loaded, your control may "flash" with the default Visibility.Visible value. Your binding fails at the first render, because there's no data bound yet. If so, then just setup the fallbackvalue:
<rectangle visibility={Binding mydata.somthing.someVisibilityProperty, FallbackValue=Collapsed} />
this will cause the binding to return "Visibility.Collapsed" whenever it fails to read from the source.
In case of
<Rectangle Visibility="{Binding TipRoundingHasError, Converter={StaticResource VisibilityConverter}}">
that you have presented in your second answer, it would basically look like:
<Rectangle Visibility="{Binding TipRoundingHasError, FallbackValue=DEFAULTVALUE, Converter={StaticResource VisibilityConverter}}">
but I dont recall now, whether yout Converter will be invoked on the FallbackValue or not. That means, that I cant tell you now, if you should substitute DEFAULTVALUE for "Collapsed" or rather for "False". But I think you will test&choose the correct one in an instant.
For more examples on Fallback, try looking at the BindingBase.FallbackValue - there's nice example with a custom binding class (yeah, not only converters may be custom:) )
I made an AppearingControl who's implementation defies calling this "coding" since it's so simple:`
public class AppearingControl : ContentControl
{
public AppearingControl()
{
if( !System.ComponentModel.DesignerProperties.IsInDesignTool )
{
this.Visibility = System.Windows.Visibility.Collapsed;
this.Loaded += new RoutedEventHandler( AppearingControl_Loaded );
}
}
void AppearingControl_Loaded( object sender, RoutedEventArgs e )
{
this.Loaded -= new RoutedEventHandler( AppearingControl_Loaded );
this.ClearValue( AppearingControl.VisibilityProperty );
}
}
I can use the control this way:
<slim:AppearingControl HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<Rectangle Visibility="{Binding TipRoundingHasError, Converter={StaticResource VisibilityConverter}}">
<Rectangle.Fill>
<SolidColorBrush Color="#FFFF4040" Opacity="0.5"/>
</Rectangle.Fill>
</Rectangle>
</slim:AppearingControl>
Can someone confirm I didn't just reinvent the wheel, or worse, use a bazooka to kill a fly?
Thanks.

Binding Setting Property but UI not updating. Can I debug within referenced project/control?

I have a custom control with bindings like below
<DataTemplate DataType="{x:Type vm:EditorTabViewModel}">
<me:MarkdownEditor
Options="{Binding Path=Options, RelativeSource={RelativeSource AncestorType=Window}}" />
</DataTemplate>
I find that binding (Window1.Options) is being set (after stepping through code in debug mode), the markdown editor options (supposed to set Fonts, Colors etc) does not get set, or at least the UI does not update. I want to bug whats happening with in the MarkdownEditor.xaml.cs but thats another (referenced) project. How can I verify that the MarkdownEditor.Options is being set at least?
I have actually tested that the MarkdownEditor side is working by the below
<Window ...>
<Grid>
<Button Content="Options" Click="Button_Click" Grid.Row="0" />
<me:MarkdownEditor Options="{Binding Options, RelativeSource={RelativeSource AncestorType=Window}}" Grid.Row="1" />
</Grid>
</Window>
So the difference is the latter is a MarkdownEditor just in a Grid in a Window. The one failing is a MarkdownEditor within a TabControl bound to a ObservableCollection<TabViewModel>
Visual Studio Solution Replicating The Problem
I am not really good at explaining things, so a simple project I made up minus all the unnecessary noise uploaded to media fire so you can take a look at whats wrong
The video showing the problem on Screenr
With just a simple usage, editor in a window/grid.
the binding works ok
Then when used in conjunction with TabControl bound to ObservableCollection<EditorTabViewModel>, the binding works as shown in the 2 TextBoxes updating its values. but the editor does not update
After reading Kent Boogaart's answer to this question I think that the right place to change SetValue to SetCurrentValue isn't in the CLR Property but in the constructor for MarkDownEditor.
public MarkdownEditor()
{
InitializeComponent();
//Options = new MarkdownEditorOptions();
this.SetCurrentValue(OptionsProperty, new MarkdownEditorOptions());
DataContext = this;
}
In fact, this will work just as good without this.SetCurrentValue also since Options will be set through the Binding.
To verify that your Binding has in fact been overwritten by SetValue you can add this code in some event for TabUsage (e.g PreviewMouseRightButtonDown for the FontSize TextBox) and the Binding will start to work again.
private void TextBox_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
MarkdownEditor.MarkdownEditor editor = VisualTreeHelpers.GetVisualChild<MarkdownEditor.MarkdownEditor>(this);
Binding binding = new Binding();
binding.Path = new PropertyPath("Options");
binding.Source = this;
binding.Mode = BindingMode.TwoWay;
editor.SetBinding(MarkdownEditor.MarkdownEditor.OptionsProperty, binding);
}

Adding Enumeration Value to Silverlight Attribute/Property

In the <ImageBrush/> element, there are AlignmentX and AlignmentY attributes with values Left/Center/Right and Top/Center/Bottom, respectively.
What I'm wanting to do is set my own value in, for example, AlignmentX either as a value or as another enumeration like AlignmentX="HalfCenter" where HalfLeft equals my own value (halfway between Center and Left). For example, if I have this:
<Rectangle Canvas.Left="0" Stroke="LimeGreen" StrokeThickness="16" Canvas.Top="0"
Width="400" Height="400" >
<Rectangle.Fill>
<ImageBrush ImageSource="newone.jpg"
Stretch="None" AlignmentX="HalfLeft" AlignmentY="Top" />
</Rectangle.Fill>
</Rectangle>
I don't know if this is a Dependency Property, Attached Property or otherwise (don't yet know how to create those). In the helpfile, it says in TileBrush.AlignmentXProperty field: Public Shared ReadOnly AlignmentXProperty As DependencyProperty. Does the ReadOnly word here mean that I can't set this property to a custom property?
If this can't be an override of that property, how can I create my own? I think this is an Attached Property and it could be called something different, like OffsetX and OffsetY that set an ImageBrush to a location inside its parent Shape. I'm getting very confused by the SL documentation on how I would do this though (almost no examples in VB.NET - but even the C# ones aren't all that revealing).
If it is possible, how would I get started on this?
Save yourself the pain and just use a value convertor and even that is going to be a little tricky, since you are going to have to apply a rendertransform or something to react to your enums.
You also could write your own panel which is probably a better idea.
You have a few different problems here to confront, creating the attached property, validating the enum, having the enum do what you want it to do when it is set.
Your also going to have to learn about MeasureOverride and ArrangeOverride
If you just can't help yourself ... Look Here

Resources