INotifyPropertyChange does not update converter-based values? - wpf

I have an image with source set by a ValueConverter:
<Image Source="{Binding Converter={StaticResource siConv}}" Width="16" Height="16"/>
When the bound object raises a PropertyChanged event (from INotifyPropertyChanged), the image does not change. I assume it is because WPF doesn't know what fields the converter looks at.
How do I tell WPF to rebind/rerun this converter when a certain property changes?

The answer here was to use data triggers, not value converters. The trigger is bound directly to the property, and picks up the propertychanged event. Also, saves writing a bunch of one-time value converters.

And if you really intent on writing a ValueConverter, may be use a MultiValueConverter with all the values that you want the WPF framework to monitor. Any changes to those values, and the framework would update the value it is binded to.

the Path is important:
<Image Source="{Binding Converter={StaticResource siConv}, ConverterParameter=yourproperty, Path=yourproperty, UpdateSourceTrigger=PropertyChanged}" Width="16" Height="16"/>

Related

XAML Binding to parent of data object

I have a grid column defined. The parent grid gets its items from an ObservableCollection of type ItemClass. ItemClass has two properties: String Foo, and bool IsEditAllowed.
This column is bound to property Foo. There's a control template for editing the cell. I'd like to bind the ItemClass.IsEditAllowed property to the IsEnabled property of the TextBox in the template.
The question is how to bind it. Can this be done? The XAML below gets me "Cannot find source for binding with reference" in the debug trace.
The grid will let me bind the ItemClass itself to the field via some "custom" event thingy, and I can then bind to any of its properties. That's fine, but it seems kludgy. But if it's the only way, it's the only way.
<dxg:GridColumn
Header="Foo Column"
FieldName="Foo">
<dxg:GridColumn.EditTemplate>
<ControlTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ItemClass}, AncestorLevel=1}}" />
</ControlTemplate>
</dxg:GridColumn.EditTemplate>
</dxg:GridColumn>
There are two potentially easier ways to set up this binding.
Name the grid. Then your binding could look something like this (assuming dxg:GridControl has a property named "Items" and that you have assigned an instance of your ItemClass to that property):
<TextBox IsEnabled="{Binding Path=Items.IsEditAllowed, ElementName=MyGridControl} />
Use relative binding, but look for the GridControl rather than something nominally internal to the way GridControl works (that is, GridControlContentPresenter). This gets you away from the implementation details of GridControl, which are perhaps more likely to change in ways that break your application than are properties on GridControl itself.
<TextBox IsEnabled="{Binding Path=Items.IsEditAllowed, RelativeSource={RelativeSource AncestorType={x:Type dxg:GridControl}}}" />
You may also want to read up on the Visual Tree and the Logical Tree in WPF/xaml. The "Ancestor" in relative bindings refers to ancestors in the visual tree, that is, things like parent containers, and not to super- or base classes (as you've discovered, I think).
Here's the answer[1]. FindAncestor finds ancestors in the runtime XAML tree, not in arbitrary C# objects. It cannot walk up to the ItemClass instance from the member we're bound to. But we do know that somebody above us in the XAML tree bound us to that member, and he was bound to the ItemClass instance itself. So whoever that is, we find him, and then we've got the ItemClass.
So let's add debug tracing to the binding, and we'll see what the XAML situation looks like at runtime. No doubt there are other and probably smarter ways to do that, but I happen to know this one without any research.
First add this to the namespaces at the top of the XAML file:
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
...and then to the binding itself, add this:
diag:PresentationTraceSources.TraceLevel=High
Like so:
<TextBox Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ItemClass}, AncestorLevel=1}, diag:PresentationTraceSources.TraceLevel=High}"
/>
At runtime, when the TextEdit's IsEnabled property tries to get a value from the binding, the binding walks up through the XAML tree looking for an ancestor of the specified type. It keeps looking until it finds one or runs out of tree, and if we put tracing on it, it traces the type of everything it finds the whole way up. We've told it to look for garbage that it'll never find, so it will give us a trace of the type of every ancestor back to the root of the tree, leaf first and root last. I get 75 lines of ancestors in this case.
I did that, and found a few likely candidates. I checked each one, and the winner turned out to be dgx:GridCellContentPresenter, which has a RowData property. RowData has a lot of properties, and RowData.Row is the row's instance of ItemClass. dxg:GridCellContentPresenter belongs to the DevExpress grid library we're using; in another vendor's grid class, there would presumably be some equivalent.
Here's the working binding:
<TextBox Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=RowData.Row.IsEditAllowed, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dxg:GridCellContentPresenter}, AncestorLevel=1}}"
/>
If DevExpress, the vendor, rewrites their GridControl class, we'll be in trouble. But that was true anyhow.
...
[1] Better answer, though it's too DevExpress specific to be of any real interest: The DataContext of the TextBox itself turns out to be dxg:EditGridCellData, which has a RowData property just like GridCellContentPresenter does. I can just use IsEnabled="{Binding Path=RowData.Row.IsEditAllowed}".
However, what I really wanted to do all along was not to present a grid full of stupid disabled textboxes, but rather to enable editing on certain rows in the grid. And the DevExpress grid lets you do that through the ShowingEditor event.
XAML:
<dxg:GridControl Name="grdItems">
<dxg:GridControl.View>
<dxg:TableView
NavigationStyle="Cell"
AllowEditing="True"
ShowingEditor="grdItems_TableView_ShowingEditor"
/>
</dxg:GridControl.View>
<!-- ... Much XAML ... -->
</dxg:GridControl Name="grdItems">
.cs:
private void grdItems_TableView_ShowingEditor(object sender, ShowingEditorEventArgs e)
{
e.Cancel = !(e.Row as ItemClass).IsEditAllowed;
}

