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

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.

Related

How to bind a child user control's data context in the parent

<TextBlock Text="{Binding ChildGroupName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, UpdateSourceTrigger=PropertyChanged,NotifyOnTargetUpdated=True,Mode=TwoWay}"
TargetUpdated="OnTextUpdated"/>
Here ChildGroupName is a child control datacontext property. I want to bind ChildGroupName values to parent window.
You cannot use FindAncestor to data bind to a descendant's data... the clue is in its name. If the child UserControl is defined in the same XAML as the parent, then you can provide it with a name and then use the Binding.ElementName Property to data bind to its properties:
<TextBlock Text="{Binding ChildPropertyName, ElementName=NameOfChildUserControl,
UpdateSourceTrigger=PropertyChanged, NotifyOnTargetUpdated=True, Mode=TwoWay}"
TargetUpdated="OnTextUpdated" />
Sheridan's answer did not work for me, because ReSharper was issuing a warning saying that "ChildPropertyName" is an unknown property.
Now, I did not actually try Sheridan's solution; it may be that it would have worked; it may be that WPF does smart tricks under the hood and manages to get things to work even with Sheridan's approach; however, for me, all warnings must always be enabled, and all code must be absolutely free from warnings, so I had to look for a solution that would not only work, but also work without eliciting a warning from ReSharper.
What worked for me was adding DataContext., as follows (without the extra clutter):
<TextBlock Text="{Binding DataContext.ChildPropertyName,
ElementName=NameOfChildUserControl}" />
In other words, when you use ElementName, the DataContext becomes the element itself, (which makes sense,) so in order to get to the actual viewmodel you need to first reference the DataContext of the element.

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;
}

Are "{Binding Path=.}" and "{Binding}" really equal

In my WPF project, I have a ListBox that displays items from a List<string> collection. I wanted to make the text of these items editable, so I wrapped each of them in an ItemTemplate with a TextBox (might not be the best way, but I'm new to WPF). I was having trouble simply binding the TextBoxes' Text property to the value of each item. I finally stumbled upon an example using a single dot or period for its Path property ({Binding Path=.}):
<ListBox ItemsSource="{Binding ElementName=recipesListbox,Path=SelectedItem.Steps}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
However I don't understand why simply using {Binding} didn't work.
It raised a "Two-way binding requires Path or XPath" exception, as according to Microsoft:
[...] a period (.) path can be used to bind to the current source. For example, Text="{Binding}" is equivalent to Text="{Binding Path=.}"
Could someone shed light on this ambiguous behavior?
EDIT: Moreover, it seems {Binding Path=.} does not necessarily give two-way binding, as modifying the text and moving the focus does not update the underlying source (the same source has also properties displayed and successfully modified on a DataGrid control). I'm definitely missing something here.
The point of the exception presumably is that you cannot two-way bind a binding-source itself, so it tries to prevent you from creating a binding which does not behave the way you would want it to. By using {Binding Path=.} you just trick the error detection.
(Also it's not unheard of that documentation is erroneous or inaccurate, though i do like the MSDN documentation a lot in general as it usually does contain the crucial points one is interested in)
The documentation states that {Binding} is equivalent to {Binding Path=.}. However it is not equivalent to {Binding Path} as you have typed. If you include the Path property, you must assign it to something, be it Path=. or Path=OtherProperty.
These are not the same. If you bind this where ConsoleMessages is an ObservableCollection string with just {Binding} you get a "Two-way binding requires Path or XPath." exception where as {Binding Path=.} works. This is with WPF 4.0...
<ItemsControl x:Name="ConsoleOutput" ItemsSource="{Binding ConsoleMessages, Mode=OneWay}" MaxHeight="400">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.}" BorderThickness="0" Margin="0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My 2p worth...
In short, the difference between the two is analogous with the difference between the traditional pass by value and pass by reference. (FYR - What's the difference between passing by reference vs. passing by value?)
However I don't understand why simply using {Binding} didn't work (it raised a "Two-way binding requires Path or XPath" exception)
Lets assume here for now that {Binding} can be used for two way binding. In general {Binding} creates a value based link with datacontext which does not allow updating the datacontext.
Whereas {Binding Path=.} creates reference based link with the memory area referenced by the 'Path' which allows updating the value through reference.(in this case 'dot' the current datacontext).
Hope this helps!

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.

Change when a RIA Services DataForm sends changes to the backing object

DataForms seem to update their CurrentItem as soon as the user tabs out of a field. This happens even when AutoCommit = false. The side effect of that behavior is that other controls that are bound to the data update while the user edits data instead of when the user clicks Ok to accept the DataForm changes. Is there a way to modify that behavior to postpone writing data to CurrentItem to when the user accepts the changes?
EDIT: Here's most of the Xaml for a DataForm I'm using:
xmlns:DataFormControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
<DataFormControls:DataForm x:Name="dataForm" AutoCommit="False" AutoEdit="False">
<DataFormControls:DataForm.EditTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<DataFormControls:DataField Label="Title">
<TextBox Text="{Binding Title, Mode=TwoWay}" Style="{StaticResource TextBoxStyle}"/>
</DataFormControls:DataField>
<DataFormControls:DataField Label="First Name">
<TextBox Text="{Binding FirstName, Mode=TwoWay}"/>
</DataFormControls:DataField>
<DataFormControls:DataField Label="Middle Name">
<TextBox Text="{Binding MiddleName, Mode=TwoWay}"/>
</DataFormControls:DataField>
<DataFormControls:DataField Label="Last Name">
<TextBox Text="{Binding LastName, Mode=TwoWay}"/>
</DataFormControls:DataField>
</StackPanel>
</DataTemplate>
</DataFormControls:DataForm.EditTemplate>
</DataFormControls:DataForm>
EDIT 2: The workaround I'm using to avoid this behavior is to make a copy of the object to be edited and set it to the DataForm.CurrentItem property, then if the user accepts the edit the data is copied back to the original object. I'm hoping there's a better solution out there.
Everything is bound to the same instance of the entity. Since the DataForm fields are bound to properties on the entity, when you leave a field, it calls the property setter, changing the value and raising INotifyPropertyChanged.PropertyChanged events. This then informs other bound controls that the value has changed and their bindings are updated.
If you really want to prevent this, you would need to do some hefty work to clone the entity that the DataForm is bound to, and then when the item is committed, update the original with the clone's values. This would not be recommended.
Alternatively, you could have multiple instances of the DomainContext and load the entities twice--once for the DataForm and once for the other displays. After submitting changes on one, you could re-load the entities for the other. This will likely lead to other problems in your application though, so it's also not recommended.
I am curious why the live binding is causing a problem.

Resources