19

Problem

I have to show a WebView inside a ScrollViewer in Windows Phone 8.1 application, with the following requirements:

  1. WebView height should be adjusted based on its content.
  2. WebView vertical scroll should be handled by an outer ScrollViewer.
  3. WebView should handle horizontal scroll, scale (pinch-zoom), text selection (with the default copy button) and links navigation.

On the picture below is my mocked layout (to the left) and the best example of similar functionality - that would be a built-in mail application (to the right)

My page layout (to the left) and example of similar functionality (to the right)

Sample XAML layout:

<ScrollViewer>
    <Grid Margin="12">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <TextBlock Text="My content" />
        </Grid>
        <WebView Grid.Row="1" x:Name="WebViewComponent"></WebView>
    </Grid>
</ScrollViewer>

What did I try

Measure HTML content and adjusting the WebView height - This part worked and with several adjustments I was able to set the correct height to the WebView element.

Subscribing to a Border element inside a WebView - Did not work. The problem here is that in Windows Phone 8.1 it seems that a WebView component does not have visual children (at least not DependencyObject's)

As well I've tried playing around with ManupulationMode and IsHitTestVisible properties with no success.

UPDATE Added text selection & copy button to required WebView functionality. Somehow missed it in the original question content.

Community
  • 1
  • 1
danyloid
  • 1,677
  • 3
  • 21
  • 47
  • Just out of curiousity, have you tried to just inject some css to disabled the overflow:scroll so that your scrollviewer actually get's to do its job. Something like [this](http://www.c-sharpcorner.com/UploadFile/269510/disable-webview-scrolling-in-windows-store-apps/)? I wish I had more free time at the moment to go play with it, seems like it would be a common issue anyway. – Chris W. Jun 11 '15 at 19:50
  • @Chris I did. `WebView` captures the pointer interactions totally and reacts with something like "compression" scroll (not quite sure if that would be a proper term - the scrolling goes very slow and reverts once finger is released). – danyloid Jun 11 '15 at 21:39
  • 2
    sort of "one scroll to scroll them all" question :) – danyloid Jun 18 '15 at 09:10

3 Answers3

5

I've been facing this problem, and i got some workaround partially (excluding the text selection).

Basically to be able to use the scrollviewer containing the webview, you need something to be dragged, so I use a rectangle with the height and width same as the webview.

<ScrollViewer Name="ScrollViewer">
    <Grid x:Name="LayoutRoot" Height="Auto">
        <Grid.RowDefinitions>
            <RowDefinition Height="200"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid Name="GridHeader">
            <Image Stretch="UniformToFill" Source="ms-appx:///Assets/img_default_header.jpg" />
         </Grid>                
        <Grid Grid.Row="1" x:Name="GridNews" Margin="12,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <WebView Margin="-6,12,0,-6" Grid.Row="0" x:Name="WebviewContent" VerticalAlignment="Top" DefaultBackgroundColor="Transparent" /> 
            <Rectangle Margin="0,12,0,0" Height="{Binding Height, ElementName=WebviewContent}" Name="RecWeb" VerticalAlignment="Top" Fill ="#00000000" Tapped="RecWeb_Tapped" />
        </Grid>
        <Grid Name="GridRelatedNews" Grid.Row="2" Margin="12,0">
            <ListView ItemsSource="{Binding NewsDetail.RelatedStory}" ScrollViewer.VerticalScrollBarVisibility="Hidden">  
            </ListView>
        </Grid>
 </ScrollViewer>

Now we need to inject some css and javascript to make the webview height adapting to the content (I found some code example on another stackoverflow thread, forget to track where it comes from)

string htmlFragment = @"
                                    <html>
                                        <head>
                                        <meta name=""viewport"" content=""width="+width+@", initial-scale=1, user-scalable=no"" />
                                            <script type=""text/javascript"">
                                                function loadLink(x,y){
                                                window.external.notify('x='+x+'y='+y);
                                                var el = document.elementFromPoint(x, y);
                                                el.click();};                                                   
                                            </script>
                                        <style>
                                        " + CSS + @"
                                        </style>
                                        </head>
                                        <body onLoad=""window.external.notify('rendered_height='+document.getElementById('content').offsetHeight);"">
                                            <div id='content' style=""font-size:16px; color:#222222;"">" + original +
                                          @"</div>
                                        </body>
                                    </html>";

Then added webview webscript notify event to determine the rendered height of the webview (and rectangle):

