This is a migrated thread and some comments may be shown as answers.

ObservableCollection and LoadOnDemand

15 Answers 500 Views
TreeView
This is a migrated thread and some comments may be shown as answers.
StrandedPirate
Top achievements
Rank 1
StrandedPirate asked on 17 Aug 2010, 10:43 PM
I've got a tree view on my page that's data bound to an observable collection in my view model. The load on demand method, retrieving records, and updating the collection all work as expected. The problem is that I have to click the expansion icon on the tree view twice in order for the child rows render. Its as if the tree view isn't listening to the property changed events of the observable collection the first time you expand it but the second time you expand it they do render. This happens at every level in the tree view no matter how deep.  My model classes do implement INotifyPropertyChanged and I even went so far as to implement INotifyCollectionChanged to resolve this issue but it has not made any difference. There must be some small detail, perhaps my xaml markup?, that is causing this click twice behavior.
Any ideas on what could be causing this?

below is my xaml to start:

 

 

 

<telerik:RadTreeView Grid.Column="0" Grid.Row="1" PathSeparator="{Binding PathSeperator, Source=tbl_Path}"

 

 

 

ItemEditTemplate="{StaticResource EditTreeViewTemplate1}"

 

 

 

ItemsSource="{Binding Paths}"

 

 

 

SelectedValuePath="PathID" x:Name="FolderTreeView"

 

 

 

IsLoadOnDemandEnabled="True"

 

 

 

IsLineEnabled="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

 

 

 

IsEditable="True"

 

 

 

Margin="2,2,2,2" Height="400" LoadOnDemand="FolderTreeView_LoadOnDemand">

 

 

 

 

<telerik:RadTreeView.ItemTemplate>

 

 

 

 

<telerik:HierarchicalDataTemplate ItemsSource="{Binding Children}">

 

 

 

 

<StackPanel Orientation="Horizontal">

 

 

 

 

<Image x:Name="Icon" Source="/Images/16x16/folder_closed.png" Width="16" Height="16" Margin="0,0,5,0"/>

 

 

 

 

<TextBlock Text="{Binding Item.Name}" />

 

 

 

 

</StackPanel>

 

 

 

 

</telerik:HierarchicalDataTemplate>

 

 

 

 

</telerik:RadTreeView.ItemTemplate>
</telerik:RadTreeView>

 

15 Answers, 1 is accepted

Sort by
0
Tina Stancheva
Telerik team
answered on 20 Aug 2010, 04:05 PM
Hello Joey,

We are not aware of such issue with the RadTreeView. And I also couldn't reproduce it. Perhaps the issue moght be caused by the logic inside the LoadOnDemand() event. If the data is taking too much time to load, that maigh be causing the behavior you described. If you can send us a code snippet we'll be able to further investigate the issue.

I also attached the project I used for testing. Please take a look at it and let me know if I am missing something or if your scenario implements a different approach.

Kind regards,
Tina Stancheva
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
StrandedPirate
Top achievements
Rank 1
answered on 24 Aug 2010, 01:09 AM

Figured out the problem. I have to manually set the IsExpanded property of the tree view item after the ObservableCollection is filled with its data in the code-behind.. Doesn't make any sense since I just clicked the node. This is a defect.

So to re-state. I click a node, the loading icon appears over the expander, the load-on-demand event fires, the data for that item is retrieved and the ObservableCollection is filled, the loading icon disappears and expander returns to normal, however the node itself does not expand to show the child nodes bound to the ObservableCollection. I must click the node again OR use the following code after the ObservableCollection has been filled in order for the node I clicked to expand to see the child nodes.

  

item.IsExpanded = true;

FolderTreeView.UpdateLayout();

0
Mark
Top achievements
Rank 1
answered on 24 Aug 2010, 05:16 PM
Hi I am also getting this problem. The expanded node is a plus (+), I click nothing happens. Click again goes to (-), then click again and voila data is there. Joey trying to implement the workaround you mentioned.
0
StrandedPirate
Top achievements
Rank 1
answered on 25 Aug 2010, 04:53 AM
I think I've found the root cause. Mark check to see if you’re doing this too to confirm this is the fix. I was calling the Clear() method on my ObservableCollection from the code-behind before populating it with new data from the db. I commented it out and the issue went away. I'm unclear whether one should never call this method in a data-binding situation (seems unlikely) or if the Telerik controls have a defect and are not able to handle the effects of calling Clear(). Hopefully someone from Telerik will give us the low down on this.

