Accessing user control properties in IvalueConverter | silverlight 4 - silverlight

I am trying to write a custom silverlight control which represents a water tank. It has two dependency properties, liquidLevel and liquidCapacity, and I want to pass both of these parameters into a converter along with a gradientBrush. The idea is the converter will perform a calcuation based on the liquidlevel and capacity and adjust the gradient stops on the brush to give the appearance of liquid rising and falling.
my tank has a "window" which is just a rectangle and gradientBrush, so far I have got this
My control template
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MoreControls;assembly=MoreControls"
xmlns:assets="clr-namespace:MoreControls.Assets">
<LinearGradientBrush x:Name="LiquidLevelTankWindow" StartPoint="0.469,0.997" EndPoint="0.487,0.013">
<GradientStop Color="#FF1010F1" Offset="0.0"/>
<GradientStop Color="#FF5555FB" Offset="0.55"/>
<GradientStop Color="#FFE4E4F1" Offset="0.6"/>
<GradientStop Color="#FFFAFAFD" Offset="1"/>
</LinearGradientBrush>
<assets:LiquidLevelBrushConverter x:Name="LiquidLevelBrushConverter" levelBrush="{StaticResource LiquidLevelTankWindow}"/>
<Style x:Key="Liquid" TargetType="local:LiquidTank">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:LiquidTank">
// * parts of the control here *
// the part of the control im interested in
<Rectangle x:Name="TankWindow" Width="32.3827" Height="64" Canvas.Left="27" Canvas.Top="42" Stretch="Fill" StrokeLineJoin="Round" Stroke="#FF000310"
Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LiquidLevel, Converter={StaticResource LiquidLevelBrushConverter}}" />
// * rest of control template *
Using the control in xaml (eventually I want to bind these properties)
<local:LiquidTank Style="{StaticResource Liquid}" LiquidCapacity="100" LiquidLevel="50"/>
and the converter
public class LiquidLevelBrushConverter : IValueConverter
{
public LinearGradientBrush levelBrush { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//I can access the liquid level parameter here
double level = 0;
double.TryParse(value.ToString(), out level);
GradientStopCollection gsc = levelBrush.GradientStops;
//some logic to alter gradient stops
return null;
}
Where I am now is that I want to access the second control property liquidCapacity from my converter so I can calculate the percentage of the tank that is full. I have tried passing liquidCapacity through as a converter parameter but if that's possible I cant figure out the syntax (I'm pretty new to silverlight).
Now that i've got this far im thinking I could have created a single dependancyproperty called fillpercentage, and perform this calculation in the eventual viewmodel, but with the way data will organised in there im pretty sure I will have a whole new set of challenges if I try this. It seems more manageable to me to be able to hardcode the liquidcapacity in the xaml, and bind the liquidlevel to the viewmodel. The view model will being pulling a bunch of values out of a database and into an observable dictionary, one of those being the liquidlevel, so it would be much easier I think to bind liquidlevel directly to the observable dictionary, rather than try and convert it to a "fillpercentage" in the view model before binding it.
Plus im pretty stubborn and in the interest of my education does anyone know if what I proposed to do is possible. If so, what the correct way to go about it ?

When you say that you
have tried passing liquidCapacity through as a converter parameter
I suspect you're trying to do something like
<Rectangle Fill="{Binding Path=LiquidLevel, ConverterParameter={Binding Path=LiquidCapacity} ...}" />
This won't work. You can't have a binding inside another binding.
Personally, I wouldn't use a converter for what you're trying to do. Instead, I'd add a method to adjust the gradient stops of the LinearGradientBrush to the code of the LiquidTank control. I'd then add PropertyChangedCallbacks to the LiquidLevel and LiquidCapacity dependency properties of the LiquidTank control and call this method from within these callbacks.

Related

Converter for Static Resources on Windows Phone

