Why is WPF/XAML Binding using x:Reference temperamental? - wpf

I have a WPF/XAML DataGrid where one column contains images. I want the width of that column (and thus the size of the images) to be controlled by a Slider that is not in the DataGrid. (I am aware this poses a problem in itself, but I found a good solution here.) The trouble is, the binding between the slider Value and the column Width seems very fragile. If the user resizes the parent window, causing the DataGrid to resize, the binding breaks. If I set VerticalScrollBarVisibility="Auto" in the DataGrid, and then the user adjusts the slider so that the DataGrid's scrollbar needs to appear, then the binding breaks. Once the binding is broken, it doesn't come back unless the user closes the window and reopens it.
Here is the relevant DataGrid column definition:
<DataGridTemplateColumn Width="{Binding Source={x:Reference PictureSizeSlider}, Path=Value}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Name="ImageBox" Stretch="Uniform" Source="{Binding Image}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And here is the Slider definition (inside a different Grid):
<Slider Grid.Column="1" Name="PictureSizeSlider" Minimum="10" Maximum="255" Value="100" IsMoveToPointEnabled="True"/>
Can anybody advise as to why the binding is so fragile, and how I can make it robust?

Use a Binding Converter that converts between DataGridLength and double:
public class DataGridLengthConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var length = (DataGridLength)value;
return length.UnitType == DataGridLengthUnitType.Pixel ? length.Value : 0d;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
return new DataGridLength((double)value);
}
}
Apply it to a Binding of the Value property of the Slider:
<DataGridTemplateColumn x:Name="imageColumn" Width="100">
...
</DataGridTemplateColumn>
<Slider Value="{Binding Width,
ElementName=imageColumn,
Converter={StaticResource DataGridLengthConverter}}" .../>
In contrast to your original Binding, this one is TwoWay and hence works in both directions, i.e. when you manipulate the Slider or the DataGrid column.
A OneWay Binding is replaced when the bound property is set by a source other than the Binding, e.g. a directly set property value.

Related

ComboBox binding works only one way with Color property

I'm implementing an ObservableCollection <-> DataGrid binding.
My class consists of a System.Windows.Media.Color field (named 'Color') and several strings.
My implementation in WPF of the Color column:
<DataGridTemplateColumn Header="Color" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={StaticResource ColorProperties}}"
SelectedItem="{Binding Path=Color, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Style="{StaticResource ComboBoxFlatStyle}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="1" Orientation="Horizontal">
<Rectangle Fill="{Binding}" Height="10" Width="10" Margin="2"/>
<TextBlock Text="{Binding}" Margin="2,0,0,0"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And ColorProperties:
<ObjectDataProvider x:Key="ColorProperties" ObjectType="{x:Type color:ColorHelper}"
MethodName="GetColorNames"/>
The ColorHelper class (I'll try to find the OP for credit):
public static class ColorHelper
{
public static IEnumerable<string> GetColorNames()
{
foreach (PropertyInfo p
in typeof(Colors).GetProperties(
BindingFlags.Public | BindingFlags.Static))
{
yield return p.Name;
}
}
}
This works just fine, I can see the combo-box with all the colors:
Now I added a few objects to the ObservableCollection, and their respective fields are populated in the DataGrid. All except the Color.
The default Color is Black, but when I run the application, the ComboBox stays empty.
When I select a color from the ComboBox, the ObservableCollection changes accordingly. However if I change the object itself, the ComboBox stays with its original value.
For troubleshooting, I added
<DataGridTextColumn Header="Color" Binding="{Binding Color, UpdateSourceTrigger=LostFocus}"
Width="100"/>
This column gets populated with the string representation of the color, and when I change the value from either side, it changes accordingly.
This fact suggests that there's something missing in the ComboBox implementation.
Am I missing some sort of translation method? Seems weird since it actually works in one way (from ComboBox to object).
I've searched everywhere for a solution, without success.
EDIT:
I implemented Color<->String converter, still doesn't work..
public class StringToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Color content = (Color)value;
return content.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string content = (string)value;
return (Color)ColorConverter.ConvertFromString(content);
}
}
WPF change:
<ComboBox ItemsSource="{Binding Source={StaticResource ColorProperties}}"
SelectedItem="{Binding Path=Color, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay,
Converter={StaticResource StringToColorConverter}}"
Style="{StaticResource ComboBoxFlatStyle}">
Any ideas?
Thanks!
The list bound to Combobox is a list of strings, whereas the actual object is not just a string. you need a converter that converts from string to your desired object.
Rest all seems fine, system dosent know how does string Black translates to Color Black
-- one way binding works because, color object knows how to get color from a string, that is why when we set Background="String" it get the corresponding object using FromName
OK i found out what was wrong. It looks like getting the actual name from a color is not that simple. ToString returns the hex string, and not the actual color name.
So I used the answer from http://www.blogs.intuidev.com/post/2010/02/05/ColorHelper.aspx to create the converter.
public class ColorToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Color KnownColor;
Color content = (Color)value;
Type ColorType = typeof(System.Windows.Media.Colors);
PropertyInfo[] ColorsCollection = ColorType.GetProperties(BindingFlags.Public | BindingFlags.Static);
foreach (PropertyInfo pi in ColorsCollection)
{
KnownColor = (Color)pi.GetValue(null);
if (KnownColor == content)
return pi.Name;
}
return String.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string content = (string)value;
return (Color)ColorConverter.ConvertFromString(content);
}
}