In short, doing any of the following on an ObservableCollection will cause this double node click issue:
1. Clear()
2. RemoveAll() - extension method
3. "New" up your collection - MyCollection = new ObservableCollection<string>();

Thanks,
Joey
0
Yang
Top achievements
Rank 1
answered on 26 Aug 2010, 01:03 AM
I have the same problem. I have to click twice to expand nodes.
I compared my codes with demo, the difference is I used MVVM to bind with ViewModel, while demo sample codes just assign the List to the ItemsSource of RadTreeView. The first level items are hard-coded. Other level items come from WCF.

Joey, I used your walk around but didn't help.
Can any Telerik guy help?
0
StrandedPirate
Top achievements
Rank 1
answered on 26 Aug 2010, 06:31 AM
The demo code isn't load-on-demand and it’s capping itself at 3 levels so it’s irrelevant. I've run some more tests on a Treeview from the Silverlight SDK and it does not have any issues when performing the aforementioned actions on an ObservableCollection. I’m going to submit a defect on this with a sample app.

Here is the sample application exhibiting the issue.
radtreeviewdoubleexpandissue.zip
0
Miroslav
Telerik team
answered on 26 Aug 2010, 05:33 PM
Hello Joey,

Thank you all for the input & Joey for the sample project!

The main reason for the strange behavior is that we have not anticipated that the LoadOnDemand will be used twice for one and the same item.

But let me explain a little how the LoadOnDemand works:

After the user expands an item that has IsLoadOnDemandEnabled = true, the LoadOnDemand event is raised and IsLoadingOnDemand property is set to true (it controls the spinning arrows animation).

Then whenever the ItemsSource of the TreeViewItem is updated, its IsExpanded property is set to true, the IsLoadingOnDemand is set to false and the items appear.

Then:
- neweing up the items collection does not seem to work because the Children property is not observable, defining it like so will notify the TreeView accordingly:

set
{
    _children = value;
    this.OnPropertyChanged(new PropertyChangedEventArgs("Children"));
}

newing up the collection will have the effect of clearing the items collection or removing all items.

When the TreeViewItems children are removed / reset, it sets is IsExpadned property to false.

Then, as I mentioned, we did not expect that LoadOnDemand will be used for the same item and you get the double-expand / click bug.

If you stop the LoadOnDemand when the items are retrieved, the TreeViewItem will work as expected:

_vml.HomeViewModel.GetChildren(path);
item.IsLoadOnDemandEnabled = false;

Stopping LoadOnDemand when items are retrieved is a good idea if there are no children, since it will remove the arrow.

We can help by doing to things:
(1) Automatically set IsLoadOnDemandEnabled to false once items are updated. (like in the code above)
(2) Make sure loading on demand works the second+ time as well

Both have advantages & disadvantages - so which one would you prefer?

Do you need to fetch items twice?

@Joey - I updated your Telerik Points.

@All - Does setting IsLoadOnDemandEnabled to false after the children are retrieved help in your case?

All the best,
Miroslav
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
StrandedPirate
Top achievements
Rank 1
answered on 26 Aug 2010, 09:04 PM

I added the OnPropertyChanged event on the “set” and now a “new up” behaves itself.

 

To your point of setting IsLoadOnDemandEnabled to false. This does not correct the double expand behavior it rather prevents the end user from ever expanding that item again. That’s not what I want either.

 

We might be headed down the wrong track here without the “why”. The reason this started is because I wanted to refresh the tree view; from the root node(s) all the way down to all of the nodes that are currently expanded by the user. Simple as that. I thought the easiest way to refresh the tree view would be to call the Clear() method on the observable collection at the root node(s) and then re-populate the collections again from the database. The logic for how the application remembers which nodes are expanded is irrelevant here and I don’t want to pursue that. I just want to be able to call the Clear() method on my ObservableCollection without the double expand issue. If I’m going about this the wrong way let me know how else a tree view can be refreshed in the manner I’ve described. E.g. all nodes need to be refreshed from the database.

 

Thanks,

Joey

0
Yang
Top achievements
Rank 1
answered on 29 Aug 2010, 07:42 PM
I finally found what cause this issue but it still makes no sense to me.
MVVM is used to bind my viewmodel to UI. So in the UI resource I put this viewmodel, which cause this viewmodel is initiated once. Also, in the constructor of UI, in order to use the methods of viewmodel, I initiate viewmodel, which cause viewmodel is initiated twice. After puting the breakpoint, I can see exactly what happened.
I am not sure why two initiations cause tree view must be clicked twice. But after removing the the second viewmodel initiation, using (this.resource["resouce"] as ResourceClass) to avoid the second initiation, everything works fine.
BTW, in the constructor of ViewModel, I load the first level of treeview. Maybe this cause the problem?
0
Miroslav
Telerik team
answered on 31 Aug 2010, 04:33 PM
Hi Yang & Joey,

