Binding templated item to Converter's Value property in DataTemplate - silverlight

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?

Related

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

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.

ObservableCollection Remove() not firing to Visibility binding

I have a strange issue with my WPF project. I have a ObservableCollection<T> bound to a ListBox. When I add and remove items, the binding works and the list displays the correct results.
The issue I have, is I'm also binding this same property to another XAML control, but it doesn't trigger the converter when I remove an item from the list. It works when I add items.
The relevant XAML is
<view:WelcomeView Visibility="{Binding Steps, Converter={StaticResource CollapseIfZero}}"/>
<ListBox ItemsSource="{Binding Steps}" />
And the converter is
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var col = value as ICollection;
return col.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
}
I have a break point in the converter. When a new item is added, the break point is hit. When an existing item is removed, the break point is not hit.
Does WPF do something magical with the ListBox which I'm not aware of (which has led to this unexpected behavior)?
ObservableCollection implements INotifyCollectionChanged and ListBox (and other ItemsControls) listens when collection was modified.
Steps property itself doesn't change, it is the same ObservableCollection.
WelcomeView.Visibility is bound to Steps, and doesn't update because property value didn't change, it keeps the same object reference.
try create binding to Steps.Count property (converter should be modified to use int value)
<view:WelcomeView Visibility="{Binding Steps.Count, Converter={StaticResource CollapseIfZeroCount}}"/>
or
there is bool HasItems property in ItemsControl. I would make a binding with ElementName and BooleanToVisibilityConverter
<view:WelcomeView "{Binding ElementName=Lst, Path=HasItems, Converter={StaticResource Bool2Visibility}}"/>
<ListBox Name="Lst" ItemsSource="{Binding Steps}" />

Binding ContentTemplate

Quite new to WPF and MVVM and I'm trying to bind a ContentTemplate (or ItemTemplate, neither have worked) to a DataTemplate property in a C# WPF program. I'm doing this because I have a config file that defines different "entry display types" for each "entry" in an attempt to not have to make countless views/viewmodels (right now, there is only one generic entry viewmodel that keeps track of the label, data, and display type and I'd prefer to keep it that way to avoid unnecessary bloat of the class structure). Is there any way to make this work?
This is example of one of the things I have tried:
XAML:
<ItemsControl IsTabStop="False" ItemsSource="{Binding Path=FNEntries}"Margin="12,46,12,12">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl ContentTemplate="{Binding Path=TypeView}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
CS (inside the entry view model class constructor which has (DataTemplate)TypeView and (string)PropertyName):
NodeTypeView = (DataTemplate)Application.Current.FindResource("TypeTest");
Resource XAML:
<DataTemplate x:Key="TypeTest">
<TextBlock Margin="2,6">
<TextBlock Text="{Binding Path=PropertyName}" />
</TextBlock>
When I run with that, nothing shows up. However, if I put the contents of the resource data template directly in place of the content control, things show up just fine (except that it isn't data-driven in the way I want). Any help/advice would be greatly appreciated. Thanks!
I'd genuinely say you are mostly doing it wrong =)
Having templates stored in the ViewModel is generally a bad idea, because you would be storing graphical objects in your VM. This should be done ll on the View side
If you want a variable DataTemplate according to the type of your items or whatever, here are a few alternative, "cleaner" solutions:
Prerequisite: All of your Templates are defined as resource somewhere.
Let's say you have a ResourceDictionary somewhere with the following, for test purposes:
<DataTemplate x:Key="Template1" />
<DataTemplate x:Key="Template2" />
<DataTemplate x:Key="Template3" />
Solution 1 : use ItemTemplateSelector
(cleanest solution imho)
For this matter, I'd redirect you to this excellent tutorial which taught me how to use it
If I could understand it, no way you can't =D
Solution 2 : use a converter in your Binding
Let's slightly change your Binding, by making it binding on the current object itself, with a converter
<DataTemplate>
<ContentControl ContentTemplate="{Binding Converter={StaticResource MyConverter}}" />
</DataTemplate>
Here is what your converter would look like (note: the value object here is the bound object, in your case you are working with its type, so this example is about Types as well)
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.GetType() == typeof(WhateverYouWant))
{
return (DataTemplate)Application.Current.FindResource("OneTemplate");
}
else if (value.getType() == typeof(AnotherTypeHere))
{
return (DataTemplate)Application.Current.FindResource("AnotherTemplate");
}
// other cases here...
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value; //We don't care about this!
}
}
This would do the trick for you
I guess both of these solutions would work, and are cleaner, but beware that this is the exact aim of ItemTemplateSelector. The Converter approach is the one I used bfore I knew about those template selectors, I don't use it anymore
Cheers!

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*/
}

WPF Combobox is changing source via SelectedValue when ItemsSource is loaded

I have this combobox in my WPF window.
<ComboBox DisplayMemberPath="Description" SelectedValuePath="ID" ItemsSource="{Binding Source={StaticResource CvsPrinters}}" SelectedValue="{Binding CheckPrinterID}" />
My problem is that when loading the window, the SelectedValue binding is causing my source data to change to the first item in the ItemsSource, instead of setting the Combobox's SelectedValue to the appropriate item in the ItemsSource.
The CheckPrinterID is from a listview selection datacontext, and this problem only occurs to the item initially selected in that listview on load. When I select another item in the listbox, the combobox correctly selects the proper item and all is fine, but unfortunately my initial item has been updated and is now incorrect.
I guess you are trying to synchronize ListView and ComboBox through a common property. Try setting IsSynchronizedWithCurrentItem to True in ListView and make sure SelectedItem or SelectedIndex for ListView is set during load.
Try re-arranging ItemsSource before DisplayMemberPath.
If you have some flexibility in the DataContext object you could try changing the selected CheckPrinter property to be of the data object type instead of the ID and switch to using SelectedItem instead of SelectedValue (for some reason SelectedValue behaves differently, especially at initial load) and then extract the ID from that value in code.
If you can't use the CheckPrinter objects in your DataContext object for whatever reason, you could also go the opposite direction on the UI side by using a list of IDs as the ItemsSource, and again using SelectedItem. To get the list to show what you want in the ComboBoxItems you would then need to use an IValueConverter to pull out Description values based on IDs:
<ComboBox ItemsSource="{Binding Source={StaticResource CvsPrinterIds}}" SelectedItem="{Binding CheckPrinterID}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock >
<TextBlock.Text>
<Binding>
<Binding.Converter>
<local:MyDescriptionLookupConverter Printers="{StaticResource CvsPrinters}"/>
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
and a simple converter to do the ID-Description lookup (add some null and cast checks):
public class MyDescriptionLookupConverter : IValueConverter
{
public IEnumerable<Printer> Printers { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Printers.First(p => p.Id == (int)value).Description;
}
...
}

Resources