Bind text with links to RichTextBox - windows-phone-7

I need to bind text which may contain hyperlinks to RichTextBox so it could show text as normal text and links as hyperlinks.
For example I have following text:
Join us on social networks
http://www.facebook.com/
I want that links in a text be hyperlinks so the result in RichTextBox would be like this:
Join us on social networks
http://www.facebook.com/

I implemented what I need
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Text.RegularExpressions;
using System.Windows.Media;
namespace NazarGrynko.UI.Controls
{
public class MyRichTextBox : RichTextBox
{
private const string UrlPattern = #"(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,#?^=%&:/~\+#]*[\w\-\#?^=%&/~\+#])?";
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof (string), typeof(MyRichTextBox ), new PropertyMetadata(default(string), TextPropertyChanged));
public string Text
{
get { return (string) GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private static void TextPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var richTextBox = (MyRichTextBox)dependencyObject;
var text = (string) dependencyPropertyChangedEventArgs.NewValue;
int textPosition = 0;
var paragraph = new Paragraph();
var urlMatches = Regex.Matches(text, UrlPattern);
foreach (Match urlMatch in urlMatches)
{
int urlOccurrenceIndex = text.IndexOf(urlMatch.Value, textPosition, StringComparison.Ordinal);
if (urlOccurrenceIndex == 0)
{
var hyperlink = new Hyperlink
{
NavigateUri = new Uri(urlMatch.Value),
TargetName = "_blank",
Foreground = Application.Current.Resources["PhoneAccentBrush"] as Brush
};
hyperlink.Inlines.Add(urlMatch.Value);
paragraph.Inlines.Add(hyperlink);
textPosition += urlMatch.Value.Length;
}
else
{
paragraph.Inlines.Add(text.Substring(textPosition, urlOccurrenceIndex - textPosition));
textPosition += urlOccurrenceIndex - textPosition;
var hyperlink = new Hyperlink
{
NavigateUri = new Uri(urlMatch.Value),
TargetName = "_blank",
Foreground = Application.Current.Resources["PhoneAccentBrush"] as Brush
};
hyperlink.Inlines.Add(urlMatch.Value);
paragraph.Inlines.Add(hyperlink);
textPosition += urlMatch.Value.Length;
}
}
if (urlMatches.Count == 0)
{
paragraph.Inlines.Add(text);
}
richTextBox.Blocks.Add(paragraph);
}
}
}
Using example:
<MyRichTextBox Text="{Binding Message}"/>