void WebviewContent_ScriptNotify(object sender, NotifyEventArgs e)
{
    if (e.Value.Contains("rendered_height"))
    {
        if (valuePair != null && valuePair[0] == "rendered_height")
        {
            double renderedHeight = double.Parse(valuePair[1]);                 
            WebviewContent.Height = renderedHeight;

        }
    }
 }

I manage to allow this solution to be able to click some link in the content using this method (the loadLink javascript method):

private async void RecWeb_Tapped(object sender, TappedRoutedEventArgs e)
    {
        Point p = e.GetPosition(WebviewContent);
        int x = Convert.ToInt32(p.X);
        int y = Convert.ToInt32(p.Y);
        string[] size = { x.ToString(), y.ToString() };
        await WebviewContent.InvokeScriptAsync("loadLink",size);
    }

Maybe you can take it from there to add an event handler for your selecting text problem. The point using this solution is also to find an equivalent event handler in javascript.

tdmanutd
  • 301
  • 1
  • 6
  • Thank you. I guess that this is the closest one. – danyloid Jun 18 '15 at 09:02
  • Here is a complete implementation of @tdmanutd's solution https://gist.github.com/Ouadie/78fc85965ebaf49e06d4 – Ouadie Dec 08 '15 at 15:27
  • I'm also facing same problem. I ll try this solution. Thanks for posting.. – Archana Aug 05 '16 at 14:44
  • @Ouadie LoadLink function is not working for me. Link is not getting clicked. Do you have any idea? – Archana Aug 06 '16 at 07:04
  • @LovetoCode first you've to check that you've called correctly `await this.WebviewContent.InvokeScriptAsync("loadLink", size);` then in javascript check with `alert` or `console.log` that you've correctly received a `loadLink` call. – Ouadie Aug 08 '16 at 08:48
  • Yes it's getting called. But problem is x,y position is not proper. I tried clicking almost everywhere inside webview, in some point link got opened but the position where link click triggered there was not link. I meant to say position which we send from tapped event not proper – Archana Aug 08 '16 at 15:47
  • @LovetoCode Could U plz post the webview part of your project? – Ouadie Aug 23 '16 at 16:14
  • Actually I had missed meta tag. So I was getting problem. Now it's fixed. Thank you – Archana Aug 23 '16 at 18:01
1

I checked it out and tried a few things, but couldn't make it work the way you want it. It might be easier to come up with a different design. Basically, something that does not include an outer ScrollViewer.

  • One option (which might be a bit hacky, haven't tested it) is to inject your content into the page, through some JS, and use only a WebView.

  • If your content is small, you could just leave it on the top of the page, remove the ScrollViewer and let the WebView handle everything. The WebView will be smaller, but it will work just fine.

These are just some examples of different designs. If you tell me why they wouldn't work, or if you give me more info about your actual problem, I might be able to come up with something more appropriate.

yasen
  • 3,580
  • 13
  • 25
  • The problem is that the content shown on top of the screen might be a complex, containing images, buttons and so on. Sorry it seems that I somehow missed that part in the original question post. I realize that that would be possible to include everything in a `WebView`, but it will require a big effort. Anyway thanks for the suggestion – danyloid Jun 18 '15 at 08:56
  • 1
    @danyloid Yeah, complex content makes it... complex :) Two more ideas: 1) use Pivot, the content on one page, the webview on another; 2) something like vertical pivot - you see the webview on the bottom and once you tap on it it goes almost full screen and there's only something small at the top from which you can return to the other content. – yasen Jun 18 '15 at 09:02
  • thanks, I have not thought about the second approach. It might be not a best solution, though a good option to consider. – danyloid Jun 18 '15 at 09:07
-1

Have you try setting the Height property of WebView to Auto?

<ScrollViewer x:Name="scrollViewer" >
    <Grid Margin="12">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBlock Text="My content" />
        <Grid Background="Transparent"
              Margin="0"
              Grid.Row="1">
            <WebView ScrollViewer.ZoomMode="Enabled"
                     x:Name="WebViewComponent"  
                     Margin="0" 
                     Height="Auto" />
        </Grid>
    </Grid>
</ScrollViewer>
Christian Amado
  • 946
  • 1
  • 7
  • 31
  • I believe that Auto is default value for `WebView.Height` property , though now I did. No effect at all. As well the `ScrollViewer.ZoomMode="Enabled"` does not affect the internal browser scroll. – danyloid Jun 11 '15 at 08:14