Yes, clearing / resetting the collections is one valid way to do it.

If you are using RIA services, requering for the same data should be enough since the RIA service context manages the relationships between the items quite well.

You are right that the details about what happens with the IsExpanded value are not what you intended to report but I digressed a little. We decided to collapse the TreeViewItem when its items are reset which means that the user will have to click just once to expand the item in your case.

All the best,
Miroslav
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Mike
Top achievements
Rank 1
answered on 14 Sep 2010, 02:47 AM
Hi,

I have a problem similar to that described above.  Here is my setup:

  • Silverlight RadTreeView bound to a collection in my custom ObjectExplorer class as follows:

    public ObservableCollection<ObjectExplorerItem> Items { getprivate set; } 

    An ObjectExplorerItem represents one item on the tree view.  Each Item has a Label property amongst others, which is the text to be displayed.

  • Each ObjectExplorerItem has a Children property defined as 

    public ObservableCollection<ObjectExplorerItem> Children { getset; }
  • Hierarchical data binding declaration as follows:

    <telerik:HierarchicalDataTemplate x:Key="ClusterTreeItemTemplate" ItemsSource="{Binding Children}"> <TextBlock Text="{Binding Label}"/> </telerik:HierarchicalDataTemplate>
  • My TreeView is declared with the following properties:

    	ItemTemplate="{StaticResource ClusterTreeItemTemplate}"
    ItemsSource="{Binding ObjectExplorer.Items}"
  • Some of my nodes load on demand. Here is an example of the code that does this:

    	void StoresContainerLoadChildren(ObjectExplorerItem treeItem)
    {
    // simplified for the sake of example - this actually calls through to a DB
    var stores = new[] { "Store 1""Store 2""Store 3" }; 

    treeItem.Children.Clear();
    foreach (var store in stores)
    {
    var storeItem = new ObjectExplorerItem(treeItem)
    {
    Kind = ObjectExplorerItemKind.Store,
    Label = store
    };
    treeItem.Children.Add(storeItem);
    }
    }

As described above, when the user elects to expand the node, the data loads on demand correctly, but the node remains unexpanded.  The user must attempt to expand the node for a second time to see the child nodes that were loaded on demand.

I have read through the comments and suggestions above, but I can't see how any of the solutions can be used in my situation.

If I remove the call to treeItem.Children.Clear() then the node expands straight away as expected.  However, I also have a Refresh option, which needs to clear the old items before loading refreshed ones, so I can't just remove the call.

It was mentioned that the OnPropertyChanged event should be raised on the Children property in the setter for the children.  I tried this as follows:

	ObservableCollection<ObjectExplorerItem> _children = new ObservableCollection<ObjectExplorerItem>();
     public ObservableCollection<ObjectExplorerItem> Children
     {
     get
     {
     return _children;
     }
set
{
_children = value;
RaisePropertyChanged("Children");
}
} }

Firstly, this doesn't solve the problem, and secondly, it shouldn't even be necessary because the Children property is already observable without having to manually raise the changed event.

As suggested by Joey, I would like to try to implement the following code after each load on demand:

item.IsExpanded = true;

FolderTreeView.UpdateLayout();


However, remember that I am using MVVM, so all communication to the view must be done through bindings.  The code that is retrieving the on-demand data is in my View Model, so I don't even have a reference to my RadTreeView control in this file to call its UpdateLayout() (this is the correct way to do things in MVVM).  I also don't have access to any of the tree view items (only my ObjectExplorerItems), so I can't set the IsExpanded property.

I don't really understand the explanation from Telerik as to why this happens, and I don't really care - I just want it to work.  But at this point I am completely stuck, and I have a broken Tree View that I am about to abandon for a different vendor's control that can cope with what I am trying to do.  Unless, that is, someone can come up with a working solution soon?

Yours hopefully,

Mike
0
StrandedPirate
Top achievements
Rank 1
answered on 14 Sep 2010, 04:42 AM
Yea, I don't understand his response either and the PITS defect I entered has not been worked on. I assume they don't believe this to be a real defect even though the sample application exhibits it and it can be duplicated by others ;)

You can work around this defect with my fix by using the following lines of code after an item's load-on-demand event has finished:
item.IsExpanded = true;

FolderTreeView.UpdateLayout();

