Juan Perez
Juan Perez

Reputation: 79

Scrollable TextBox in WP7 (ala Skype and Facebook)

Basically, I'm developing a note-taking app where the user can type as long as he wants (kinda like Scrollable TextBox in WP7). I put the TextBox in a ScrollViewer, and everything is fine; I disable the ScrollViewer when the TextBox gets focus, so it scrolls automatically while the user is typing.

My problem is, I want the user to be able to scroll while editing the note, just as he's able to scroll while reading the note. I thought the only way to do this would be to hold down until the oversized caret appears, then moving it around, but I discovered that, in fact, third party apps support this type of scrolling.

What I'm trying to achieve is something like Word/OneNote on the phone, where the user can easily scroll while editing the document (here's a video demonstrating it). The same effect is seen in the Skype and Facebook apps where, while writing a message, you can scroll it to see more of it.

I wonder if this is a custom control, or if the layout was designed in a specific way, because a TextBox inside a ScrollViewer simply doesn't work.

I'd appreciate any help. Thanks in advance.

Upvotes: 2

Views: 1727

Answers (2)

Juan Perez
Juan Perez

Reputation: 79

Based on the answer given by Ku6opr, I tweaked the code so it works under my circumstances. Now, the TextBox has regular behavior, but is scrollable, and the RootFrame doesn't go up automatically.

XAML:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <ScrollViewer x:Name="InputScrollViewer">
        <TextBox x:Name="MessageText" TextWrapping="Wrap" Text="" AcceptsReturn="True" TextChanged="inputText_TextChanged" GotFocus="MessageText_GotFocus" Padding="0,0,0,400" Tap="MessageText_Tap" />
    </ScrollViewer>
</Grid>

C#:

public partial class MainPage : PhoneApplicationPage
{
    double InputHeight = 0.0;

    // Constructor
    public MainPage()
    {
        InitializeComponent();
    }

    private void MessageText_GotFocus(object sender, System.Windows.RoutedEventArgs e)
    {
        (App.Current as App).RootFrame.RenderTransform = new CompositeTransform();
    }

    private void inputText_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
    {
        Dispatcher.BeginInvoke(() =>
        {
            double CurrentInputHeight = MessageText.ActualHeight;

            if (CurrentInputHeight > InputHeight)
            {
                InputScrollViewer.ScrollToVerticalOffset(InputScrollViewer.VerticalOffset + CurrentInputHeight - InputHeight);
            }

            InputHeight = CurrentInputHeight;
        });
    }

    public void MessageText_Tap(object sender, GestureEventArgs e)
    {
        InputScrollViewer.ScrollToVerticalOffset(e.GetPosition(MessageText).Y - 80);
    }
}

The Tap event handlers detects the vertical position of the tap, and scrolls the ScrollViewer so the caret is in view when the TextBox gets focus.

Upvotes: 3

Ku6opr
Ku6opr

Reputation: 8126

Maybe it's not a best solution, but it's work:

xaml:

<Grid x:Name="LayoutRoot" Background="Transparent">
    <ScrollViewer x:Name="InputScrollViewer" Margin="12,0" Height="800" VerticalAlignment="Top">
        <StackPanel Orientation="Vertical" Margin="0,12">
            <TextBlock TextWrapping="Wrap" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed in ligula augue. Morbi facilisis varius enim in congue. Nam vehicula imperdiet ipsum in ullamcorper. Integer quis augue in dui tincidunt elementum. Nulla in mi mauris, eu laoreet leo. Sed vehicula quam nec leo imperdiet a rutrum felis viverra." Style="{StaticResource PhoneTextNormalStyle}" Margin="12,12,12,0"/>
            <TextBlock TextWrapping="Wrap" Text="Morbi molestie facilisis eleifend. Cras volutpat, lectus nec tincidunt accumsan, mi purus faucibus purus, vitae semper mauris lacus id mauris. Fusce eget massa ut magna lacinia gravida. Ut id velit purus. Nullam eu mi ac justo imperdiet pretium. Curabitur vehicula congue purus vitae sollicitudin." Style="{StaticResource PhoneTextNormalStyle}" Margin="12,12,12,0"/>
            <TextBlock TextWrapping="Wrap" Text="Aenean eget dui a urna commodo faucibus sit amet nec eros. Nam tempus facilisis urna, ut varius justo euismod sit amet. Vivamus ultrices volutpat tortor in viverra. Vestibulum laoreet odio at tellus consectetur ut convallis quam semper. Duis in iaculis lectus. Aliquam erat volutpat. Nulla facilisi. Quisque vitae metus lorem. Fusce et erat nisl, sit amet gravida libero. Cras elementum eros vitae tellus sollicitudin accumsan. Pellentesque egestas luctus bibendum. Duis eros ipsum, mollis ut laoreet eu, consectetur id lectus. Maecenas viverra risus urna." Style="{StaticResource PhoneTextNormalStyle}" Margin="12,12,12,0"/>
            <TextBox x:Name="MessageText" TextWrapping="Wrap" Text="" AcceptsReturn="True" TextChanged="inputText_TextChanged" GotFocus="MessageText_GotFocus" LostFocus="MessageText_LostFocus"/>
        </StackPanel>
    </ScrollViewer>
</Grid>

code-behind:

public partial class MainPage : PhoneApplicationPage
{
    bool IsInputFocused = false;
    double InputHeight = 0.0;
    double KeyboardHeight = 338;
    double KeyboardClipboardHeight = 72;
    double RootHeight = 800;

    public MainPage()
    {
        InitializeComponent();

        DeviceStatus.KeyboardDeployedChanged += new EventHandler(DeviceStatus_KeyboardDeployedChanged);
    }

    void DeviceStatus_KeyboardDeployedChanged(object sender, EventArgs e)
    {
        if (IsInputFocused)
        {
            UpdateKeyboard();
        }
    }

    private void inputText_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
    {
        Dispatcher.BeginInvoke(() =>
        {
            double CurrentInputHeight = MessageText.ActualHeight;

            if (CurrentInputHeight > InputHeight)
            {
                InputScrollViewer.ScrollToVerticalOffset(InputScrollViewer.VerticalOffset + CurrentInputHeight - InputHeight);
            }

            InputHeight = CurrentInputHeight;
        });
    }

    private void UpdateKeyboard()
    {
        (App.Current as App).RootFrame.RenderTransform = new CompositeTransform();

        if (!DeviceStatus.IsKeyboardDeployed)
        {
            InputScrollViewer.Height = RootHeight - (KeyboardHeight + GetClipboardHeight());
        }
        else
        {
            InputScrollViewer.Height = RootHeight;
        }
    }

    private double GetClipboardHeight()
    {
        return (Clipboard.ContainsText()) ? KeyboardClipboardHeight : 0;
    }

    private void MessageText_GotFocus(object sender, System.Windows.RoutedEventArgs e)
    {
        IsInputFocused = true;

        (App.Current as App).RootFrame.RenderTransform = new CompositeTransform();

        UpdateKeyboard();

        Dispatcher.BeginInvoke(() =>
        {
            InputScrollViewer.ScrollToVerticalOffset(InputScrollViewer.VerticalOffset + 338 + GetClipboardHeight());
        });
    }

    private void MessageText_LostFocus(object sender, System.Windows.RoutedEventArgs e)
    {
        IsInputFocused = false;

        InputScrollViewer.Height = RootHeight;
    }
}

first of all this code disables transformation of a Frame (moving up) when TextBox gets focused. Also, this code perform managing of a new free screen height when keyboard is on the screen.

Upvotes: 1

Related Questions