I have a TextBlock whose data comes from JSON. I would like if the textblock the website address or email, the text color becomes blue and the user can click (if the email address it will go to the email application and the user can write an email directly to this address. Meanwhile, if the website address, it will immediately open the web browser).
XAML:
<TextBlock x:Name="DetailDeskripsi" Width="290" Text="{Binding Deskripsi}" VerticalAlignment="Top" HorizontalAlignment="Left" Height="auto" TextWrapping="Wrap" FontSize="15" TextAlignment="Justify" Foreground="#FFCA6402"/>
Example of JSON Data from http://.../mobileapp/GetPostByCategoryXMLa?term_id=378:
How do I apply it?
I've modified a little the answer from here and now it processes the bound string, searching for website and e-mail adresses. Once it founds one, it creates a hyperlink which should fire e-mail app or webbrowser.
The code for TextBlock extendion:
public static class TextBlockExtension
{
public static string GetFormattedText(DependencyObject obj)
{ return (string)obj.GetValue(FormattedTextProperty); }
public static void SetFormattedText(DependencyObject obj, string value)
{ obj.SetValue(FormattedTextProperty, value); }
public static readonly DependencyProperty FormattedTextProperty =
DependencyProperty.Register("FormattedText", typeof(string), typeof(TextBlockExtension),
new PropertyMetadata(string.Empty, (sender, e) =>
{
string text = e.NewValue as string;
var textBl = sender as TextBlock;
if (textBl != null && !string.IsNullOrWhiteSpace(text))
{
textBl.Inlines.Clear();
Regex regx = new Regex(#"(http(s)?://[\S]+|www.[\S]+|[\S]+#[\S]+)", RegexOptions.IgnoreCase);
Regex isWWW = new Regex(#"(http[s]?://[\S]+|www.[\S]+)");
Regex isEmail = new Regex(#"[\S]+#[\S]+");
foreach (var item in regx.Split(text))
{
if (isWWW.IsMatch(item))
{
Hyperlink link = new Hyperlink { NavigateUri = new Uri(item.ToLower().StartsWith("http") ? item : $"http://{item}"), Foreground = Application.Current.Resources["SystemControlForegroundAccentBrush"] as SolidColorBrush };
link.Inlines.Add(new Run { Text = item });
textBl.Inlines.Add(link);
}
else if (isEmail.IsMatch(item))
{
Hyperlink link = new Hyperlink { NavigateUri = new Uri($"mailto:{item}"), Foreground = Application.Current.Resources["SystemControlForegroundAccentBrush"] as SolidColorBrush };
link.Inlines.Add(new Run { Text = item });
textBl.Inlines.Add(link);
}
else textBl.Inlines.Add(new Run { Text = item });
}
}
}));
}
And the code in xaml:
<TextBlock extension:TextBlockExtension.FormattedText="{x:Bind TextToFormat, Mode=OneWay}" FontSize="15" Margin="10" TextWrapping="WrapWholeWords"/>
The working sample you will find at my Github - I've tested it with your json and it looks/works quite nice:
Related
I want to show in my C#-WPF application a text containing links. The texts are static and known during compile time.
The following is doing want i want when working directly on the XAML file:
<TextBlock Name="TextBlockWithHyperlink">
Some text
<Hyperlink
NavigateUri="http://somesite.com"
RequestNavigate="Hyperlink_RequestNavigate">
some site
</Hyperlink>
some more text
</TextBlock>
Since using MVVM i want to bind the Textblock to a newly constructed Textblock object, through a dependency property. The XAML then looks like this:
<StackPanel Grid.Row="1" Margin="5 0 0 0">
<TextBlock Height="16" FontWeight="Bold" Text="Generic Text with link"/>
<TextBlock Text="{Binding Path=TextWithLink, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
In my ViewModel i place
private void someMethod(){
...
TextWithLink = CreateText();
...
}
private TextBlock(){
TextBlock tb = new TextBlock();
Run run1 = new Run("Text preceeding the hyperlink.");
Run run2 = new Run("Text following the hyperlink.");
Run run3 = new Run("Link Text.");
Hyperlink hyperl = new Hyperlink(run3);
hyperl.NavigateUri = new Uri("http://search.msn.com");
tb.Inlines.Add(run1);
tb.Inlines.Add(hyperl);
tb.Inlines.Add(run2);
return tb;
}
private TextBlock _textWithLink;
public TextBlock TextWithLink {
get => _textWithLink;
set{
_textWithLink = value;
OnPropertyChanged();
}
}
The dependency property setup is working i see a new TextBlock getting assigned to the XAML control, however there is no content shown, just the displayed text reads
System.Windows.Controls.TextBlock
rather than the content. I cannot get my head around what i have to change to show the desired mixed text. Happy for an help.
Instead of using a TextBlock instance in a view model, you should instead use a collection of Inline elements with a UI element that accept it as the source of a Binding.
Since the Inlines property of a TextBlock is not bindable, you may create a deribed TextBlock with a bindable property like this:
public class MyTextBlock : TextBlock
{
public static readonly DependencyProperty BindableInlinesProperty =
DependencyProperty.Register(
nameof(BindableInlines),
typeof(IEnumerable<Inline>),
typeof(MyTextBlock),
new PropertyMetadata(null, BindableInlinesPropertyChanged));
public IEnumerable<Inline> BindableInlines
{
get { return (IEnumerable<Inline>)GetValue(BindableInlinesProperty); }
set { SetValue(BindingGroupProperty, value); }
}
private static void BindableInlinesPropertyChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var textblock = (MyTextBlock)o;
var inlines = (IEnumerable<Inline>)e.NewValue;
textblock.Inlines.Clear();
if (inlines != null)
{
textblock.Inlines.AddRange(inlines);
}
}
}
Now you may use it like
<local:MyTextBlock BindableInlines="{Binding SomeInlines}"/>
with a view model property like this:
public IEnumerable<Inline> SomeInlines { get; set; }
...
var link = new Hyperlink(new Run("Search"));
link.NavigateUri = new Uri("http://search.msn.com");
link.RequestNavigate += (s, e) => Process.Start(e.Uri.ToString());
SomeInlines = new List<Inline>
{
new Run("Some text "),
link,
new Run(" and more text")
};
I have a RichTextBox bound to a string.
Using C# I generate a string that writes to it.
But if I want to manually change the text by clicking into the RichTextBox and deleting it with the backspace key, or pressing Enter to make a new line, the binding becomes broken and I can no longer programmatically write to it with the string a second time.
XAML
<RichTextBox x:Name="rtbScriptView"
Margin="11,71,280,56"
Padding="10,10,10,48"
FontSize="14"
Grid.ColumnSpan="1"
VerticalScrollBarVisibility="Auto"
RenderOptions.ClearTypeHint="Enabled"
Style="{DynamicResource RichTextBoxStyle}">
<FlowDocument>
<Paragraph>
<Run Text="{Binding ScriptView_Text,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
</Paragraph>
</FlowDocument>
</RichTextBox>
View Model
private string _ScriptView_Text;
public string ScriptView_Text
{
get { return _ScriptView_Text; }
set
{
if (_ScriptView_Text == value)
{
return;
}
_ScriptView_Text = value;
OnPropertyChanged("ScriptView_Text");
}
}
C#
ViewModel vm = new ViewModel();
DataContext = vm;
// Display a string in the RichTextBox
vm.ScriptView_Text = "This is a test."; // <-- This won't work if text is manually modified
When you edit the RichTextBox, you alter the elements inside of the FlowDocument element. The element you have a binding on, is probably removed at some point during this editing.
Have a look at RichtTextBox.Document.Groups to see what's happening when you edit the RichTextBox.
The default RichTextBox does not really support MVVM/Binding very well. You'd want to have a binding on the Document property, but this is not supported for the default RichTextBox.
You could have a look here.
Or extend it yourself, something like this?:
BindableRichTextBox class
public class BindableRichTextBox : RichTextBox
{
public static readonly DependencyProperty DocumentProperty = DependencyProperty.Register(nameof(Document), typeof(FlowDocument), typeof(BindableRichTextBox), new FrameworkPropertyMetadata(null, OnDocumentChanged));
public new FlowDocument Document
{
get => (FlowDocument)GetValue(DocumentProperty);
set => SetValue(DocumentProperty, value);
}
public static void OnDocumentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var rtb = (RichTextBox)obj;
rtb.Document = args.NewValue != null ? (FlowDocument)args.NewValue : new FlowDocument();
}
}
XAML
<controls:BindableRichTextBox Document="{Binding YourFlowDocumentObject, Mode=OneWay}"/>
Then you can get the string from the FlowDocument.
Why you have to write this line. Please remove line after check.
if (_ScriptView_Text == value)
{
return;
}
I need to change the display order of the text in my UWP app but unfortunately I don't find any straight solution to do so.
The textblock in WinRT does not support this property, at least I can't found any information about this feature from MSDN. I found a solution that I need create a "New" textblock control which supports the text display in vertical order but the solution is for silverlight so I'm working on it to see whether it works or not.
This is how textblock works normally:
This is how textblock that I want it to work:
I know there is a way that just setting up the Width and text wraping something but it only works for a certain screen size & resolution, which means under other screen the text will not display properly
Any tips would be appreciated.
To get a "real" vertical text in UWP try the following:
<TextBlock Text="Rotated Text"
FontSize="18"
Foreground="Black">
<TextBlock.RenderTransform>
<RotateTransform Angle="-90" />
</TextBlock.RenderTransform>
</TextBlock>
Edit - UWP verison with user control
VerticalTextBlock - code behind
public partial class VerticalTextBlock : UserControl
{
public VerticalTextBlock()
{
InitializeComponent();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text",
typeof(string),
typeof(VerticalTextBlock),
new PropertyMetadata(string.Empty, textChangeHandler));
private static void textChangeHandler(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var prop = d as VerticalTextBlock;
var textBlock = prop.TheTextBlock;
var str = (e.NewValue as string);
textBlock.Inlines.Clear();
for (int i = 0; i < str.Length-1; i++)
{
textBlock.Inlines.Add(new Run() { Text = str[i] + Environment.NewLine });
}
textBlock.Inlines.Add(new Run() { Text = str[str.Length-1].ToString()});
}
}
VerticalTextBlock - XAML
<UserControl
...
>
<TextBlock x:Name="TheTextBlock"/>
</UserControl>
Usage and test - XAML
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Name="a" Text="ASD"></TextBlock>
<local:VerticalTextBlock x:Name="b" Text="{Binding ElementName=a, Path=Text}" />
<local:VerticalTextBlock x:Name="c" Text="{Binding ElementName=b, Path=Text}" />
<TextBlock x:Name="d" Text="{Binding ElementName=c, Path=Text}"></TextBlock>
<TextBlock TextAlignment="Center" HorizontalAlignment="Left">
<Run Text="A"/>
<LineBreak/>
<Run Text="S"/>
<LineBreak/>
<Run Text="D"/>
<LineBreak/>
<Run Text="A"/>
<LineBreak/>
<Run Text="S"/>
<LineBreak/>
<Run Text="D"/>
</TextBlock>
</StackPanel>
Original Answer - didn't notice it's UWP not WPF
You got me interested as I've only done this in Android, so there are a few solutions that will work but I decided to try custom control extending TextBlock
public partial class VerticalTextBlock : TextBlock
{
public VerticalTextBlock()
{
InitializeComponent();
}
new public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
new public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text",
typeof(string),
typeof(VerticalTextBlock),
new PropertyMetadata(string.Empty, textChangeHandler));
private static void textChangeHandler(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var prop = d as VerticalTextBlock;
var str = (e.NewValue as string);
var inlines = str.Select(x => new Run(x + Environment.NewLine));
prop.Inlines.Clear();
prop.Inlines.AddRange(inlines);
}
}
Usage in XAML
<local:VerticalTextBlock Text="AABBCCDDEEFF" />
So I am trying to impliment a simple Kerning UserControl to use with DataBound text in a ListBoxTemplate and I am getting the error that is in the title of this question.
I am using Design Time data to populate data while I am developing in VS or Expression Blend but I am not sure if this is the cause as it builds and only crashes when I populate the data.
<ListBox
x:Name="MainList"
ItemsSource="{Binding FeedItems}"
SelectionChanged="ListBox_SelectionChanged"
>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" x:Uid="{Binding ItemLink}" Margin="10">
<Controls:KerningTextBlock
Spacing="2"
Font="Verdana"
VerticalAlignment="Center"
HorizontalAlignment="Left"
FontSize="32"
InputText="{Binding ItemTitle}"/>
....
private StackPanel Stack = new StackPanel()
{
FlowDirection = System.Windows.FlowDirection.LeftToRight,
Orientation = System.Windows.Controls.Orientation.Horizontal
};
private void KernIt()
{
// Clear the contents
this.LayoutRoot.Children.Clear();
// Convert input string to character array
char[] Letters = !string.IsNullOrEmpty(this.InputText)? this.InputText.ToCharArray() : " ".ToCharArray();
// For each item create a new text block with the following test
foreach (var letter in Letters)
{
// Set up the formatted text block
TextBlock TempText = new TextBlock();
TempText.FontFamily = new FontFamily(this.Font);
TempText.FontSize = 30;
TempText.Padding = new Thickness(0, 0, this.Spacing, 0);
TempText.Text = letter.ToString();
// Add to the stack
Stack.Children.Add(TempText);
}
// Add to the grid
if (Stack.Children.Count() > 0)
this.LayoutRoot.Children.Add(Stack);
}
public string InputText { get; set; }
public double Spacing { get; set;}
public string Font { get; set; }
Got it...In the user control you have to add
DataContext="{Binding}"
Then set the Layout root as follows since I am clearing out the LayoutRoot (which needs to now be changed to SubRoot.Children.Clear():
<Grid x:Name="LayoutRoot" >
<TextBlock x:Name="Title" Text="{Binding ItemTitle}"/>
<Grid x:Name="SubRoot">
</Grid>
</Grid>
Next in the code behind add an on loaded event:
public KerningTextBlock()
{
UpdateLayout();
InitializeComponent();
this.Loaded += new RoutedEventHandler(KerningTextBlock_Loaded);
}
void KerningTextBlock_Loaded(object sender, RoutedEventArgs e)
{
if (string.IsNullOrEmpty(Title.Text))
this.InputText = "why am I empty?";
else
this.InputText = Title.Text;
KernIt();
}
Then where you are calling the User Control change it to this:
<Controls:KerningTextBlock
DataContext="{Binding}"
Spacing="5"
Font="Verdana"
x:Name="Button_Name"
Margin="135,5,15,0"
VerticalAlignment="Center"
HorizontalAlignment="Left"
FontSize="32"/>
I have a list box displaying the names of help topics which can be added to and the names of the topics changed. Originally it was just displaying strings, but to get the inline editing working I changed it to use a custom type consisting of a string and an InEdit property so the UI can determine whether to display the TextBlock or TextBox:
XAML:
<ListBox ItemsSource="{Binding HelpTopics, Mode=TwoWay}"
SelectedValuePath="Description"
SelectedValue="{Binding SelectedPageId, Mode=TwoWay}"
SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Description, Mode=TwoWay}"
VerticalAlignment="Center"
MouseLeftButtonUp="TopicTextBlock_MouseLeftButtonUp"
Visibility="{Binding InEdit, Converter={StaticResource boolToVisibilityConverter}, ConverterParameter=contra}"/>
<TextBox Text="{Binding Description, Mode=TwoWay}"
Visibility="{Binding InEdit, Converter={StaticResource boolToVisibilityConverter}, ConverterParameter=pro}"
LostFocus="EditTopicTextBox_LostFocus"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Margin="5" Content="Add Topic" Command="{Binding AddTopicCommand}"/>
HelpTopics is an ObservableCollection<EditableHelpTopic>.
SelectedPageId is a string.
boolToVisibilityConverter is a converter that does what it says.
What works:
Adding a topic creates a new item and adds it to the list and put the item in to edit mode.
Double clicking on an existing item puts that item into edit mode sets the focus to the TextBox and selects all the text so it can be overwritten.
When the TextBox loses focus the edit is saved and the display returns to the TextBlock.
What doesn't work:
When a new topic is added the TextBox should have focus and the text selected so the user can enter a new name.
So my question is is there a point in the code or an event where I know that the TextBox has been created and is visible so I can set focus and select its contents. I've tried hooking into the SelectionChanged event but when that fires the TextBox hasn't yet been displayed. I also added an event to the OnAddTopicExecute method in the view model which I handled in the view, but again that fired before the TextBox was visible.
Below is the code that supports the above XAML. I've tried to cut it down, but there still seems to be a lot of it, so you can skip this if you're not interested ;)
Code behind:
private DateTime lastClickTime = DateTime.MinValue;
private Point lastClickPosition;
private void TopicTextBlock_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
UIElement element = sender as UIElement;
if ((DateTime.Now - this.lastClickTime).TotalMilliseconds > 300)
{
this.lastClickPosition = e.GetPosition(element);
this.lastClickTime = DateTime.Now;
}
else
{
Point position = e.GetPosition(element);
if (Math.Abs(this.lastClickPosition.X - position.X) < 4 && Math.Abs(this.lastClickPosition.Y - position.Y) < 4)
{
var textBlock = sender as TextBlock;
var editableHelpTopic = textBlock.DataContext as EditableHelpTopic;
editableHelpTopic.InEdit = true;
var parent = textBlock.Parent as Grid;
TextBox textBox = parent.Children.First(c => c.GetType() == typeof(TextBox)) as TextBox;
textBox.Focus();
textBox.SelectAll();
}
}
}
private void EditTopicTextBox_LostFocus(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
var editableHelpTopic = textBox.DataContext as EditableHelpTopic;
editableHelpTopic.InEdit = false;
if (!textBox.Text.Equals(editableHelpTopic.Description))
{
this.editViewModel.RenameTopic(textBox.Text);
}
}
View Model:
public EditViewModel()
{
...
this.AddTopicCommand = new DelegateCommand(this.OnAddTopicExecute, this.OnAddTopicCanExecute);
...
}
where DelegateCommand is an implemetation of ICommand.
private void OnAddTopicExecute(object parameter)
{
var newTopic = new EditableHelpTopic
{
Description = "NewTopic",
InEdit = true
};
this.HelpTopics.Add(newTopic);
this.SelectedPageId = newTopic.Description;
}
Definitions:
public class EditableHelpTopic : INotifyPropertyChanged
{
public bool InEdit { ... }
public string Description { ... }
}
It turned out to be simpler than I thought.
I just needed to add a Loaded event handler to the TextBox:
private void EditTopicTextBox_Loaded(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
var editableHelpTopic = textBox.DataContext as EditableHelpTopic;
if (editableHelpTopic.InEdit)
{
textBox.Focus();
textBox.SelectAll();
}
}