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}" />
Related
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.
I have a following grid:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
...
<ContentPresenter Grid.Row="1" Content="{Binding Path=PredictiveWorkspace}"
Visibility="{Binding Path=ShowPredictiveWorkspace,
Converter={StaticResource boolToVisibility}}"/>
<ContentPresenter Grid.Row="1" Content="{Binding Path=M2Workspace}"
Visibility="{Binding Path=ShowStandardWorkspace,
Converter={StaticResource boolToVisibility}}"/>
...
</Grid>
Those two ContentPresenters has the same Grid.Row definded because only one of them should be visible at once.
I have following boolToVisibility converter:
[ValueConversion(typeof(bool), typeof(System.Windows.Visibility))]
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
{
return System.Windows.Visibility.Visible;
}
else
return System.Windows.Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
And there's the problem: both ContentPresenters are visible! I noticed also that only ShowPredictiveWorkspace property is being read by a app. Breakpoint set on ShowStandardWorkspace getter is never called.
I guess it some stupid mistake but I really can't find it.
EDIT:
public bool ShowStandardWorkspace
{
get { return this._showStandardWorkspace; }
set
{
this._showStandardWorkspace = value;
this.OnPropertyChanged(() => this.ShowStandardWorkspace);
}
}
This is because it does not work to bind visibility with a converter on the ContentPresenter element.
If you change the ContentPresenter to a ContentControl it will work to bind the visibility property with a converter, and then you don't have to nest it within another element.
This is apparently because ContentPresenter is a light weight element that is meant to be used within a ControlTemplate.
From MSDN (with my highlighting):
You typically use the ContentPresenter in the ControlTemplate of a
ContentControl to specify where the content is to be added. Every
ContentControl type has a ContentPresenter in its default
ControlTemplate.
When a ContentPresenter object is in a
ControlTemplate of a ContentControl, the Content, ContentTemplate, and
ContentTemplateSelector properties get their values from the
properties of the same names of the ContentControl. You can have the
ContentPresenter property get the values of these properties from
other properties of the templated parent by setting the ContentSource
property or binding to them.
I have been searching a lot plus I made some tests and I'm pretty sure you cannot control the visibility of a contentpresenter. Plus - if the ViewModel that is going to be presented by the ContentPresenter is null when view is being showed - it does not even read the property using by boolToVisibilityConverter.
I made a simple workaround - I put ContentPresenter inside a Grid (you can use other type of container obviously) and bound Visibility of the Grid to the boolean properties. It works perfectly.
You should use the AncestorType. The DataContext isn't the same, when you use the ContentPresenter, but you can navigate up in the Visual Tree to find it. In Your case:
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type Grid}}, Path=ShowStandardWorkspace}"
Where Grid is the first ancestor by default, and its DataContext is used. If you need a second, third etc. ancestor, use the AncestorLevel property with an int value.
The converter is fine, I think.
possible sources of error:
spelling error ShowStandardWorkspace
OnPropertyChanged("ShowStandardWorkspace") not raised in property setter
ShowStandardWorkspace property simply not set to false
maybe wrong DataContext for the 2nd ContentPresenter
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?
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*/
}
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;
}
...
}