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

Editing a bound item

3 Answers 239 Views
TreeView
This is a migrated thread and some comments may be shown as answers.
Jerome
Top achievements
Rank 1
Jerome asked on 26 Jul 2011, 06:41 PM
I have a data class, Category, which has a property Children. This hierarchy, from a root Category node is bound to a TreeView. Category.Name is what displays for the Category. TreeView currently works fine.

What I need to figure out how to do is raise an edit event when the user makes a modification of the display value. I do not want to actually have anything edit the underlying bound value, until I do it myself.

I have tried with and without an EditItemTemplate. In the RadTreeView.Edited event, Source is set to the Category instance itself. OriginalSource is set to the RadTreeViewItem. NewText and OldText are both null (deprecated.) OldValue and NewValue are set to teh Category instance itself. And the Category.Name property has been edited. OldValue.Name and NewValue.Name are both the new value. Of course. Since it's set to the instance itself, there's no way to distuinguish between the before and after states.

What the heck can I do here? I thought I'd just use an EditItemTemplate, with a custom TextBox, in order to capture the new value, and set the Binding.Mode of the HierarchalDataTemplate to OneWay, to prevent it from being changed. However, now I have no way in the Edited event to retrieve the new value. I can't find the TextBox of the EditItemTemplate anywhere underneath RadTreeViewItem, and the OldValue and NewValue are of course still set to the Category itself (which is no longer altered, at least.)

Help?

3 Answers, 1 is accepted

Sort by
0
Kiril Stanoev
Telerik team
answered on 27 Jul 2011, 03:14 PM
Hi Jerome,

I'm pasting a part of a support communication which is related to your question. Take a look at it and let me know if anything is unclear and you need further assistance. I'd be glad to assist you.

====================== Customer ======================

The RadTreeView PreviewEdited event is being fired after the underlying data has already been updated. I am trying to catch this event to validate the data before allowing the commit.  When the event is caught in my code the underlying bound data has already been changed. As a test I hooked into the EditStarted and that fired appropriately, as did the Edited event. I set up the source code and debugged into the EditableHeaderItemsControl.cs -> CommitEdit(). If I change the code to move the following two lines after the preview event then things fire when expected:

550: this.UpdateTextBoxesInBeforeEditEnd();
551: var newEditValue = this.GetEditValue();

Move them down past the this.OnPreviewEdited() block beginning on line 558.

The full change is below.  This still isn't 100% right as there are two different sets for the 'newEditValue'.  Any pointers as to what I should change the function to to fix this error?  Can you confirm on your side that the event is indeed firing too late.

public virtual bool CommitEdit()
{
    if (!this.IsInEditMode && !this.isInEditModeReentrancyCheck)
    {
        return true;
    }
 
    this.UpdateTextBoxesInBeforeEditEnd();
 
    var newEditValue = this.GetEditValue();
 
    if (this.HasInvalidEditElements())
    {
        return false;
    }
 
    var previewCommit = new RadTreeViewItemEditedEventArgs(newEditValue, this.oldEditValue, PreviewEditedEvent, this);
 
    if (this.OnPreviewEdited(previewCommit))
    {
        return false;
    }
 
    this.UpdateTextBoxesInBeforeEditEnd();
 
    var newEditValue = this.GetEditValue();
 
    newEditValue = previewCommit.NewValue;
 
    this.IsInEditMode = false;
 
#if !WPF
    this.ChangeVisualState();
#endif
 
    this.SetEditValue(newEditValue);
 
    var commit = new RadTreeViewItemEditedEventArgs(newEditValue, this.oldEditValue, EditedEvent, this);
 
    this.OnEdited(commit);
 
    return true;
}

Follow-up.  Looks like there are two problems:

1) The early call, before the PreviewEvent is fired, to this.UpdateTextBoxesInBeforeEditEnd();

This updates the underlying data before the event fires.

2) An error (?) in the call to this.GetEditValue():

var newEditValue = this.GetEditValue();

Should this code in GetEditValue() be checking for != on the GetBindingExpression() check?