Binding templated item to Converter's Value property in DataTemplate

I'm using Silverlight 4.
I have a DataTemplate defined for a DataGrid which allows me to successfully display values to my liking. I have a Rating control inside of this DataTemplate that has a Converter on the Value property like so..
<DataTemplate>
<toolkit:Rating Value="{Binding Converter={StaticResource MyConverter}" ItemCount="5" />
</DataTemplate>
When I step through the code and get into the converter, I see that the value parameter isn't the item corresponding to the row being rendered by the template but my ViewModel that is the DataContext of the DataGrid itself!
Now, if I adjust this slightly like so,
<DataTemplate>
<toolkit:Rating Value="{Binding SomeProperty Converter={StaticResource MyConverter}" ItemCount="5" />
</DataTemplate>
The value passed to MyConverter is SomeProperty of the item rendered by the DataTemplate.
Does anyone know why this might be? How can I bind to the item the template refers to instead of the DataContext of the DataGrid?
Try "{Binding ., Converter={StaticResource MyConverter}"
I figured this out.
During the MeasureOverride stage of Silverlight's DataGrid, my converter is being invoked. It feels like a bug in the DataGrid's implementation of MeasureOverride to ignore the
<DataGrid ItemsSource="{Binding MySource}"></DataGrid>
binding expression with respect to a defined DataTemplate at this stage and use the DataContext of the DataGrid which will certainly cause a typical Converter to fail.
My band-aid solution for now is to add an if statement in my converter implementation to just make sure the type of value I get is what I expect so it passes MeasureOverride.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ExpectedType)
//do things
else
//return an instance of targetType
}
Can anyone confirm if this still happens in SL5?

How Binding Converter to a control's property can access other properties of the control

I am trying to bind a value "MaxLines" to the TextBlock's Height property in WP7 app. There is a converter to the binding which is supposed to multiple the LineHeight with the MaxLines and return the expected height. What I am trying to say is I want to control the number of lines being shown in the TextBlock. How will I be able to access the TextBlock's LineHeight property from the converter.
To make this generic I did not want maintain the LineHeights separately or access them from viewModel
Check out this article, Silverlight data binding and value converters, where he explains how to Databind in Silverlight. In the example he uses a ValueConverter with parametervalue.
I think that is what you need, just bind your LineHeight to the parameter. (You can use Blend for that)
You can use the ConverterParameter:
<TextBlock x:Name="MyTextBlock" Height="{Binding ConverterParameter=Height, ElementName=MyTextBlock, Converter={StaticResource SomeConverter}}" Text="{Binding SomeLongText}" />
or pass the whole textblock:
<TextBlock x:Name="MyTextBlock" Height="{Binding Converter={StaticResource ImageFileConverter}, ElementName=DropdownImage}" Text="{Binding SomeLongText}" />
Then inside the controller:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var image = value as TextBlock;
/*do your magic here*/
}

