Использую ListView
для отображения логов.
Иногда в логе бывают ссылки (напимер, "Некорректная ссылка: http://example.com/"), хотелось бы, чтобы на них просто нажать можно было и они открылись в обычном браузере.
Можно ли это сделать автоматически или придётся парсить ручками и конвертировать в комбинацию Run
и Hyperlink
?
Если ссылки могут встречаться в любом месте лога, то можно написать регулярное выражение, которое вытащит нужную ссылку или ссылки. Сформировать на основе полученной информации модель представления. А потом написать шаблон для элемента ListView, который будет использовать HyperLink.
Используя реализацию отсюда и regex отсюда (правда, я добавил в свой шаблон запятые, как разделитель, в логах так встречается, к сожалению) получилась вот такая реализация.
public class TextBlockWithHyperlinks : TextBlock
{
private static readonly Regex HyperlinkRegex = new Regex(@"(https?|ftp):\/\/[^\s/$.?#].[^\s,]*");
static TextBlockWithHyperlinks()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBlockWithHyperlinks), new FrameworkPropertyMetadata(typeof(TextBlockWithHyperlinks)));
}
public TextBlockWithHyperlinks()
{
TargetUpdated += (s, args) => Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(ParseHyperlinks));
}
private void ParseHyperlinks()
{
var style = HyperlinkStyle;
var text = Text;
var matches = HyperlinkRegex.Matches(text);
if (matches.Count == 0)
return;
Inlines.Clear();
var lastIndex = 0;
foreach (Match match in matches)
{
Inlines.Add(text.Substring(lastIndex, match.Index - lastIndex));
lastIndex = match.Index + match.Length;
var run = new Run(match.Value) { Style = style };
run.MouseDown += RunOnMouseDown;
Inlines.Add(run);
}
Inlines.Add(text.Substring(lastIndex));
}
private void RunOnMouseDown(object sender, MouseButtonEventArgs args)
{
var run = sender as Run;
if (run == null)
return;
OnHyperlinkPressed(new HyperlinkPressedEventArgs(run.Text));
}
public event EventHandler<HyperlinkPressedEventArgs> HyperlinkPressed;
public void OnHyperlinkPressed(HyperlinkPressedEventArgs args)
{
var handler = HyperlinkPressed;
handler?.Invoke(this, args);
}
public static readonly DependencyProperty HyperlinkStyleProperty = DependencyProperty.Register("HyperlinkStyle",
typeof(Style),
typeof(TextBlockWithHyperlinks));
public Style HyperlinkStyle
{
get { return (Style)GetValue(HyperlinkStyleProperty); }
set { SetValue(HyperlinkStyleProperty, value); }
}
}
public class HyperlinkPressedEventArgs : EventArgs
{
public readonly Uri Hyperlink;
public HyperlinkPressedEventArgs(Uri hyperlink)
{
Hyperlink = hyperlink;
}
public HyperlinkPressedEventArgs(string hyperlink) : this(new Uri(hyperlink))
{
}
}
В разметку вставляется легко, есть только одно но - всегда надо включать NotifyOnTargetUpdated
.
<local:TextBlockWithHyperlinks HyperlinkPressed="TextBlockWithLinks_OnHyperlinkPressed"
HyperlinkStyle="{StaticResource HyperlinkStyle}"
Text="{Binding FormattedMessage, NotifyOnTargetUpdated=True}"/>
Стиль простой, подчеркивание и синий цвет:
<Style x:Key="HyperlinkStyle" TargetType="{x:Type Run}" >
<Style.Setters>
<Setter Property="Foreground" Value="Blue"/>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="TextDecorations" Value="Underline"/>
<Setter Property="Cursor" Value="Hand"/>
</Trigger>
</Style.Triggers>
</Style>
Обработчик ссылок по идее надо бы делать с защитой от опасных операций, но пока так:
private void TextBlockWithLinks_OnHyperlinkPressed(object sender, HyperlinkPressedEventArgs args)
{
System.Diagnostics.Process.Start(args.Hyperlink.OriginalString);
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Имеется такая функция, которая вызывает сама себя, то есть рекурсивнаяДопустим n = 5
Сейчас я только понял как добавить в источники данных БДДальше не могу понять, как поступить дальше
Ругается на picturebox1 - хотя на форме он естьВо время выполнения NullReferenceException