Binding a DataPager to ComboBox?

I have a comboxbox defined like this (basically):
<ComboBox x:Name="pageViewSize">
<ComboBox.Items>
<ComboBoxItem IsSelected="True">5</ComboBoxItem>
<ComboBoxItem>10</ComboBoxItem>
<ComboBoxItem>20</ComboBoxItem>
<ComboBoxItem>30</ComboBoxItem>
<ComboBoxItem>50</ComboBoxItem>
<ComboBoxItem>100</ComboBoxItem>
</ComboBox.Items>
</ComboBox>
Now i would like my DataPager's PageSize (which is the source to a DataGrid) be bound to this ComboBox's SelectedItem.Value (or is it SelectedValue?):
<DataPager PageSize="{Binding Path=SelectedItem.Value, ElementName=pageViewSize}" Source="{Binding PageView}"/>
This, unfortunately, is not working. The initial pagesize is not 10. And whenever i changed the selection in the ComboBox nothing happens to the displayed pagesize in the DataGrid.
What am i doing wrong?
Thanks
From DataPager.PageSize documentation:
The source typically implements the IPagedCollectionView interface. In this case, PageSize gets or sets the IPagedCollectionView.PageSize of the IPagedCollectionView.
If the source is a collection that implements IEnumerablebut not IPagedCollectionView, the DataPager ignores PageSize.
Maybe your data source doesn't properly support PageSize?
EDIT: I currently have the same issue as you I had the same issue as you, it was fixed by using #devdigital's answer.
I'm using data binding instead of element binding, on radio buttons + custom converter instead of combo, but it applies in the same way.
What I'm doing is data binding IsChecked to a value in my View Model, with a custom two-way converter checking whether value is equal to converter's parameter.
So here is an example from one of my RadioButtons:
IsChecked="{Binding MyBindedValue, Converter={StaticResource EqualStringConverter}, ConverterParameter=5, Mode=TwoWay}"
And your DataPager, modified:
<DataPager PageSize="{Binding MyBindedValue, Mode=TwoWay}" Source="{Binding PageView}"/>
Try setting the Mode to TwoWay.
PageSize="{Binding Path=SelectedItem.Value, Mode=TwoWay, ElementName=pageViewSize}"

Xaml binding for radio/checkbox to toggle between imperial/metric

Pretty much as the title states, I'm grabbing some values from a Db, which are all in Km, but I want to implement a converter which I can toggle between Miles or Kilometers, and want to bind which is displayed to either a checkbox, or a radio button group, whichever is easiest (Radio would be preferred).
I'm thinking I can just use an IValueConverter rather than an IMultiValueConverter, and the Convert/ConvertBack methods, as the default will be Kilometers, but I don't know how to call the ConvertBack method. Or I could pass true/false as the ConverterParameter depending on whether I want Km/Miles displayed.
But either way I'm not sure how to hook up the Xaml Binding on either method (I know how to do a standard value converter binding, but not the extra flumff needed.
Any hints appreciated.
<StackPanel Grid.Row="0" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<RadioButton Content="Km" GroupName="rdBtnGrpValue" IsChecked="True" />
<RadioButton Content="Miles" GroupName="rdBtnGrpValue" />
</StackPanel>
And:
<TextBox HorizontalAlignment="Stretch" VerticalAlignment="Top" Grid.Column="1" Text="{Binding EquatorialCircumference, Converter={StaticResource KmMiConv}, StringFormat='{}{0:0,0.0}'}" />
If you are using the MVVM pattern, and are using a view-model as your DataContext, you could use a Mode=TwoWay binding between the RadioButtons and a boolean property in the view-model, something like bool ConvertToImperial { get; set; }.
Your actual conversion can occur in the getter for the EquatorialCircumference property. If ConvertToImperial is true, return the value in miles, otherwise return the value in kilometres.
Then, for the TextBox, you can simply bind it to the EquatorialCircumference property, and the value displayed will be in the selected unit.
You will, however, need to raise a property change notification for any properties whose values are affected by a change in units.

