Format part of the text of TextBlock using iValueConverter - wpf

I want to make part of the text of a textblock bold. This is what i tried in the IValueConverter but it does not seem to work.
public class Highlighter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return null;
}
return "Question1:<Bold>Answer1</Bold>, Question2:<Bold>Answer2</Bold>, Question3:<Bold>Answer3</Bold>";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This does not make the Answer bold.
This is how i am using it in XAML.
<TextBlock Height="Auto" Width="Auto" MaxHeight="64" Text="{Binding Path=QuestionAnswer, Mode=OneWay, Converter={x:Static Highlighter}}" />
Is there a way i can achieve this by formatting the text or by sending the TextBlock to the converter?

It is definitely possible to do with TextBlock control, but considering all the efforts you might want to switch to other control (ItemsControl for example).
Anyway, here is a solution. There are actually several problems to solve:
TextBlock.Text property is string, and you can't assign preformatted text to it
TextBlock.Inlines can accept formatted text, but it is read-only property
You'll have to format text yourself (probably there are easy ways to parse text with tags and produce formatted output as a collection of Inline objects, but I don't know any)
You can create an attached property to deal with the first 2 problems:
public static class TextBlockEx
{
public static Inline GetFormattedText(DependencyObject obj)
{
return (Inline)obj.GetValue(FormattedTextProperty);
}
public static void SetFormattedText(DependencyObject obj, Inline value)
{
obj.SetValue(FormattedTextProperty, value);
}
public static readonly DependencyProperty FormattedTextProperty =
DependencyProperty.RegisterAttached(
"FormattedText",
typeof(Inline),
typeof(TextBlockEx),
new PropertyMetadata(null, OnFormattedTextChanged));
private static void OnFormattedTextChanged(
DependencyObject o,
DependencyPropertyChangedEventArgs e)
{
var textBlock = o as TextBlock;
if(textBlock == null) return;
var inline = (Inline)e.NewValue;
textBlock.Inlines.Clear();
if(inline != null)
{
textBlock.Inlines.Add(inline);
}
}
}
XAML would change just a bit:
<TextBlock local:TextBlockEx.FormattedText="{Binding Path=QuestionAnswer,
Mode=OneWay,
Converter={x:Static Highlighter}}" />
Note that you'll need to map you namespace where TextBlockEx is declared in xmlns:local="clr-namepace:<namespace_name>" in XAML.
Now you need to construct formatted text in converter instead of plain text to solve the last problem:
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if(value == null)
{
return null;
}
var span = new Span();
span.Inlines.Add(new Run("Question1: "));
span.Inlines.Add(new Run("Answer1") { FontWeight = FontWeights.Bold });
span.Inlines.Add(new Run(", "));
span.Inlines.Add(new Run("Question2: "));
span.Inlines.Add(new Run("Answer2") { FontWeight = FontWeights.Bold });
span.Inlines.Add(new Run(", "));
span.Inlines.Add(new Run("Question3: "));
span.Inlines.Add(new Run("Answer3") { FontWeight = FontWeights.Bold });
return span;
}

Ya, something like this should put ya on track;
<TextBlock>
<Run Text="Question / Binding / Whatever..."/>
<Run Text="Answer / Binding / Whatever..." FontWeight="Bold"/>
</TextBlock>

Related

WPF/XAML: How to make all text upper case in TextBlock?

