How to convert Double to Thickness in XAML (x:Bind)? - c#

I'm trying to pass the value of a slider control to the border thickness of another control:
<Border
BorderThickness="{x:Bind ThicknessSlider.Value, Mode=OneWay}">
</Border>
But this error occurs:
Cannot directly bind type 'System.Double' to
'Microsoft.UI.Xaml.Thickness'. Use a cast, converter or function
binding to change the type
How is this conversion done?

You can create a converter like this:
DoubleToThicknessConverter.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
using System;
namespace Converters;
public class DoubleToThicknessConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is double doubleValue)
{
List<int>? coefficients = (parameter as string)?
.Split(',')
.Select(x => int.Parse(x))
.ToList<int>();
if (coefficients?.Count is not 4)
{
coefficients = new() { 1, 1, 1, 1 };
}
return new Thickness(
left: doubleValue * coefficients[0],
top: doubleValue * coefficients[1],
right: doubleValue * coefficients[2],
bottom: doubleValue * coefficients[3]);
}
throw new ArgumentException();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
And use it like this:
MainPage.xaml
<Page
x:Class="Converters.MainPage"
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="using:Converters"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Page.Resources>
<local:DoubleToThicknessConverter x:Key="DoubleToThicknessConverter" />
</Page.Resources>
<Border
BorderBrush="SkyBlue"
BorderThickness="{x:Bind ThicknessSlider.Value, Mode=OneWay, Converter={StaticResource DoubleToThicknessConverter}, ConverterParameter='0,3,1,3'}">
<Slider x:Name="ThicknessSlider" />
</Border>
</Page>
UPDATE
Added arbitrary coefficients as converter parameter. Now you can set a coefficient to left, top, right and bottom thickness.
For example, you can set '0,0,0,1' if you only want to use the bottom thickness or set '1,2,1,2' to double the thickness of top and bottom.

Related

c# wpf xaml using StringFormat without or with CUSTOM error message

