locked
UserControl with textbox - LostFocus not triggering RRS feed

  • Question

  • Hi, i am using custom controls to make stuff behave like i want it to.

    In this control i use a double click to swap the label to a textbox, so the user can edit the text, and then press enter OR click somewhere else to lose focus and switch back to the label with the new text. But the textbox does not lose focus and im not sure how to fix this

    XML:

    <UserControl x:Class="DisSFCEdit.Comment"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:DisSFCEdit"
                 mc:Ignorable="d" 
                 d:DesignHeight="40" d:DesignWidth="100" MaxHeight="40px" MinWidth="90px" Height="40px" LostFocus="UserControl_LostFocus">
        <Grid FocusManager.IsFocusScope="True">
    
            <Border BorderBrush="White" BorderThickness="0" Margin="1" CornerRadius="2" Background="White">
                <Grid Background="White" Margin="2">
                    <Rectangle Fill="White" Margin="2" />
                    <Label Content="{Binding Path=ContentTextComment}" Foreground="Black" Name="lblText" MouseDoubleClick="lblText_MouseDoubleClick" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    <TextBox Name="txtText" 
                             Text="{Binding Path=ContentTextComment}" Visibility="Hidden"
                             LostFocus="txtText_LostFocus" Background="Yellow"
                             KeyDown="txtText_KeyDown" Margin="2" FontWeight="Bold"
                             />
                </Grid>
            </Border>
    
        </Grid>
    </UserControl>
    C#:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    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;
    
    namespace DisSFCEdit
    {
        /// <summary>
        /// Interaction logic for Comment.xaml
        /// </summary>
        public partial class Comment : UserControl
        {
    
    
            private string lblOld;
    
            public ObjectType Type
            {
                get
                {
                    return ObjectType.Comment;
                }
            }
    
            private int _row;
            private int _column;
    
            public int Row
            {
                get { return _row; }
                set { _row = value; }
            }
    
            public int Column
            {
                get { return _column; }
                set { _column = value; }
            }
    
    
            public Comment()
            {
                InitializeComponent();
                (this.Content as FrameworkElement).DataContext = this;
            }
    
            public static readonly DependencyProperty ContentTextProperty =
                       DependencyProperty.Register(
                             "ContentTextComment",
                              typeof(string),
                              typeof(Comment));
    
            private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var c = d as Comment;
                // now, do something
    
            }
    
            public string ContentTextComment
            {
                get
                {
                    return (string)GetValue(ContentTextProperty);
                }
                set
                {
                    SetValue(ContentTextProperty, value);
                }
            }// ContentTextComment
    
            private void lblText_MouseDoubleClick(object sender, MouseButtonEventArgs e)
            {
                using (var d = Dispatcher.DisableProcessing())
                {
                    lblText.Visibility = Visibility.Hidden;
                    txtText.Visibility = Visibility.Visible;
                    lblOld = ContentTextComment;
                    txtText.Focus();
                    txtText.SelectAll();
                }
    
                System.Threading.ThreadPool.QueueUserWorkItem(
                                   (a) =>
                                   {
                                       System.Threading.Thread.Sleep(100);
                                       txtText.Dispatcher.Invoke(
                           new Action(() =>
                                            {
                                                txtText.Focus();
    
                                            }));
                                   }
                                   );
            }
    
            private void txtText_LostFocus(object sender, RoutedEventArgs e)
            {
                lblText.Visibility = Visibility.Visible;
                txtText.Visibility = Visibility.Hidden;
            }
    
            private void txtText_KeyDown(object sender, KeyEventArgs e)
            {
                if ((e.Key == Key.Enter) || (e.Key == Key.Escape))
                {
                    ContentTextComment = txtText.Text;
                    lblText.Visibility = Visibility.Visible;
                    txtText.Visibility = Visibility.Hidden;
                    e.Handled = true;
                    if (e.Key == Key.Escape)
                    {
                        ContentTextComment = lblOld;
                    }
    
                }
            }
    
            private void UserControl_LostFocus(object sender, RoutedEventArgs e)
            {
                lblText.Visibility = Visibility.Visible;
                txtText.Visibility = Visibility.Hidden;
            }
        }
    }


    • Edited by FuriousRage Thursday, February 11, 2016 7:39 AM
    Thursday, February 11, 2016 7:37 AM