Hopefully this should be an easy one, i have a background of a rectangle i want to display as the phone accent colour or a disabled color based on a boolean in my view model.
I assume that converters are the way to go, but not sure of the syntax to get access to the static resources.
<Rectangle.Fill>
<SolidColorBrush Color="{StaticResource PhoneAccentColor}"/>
</Rectangle.Fill>
Grab the code for a generic BoolToValueConverter from this blog article:-
A Generic Boolean Value Converter
Also include in your code this specialisation for a converter to a Brush:-
public class BoolToBrushConverter : BoolToValueConverter<Brush> { }
Now add the converter to your Xaml like this:-
<Grid.Resources>
<local:BoolToBrushConverter x:Key="DisabledBrushConv"
FalseValue="{StaticResource PhoneAccentBrush}"
TrueValue="{StaticResource PhoneDisabledBrush}" />
</Grid>
Then in rectangle :-
<Rectangle Fill="{Binding Disabled, Converter={StaticResource DisabledBrushConv}}" ... />
This assumes the property in your view model is called Disabled.
You have two options:
Use a converter
Define a property on your viewmodel that returns a Brush based on the boolean value. I would prefer this solution because the performance hit of converters are more noticable on the phone than on the desktop.

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

WPF Dynamic Binding X and Y Co-ordinates

I have a question on WPF dynamic positioning.
I want to place Elipses on the screen based on X and Y co-ordinates that i have stored in a collection in C#.
I have been made aware of the drawing ability in WPF which you do from C# using the Windows.Media and Windows.Shapes.
Now what i actually want to do is use these namespaces to draw the elipses in the first case in a canvas all done in c# using my datasource that i have in c# to position the elipses using the x and y co-ordinates.
Now the complex part which is confusing me is what if the data in the datasource is changed as the data in the database changes, i will implement some sort of routine that checks the database every few seconds pulling back back any data that has changed since the last retrieval. Now i have seen the IPropertyChanged interface which i will inhert from for my class that i expose as my datasource for the page so when i retrieve the updated dataset i can call the PropertyChanged event which will notify WPF that the datasource has changed.
How would i bind the elipses in the UI when i was laying them out originally in C# to certain items from the datasource so when the datasource changed the elipses would automatically change as required to reflect the changed datasource as long as the ID for each x and y co-ordinate remained the same. So can i bind to specific rows from the collection for each elipse in my canvas when i'm setting them out?
I don't even know if its possible to bind a datasource to a Canvas inside which i can use the collection as i require to begin with but i thought i'd put this question out there incase someone has done something similar so i have a good starting point.
Thanks
Iffy.
To build on what others have said here is a complete self contained example - you can copy it straight into kaxaml or xamlpad (or blend, but I think in that case it has to go into a body of a usercontrol or a window) and see how it works.
Instead of using the rendertransform I prefer to use the canvas and set the left and top property, I just find it more readable that way. Alternatively, you can use a grid and set the margin but then you'll need a value converter of some kind.
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid.Resources>
<!-- This is our list of shapes, in this case an inline XML list -->
<XmlDataProvider x:Key="ShapeList">
<x:XData>
<ObjectList xmlns="">
<Shapes>
<shape height="30" width="30" x="50" y="50"/>
<shape height="30" width="40" x="100" y="100"/>
<shape height="30" width="50" x="150" y="150"/>
<shape height="30" width="60" x="200" y="200"/>
<shape height="30" width="70" x="250" y="350"/>
</Shapes>
</ObjectList>
</x:XData>
</XmlDataProvider>
</Grid.Resources>
<ItemsControl ItemsSource="{Binding Source={StaticResource ShapeList}, XPath=ObjectList/Shapes/*}">
<!-- this template sets the panel as canvas for easy positioning -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- this template defines how each bound item is represented -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="{Binding XPath=#width}" Height="{Binding XPath=#height}">
<Ellipse Fill="White" Stroke="Black" StrokeThickness="2"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- This style positions each bound item's container -->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding XPath=#x}"/>
<Setter Property="Canvas.Top" Value="{Binding XPath=#y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
Instead of binding to an inline xml list you can bind to a collection on your viewmodel (best choice), a dependency property on your control or window, set the resource from codebehind, etc.
The key point is that you shouldn't be laying out the ellipses in C# unless you absolutely have to. Provide the data as some sort of a list of meaningful objects. Then create a data template that defines how that data is represented. Assuming you don't have to do any sort of complicated processing of your object to get the relevant ellipse properties you should be able to do this without any code, or at most with a few value converters.
This is the sort of UI separation that allows you to deal with updating the datasource (business logic) and displaying items (ui) separately.
So basically the idea is:
Expose a collection of objects - in my example this would be a collection of classes mirroring the structure of the shape xml element in the list. This can be the business object itself, or a viewmodel - a class that wraps a business objects and exposes conveniently bindable properties (in this case, position and size). The collection itself would prefereably be an ObservableCollection, so that UI is notified when you add or remove objects. Toss in some design time data into it if possible.
Bind to the collection, using the WPF datatemplates to define how an element should be presented. In this case I used a plain ItemsControl with a few simple templates, but this can be as complex as required
Work out how the collection will be updated from the original datasource. If you set up the previous steps correctly this is essentially a separate problem
You can use a translate transform to position the ellipses as you create them.
TranslateTransform transform = new TranslateTransform();
transform.X = X;
transform.Y = Y;
Ellipse ellipse = new Ellipse();
ellipse.RenderTransform = transform;
...
You could store the ellipses in a dictionary with the id as they key for quick and easy retrieval.
TranslateTransform transform = data[id].RenderTransform as TranslateTransform;
transform.X = newX;
transform.Y = newY;
You can accomplish this within a DataTemplate if your Ellipse objects are represented by a class, and perhaps displayed in an ItemsControl.
<Ellipse>
<Ellipse.LayoutTransform>
<TranslateTransform X="{Binding XCoord}"
Y="{Binding YCoord}" />
</Ellipse.LayoutTransform>
</Ellipse>
You would choose between LayoutTransform and RenderTransform based on the panel which held your Ellipse objects.
I also recommend reviewing an article by Bea Stollnitz (neƩ Costa) which shows how to leverage a ListBox backed by a Canvas with DataBinding to produce offset objects. Very cool.

