Converting controls from MM to Inches in XAML - wpf

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.

Related

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.

WPF content binding - want to display two attributes of a ListView in a StatusBarItem

I have a StatusBarItem which I want to display the following message at any given moment "X/Y" where X is the number of the row of the currently selected element, and Y is the row count.
Now, if I use the Content="{Binding ElementName=lvTabela, Path=SelectedIndex}" code in xaml I am able to get the first attribute to display, but I'm not sure how I can get both.
I suppose I could always use two StatusBarItem elements next to each other, but I'd like to learn how to do this as well.
Oh, and while we're at it, how would I go about incrementing the selected index? Basically, instead of -1 to rowCount-1 I'd like it to display 0 to rowCount. I've seen people using the formatter to add additional text to their data binding, but I'm not sure how I can manipulate the data like that.
You got 2 options:
Either set the Content of the StatusbarItem a TextBlock to use StringFormat with MultiBinding, Something like:
<StatusBarItem>
<StatusBarItem.Content>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}/{1}">
<MultiBinding.Bindings>
<Binding ElementName="listView"
Path="SelectedIndex" />
<Binding ElementName="listView"
Path="Items.Count" />
</MultiBinding.Bindings>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StatusBarItem.Content>
</StatusBarItem>
or use a Converter on the MultiBinding to not have to use the TextBlock
<Window.Resources>
<local:InfoConverter x:Key="InfoConverter" />
</Window.Resources>
...
<StatusBarItem>
<StatusBarItem.Content>
<MultiBinding Converter="{StaticResource InfoConverter}">
<MultiBinding.Bindings>
<Binding ElementName="listView"
Path="SelectedIndex" />
<Binding ElementName="listView"
Path="Items.Count" />
</MultiBinding.Bindings>
</MultiBinding>
</StatusBarItem.Content>
</StatusBarItem>
and InfoConverter.cs:
class InfoConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
return values[0].ToString() + "/" + values[1].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
StatusBarItem expects an object, when StringFormat returns a string and hence why we cannot use StringFormat with the MultiBinding without the TextBlock which can take a string in it's Text field.
As for your second question on how to increment the SelectedIndex value, you can do that with the Converter easily,
Just switch the Convert(...) function in InfoConverter.cs to
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
return (System.Convert.ToInt32(values[0]) + 1).ToString() + "/" + values[1].ToString();
}

WPF : Conditional templating of textblock

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>

WPF Binding FallbackValue set to Binding

Is there a way to have another binding as a fallback value?
I'm trying to do something like this:
<Label Content="{Binding SelectedItem.Name, ElementName=groupTreeView,
FallbackValue={Binding RootGroup.Name}}" />
If anyone's got another trick to pull it off, that would be great.
What you are looking for is something called PriorityBinding (#6 on this list)
(from the article)
The point to PriorityBinding is to
name multiple data bindings in order
of most desirable to least desirable.
This way if the first binding fails,
is empty and/or default, another
binding can take it's place.
e.g.
<TextBox>
<TextBox.Text>
<PriorityBinding>
<Binding Path="LastNameNonExistant" IsAsync="True" />
<Binding Path="FirstName" IsAsync="True" />
</PriorityBinding>
</TextBox.Text>
</TextBox>
If you run into problems with binding to null values and PriorityBinding (as Shimmy pointed out) you could go with MultiBinding and a MultiValueConverter like that:
public class PriorityMultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.FirstOrDefault(o => o != null);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usage:
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource PriorityMultiValueConverter}">
<Binding Path="LastNameNull" />
<Binding Path="FirstName" />
</MultiBinding>
</TextBox.Text>
</TextBox>
Under what conditions would you like it to use the Fallback value? How would you determine that a binding has failed? A binding is still valid even if it's bound to a null value.
I think a good bet may be to use a converter to convert to a default value if the binding returns null. I'm not sure how you could default to another bound value though.
Check out converters here

Resources