In my TextBox I have;
Text="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:N2}}"
The binding data is decimal while the textbox formatting should be like 1,000.00.
My only concern here is that when the TextBox is empty, or if I deleted the value, then the border of the textbox gets red and I get the error message of Value * could not be converted just below the textbox, this causes by using StringFormat.
Now, I really don't care if the value is empty because in my database, the default value is zero and I do accept an empty value, and also the textbox accepts only digits.
What I want to know is how can I disable this validation but still be able to use the StringFormat? Second, just in case in the future, I wanted to use the same behavior, how can I change the default error message to something else?
EDIT:
As suggested, I tried using a binding converter and DataTrigger to apply the StringFormat, but I still got the error message.
Using Binding Converter
//AmountFormatter.class
public class AmountFormatter : IValueConverter{
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture){
decimal amount = (decimal.TryParse(value.ToString(), out decimal n)) ? n : 0; // if it failed to convert into decimal, meaning wrong value, then set default value as 0.
return (amount>0)?string.Format(culture, "{0:N2}", amount):null; //return null, empty if value is less than 1. Maybe the user wants to type new value, so leave the textbox empty.
}
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
return null;
}
}
//xaml layout
<DataTemplate>
<DockPanel LastChildFill="True">
<TextBox x:Name="TextBoxAmount" Text="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource AmountFormatter}}" />
</DockPanel>
</DataTemplate>
//App.xaml
<converters:DecimalOnly x:Key="AmountFormatter" />
Using DataTrigger to apply StringFormat
//AmountFormatter.class
public class AmountFormatter : IValueConverter{
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture){
decimal amount = (decimal.TryParse(value.ToString(), out decimal n)) ? n : 0; // if it failed to convert into decimal, meaning wrong value, then set default value as 0.
return (amount>0) //return true or false if amount is greather than zero. Maybe the user wants to type new value, so leave the textbox empty.
}
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
return null;
}
}
//xaml layout
<DataTemplate>
<DockPanel LastChildFill="True">
<TextBox x:Name="TextBoxAmount" />
</DockPanel>
<DataTemplate.Triggers>
<!-- StringFormat when value is greather than 0 -->
<DataTrigger Binding="{Binding Path=Amount, Converter={StaticResource AmountFormatter}}" Value="true">
<Setter TargetName="TextBoxAmount" Property="Text" Value="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:N2}}" />
</DataTrigger>
<!-- Otherwise, no StringFormat -->
<DataTrigger Binding="{Binding Path=Amount, Converter={StaticResource AmountFormatter}}" Value="false">
<Setter TargetName="TextBoxAmount" Property="Text" Value="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
//App.xaml
<converters:DecimalOnly x:Key="AmountFormatter" />
It turns out that the validation error message is not caused by using the StringFormat.
The data type of Amount in my ViewModel which is bound into the TextBox is Decimal.
What happens when there is no value in the TextBox is that, the UpdateSourceTrigger=PropertyChanged is being triggered. But since it's empty, it cannot convert the value into the format I wanted. As result, the border of my TextBox turns to red, and an error message below it saying value * cannot be converted. I don't know if this is a fact but I think it's a kind of feature when binding data.
My solution is from the snippet code of Mark Feldman.
First, in my ViewModel, change the data type of Amount from Decimal into String.
/** MyViewModel Class **/
public class MyViewModel{
...
public string? Amount { get; set; } //instead of decimal, I used string. This is the one that is causing the validation error/message when no value in the textbox.
}
Next is to create an IValueConverter class which I named AmountFormatter.class. This will be the class that will handle the formatting when typing.
/** AmountFormatter.class **/
// (1) convert the value of the textbox which is string into decimal.
// //a succesfull convert, meaning the value is valid and in correct format.
// (2) format the decimal into {0:N2}
public class AmountFormatter : IValueConverter{
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture){
decimal amount = (decimal.TryParse(value.ToString(), out decimal n)) ? n : 0; // if it failed to convert into decimal, meaning wrong value, then set default value as 0.
return (amount>0)?string.Format(culture, "{0:N2}", amount):""; //return "" (empty string) if value is less than 1. Maybe the user wants to type new value, so leave the textbox empty.
}
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
if ((value==null) || (string.IsNullOrEmpty(value.ToString()))) return ""; // returning empty string will also trigger the `updatesourcetrigger`.
return (decimal.TryParse(value.ToString(), out decimal n)) ? n : "";
}
}
Then declared the AmountFormatter.class into my App.xaml.
<Application
x:Class="MyApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:MyApp.Converters"
xmlns:local="clr-namespace:MyApp"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
....
</ResourceDictionary.MergedDictionaries>
...
<converters:AmountFormatter x:Key="AmountFormatter" />
</ResourceDictionary>
</Application.Resources>
</Application>
After that in my xaml layout which contains my TextBox, bind the Amount and the AmountFormatter converter;
/** xaml layout **/
<DataTemplate>
<DockPanel LastChildFill="True">
<TextBox
x:Name="TextBoxAmount"
DataObject.Pasting="TextBoxAmount_Paste"
PreviewKeyDown="TextBoxAmount_PreviewKeyDown"
PreviewTextInput="TextBoxAmount_PreviewTextInput"
Text="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource AmountFormatter}}" />
</DockPanel>
</DataTemplate>
And viola!
Miscellaneus;
The Textbox is for currency, I wanted to format it as the user types on the TextBox. In order to make sure that the users type the correct input, I limit the textbox to positive numbers only, no spacing is allowed, single decimal point, and two decimal places only. I also don't allow copy-paste unless the data is in correct format.
In my xaml layout class, I have the following;
/** xaml layout class **/
//handle the pasting event, if the copied data failed to convert into decimal, then the format is invalid and do not allow to paste it.
private void TextBoxAmount_Paste(object sender, DataObjectPastingEventArgs e){
if (e.DataObject.GetDataPresent(typeof(String))){
if (!decimal.TryParse((String)e.DataObject.GetData(typeof(String)), out _)) e.CancelCommand();
} else {
e.CancelCommand();
}
}
//prevent from using space
private void TextBoxAmount_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e){
e.Handled = (e.Key == Key.Space); //do not allow spacing
}
//accept only numbers, single decimal, and two decimal places.
private void TextBoxAmount_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e){
TextBox tb = (TextBox)sender;
char ch = e.Text[0];
if (!Char.IsDigit(ch) && (ch!='.')) e.Handled = true;
if ((ch == '.') && tb.Text.IndexOf('.') > -1) e.Handled = true;
}
I hope there is no bug in this method.
Does this solve your problem?
Converter:
using System;
using System.Globalization;
using System.Windows.Data;
internal class DecimalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return "";
return ((Decimal)value).ToString("0.00");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
if ((value == null) || (value.ToString() == ""))
return Binding.DoNothing;
return Decimal.Parse(value.ToString());
}
catch
{
return null;
}
}
}
Usage:
<TextBox>
<TextBox.Text>
<Binding Path="Amount" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" StringFormat="{}{0:N2}">
<Binding.Converter>
<local:DecimalConverter />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>