TwoWay MultiBinding with read-only properties

How to skip updating some of the sub-bindings of a MultiBinding? I have defined in code-behind (I had some troubles making it in XAML and I don't think it matters - after all code-behind is not less expressive then XAML) a MultiBinding which takes two read-only properties and one normal property to produce a single value. In case of ConvertBack the read-only properties are not modified (they sustain their value) and only the normal property is changed.
While defining the MultiBinding the entire MultiBinding was set to TwoWay however particular sub-bindings where set appropriate (first two to OneWay and the third two TwoWay).
The problem occurs in a my own control. However for the sake of presentation I simplified it to a smaller control. The control presented in this example is a Slider-like control allowing to select a value in [0.0; 1.0] range. The selected value is represented by the thumb and exposed as a DependencyProperty.
Basically the control is build by a 1 row x 3 column Grid where the thumb is in the middle column. To correctly position the thumb left column must be assigned width corresponding to selected position. However this width depends also on the actual width of the entire control and actual width of the thumb itself (this is because the position is given as a relative value in [0.0; 1.0] range).
When the thumb is moved the position should be updated appropriately however the thumb width and control width obviously do not change.
The code works as expected however when run in IDE during thumb moving Output window is cluttered with exceptions information as reported when MultiBinding tries to set value to those two read-only properties. I suspect it is not harmful however it is somewhat annoying and misleading. And also it means that the code does something else then I wanted it to do as I didn't want to set those properties (this matters in case they were not read-only and this would actually modify them).
MultiBinding documentation in Remarks section mentions that individual sub-bindings are allowed to override the MultiBinding mode value but it doesn't seem to work.
Maybe this could be solved somehow by expressing the dependency on the control and thumb widths (the read-only properties) somehow differently. For example registering to their notifications separately and enforcing update upon their change. However it does not seem natural to me. MultiBinding does on the other hand as after all left column width does depend on those three properties.
Here is the example XAML code.
<UserControl x:Class="WpfTest.ExampleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="leftColumn" />
<ColumnDefinition x:Name="thumbColumn" Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- Rectangle used in the left column for better visualization. -->
<Rectangle Grid.Column="0">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<!-- Thumb representing the Position property. -->
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Center" />
<!-- Rectangle used in the right column for better visualization. -->
<Rectangle Grid.Column="2">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="White" Offset="0" />
<GradientStop Color="Black" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</UserControl>
And here is the corresponding code-behind
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfTest
{
public partial class ExampleUserControl : UserControl
{
#region PositionConverter
private class PositionConverter : IMultiValueConverter
{
public PositionConverter(ExampleUserControl owner)
{
this.owner = owner;
}
#region IMultiValueConverter Members
public object Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
double thisActualWidth = (double)values[0];
double thumbActualWidth = (double)values[1];
double position = (double)values[2];
double availableWidth = thisActualWidth - thumbActualWidth;
double leftColumnWidth = availableWidth * position;
return new GridLength(leftColumnWidth);
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
CultureInfo culture)
{
double thisActualWidth = owner.ActualWidth;
double thumbActualWidth = owner.thumbColumn.ActualWidth;
GridLength leftColumnWidth = (GridLength)value;
double availableWidth = thisActualWidth - thumbActualWidth;
double position;
if (availableWidth == 0.0)
position = 0.0;
else
position = leftColumnWidth.Value / availableWidth;
return new object[] {
thisActualWidth, thumbActualWidth, position
};
}
#endregion
private readonly ExampleUserControl owner;
}
#endregion
public ExampleUserControl()
{
InitializeComponent();
MultiBinding leftColumnWidthBinding = new MultiBinding()
{
Bindings =
{
new Binding()
{
Source = this,
Path = new PropertyPath("ActualWidth"),
Mode = BindingMode.OneWay
},
new Binding()
{
Source = thumbColumn,
Path = new PropertyPath("ActualWidth"),
Mode = BindingMode.OneWay
},
new Binding()
{
Source = this,
Path = new PropertyPath("Position"),
Mode = BindingMode.TwoWay
}
},
Mode = BindingMode.TwoWay,
Converter = new PositionConverter(this)
};
leftColumn.SetBinding(
ColumnDefinition.WidthProperty, leftColumnWidthBinding);
}
public static readonly DependencyProperty PositionProperty =
DependencyProperty.Register(
"Position",
typeof(double),
typeof(ExampleUserControl),
new FrameworkPropertyMetadata(0.5)
);
public double Position
{
get
{
return (double)GetValue(PositionProperty);
}
set
{
SetValue(PositionProperty, value);
}
}
}
}
Finally I found the solution myself. Actually it is in the documentation - I don't know how I missed that but I paid dearly (in wasted time) for it.
According to the documentation ConvertBack ought to return Binding.DoNothing on positions on which no value is to be set (in particular there were OneWay binding is desired). Another special value is DependencyProperty.UnsetValue.
This is not a complete solution as now IMultiValueConverter implementation must know where to return a special value. However I think most reasonable cases are covered by this solution.
It looks like MultiBinding doesn't work right. I've seen some unexpected behavior (something like yours) before in my practice. Also you can insert breakpoints or some tracing in converter and you can find some funny things about which converters and when are called.
So, if its possible, you should avoid using MultiBinding. E.g. you can add special property in your view model that will set value of your mutable property in its setter and return needed value using all three your properties in its getter. Its something like a MultiValueConverter inside a property =).
Hope it helps.