I want all characters in a TextBlock to be displayed in uppercase
<TextBlock Name="tbAbc"
FontSize="12"
TextAlignment="Center"
Text="Channel Name"
Foreground="{DynamicResource {x:Static r:RibbonSkinResources.RibbonGroupLabelFontColorBrushKey}}" />
The strings are taken through Binding. I don't want to make the strings uppercase in the dictionary itself.
Or use
Typography.Capitals="AllSmallCaps"
in your TextBlock definition.
See here: MSDN - Typography.Capitals
EDIT:
This does not work in Windows Phone 8.1, only in Windows 8.1 ...
Implement a custom converter.
using System.Globalization;
using System.Windows.Data;
// ...
public class StringToUpperConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is string )
{
return ((string)value).ToUpper();
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
Then include that in your XAML as a resource:
<local:StringToUpperConverter x:Key="StringToUpperConverter"/>
And add it to your binding:
Converter={StaticResource StringToUpperConverter}
You can use an attached property like this:
public static class TextBlock
{
public static readonly DependencyProperty CharacterCasingProperty = DependencyProperty.RegisterAttached(
"CharacterCasing",
typeof(CharacterCasing),
typeof(TextBlock),
new FrameworkPropertyMetadata(
CharacterCasing.Normal,
FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.NotDataBindable,
OnCharacterCasingChanged));
private static readonly DependencyProperty TextProxyProperty = DependencyProperty.RegisterAttached(
"TextProxy",
typeof(string),
typeof(TextBlock),
new PropertyMetadata(default(string), OnTextProxyChanged));
private static readonly PropertyPath TextPropertyPath = new PropertyPath("Text");
public static void SetCharacterCasing(DependencyObject element, CharacterCasing value)
{
element.SetValue(CharacterCasingProperty, value);
}
public static CharacterCasing GetCharacterCasing(DependencyObject element)
{
return (CharacterCasing)element.GetValue(CharacterCasingProperty);
}
private static void OnCharacterCasingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is System.Windows.Controls.TextBlock textBlock)
{
if (BindingOperations.GetBinding(textBlock, TextProxyProperty) == null)
{
BindingOperations.SetBinding(
textBlock,
TextProxyProperty,
new Binding
{
Path = TextPropertyPath,
RelativeSource = RelativeSource.Self,
Mode = BindingMode.OneWay,
});
}
}
}
private static void OnTextProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.SetCurrentValue(System.Windows.Controls.TextBlock.TextProperty, Format((string)e.NewValue, GetCharacterCasing(d)));
string Format(string text, CharacterCasing casing)
{
if (string.IsNullOrEmpty(text))
{
return text;
}
switch (casing)
{
case CharacterCasing.Normal:
return text;
case CharacterCasing.Lower:
return text.ToLower();
case CharacterCasing.Upper:
return text.ToUpper();
default:
throw new ArgumentOutOfRangeException(nameof(casing), casing, null);
}
}
}
}
Then usage in xaml will look like:
<StackPanel>
<TextBox x:Name="TextBox" Text="abc" />
<TextBlock local:TextBlock.CharacterCasing="Upper" Text="abc" />
<TextBlock local:TextBlock.CharacterCasing="Upper" Text="{Binding ElementName=TextBox, Path=Text}" />
<Button local:TextBlock.CharacterCasing="Upper" Content="abc" />
<Button local:TextBlock.CharacterCasing="Upper" Content="{Binding ElementName=TextBox, Path=Text}" />
</StackPanel>
If it's not a big deal you could use TextBox instead of TextBlock like this:
<TextBox CharacterCasing="Upper" IsReadOnly="True" />
While there's already a great answer here that uses a converter, I'm providing an alternative implementation that simplifies the conversion to a single line (thanks to null coalescing), as well as making it a subclass of MarkupExtension so it's easier to use in XAML.
Here's the converter...
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace IntuoSoft.Wpf.Converters {
[ValueConversion(typeof(string), typeof(string))]
public class CapitalizationConverter : MarkupExtension, IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> (value as string)?.ToUpper() ?? value; // If it's a string, call ToUpper(), otherwise, pass it through as-is.
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
public override object ProvideValue(IServiceProvider serviceProvider) => this;
}
}
And here's how you use it (Note: This assumes the above namespace is prefixed with is in your XAML):
<TextBlock Text={Binding SomeValue, Converter={is:CapitalizationConverter}}" />
Because it's a MarkupExtension subclass, you can simply use it right where/when it's needed. No need to define it in the resources first.
I use a character casing value converter:
class CharacterCasingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var s = value as string;
if (s == null)
return value;
CharacterCasing casing;
if (!Enum.TryParse(parameter as string, out casing))
casing = CharacterCasing.Upper;
switch (casing)
{
case CharacterCasing.Lower:
return s.ToLower(culture);
case CharacterCasing.Upper:
return s.ToUpper(culture);
default:
return s;
}
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

Binding the Path Property of a Binding