Since your using MVVM all you need to do is one of the following:

  1. Use your MVVM framework to issue a message back to the view and put the above code in that "callback".
  2. Simply create your own public event in the ViewModel and attach to it in the View's constructor and use the above code in the callback.
I'm using the MVVM light toolkit and this is how I'm currently doing it:

This is the ViewModel's method for load-on-demand and I fire a message to any one listening that the event has completed. Notice that I'm still calling the Clear() method on my ObservableCollection which is the initiator of this defect.

 

 

private void FolderTreeViewLoadOnDemand(tbl_Path expandingItem)

 

{

MyService.GetChildPaths(

 

new Action<IEnumerable<tbl_Path>, Exception>((a, b) =>

 

{

HeirarchicalPath currentItem =

 

this.Paths.Traverse(x => x.Children).Where(x => x.Item.FullPath == expandingItem.FullPath).FirstOrDefault();

 

 

 

if (currentItem != null)

 

{

currentItem.Children.Clear();

 

 

 

 

 

 

 

 

 

foreach (tbl_Path path in a)

 

currentItem.Children.Add(

 

new HeirarchicalPath(path));

 

}

 

 

// notify view that this is done

 

 

 

 

Messenger.Default.Send<NotificationMessage>(new NotificationMessage(currentItem, "GetChildPathsComplete"));

 

}), expandingItem.PathID);

}

Then in the view I simply run the following code any time that message comes back. I will most likely re-write this to use concrete events because I cringe at string based comparison which is not compile time enforced. This was just the simplest way to get it done while I was learning.

 

 

 

public FolderExplorerView()

 

{

InitializeComponent();

 

 

// register for view model notifications

 

 

 

 

 

 

 

 

Messenger.Default.Register<NotificationMessage>(this, (nm) =>

 

{

 

 

if (nm.Notification == "GetChildPathsComplete")

 

{

 

 

HeirarchicalPath path = nm.Sender as HeirarchicalPath;

 

RadTreeViewItem viewItem = FolderTreeView.GetTreeViewItem(path);

 

 

// remove the load-on-demand cabability for the UI item if the data item has no children

 

 

 

 

 

 

 

 

if (path.Children.Count == 0)

 

{

viewItem.IsLoadOnDemandEnabled =

 

false;

 

}

 

 

// expand the current item

 

 

 

 

 

 

viewItem.IsExpanded =

 

true;
FolderTreeView.UpdateLayout();

 

}

});

}

I'm using the MVVM light toolkit and this is how I'm currently doing it:
0
StrandedPirate
Top achievements
Rank 1
answered on 14 Sep 2010, 04:46 AM
BTW this WYSIWIG is horrible. It's clearly not "what you get".

Mr. 8 ball says "Outlook not so good"
0
Mark
Top achievements
Rank 1
answered on 14 Dec 2010, 04:35 AM
Joey
Slightly out of context - are you binding the LoadOnDemand event directly to the viewmodel, if so could to post that syntax, I am only using the simplest MVVM light command binding and it needs expanding.

LoadOnDemand="{Binding LoadNode}"
0
StrandedPirate
Top achievements
Rank 1
answered on 04 Apr 2011, 09:30 AM

 

 

 

 

 

 

 

 

Mark, find the code below. I'm also using Unity 2.0 for Silverlight so the view model gets passed into my constructor of my view and then execute a RelayCommand during the load-on-demand event.

public

 

 

partial class ContentExplorerView : UserControl

 

 

 

 

 

 

 

 

{

 

 

 

 

private

 

 

 

ContentExplorerViewModel _viewModel;

 

 

 

 

 

public ContentExplorerView(IUnityContainer container, ContentExplorerViewModel viewModel)

 

 

{

 

    _viewModel = viewModel;

 

 

    InitializeComponent();

 

 

 

 

}

 

 

 

 

 

private

 

 

void radTreeView1_LoadOnDemand(object sender, Telerik.Windows.RadRoutedEventArgs e)

 

 

{
    RadTreeViewItem clickedItem = (RadTreeViewItem)e.OriginalSource;

 

    _viewModel.NavigationTreeViewLoadOnDemandCommand.Execute(clickedItem.Item);

}
}

Tags
TreeView
Asked by
StrandedPirate
Top achievements
Rank 1
Answers by
Tina Stancheva
Telerik team
StrandedPirate
Top achievements
Rank 1
Mark
Top achievements
Rank 1
Yang
Top achievements
Rank 1
Miroslav
Telerik team
Mike
Top achievements
Rank 1
Mark
Top achievements
Rank 1
Share this question
or