Enable data binding in shared WPF resources inside a ResourceDictionary

I'm using the M-V-VM pattern in WPF and I have a background brush I'm going to be using rather often and I'd like to move it out in to a shared ResourceDictionary.
The only problem is the brush uses a color which it gets via Databinding to its hosted context.
Is there anyway I can move the brush out in to a ResourceDictionary and still have it find the value it needs?
The Brush:
<RadialGradientBrush>
<RadialGradientBrush.RelativeTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5"
CenterY="0.5"
ScaleX="2.3"
ScaleY="2.3" />
<TranslateTransform X="-0.3"
Y="-0.3" />
</TransformGroup>
</RadialGradientBrush.RelativeTransform>
<GradientStop Color="{Binding Path=BackdropColor}"
Offset="1.2" />
<GradientStop Color="#FFFFFFFF"
Offset="-0.1" />
</RadialGradientBrush>
After re-factoring it out to a ResourceDictionary and adding a key, I called it as such:
<StackPanel Grid.Row="0"
Margin="0,0,0,0"
Orientation="Horizontal"
Background="{DynamicResource BackdropRadGradBrush}">
But this resulted in this output in the debugger:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=BackdropColor; DataItem=null; target element is 'GradientStop' (HashCode=16001149); target property is 'Color' (type 'Color')
I don't think that you can keep this Brush in your resource dictionary and use binding to pull in the color. Since the brush is only created once (which is why you want it in the resource dictionary in the first place), at the time of creation WPF doesn't know where it will be used, so it can't pull in the value for the color.
If the color were kept in Setings, for example, that would probably work - but I'm guessing that won't help you, because you probably want the color to change on each control that it is used on (otherwise, you could just hard code the color or it would already be in settings).
Maybe you could create a RadialGradientBrush subclass, and expose the first GradientStop color as a DependencyProperty? You could then create an instance of this subclass wherever you need it, and use binding to pull in the correct color there.
This is a little late, but take a look at using a StaticResource or a DynamicResource instead of a Binding - it will allow you to access another Resource. Not quite Binding, but it's better than nothing.

Resources