WPF/DataGrid: Binding to different properties for displaying and editing

I have an object that contains eg a string property like "10; 20; 30". I have also a get property that splits the string, converts each part to a double and sums them up. Thus I have "10; 20; 30" and 60.0 (as double).
Now the question is. Is there a way to display the 60.0 (as double) in a TextColumn, but when going to edit mode editing the string "10; 20; 30"?
So that I can bind to one property for displaying and to bind to another property for editing?
You can achieve this with your existing property itself by using different template displaying and editing.
Below CellTemplate and CellEditingTemplate can used for this.
<Grid>
<Grid.Resources>
<local:ValueConverter x:Key="ValueConverter"/>
<DataTemplate x:Key="DisplayTemplate" >
<TextBlock Text="{Binding StringProperty,
Converter={StaticResource ValueConverter}}"/>
</DataTemplate>
<DataTemplate x:Key="EditTemplate">
<TextBox Text="{Binding StringProperty}" />
</DataTemplate>
</Grid.Resources>
<DataGrid Name="DG1" ItemsSource="{Binding Items}" AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Total"
CellTemplate="{StaticResource DisplayTemplate}"
CellEditingTemplate="{StaticResource EditTemplate}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
You can use IValueConverter to convert the updated string values to double as per your desired calculation.
public class ValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
double total = 0.0d;
foreach (var item in value.ToString().Split(';'))
total += System.Convert.ToDouble(item.Trim());
return total;
}
catch
{
return 0.0d;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note: You can add the necessary validation for the user values inside your ValueConverter class.

Currency Converter using IValueConverter

I am working on Windows 8.1 Store App using XAML and C#.
I have added 2 text boxes and implemented an IValueConverter Interface.
here is the Xaml Code
<Page.Resources>
<local:ConverterClass x:Key="C_Converter" />
<local:EuroConverterClass x:Key="Euro_Converter" />
<local:YenConverterClass x:Key="Yen_Converter" />
</Page.Resources>
<TextBox Name="PKR_TextBox"
Grid.Row="1"
Grid.Column="2"
Width="450"
Height="50"
FontSize="30"
FontWeight="Bold"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBox Name="DOLLAR_TextBox"
Grid.Row="2"
Grid.Column="2"
Text="{Binding ElementName=PKR_TextBox, Path=Text, Converter={StaticResource C_Converter}}"
Width="450"
Height="50"
FontSize="30"
FontWeight="Bold"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
Here my Converter Class Code:
class ConverterClass : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
int pkr;
int dollar = 0;
if (Int32.TryParse(value.ToString(), out pkr))
{
dollar = pkr * 0.0099;
}
return dollar;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
I an trying to convert currency at run time when user enters a value in PKR text box it should automatically update USD Textbox. But instead it is giving me an error "Cannot implicitly convert type 'double' to 'int'. An explicit conversion exists (are you missing a cast?)".
Please help and ignore my bad english.
The error message is pretty clear. You have to use double values for your calculation. double is the default floating point type when you use C#.
public object Convert(
object value, Type targetType, object parameter, string language)
{
double pkr;
double dollar = 0.0;
if (double.TryParse(value.ToString(), out pkr))
{
dollar = pkr * 0.0099;
}
return dollar;
}

Calculating the padding of a XAML element as a percentage of its width

I'm trying to set the left and right padding of a textbox element in a XAML C# Windows Runtime app to be a percentage (10%) of the same element's width, so that when the element is resized along with the window, the padding changes to compensate. What is the best way to do this?
I've tried to do this by making a custom template for the textbox element and putting a grid with the correct column definitions inside the ScrollViewer along with a new ContentPresenter element. For some reason that I can't understand, however, moving the name attribute of the ScrollViewer to the newly created ContentPresenter causes a significant loss of performance. I would prefer if possible to use the default template if there is a better way of doing this.
Thanks in advance for any suggestions.
You could just create a ValueConverter that takes in the element that you want to match your width to. Then in your Textbox, you would bind your padding to the element, using the ValueConverter.
Something like this:
public sealed class PercentageOfValueConverter : IValueConverter
{
private const double DefaultValue = 150;
private const double Percentage = 0.9;
public object Convert(object value, Type targetType, object parameter, string language)
{
double result = 0;
double.TryParse(value.ToString(), out result);
if (result == double.NaN)
{
return DefaultValue; // Default value.
}
// Thickness gets assigned to the Padding property.
return new Thickness(result * Percentage, 0, result * Percentage, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Then you would use the converter by binding the padding of your text box to it. The converter is provided the Binding Path, which in this case, is the Grid.Width property. We can take that and return back 90% of that value as a Thickness object, which is then assigned to the padding property.
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<local:PercentageOfValueConverter x:Key="PercentageOfValueConverter" />
</Grid.Resources>
<TextBox Padding="{Binding ElementName=LayoutRoot,
Converter={StaticResource PercentageOfValueConverter},
Path=Width, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />

XAML Binding to a converter

what I am trying to do is relatively simple. I am just trying to bind the Y element of a TranslateTransform on an ellipse to 1/2 the height of the ellipse:
<Ellipse Name="EllipseOnlyLFA" Height="200" Fill="Yellow" HorizontalAlignment="Left" VerticalAlignment="Bottom" ClipToBounds="True">
<Ellipse.Width>
<Binding ElementName="EllipseOnlyLFA" Path="Height"/>
</Ellipse.Width>
<Ellipse.RenderTransform>
<TranslateTransform>
<TranslateTransform.Y>
<Binding Converter="MultiplyByFactor" ElementName="EllipseOnlyLFA" Path="Height" ConverterParameter="0.5"/>
</TranslateTransform.Y>
</TranslateTransform>
</Ellipse.RenderTransform>
</Ellipse>
I also have the following converter:
public class MultiplyByFactor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((double)value * (double)parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return true;
}
}
I am getting an error on the XAML line where I actually use the converter. The error is
'Set property
'System.Windows.Data.Binding.Converter'
threw an exception.' Line number '22'
and line position '8'.
Can anyone shed some light on how to do this? EDIT: Yes, I have the converter added as a resource.
There are 2 thing wrong with your code
1) your converter needs to be accessed using the StaticResource declaration
<Binding Converter="{StaticResource myMultiplyByFactor}"
ElementName="EllipseOnlyLFA" Path="Height" ConverterParameter="0.5"/
2) Your converter parameter is a string by default, so you need to convert it to a double
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
double.TryParse((parameter as string).Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out double param);
return param * (double)value;
}
You need to add the converter to the resources
Edit
You need to add the namespace too
xmlns:c="clr-namespace:WpfApplication1"
end edit
<Window.Resources>
<c:MultiplyByFactor x:Key="myMultiplyByFactor"/>
</Window.Resources>
Then you can use the instance from the resources
<TranslateTransform.Y>
<Binding Converter={StaticResource myMultiplyByFactor}"
ElementName="EllipseOnlyLFA"
Path="Height" ConverterParameter="0.5"/>
</TranslateTransform.Y>
The Parameter probably gets passed as a String. Set a breakpoint in your Converter and look at the values of value and parameter. You might need to use double.Parse instead of the cast.

Categories

Resources