Editing the Lost Focus event using Click-to-Edit

I am working on a simple user control that should go into edit mode by double clicking on it

The concept is based on this question. Click for editing in Silverlight.

By double-clicking, it changes the initial template to Edit Template and it seems pretty clear, except for part (5) How to change the template back when the control loses focus The event "Lost focus" is triggered only when the held controls lose focus Here article that talks about this http://programmerpayback.com/2008/11/20/gotfocus-and-lostfocus-events-on-containers/

I tried to implement the same Technic, but still no result, I can not get the LostFocus event that works for me when I go outside the control

Where is my problem?

My xaml

<ContentControl x:Class="Splan_RiaBusinessApplication.Controls.TimeCodeControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:obj="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"  
    xmlns:behaviour="clr-namespace:Splan_RiaBusinessApplication.Behavior"
    xmlns:controls="clr-namespace:Splan_RiaBusinessApplication.Controls"
    xmlns:Primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
    mc:Ignorable="d"
    IsTabStop="True"
    IsEnabled="True"
    Visibility="Visible"
    d:DesignHeight="100" d:DesignWidth="200"
    d:Height="200" d:Width="200"
                >
    <ContentControl.Resources>
        <ControlTemplate x:Key="DisplayTemplate">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Target.Code, Mode=TwoWay}" />
                <StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" >
                    <TextBlock Text="{Binding Target.Start, Mode=TwoWay, StringFormat=hh\\:mm }" />
                    <TextBlock Text='-' />
                    <TextBlock Text="{Binding Target.End, Mode=TwoWay, StringFormat=hh\\:mm }" />
                </StackPanel>
            </Grid>
        </ControlTemplate>
        
        <ControlTemplate x:Key="EditTemplate">
            <Grid Background="Aqua" Height="200" Width="200">
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <ComboBox Width="100" Height="25" x:Name="cbTimeCode"
                        ItemsSource="{Binding TimeCodes}"
                        SelectedValue="{Binding Target.CodeId, Mode=TwoWay}"                                                                                           
                        SelectedValuePath="TimeCodeId"
                    >
                    <ComboBox.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="40"/>
                                    <ColumnDefinition Width="100"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Text="{Binding Target.Code}" />
                                <TextBlock Grid.Column="1" Text="{Binding Target.Description}" />
                            </Grid>
                        </DataTemplate>
                    </ComboBox.ItemTemplate>
                    <i:Interaction.Triggers>
                        <i:EventTrigger>
                            <behaviour:ResolveElementName PropertyName="ItemsSource" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </ComboBox>

                <!--<controls:TimeRangePickerControl Grid.Row="1" StartTime="{Binding Target.Start, Mode=TwoWay}"  EndTime="{Binding Target.End, Mode=TwoWay}"/>-->
            </Grid>
        </ControlTemplate>
    </ContentControl.Resources>
    
    <Grid x:Name="Layout" Background="Aquamarine">
        <ItemsControl x:Name="PlaceHolder" Template="{StaticResource DisplayTemplate}">
        </ItemsControl>
    </Grid>
</ContentControl>
Run codeHide result

Code for

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;


namespace Splan_RiaBusinessApplication.Controls
{
public class TimeCode
{

    public int TimeCodeId {get;set;}

    public string Code { get; set; }

    public string Description { get; set; }
}

public class TimeDetail
{
    public int TimeDetailId { get;set; }

    public int CodeId { get;set;}

    public TimeSpan Start { get; set; }
    public TimeSpan End { get; set; }

    public string Code { get; set; }

    public string Comment { get; set; }
}

    public partial class TimeCodeControl : ContentControl
    {

        public class TimeCodeControlEventArgs : EventArgs
        {
            public string userName { get; set; }
        }

        private static TimeSpan DoubleClickThreshold = TimeSpan.FromMilliseconds(300);
        private DateTime _lastClick;

        private Boolean m_EditMode = false;
        public Boolean EditMode
        {
            get { return m_EditMode; }
            set
            {
                if (m_EditMode != value)
                {
                    switch (value)
                    {
                        case false:
                            PlaceHolder.Template = this.Resources["DisplayTemplate"] as ControlTemplate;                            
                            break;

                        case true:
                            PlaceHolder.Template = this.Resources["EditTemplate"] as ControlTemplate;
                            break;
                    }

                    m_EditMode = value;
                }
            }
        }

        public bool IsFocused
        {
            get
            {
                return FocusManager.GetFocusedElement() == this;
            }
        }