if (focusableTextBox != null && focusableTextBox.GetBindingExpression(TextBox.TextProperty) == null)
{

If I move just the UpdateTextBoxesInBeforeEndEdit() down, and change to != null it looks like things are working as expected.  I need to do more testing to confirm.

====================== Telerik ======================

Hi,

Actually the points you mentioned are implemented intentionatelly in that manner.

1. The UpdateTextBoxesInBeforeEditEnd() is called before the preview event is fired because there is no other easy way that we can report the new value to the user.
Imagine you have a bound tree view with several TextBox-es in the header edit template. You could define oldValues and newValues (plural) properties of type List or Dictionary and provide all the new and old values of the TextBox-es.
Another option is to return a reference to the business item that is bound to that particular RadTreeViewItem and let the user operate with it (natural because he knows how to work with its own business item). This is how the tree view is implemented. That is why we need to update the business data so that the user would be able to retrieve the newly entered value.

Perhaps the "Preview" part of the name is a bit confusing. You should think of the PreviewEdited event as event that is fired before the action is completed and can be used to rollback the operation.

2. The GetEditValue() method returns the first TextBox text when there is no binding attached to it. Otherwise it returns the header, which typically contains the bound business object from RadTreeView.ItemsSource property.

If you need to rollback the edit action you can do the following:

private void RadTreeView_PreviewEdited(object sender, Telerik.Windows.Controls.RadTreeViewItemEditedEventArgs e)
{
    e.Handled = ........
    if (e.Handled)
    {
        treeView1.SelectedContainer.CancelEdit();
    }
}

You can handle the PreviewEdited event and call CancelEdit on the RadTreeViewItem that is currently under edit.
Hope this helps. Please let us know if you need more info on the topic or have any further questions.

====================== Customer ======================

Hello,

I've reverted my change to the Telerik source to re-test based on your email.  Things still don't work as expected:

1) My treeview is bound to hierarchical data
2) When the PreviewEdited event is fired the OldText and NewText values are null
3) When the PreviewEdited event is fired the OldValue and NewValue values are the same -- I would expect these to be different!!!  This is the root of my problem.  The old value is gone so I can't perform the logic/action I need based on comparing the old and new values.
4) Why does the underlying data model value (bound hierarchical data) get changed BEFORE I get a chance to validate the edit and set e.Handled appropriately?

I am not using the ItemEditTemplate or ItemEditTemplateSelector at this time.

Based on the WPF TreeView documentation, which states:

"The PreviewEdited event is fired just before the new Header text of the item is applied. If the treeview is data bound you can update your DataSource with the new value. The Edited event is fired once the new Header text for the item is applied. Via the RadTreeViewItemEditedEventArgs of the PreviewEdited and Edited events you can get access to the new text of the Header property, as well as to the old one."

I wouldn't expect the data bound value to be changed until the PreviewEdit event exited with e.Handled = false.

Thanks.

====================== Telerik ======================

Hi,

Thank you for elaborating more on your scenario. I've spoken with Kiril and he is aware of the answer I've provided you with. At first glance it seemed that the suggestion you gave us would work. However, digging deeper we discovered that there are valid reasons why RadTreeView works the way it does.

Regarding your points:

"2) When the PreviewEdited event is fired the OldText and NewText values are null"


The NewText and OldText properties are not null only in cases when you edit a RadTreeView that is not bound to a data source:

<telerik:RadTreeView PreviewEdited="RadTreeView_PreviewEdited" IsEditable="True">
    <telerik:RadTreeViewItem Header="Item 0">
        <telerik:RadTreeViewItem Header="Item 0.1" />
    </telerik:RadTreeViewItem>
</telerik:RadTreeView>

In the above sample, if you change Item 0 to 123...



...in the PreviewEdited event you will receive Item0 for OldText and 123 for NewText. The OldText and NewText properties are always null when your RadTreeView is bound to a data source. This is the reason why we've marked them with obsolete and will remove them in any of the following major releases. For data-bound scenarios, we strongly advocate you use the NewValue and OldValue properties.

"3) When the PreviewEdited event is fired the OldValue and NewValue values are the same -- I would expect these to be different!!!  This is the root of my problem.  The old value is gone so I can't perform the logic/action I need based on comparing the old and new values."

Apparently I did not manage to explain very clearly why OldValue and NewValue are the same in the PreviewEdited event. Imagine the following basic scenario. You have a data-bound RadTreeView:


