WPF : Conditional templating of textblock - wpf

I have a bunch of textblocks in an itemscontrol... I need to know how can I underline the text in the textblock based on whether the text is available in a list in the data model..
Sounds very simple to me...but I have been googling since the past 8 hrs...
Can I use datatriggers and valueconverters for this purpose? If yes, then how can I execute the method which lies in the viewModel (the method which helps me to check whther a given a text exists in the data model list)...
Even if I go for conditional templating....how do I access the list which lies in my model (the viewmodel can fetch it...but then how do i access the viewmodel?)..
This should be a fairly easy thing to do...Am I really missing something very simple here?? :)
I am following the MVVM pattern for my application..

One way is to use a multivalueconverter which is a class that implements IMultiValueConverter. A multivalueconverter allows you to bind to several values which means that you can get a reference to both your viewmodel and the text of your TextBlock in your valueconverter.
Assuming that your viewmodel has a method called GetIsUnderlined that returns true or false indicating whether or not the text should be underlined your valueconverter can be implemented along these lines:
class UnderlineValueConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var viewmodel = values[0] as Window1ViewModel;
var text = values[1] as string;
return viewmodel.GetIsUnderlined(text) ? TextDecorations.Underline : null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
You can use this valueconverter in the following way for a TextBlock:
<Grid x:Name="grid1" >
<Grid.Resources>
<local:UnderlineValueConverter x:Key="underlineValueConverter" />
</Grid.Resources>
<TextBlock Text="Blahblah">
<TextBlock.TextDecorations>
<MultiBinding Converter="{StaticResource underlineValueConverter}">
<Binding /> <!-- Pass in the DataContext (the viewmodel) as the first parameter -->
<Binding Path="Text" RelativeSource="{RelativeSource Mode=Self}" /> <!-- Pass in the text of the TextBlock as the second parameter -->
</MultiBinding>
</TextBlock.TextDecorations>
</TextBlock>
</Grid>

Related

In WPF XAML, is it possible to bind a ViewModel property to a DependencyProperty defined in a Converter without using `x:Name`? [duplicate]