is it possible to bind the Path property of a binding to another property?
I want to realize this code:
Text="{Binding Path={Binding Path=CurrentPath}}"
So I can adjust dynamically to which Property my actual binding is refering.
Thanks for your Help
Jonny
I worked it out on myself.
Heres the solution, I hope it might help anyone got the same problem like me.
public class CustomBindingBehavior : Behavior<FrameworkElement>
{
public bool IsBinding
{
get
{
return (bool)GetValue(IsBindingProperty);
}
set
{
SetValue(IsBindingProperty, value);
}
}
public string PropertyPath
{
get
{
return (string)GetValue(PropertyPathProperty);
}
set
{
SetValue(PropertyPathProperty, value);
}
}
public static DependencyProperty
PropertyPathProperty = DependencyProperty.Register("PropertyPath", typeof(string),
typeof(CustomBindingBehavior), null);
public static DependencyProperty
IsBindingProperty = DependencyProperty.Register("IsBinding", typeof(bool),
typeof(CustomBindingBehavior), null);
protected override void OnAttached()
{
if (AssociatedObject is TextBlock)
{
var tb = AssociatedObject as TextBlock;
tb.Loaded += new RoutedEventHandler(tb_Loaded);
}
}
private void tb_Loaded(object sender, RoutedEventArgs e)
{
AddBinding(sender as TextBlock, TextBlock.TextProperty);
}
private void AddBinding(DependencyObject targetObj, DependencyProperty targetProp)
{
if (IsBinding)
{
Binding binding = new Binding();
binding.Path = new PropertyPath(this.PropertyPath, null);
BindingOperations.SetBinding(targetObj, targetProp, binding);
}
else
{
targetObj.SetValue(targetProp, this.PropertyPath);
}
}
}
And heres the implementation in XAML:
<TextBlock >
<i:Interaction.Behaviors>
<behaviors:CustomBindingBehavior PropertyPath="{Binding Path=HeaderPropertyBinding}" IsBinding="{Binding Path=HeaderIsBinding}" />
</i:Interaction.Behaviors>
</TextBlock>
Greetings
Jonny
As other posters have mentioned, you can only set a binding on a dependency property - which path is not. The underlying reason is that xaml is source code that gets compiled. At compile time the compiler has no idea what the value of 'CurrentPath' is, and would not be able to compile. Essentially what you are looking to do is runtime reflection of a property value - which could be done using another property in the ViewModel you are binding to, or using a converter.
ViewModel:
public string CurrentValue
{
get
{
var property = this.GetType().GetProperty(CurrentPath);
return property.GetValue(this, null);
}
}
Using a converter:
public class CurrentPathToValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var viewModel = (ViewModel)value;
var property = viewModel.GetType().GetProperty(viewModel.CurrentPath);
var currentValue = property.GetValue(viewModel, null);
return currentValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Of couse these only work if you want to get a simple property of the object - if you want to get something more complex your reflection code is going to get a lot more complex.
Unless you are building something like a property grid, or for some other reason you actually want to introspect the objects running in your application, I would suggest you revisit your design, as reflection is really only suited to a few situations.
Path is not a dependency property, therefore the binding will not work.
Perhaps you could bind to a property that returns another property based on a switch statement and bind to that. Change the 'switch' property and you change the output of the other property.
Just don't forget to include your NotifyPropertyChanged stuff in the switch property for the bound property otherwise your view will not update.
e.g.
private int _mySwitch;
//Set this to determine what the other property will return.
public int SwitchProperty
{
get { return _mySwitch; }
set
{
_mySwitch = value;
NotifyPropertyChanged("MySwitchableProperty");
}
}
public String PropertyA { get; set; }
public String PropertyB { get; set; }
//Bind to this property
public String MySwitchableProperty
{
get
{
switch (SwitchProperty)
{
case 1:
return PropertyA;
break;
case 2:
return PropertyB;
break;
default :
return String.Empty;
break;
}
}
}
I think converter can helps your.
Expample
First control
Text="{Binding Path=CurrentPath}"
Second control
Text="{Binding Path=CurrentPath, Convertor={converters:MyConvertor}}"
Base converter
public abstract class ConvertorBase<T> : MarkupExtension, IValueConverter
where T : class, new()
{
public abstract object Convert(object value, Type targetType, object parameter,
CultureInfo culture);
public virtual object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
#region MarkupExtension members
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (_converter == null)
_converter = new T();
return _converter;
}
private static T _converter = null;
#endregion
}
MyConverter
public class MyConverter: ConvertorBase<MyConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (string)value.Equals("blabla") ? "Yes" : "No"; // here return necessary parametr
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}

