Search code examples
wpfrendertransform

WPF 'Children' property value in the path '(0).(1)[0].(2)' points to immutable instance of 'System.Windows.Media.TransformCollection'


I have been battling this for a couple of days now, and just cannot find the answer. Hoping someone here can help.

We have an animated keyboard that pops up when a user selects a Textblock Control that requires keyboard input. The code that animates the keyboard is fine. But it calls code to adjust the grid that contains the textblock control so that the textblock control always sits just above the animated keyboard. The problem that I am seeing is that when the page that contains the grid is closed, it exceptions with the 'Children' property value in the path '(0).(1)[0].(2)' points to immutable instance of 'System.Windows.Media.TransformCollection' on this line:
_AppWindowControl.IsEnabled = false;

The code that gets called when the keyboard is removed (hidden by "Done" keypress) is this:

    /// <summary>
    /// Animation to hide keyboard
    /// </summary>
    private void HideKeyboard()
    {
            if (_verticalOffset != 0)
            {
                TranslateTransform tt = new TranslateTransform();
                DoubleAnimation slide = new DoubleAnimation(_verticalOffset, 0, TimeSpan.FromMilliseconds(400));
                var name = "myTransform" + tt.GetHashCode();
                _mainGrid.RegisterName(name, tt);
                name = "mySlide" + slide.GetHashCode();
                _mainGrid.RegisterName(name, slide);
                _mainGrid.RenderTransform = tt;
                tt.BeginAnimation(TranslateTransform.YProperty, slide);
                _verticalOffset = 0;
            }

            Storyboard sb = (Storyboard)this.TryFindResource("HideKeyboard");
            sb.Completed += new EventHandler(HideKeyboard_Completed);
            sb.Begin(this);
    }

I added the name registration in hopes that would fix the problem. But it does not. If I remove the assignment _mainGrid.RenderTransform = tt; then the appWindow closes without any error.

Also, I said the problem occurs when closing the keyboard. This code was just the easiest to show. When the keyboard appears, there is a call to AdjustScreen, which creates a similar assignment of a TranslateTransform to the _mainGrid.RenderTransform. Again, if I remove the assignment, no problem occurs (no animation occurs either). Otherwise, the same error described above will occur.

Any help would be greatly appreciated. Thanks!

Edit. Here is the StoryBoard from the xaml file:

        <Storyboard x:Key="HideKeyboard">
        <DoubleAnimationUsingKeyFrames AccelerationRatio=".75" BeginTime="00:00:00" DecelerationRatio=".25" Storyboard.TargetName="KeyboardGrid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0" />
            <!--<SplineDoubleKeyFrame KeyTime="00:00:00.20" Value="-10" />-->
            <!--<SplineDoubleKeyFrame KeyTime="00:00:00.45" Value="450" />-->
            <SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="450" />
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>`enter code here`

Also, I have a workaround for this that basically stores the _mainGrid.RenderTransfrom prior to changing it here. Then, when the HideKeybaord_Completed handler is called, it reverts it back. This method works. But it seems pretty hackish.

The crash is an application crash. Most of the time, we are exiting the UI anyway, so no one ever noticed. But, as I am adding a new view to the model, it crashes when closing my view and so it doesn't get back to the previous view.


Solution

  • I attempted to make a Virtual Keyboard kind of behavior

    xaml

    <StackPanel xmlns:l="clr-namespace:CSharpWPF">
        <StackPanel.Resources>
            <UniformGrid Columns="5"
                         Rows="2"
                         x:Key="dummyKeyboard">
                <Button Content="1" />
                <Button Content="2" />
                <Button Content="3" />
                <Button Content="4" />
                <Button Content="5" />
                <Button Content="A" />
                <Button Content="B" />
                <Button Content="C" />
                <Button Content="D" />
                <Button Content="E" />
            </UniformGrid>
        </StackPanel.Resources>
        <TextBox Text="regular textbox" />
        <TextBox Text="virtual keyboard enabled textbox"
                 l:InputProvider.VirtualKeyboard="{StaticResource dummyKeyboard}" />
        <TextBox Text="another regular textbox" />
        <TextBox Text="another virtual keyboard enabled textbox"
                 l:InputProvider.VirtualKeyboard="{StaticResource dummyKeyboard}" />
        <TextBox Text="one more regular textbox" />
    </StackPanel>
    

    for example purpose I have defined a dummyKeyboard containing some buttons. then I have assigned this keyboard to some of the textboxes by setting InputProvider.VirtualKeyboard="{StaticResource dummyKeyboard}", you can assign to all manually or even assign via styles for textbox

    InputProvider class

    namespace CSharpWPF
    {
        public class InputProvider : DependencyObject
        {
            public static object GetVirtualKeyboard(DependencyObject obj)
            {
                return (object)obj.GetValue(VirtualKeyboardProperty);
            }
    
            public static void SetVirtualKeyboard(DependencyObject obj, object value)
            {
                obj.SetValue(VirtualKeyboardProperty, value);
            }
    
            // Using a DependencyProperty as the backing store for VirtualKeyboard.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty VirtualKeyboardProperty =
                DependencyProperty.RegisterAttached("VirtualKeyboard", typeof(object), typeof(InputProvider), new PropertyMetadata(null, OnVirtualKeyboardChanged));
    
            private static void OnVirtualKeyboardChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                TextBox tb = d as TextBox;
                tb.GotFocus += (sender, ee) => OpenPopup(sender as TextBox);
                tb.LostFocus += (sender, ee) => ((Popup)tb.GetValue(PopupProperty)).IsOpen = false;
            }
    
            private static void OpenPopup(TextBox textBox)
            {
                Popup popup = new Popup() { Child = new ContentControl() { Content = GetVirtualKeyboard(textBox), Focusable = false } };
                FocusManager.SetIsFocusScope(popup.Child, true);
                popup.PlacementTarget = textBox;
                popup.AllowsTransparency = true;
                popup.PopupAnimation = PopupAnimation.Slide;
                textBox.SetValue(PopupProperty, popup);
                popup.IsOpen = true;
            }
    
            // Using a DependencyProperty as the backing store for Popup.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty PopupProperty =
                DependencyProperty.RegisterAttached("Popup", typeof(Popup), typeof(InputProvider), new PropertyMetadata(null));
        }
    }
    

    this class defined an attached property VirtualKeyboard which is of type object, thus allowing you to define data templates as necessary

    so this class listen to the GotFocus & LostFocus events of the textbox and displays or hides the virtual keyboard in a sliding animation.

    give this a try, I hope this may help you to achieve the desired.

    note that the dummy keyboard is really dummy it does not do any typing, you need to replace the same with your actual working virtual keyboard while implementing the same in your project.