        public TimeCodeControl()
        {

            TimeCodes = new List<TimeCode>() { new TimeCode { TimeCodeId = 200, Code= "C", Description="Cash" } };
            InitializeComponent();

            Layout.DataContext = this;
            this.IsTabStop = true;
            this.Visibility = Visibility.Visible;
            this.IsEnabled = true;
            this.Focus();


            Layout.MouseLeftButtonDown += Layout_MouseLeftButtonDown;
            //Layout.KeyDown += Layout_KeyDown;
            //Layout.KeyUp += Layout_KeyUp;
            this.LostFocus += TimeCodeControl_LostFocus;
            this.GotFocus += TimeCodeControl_GotFocus;

        }


        void TimeCodeControl_GotFocus(object sender, RoutedEventArgs e)
        {
        }

        void TimeCodeControl_LostFocus(object sender, RoutedEventArgs e)
        {
        }

        public TimeDetail Source
        {
            get { return (TimeDetail)GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        public static readonly DependencyProperty SourceProperty =
            DependencyProperty.Register("Source", typeof(TimeDetail), typeof(TimeCodeControl),
                                        new PropertyMetadata(SourceChanged));

        private static void SourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var control = sender as TimeCodeControl;
            if (control == null) return;

            control.Target = control.Source; //.Copy();

        }

        public List<TimeCode> TimeCodes { get; set; }

        public TimeDetail Target { get; set; }


        private bool FocusIsInside(object parent)
        {
            bool rs = false;
            dynamic oFocus = FocusManager.GetFocusedElement();
            while (oFocus != null)
                try
                {
                    if ((oFocus.GetType() == parent.GetType()) && (oFocus.Equals(this)))
                    {
                        rs = true;
                        break;
                    }
                    else
                    {
                        oFocus = oFocus.Parent;
                    }
                }
                catch
                {
                    break;
                }

            return rs;
        }

        private Boolean hasFocus = false;

        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);
            if (!hasFocus)
            {
                hasFocus = true;
                Debug.WriteLine("Container Got Focus");
            }
        }
        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);

            //if (!FocusIsInside(Layout))
            //{
            //    hasFocus = false;
            //    Debug.WriteLine("Container Lost Focus");
            //    EditMode = false;
            //}

        }


        void Layout_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (DateTime.Now - this._lastClick <= DoubleClickThreshold)
            {
                EditMode = true;
                this._lastClick = DateTime.Now;

                e.Handled = true;
                return;
            }
            this._lastClick = DateTime.Now;

        }
    }
}
Run codeHide result

UPDATE: I decided to use timers to determine the scenario when the user enters focus from outside the container or simply switches focus from one control to another inside the container. this may not be the best solution, but it seems to be working now. I would appreciate any suggestions or recommendations on various approaches or implementations.

public partial class    MyControl: ContentControl
{
   ...

   public event EventHandler<RoutedEventArgs> LostFocus;
   public event EventHandler<RoutedEventArgs> GotFocus;

   bool Focused = false;
   DispatcherTimer FocusTimer = null;

    protected override void OnGotFocus(RoutedEventArgs e)
    {
        base.OnGotFocus(e);
        if (Focused) return;

        Focused = true;

        // it focused from the outside of the control
        // becouse the timer wasn't initialised on the previous LostFocused event
        // generated by other control in the same ContentControl contaner
        if (FocusTimer == null)
        {
           if (GotFocus != null)
                GotFocus(e.OriginalSource, e);

            Debug.WriteLine("Got Focus ");

            return;
        }

        // It was switched from one hosted control to another one
        FocusTimer.Stop();

        FocusTimer = null;
    }

        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);

        if (e.OriginalSource is ComboBox && FocusManager.GetFocusedElement() is ComboBoxItem)
            return;

            FocusTimer = new DispatcherTimer();

            Focused = false;

            FocusTimer.Interval = new TimeSpan(0, 0, 0, 0, 50);
            FocusTimer.Tick += (s, args) =>
            {
                FocusTimer.Stop();
                FocusTimer = null;

                // if after the timeout the focus still not triggered 
                // by another contained element
                // the We lost a focus on the container
                if (!Focused )
                {
                    if(LostFocus != null)
                        LostFocus(e.OriginalSource, e);

                    Debug.WriteLine("Lost Focus " );
                }

            };

            FocusTimer.Start();
        }

   ...
}
Run codeHide result
0
source share
1 answer

There are a few questions. We will see...

LostFocus, ?

, . , , (, TextBox ). "Tab", , .

:

ControlTemplate x:Key="DisplayTemplate" ControlTemplate x:Key="EditTemplate"

ControlTemplates . DataTemplate ContentPresenters.

TimeCodeControl : ContentControl x:Class="Splan_RiaBusinessApplication.Controls.TimeCodeControl"