This question already has answers here:
Binding ConverterParameter
(3 answers)
Closed 27 days ago.
In the following xaml sample source, I am trying to bind the A property in the SampleViewModel to the B property, which is a DependencyProperty in the SampleConverter.
However, when I do this, I get a XAML bind error and the Data Context is displayed as null.
I know it is possible to get the Data Context using x:Name, but is it possible to get the Data Context without using x:Name?
<Window
x:Class="WpfApp1.BindPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Window.DataContext>
<local:SampleViewModel />
</Window.DataContext>
<StackPanel>
<StackPanel.Height>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Mode=Self}">
<Binding.Converter>
<local:SampleConverter B="{Binding A}" />
</Binding.Converter>
</Binding>
</StackPanel.Height>
</StackPanel>
</Window>
I should mention that with RelativeSource I could not get other than myself (in this case, other than the SampleConverter).
That isn't how you use converters.
I don't know what local:SampleConverter is exactly but it has a property B
B="{Binding A}"
The binding provides the value, you may also bind a command parameter
Here's an example
<Image Name="GridImage"
Visibility="{Binding AppSettings.ShowGrid
, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
This is going to set the Visibility property, the binding is to AppSetting.ShowGrid which is in the datacontext rather than converter. The BooleanToVibilityConverter is taking a bool from ShowGrid and converts it to a Visibility.
If you wanted to bind multiple properties then you can use a multiconverter with a multibinding.
<MultiBinding Converter="{ui:AllMultiplyMultiConverter}" StringFormat="{}{0:n0}">
<Binding Path="Value" ElementName="turnTime"/>
<Binding Path="MoveRate"/>
</MultiBinding>
A multiconverter receives an array object[] for those values.
Since markup extension and ivalueconverter are not dependency objects you would need to reference the parent object to use a dependency property.
You could add dependency properties to your window and reference them in a markup extension that's also a valueconverter.
Consider this value converter that's also a markup extension.
public class AddConverter : MarkupExtension, IValueConverter
{
public double ValueToAdd { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double bound = (Double)value;
return bound + ValueToAdd;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
ValueToAdd is just a property, you can't bind it.
You can get a reference in that ProvideValue to the parent dependency object. Hence window or usercontrol.
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
That target is your control and you can grab dependency properties off that. You could (say) set ValueToAdd in there and use it in the converter. You could cast the datacontext of the targetobject and read values off properties directly.
This is a very complicated approach. I've never had the need for this myself and I would recommend multibinding and multiconverter instead.

Manage the targetType of a Binding in a MultiBinding

So, I have a multi-binding with a converter that takes in some values and finds the max of them. The problem is that one of the bindings utilises a converter that expects a double target type, while the binding has an object target type. I was wondering if there was any way to modify the target type of a binding in any way.
Below is an approximation of my xaml:
<TextBlock>
<TextBlock.Width>
<MultiBinding Converter="{StaticResource _maxValueConverter}">
<Binding Source="{StaticResource _constantZeroValue}"/>
<Binding Path="ActualWidth"
ElementName="_previousTextBlock"
Converter="{StaticResource _requiresDoubleTargetConverter}"/>
</MultiBinding>
</TextBlock.Width>
</TextBlock>
So basically if there is any way to tell the second binding that it is outputting to a double value, that'd be great.
Minimal Verifiable Complete Example:
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApplication1"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<StackPanel.Resources>
<sys:Double x:Key="constantZero">0</sys:Double>
<local:RequiresDoubleTargetConverter x:Key="requiresDoubleTargetConverter" />
<local:MaxValueConverter x:Key="maxValueConverter" />
</StackPanel.Resources>
<Border x:Name="topBorder"
BorderThickness="1"
BorderBrush="Black"
HorizontalAlignment="Left">
<TextBlock x:Name="topTextBlock"
Background="Aqua"
Text="{Binding TopText}" />
</Border>
<Border BorderThickness="1"
BorderBrush="Black"
MinWidth="100"
HorizontalAlignment="Left">
<TextBlock Background="ForestGreen"
Text="{Binding BottomText}"
TextWrapping="Wrap"
MinWidth="100">
<TextBlock.Width>
<MultiBinding Converter="{StaticResource maxValueConverter}">
<MultiBinding.Bindings>
<Binding Path="ActualWidth" ElementName="topTextBlock" Converter="{StaticResource requiresDoubleTargetConverter}" />
<Binding Source="{StaticResource constantZero}" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Width>
</TextBlock>
</Border>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string TopText
{
get { return "Hello World!"; }
}
public string BottomText
{
get { return "hi earth."; }
}
public MainWindow()
{
InitializeComponent();
}
}
public class RequiresDoubleTargetConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// I am looking for a way to manually ensure that "targetType == typeof(double)" evaluates to true.
if (targetType != typeof(double))
{
return null;
}
else
{
// Actual converter performs this calculation.
return (double)value - 14;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Irrelevant method for our purposes.
throw new NotImplementedException();
}
}
public class MaxValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double max = double.NegativeInfinity;
foreach (object value in values)
{
if (value is double)
{
max = Math.Max((double)value, max);
}
else
{
Debug.Fail("All values must be doubles");
}
}
return max;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
// Irrelevant method for our purposes.
throw new NotImplementedException();
}
}
}
This was created using Visual Studio 2015, and is verified to show the erroneous behaviour. What I am trying to determine is if it is possible to manually set the targetType of the RequiresDoubleTargetConverter from the xaml.
Bindings operate on object, as far as the type system is concerned. If you want a specific type, you'll need to ensure that yourself.
However you could use the target type passed to the converter to determine which type is required, and modify the converter's return value accordingly.
So is there a way to set the target type of the converter manually, or am I stuck with it being object?
You are stuck with it being object as the signature of the Convert method is always the same, i.e. it accepts an object[] of values and nothing else.
You will have to cast values[1] to a double in your Convert method (because the method will always and only be passed values of type object):
double d = (double)values[1];
I've come up with a way around this, but it only works if you have access to the converter you're using on the MultiBinding itself (or you can add one.) as well as a little extra effort if it also uses a ConverterParameter, TargetNullValue and/or a StringFormat.
The trick is when you add the child Binding to the MultiBinding, you remove the Converter, ConverterParameter, TargetNullValue and StringFormat values from that child binding and store them somewhere that's accessible by the Convert method for the MultiBinding's converter. (We use a wrapper MarkupExtension to simulate the MultiBinding so we have access to everything before they're actually applied as once they are, you can't change them.)
Then in the MultiBinding's Convert method, you're now getting the raw, not-yet-converted/formatted/coalesced value from the child binding, but you also have the ultimate target that you need (double in this example) as it was handed to the Convert method for the MultiBinding that you're in.
With that information, you then call the child converter's Convert method manually, passing in the not-yet-converted value, the targetType (passed in to you), and the childConverterParameter.
You take the result of that call, and if null, return the TargetNullValue from the child binding.
If it's not null and both targetType is a string and you have a String Format, finally format the results.
Here's pseudo-code (i.e. off the top of my head. Prolly lots of syntax errors, etc. For the actual code, you can see me using it in my DynamicResourceBinding class that I have up on StackOverflow here.)
// Convert function for the MultiBinding
private object Convert(object[] values, Type targetType, object parameter, Culture culture){
var rawChildBindingResult = values[0]; // assuming it's in the first position
var convertedChildBindingResult = childConverter(rawChildBindingResult, targetType, childConverterParameter, culture);
if(convertedChildBindingResult == null)
convertedChildBindingResult = childTargetNullValue;
else if(targetType == typeof(string) && childStringFormat != null)
convertedChildBindingResult = string.Format(childStringFormat, convertedChildBindingResult);
// Now do whatever you would with the 'convertedChildBindingResult' as if things were normal
}
Again, take a look at the link to see it in context.
Hope this helps!