Parse the Hyperlink, and create following structure (With C#, of course):
<RichTextBlock>
<Run>Hello World!</Run>
<Hyperlink NavigateUri="http://www.stackoverflow.com">http://www.stackoverflow.com</Hyperlink>

Thanks for the solution!
One minor modification I made was right at the end, I replaced the check on the count, with a line that just adds a substring of the full text, this way it does not truncate everything after the last URL, all text is retained.
paragraph.Inlines.Add(text.Substring(textPosition, text.Length - textPosition));
//if (urlMatches.Count == 0)
//{
// paragraph.Inlines.Add(text);
//}

Related

Xamarin bug happens when linker is set to "Link All". Can't use DependencyService

Right now, I have to have my linker set to "Link All" in order to submit to the App Store because of the deprecated UIWebView. While doing this, I had to add [Preserve(AllMembers = true)] to my DataStores to make them work this way, but then I ran into this issue.
The following line will return null:
var dp = DependencyService.Get<ITextMeter>();
After looking into the solution, it seemed the best answer would be to add the following line into the AppDelegate:
DependencyService.Register<ITextMeter, TextMeterImplementation>();
Once I did that, I started receiving this exception:
DependencyService: System.MissingMethodException: Default constructor not found for [Interface]
https://forums.xamarin.com/discussion/71072/dependencyservice-system-missingmethodexception-default-constructor-not-found-for-interface
I just want to find a working solution that will allow everything to work with the linker set to "Link All". Thanks in advance.
ITextMeter:
using System;
using Xamarin.Forms.Internals;
namespace RedSwipe.Services
{
public interface ITextMeter
{
double MeasureTextSize(string text, double width, double fontSize, string fontName = null);
}
}
TextMeterImplementation:
using System.Drawing;
using Foundation;
using RedSwipe.iOS.Services;
using RedSwipe.Services;
using UIKit;
[assembly: Xamarin.Forms.Dependency(typeof(TextMeterImplementation))]
namespace RedSwipe.iOS.Services
{
public class TextMeterImplementation : ITextMeter
{
public double MeasureTextSize(string text, double width, double fontSize, string fontName = null)
{
var nsText = new NSString(text);
var boundSize = new SizeF((float)width, float.MaxValue);
var options = NSStringDrawingOptions.UsesFontLeading | NSStringDrawingOptions.UsesLineFragmentOrigin;
if (fontName == null)
{
fontName = "HelveticaNeue";
}
var attributes = new UIStringAttributes
{
Font = UIFont.FromName(fontName, (float)fontSize)
};
var sizeF = nsText.GetBoundingRect(boundSize, options, attributes, null).Size;
//return new Xamarin.Forms.Size((double)sizeF.Width, (double)sizeF.Height);
return (double)sizeF.Height;
}
}
}
Add this [Preserve (AllMembers = true)] in your TextMeterImplementation before class implementation.
Your code would be like:
using System.Drawing;
using Foundation;
using RedSwipe.iOS.Services;
using RedSwipe.Services;
using UIKit;
using Xamarin.Forms.Internals; // Add This import
[assembly: Xamarin.Forms.Dependency(typeof(TextMeterImplementation))]
namespace RedSwipe.iOS.Services
{
[Preserve (AllMembers = true)]
public class TextMeterImplementation : ITextMeter
{
public double MeasureTextSize(string text, double width, double fontSize, string fontName = null)
{
var nsText = new NSString(text);
var boundSize = new SizeF((float)width, float.MaxValue);
var options = NSStringDrawingOptions.UsesFontLeading | NSStringDrawingOptions.UsesLineFragmentOrigin;
if (fontName == null)
{
fontName = "HelveticaNeue";
}
var attributes = new UIStringAttributes
{
Font = UIFont.FromName(fontName, (float)fontSize)
};
var sizeF = nsText.GetBoundingRect(boundSize, options, attributes, null).Size;
//return new Xamarin.Forms.Size((double)sizeF.Width, (double)sizeF.Height);
return (double)sizeF.Height;
}
}
}

How to select multiple picture from gallery using GMImagePicker in xamarin IOS?

I am following this blog for selecting multiple pictures from the gallery. For IOS I am Using GMImagePicker for selecting multiple pictures from the gallery.(In the blog suggesting elcimagepicker, but that is not available in Nuget Store now)
I go through the GMImagePicker usage part but didn't find how to add the selected images to List and pass that value in MessagingCenter(like the android implementation). In that usage part only telling about the picker settings. Anyone please give me any sample code for doing this feature?
Hi Lucas Zhang - MSFT, I tried your code but one question. Here you are passing only one file path through the messagecenter, so should I use a List for sending multiple file paths?
I am passing the picture paths as a string List from android. Please have a look at the android part code added below. Should I do like this in IOS?
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (resultCode == Result.Ok)
{
List<string> images = new List<string>();
if (data != null)
{
ClipData clipData = data.ClipData;
if (clipData != null)
{
for (int i = 0; i < clipData.ItemCount; i++)
{
ClipData.Item item = clipData.GetItemAt(i);
Android.Net.Uri uri = item.Uri;
var path = GetRealPathFromURI(uri);
if (path != null)
{
//Rotate Image
var imageRotated = ImageHelpers.RotateImage(path);
var newPath = ImageHelpers.SaveFile("TmpPictures", imageRotated, System.DateTime.Now.ToString("yyyyMMddHHmmssfff"));
images.Add(newPath);
}
}
}
else
{
Android.Net.Uri uri = data.Data;
var path = GetRealPathFromURI(uri);
if (path != null)
{
//Rotate Image
var imageRotated = ImageHelpers.RotateImage(path);
var newPath = ImageHelpers.SaveFile("TmpPictures", imageRotated, System.DateTime.Now.ToString("yyyyMMddHHmmssfff"));
images.Add(newPath);
}
}
MessagingCenter.Send<App, List<string>>((App)Xamarin.Forms.Application.Current, "ImagesSelected", images);
}
}
}
Also, I am getting an error, screenshot adding below:
GMImagePicker will return a list contains PHAsset .So you could firstly get the filePath of the images and then pass them to forms by using MessagingCenter and DependencyService.Refer the following code.
in Forms, create an interface
using System;
namespace app1
{
public interface ISelectMultiImage
{
void SelectedImage();
}
}
in iOS project
using System;
using Xamarin.Forms;
using UIKit;
using GMImagePicker;
using Photos;
using Foundation;
[assembly:Dependency(typeof(SelectMultiImageImplementation))]
namespace xxx.iOS
{
public class SelectMultiImageImplementation:ISelectMultiImage
{
public SelectMultiImageImplementation()
{
}
string Save(UIImage image, string name)
{
var documentsDirectory = Environment.GetFolderPath
(Environment.SpecialFolder.Personal);
string jpgFilename = System.IO.Path.Combine(documentsDirectory, name); // hardcoded filename, overwritten each time
NSData imgData = image.AsJPEG();
if (imgData.Save(jpgFilename, false, out NSError err))
{
return jpgFilename;
}
else
{
Console.WriteLine("NOT saved as " + jpgFilename + " because" + err.LocalizedDescription);
return null;
}
}
public void SelectedImage()
{
var picker = new GMImagePickerController();
picker.FinishedPickingAssets += (s, args) => {
PHAsset[] assets = args.Assets;
foreach (PHAsset asset in assets)
{
PHImageManager.DefaultManager.RequestImageData(asset, null, (NSData data, NSString dataUti, UIImageOrientation orientation, NSDictionary info) =>
{
NSUrl url = NSUrl.FromString(info.ValueForKey(new NSString("PHImageFileURLKey")).ToString());
string[] strs = url.Split("/");
UIImage image = UIImage.LoadFromData(data);
string file = Save(UIImage.LoadFromData(data), strs[strs.Length - 1]);
MessagingCenter.Send<Object, string>(this, "ImagesSelected", file);
}
);
}
};
UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(picker, true,null);
}
}
}
in your contentPages
...
List<string> selectedImages;
...
public MyPage()
{
selectedImages = new List<string>();
InitializeComponent();
MessagingCenter.Subscribe<Object,string>(this, "ImagesSelected",(object arg1,string arg2) =>
{
string source = arg2;
selectedImages.Add(source);
});
}
If you want to select the images ,call the method
DependencyService.Get<ISelectMultiImage>().SelectedImage();

