实现类似微博内容里,用户名+Url的解析效果。
注:这里使用的数据是twitter的格式,可能跟微博的不一样。但是原理是相通的。
原理:使用richtextbox。
将正文内容用正则表达式解析之后,获取每一个的匹配的index, 然后按照index排序。
遍历每一个匹配项,把文本作为run动态添加到richtextbox;把用户名作为hyperlink添加到richtextbox。
原始数据大概是这样:
{ "id": 174858924, "id_str": "174858924", "name": "喔喔(必须同意许可协议才能查看推文)", "screen_name": "im_wower", "description": "@Chicken4WP gmail/gtalk: [email protected]", }
用户名的正则:
private static Regex UserNameRegex = new Regex(@"([^A-Za-z0-9_]|^)@(?<name>(_*[A-Za-z0-9]{1,15}_*)+)(?![A-Za-z0-9_@])");说明:仅限twitter的用户名。
解析用户名:
public static IEnumerable<EntityBase> ParseUserMentions(string text) { if (string.IsNullOrEmpty(text)) yield break; var matches = UserNameRegex.Matches(text); foreach (Match match in matches) { var entity = new UserMention { Index = match.Groups["name"].Index - 1,//remove @ DisplayName = match.Groups["name"].Value }; yield return entity; } }
UserMention是一个Entity类。
public class UserMention : EntityBase { public override EntityType EntityType { get { return EntityType.UserMention; } } public string Id { get; set; } public string DisplayName { get; set; } public override string Text { get { return "@" + DisplayName; } } }EntityBase类:
public class EntityBase { public virtual EntityType EntityType { get; private set; } public int Index { get; set; } public virtual string Text { get; set; } } public enum EntityType { None = 0, Media = 1, HashTag = 2, Url = 3, UserMention = 4, }添加一个继承自RichTextBox的类:
public class AutoRichTextBox : RichTextBox注册依赖属性:
public static DependencyProperty TweetDataProperty = DependencyProperty.Register("TweetData", typeof(ModelBase), typeof(AutoRichTextBox), new PropertyMetadata(TweetDataPropertyChanged));
依赖属性的回调:
public static void TweetDataPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { #region init var textBox = sender as AutoRichTextBox; if (textBox == null || e.NewValue == null) return; textBox.AddTweetData(e.NewValue); }动态生成控件:
private void AddTweetData(object data) { this.Blocks.Clear(); string text = string.Empty; var paragraph = new Paragraph(); var entities = new List<EntityBase>(); #region tweet if (data is TweetBase) { var tweet = data as TweetBase; text = tweet.Text; #region add entity if (tweet.Entities.UserMentions != null && tweet.Entities.UserMentions.Count != 0) { entities.AddRange(TwitterHelper.ParseUserMentions(text, tweet.Entities.UserMentions)); } if (tweet.Entities.HashTags != null && tweet.Entities.HashTags.Count != 0) { entities.AddRange(TwitterHelper.ParseHashTags(text, tweet.Entities.HashTags)); } if (tweet.Entities.Urls != null && tweet.Entities.Urls.Count != 0) { entities.AddRange(TwitterHelper.ParseUrls(text, tweet.Entities.Urls)); } if (tweet.Entities.Medias != null && tweet.Entities.Medias.Count != 0) { entities.AddRange(TwitterHelper.ParseMedias(text, tweet.Entities.Medias)); } #endregion } #endregion #region profile else if (data is UserProfileDetail) { var profile = data as UserProfileDetail; text = profile.Text; var mentions = TwitterHelper.ParseUserMentions(profile.Text); entities.AddRange(mentions); var hashtags = TwitterHelper.ParseHashTags(profile.Text); entities.AddRange(hashtags); #region url if (profile.UserProfileEntities != null && profile.UserProfileEntities.DescriptionEntities != null && profile.UserProfileEntities.DescriptionEntities.Urls != null) { var parsedUrls = TwitterHelper.ParseUrls(profile.Text, profile.UserProfileEntities.DescriptionEntities.Urls); entities.AddRange(parsedUrls); } #endregion } #endregion #endregion #region none if (entities.Count == 0) { paragraph.Inlines.Add(new Run { Text = HttpUtility.HtmlDecode(text) }); this.Blocks.Add(paragraph); } #endregion #region add else { #region replace int index = 0; foreach (var entity in entities.OrderBy(v => v.Index)) { #region starter if (index < entity.Index) { paragraph.Inlines.Add(new Run { Text = HttpUtility.HtmlDecode(text.Substring(index, entity.Index - index)), }); index = entity.Index; } #endregion var hyperlink = new Hyperlink(); hyperlink.TextDecorations = null; hyperlink.Foreground = App.PhoneAccentBrush; #region entity switch (entity.EntityType) { #region mention, hashtag case EntityType.UserMention: case EntityType.HashTag: hyperlink.CommandParameter = entity; hyperlink.Click += this.Hyperlink_Click; hyperlink.Inlines.Add(entity.Text); break; #endregion #region media, url case EntityType.Media: var media = entity as MediaEntity; hyperlink.NavigateUri = new Uri(media.MediaUrl, UriKind.Absolute); hyperlink.TargetName = "_blank"; hyperlink.Inlines.Add(media.TruncatedUrl); break; case EntityType.Url: var url = entity as UrlEntity; hyperlink.NavigateUri = new Uri(url.ExpandedUrl, UriKind.Absolute); hyperlink.TargetName = "_blank"; hyperlink.Inlines.Add(url.TruncatedUrl); break; #endregion } #endregion paragraph.Inlines.Add(hyperlink); index += entity.Text.Length; } #region ender if (index < text.Length) { paragraph.Inlines.Add(new Run { Text = HttpUtility.HtmlDecode(text.Substring(index, text.Length - index)), }); } #endregion #endregion this.Blocks.Add(paragraph); } #endregion }
一些说明:
0. 为什么不使用xaml.parse(string)动态生成控件?
因为转义字符很麻烦。而且动态生成控件需要加入命名空间,比较麻烦。
截图效果(来自Chicken4WP):
示例代码:
呃,没有示例代码。。可以查看Chicken4WP的源码>>