Databinding Not Updating When Using {Binding .} or {Binding}

I have an ObservableCollection of addresses that I am binding to a ListBox. Then in the ItemTemplate I am Binding to the current address record using {Binding .}. This results in my addresses displaying using their ToString method which I have setup to format the address. All is good, except if I update properties on an individual address record the list in the UI does not update. Adds/Deletes to the list do update the UI (using the ObservableCollection behavior). If I bind directly to properties on the address the UI does update (using the INotifyPropertyChanged behavior of the Address object).
My question is, is there a way to notify the UI of the change to the object as a whole so that I can still use this syntax or do I need to punt and put a DisplayText property on my address type that calls the ToString method and bind to that? FYI, this is an MVVM architecture so I don't have the luxury of calling Refresh on the ListBox directly.
Thanks for any help/ideas.
<ListBox x:Name="AddressList" ItemsSource="{Binding Addresses}" Background="Transparent" BorderBrush="Transparent"
Width="200" HorizontalAlignment="Left">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding .}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When you bind to the Address object itself, the object itself -- that is, its identity -- doesn't change, even though its properties do. WPF therefore doesn't know to refresh the binding in this case.
So yes, you need to bind to a notifying property (or properties) rather than the whole object. As you say, one way to do this is to create a DisplayText property, and raise the PropertyChanged event for that property whenever something that affects the display text changes. Another is to use multiple TextBlocks in a horizontally oriented StackPanel, each bound to a particular property e.g.
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding HouseNumber}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding Street}" />
<TextBlock Text=", " />
<TextBlock Text="{Binding City}" />
</StackPanel>
The advantage of the second approach is that it gives you flexibility in the UI to change how addresses are displayed, e.g. multiple lines, formatting, etc.; the downside is that it gets complicated if you have conditional logic e.g. an optional flat number or second address line.
I tried to reproduce the problem and succeeded.
I activated the step-into-.NET debugging options, and saw that WPF does not listen to INotifyPropertyChanged if the path in the binding is empty.
What worked to get a change to be reflected in the list box is to replace the whole object in the ObservableCollection. This triggers the INotifyCollectionChanged, with the Replace action.
But this may not be acceptable in your case. And it could be seen more like a hack than a solid solution.
I'd seriously consider having a DataTemplate for Address. There you should bind to the exact properties you need (which would create the listener for INotifyPropertyChanged). It is more flexible than ToString() and you may encounter cases where you have a need for ToString() to do something for non-UI stuff, which would create a conflict. And honestly, ToString is not really meant for UI stuff.

WPF databinding and converters

I'm trying to databind to a listbox like so:
<ListBox x:Name="MyListBox" Margin="0,0,0,65">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MyConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The reason I am binding to the whole object and not a property is because my converter will need multiple properties of the object to build the string that it returns.
This works and my string is returned. But then when I change the ObservableCollection that this is based on the value doesn't change on the screen. If I bind to just a single property and change it, then the value does change.
What can I do differently? I can't bind to a single property since I need the entire object in the converter... And the ConverterParameter is already being used.
Remember, if you bind to the "main" property and the value of the main property itself isn't changed, the binding will have no reason to refresh itself. It has no clue that your converter is actually based off of a sub-property. What you can do is use a MultiBinding where you bind not only the "main" property, but also a specific sub-property. This gives your IMultiValueConverter implementation access to the main data object, but because you're also binding to the sub-property that's changing, will also be refreshed when that sub-property's value changes.
You can try using a MultiBinding which I believe updates whenever any of its Bindings are triggered. You can also use an IMultiValueConverter or just take advantage of the StringFormat of the binding.

Resources