Xamarin - How to add multiple hyperlinks - c#

I am trying to add hyperlinks to labels on my Xamarin Forms page. For some reason only the first hyperlink function call works, and subsequent calls to my function hyperlink() seem to be ignored. I am unsure why it only works once.
I think it is because tapGestureRecognizer() can only handle one thing at once, but honestly I am not sure. How can I add hyperlinks to multiple labels on my Xamarin app?
Code for reference:
private void hyperlinkLabel(Uri uri, Label label)
{
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (s, e) => {
Device.OpenUri((uri));
};
label.GestureRecognizers.Add(tapGestureRecognizer);
}
And then I call it as such:
hyperlinkLabel(new Uri("https://www.mycoolwebsite.com", NewAccountLabel),
hyperlinkLabel(new Uri("https://www.myothercoolwebsite.com"), RegisterLabel);

Related

UWP Webview - Trigger event when Tab Title changes (DocumentTitle)

I have switched to using UWP (Windows.UI.Xaml.Controls) but now have lost the ability to detect when the webview tab text changes.
Example of text
I previously used an OnTitleChanged event but I cannot find an alternative for this in UWP.
I can see the 'DocumentTitle' in the webView, and it is always updating when necessary.
WebView wv_Browser = new WebView();
string Example = wv_Browser.DocumentTitle;
I have tried every built in event in the WebView but not one of them seem to fire when this updates.
Can anyone suggest and alternative way to trigger an event or monitor this value?
Unfortunately, there is no OnTitleChanged event in UWP WebView, but you could inject eval function into html page as Mehrzad Chehraz said. Please refer the following detail code.
Make eval function for detecting title changed.
string functionString = " new MutationObserver(function () { window.external.notify(document.title); }).observe(document.querySelector('title'), { childList: true })";
Call InvokeScriptAsync method to inject eval. (call below when webview navigation completed)
await MyWebView.InvokeScriptAsync("eval", new string[] { functionString });
listen value changed in ScriptNotify event handler
private void MyWebView_ScriptNotify(object sender, NotifyEventArgs e)
{
var title = e.Value;
}
For more info please check UWP WebView tutorial.
What I ended up using
private void CreateWebView()
{
WebView webview = new WebView();
webview .DOMContentLoaded += async (s, e) =>
{
WebView_DOMContentLoaded(webview);
};
webview .ScriptNotify += (s, e) => {
. . . Do whatever
};
}
private async void WebView_DOMContentLoaded(WebView sender)
{
string function = #"new MutationObserver(function(mutations){window.external.notify(document.getElementById('pageTitle').innerHTML)}).observe(document.getElementById('pageTitle'),{attributes:true,childList:true,subtree:true});";
await sender.InvokeScriptAsync("eval", new string[] { function });
}

How to have C# Webbrowser handle webpage login popup for webscraping

I'm trying to programmatically login to a site like espn.com. The way the site is setup is once I click on the Log In button located on the homepage, a Log In popup window is displayed in the middle of the screen with the background slightly tinted. My goal is to programmatically obtain that popup box, supply the username and password, and submit it -- hoping that a cookie is returned to me to use as authentication. However, because Javascript is used to display the form, I don't necessarily have easy access to the form's input tags via the main page's HTML.
I've tried researching various solutions such as HttpClient and HttpWebRequest, however it appears that a Webbrowser is best since the login form is displayed using Javascript. Since I don't necessarily have easy access to the form's input tags, a Webbrowser seems the best alternative to capturing the popup's input elements.
class ESPNLoginViewModel
{
private string Url;
private WebBrowser webBrowser1 = new WebBrowser();
private SHDocVw.WebBrowser_V1 Web_V1;
public ESPNLoginViewModel()
{
Initialize();
}
private void Initialize()
{
Url = "http://www.espn.com/";
Login();
}
private void Login()
{
webBrowser1.Navigate(Url);
webBrowser1.DocumentCompleted +=
new WebBrowserDocumentCompletedEventHandler(webpage_DocumentCompleted);
Web_V1 = (SHDocVw.WebBrowser_V1)this.webBrowser1.ActiveXInstance;
Web_V1.NewWindow += new SHDocVw.DWebBrowserEvents_NewWindowEventHandler(Web_V1_NewWindow);
}
//This never gets executed
private void Web_V1_NewWindow(string URL, int Flags, string TargetFrameName, ref object PostData, string Headers, ref bool Processed)
{
//I'll start determing how to code this once I'm able to get this invoked
}
private void webpage_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
HtmlElement loginButton = webBrowser1.Document.GetElementsByTagName("button")[5];
loginButton.InvokeMember("click");
//I've also tried the below InvokeScript method to see if executing the javascript that
//is called when the Log In button is clicked, however Web_V1_NewWindow still wasn't called.
//webBrowser1.Document.InvokeScript("buildOverlay");
}
}
I'm expecting the Web_V1_NewWindow handler to be invoked when the InvokeMember("click") method is called. However, code execution only runs through the webpage_DocumentCompleted handler without any calls to Web_V1_NewWindow. It might be that I need to use a different method than InvokeMember("click") to invoke the Log In button's click event handler. Or I might need to try something completely different altogether. I'm not 100% sure the Web_V1.NewWindow is the correct approach for my needs, but I've seen NewWindow used often when dealing with popups so I figured I should give it a try.
Any help would be greatly appreciated as I've spent a significant amount of time on this.
I know it is the late answer. But it will help someone else.
You can extract the value from FRAME element by following
// Get frame using frame ID
HtmlWindow frameWindow = (from HtmlWindow win
in WbBrowser.Document.Window.Frames select win)
.Where(x => string.Compare(x.WindowFrameElement.Id, "frm1") == 0)
.FirstOrDefault();
// Get first frame textbox with ID
HtmlElement txtElement = (from HtmlElement element
in frameWindow.Document.GetElementsByTagName("input")
select element)
.Where(x => string.Compare(x.Id, "txt") == 0).FirstOrDefault();
// Check txtElement is nul or not
if(txtElement != null)
{
Label1.Text = txtElement.GetAttribute("value");
}
For more details check
this article