Set Multibing for text - get and set

I would like to bind my TextBox.Text to two different sources.
I have 2 ViewModels, one is general ViewModel and one is more specific (which is inherit from its parent).
Both ViewModels has a property called "Hotkey".
I would like to bind my TextBox.Text so it will get the value from the general ViewModel and set it to the specific ViewModel.
I tried the following:
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center" Foreground="#000">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource test}">
<Binding Path="DataContext.Hotkey" RelativeSource="{RelativeSource AncestorType={x:Type MetroStyle:MetroWindow}}" Mode="OneWay" />
<Binding Path="Hotkey" Mode="OneWayToSource"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
It does get the value from the general ViewModel, but it doesn't set its value to the specific one (which inherits from the parent)
I believe the problem may be in the Converter you used for MultiBinding, I've just tried a simple demo and looks like that Converter should be implemented like this:
public class TestConverter : IMultiValueConverter
{
private bool justConvertedBack;
object IMultiValueConverter.Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (justConvertedBack) {
justConvertedBack = false;
return Binding.DoNothing;
}
return values[0];
}
object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
justConvertedBack = true;
return new object[] {null, value};
}
}
It happens that after the ConvertBack has been done, the Convert will be triggered and keep the Text of your TextBox unchanged (although you tried deleting/modifying it before). So we need some flag justConvertedBack here to prevent that from occurring.
Currently changing the source from the general ViewModel will change the TextBox's Text but does not update the source from the specific ViewModel. However if setting/typing some value for the TextBox's Text will update the source from the specific ViewModel but won't reflect that value back to the source from the general ViewModel. I hope that behavior is what you want.

Converting controls from MM to Inches in XAML

I have a lot of controls in my XAML in the form of
TextBlock: TextBox.
So for example:
XSize(mm):25.4
YSize(mm):50.8
etc
Now when the user clicks on an option to use imperial units I want to change all textBlocks + textBoxes to something like
XSize(in):1
YSize(in):2
etc
What is the best way to go about this ?
Try to use Converters. Create a MM to Inch converter and use that converter to change the values when the user inputs a value on the certain value.
Sample usage of a converter. You have to define a static resource under the resources of your view for the controls you want to use the converter with
MainWindow.xaml
<Window.Resources>
<spikes:Convertaaa x:Key="Convertaaa" />
</Window.Resources>
<ComboBox x:Name="OptionsToChoose"/>
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource Convertaaa}">
<Binding ElementName="OptionsToChoose" Path="SelectedValue"/>
<Binding RelativeSource="{RelativeSource Self}" Path="Text"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
Converter.cs
public class Convertaaa : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//Implement conversion here. values[0] will give you the selected option values[1] will give you the value to convert, then do a return
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This assumes that you do know MVVM pattern in WPF and Bindings. If you are doing behind the code, then you probably want to hook up to the TextChanged of your TextBox and instantiate a new Converter Class and call the Convert method and set the Text property of the Textbox.

