实现类似微博内容里,用户名+Url的解析效果。
注:这里使用的数据是twitter的格式,可能跟微博的不一样。但是原理是相通的。
原理:使用richtextbox。
将正文内容用正则表达式解析之后,获取每一个的匹配的index, 然后按照index排序。
遍历每一个匹配项,把文本作为run动态添加到richtextbox;把用户名作为hyperlink添加到richtextbox。
原始数据大概是这样:
{
"id": 174858924,
"id_str": "174858924",
"name": "喔喔(必须同意许可协议才能查看推文)",
"screen_name": "im_wower",
"description": "@Chicken4WP
gmail/gtalk: george674834080@gmail.com",
}
用户名的正则:
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的源码>>