This RadTreeView is bound to a collection of business items that look as the one bellow:

public class DataItem
{
    public string Name
    {
        get;
        set;
    }
}

You select Item 0, press F2 on the keyboard and Item 0 goes into edit mode:


You type 123 and press Enter on the keyboard. The PreviewEdited event is fired. We can't know what you have typed unless we update the business object. That is why we must update the business object (which happens in UpdateTextBoxesInBeforeEditEnd). This way you can receive the updated business object as NewValue in the PreviewEdited event. Since we have updated the business object, OldValue is lost. The only way for us to send you correct versions of OldValue and NewValue is only if we create a deep copy of the business object before it has been updated in UpdateTextBoxesInBeforeEditEnd. As you understand, this will significantly impact the performance of RadTreeView since deep copying will be done using reflection.

One possible workaround, which in our opinion will serve you good, is to preserve the property you want in a variable in the PreviewEditStarted event. Then, in the PreviewEdited event you can decide whether you want to rollback to that property value or not.

<Grid x:Name="LayoutRoot" Background="White">
    <telerik:RadTreeView x:Name="treeView1" PreviewEditStarted="RadTreeView_PreviewEditStarted"
            PreviewEdited="RadTreeView_PreviewEdited" IsEditable="True">
        <telerik:RadTreeView.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <TextBlock Text="{Binding Name}" />
                </Grid>
            </DataTemplate>
        </telerik:RadTreeView.ItemTemplate>
    </telerik:RadTreeView>
</Grid>

public partial class MainPage : UserControl
{
    string dataItemName;
  
    public MainPage()
    {
        InitializeComponent();
  
        treeView1.ItemsSource = Enumerable.Range(0, 5).Select(i => new DataItem() { Name = "Item " + i });
    }
  
    private void RadTreeView_PreviewEditStarted(object sender, Telerik.Windows.Controls.RadTreeViewItemEditedEventArgs e)
    {
        this.dataItemName = (e.OldValue as DataItem).Name;
    }
  
    private void RadTreeView_PreviewEdited(object sender, Telerik.Windows.Controls.RadTreeViewItemEditedEventArgs e)
    {
        DataItem dataItem = e.NewValue as DataItem;
        if (dataItem.Name == "123" /* or some other bad value */)
        {
            e.Handled = true;
            dataItem.Name = this.dataItemName;
        }
    }
}
  
public class DataItem : INotifyPropertyChanged
{
    private string name;
  
    public string Name
    {
        get
        {
            return this.name;
        }
        set
        {
            if (this.name != value)
            {
                this.name = value;
                this.OnPropertyChanged("Name");
            }
        }
    }
  
    public event PropertyChangedEventHandler PropertyChanged;
  
    private void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

"4) Why does the underlying data model value (bound hierarchical data) get changed BEFORE I get a chance to validate the edit and set e.Handled appropriately?"

I believe the above explanation answers this question.

Let us know how this sounds to you. We'd be glad to continue the discussion further.

All the best,
Kiril Stanoev
the Telerik team

Register for the Q2 2011 What's New Webinar Week. Mark your calendar for the week starting July 18th and book your seat for a walk through of all the exciting stuff we will ship with the new release!

0
Jerome
Top achievements
Rank 1
answered on 27 Jul 2011, 03:46 PM
Well that was a lot of reading, for this gem:

"One possible workaround, which in our opinion will serve you good, is to preserve the property you want in a variable in the PreviewEditStarted event. Then, in the PreviewEdited event you can decide whether you want to rollback to that property value or not."

So that's that then. Not much I can do other than maintain the state myself?
0
Kiril Stanoev
Telerik team
answered on 28 Jul 2011, 09:16 AM
Hi Jerome,

Yep. Currently that's the one possible solution to this particular case.

Greetings,
Kiril Stanoev
the Telerik team

Register for the Q2 2011 What's New Webinar Week. Mark your calendar for the week starting July 18th and book your seat for a walk through of all the exciting stuff we will ship with the new release!

Tags
TreeView
Asked by
Jerome
Top achievements
Rank 1
Answers by
Kiril Stanoev
Telerik team
Jerome
Top achievements
Rank 1
Share this question
or