Hi,
I think i may have run into a bug, maybe you'll say it's not a bug but it's not how i would have expected the framework to handle this particular case anyway...
....I'm running Vista with the feb CTP
I'm setting the windows datacontext equal to an instance of a class that inherits from DependencyObject (trying to embrace the new way of handling changes in the UI / class). Then i have a listbox with it's itemssource property bound to a DependencyProperty called "Selected". I also then have a grid with it's data context set to the same path=Selected so that it shows the detail of the item selected in the listbox. This all works fine, the controls in that grid are bound to properties of the select item and i have those properties defined as DependencyProperties also. so you can change something and see the result in the list box ... works great, love that!
....getting to the point:
One of the controls however is a combobox with it's itemssource bound to a property of the currently selected item (the items in the combo are specific to the item in the master list, some items from the master list will have an empty combobox too) and the problem is due to the rebind of the list i think guess the combo fires a change event to the DependencyProperty with a value of null in between switching from one item in the master list to another. Example: I select a value in the combo, it updates fine then i change to another item in the master list and it changes the property back to null then moves over to the new item... i've had to work around the problem by adding a change listner to the property and reset the property in the event that the NewValue passed in is null, like this...
public static void LevelAsBenefitLevel_Change(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null)
SetValue(LevelAsBenefitLevel, _UserBenefit.LevelAsBenefitLevel);
else
((BenefitSelectionUserBenefit)d).SetLevelAsBenefitLevel((BenefitLevel)e.NewValue);
}
** ultimately i'd like it not to fire the change with a null value but also is there a way to just cancel the change
*** plus is there a way to have a non-static change handler I'd like to not have to write this extra method if possible, you can see i just end up calling an instance method to make the change.
I thought another option for cancelling the change would be to put in a ValidateValueCallback like this one...
public static bool LevelAsBenefitLevel_Validate(object value)
{
return (value != null);
}
but in the case where there are no items in the list i need the value to be null and in fact that's what it will be initialised as - so the validation fails immediately.
** another side note, it would be good to have a reference to the DependencyObject passed through to the ValidateValueCallback or all i have to go on is the value being set... my validation is often far more complicated than that.
Lastly, the way i worked out that it was calling the change event with a null value between changes is by putting in some Debug.WriteLines in the LevelAsBenefitLevel_Change method and i notice while watching the output window that MSFT seem to be using something similar for databinding, i'm getting this error when switching from one item in the masterlist to another which has a list for the combobox (i doesn't fire if that list is empty)
BindingExpression path error: 'Name' property not found on 'object' ''null''.
'BindingExpression':Path='Name'; DataItem=''null'';
target element is ''ComboBox' (Name='cboLevel')'; target property is 'NoTarget' (type 'Object');
** cboLevel is the combobox mentioned above.. this is it's xaml:
<ComboBox HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5,0,5,0" Width="200" Grid.Column="1" Grid.Row="2" Name="cboLevel" RenderTransformOrigin="0.5,0.5" Padding="0,0,0,0" ItemsSource="{Binding Levels}" SelectedItem="{Binding Path=LevelAsBenefitLevel}" DisplayMemberPath="Name"/>
Thanks

