What I am trying to do:
<Grid>
<Grid.RowDefinitions>
...
<!--The next line is pseudo code for what I am trying to achieve-->
<RowDefintion Height="if(EditEnabled) { 10* } else { 0 }" />
...
</Grid.RowDefinition>
...
<DockPanel Visibility="{Binding EditEnabled, Converter={StaticResource InverseBooleanToVisibilityConverter}}" ...>
...
I am trying to change the visibility of the DockPanel depending on whether editing is enabled, while keeping he ability to resize and have fixed heights and relative heights.
The question:
Is there an IValueConverter (System.Windows.Data.IValueConverter) that can take a boolean, and two numbers and choose one of the GridLengths based on the boolean? From just inspecting the interface of IValueConverter it doesn't look like this is quite the right type to use.
Or is there a better way to inject the GridLength that I want?
What I have tried:
Looking through the inheritors of IValueConverter - nothing obvious to me
Moving the Height="10*" inside the DockPanel tag and changing the RowDefinition to be Auto - this created an conversion exception
Searching here
Unfortunately, there is no IValueConverter that does if-then.
(and to be more specific: you can not do if-then logic with the XAML)
But you can do the if-then logic in the C# code.
here is the solution
public class HeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool enableEdit = (bool)value;
double param = System.Convert.ToDouble(parameter);
if (enableEdit)
return new GridLength(param, GridUnitType.Star);
else
return new GridLength(0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the window like this.
<Window.Resources>
<local:HeightConverter x:Key="heightConverter"/>
<sys:Int32 x:Key="param">10</sys:Int32>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="{Binding Path=EditEnabled, Converter={StaticResource heightConverter}, ConverterParameter={StaticResource param}}" />
</Grid.RowDefinitions>
</Grid>
please consider also define the required namespace that you will use, like the following
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:[your namespace]"
Update the same result could be achieved by using IMutliValueConverter
public class HeightMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool enableEdit = (bool)values[0];
double param = System.Convert.ToDouble(values[1]);
if (enableEdit)
return new GridLength(param, GridUnitType.Star);
else
return new GridLength(0);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the window like this
<Window.Resources>
<local:HeightMultiConverter x:Key="heightMutliConverter"/>
<sys:Int32 x:Key="param">10</sys:Int32>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition >
<RowDefinition.Height>
<MultiBinding Converter="{StaticResource heightMutliConverter}">
<Binding Path="EditEnabled"/>
<Binding Source="{StaticResource param}"/>
</MultiBinding>
</RowDefinition.Height>
</RowDefinition>
</Grid.RowDefinitions>
</Grid>
NOTE: just do not forget, you have to take care of the Source by setting the DataContext property.
There is a built-in converter you may be able to use: AlternationConverter. You specify a list of values (of arbitrary type), bind to an integer, and the converter looks up the integer in the list of values (modulo the value count).
If you specify two values for this AlternationConverter, and you're able to provide your EditEnabled property as an integer 0 or 1, then you can map that 0 and 1 to any value you want.
If you feel it doesn't make sense to convert your bool to an integer first (something I can sympathise with), you could still use AlternationConverter as inspiration for a custom converter that doesn't require the model value to be of type int.
Create a BooleanConverter<T> base class as described in http://stackoverflow.com/a/5182660/469708
public class BooleanConverter<T> : IValueConverter
{
public BooleanConverter(T trueValue, T falseValue)
{
True = trueValue;
False = falseValue;
}
public T True { get; set; }
public T False { get; set; }
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is bool && ((bool) value) ? True : False;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is T && EqualityComparer<T>.Default.Equals((T) value, True);
}
}
Then write
public class BooleanToGridLengthConverter : BooleanConverter<System.Windows.GridLength>
{
public BooleanToGridLengthConverter() : base(
new System.Windows.GridLength(1, System.Windows.GridUnitType.Star),
new System.Windows.GridLength(0))
{
}
}
The values for true and false can be set directly, no need for a MultiValueConverter (as long as only the boolean parameter needs to be bindable).
<convert:BooleanToGridLengthConverter x:Key="Converter" True="10*" False="0" />
Alternatively, you can derive the converter from MarkupExtension and directly use it like this:
<RowDefinition Height="{Binding EditEnabled, Converter={convert:BooleanToGridLengthConverter True=10*, False=0}" />
Related
I'm new to WPF, and I'm wondering if it's possible to create a custom TextBlock so that a string can be rendered in a certain way.
So for any given string:
Wrap the string at a maximum of 32 characters (or any arbitrary length)
Insert a space between the fourth and fifth character
Insert a tab between the sixth and seventh character
Add a vertical pipe at the end of the line, before wrapping.
The list goes on, essentially I'm wondering if I create a custom TextBlock inheriting from TextBlock, can I override the default behaviour in some way?
You could write a ValueConverter that transforms the contents of TextBlock.Text:
public class MyTextConverter : IValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (string.IsNullOrEmpty(values?[0]))
{
return string.Empty;
}
var manipulatedString = values[0] as string;
// Do something with the string...
return manipulatedString;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then use the converter in your Window or UserControl:
MyNamespace.MyWindowClass.xaml.cs
public class MyWindowClass : Window
{
public MyWindowClass() {}
public MyTextProperty { get; set; }
}
MyNamespace.MyWindowClass.xaml
<Window
x:Class="MyNamespace.MyWindowClass"
xmlns:local="clr-namespace:MyNamespace"
DataContext="{Binding RelativeSource={RelativeSource Self}}"/>
<Window.Resources>
<local:MyTextConverter x:Key="myTextConverterInstance"/>
</Window.Resources>
<!-- ... -->
<TextBlock Text="{Binding MyTextProperty,
Converter={StaticResource myTextConverterInstance}}"/>
<!-- ... -->
</Window>
Currently I would like to bind my Height property on a Rowdefinition in a Grid. I want to show the row if the property IsOnline on my ViewModel is set to true.
Binding a number as Height is no problem at all, I am just wondering how I could bind it to Auto.
My View:
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="{Binding IsOnline, Converter={StaticResource HeightConverter}}"/>
</Grid.RowDefinitions>
My Converter HeightConverter:
public class HeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
if ((bool)value)
{
return "Auto";
}
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Check GridLength Struct it has properties like Auto, Star and etc. You could use those as return values from the IValueConverter.
Why would I get a "BoolToRowHeightConverter is not supported in a Windows Presentation Foundation(WPF) project error in xaml?
I was using a converter to convert rowheight to * and Auto in a grid based on the expander's IsExpanded property.
Code in xaml:
<RowDefinition Height="{Binding IsExpanded, ElementName=Expander5, Converter={x:Static BoolToRowHeightConverter.Instance}}"/>
Code in xaml.cs:
public class BoolToRowHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value) return new GridLength(1, GridUnitType.Star);
else
return GridLength.Auto;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Typically, IValueConverters are used like this:
a) Add a namespace in your XAML page that references your converter class... usually it looks something like this:
xmlns:Converters="clr-namespace:WpfApplication1.Converters"
b) Add an instance of your converter class into the Resources section of your page (or of App.xaml:
<Window.Resources>
<Converters:BoolToRowHeightConverter x:Key="BoolToRowHeightConverter" />
...
</Window.Resources>
c) Reference your converter instance by the x:Key value that you gave it:
<RowDefinition Height="{Binding IsExpanded, ElementName=Expander5,
Converter={StaticResource BoolToRowHeightConverter}}" />
You have decided to reference the value converter by using the x:Static markup extension ({x:Static BoolToRowHeightConverter.Instance}) but then you also need to provide the actual field or property that you reference (Instance). To do that you need to add it to the BoolToRowHeightConverter class:
public class BoolToRowHeightConverter : IValueConverter
{
// Convert and ConvertBack methods ...
public static readonly BoolToRowHeightConverter Instance = new BoolToRowHeightConverter();
}
I got a static class like the following:
public static class Lang
{
public static string GetString(string name)
{
//CODE
}
}
Now i want to access this static function within xaml as a binding.
Is there such a way for example:
<Label Content="{Binding Path="{x:static lang:Lang.GetString, Parameters={parameter1}}"/>
Or is it necessary to create a ObjectDataProvider for each possible parameter?
Hope someone is able to help me. Thanks in advance!
I get this need too. I "solved" using a converter (like suggested here).
First, create a converter which return the translated string:
public class LanguageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter == null)
return string.Empty;
if (parameter is string)
return Resources.ResourceManager.GetString((string)parameter);
else
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
then use it into XAML:
<Window.Resources>
<local:LanguageConverter x:Key="LangConverter" />
</Window.Resources>
<Label Content="{Binding Converter={StaticResource LangConverter},
ConverterParameter=ResourceKey}"/>
Regards.
The right way would be to go the objectdataprovider route. Although if you are just binding to text rather than use a label, I would use a textblock.
<ObjectDataProvider x:Key="yourStaticData"
ObjectType="{x:Type lang:Lang}"
MethodName="GetString" >
<ObjectDataProvider.MethodParameters>
<s:String>Parameter1</s:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<TextBlock Text={Binding Source={StaticResource yourStaticData}}/>
Q: Why does my custom TextBox UserControl using a MultiBinding and IMultiValueConverter gets its Convert() method called only once (during instanciation) ??
I have defined a UserControl that requires a MultiBinding and a IMultiValueConverter in order to change its behavior/presentation upon 2 indenpendant DependencyProperty.
<proj:MyControl Value="10" Digits="1" />
UserControl:
<UserControl x:Class="MyControl"
x:Name="uc"
...>
<UserControl.Resources>
<conv:DecimalToStringMultiConverter x:Key="DecToString" />
</UserControl.Resources>
[...]
<Grid>
<ctrl:VTextBox x:Name="vTb" Grid.Column="0" Margin="0,0,2,0">
<ctrl:VTextBox.Text>
<MultiBinding Converter="{StaticResource DecToString}" UpdateSourceTrigger="LostFocus" Mode="TwoWay">
<Binding ElementName="uc" Path="Value" Mode="TwoWay" />
<Binding ElementName="uc" Path="Digits" Mode="TwoWay" />
</MultiBinding>
</ctrl:VTextBox.Text>
</ctrl:VTextBox>
</Grid>
</UserControl>
When executing the application, the UserControls are all correctly instanciated. However, the IMultiValueConverter.Convert() method gets called only once.
Using an simple Binding + IValueConverter with a constant ConvertParameter worked great: the converter's Convert() method would get called everytime the TextBox contained inside the UserControl had its Text property changed.
Design changed and I had to resort to using a MultiBinding + IMultiValueConverter, and now the Convert() method only gets called once, and the TextBox.Text property is never updated upon the LostFocus() event.
What gives?
The MultiValueConverter is defined as below. I just wrap the IMultiValueConverter upon the IValueConverter to reuse existing code.
[ValueConversion(/*sourceType*/ typeof(Decimal), /*targetType*/ typeof(string))]
public class DecimalToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return "0.00";
int? digits = parameter as int?;
if(digits == null)
digits = 2;
NumberFormatInfo nfi = (NumberFormatInfo) CultureInfo.InvariantCulture.NumberFormat.Clone();
nfi.NumberGroupSeparator = " ";
nfi.CurrencyDecimalSeparator = ".";
nfi.NumberDecimalDigits = (int)digits;
return ((decimal)value).ToString("n", nfi);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return 0.00m;
decimal d;
return decimal.TryParse((string)value, out d) ? d : 0.00m;
}
}
[ValueConversion(/*sourceType*/ typeof(Decimal), /*targetType*/ typeof(string))]
public class DecimalToStringMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
DecimalToStringConverter conv = new DecimalToStringConverter();
return conv.Convert(values[0], targetType, values.Length > 1 ? values[1] : null, culture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
DecimalToStringConverter conv = new DecimalToStringConverter();
return new[] { conv.ConvertBack(value, targetTypes[0], null, culture) };
}
}
It seems like you have some conflicting expectations about the updating behavior of the Binding and TextBox. The only reason Convert will be called multiple times is if the values of Digits or Value change multiple times, and there is nothing in your posted code to indicate that will happen. Changes to TextBox.Text won't cause calls to Convert, but should instead be calling ConvertBack on every change+LostFocus. Are you seeing that when you run your code?
You also need to return two values, instead of the one there now, from your ConvertBack method in order to supply both of the Bindings used in the MultiBinding with values.