0
votes

I'm trying to implement in place editing of treenodes. I'm fairly close but no cigar. All bindings seem correct. The application runs fine, no output messages, the treeview gets populated. I'm able to select a node, press F2, I can edit the node but as soon as I hit enter the text reverts to what it was before, as if I didn't make an edit. If I make an edit of the Name property somewhere else in the application that change gets reflected in the EditableTextBlock, both in the TextBlock and in the TextBox. Why can't I make the change persistent? It's as if the binding works in one way only.

This is the user control's xaml:

<UserControl x:Class="WPFApplication.EditableTextBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFApplication"
x:Name="MyUserControl">
<UserControl.Resources>

    <DataTemplate x:Key="EditModeTemplate">
        <TextBox KeyDown="TextBox_KeyDown" Loaded="TextBox_Loaded" LostFocus="TextBox_LostFocus"
                 Text="{Binding Path=Text, ElementName=MyUserControl, Mode=TwoWay}"
                 Margin="0" BorderThickness="1" />
    </DataTemplate>

    <DataTemplate x:Key="DisplayModeTemplate">
        <TextBlock Text="{Binding ElementName=MyUserControl, Path=Text, Mode=TwoWay}"/>
    </DataTemplate>

    <Style TargetType="{x:Type local:EditableTextBlock}">
        <Style.Triggers>
            <Trigger Property="IsInEditMode" Value="True">
                <Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
            </Trigger>
            <Trigger Property="IsInEditMode" Value="False">
                <Setter Property="ContentTemplate" Value="{StaticResource DisplayModeTemplate}" />
            </Trigger>
        </Style.Triggers>
    </Style>
</UserControl.Resources>

and the code behind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace WPFApplication
{
    public partial class EditableTextBlock : UserControl
    {

        #region Constructor

        public EditableTextBlock()
        {
            InitializeComponent();
            base.Focusable = true;
            base.FocusVisualStyle = null;
        }

        #endregion Constructor

        #region Member Variables

        // We keep the old text when we go into editmode
        // in case the user aborts with the escape key
        private string oldText;

        #endregion Member Variables

        #region Properties

        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register(
            "Text",
            typeof(string),
            typeof(EditableTextBlock),
            new PropertyMetadata(""));

        public bool IsEditable
        {
            get { return (bool)GetValue(IsEditableProperty); }
            set { SetValue(IsEditableProperty, value); }
        }
        public static readonly DependencyProperty IsEditableProperty =
            DependencyProperty.Register(
            "IsEditable",
            typeof(bool),
            typeof(EditableTextBlock),
            new PropertyMetadata(true));

        public bool IsInEditMode
        {
            get 
            {
                if (IsEditable)
                    return (bool)GetValue(IsInEditModeProperty);
                else
                    return false;
            }
            set
            {
                if (IsEditable)
                {
                    if (value) oldText = Text;
                    SetValue(IsInEditModeProperty, value);
                }
            }
        }
        public static readonly DependencyProperty IsInEditModeProperty =
            DependencyProperty.Register(
            "IsInEditMode",
            typeof(bool),
            typeof(EditableTextBlock),
            new PropertyMetadata(false));

        public string TextFormat
        {
            get { return (string)GetValue(TextFormatProperty); }
            set
            {
                if (value == "") value = "{0}";
                SetValue(TextFormatProperty, value);
            }
        }
        public static readonly DependencyProperty TextFormatProperty =
            DependencyProperty.Register(
            "TextFormat",
            typeof(string),
            typeof(EditableTextBlock),
            new PropertyMetadata("{0}"));

        public string FormattedText
        {
            get { return String.Format(TextFormat, Text); }
        }

        #endregion Properties

        #region Event Handlers

        // Invoked when we enter edit mode.
        void TextBox_Loaded(object sender, RoutedEventArgs e)
        {
            TextBox txt = sender as TextBox;

            // Give the TextBox input focus
            txt.Focus();

            txt.SelectAll();
        }

        // Invoked when we exit edit mode.
        void TextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            this.IsInEditMode = false;
        }

        // Invoked when the user edits the annotation.
        void TextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                this.IsInEditMode = false;
                e.Handled = true;
            }
            else if (e.Key == Key.Escape)
            {
                this.IsInEditMode = false;
                Text = oldText;
                e.Handled = true;
            }
        }

        #endregion Event Handlers

    }
}

The treeview then uses this datatemplate:

<HierarchicalDataTemplate DataType="{x:Type localvm:TreeViewItemViewModel}" ItemsSource="{Binding Children}">
<StackPanel>
    <local:EditableTextBlock Text="{Binding Name}"/>
</StackPanel>

1
There surely is some other stuff in your EditableTextBlock XAML other than the Resources. Could you please update your answer with those as well?Szabolcs Dézsi
Did you try to turn your Text DP's PropertyMetadata into a FrameworkMetaData and set BindsTwoWayByDefault = true?nkoniishvt
@ Szabolcs Dézsi no there isn't. @nkoniishvt yes, and I just retried but that doesn't make a differenceJef Patat

1 Answers

1
votes

Ok I tried on my end and the problem was that the Text property was never set because your TextBox never lost focus (it seems).

So I changed the TextBox's Text binding to add UpdateSourceTrigger=PropertyChanged and a RelativeSource instead of ElementName:

Text="{Binding Path=Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:EditableTextBlock}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

And it worked