У меня есть кастомный TextBlock, который определяет ссылки в тексте и делает их кликабельными. Возникла необходимость добавить возможность выделения произвольного текста в TextBlock для его дальнейшего копирования в буфер памяти. Есть идеи как это можно сделать?
Класс HyperlinkTextBlock:
public class HyperlinkTextBlock : TextBlock
{
private static readonly Regex HyperlinkRegex = new Regex(@"(https?|ftp):\/\/[^\s/$.?#].[^\s,]*");
public static readonly DependencyProperty HyperlinkStyleProperty = DependencyProperty.Register("HyperlinkStyle",
typeof(Style), typeof(HyperlinkTextBlock));
static HyperlinkTextBlock()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(HyperlinkTextBlock),
new FrameworkPropertyMetadata(typeof(HyperlinkTextBlock)));
}
public HyperlinkTextBlock()
{
TargetUpdated += (s, args) =>
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(ParseHyperlinks));
}
public event EventHandler<HyperlinkPressedEventArgs> HyperlinkPressed;
public Style HyperlinkStyle
{
private get => (Style) GetValue(HyperlinkStyleProperty);
set => SetValue(HyperlinkStyleProperty, value);
}
private void ParseHyperlinks()
{
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 = HyperlinkStyle};
run.MouseDown += RunOnMouseDown;
Inlines.Add(run);
}
Inlines.Add(text.Substring(lastIndex));
}
private void RunOnMouseDown(object sender, MouseButtonEventArgs args)
{
if (!(sender is Run run)) return;
var handler = HyperlinkPressed;
handler?.Invoke(this, new HyperlinkPressedEventArgs(run.Text));
}
}
public class HyperlinkPressedEventArgs : EventArgs
{
public readonly Uri Hyperlink;
public HyperlinkPressedEventArgs(string hyperlink)
{
Hyperlink = new Uri(hyperlink);
}
}
Использование:
<Style x:Key="HyperlinkStyle" TargetType="{x:Type Run}">
<Style.Setters>
<Setter Property="Foreground" Value="Blue" />
<Setter Property="TextDecorations" Value="Underline" />
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Foreground" Value="Orange" />
</Trigger>
</Style.Triggers>
</Style>
<htb:HyperlinkTextBlock Background="Transparent"
Margin="10,0,0,0"
HyperlinkStyle="{StaticResource HyperlinkStyle}"
HyperlinkPressed="OnHyperlinkPressed"
Text="{Binding Message, NotifyOnTargetUpdated=True}"
TextWrapping="Wrap" />
private void OnHyperlinkPressed(object sender, HyperlinkPressedEventArgs args)
{
System.Diagnostics.Process.Start(args.Hyperlink.OriginalString);
}
Обычно делают из TextBox
. Стилизуют его под TextBlock
(рамку убрать, фон, etc) делают readonly и всё. Вот тебе и выделение с копированием.
Update. Накидал на скорую руку решение на RichTextBox
(может кто предложит лучше, я только за):
public class HyperlinkRichTextBox : RichTextBox
{
#region CustomText Dependency Property
public static readonly DependencyProperty CustomTextProperty = DependencyProperty.Register("CustomText", typeof(string), typeof(HyperlinkRichTextBox),
new PropertyMetadata(string.Empty, CustomTextChangedCallback), CustomTextValidateCallback);
public string CustomText
{
get => (string)GetValue(CustomTextProperty);
set => SetValue(CustomTextProperty, value);
}
private static void CustomTextChangedCallback(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((HyperlinkRichTextBox) obj).Document = GetCustomDocument(e.NewValue as string);
}
private static bool CustomTextValidateCallback(object value) => value != null;
#endregion
private const string HttpPrefix = "http://";
private static readonly Regex LinkRegex =
new Regex(
@"([--:А-Яа-я\w?@%&+~#=]*\.[a-zа-я]{2,4}\/{0,2})((?:[?&](?:[А-Яа-я\w]+)=(?:[А-Яа-я\w]+))+|[--:А-Яа-я\w?@%&+~#=]+)?",
RegexOptions.Compiled);
private static readonly Regex PrefixRegex =
new Regex("^(http|ftp)(s)?", RegexOptions.Compiled);
private static FlowDocument GetCustomDocument(string text)
{
var document = new FlowDocument();
var para = new Paragraph {Margin = new Thickness(0)};
var linksMatches = LinkRegex.Matches(text);
if (linksMatches.Count == 0)
{
para.Inlines.Add(text);
}
else
{
Match lastMatch = null;
foreach (Match linksMatch in linksMatches)
{
var previousText = GetPreviousText(text, lastMatch, linksMatch);
para.Inlines.Add(previousText);
var link = CreateLink(linksMatch.Value);
para.Inlines.Add(link);
lastMatch = linksMatch;
}
var tail = GetPreviousText(text, lastMatch);
para.Inlines.Add(tail);
}
document.Blocks.Add(para);
return document;
}
private static Hyperlink CreateLink(string url)
{
var link = new Hyperlink
{
IsEnabled = true
};
link.Inlines.Add(url);
link.NavigateUri = PrefixRegex.IsMatch(url)
? new Uri(url)
: new Uri($"{HttpPrefix}{url}");
link.RequestNavigate += (sender, args) => Process.Start(args.Uri.ToString());
return link;
}
private static string GetPreviousText(string text, Capture lastMatch, Capture currentMatch = null)
{
var startIndex = lastMatch != null ? lastMatch.Index + lastMatch.Length : 0;
return currentMatch != null
? text.Substring(startIndex, currentMatch.Index - startIndex)
: text.Substring(startIndex);
}
}
Объявляем так:
<local:HyperlinkRichTextBox BorderThickness="0" Padding="-5" CustomText="{Binding Text}" IsReadOnly="True" IsDocumentEnabled="True">
Padding="-5"
добавил чтоб отображение совпадало с TextBlock
.
Ну и, конечно, выделение и копирование доступно
Не знаю правильные ли это подходы, но можно в ToolTip добавить текст типа при нажатии ПКМ (или Ваш вариант) данная ссылка будет скопирована. Либо организовать MenuItem c Heder = "Копировать ссылку" и вызвать метод копирования. По примерам кода я не знаю что Вам привести.
Виртуальный выделенный сервер (VDS) становится отличным выбором
Развернул сайт на Docker, но не могу сконнектить IDE VSCode c моим PHP серверомПосмотрел кучу мануалов, но даже не знаю где посмотреть ошибки от этого...