How to change the font color of a datatemplated wpf listbox using a converter?

I have a DataTemplate that is used by a listbox:
<local:BooleanToFontColorConverter x:Key="boolToFontColor" />
<DataTemplate x:Key="ListBox_DataTemplateSpeakStatus">
<Label Width="Auto">
<TextBlock Name="MY_TextBlock" Text="Hello!" Foreground="{Binding Path=MY_COLOR, Converter={StaticResource boolToFontColor}}" />
</Label>
</DataTemplate>
MY_COLOR is the following bit of code:
public class Packet_Class : INotifyPropertyChanged
{
private bool _my_color = false;
public bool MY_COLOR { get { return _my_color; }
set { _my_color = value; RaisePropertyChanged("MY_COLOR"); } }
}
and then when appropriate I set the property, which I think would fire the RaisePropertyChanged function
myPacketClass.MY_COLOR = true;
while boolToFontColor is "trying" to use this bit:
public class BooleanToFontColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (value is Boolean)
{
return ((bool)value) ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Black);
}
return new SolidColorBrush(Colors.Black);
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
When I change the value of MY_COLOR from true to false, or vice versa, I see no visible changes in my text foreground color during runtime. Is anyone able to give advice as to where I am going wrong? Much appreciated and thank you in advance.
EDIT:
Some additional information to attempt to provide more clarity. I am using my DataTemplate in a ListBox like this:
<ListBox x:Name="MyUserList" ItemTemplate="{StaticResource ListBox_DataTemplateSpeakStatus}" SelectionMode="Extended" />
And in my WPF Window element I set my local namespace to the namespace that my mainwindow.xaml.cs is encapsulated in:
xmlns:local ="clr-namespace:My_NameSpace"
the RaisePropertyChanged method should raise the PropertyChanged event define in the interface and look like:
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged (string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
the converter:
public class BooleanToFontColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
if (value is Boolean)
{
return ((bool)value) ? new SolidColorBrush(Colors.Red) : new SolidColorBrush(Colors.Black);
}
return new SolidColorBrush(Colors.Black);
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
You have to use a SolidColorBrush to make it work.
It works on my environment, let me know if you encounter any trouble.

Bind a Silverlight TabControl to a Collection

I have a Collection of Model-objects in my ViewModel. I would like to be able to bind a TabControl to these and use a DataTemplate to extract the information from the Model-objects. When I try to do this I get the errormessage: Unable to cast object of type Model to object of type TabItem. After spending some time looking for a solution I found the following:
The Silverlight TabControl is
broken. Use a combination of ListBox
and ContentControl to mimic the
behaviour of a TabControl. (Means
that I have to skin the ListBox to
look like a TabControl)
TabControl does not override
PrepareContainerForItemOverride and
the solution is to make a
Converter. (Not so good because I
then need to specify the type of the
convertee in the Converter)
Anyone know any better solution?
XAML
<sdk:TabControl ItemsSource="{Binding Items, ElementName=MyControl}">
<sdk:TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</sdk:TabControl.ItemTemplate>
</sdk:TabControl>
C#
public ObservableCollection<Model> Items { get; set; }
public ViewModel()
Items = new ObservableCollection<Model>{
new Model { Name = "1"},
new Model { Name = "2"},
new Model { Name = "3"},
new Model { Name = "4"}
};
}
Suggested Converter:
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
List<TabSource> source = value as List<TabSource>;
if (source != null)
{
List<TabItem> result = new List<TabItem>();
foreach (TabSource tab in source)
{
result.Add(new TabItem()
{
Header = tab.Header,
Content = tab.Content
});
}
return result;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Create converter
public class SourceToTabItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
var source = (IEnumerable)value;
if (source != null)
{
var controlTemplate = (ControlTemplate)parameter;
var tabItems = new List<TabItem>();
foreach (object item in source)
{
PropertyInfo[] propertyInfos = item.GetType().GetProperties();
//тут мы выбираем, то поле которое будет Header. Вы должны сами вводить это значение.
var propertyInfo = propertyInfos.First(x => x.Name == "name");
string headerText = null;
if (propertyInfo != null)
{
object propValue = propertyInfo.GetValue(item, null);
headerText = (propValue ?? string.Empty).ToString();
}
var tabItem = new TabItem
{
DataContext = item,
Header = headerText,
Content =
controlTemplate == null
? item
: new ContentControl { Template = controlTemplate }
};
tabItems.Add(tabItem);
}
return tabItems;
}
return null;
}
catch (Exception)
{
return null;
}
}
/// <summary>
/// ConvertBack method is not supported
/// </summary>
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("ConvertBack method is not supported");
}
Create ControlTemplate:
<ControlTemplate x:Key="MyTabItemContentTemplate">
<StackPanel>
<TextBlock Text="{Binding Path=name}" />
</StackPanel>
</ControlTemplate>
And binding convert, controltemplate
<controls:TabControl x:Name="tabControl"
ItemsSource="{Binding ElementName=tabControl,
Path=DataContext,
Converter={StaticResource ConverterCollectionToTabItems},
ConverterParameter={StaticResource MyTabItemContentTemplate}}">
</controls:TabControl>
taken from the blog binding-tabcontrol