Bind to parent object in xaml

I have a hierarcial class like this
Part
SubPart
SubSubPart1
SubSubPart2
I have a control that is populated by SubSubPart and in that control i want to show information about the parent SubPart and Part. I want to use normal binding in xaml to display information about parent part.
Each part has a unique ObjectId as a property, each part has multiple properties that i want to display.
The control only knows about one subsubpart.
I realize that i can write a converter
public object Convert(object value, System.Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{ return "Design Part"; }
else
{
IDataService applicationService = ServiceLocator.Current.GetInstance<IDataService>();
IPartItem partItem = applicationService.GetEquipmentFromComponent(value.ToString());
return partItem.PartData.Name;
}
}
and apply it like this
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding Path=ObjectId,Converter={StaticResource partConverter}}" Margin="0,0,10,0">
</TextBlock>
But then i need to write a converter for every property of the parent parts. Any solutions out there.
You could do what you're looking for by using the FindAncestor mode of a RelativeSource binding.
For example the text property of a TextBlock would be the following:
Text="{Binding Path=ObjectId, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:SubPart}, AncestorLevel=1}
where local would be declared to be the namespace where the class SubPart is declared.
You can follow the same pattern for the Part class, changing the AncestorType and AncestorLevel attributes as needed.
Bind the DataContext of your control using the converter and update your converter to just return the parent part
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding ObjectId}" DataContext="{Binding Converter={StaticResource partConverter}}" Margin="0,0,10,0" />

WPF: multiple controls binding to same property

Hello
I'm trying to change several controls' property according to some environment variables and i want to avoid creating a property for each control in the datacontext, so i thought using a converter which sets the property according to control name. Goal is to use one property for all controls:
<Grid.Resources>
<local:NameToStringConverter x:Key="conv" />
</Grid.Resources>
<TextBlock Name="FordPerfect"
Text="{Binding ElementName="FordPerfect" Path=Name, Converter={StaticResource conv}, Mode=OneWay}"/>
<TextBlock Name="Arthur"
Text="{Binding ElementName="Arthur" Path=Name, Converter={StaticResource conv}, Mode=OneWay}"/>
<TextBlock Name="ZaphodBeeblebrox"
Text="{Binding ElementName="ZaphodBeeblebrox" Path=Name, Converter={StaticResource conv}, Mode=OneWay}"/>
and ...
public class NameToStringConverter : IValueConverter
{
public object Convert(
object value, Type targetType,
object parameter, CultureInfo culture)
{
if (MyGlobalEnv.IsFlavor1 && ((string)value).Equals("ZaphodBeeblebrox")) return "42"
if (MyGlobalEnv.IsFlavor2 && ((string)value).Equals("ZaphodBeeblebrox")) return "43"
if (MyGlobalEnv.IsFlavor1 && ((string)value).Equals("Arthur")) return "44"
return "?";
}
public object ConvertBack(
object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException("Cannot convert back");
}
}
I'm sure there's a better and more elegant way... Any ideas?
The point of oneway databinding is just to decouple UI (XAML) from code (CS). Here, your code and UI are tied so tightly together that trying to do this through databinding is really not buying you anything. You might simplify things by writing a method that takes the data value and applies it correctly to each control - still tightly coupled (bad) but at least the code is condensed and easy to follow (less bad).
What you should probably do though is not rely on the control name but define a ConverterParameter. See the bottom 1/3 of this article http://www.switchonthecode.com/tutorials/wpf-tutorial-binding-converters
You may bind directly to environment variable in your situation :
<Window xmlns:system="clr-namespace:System;assembly=mscorlib" ...>
<TextBlock Text="{Binding Source={x:Static system:Environment.OSVersion}}"/>

Resources