I have a WebView control in a UWP app which loads a Steam broadcast page URL. It works well and I also have a full-screen button to switch the whole window to full screen.
But the Steam broadcast has its own full-screen toggle button, which I would like to click programmatically so that when I press my full screen button, both the window and the broadcast are switched to full screen.
I tried to achieve this via JS injection, but it seems that the webkitRequestFullscreen() function only responds if it's called inside an event handler. How can I inject an event handler into the WebView and call it?
Here's the XAML:
<Grid Background="{StaticResource AppBackgroundColor}">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<WebView Grid.Row="0" x:Name="Browser" />
<CommandBar x:Name="WatchCommandBar" Grid.Row="1" FlowDirection="LeftToRight">
<AppBarButton Icon="FullScreen" Label="Full Screen" Tapped="AppBarFullScreenButton_Tapped" />
</CommandBar>
</Grid>
And here's the C#:
private void AppBarFullScreenButton_Tapped(object sender, RoutedEventArgs e)
{
var view = ApplicationView.GetForCurrentView();
if (view.IsFullScreenMode)
{
view.ExitFullScreenMode();
WatchCommandBar.Visibility = Visibility.Visible;
}
else
{
view.TryEnterFullScreenMode();
WatchCommandBar.Visibility = Visibility.Collapsed;
}
}
I've tried calling these functions when switching to full-screen, but neither of them work:
await Browser.InvokeScriptAsync("eval", new [] { "BroadcastWatch.m_playerUI.ToggleFullscreen();" });
await Browser.InvokeScriptAsync("eval", new [] { "document.getElementById('videoplayer').webkitRequestFullscreen();" });
Apparently it's not possible to inject the full screen request. And I wasn't able to spoof the mouse click event that would theoretically make the request either.
As a workaround I ended up hiding and styling elements that I did not want to see by injecting this script when the WebView has finished loading:
private async void Browser_HideElements(object sender, WebViewDOMContentLoadedEventArgs args)
{
try
{
var elementsToHide = "#global_header, #footer_spacer, #footer_responsive_optin_spacer, #footer, #ChatWindow, .BroadcastInfoWrapper, .fullscreen_button";
// Hide HTML elements and change styles so it looks like the broadcast is in full-screen
// We can't call the broadcast's own toggle full-screen function
var lines = new[]
{
"document.body.style.overflow = 'hidden';",
"document.getElementById('video_wrapper').className += ' fullscreen';",
"document.getElementById('video_content').style.padding = 0;",
"document.getElementById('video_content').style.margin = 0;",
"document.getElementsByClassName('pagecontent')[0].style.padding = 0;",
"var list = document.querySelectorAll('" + elementsToHide + "');",
"for (var i = 0; i < list.length; i++) { var e = list[i]; e.style.display = 'none'; }"
};
await Browser.InvokeScriptAsync("eval", new[] { string.Join(" ", lines) });
}
catch
{
// Ignore script exceptions
}
}
So it looks like the broadcast is running in full screen, and when my own button is pressed, the title bar disappears and it goes into proper full screen.
Related
I have an application where I need to print content that doesn't exist on the page but does exist in the angular template. In the template, I have a div with a click method that will accept an identifier, and then build an iframe based on the content of the id it received. The first snippet is what the clickable element looks like.
<div (click)="printButton('#printThis')" class="menu-item print-menu-item">Race {{ raceSelected?.raceNumber }}</div>
This code snippet is just a basic element with omitted private data. So it will just be an ng-template with an identifier and an iframe that I access from the method pasted below.
<ng-template #printThat>
// content that I will print but not important for this question
</ng-template>
<ng-template #printThis>
// content that I will print but not important for this question
</ng-template>
<iframe #iframe></iframe>
The last code snippet is the method on the component that builds the iframe and brings up the print dialog.
public async printButton(printMode: string): Promise<void> {
let printTemplate;
if (printMode === '#printThat') {
await this.getAllRacesForPrint()
printTemplate = this.printAll;
} else if (printMode === '#printThis') {
printTemplate = this.printSingleRace;
}
const iframe = this.iframe.nativeElement;
this.portalHost = new DomPortalOutlet(
iframe.contentDocument.body,
this.componentFactoryResolver,
this.appRef,
this.injector
);
const portal = new TemplatePortal(
printTemplate,
this.viewContainerRef
);
// Attach portal to host
this.portalHost.attach(portal);
iframe.contentWindow.onafterprint = () => {
iframe.contentDocument.body.innerHTML = '';
};
this._attachStyles(iframe.contentWindow);
iframe.contentWindow.print();
}
These two private functions are invoked in the method above to add the css to the iframe.
private _attachStyles(targetWindow: Window): void {
// Copy styles from parent window
document.querySelectorAll('style').forEach((htmlElement) => {
targetWindow.document.head.appendChild(htmlElement.cloneNode(true));
});
// Copy stylesheet link from parent window.
const styleSheetElement = this._getStyleSheetElement();
targetWindow.document.head.appendChild(styleSheetElement);
}
private _getStyleSheetElement(): HTMLLinkElement {
const styleSheetElement = document.createElement('link');
document.querySelectorAll('link').forEach((htmlElement) => {
if (htmlElement.rel === 'stylesheet') {
const absoluteUrl = new URL(htmlElement.href).href;
styleSheetElement.rel = 'stylesheet';
styleSheetElement.type = 'text/css';
styleSheetElement.href = absoluteUrl;
}
});
return styleSheetElement;
}
This works perfectly on all devices without a problem EXCEPT Android devices. On Android, the print dialog box comes up but the page is blank and has no content. Are there any good solutions for printing for Android that do not require bringing in a package or am I missing something here?
I have an app that has a webview in xamarin and shows a web which has links that are configured in this way:
<a target="_blank" href="http://www.web.com"> http://www.web.com </a>
but they do not work, I suppose this happens because when viewing the web from an app, it is not able to open the link in a new window.
I have also tried window.open without any changes.
How I could configure the link to force open the browser with a new link window.
Thanks.
I think this should happen on Android,if you want to open a new window with browser,you could use SetWebChromeClient method for your WebView in your custom renderer.
create a custom renderer in android project:
[assembly: ExportRenderer(typeof(WebView), typeof(AndroidWebView))]
namespace your namespace
{
class AndroidWebView:WebViewRenderer
{
public AndroidWebView(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
Control.SetWebChromeClient(new MywebviewChrome());
}
private class MywebviewChrome : Android.Webkit.WebChromeClient
{
public override bool OnCreateWindow(Android.Webkit.WebView view, bool isDialog, bool isUserGesture, Message resultMsg)
{
Android.Webkit.WebView.HitTestResult result = view.GetHitTestResult();
string data = result.Extra;
Context context = view.Context;
Intent browserIntent = new Intent(Intent.ActionView,Android.Net.Uri.Parse(data));
context.StartActivity(browserIntent);
return false;
}
}
}
}
in your forms project xaml :
<WebView HeightRequest="800" WidthRequest="600" x:Name="webview" ></WebView>
in your page.xaml.cs:
var htmlSource = new HtmlWebViewSource();
htmlSource.Html = #"<a target='_blank' href='http://www.web.com'> http://www.web.com </a>";
webview.Source = htmlSource;
As you supposed, Xamarin.Forms doesn't support opening new tabs / windows.
BUT the Webview component has an event handler called "Navigating" on which you can subscribe to execute code every time the webview tries to open a new page.
public void NavigatingEventHandler(object sender, WebNavigatingEventArgs args)
{
if (args.Url.StartsWith("https://"))
{
//If you want to open the new window in the OS browser
Device.OpenUri(new Uri(args.Url));
//If you want to open the new window inside the webview
webview.Source = args.Url;
args.Cancel = true;
}
}
XAML:
<WebView x:Name="webview" Navigating="NavigatingEventHandler" />
I am making a Web Browser in C# and displaying custom context menus based on the element right clicked by the user. I am having trouble figuring out which elements should be treated as links?
if (el_tag == "a")
showLinkMenu();
else if (el_tag == "img" && parent_tag == "a")
showAdvancedMenu();
else if (el_tag == "img")
showImgMenu();
Shot1: Google chrome detected this as a link
Shot2: Structure a > div > div > div > div > img
What should I do to detect such links?
You should iterate up the list of parent tags and call it a link if any of them are an <a> with an href attribute.
I recommend implementing your own IContextMenuHandler and use the built in CefMenuCommand's and TypeFlag's to help you. With this, you can add or remove context menu items based on the element that has been right-clicked.
MenuHandler.cs
internal class MenuHandler : IContextMenuHandler
{
private const int SaveImage = 26503;
private const int OpenLinkNewTab = 26501;
public event EventHandler OnSaveImage = delegate { };
void IContextMenuHandler.OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
{
if(parameters.TypeFlags.HasFlag(ContextMenuType.Media) && parameters.HasImageContents)
{
model.AddItem((CefMenuCommand)SaveImage, "Save image");
}
if(!string.IsNullOrEmpty(parameters.UnfilteredLinkUrl))
{
model.AddItem((CefMenuCommand)OpenLinkNewTab, "Open link in new tab");
}
}
bool IContextMenuHandler.OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags)
{
return false;
}
void IContextMenuHandler.OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame)
{
if ((int)commandId == SaveImage)
{
OnSaveImage?.Invoke(this, new ImageSaveEventArgs(parameters.SourceUrl)); //ImageSaveEventArgs is just a class with one property that houses the source url of the image to download.
}
}
bool IContextMenuHandler.RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback)
{
return false;
}
}
Then wherever you create your ChromiumWebBrowser instance, you can do something like this:
ChromiumWebBrowser browser = new ChromiumWebBrowser();
MenuHandler menuHandler = new MenuHandler();
menuHandler.OnSaveImage += Handler_OnSaveImage;
browser.MenuHandler = menuHandler;
private void Handler_OnSaveImage(object sender, EventArgs e)
{
DownloadImage(((ImageSaveEventArgs)e).SourceUrl);
}
Note, this is just an example of using the built-in IContextMenuHandler to obtain what the user has right-clicked, and then handling my own event so I can implemented the required behaviour. In this case, the ability to download a file from the URL.
See MenuHandler.cs on CefSharp's GitHub page for more details and other examples.
I have a very basic application, that shows website content within the WPF app. Everything works fine, except TitleChangedEvent. Here is the code sample (XAML):
<Window xmlns:awe="http://schemas.awesomium.com/winfx" x:Class="ShopChat.Desktop.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:webControls="clr-namespace:System.Web.UI.WebControls;assembly=System.Web"
Title="{Binding ElementName=WebControl, Path=Title}" MinHeight="480" MinWidth="640">
<Grid>
<awe:WebControl x:Name="WebControl"/>
</Grid>
And this is main window code-behind:
public MainWindow()
{
InitializeComponent();
string url = #"http://shopchat.dev";
try
{
url = ConfigurationManager.AppSettings.Get("Url");
}
catch (Exception)
{
}
WebControl.Source = new Uri(url);
WebControl.TitleChanged += WebControl_OnTitleChanged;
this.WindowTitle = "Quickchat";
}
public string WindowTitle
{
get { return (string)GetValue(WindowTitleProperty); }
set { SetValue(WindowTitleProperty, value); }
}
// Using a DependencyProperty as the backing store for WindowTitle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty WindowTitleProperty =
DependencyProperty.Register("WindowTitle", typeof(string), typeof(MainWindow), new PropertyMetadata(null));
private void WebControl_OnTitleChanged(object sender, TitleChangedEventArgs e)
{
this.WindowTitle = e.Title;
}
I've also tried to bind to window title directly using Binding ElementName=WebControl. That didn't help me either.
JavaScript client code is very simple: it changes the document title on timer (setInterval).
What am I doing wrong?
try like this code
public MainWindow()
{
InitializeComponent();
DataContext = this;
MyTitle = "Title";
}
Then you just need in the XAML
Title="{Binding MyTitle}"
Then you don't need the dependency property.
Then I would like to use this INotifyPropertyChanged with a standard property.
The issue was solved. TitleChanged event seemed to be insufficient. I've incorporated the usage of global js object to get the necessary behavior.
I am trying to implement some extra feature onto the wikitude sample project
What I tried is the following: I have button ("Take me there") in Poi detail panel near the "More" button and when user clicked to the "Take me there" button, I want to call a java method to do some calculation.
in my js file on button click method I have:
var currentMarker = World.currentMarker;
var architectSdkUrlm = "architectsdk://navigation?id=" + encodeURIComponent(currentMarker.poiData.id) + "&title=" + encodeURIComponent(currentMarker.poiData.title);
document.location = architectSdkUrlm;
where "Navigation" in the architectSdkUrlm is name of the java class that I created for the calculations. Navigation class is:
public class Navigation extends AbstractArchitectCamActivity {
#Override
public ArchitectView.ArchitectUrlListener getUrlListener() {
return new ArchitectView.ArchitectUrlListener() {
#Override
public boolean urlWasInvoked(String uriString) {
Uri invokedUri = Uri.parse(uriString);
// pressed "Take me there" button on POI-detail panel
if ("navigation".equalsIgnoreCase(invokedUri.getHost())) {
Log.d("title",String.valueOf(invokedUri.getQueryParameter("title")) );
/*final Intent navIntent = new Intent(Navigation.this, navigationIntent.class);
navIntent.putExtra(navigationIntent.EXTRAS_KEY_POI_ID, String.valueOf(invokedUri.getQueryParameter("id")) );
navIntent.putExtra(navigationIntent.EXTRAS_KEY_POI_TITILE, String.valueOf(invokedUri.getQueryParameter("title")) );
Navigation.this.startActivity(navIntent);*/
return true;
}
return true;
}
};
}
}
I want to see if I could call the java file from js by a Log message but I don't get anything.
Do you have any idea about what might be the problem?
Thanks.