, , , . : Click-To-Edit : DisplayTemplate EditTemplate TimeCode TimeDetail ( , ). - , . ContentControl, UserControl.

: Click-To-Edit , : DisplayTemplate EditTemplate. DataContext. ContentControl . Control, DependencyProperties DataTemplate, , ContentPresenters . MouseLeftButtonDown LostFocus .

:

... :

public static class ControlExtensions
{
    public static bool IsFocused( this UIElement control )
    {
        DependencyObject parent;
        for (DependencyObject potentialSubControl = FocusManager.GetFocusedElement() as DependencyObject; potentialSubControl != null; potentialSubControl = parent)
        {
            if (object.ReferenceEquals( potentialSubControl, control ))
            {
                return true;
            }
            parent = VisualTreeHelper.GetParent( potentialSubControl );
            if (parent == null)
            {
                FrameworkElement element = potentialSubControl as FrameworkElement;
                if (element != null)
                {
                    parent = element.Parent;
                }
            }
        }
        return false;
    }
}

... :

public class ClickToEditControl : Control
{
    public ClickToEditControl()
    {
        DefaultStyleKey = typeof (ClickToEditControl);
        MouseLeftButtonDown += OnMouseLeftButtonDown;
    }

    private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ClickCount==2)
        {
            GotoEditMode();
            e.Handled = true;
        }
    }

    protected override void OnLostFocus(RoutedEventArgs e)
    {
        base.OnLostFocus(e);

        if (!this.IsFocused())
            GotoDisplayMode();
    }

    private void GotoDisplayMode()
    {
        IsInEditMode = false;
    }

    private void GotoEditMode()
    {
        IsInEditMode = true;
    }

    public DataTemplate EditTemplate
    {
        get { return (DataTemplate) GetValue( EditTemplateProperty ); }
        set { SetValue( EditTemplateProperty, value ); }
    }

    public static readonly DependencyProperty EditTemplateProperty =
        DependencyProperty.Register( "EditTemplate", typeof( DataTemplate ), typeof( ClickToEditControl ), null );

    public DataTemplate DisplayTemplate
    {
        get { return (DataTemplate) GetValue( DisplayTemplateProperty ); }
        set { SetValue( DisplayTemplateProperty, value ); }
    }

    public static readonly DependencyProperty DisplayTemplateProperty =
        DependencyProperty.Register( "DisplayTemplate", typeof( DataTemplate ), typeof( ClickToEditControl ), null );

    public bool IsInEditMode
    {
        get { return (bool) GetValue( IsInEditModeProperty ); }
        set { SetValue( IsInEditModeProperty, value ); }
    }

    public static readonly DependencyProperty IsInEditModeProperty =
        DependencyProperty.Register( "IsInEditMode", typeof( bool ), typeof( ClickToEditControl ), null );
}

... ControlTemplate:

<clickToEdit:BoolToVisibilityConverter x:Key="VisibleIfInEditMode"/>
<clickToEdit:BoolToVisibilityConverter x:Key="CollapsedIfInEditMode" VisibleIfTrue="False"/>

<Style TargetType="clickToEdit:ClickToEditControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="clickToEdit:ClickToEditControl">
                <Grid>
                    <ContentPresenter
                        ContentTemplate="{TemplateBinding EditTemplate}"
                        Content="{Binding}"
                        Visibility="{Binding IsInEditMode, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource VisibleIfInEditMode}}"/>
                    <ContentPresenter
                        ContentTemplate="{TemplateBinding DisplayTemplate}"
                        Content="{Binding}"
                        Visibility="{Binding IsInEditMode, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource CollapsedIfInEditMode}}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

BoolToVisibilityConverter

public class BoolToVisibilityConverter : IValueConverter
{
    public bool VisibleIfTrue { get; set; }

    public BoolToVisibilityConverter(){VisibleIfTrue = true;}

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (VisibleIfTrue)
            return ((bool) value) ? Visibility.Visible : Visibility.Collapsed;
        else
            return ((bool) value) ? Visibility.Collapsed : Visibility.Visible;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotSupportedException();}
}

:

<clickToEdit:ClickToEditControl Height="20" Width="200">
        <clickToEdit:ClickToEditControl.DisplayTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding MyText}"/>
            </DataTemplate>
        </clickToEdit:ClickToEditControl.DisplayTemplate>
        <clickToEdit:ClickToEditControl.EditTemplate>
            <DataTemplate>
                <TextBox Text="{Binding MyText, Mode=TwoWay}"/>
            </DataTemplate>
        </clickToEdit:ClickToEditControl.EditTemplate>
    </clickToEdit:ClickToEditControl>
+2

All Articles