WPF/XAML - hide image if another control's text property is empty/not set

i'm new to wpf and this is my first attempt of creating a custom user control. its purpose is to display two values (myText1 and myText2) with their corresponding images (myimage1, myimage2). sometimes, one of these values is not set and therefore oneimage should be hidden as well. here's my code so far:
Window1.xaml
<local:myControl myText2="Hello World!" />
myControl.xaml
<TextBlock Text="{Binding ElementName=myControl,Path=myText1}" />
<Image Source="myimage1.jpg" />
<TextBlock Text="{Binding ElementName=myControl,Path=myText2}" />
<Image Source="myimage2.jpg" />
myText1 was not set in window1.xaml and therefore the textblock remains empty. but the image is still displayed. which lines of code am i missing to hide the image if myText1 (or myText2) was not set in window1.xaml?
You have write converter for text to visibility
public class TextToVisibilityConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is string && targetType == typeof(bool))
{
if (value.ToString().Equals(string.Empty))
return Visibility.Hidden;
else
return Visibility.Hidden;
}
else
{
return null;
}
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Visibility && targetType == typeof(string))
{
if ((Visibility)value == Visibility.Visible)
{
return "Text";
}
else
{
return string.Empty;
}
}
else
{
return null;
}
}
}
And in XAML < TextToVisibilityConverter x:Key="myCnverter"/>
Couple of small mistakes there:
if (value is string && targetType == typeof(bool))
{
if (value.ToString().Equals(string.Empty))
return Visibility.Hidden;
else
return Visibility.Hidden;
}
Should be
if (value is string && targetType == typeof(Visibility))
{
if (value.ToString().Equals(string.Empty))
return Visibility.Hidden;
else
return Visibility.Visible;
}
You need the following usings:
using System.Windows;
using System.Windows.Data;
You may also consider returning Visibility.Collapsed rather than Visibility.Hidden
Once you create the right converter then is easy.
And not much answers also got that Text.IsEmpty is available for TextBlock Text property
I created a BooleanVisibilityConverter that depends on a parameter the boolean against True or False.
Gives you the !True flexibility that is missing on xaml.
public class BooleanVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility v = Visibility.Collapsed;
bool checkValue = true;
if(parameter != null)
{
checkValue = Boolean.Parse(parameter.ToString());
}
if(value.Equals(checkValue))
{
v = Visibility.Visible;
}
return v;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then in your xaml import that namespace into:
xmlns:conv="clr-namespace:ConvertersNamespace"
Create the converter into the Resources:
<UserControl.Resources>
<conv:BooleanVisibilityConverter x:Key="bool2vis" />
</UserControl.Resources>
Then just use into your design:
<TextBlock Text="{Binding ElementName=myControl,Path=myText1}" x:Name="txtBlock"/>
<Image Source="myimage1.jpg"
Visibility="{Binding ElementName=txtBlock,Path=Text.IsEmpty,
Converter={StaticResource bool2vis},ConverterParameter=False}"/>

Resources