databinding bug, or am i just expecting too much? - master/detail with a combobox in the detail
crc32
Hi Will,
I advise you very strongly to move away from using DependencyObjects and DependencyProperties as your source. DependencyProperties are useful if you want that particular property to be the target of a Binding, or if you want to animate it, for example. But it seems to me from your samples that you are using this DO simply as the source data of your bindings, and if that is the case, it is too much overhead to use DOs and DPs. You said you are using DOs and DPs because you want your bindings to be notified of changes in the source. It turns out that there are lighter ways to provide these notifications: your source could implement the INotifyPropertyChanged or INotifyCollectionChanged interfaces. I know we have samples in the SDK that show how to use these. If you still have questions after you look at those samples, I'll be happy to help you.
About your SelectedItem sample, I don't quite understand why you have the SelectedItem property in your source. That property is being set because the Binding in the SelectedItem of the ComboBox is two-way be default, so when the selected item changes, that gets propagated to the source. It's being set to null when you select "B" during the very short period of time where the ComboBox is changing data source, which seems reseonable to me. But do you really need to keep the information of which item is selected in your data source Since that information is kept in the UI (and in the View, if you keep selection and currency in sync), I can't think of a good reason to duplicate it in the source. Unless you are using it to drive selection, if that's your scenario then it would make more sense.
Are you familiar with the notion of views When we bind to a collection, we automatically create a CollectionView on top of it, which tracks grouping, sorting, filtering, and more importantly to you, it knows which item is current. You may want to synchronize selection of it, by setting IsSynchronizedWithCurrentItem to true. You can control which item is current by getting the view created on top of your collection and calling the MoveCurrentToX methods (e.g. MoveCurrentToPrevious, MoveCurrentToNext, etc).
In case you're interested in learning more about views, I highly recommend the data binding overview page in the SDK. I know people complain the SDK is missing a lot of information, but this overview is actually really good. I also have several blog posts that explain views in my blog, in particular:
http://www.beacosta.com/Archive/2005_11_01_bcosta_archive.html
http://www.beacosta.com/2006/04/how-can-i-add-my-own-sorting-logic.html
Let me know if this helps.
Bea
Helloimbob
Hi,
I tried to repro your scenario based on your description but it works well for me; I am not able to see the binding fail. I uploaded my attempt to http://www.beacosta.com/Forum/MasterDetailComboBox.zip. Please look into it and let me know how your scenario is different from the one I have so I can open the appropriate bug.
I was thinking about this and I have a question for you: is there any particular reason why you need your source data to be DPs and DOs The target of a binding has to be a DP, but the source does not need to, it can be a simple CLR property. In fact, unless you need it to be a DP for some other reason, I would advise your source to be CLR objects/properties. You can still propagate changes to the UI if your source object implements INotifyPropertyChanged or INotifyCollectionChanged. I discussed when to use DPs vs CLR objects as the source of a data binding in the following forum post: http://forums.microsoft.com/MSDN/ShowPost.aspx PostID=160345&SiteID=1.
Thanks,
Bea
boon3333
Just realised that in the code i posted back I had incorrectly left the IsSynchronisedWithCurrentItem="True" property on the ComboBox so that's why the behaviour was not what i'd seen previously but now having removed it I'm seeing a null value set for the selecteditem every time you switch from one item in the master list to another.
Here's the new xaml:
<
Window Background="Cornsilk" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Repro.Window1"><
StackPanel Name="sp1"><
ListBox ItemsSource="{Binding Path=Selected}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True"/><
StackPanel Name="sp2" DataContext="{Binding Path=Selected}"><
TextBlock Text="{Binding Path=Name}" /><
ComboBox ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedItem}" /></
StackPanel></
StackPanel></
Window>thanks again... let me know how you go with it. You should now see "null" being written in the output window as you switch from one item in the master list to another.
Will
martynh
1) Sadly, we don't have a cancel select in V1. We know it's something people want.
2) You should write a non-static LevelAsBenefitLevel_Change method on your control and call that from the static method ((BenefitSelectionUserBenefit)d).LevelAsBenefitLevel_Change(e)
Have you tried this code on more recent bits Are you still seeing the same problems
stan_mitchell
Hi Will,
I see, you want changes in your UI to be propagated to the source but don't need changes to the source to be propagated to the UI. In that case, you don't need to implement INotifyPropertyChanged in the source. Propagating changes from A to B only requires A to be able to notify of those changes. In your scenario, A is the UI element, and most of Avalon's properties are DPs anyway, so you don't have to do anything special. Your source can be any object, and does not need to implement or derive from anything in particular.
The issue you're seeing with setting the DataContext to something that is not a DO is strange. You should be able to bind to *any* object. Do you get any debug spew in the output window when you set it to the List<T>
If you want to be notified of changes in selection, you can listen to the SelectionChanged event in Selector (which ListBox derives from). If your currency and selection are in sync, alternatively you can listen to the CurrentChanged/CurrentChanging events on CollectionView.
Here you can find a sample that shows that you can set the DataContext to a non-DO object (in this case, an ObservableCollection<string>). It also shows how changes in the UI are propagated to the source, without the source having to implement anything special, and without having to be a DO: http://www.beacosta.com/Forum/PropagateToSource.zip
Let me know if this helped.
Bea
scott13
Anybody from MSFT perhaps have an answer for me on this
As mentioned i do have a workaround but i would like to know what your view is on this - if it will change in future etc...
Thanks
Joaquin Raya
Thanks for the reply, sorry it's taken me so long to get back to you ... i've been off R&D for a bit but i'm back to it now for a while again.
I've made some changes to your example that perhaps don't highlite exactly what i was saying about the null value but it's definately still showing some strange behaviour.
I have no way of posting the zip file of the project here so i'll just paste in the code but if you're having trouble getting to work (or not work) then perhaps you can email me directly so i can send you over the zip file.
Here's the changes to the xaml:
<Window Background="Cornsilk"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Repro.Window1">
<StackPanel Name="sp1">
<ListBox ItemsSource="{Binding Path=Selected}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True"/>
<StackPanel Name="sp2" DataContext="{Binding Path=Selected}">
<TextBlock Text="{Binding Path=Name}" />
<ComboBox ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedItem}" IsSynchronizedWithCurrentItem="True"/>
</StackPanel>
</StackPanel>
</Window>
The main thing that's new is the SelectedItem attribute on the <ComboBox tag, you'll see the property declared below.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Data;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace Repro
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
List<string> itemsA = new List<string>();
itemsA.Add("aaa1");
itemsA.Add("aaa2");
itemsA.Add("aaa3");
List<string> itemsB = new List<string>();
itemsB.Add("bbb1");
itemsB.Add("bbb2");
itemsB.Add("bbb3");
DataItem dataItem1 = new DataItem();
dataItem1.Name = "A";
dataItem1.Items = itemsA;
DataItem dataItem2 = new DataItem();
dataItem2.Name = "B";
dataItem2.Items = itemsB;
MyDependencyObject mdo = new MyDependencyObject();
mdo.Selected = new List<DataItem>();
mdo.Selected.Add(dataItem1);
mdo.Selected.Add(dataItem2);
sp1.DataContext = mdo;
sp2.DataContext = mdo.Selected;
}
}
public class MyDependencyObject : DependencyObject
{
public List<DataItem> Selected
{
get { return (List<DataItem>)this.GetValue(SelectedProperty); }
set { this.SetValue(SelectedProperty, value); }
}
public static readonly DependencyProperty SelectedProperty = DependencyProperty.Register("Selected", typeof(List<DataItem>), typeof(MyDependencyObject), new PropertyMetadata(null));
}
public class DataItem : DependencyObject
{
public string Name
{
get { return (string)this.GetValue(NameProperty); }
set { this.SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(DataItem), new PropertyMetadata(""));
public List<string> Items
{
get { return (List<string>)this.GetValue(ItemsProperty); }
set { this.SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(List<string>), typeof(DataItem), new FrameworkPropertyMetadata(null, DataItem.OnPropertyChanged));
static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
object newValue = e.NewValue; // this is never null!!
}
public string SelectedItem
{
get { return (string)this.GetValue(SelectedItemProperty); }
set { this.SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(string), typeof(DataItem), new FrameworkPropertyMetadata(null, DataItem.OnSelectedItemPropertyChanged));
static void OnSelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine(e.NewValue == null "null" : e.NewValue.ToString());
object newValue = e.NewValue; // this is never null!!
}
}
}
I've put in the Debug.WriteLine call so you can see what's going on there also but you can see it in the UI anyway.
Here's what to do to reproduce the problem:
1. switch back and forward between A & B in the top list ... notice it calls the OnSelectedItemPropertyChanged method as soon as you select B the first time and with the first item in the ComboBox ... but it doesn't appear to actually set the property
2. select an item in the Combo for A .. say "aaa2" ... fires the change handler ... all fine. Now switch to B in the top list. Notice again it fires the change event when it should not. Go back to A now ... it fires it again with the first item in the A list ... "aaa1" then switch back to B in the top list, it's now set B's SelectedItem to "bbb1" ... the first item in the list.
Also i notice it changing from the item i select in the Combo back to the first item then throwing this error "The thread 0x85fc has exited with code 0 (0x0)" in the Output window sometimes
Hopefully this is enough info for you to see what's happening anyway. Maybe i have done something wrong with the setup... please let me know either way.
Also to answer you question about using DependencyProperties... i used them with the understanding that they're the new databinding way to have the changes the user makes in the UI controls be reflected back in the class.
The system i'm building already has a business layer filled with classes / collections that handle updates/inserts etc.. and have their own system for validating user input. My plans were to either change them to be implemeted as DependencyObjects or have a UI layer in between with classes that control the UI, are inhereted from DependencyObject and basically serve to feed info back to the business and error messages from there back to the UI if necessary. We're an Agile development house so i want to have everything unit tested possibly bar the xaml & xaml.cs files initially. How does this sound to you I'd love your feedback more specific to my situation. I also need to make some changes to split our appliction tiers so that the UI classes will reside in the SmartClient application and work out how is the best way to transport the data via WCF .. but perhaps that's outside of this discussion.
Thanks
Will
JeffLomax
Hi Bea,
thanks for the reply again...
Okay firstly on the DO's / DP's issue, I'm using them because i want the selection the user makes to be propagated back to the objects. Once they're bound to the controls I'm not even too concerned about having changes from the source objects propagated to the UI. Can i still use INotifyPropertyChanged to be notified of changes the user makes via the UI, from what you've been saying i understood it's the lower overhead way to go if you're looking for 1way updates only
On a side issue i've just upgraded my R&D system to the latest Beta 2 Build 5384.. and it doesn't seem to me that you can set the DataContext to a class that's not a DependencyObject or something like a List<T> - there are no errors it just doesn't seem to work. Example: I set the DataContext of a window to an instance of a class that is not inhereted from DependencyObject the set the ItemsSource of a ListBox to Path=[property of class - which is a List<T>] ... nothing appears in the list Then i change the class to ineheret from DependencyObject and it works This goes against your advice that i should avoid DO's & DP's where possible because in this case it just doesn't work without them.
** I can elaborate more on this with some code if necessary.
My reasons for binding a property to the SelectedItem property of the ComboBox are so that i can be notified of the change then make the appropriate change in my underlying business logic, validate that change, then cancel it, throw an error if necessary etc.. Perhaps this can be done by looking at the CollectionView.Current property of the List i bound to the Combo or similar but i still need to be notified of the change in the first place.
This is all part of an investigation i'm doing to work out how to re-architect our existing system for the new technology... At this stage I'm going down the route of building these UI helper classes which are DO's with DP's. After i'm done with the initial tests my next step is to also have those UI helpers comunicate back to the business logic via WCF so the separation is necessary anyway. How does this sound to you Any suggestions
Thanks again.
Will