Xamarin.Forms (iOS) - How to change the text of the SearchBar "Cancel" button?

We are internationalizing a mobile app my team is developing, and I noticed the "Cancel" button in the SearchBar is not translating (everything else is translating just fine) - it always says "Cancel" in English no matter what language I change the iPad simulator to. If I can set the text myself, then I can ensure it's internationalized properly. So...
How do I change the "Cancel" button text on a Xamarin.Forms SearchBar? I've tried a custom renderer, but am having difficulty targeting the Cancel button subview. This answer seems to do a great job of explaining how to do that in Objective C, but I'm having trouble translating it into C# within the Xamarin.Forms framework.
Create a new custom renderer for iOS. Something like CustomSearchBarRenderer and subclass the Xamarin's original SearchBarRenderer
public class CustomSearchBarRenderer : SearchBarRenderer { }
I'd probably try to override OnElementChanged method and set the custom title after the base class has done all the work. Like this:
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
var cancelButton = Control.FindDescendantView<UIButton>();
cancelButton.Title = "Whatever";
}
Edit: Please be aware that the cancelButton might get recreated at some point so you might have to set the title at some other point too. However, this should be a good starting point.
You can also take a look at the current SearchBarRenderer implementation here.
I combined the answers and set the button text on each event which is needed. I also fixed the showing and hiding of the cancel button (see here):
using FestivalHolledauApp.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using System.Linq;
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
namespace FestivalHolledauApp.iOS
{
public class CustomSearchBarRenderer : SearchBarRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
// Fixing Cancel button
if (e.NewElement != null)
{
this.Control.TextChanged += (s, ea) =>
{
this.Control.ShowsCancelButton = true;
SetCancelButtonText();
};
this.Control.OnEditingStarted += (s, ea) => //when control receives focus
{
this.Control.ShowsCancelButton = true;
SetCancelButtonText();
};
this.Control.OnEditingStopped += (s, ea) => //when control looses focus
{
this.Control.ShowsCancelButton = false;
};
}
}
private void SetCancelButtonText()
{
var cancelButton = Control.Descendants().OfType<UIButton>().FirstOrDefault();
if (cancelButton != null)
{
cancelButton.SetTitle("Schließen", UIControlState.Normal);
}
}
}
}
I've come over the same issue with ...
the "Cancel" button in a UISearchBar
the "Back" button to prior pages without title
context menu actions like "Cut", "Copy" and "Paste"
the "Done" buttons on date pickers and multiline editors
Everything else I got localized with resource based localization.
There are a lot of hacks out there finding the native buttons and setting texts manually but I knew this was not the way it was meant to be. So I digged deeper and found the following excellent article from Gerald Versluis:
https://blog.verslu.is/xamarin/xamarin-forms-xamarin/localization-default-ios-controls/
For me, this just meant to add the following block to my Info.plist:
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>de</string>
</array>
With this, iOS automatically translated all the UI elements for the current system culture (without adding these text to any resource files).

How to navigate from ContentPage to ContentPage?

