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 have a ContentControl in WPF,which has an image in it
ContentControl myContentControl = new ContentControl();
myContentControl.Content = image;
How do I to add a textblock next to the image inside the ContentControl?
Thank you.
You need to change the property ContentTemplate of the contentControl, as to ContentTemplate, here is some explanation:
Gets or sets the data template used to display the content of the ContentControl.
Also you need create a class to represent your data, like this:
public class ImageInfo
{
public string Caption { get; set; }
public ImageSource Image { get; set; }
}
It is better to create ContentControl in XAML, like this:
<ContentControl x:Name="cc">
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Image}" />
<TextBlock Text="{Binding Caption}" />
</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Then, assign the data to the ContentControl:
cc.Content = new ImageInfo() { Caption = "Hello", Image = new BitmapImage(new System.Uri("/Assets/User.jpg", UriKind.Relative)) };
What you are doing looks like you are not familiar with MVVM:
Please check here
Easy Solution
But if you want the ugly solution and create UIElements in your code behind - go like this:
public static ContentControl CreateControl(string title, Uri path)
{
//Create your image
BitmapImage bitmapImage = new BitmapImage(path);
Image image = new Image()
{
Source = bitmapImage
};
//Create your Text
TextBlock textB = new TextBlock()
{
Text = title
};
//Put them together
StackPanel content = new StackPanel();
content.Children.Add(image);
content.Children.Add(textB);
//Add this to the content control
return new ContentControl()
{
Content = content
};
}
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:
<TextBlock Width="100" Text="The quick brown fox jumps over the lazy dog" TextTrimming="WordEllipsis">
<TextBlock.ToolTip>
<ToolTip DataContext="{Binding Path=PlacementTarget, RelativeSource={x:Static RelativeSource.Self}}">
<TextBlock Text="{Binding Text}"/>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
How can I show the ToolTip only when the text is trimmed? Like the windows desktp shortcut icons.
Working off of Eyjafj...whatever's idea, I arrived at a working, mostly declarative solution that at least doesn't require a custom control. The first hurdle to overcome is getting at the TextBlock. Because the ToolTip is rendered outside of the visual tree, you can't use a RelativeSource binding or ElementName to get at the TextBlock. Luckily, the ToolTip class provides a reference to its related element via the PlacementTarget property. So you can bind the ToolTip's Visibility property to the ToolTip itself and use its PlacementTarget property to access properties of the TextBlock:
<ToolTip Visibility="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget, Converter={StaticResource trimmedVisibilityConverter}}">
The next step is using a converter to look at the TextBlock we've bound to to determine if the ToolTip should be visible or not. You can do this using the ActualWidth and the DesiredSize. ActualWidth is exactly what it sounds like; the width your TextBlock has been rendered to on the screen. DesiredSize is the width your TextBlock would prefer to be. The only problem is, DesiredSize seems to take the TextTrimming into account and does not give you the width of the full, untrimmed text. To solve this, we can re-call the Measure method passing Double.Positive infinity to, in effect, ask how wide the TextBlock would be if it its width were not constrained. This updates the DesiredSize property and then we can do the comparison:
textBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
if (((FrameworkElement)value).ActualWidth < ((FrameworkElement)value).DesiredSize.Width)
return Visibility.Visible;
This approach is actually illustrated here as an attached behavior if you want to apply it automatically to TextBlocks or don't want to waste resources on creating ToolTips that will always be invisible. Here is the full code for my example:
The Converter:
public class TrimmedTextBlockVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return Visibility.Collapsed;
FrameworkElement textBlock = (FrameworkElement)value;
textBlock.Measure(new System.Windows.Size(Double.PositiveInfinity, Double.PositiveInfinity));
if (((FrameworkElement)value).ActualWidth < ((FrameworkElement)value).DesiredSize.Width)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The XAML:
<UserControl.Resources>
<local:TrimmedTextBlockVisibilityConverter x:Key="trimmedVisibilityConverter" />
</UserControl.Resources>
....
<TextBlock TextTrimming="CharacterEllipsis" Text="{Binding SomeTextProperty}">
<TextBlock.ToolTip>
<ToolTip Visibility="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget, Converter={StaticResource trimmedVisibilityConverter}}">
<ToolTip.Content>
<TextBlock Text="{Binding SomeTextProperty}"/>
</ToolTip.Content>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
I found the simplest solution to extend TextBlock and compare text lengths to determine whether to show tooltip, i.e.,
public class ToolTipTextBlock : TextBlock
{
protected override void OnToolTipOpening(ToolTipEventArgs e)
{
if (TextTrimming != TextTrimming.None)
{
e.Handled = !IsTextTrimmed();
}
}
private bool IsTextTrimmed()
{
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var formattedText = new FormattedText(Text, CultureInfo.CurrentCulture, FlowDirection, typeface, FontSize, Foreground);
return formattedText.Width > ActualWidth;
}
}
Then simply use this custom text block in xaml as follows:
<local:ToolTipTextBlock Text="This is some text that I'd like to show tooltip for!"
TextTrimming="CharacterEllipsis"
ToolTip="{Binding Text,RelativeSource={RelativeSource Self}}"
MaxWidth="10"/>
Based on ideas on this page and with additional algorithmic corrections from another answer I made this very portable class that can be used very easily. Its purpose is to enable trimming and show a ToolTip over the TextBlock when the text is trimmed, like it is known from many applications.
The trimming detection has proven to be precise in my application. The tool tip is shown exactly when the trimming ellipsis is shown.
XAML usage
<!-- xmlns:ui="clr-namespace:Unclassified.UI" -->
<TextBlock Text="Demo" ui:TextBlockAutoToolTip.Enabled="True"/>
C# usage
var textBlock = new TextBlock { Text = "Demo" };
TextBlockAutoToolTip.SetEnabled(textBlock, true);
The complete class
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace Unclassified.UI
{
/// <summary>
/// Shows a ToolTip over a TextBlock when its text is trimmed.
/// </summary>
public class TextBlockAutoToolTip
{
/// <summary>
/// The Enabled attached property.
/// </summary>
public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
"Enabled",
typeof(bool),
typeof(TextBlockAutoToolTip),
new FrameworkPropertyMetadata(new PropertyChangedCallback(OnAutoToolTipEnabledChanged)));
/// <summary>
/// Sets the Enabled attached property on a TextBlock control.
/// </summary>
/// <param name="dependencyObject">The TextBlock control.</param>
/// <param name="enabled">The value.</param>
public static void SetEnabled(DependencyObject dependencyObject, bool enabled)
{
dependencyObject.SetValue(EnabledProperty, enabled);
}
private static readonly TrimmedTextBlockVisibilityConverter ttbvc = new TrimmedTextBlockVisibilityConverter();
private static void OnAutoToolTipEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
TextBlock textBlock = dependencyObject as TextBlock;
if (textBlock != null)
{
bool enabled = (bool)args.NewValue;
if (enabled)
{
var toolTip = new ToolTip
{
Placement = System.Windows.Controls.Primitives.PlacementMode.Relative,
VerticalOffset = -3,
HorizontalOffset = -5,
Padding = new Thickness(4, 2, 4, 2),
Background = Brushes.White
};
toolTip.SetBinding(UIElement.VisibilityProperty, new System.Windows.Data.Binding
{
RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.Self),
Path = new PropertyPath("PlacementTarget"),
Converter = ttbvc
});
toolTip.SetBinding(ContentControl.ContentProperty, new System.Windows.Data.Binding
{
RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.Self),
Path = new PropertyPath("PlacementTarget.Text")
});
toolTip.SetBinding(Control.ForegroundProperty, new System.Windows.Data.Binding
{
RelativeSource = new System.Windows.Data.RelativeSource(System.Windows.Data.RelativeSourceMode.Self),
Path = new PropertyPath("PlacementTarget.Foreground")
});
textBlock.ToolTip = toolTip;
textBlock.TextTrimming = TextTrimming.CharacterEllipsis;
}
}
}
private class TrimmedTextBlockVisibilityConverter : IValueConverter
{
// Source 1: https://stackoverflow.com/a/21863054
// Source 2: https://stackoverflow.com/a/25436070
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var textBlock = value as TextBlock;
if (textBlock == null)
return Visibility.Collapsed;
Typeface typeface = new Typeface(
textBlock.FontFamily,
textBlock.FontStyle,
textBlock.FontWeight,
textBlock.FontStretch);
// FormattedText is used to measure the whole width of the text held up by TextBlock container
FormattedText formattedText = new FormattedText(
textBlock.Text,
System.Threading.Thread.CurrentThread.CurrentCulture,
textBlock.FlowDirection,
typeface,
textBlock.FontSize,
textBlock.Foreground,
VisualTreeHelper.GetDpi(textBlock).PixelsPerDip);
formattedText.MaxTextWidth = textBlock.ActualWidth;
// When the maximum text width of the FormattedText instance is set to the actual
// width of the textBlock, if the textBlock is being trimmed to fit then the formatted
// text will report a larger height than the textBlock. Should work whether the
// textBlock is single or multi-line.
// The width check detects if any single line is too long to fit within the text area,
// this can only happen if there is a long span of text with no spaces.
bool isTrimmed = formattedText.Height > textBlock.ActualHeight ||
formattedText.MinWidth > formattedText.MaxTextWidth;
return isTrimmed ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
}
Behaviors are love, behaviors are life.
public class TextBlockAutoToolTipBehavior : Behavior<TextBlock>
{
private ToolTip _toolTip;
protected override void OnAttached()
{
base.OnAttached();
_toolTip = new ToolTip
{
Placement = PlacementMode.Relative,
VerticalOffset = 0,
HorizontalOffset = 0
};
ToolTipService.SetShowDuration(_toolTip, int.MaxValue);
_toolTip.SetBinding(ContentControl.ContentProperty, new Binding
{
Path = new PropertyPath("Text"),
Source = AssociatedObject
});
AssociatedObject.TextTrimming = TextTrimming.CharacterEllipsis;
AssociatedObject.AddValueChanged(TextBlock.TextProperty, TextBlockOnTextChanged);
AssociatedObject.SizeChanged += AssociatedObjectOnSizeChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.RemoveValueChanged(TextBlock.TextProperty, TextBlockOnTextChanged);
AssociatedObject.SizeChanged -= AssociatedObjectOnSizeChanged;
}
private void AssociatedObjectOnSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs)
{
CheckToolTipVisibility();
}
private void TextBlockOnTextChanged(object sender, EventArgs eventArgs)
{
CheckToolTipVisibility();
}
private void CheckToolTipVisibility()
{
if (AssociatedObject.ActualWidth == 0)
Dispatcher.BeginInvoke(
new Action(
() => AssociatedObject.ToolTip = CalculateIsTextTrimmed(AssociatedObject) ? _toolTip : null),
DispatcherPriority.Loaded);
else
AssociatedObject.ToolTip = CalculateIsTextTrimmed(AssociatedObject) ? _toolTip : null;
}
//Source: https://stackoverflow.com/questions/1041820/how-can-i-determine-if-my-textblock-text-is-being-trimmed
private static bool CalculateIsTextTrimmed(TextBlock textBlock)
{
Typeface typeface = new Typeface(
textBlock.FontFamily,
textBlock.FontStyle,
textBlock.FontWeight,
textBlock.FontStretch);
// FormattedText is used to measure the whole width of the text held up by TextBlock container
FormattedText formattedText = new FormattedText(
textBlock.Text,
System.Threading.Thread.CurrentThread.CurrentCulture,
textBlock.FlowDirection,
typeface,
textBlock.FontSize,
textBlock.Foreground) {MaxTextWidth = textBlock.ActualWidth};
// When the maximum text width of the FormattedText instance is set to the actual
// width of the textBlock, if the textBlock is being trimmed to fit then the formatted
// text will report a larger height than the textBlock. Should work whether the
// textBlock is single or multi-line.
// The width check detects if any single line is too long to fit within the text area,
// this can only happen if there is a long span of text with no spaces.
return (formattedText.Height > textBlock.ActualHeight || formattedText.MinWidth > formattedText.MaxTextWidth);
}
}
Usage:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:behavior="clr-namespace:MyWpfApplication.Behavior"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<TextBlock Text="{Binding Text}">
<i:Interaction.Behaviors>
<behavior:TextBlockAutoToolTipBehavior />
</i:Interaction.Behaviors>
</TextBlock>
</Window>
The needed extension methods:
public static class UITools
{
public static void AddValueChanged<T>(this T obj, DependencyProperty property, EventHandler handler)
where T : DependencyObject
{
var desc = DependencyPropertyDescriptor.FromProperty(property, typeof (T));
desc.AddValueChanged(obj, handler);
}
public static void RemoveValueChanged<T>(this T obj, DependencyProperty property, EventHandler handler)
where T : DependencyObject
{
var desc = DependencyPropertyDescriptor.FromProperty(property, typeof (T));
desc.RemoveValueChanged(obj, handler);
}
}
I think that you can create a converter that compares between the ActualWidth of textblock and it's DesiredSize.Width, and return Visibility.
Posted an alternative answer with attached property here, which I think is nicer than using a converter or a derived TextBlock control.
I used this from #pogosoma but with the CalculateIsTextTrimmed function from #snicker which is perfect
private static void SetTooltipBasedOnTrimmingState(TextBlock tb)
{
Typeface typeface = new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch);
FormattedText formattedText = new FormattedText(tb.Text, System.Threading.Thread.CurrentThread.CurrentCulture, tb.FlowDirection, typeface, tb.FontSize, tb.Foreground)
{ MaxTextWidth = tb.ActualWidth };
bool isTextTrimmed = (formattedText.Height > tb.ActualHeight || formattedText.MinWidth > formattedText.MaxTextWidth);
ToolTipService.SetToolTip(tb, isTextTrimmed ? tb.ToolTip : null);
}
I have a simple XAML file, it contains a Label whose Foreground property contains a binding:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Width="200" Height="100" >
<Label Content="Sampletext" Foreground="{Binding Path=Color}" />
</Grid>
When I load the template and apply a DataContext the Foreground still has the default value.
Is it possible to get bound foreground value without rendering the Grid?
// Load template
string templatePath = "/WpfApplication1;component/Template.xaml";
Grid grid = Application.LoadComponent(new Uri(templatePath, UriKind.Relative)) as Grid;
// Set dataContext
grid.DataContext = new { Color = Brushes.Green };
// Foregound still has default value
var foreground = ((Label)grid.Children[0]).Foreground;
Project can be downloaded here: http://dl.dropbox.com/u/21096596/WpfApplication1.zip
try
lblName.GetBindingExpression(Label.ForegroundProperty).UpdateTarget();
before
var foreground = ((Label)grid.Children[0]).Foreground;
There are automatic DataContext change notifications, the binding will update if the necessary conditions are met. One of them is that the control is loaded (IsLoaded == true) which is not the case in your code. The control will only load if you add it to the your UI somewhere.
Example test code:
private void Button_Click(object sender, RoutedEventArgs e)
{
Grid grid = null;
Action action = () =>
{
var foreground = ((Label)grid.Children[0]).Foreground;
MessageBox.Show(foreground.ToString());
grid.DataContext = new { Color = Brushes.Green };
foreground = ((Label)grid.Children[0]).Foreground;
MessageBox.Show(foreground.ToString());
};
grid = Application.LoadComponent(new Uri("Stuff/GridOne.xaml", UriKind.Relative)) as Grid;
if (grid.IsLoaded)
{
action();
}
else
{
grid.Loaded += (s, _) => action();
}
// This adds the grid to some StackPanel, if you do not do something like this
// nothing will happen since the control will not be loaded and thus the event
// will not fire, etc.
ControlStack.Children.Add(grid);
}
Why do you need the onetime binding? remove that, and it should work.
Wrap your DataContext in an object, and implement INotifyPropertyChanged, then the binding will update when the property changes, and there's no need to update the binding manually:
public class MyDataContext : INotifyPropertyChanged
{
private Brush color;
public Brush Color
{
get { return color; }
set
{
color = value;
RaisePropertyChanged("Color");
}
}
//implementation of PropertyChanged and RaisePropertyChanged omitted
}
and then update it like so:
var dc = new MyDataContext();
grid.DataContext = dc;
dc.Color = Brushes.Green; //this will trigger the NotifyPropertyChanged and update the binding
//color should be changed now
var foreground = ((Label)grid.Children[0]).Foreground;
Hopefully this helps...
If you want a property of a control to be binding to a property of DataContext but want to change the datacontext in runtime, there is a much more simple way to do it.
Create a ContentControl, then use ContentControl.ContentTemplate
<ContentControl Content=something>
<ContentControl.ContentTemplate>
<DataTemplate>
<Label Foreground="{Binding Path=Color}" />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Change the Content of the ContentControl instead of changing DataContext of the Label.