How to load an Image from Assets in Xamarin.Forms on Android?

I am using the following code:
var baseUrl = DependencyService.Get<IBaseUrl> ().Get ();
var backgroundImage = new Image () {
Source = FileImageSource.FromFile (
System.IO.Path.Combine (baseUrl, "Content", "images", "background-2.jpg")
)
};
Where the DependencyServices for iOS and Androids are as below:
// iOS
[assembly: Xamarin.Forms.Dependency (typeof (BaseUrl_iOS))]
namespace TuneProtectApp.iOS
{
public class BaseUrl_iOS : IBaseUrl
{
public BaseUrl_iOS () { }
public string Get ()
{
return NSBundle.MainBundle.BundlePath;
}
}
}
// Android
[assembly: Xamarin.Forms.Dependency (typeof (BaseUrl_Droid))]
namespace TuneProtectApp.Droid
{
public class BaseUrl_Droid : IBaseUrl
{
public BaseUrl_Droid () {}
public string Get ()
{
return "file:///android_asset/";
}
}
}
The backgroundImage loads fine on iOS but not on Android. How to load an Image from Assets in Xamarin.Forms on Android?
In my Xamarin.forms (shared) app I have a registration-page, where the user also have to select an image for his avatar. Based on the sex of the user, I show a male or a female symbol-image as default (the user then can select another, if he want to do).
I have implemented it as follows:
First created a sub-directory \Embedded for all projects (iOS, Android and WP) (directly in the project-root of each project-type).
Then added the two .jpg’s to the new directories in all projects.
In my app I have a global variable (GV.cEmbeddedAblage)
This is filled in startup-code:
string cNameSpace = "";
switch (Device.OS)
{
case TargetPlatform.WinPhone:
cNameSpace = "MatrixGuide.WinPhone";
break;
case TargetPlatform.iOS:
cNameSpace = "MatrixGuide.iOS";
break;
case TargetPlatform.Android:
cNameSpace = "MatrixGuide.Droid";
break;
//
}
GV.cEmbeddedAblage = cNameSpace + ".Embedded.";
Further, I create a global byte-array for the images (example to male):
static Byte[] _SymbolMann;
public static Byte[] ByteSymbolMann
{
get { return _SymbolMann; }
set { _SymbolMann = value; }
}
I then easily can access the images from shared code (on the registration-page) with (e.g.):
Generate the path, load image in byte-array (if not already loaded):
string cFilename = "";
if (GV.ByteSymbolMann == null) // not yet loaded - only load one time
{
cFilename = GV.cEmbeddedAblage + "SymbolMann.jpg";
var assembly = this.GetType().GetTypeInfo().Assembly;
byte[] buffer;
using (Stream s = assembly.GetManifestResourceStream(cFilename))
{
long length = s.Length;
buffer = new byte[length];
s.Read(buffer, 0, (int)length);
GV.ByteSymbolMann = buffer;
}
}
Fill another byte.array (with selected (loaded) male- / female- image):
AvatarErfassung = GV.ByteSymbolMann;
create the image on the page:
var Avatar = new Image { HeightRequest = 70, WidthRequest = 70, HorizontalOptions = LayoutOptions.Start };
Overtake the selected image as Source to the Image:
Avatar.Source = ImageSource.FromStream(() => new MemoryStream(AvatarErfassung));
You should be able to do it similar...