I am trying to navigate from one ContentPage to another.
In my WelcomePage, I have a button which should direct me to the other page with following content
_loginButton.Clicked += (s, e) =>
{
if (OnLoginEnter != null) OnLoginEnter();
};
Now, in my PageManager class, where I try to manage all pages, I have something like this
public class PageManager : Page
{
#region Fields
public static WelcomePage WelcomePage = new WelcomePage();
public static LoginPage LoginPage = new LoginPage();
#endregion
public Page GetStarted()
{
return WelcomePage.Generate();
}
}
And finally, in MainActivity.cs (Android project), I am trying to manage all this with the following:
//I have to use a method for that for some reason. Can't call .Generate() directly.
SetPage(PageManager.GetStarted());
PageManager.WelcomePage.OnLoginEnter += () =>
{
SetPage(PageManager.LoginPage.Generate());
};
I find this to be very confusing and unproductive.
All I want is a way to navigate from one ContentPage to another.
I share your frustration - however, I understand Xamarin are working on improving the page navigation.
For now, you have a few options.
If you are happy to have a flow, so no need to replace the whole page but be able to go back, use a NavigationPage or the PageManager echyzero mentioned.
If you are going to have an options page, use a MasterDetailPage and replace the detail.
Alternatively, create an interface which has a method called SetRootPage and implement it for both Android and iOS. Pass the instance of the interface in to your App.Run(IPageLoader) on startup and you can then call the SetRootPage from the App class to replace the Root. I did report a bug with this a while ago, which may have been fixed now. In the meantime, my workaround was to use the CarouselPage, with only a single page on the Carousel, which I replace when needed - it actually works quite well, if a bit hacky.
Answer taken from Xamarin.Forms documation here
listView.ItemSelected += async (sender, e) => {
var todoItem = (TodoItem)e.SelectedItem;
var todoPage = new TodoItemPage(todoItem); // so the new page shows correct data
await Navigation.PushAsync(todoPage);
};
So for you, it would probably look something like this:
PageManager.WelcomePage.OnLoginEnter += () =>
{
await Navigation.PushAsync(PageManager.LoginPage.Generate());
};
For iOS, you will need to do this through a NavigationPage as shown below (example is inside the AppDelegate.cs file)
window.RootViewController = new NavigationPage (new MyLoginForm ()).CreateViewController ();
After that you can call await Navigation.PushAsync('any page you want') inside any ContentPage in your application.

Button Screen Change in Android Mono using delegates

The following code is from the C# portion of my Android Mono application. It is going to eventually be the GUI for a multimeter simulator, but right now just displays text. It is rather straight forward:
-Click one of the buttons to go to that meter (voltmeter, ammeter, ohmmeter)
-Click the "re-scan" button and a TextView tells you how many times you clicked that button.
-Click one of the other meter buttons or the home button to switch views
That much is working flawlessly. Unfortunately, once I switch views, the buttons cease to work. Below is the code for the Ohm button and the Amp button. The Ohm button is the 'complete' one that brings up views of all of the other screens. For testing purposes, I was going to the amp screen but when I go there, its re-scan button does nothing. None of the buttons do anything.
I am fairly certain that the issue is my use of the delegate commands, but none of my research has led me in any way towards a solution.
I can provide more of the main code and the XML code if needed.
ampButton.Click += delegate
{
SetContentView(Resource.Layout.AmpScreen);
Button ampButtonData = FindViewById<Button>(Resource.Id.CurrentButtonamp);
TextView ampData = FindViewById<TextView>(Resource.Id.ampdata);
ampButtonData.Click += delegate
{
ampData.Text = string.Format("{0} clicks!", count2++);
};
Button amp2volt = FindViewById<Button>(Resource.Id.Amp2VoltButton);
Button amp2ohm = FindViewById<Button>(Resource.Id.Amp2OhmButton);
Button amp2home = FindViewById<Button>(Resource.Id.Amp2HomeButton);
};
ohmButton.Click += delegate
{
SetContentView(Resource.Layout.OhmScreen);
Button ohmButtonData = FindViewById<Button>(Resource.Id.CurrentButtonohm);
TextView ohmData = FindViewById<TextView>(Resource.Id.ohmdata);
ohmButtonData.Click += delegate
{
ohmData.Text = string.Format("{0} clicks!", count3++);
};
Button ohm2amp = FindViewById<Button>(Resource.Id.Ohm2AmpButton);
Button ohm2volt = FindViewById<Button>(Resource.Id.Ohm2VoltButton);
Button ohm2home = FindViewById<Button>(Resource.Id.Ohm2HomeButton);
ohm2amp.Click += delegate
{
SetContentView(Resource.Layout.AmpScreen);
};
ohm2volt.Click += delegate
{
SetContentView(Resource.Layout.VoltScreen);
};
ohm2home.Click += delegate
{
SetContentView(Resource.Layout.Main);
};
};
I think your problem is that you are replacing the entire view each time - so the button instances are changing.
What happens inside SetContentView is that the InflatorService gets asked to create a brand new set of UI objects based on the passed in XML, the existing UI is wiped clean and then those new UI objects are put in their place.
It doesn't matter if the new UI objects happen to have the same resource identifiers as the old objects - they are still separate instances.
If you want to continue using your current approach, then you need to rewire all your events after each SetContentView - e.g.
ohm2amp.Click += delegate
{
SetContentView(Resource.Layout.AmpScreen);
RewireEvents();
};
with
private void RewireEvents()
{
var ohm2home = FindViewById<Button>(Resource.Id.ohm2home);
ohm2home.Click += { /* todo */ };
// etc
}
alternatively, maybe consider a different UI:
e.g. you could change the Visibility on different child layouts rather than calling SetContentView to replace everything
e.g. or you could use multiple activities (or tabs) instead of a single activity
Hope that helps

Categories

Resources