Answers

  • I went around and did it like this:

    In the control:

            public void haveLostFocus()
            {
                if (bLostFocus)
                {
                    ContentTextComment = txtText.Text;
                    lblText.Visibility = Visibility.Visible;
                    txtText.Visibility = Visibility.Hidden;
                    ContentTextComment = "\"" + ContentTextComment + "\"";
                }
            else
                {
                    bLostFocus = true;
                }
            }
    

    The bool in there is because the usercontrol loses the keyboardfocus when i switch to the textbox and focus on it.

    And in the main window i attach this function to the objects:           
    tmp.LostKeyboardFocus += LF;

            private void LF(object sender, RoutedEventArgs e)
            {
                if (sender is Comment)
                {
                    Comment tmp = (Comment)sender;
                    tmp.haveLostFocus();
                }
            }
    

    Maybe a bit ugly way, but it works.

    • Proposed as answer by Xavier Xie-MSFT Friday, February 12, 2016 3:22 AM
    • Marked as answer by FuriousRage Friday, February 12, 2016 5:59 AM
    Thursday, February 11, 2016 11:53 AM

All replies

  • Hi,

        <Grid FocusManager.IsFocusScope="True">

    hinders it from firing.

    Regards,

      Thorsten

    Thursday, February 11, 2016 8:20 AM
  • Hi,

        <Grid FocusManager.IsFocusScope="True">

    hinders it from firing.

    Regards,

      Thorsten

    Hi.

    Unfortunately, the LostFocus still does not trigger even when i removed that FocusManager part.

    Thursday, February 11, 2016 8:35 AM

  • Hi.

    Unfortunately, the LostFocus still does not trigger even when i removed that FocusManager part.

    Do you have a focusable Control on your window except the usercontrol?

    Regards,

      Thorsten

    Thursday, February 11, 2016 8:52 AM

  • Hi.

    Unfortunately, the LostFocus still does not trigger even when i removed that FocusManager part.

    Do you have a focusable Control on your window except the usercontrol?

    Regards,

      Thorsten

    Hi, yes i have it on the canvas:

                <Canvas FocusManager.IsFocusScope="True" Grid.Row="1" Grid.Column="1" Height="10000px" Width="{Binding ActualWidth, ElementName=scrollViewer}" Name="grid"  RenderTransformOrigin="0.5,0.5" Focusable="True">

    Thursday, February 11, 2016 8:54 AM

  • Hi, yes i have it on the canvas:

                <Canvas FocusManager.IsFocusScope="True" Grid.Row="1" Grid.Column="1" Height="10000px" Width="{Binding ActualWidth, ElementName=scrollViewer}" Name="grid"  RenderTransformOrigin="0.5,0.5" Focusable="True">

    But the Canvas doesnt get the focus if you click onto it (try with Focusable="True" and giving a BGColor)...

    You could handle the mouseLeftButtonDown for the Window and check the HitTestResults when clicking... but this is rather a trick than a clean solution...


    [Note that my UserControl here is named UserControl1, not DisSFCEdit.Comment]

    So, if you have a Window like:

    <Window x:Class="WpfApplication1.MainWindow"
            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:local="clr-namespace:WpfApplication1"
            mc:Ignorable="d"  MouseLeftButtonDown="window_MouseLeftButtonDown"
            Title="MainWindow" Height="350" Width="525">
        <Canvas  Margin="0">
            <local:UserControl1 HorizontalAlignment="Left" Margin="77,58,0,0" VerticalAlignment="Top" ContentTextComment="Hallo Du da" x:Name="uc1"/>
        </Canvas>
    </Window>

    where your UserControl is, you could add this code:

            private void window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                Point pt = e.GetPosition(this);
                HitTestResult ht = VisualTreeHelper.HitTest(this, pt);
                if (ht != null && ht.VisualHit.GetType().Name.Contains("TextBoxView") == false  && ht.VisualHit.GetType().Name.Contains("TextBoxLineDrawingVisual") == false )
                    FocusManager.SetFocusedElement(this, this);
            }

    to determine, if you clicked onto the UserControl or the window....

    Regards,

      Thorsten




    Thursday, February 11, 2016 9:04 AM
  • ... if you set the canvas focusable and specify a bg-color it works with the Tab key...

    <Canvas  Margin="0" Focusable="True" Background="Aqua" x:Name="cv">
    and put your UserControl into this canvas...

    Thursday, February 11, 2016 9:40 AM
  • A usercontrol has an iskeyboardfocuswithin property.

    https://msdn.microsoft.com/en-us/library/system.windows.uielement.iskeyboardfocuswithin(v=vs.110).aspx

    You can handle iskeyboardfocuswithinchanged - on the usercontrol

    https://msdn.microsoft.com/en-us/library/system.windows.uielement.iskeyboardfocuswithinchanged(v=vs.110).aspx

    And check is focus is still within the usercontrol.


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    • Proposed as answer by Thorsten Gudera Thursday, February 11, 2016 9:53 AM
    Thursday, February 11, 2016 9:44 AM
  • I have tried both ways, in the first one i am not sure if i should change TextBoxView or not.

    This is a part of the whole mainview. im both windows LMBD, and ScrollViewer and the canvas, i have put the first example code into.

            <ScrollViewer Name="scrollViewer" Grid.Row="1" Grid.Column="1" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" CanContentScroll="True" BorderThickness="1" MaxHeight="10000px" PreviewMouseLeftButtonDown="scrollViewer_PreviewMouseLeftButtonDown">
                <!--<Canvas Grid.Row="1" Grid.Column="1" Height="{Binding ActualHeight, ElementName=scrollViewer}" Width="{Binding ActualWidth, ElementName=scrollViewer}" Name="grid"  RenderTransformOrigin="0.5,0.5">-->
                <Canvas FocusManager.IsFocusScope="True" Grid.Row="1" Grid.Column="1" Height="10000px" Width="{Binding ActualWidth, ElementName=scrollViewer}" Name="grid"  RenderTransformOrigin="0.5,0.5" Focusable="True" PreviewMouseLeftButtonDown="grid_PreviewMouseLeftButtonDown" Background="White">
                    <Canvas.LayoutTransform>
                        <TransformGroup>
                            <ScaleTransform x:Name="scaleTransform" />
                        </TransformGroup>
                    </Canvas.LayoutTransform>
                </Canvas>
            </ScrollViewer>
    

    Thursday, February 11, 2016 9:44 AM
  • ... ok, I found an easier solution:

    1. Make your Canvas focusable, specify a Background for it and add a handler for MouseLeftButtonDown

        <Canvas  Margin="0" Focusable="True" Background="Aqua" x:Name="cv" MouseLeftButtonDown="cv_MouseDown">
            <local:UserControl1 HorizontalAlignment="Left" Margin="77,58,0,0" VerticalAlignment="Top" ContentTextComment="Hallo Du da" x:Name="uc1"/>
        </Canvas>

    2. add some code:

            private void cv_MouseDown(object sender, MouseButtonEventArgs e)
            {
                if (e.OriginalSource.GetType().Equals(typeof(TextBlock)) == false && e.OriginalSource.GetType().Equals(typeof(Border)) == false)
                {
                    UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;
                    elementWithFocus.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
                }
            }

    Regards,

      Thorsten

    Thursday, February 11, 2016 9:49 AM
  • A usercontrol has an iskeyboardfocuswithin property.

    https://msdn.microsoft.com/en-us/library/system.windows.uielement.iskeyboardfocuswithin(v=vs.110).aspx

    Oh, this is good!! Thanks Andy... I wasnt aware of that.

    But this seems to work with the TabKey but not when clicking the parent canvas (Focusable + BGColor)

    Regards,

      Thorsten


    Thursday, February 11, 2016 9:53 AM
  • Tried that too. the FocusLost in the userobject's textbox does not trigger. But the control seem to lose focus, because marker gets removed same as the highlight.
    Thursday, February 11, 2016 9:54 AM
  • I've not really picked your usercontrol apart.

    IsKeyBoardFocusWithin ought to be reliable unless you're somehow interfering with focusmanager setting scopes.

    Or

    The focus isn't really changing.

    .

    I think I would probably approach this in a different way. By inheriting from textbox and re-templating so there's a textblock and texbox in the one control.

    As an example, this is a template for a textbox that hides the text and shows ellipses instead, and has a watermark which disappears as the textbox is focussed.

    It's a password format textbox - you can bind Text.

    Note the isfocused datatrigger

        <Window.DataContext>
            <local:MainWindowViewModel/>
        </Window.DataContext>
        <Grid>
            <Grid.Resources>
                <Style x:Key="PwdStyle" TargetType="{x:Type TextBoxBase}">
                    <Setter Property="SnapsToDevicePixels" Value="True"/>
                     <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
                    <Setter Property="Foreground" Value="Transparent"/>
                    <Setter Property="FontFamily" Value="Courier New"/>
    
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type TextBoxBase}">
                                <Border 
                                       Name="border"
                                       BorderBrush="Gray" 
                                       BorderThickness="1" 
                                       Padding="2"
                                       Background="{TemplateBinding Background}"
                                       SnapsToDevicePixels="true">
                                    <Grid>
                                        <AdornerDecorator 
                                         x:Name="PART_ContentHost" />
                                        <TextBlock  Text="{Binding Text
                                                  , RelativeSource={RelativeSource TemplatedParent}
                                                  , Converter={local:BulletConverter}}" 
                                       Background="Transparent"
                                       Foreground="Black"
                                       IsHitTestVisible="False"/>
                                        <TextBlock Text="Please Enter Password" 
                                       IsHitTestVisible="False"
                                       Foreground="Gray">
                                            <TextBlock.Style>
                                                <Style TargetType="TextBlock">
                                                    <Setter Property="Visibility" Value="Collapsed"/>
                                                    <Style.Triggers>
                                                        <DataTrigger Binding="{Binding Text.Length, RelativeSource={RelativeSource TemplatedParent}}" Value="0">
                                                            <Setter Property="Visibility" Value="Visible"/>
                                                        </DataTrigger>
                                                        <DataTrigger Binding="{Binding IsFocused, RelativeSource={RelativeSource TemplatedParent}}" Value="true">
                                                            <Setter Property="Visibility" Value="Collapsed"/>
                                                        </DataTrigger>
                                                    </Style.Triggers>
                                                </Style>
                                            </TextBlock.Style>
                                        </TextBlock>
                                    </Grid>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsEnabled" Value="false">
                                        <Setter Property="Background" TargetName="border" 
                                                Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                                        <Setter Property="Foreground" 
                                                Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Grid.Resources>
            <TextBox 
                Text="{Binding Password, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
                Style="{StaticResource PwdStyle}"
                VerticalAlignment="Top"/>
                  
        </Grid>


    There's also a bulletconverter which gives you a round blob instead of each character as you type.

        public class BulletConverter : MarkupExtension, IValueConverter
        {
    
            public override object ProvideValue(IServiceProvider serviceProvider)
            {
                return this;
            }
            public object Convert(object value, Type targetType, object parameter,
                CultureInfo culture)
            {
                string pw = (string)value;
               // Debug.WriteLine(new string("\u2022".ToCharArray()[0], pw.Length));
                return new string("\u2022".ToCharArray()[0], pw.Length);
            }
    
            public object ConvertBack(object value, Type targetType, object parameter,
                CultureInfo culture)
            {
                return value;
            }
    
        }

    .

    I don't really follow why you want to make it hard for people to edit text.

    Some users prefer to only use the keyboard and so double click would be right out for them.


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    • Edited by Andy ONeill Thursday, February 11, 2016 10:13 AM
    Thursday, February 11, 2016 10:12 AM

  • I don't really follow why you want to make it hard for people to edit text.

    Some users prefer to only use the keyboard and so double click would be right out for them.


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    Hi, the program is more like a "drawing" program, something like powerpoint, and thats why the double clicking on it to edit the text. Here in this picture, there is 4 UserObjects, all but the last one has editable
    text, and the one with yellow has been double clicked, for edit.

    Im gonna look into your code and see if i can make it work.


    Thursday, February 11, 2016 10:56 AM
  • I still think focusscope or whatever you're clicking not taking keyboard focus has to be your issue.

    A trigger using isfocused on the tectbox should work. So long as it really does actually lose focus.

    Note that one s in isfocused, for whatever reason they misspelt it.

    .

    Another thought here.

    Do you really need anything more than a textbox?

    If you make it isreadonly true and white background, couldn't you just make it isreadonly false and yellow background when they double click? Or yellow on isfocused true.


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    Thursday, February 11, 2016 11:25 AM
  • I went around and did it like this:

    In the control:

            public void haveLostFocus()
            {
                if (bLostFocus)
                {
                    ContentTextComment = txtText.Text;
                    lblText.Visibility = Visibility.Visible;
                    txtText.Visibility = Visibility.Hidden;
                    ContentTextComment = "\"" + ContentTextComment + "\"";
                }
            else
                {
                    bLostFocus = true;
                }
            }
    

    The bool in there is because the usercontrol loses the keyboardfocus when i switch to the textbox and focus on it.

    And in the main window i attach this function to the objects:           
    tmp.LostKeyboardFocus += LF;

            private void LF(object sender, RoutedEventArgs e)
            {
                if (sender is Comment)
                {
                    Comment tmp = (Comment)sender;
                    tmp.haveLostFocus();
                }
            }
    

    Maybe a bit ugly way, but it works.

    • Proposed as answer by Xavier Xie-MSFT Friday, February 12, 2016 3:22 AM
    • Marked as answer by FuriousRage Friday, February 12, 2016 5:59 AM
    Thursday, February 11, 2016 11:53 AM