Can one use system icons on tabs in Xamarin Forms?

Is there a way to specify a "system" icon to be displayed on a tab when using Xamarin Forms? I would like to use icons such as Favourites, Bookmark, History etc but I do not want to supply all my own images for the various platforms.
Using Xamarin.iOS one can use this syntax:
tab1.TabBarItem = new UITabBarItem (UITabBarSystemItem.Favorites, 0);
I can however not find how to do this in the cross-platform Xamarin.Forms project.
This is my current code:
var profilePage = new ContentPage {
Title = "Profile",
//This requires my own images to be added to the project whereas
//I wish to use built-in images which are platform specific for
//Favourites, Bookmark, More, etc...
//Icon = "Profile.png",
Content = new StackLayout {
Spacing = 20, Padding = 50,
VerticalOptions = LayoutOptions.Center,
Children = {
new Entry { Placeholder = "Username" },
new Entry { Placeholder = "Password", IsPassword = true },
new Button {
Text = "Login",
TextColor = Color.White,
BackgroundColor = Color.FromHex("77D065") }}}};
var settingsPage = new ContentPage {
Title = "Settings",
Content = new StackLayout {
Spacing = 20, Padding = 50,
VerticalOptions = LayoutOptions.Center,
Children = {
new Entry { Placeholder = "Username" },
new Entry { Placeholder = "Password", IsPassword = true }}}
};
MainPage = new TabbedPage { Children = {profilePage, settingsPage} };
For iOS
you need a custom renderer for your page. In my example, it is CustomTabsPage class. You cannot just use system icons to create a UIImage. We need to use UITabBarItem. The problem is that UITabBarItem doesn't allow changes to either title or image/icon. But, we can copy an image from it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UIKit;
using ButtonRendererDemo;
using ButtonRendererDemo.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(CustomTabsPage), typeof(CustomTabsPageRenderer))]
namespace ButtonRendererDemo.iOS
{
public class CustomTabsPageRenderer : TabbedRenderer
{
#region Sytem Image with custom title
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
foreach (var item in TabBar.Items)
{
item.Image = GetTabIcon(item.Title);
}
}
private UIImage GetTabIcon(string title)
{
UITabBarItem item = null;
switch (title)
{
case "Profile":
item = new UITabBarItem(UITabBarSystemItem.Search, 0);
break;
case "Settings":
item = new UITabBarItem(UITabBarSystemItem.Bookmarks, 0);
break;
}
var img = (item != null) ? UIImage.FromImage(item.SelectedImage.CGImage, item.SelectedImage.CurrentScale, item.SelectedImage.Orientation) : new UIImage();
return img;
}
#endregion
}
}
For Android
things are easier
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ButtonRendererDemo;
using ButtonRendererDemo.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
using Android.Support.Design.Widget;
[assembly: ExportRenderer(typeof(CustomTabsPage), typeof(CustomTabsPageRenderer))]
namespace ButtonRendererDemo.Droid
{
public class CustomTabsPageRenderer : TabbedPageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
//var layout = (TabLayout)ViewGroup.GetChildAt(1); //should be enough but just for robustness we use loop below
TabLayout layout = null;
for (int i = 0; i < ChildCount; i++)
{
layout = GetChildAt(i) as TabLayout;
if (layout != null)
break;
}
if (layout != null)
{
for (int tabIndex = 0; tabIndex < layout.TabCount; tabIndex++)
SetTabIcon(layout, tabIndex);
}
}
private void SetTabIcon(TabLayout layout, int tabIndex)
{
var tab = layout.GetTabAt(tabIndex);
switch (tabIndex)
{
case 0:
tab.SetIcon(Resource.Drawable.icon2);//from local resource
break;
case 1:
tab.SetIcon(Resource.Drawable.ic_media_play_dark);//from Android system, depends on version !
break;
}
}
}
}
In order to use icons, you need a platform-specific structure. In Xamarin.Forms, for this to work:
Icon = "youricon.png";
You need:
iOS: put the image in /Resources/youricon.png
Android: put the image in /Resources/drawable/youricon.png
Win Phone: put the image in the Windows Phone application project root.
You should write custom TabbedPage renderer for iOS project:
public class YourTabbedRenderer : TabbedRenderer {
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
var items = TabBar.Items;
for (var i = 0; i < items.Length; i++) {
// set your icons here, you could do some string comparison (check tab title or tab icon resource name)
// items[i].Image =
// items[i].SelectedImage =
}
}
}
http://developer.xamarin.com/guides/cross-platform/xamarin-forms/custom-renderer/