Change WPF Calendar control day name format

I am currently using WPF Calendar control. The Day names are displayed as Su, Mo, Tu, etc. But I want those to be displayed as Sun, Mon, Tue, etc.. I found there is no data template property to achieve this one.
Any suggestion will be greatly appreciated.
Thanks
I've looked into this and unfortunately, I don't think that you will be able to achieve what you want.
To start with, I found the StringFormat to show three character day names in the Custom Date and Time Format Strings page at MSDN:
StringFormat=ddd
Then I thought that you might find a solution for the rest of your problem in the Custom date format for WPF Calendar CalendarItems post. So, adapting the idea from #Quartermeister, I could tried the following:
<Calendar>
<Calendar.CalendarButtonStyle>
<Style TargetType="primitives:CalendarButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="primitives:CalendarButton">
<primitives:CalendarButton>
<TextBlock Text="{Binding Day, StringFormat=ddd}"/>
</primitives:CalendarButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Calendar.CalendarButtonStyle>
</Calendar>
As you can imagine, I was way off because this answered a different problem. So I went back to MSDN to find the default ControlTemplate for the Calendar control to experiment further. If you look at that ControlTemplate, you will see a Style named CalendarItemStyle.
In that Style is a ControlTemplate and in its Resources section, you will see a DataTemplate with the key {x:Static CalendarItem.DayTitleTemplateResourceKey}. Finally, in that DataTemplate, you will see the TextBlock that is responsible for displaying the day names:
<TextBlock Foreground="#FF333333"
FontWeight="Bold"
FontSize="9.5"
FontFamily="Verdana"
Margin="0,6,0,6"
Text="{Binding}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
I increased the size of the day names, so we can be sure that that is the correct TextBlock. (Ignore the odd looking Buttons at the top - they are just like that because I didn't copy their ControlTemplates):
Now if you look at the Binding in that TextBlock, you will see that unfortunately, it is set to {Binding}. This means that it is using the whole data bound value, rather than setting a particular StringFormat on a DateTime object. This means that we cannot use a StringFormat because the data bound value is already a string.
Finally, I solved it with following converter,
public class CalendarDayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var daynames = CultureInfo.CurrentCulture.DateTimeFormat.DayNames;
string dayname = value.ToString();
return daynames.First(t => t.StartsWith(dayname)).Substring(0, 3);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Late to the party here, but I came up with a slightly different solution that, hopefully, will work a bit more reliably internationally. It's a two part thing. First, make a template for the day name as mentioned by #Sheridan above:
<DataTemplate x:Key="{x:Static CalendarItem.DayTitleTemplateResourceKey}">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource CalendarDayNameConverter}">
<Binding Path="(Grid.Column)" RelativeSource="{RelativeSource Self}" Mode="OneWay"/>
<Binding Path="FirstDayOfWeek" RelativeSource="{RelativeSource AncestorType={x:Type Calendar}}" Mode="OneWay"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
Of course, format it as per your requirements. The trick here is that the Text is bound to Grid.Column instead of the usual data context value (the two-letter day name provided by WPF). The grid column number is set by internal WPF logic and should be reliable. We have to use an IMultiValueConverter because we depend on the FirstDayOfWeek value from the parent Calendar control too:
public sealed class CalendarDayNameConverter : IMultiValueConverter
{
/// <inheritdoc/>
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values?.Length > 0 && values[0] is int index)
{
var dayNames = culture?.DateTimeFormat?.AbbreviatedDayNames;
if (dayNames?.Length > 0)
{
var firstDayOfWeek = values.Length > 1 && values[1] is DayOfWeek d ? d : culture.DateTimeFormat.FirstDayOfWeek;
return dayNames[(index + (int)firstDayOfWeek) % dayNames.Length];
}
}
return DependencyProperty.UnsetValue;
}
/// <inheritdoc/>
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
null;
}
I've looked into it aswell.
The only solution I have seen was localizing the calendar with a custom localisation dictionary, but unfortunately there are no keys given, we could use.
Decompiling the calendar control I found out, that the calendar control uses a native function (nativeGetCalendarData()) to localize the calendar, so I wont be able to retrieve those keys the easy way :(
Refer this site nullskull.com. The Downloadable Source contain a day name converter and description

Resources