How can I use Asp.Net Chart control in Asp.Net MVC 3?

I know that there is a Chart control comes with the new Helpers library, but it doesn't have the same features as the Asp.Net Charting control. I have to represent a data where on the pie or the bar, I need to have a clickable link on the legend or the label.
I am using Asp.Net MVC 3 Razor and I couldn't tie the Asp.Net Chart control with this feature. I can show the chart but the links are not rendered.
Any suggestions?
You can use ActionResult to render the chart. On the following link is a blog post of Daniel A Hill - Rendering Microsoft .NET 4.0 Charts in ASP.NET MVC
using System;
using System.IO;
using System.Web.Mvc;
using System.Web.UI.DataVisualization.Charting;
namespace Serviscope.Proviso.Web.Code
{
public class ChartActionResult : ActionResult
{
private readonly Chart _chart;
private readonly ChartImageFormat _imageFormat;
public ChartActionResult(Chart chart, ChartImageFormat imageFormat = ChartImageFormat.Png)
{
if ( chart == null ) { throw new ArgumentNullException("chart"); }
_chart = chart;
_imageFormat = imageFormat;
}
public override void ExecuteResult(ControllerContext context)
{
var response = context.HttpContext.Response;
response.Clear();
response.Charset = String.Empty;
response.ContentType = "image/" + _imageFormat;
if ( _imageFormat == ChartImageFormat.Png )
{
// PNG can only write to a seek-able stream
// Thus we have to go through a memory stream, which permits seeking.
using ( var mStream = new MemoryStream() )
{
_chart.SaveImage(mStream, _imageFormat);
mStream.Seek(0, SeekOrigin.Begin);
mStream.CopyTo(response.OutputStream);
}
}
else
{ // If we don't have to provide a seek-able stream, write directly to
// where the data needs to go.
_chart.SaveImage(response.OutputStream, _imageFormat);
}
_chart.Dispose();
}
}
}
and example:
public ActionResult MyChart()
{
// Build Chart
var chart = new Chart()
{
Height = 300,
Width = 400,
BackGradientStyle = GradientStyle.TopBottom,
BackColor = Color.Gray,
BorderSkin = new BorderSkin() { SkinStyle = BorderSkinStyle.Emboss }
};
// Add Chart Area and Set 3-D Settings
chart.ChartAreas.Add(new ChartArea());
chart.ChartAreas[0].Area3DStyle = new ChartArea3DStyle()
{
Enable3D = true,
Perspective = 10,
Inclination = 30,
Rotation = 10
};
// Add Random values
chart.Series.Add(GenerateRandomSeries(10, 10));
chart.Series.Add(GenerateRandomSeries(10, 10));
chart.Series.Add(GenerateRandomSeries(10, 10));
// Return chart object, wrapped in our custom action result
return new ChartActionResult(chart);
}
private static readonly Random RandomPointGenerator = new Random();
private static Series GenerateRandomSeries(int max, int count)
{
var series = new Series();
series.ChartType = SeriesChartType.Line;
for (int x = 0; x < count; x++)
{
series.Points.AddXY(x + 1, RandomPointGenerator.Next(max));
}
return series;
}
You should just create a standard webforms page inside your ASP.NET MVC application.
Scott Hanselman explains how to do that here.

Resources