WPF: UpdateSourceTrigger=PropertyChanged issue with Double Values - c#

I'm working on a WPF project where I have a DataGrid bounded to a ObservableCollection. The values are being bounded properly but the issue that I am running into is that I can't edit the columns with double values. It will not let me insert a period into the cell.
This is what I have for the XAML:
<DataGrid Name="dataGrid" AutoGenerateColumns="False"
CanUserResizeColumns="True" CanUserAddRows="False" CanUserSortColumns="True" ItemsSource="{Binding}"
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
ColumnWidth="*" Margin="0,51,186,58"
RowEditEnding="dataGrid_RowEditEnding">
<DataGrid.Columns>
<DataGridTextColumn Header="Field 1" Binding="{Binding Field1, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Field 2" Binding="{Binding Field2, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Field 3" Binding="{Binding Field3, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Field 4" Binding="{Binding Field4, UpdateSourceTrigger=PropertyChanged}" />
<DataGridCheckBoxColumn Header="Field 5" Binding="{Binding Field5, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Field 6" Binding="{Binding Field6, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
<DataGrid>
And here is the class (Sorry for the weird class properties :/ I made it that way on purpose)
class FieldClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _field1;
public int Field1
{
get { return _field1; }
set
{
_field1 = value;
OnPropertyChanged("Field1");
}
}
private int _field2;
public int Field2
{
get { return _field2; }
set
{
_field2 = value;
OnPropertyChanged("Field2");
}
}
private double _field3;
public double Field3
{
get { return _field3; }
set
{
_field3 = value;
OnPropertyChanged("Field3");
}
}
private double _field4;
public double Field4
{
get { return _field4; }
set
{
_field4 = value;
OnPropertyChanged("Field4");
}
}
private bool _field5;
public bool Field5
{
get { return _field5; }
set
{
_field5 = value;
OnPropertyChanged("Field5");
}
}
private double _field6;
public double Field6
{
get { return _field6; }
set
{
_field6 = value;
OnPropertyChanged("Field6");
}
}
public FieldClass()
{
_field1 = 0;
_field2 = 0;
_field3 = 0.0;
_field4 = 0.0;
_field5 = false;
_field6 = 0.0;
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
How can I make it so on the DataGrid, if I wanted to update the value of a column (lets say i want to update Field3 or any field that has a double), I can insert a double value like 2.1?
Is a data template needed? Not to sure how to go about that, still a beginner.
Thanks for the help!

If you really want to obtain the behavior with PropertyChanged trigger you can try to use IsAsync=true of Binding, but I'm not sure that's the correct solution.
<DataGridTextColumn Header="Field 3" Binding="{Binding Field3, UpdateSourceTrigger=PropertyChanged, StringFormat=\{0:n\}, IsAsync=True}" />

I would say that the solution would be to change DataGridTextColumn to DataGridTemplateColumn and use NumericUpDown in its template. NumericUpDown is supposed to handle such cases better than TextBox.
Here is an example with Extended WPF Toolkit control (Spin buttons can be hidden if necessary for it to look like TextBox)
<DataGridTemplateColumn Header="Field1" Width="200">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<xctk:DoubleUpDown AllowSpin="False" ShowButtonSpinner="False"
BorderThickness="0"
CultureInfo="en-US"
Value="{Binding Field1, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
License allows non-commercial use.
As a quick fix I would simply add Delay property (in milliseconds) to DataGridTextColumn bindings. It makes binding to update only after that amount of time has elapsed since the user stopped typing.
<DataGridTextColumn Header="Field1"
Width="200"
Binding="{Binding Field1, Delay=500, UpdateSourceTrigger=PropertyChanged}"/>
It is reasonable enough for me: if I type some number with decimal separator, I type the separator and next digit fast enough to fit the Delay.
Delay is available since .NET Framework 4.5.

A summary of the article "Not an easy solution for a simple common problem. Binding a TextBox to a numeric property".
1) Description of the problem.
The main one solved in the topic is the problem with entering a decimal point in a TextBox bound to a non-integer property. It manifests itself when using the UpdateSourceTrigger = PropertyChanged binding in TwoWay mode.
In addition to a dot, it is also impossible to enter insignificant zeros.
While this is rarely needed for leading zeros (000123), trailing zeros (123.000) pose a problem.
Let's say entering the number 123.0004, with a trivial binding, will not work.
The second, incidentally solved and often required, task is to restrict the user to enter "only numbers".
It is solved in a more general form: by restricting the entry of only numerical values. Signs are allowed for signed types, decimal point for non-integer numbers, separators of digit groups are allowed in a given culture, signs of scientific presentation.
2) Causes of the problem. Internal logic implemented in Binding.
Consider what happens when you enter a number with a decimal point and non-significant zeros:
The number 123.000 is entered;
The assignment of the value to the source property (UpdateSource) is triggered;
For assignment, the string is converted to 123.0;
The number is transferred to Reflection;
Reflection assigns the passed number and calls the binding to update the target (UpdateTarget);
The binding converts the number 123.0 to its string representation. And here the point and insignificant zeros are discarded! The string "123" is obtained from the number 123.0;
This string is passed to the DependencyObject to be assigned to the Text property. BUT in the Text property there is a string "123.000". And since it is not equal to the string "123", the value "123" is assigned to the Text property - the point and zeros are gone!
In order for the assignment not to occur, it is necessary to compare the new value not just with the current one, but convert both values ​​to the numeric type of the source property and compare these numbers.
But DependencyObject doesn't "know" anything about bindings. Doesn't even know if this property has bindings.
To solve the problem, you need to make a decision analyzing the current value of the Text property even before converting the number to a string. And if the same number can be obtained from Text, then the assignment should not occur.
3) Applying a multi converter to bind Double properties.
First, let's implement the most obvious solution through a multi-converter.
The algorithm of its work is very simple - it receives two values: binding to the source property and binding to the target property.
By comparing two values, it can return either a string representation of the number, or it can undo the assignment by returning Binding.DoNothing.
Converting a number to a string and back is carried out taking into account the culture transferred to the converter.
For debugging added output to Debug, and in the Control Window showing this output.
Two more validators have been added to the XAML in the binding to demonstrate type conversion.
Full code of MultiConverter:
using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Data;
namespace BindingStringToNumeric
{
/// <summary>Сравнивает полученное <see cref="Double"/> число со <see cref="String"/> текстом.<br/>
/// Если из текста получается такое же число, то присвоение значение по привязке отменяется.</summary>
/// <remarks>Значения должны приходить в массиве в параметре values метода <see cref="IMultiValueConverter.Convert(object[], Type, object, CultureInfo)"/><br/>
/// В массиве должо быть два значения:<br/>
/// 0 - значение источника в <see cref="Double"/> типе,<br/>
/// 1 - <see cref="String"/> Text с которым надо сравнить число.</remarks>
public class DoubleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
Debug.Write(GetType() + $".Convert.values: {values[0]}, \"{values[1]}\"");
double source = (double)values[0];
string text = (string)values[1];
object ret;
// Получение из текста числа (в переданной культуре) и сравнение его с числом источника.
// Если они равны, то отменяется присвоение значения.
if (double.TryParse(text, NumberStyles.Any, culture, out double target) && target == source)
ret = Binding.DoNothing;
// Иначе число источника переводится в строку в заданнной культуре и возвращается.
else
ret = source.ToString(culture);
Debug.WriteLine($"; return: {ret ?? "null"}");
return ret; ;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
Debug.Write(GetType() + $".ConvertBack.value: \"{value}\" to ");
object ret = null;
string text = (string)value;
// Если строка пустая, то это считается эквивалентом нуля.
if (string.IsNullOrWhiteSpace(text))
ret = 0.0;
// Иначе проверяется возвожность перевода строки в число в заданной культуре.
// Если перевод возможен, то возвращается полученное число.
else if (double.TryParse(text, NumberStyles.Any, culture, out double target))
ret = target;
Debug.WriteLine($"return: {ret ?? "null"}");
// Если ret значение не присваивалось, то значит строка некорректна
// Тогда возвращается null, что вызывает ошибку валидации.
if (ret == null)
return null;
// Иначе возвращается массив с одним элементом: полученным числом.
return new object[] { ret };
}
}
}
Full XAML Windows:
<Window x:Class="AppBindingToNumeric.DoubleConverterWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AppBindingToNumeric"
xmlns:dgn="clr-namespace:WpfCustomControls.Diagnostics;assembly=WpfCustomControls"
xmlns:bnd="clr-namespace:BindingStringToNumeric;assembly=BindingToNumeric"
mc:Ignorable="d" FontSize="20"
Title="Example #2: Binding to Double Property with MultiConverter"
Height="450" Width="1000">
<FrameworkElement.Resources>
<bnd:DoubleConverter x:Key="DoubleConverter"/>
<local:Numbers x:Key="Numbers"/>
</FrameworkElement.Resources>
<FrameworkElement.DataContext>
<Binding Mode="OneWay" Source="{StaticResource Numbers}"/>
</FrameworkElement.DataContext>
<Grid Background="LightGreen">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<UniformGrid Background="LightBlue" Columns="2">
<TextBlock Text="TextBlock"/>
<TextBox Margin="5" Text="{Binding DoubleValue}" IsEnabled="False"/>
<TextBlock Text="BindingMode=TwoWay"/>
<TextBox x:Name="tbValidate" Margin="5">
<TextBox.Text>
<MultiBinding Converter="{StaticResource DoubleConverter}" UpdateSourceTrigger="PropertyChanged">
<MultiBinding.ValidationRules>
<local:DebugValidationRule Title="MultiBinding"/>
</MultiBinding.ValidationRules>
<Binding Path="DoubleValue">
<Binding.ValidationRules>
<local:DebugValidationRule Title="Binding"/>
</Binding.ValidationRules>
</Binding>
<Binding Path="Text" ElementName="tbValidate"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
<TextBlock Text="BindingMode=OneTime"/>
<TextBox x:Name="tbDogitsOnly" Margin="5">
<TextBox.Text>
<MultiBinding Converter="{StaticResource DoubleConverter}" UpdateSourceTrigger="PropertyChanged">
<Binding Path="DoubleValue"/>
<Binding Path="Text" ElementName="tbDogitsOnly" Mode="OneTime"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</UniformGrid>
<dgn:DebugBox Grid.Row="1" Margin="10" FontSize="18"
IsOutputsText="{Binding IsActive,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"/>
</Grid>
</Window>
Video test of the converter: https://youtu.be/TauKTs7279Y
4) A universal multiconverter for binding to a numerical property of any type.
For a full-fledged generic converter, we first need to create a generic method for obtaining a validating parser for any numeric type.
I solved this by creating parsers that return an object for each numeric type, a dictionary that stores these parsers, and a method that returns a parser by type.
Everything is implemented in a static class.
The logic is very simple, so there are no detailed comments. Only XML documentation tags are specified.
The source codes are posted on GitHub (link to the repository at the end of the answer), so I don't post them here.
Video test of the converter: https://youtu.be/0LFHlgxvQso
The big drawback of this solution is the difficulty of applying it in XAML. And an error in one of the bindings or in its parameter will cause the converter to work incorrectly.
To simplify the use, need to encapsulate this converter in a markup extension and implement the transfer of the old value of the Text property to the converter in the same place.
5) Extending the markup, including creating an attached property, a private converter and a private MultiBinding.
The entire code is over 700 lines long.
It has been published on GitHub and there is no point in republishing it here.
Here is just an example of its use:
<TextBox Margin="5" Text="{bnd:BindToNumeric DoubleValue}"/>
<TextBox Margin="5" Text="{bnd:BindToNumeric DecimalValue, IsNumericOnly=False}"/>
For reference, also the complete XAML of the тest Window:
<Window x:Class="AppBindingToNumeric.MarkupExtensionWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:AppBindingToNumeric"
xmlns:bnd="clr-namespace:BindingStringToNumeric;assembly=BindingToNumeric"
xmlns:dgn="clr-namespace:WpfCustomControls.Diagnostics;assembly=WpfCustomControls"
mc:Ignorable="d"
Title="Example #4: Binding to a Numeric Properties with the Markup Extension"
Height="450" Width="1000" FontSize="20">
<FrameworkElement.Resources>
<local:Numbers x:Key="Numbers" DoubleValue="123" DecimalValue="456" IntegerValue="799"/>
</FrameworkElement.Resources>
<FrameworkElement.DataContext>
<Binding Mode="OneWay" Source="{StaticResource Numbers}"/>
</FrameworkElement.DataContext>
<Grid Background="LightGreen">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<UniformGrid Background="LightBlue" Columns="3">
<TextBlock Text="To Double - Numeric Only"/>
<TextBox Margin="5" Text="{bnd:BindToNumeric DoubleValue}"/>
<TextBox Margin="5" Text="{Binding DoubleValue}" IsEnabled="False"/>
<TextBlock Text="To Decimal - Any Value"/>
<TextBox Margin="5" Text="{bnd:BindToNumeric DecimalValue, IsNumericOnly=False}"/>
<TextBox Margin="5" Text="{Binding DecimalValue}" IsEnabled="False"/>
<TextBlock Text="To Integer - Numeric Only"/>
<TextBox Margin="5" Text="{bnd:BindToNumeric IntegerValue}"/>
<TextBox Margin="5" Text="{Binding IntegerValue}" IsEnabled="False"/>
</UniformGrid>
<dgn:DebugBox Grid.Row="1" Margin="10" FontSize="18"
IsOutputsText="{Binding IsActive,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}"/>
</Grid>
</Window>
Source codes for GitHub: https://github.com/EldHasp/CyberForumMyCycleRepos/tree/master/BindingToNumber

Related

WPF TextAlignmentProperty.OverrideMetadata not working in inheritor of TextBox

I have created a custom control, which inherits TextBox. It basically has an extra property 'Seconds' and set a binding on 'Text', to shown the 'Seconds' formatted, as eg. 2m 5s, using a converter.
I now want to default right-align the text.
From other custom controls I know we sometimes will want to set/override values using styles. If I set the value directly in the constructor I will not be able to do this.
I would usually something like this:
TextAlignmentProperty.OverrideMetadata(typeof(DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
But this does NOT appear to work:
First two have the default alignment, then they are Left, Center and Right aligned directly on the controls. Seconds row has a style setting alignment to Center
I have bound a TextBlock to the TextAlignment of the first DurationTextBox, and this states that the aligment is 'Right', but this is not how it is shown!
Can anyone explain:
A. Why this is not working?
B. How to do this correctly, or something with the same end effect? (default aligned Right, but possible to override from Style)
C# class :
Please note that this is a simplified version. The complete one has Min, Max, option of confirming value changed and option for out of range action, which is the reason for the structure of the class. Please keep focus on the TextAlignment issue!
(The SecondsToDurationStringConverter and DurationStringValidator can be removed to make the example compile with the same effect)
public class DurationTextBox : TextBox
{
#region Dependency properties
/// <summary>
/// Property for <see cref="Seconds"/>
/// </summary>
[NotNull] public static readonly DependencyProperty SecondsProperty = DependencyProperty.Register(nameof(Seconds), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), SecondsChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Seconds to show as duration string
/// </summary>
public double Seconds
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(SecondsProperty); }
set { SetValue(SecondsProperty, value); }
}
/// <summary>
/// Property for <see cref="EditValue"/>
/// </summary>
[NotNull] public static readonly DependencyProperty EditValueProperty = DependencyProperty.Register(nameof(EditValue), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), EditValueChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Number being edited by the actual text box. Transferred to <see cref="Seconds"/>.
/// <para>Do NOT bind to this property from outside this control</para>
/// </summary>
public double EditValue
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(EditValueProperty); }
set { SetValue(EditValueProperty, value); }
}
#endregion Dependency properties
private bool _isLocked;
static DurationTextBox()
{
// TextAlignment
TextAlignmentProperty.OverrideMetadata(typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
}
/// <inheritdoc />
public DurationTextBox()
{
SecondsToDurationStringConverter secondsToDurationStringConverter = new SecondsToDurationStringConverter();
// Text
Binding binding = new Binding(nameof(EditValue)) { Source = this, Converter = secondsToDurationStringConverter, NotifyOnValidationError = true };
binding.ValidationRules.Add(new DurationStringValidation());
SetBinding(TextProperty, binding);
}
private static void SecondsChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(EditValueProperty, durationTextBox.Seconds);
durationTextBox._isLocked = false;
}
}
private static void EditValueChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(SecondsProperty, durationTextBox.EditValue);
durationTextBox._isLocked = false;
}
}
}
XAML code:
<Label Content="Demo.DurationTextBox" FontWeight="Bold"/>
<WrapPanel>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" x:Name="DemoDurationTextBox"/>
<TextBlock Text="{Binding ElementName=DemoDurationTextBox, Path=TextAlignment}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right"/>
</WrapPanel>
<WrapPanel>
<WrapPanel.Resources>
<Style TargetType="demo:DurationTextBox">
<Setter Property="TextAlignment" Value="Center"/>
</Style>
</WrapPanel.Resources>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right" />
</WrapPanel>
A: This isn't working because TextBox.TextAlignment is inherited, so by default the active value is determined by the parent control at runtime. It doesn't make sense for a class to override the default value of the static DependencyProperty object itself.
The fact that the debug binding you added displays "Right" is strange, and probably an internal WPF optimisation glitch.
B: The correct solution is to set TextBox.TextAlignment="Right" (note the type name qualifier) on a parent control, e.g. your wrap panel. That value will then be automatically applied to all child text blocks unless they or an intermediate parent override it further, including via a style.
C: I would add that the code you posted seems to be an attempt to re-invent DataGrid. That control supports transactional editing out of the box and might save you a lot of time if you switch to it!
While it is not the solution I would have preferred, I have found a way that allows me to have the default alignment of my control to Right, while being able to overwrite it locally by using a style, or directly on the control, without affecting other TextBoxes
I made a default style:
<Style TargetType="controls:DurationTextBox">
<Setter Property="TextAlignment" Value="Right"/>
</Style>
As I have other resource dictionaries which must be included anyway this will work in my situation.
I would have preferred simply setting the default value of my control, but according to Artfunkel that is sadly not possible.
The issue with my chosen approach of using a default style is if a different default style is set for TextBox in a project, the DurationTextBox will not use/inherit this style, because it has its own default style, thus someone using the library has to also set a similar style/override the style for the DurationTextBox for them not to appear different.
If the DurationTextBox had not needed a different default style, containing the text alignment, it would have been possible to have the default style be the same as a TextBox, but this is not a possibility now.
As we do have a different default style for TextBox I have added a BasedOn to my default style:
<Style TargetType="controls:DurationTextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="TextAlignment" Value="Right"/>
</Style>

change checkbox state in DataGridTemplateColumn after row is selected in datagrid using xaml

I am creating a wpf app and struggling with this problem for sometime now.
I have a datagrid with DataGridTemplateColumn, which contains a checkbox and textblock.
<DataGrid
Name="ChargeDataGrid"
Grid.Row="1"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="CheckBox1"/>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox />
<TextBlock Text="Title" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<system:String>123</system:String>
<system:String>124</system:String>
<system:String>125</system:String>
<system:String>126</system:String>
<system:String>127</system:String>
</DataGrid>
What i need to achieve is when row is clicked checkbox in this row must be in checked state too.
I tried to use style triggers:
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="CheckBox1.IsChecked" Value="True" />
<Setter Property="Background" Value="Blue" />
</Trigger>
</Style.Triggers>
</Style>
but it didn't seem possible to change checkbox state like this. I know how to do it in code-behind or mvvm style, but in this case i am wondering is it possible to do using xaml only?
Any help would be appreciated.
I am afraid but with plain standard XAML you can't do it.
As I see you have two options:
You can use some extension libraries which will expand functionality of bindings. Some functionality can be found in mvvm frameworks like MugenMvvmToolkit
Second option is to use some converter for this purpose.
My solution for the second variant is a kind of hack and to my mind more elegant way would be with code behind. Converter:
public class MultiValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length != 3) throw new ArgumentException("Should be 3 params");
if (!(values[2] is FrameworkElement element)) return values[1];
if (!(bool)values[0])
{
element.Tag = "Value need to be changed.";
return values[1];
}
if (element.Tag.Equals("Value changed.")) return values[1];
var res = !(bool)(values[1] ?? true);
element.Tag = "Value changed.";
return res;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Converter inversing bool variable and saves previous state in Tag property of control. This is still not code behind and pretty reusable solution. You can use such converter in any other view where you need such behaviour
In XAML I've changed only checkbox control definition:
<CheckBox x:Name="RowCheckBox" IsHitTestVisible="False">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource MultiValueConverter}" Mode="OneWay">
<Binding Path="IsSelected" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}" />
<Binding Path="IsChecked" RelativeSource="{RelativeSource Self}" />
<Binding Mode="OneWay" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
I think it can bed done rather simple like this:
<CheckBox x:Name="CheckBox1" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Path=IsSelected}"/>
EDIT:
If the desired result is to only change the IsChecked state when (re)selecting the row it can be done with a attached property on a DependencyObject (for instance the containing window) like this:
1) Define the checkbox as this:
<CheckBox x:Name="CheckBox1" IsEnabled="true" local:MainWindow.CheckboxChecked="{Binding RelativeSource={RelativeSource AncestorType=DataGridRow}, Path=IsSelected,Mode=OneWay}">
2) Define the attached property as this:
public static bool GetCheckboxChecked(DependencyObject obj)
{
return (bool)obj.GetValue(CheckboxCheckedProperty);
}
public static void SetCheckboxChecked(DependencyObject obj, bool value)
{
obj.SetValue(CheckboxCheckedProperty, value);
}
public static readonly DependencyProperty CheckboxCheckedProperty =
DependencyProperty.RegisterAttached("CheckboxChecked", typeof(bool), typeof(MainWindow), new PropertyMetadata(false, CheckboxChecked_Changed));
private static void CheckboxChecked_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CheckBox chk = d as CheckBox;
if (chk != null && chk.Tag == null)
{
bool chkValue = chk.IsChecked.GetValueOrDefault();
bool oldValue = (bool)e.OldValue;
bool newValue = (bool)e.NewValue;
chk.Tag = true; // Just to prevent an infinite loop
chk.IsChecked = !chkValue && !newValue || chkValue && !oldValue && newValue ? false : true;
chk.Tag = null;
}
}

Binding property of usercontrol to data

I'm not sure of the correct terminology to use. I created a Windows Store app about a year ago and the main page was created by Visual Studio and I never changed it much. It uses a view model that works fine but I don't know enough to fix problems. Anyhow...
The page uses a GridView to display the contents of CollectionViewSource element to reference an ObservableCollection. This all works fine. The DataTemplate for one of the data items looks like this right now:
<DataTemplate x:Key="TopImageTileTemplate">
<Grid MinHeight="135" Width="350" Margin="0" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="135"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=ImagePath}" FontSize="33"/>
<usercontrols:WaitingImageControl SourcePath="{Binding Path=ImagePath}" Grid.Row="0" Width="350" Height="165" HorizontalAlignment="Center" VerticalAlignment="Stretch" AutomationProperties.Name="{Binding Title}" Visibility="{Binding TypeDescription, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource TextToVis}}"/>
<usercontrols:WaitingImageControl SourcePath="XXX" Grid.Row="0" Width="350" Height="165" HorizontalAlignment="Center" VerticalAlignment="Stretch" AutomationProperties.Name="{Binding Title}" Visibility="{Binding TypeDescription, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource TextToVis}}"/>
<ProgressRing Opacity="0.5" Foreground="#FF8A57FF" Grid.Row="0" Name="TheProgressControl" IsActive="True" Height="32" Width="32" Background="Transparent" VerticalAlignment="Center" HorizontalAlignment="Center"/>
...
</Grid>
</DataTemplate>
The problem that I have is that the data item for this contains a string called ImagePath that I want to pass into the WaitingImageControl usercontrol and it's not working. The TextBlock works fine and the text displays the ImagePath string just fine. The second WaitingImageControl works fine and the code that handle SourcePath does get passed the "XXX" just fine too. But the first WaitingImageControl never gets passed the ImagePath value from the data item.
This is some sort of binding issue and I know so little about binding that I'm to even sure what to try (or what to show in this question). given that the TextBlock binding works and the second WaitingImageControl binding works, I'm at a loss.
Here's the WaitingImageControl code for the SourcePath property:
public static readonly DependencyProperty SourcePathProperty =
DependencyProperty.Register("SourcePath", typeof(string), typeof(WaitingImageControl), new PropertyMetadata(""));
public string SourcePath
{
get { return m_SourcePath; }
set
{
if( string.IsNullOrEmpty( value ) )
return;
m_SourcePath = value;
ResourcesStore Store = new ResourcesStore();
if( Store.Count() == 0 )
{
var IgnoreMe = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync( CoreDispatcherPriority.Normal, () =>
{
// No progress and no image...
TheProgressControl.Visibility = Visibility.Collapsed;
TheImage.Visibility = Visibility.Collapsed;
} );
return;
}
ResourceItem Item = Store.getItemByFilename( m_SourcePath );
LocalInboxService.Instance.InboxStatusChanged -= InboxStatusChanged;
InboxStatusChanged( null );
LocalInboxService.Instance.InboxStatusChanged += InboxStatusChanged;
}
}
The code is supposed to show the Image element and hide the ProgressRing element when the image has been downloaded.
And the code for the data item, which again, works just fine when the ImagePath is passed automatically to the TextBlock:
public string ImagePath
{
get
{
return this._imagePath;
}
set
{
this._imagePath = value;
this.SetProperty(ref this._imagePath, value);
}
}
Any help is appreciated making the ImagePath to SourcePath binding (below) work:
<usercontrols:WaitingImageControl SourcePath="{Binding Path=ImagePath}"
Grid.Row="0" Width="350" Height="165" HorizontalAlignment="Center"
VerticalAlignment="Stretch" AutomationProperties.Name="{Binding Title}"
Visibility="{Binding TypeDescription, RelativeSource={RelativeSource
Mode=TemplatedParent}, Converter={StaticResource TextToVis}}"/>
After hours of searching, I found a StackOverflow answer to a similar question. The answer was to add a PropertyChanged function to the Propertymetadata. I'm not sure yet what this actually means or why it is only needed here, but it works properly:
public static readonly DependencyProperty SourceImageResourceIdProperty =
DependencyProperty.Register("SourceImageResourceId", typeof(string), typeof(WaitingImageControl), new PropertyMetadata( string.Empty, OnSourcePathPropertyChanged ));
private static void OnSourcePathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as WaitingImageControl).SourceImageResourceId = e.NewValue.ToString();
}
The OnSourcePathPropertyChanged function gets called and the property gets set like it should.
Now I just hope that it wasn't one of the twenty other experiments that actualy fixed this!

Two way binding not working in WPF

I have a WPF application with several dialogs with lots of controls. I have a dropdown list that is not binding from the C# to the XAML. The other way around works fine.
Here is the C#:
public class AdditionalCostView : ViewBase, IEquatable<AdditionalCostView>
{
// ..
public OfficeReferenceDataView Category
{
get { return _category; }
set
{
this._category = value;
this.OnPropertyChanged("Category");
}
}
// ..
}
and here is the corresponding XAML:
<ComboBox Grid.Row="0" Grid.Column="1" Validation.Error="Validation_Error"
DisplayMemberPath="Value" SelectedValuePath="ID"
ItemsSource="{Binding AllCategories}">
<ComboBox.SelectedItem>
<Binding Path="Category" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.NotifyOnValidationError>
<sys:Boolean>true</sys:Boolean>
</Binding.NotifyOnValidationError>
<Binding.NotifyOnSourceUpdated>
<sys:Boolean>true</sys:Boolean>
</Binding.NotifyOnSourceUpdated>
<Binding.NotifyOnTargetUpdated>
<sys:Boolean>true</sys:Boolean>
</Binding.NotifyOnTargetUpdated>
<Binding.ValidationRules>
<validators:MandatoryValueValidationRule IsRequired="True" Message="Please select a category."></validators:MandatoryValueValidationRule>
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
</ComboBox>
The AllCategories binding is working fine, but the SelectedItem binding is not. ViewBase implements INotifyPropertyChanged.
When I change the value of Category in my view in C#, nothing happens to the dropdown.
Where am I going wrong?
UPDATE
As per comment request, here is the code where I set my value:
private void ctlBooking_PricesAndFees_AdditionalCostEditing(object sender, RoutedEventArgs e)
{
try
{
AdditionalCostEventArgs args = e as AdditionalCostEventArgs;
AdditionalCostView costToEdit = args.AdditionalCost;
AdditionalCostView tempCost = args.AdditionalCost.Clone() as AdditionalCostView;
// ..
}
// ..
}
I know what you're thinking, because I thought the same thing. The Clone() is not copying the values or raising the events, but it is. I wrote it longhand after the clone to explicitly set those values but that still didn't work.
M
You can try with this XAML (don't forget to declare your data):
<UserControl.DataContext> <!--<Window.DataContext>-->
<data:AdditionalCostView />
</UserControl.DataContext> <!--</Window.DataContext>-->
<ComboBox Grid.Row="0" Grid.Column="1"
DisplayMemberPath="Value" SelectedValuePath="ID"
ItemsSource="{Binding AllCategories, Mode=TwoWay}" SelectedValue="{Binding Category.ID, Mode=TwoWay" />
Apply your ValidationRules in your model not in your UI. Search about DataAnnotations :P

Vertical Text in Wpf TextBlock

Is it possible to display the text in a TextBlock vertically so that all letters are stacked upon each other (not rotated with LayoutTransform)?
Nobody has yet mentioned the obvious and trivial way to stack the letters of an arbitrary string vertically (without rotating them) using pure XAML:
<ItemsControl
ItemsSource="Text goes here, or you could use a binding to a string" />
This simply lays out the text vertically by recognizing the fact that the string is an IEnumerable and so ItemsControl can treat each character in the string as a separate item. The default panel for ItemsControl is a StackPanel, so the characters are laid out vertically.
Note: For precise control over horizontal positioning, vertical spacing, etc, the ItemContainerStyle and ItemTemplate properties can be set on the ItemsControl.
Just in case anybody still comes across this post... here is a simple 100% xaml solution.
<TabControl TabStripPlacement="Left">
<TabItem Header="Tab 1">
<TabItem.LayoutTransform>
<RotateTransform Angle="-90"></RotateTransform>
</TabItem.LayoutTransform>
<TextBlock> Some Text for tab 1</TextBlock>
</TabItem>
<TabItem Header="Tab 2">
<TabItem.LayoutTransform>
<RotateTransform Angle="-90"></RotateTransform>
</TabItem.LayoutTransform>
<TextBlock> Some Text for tab 2</TextBlock>
</TabItem>
</TabControl>
I don't think there is a straighforward of doing this withought changing the way the system inherently laysout text. The easiest solution would be to change the width of the textblock and supply a few extra properties like this:
<TextBlock TextAlignment="Center" FontSize="14" FontWeight="Bold" Width="10" TextWrapping="Wrap">THIS IS A TEST</TextBlock>
This is hacky, but it does work.
Just use a simple LayoutTransform..
<Label Grid.Column="0" Content="Your Text Here" HorizontalContentAlignment="Center">
<Label.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="90" />
<ScaleTransform ScaleX="-1" ScaleY="-1"/>
</TransformGroup>
</Label.LayoutTransform>
</Label>
It's doable:
Your TextBlock's TextAlignment property should be set to Center:
<TextBlock Name="textBlock1" TextAlignment="Center" Text="Stacked!" />
Then add NewLines between every character:
textBlock1.Text =
String.Join(
Environment.NewLine,
textBlock1.Text.Select(c => new String(c, 1)).ToArray());
(Uses System.Linq to create an array of strings from the individual characters in the original string. I'm sure there are other ways of doing that...)
Below XAML code changes the angle of text displayed in a textblock.
<TextBlock Height="14"
x:Name="TextBlock1"
Text="Vertical Bottom to Up" Margin="73,0,115,0" RenderTransformOrigin="0.5,0.5" >
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-90"/>
<TranslateTransform/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
the accepted answer suggested by Ray Burns does not work for me on .net 4.0. Here is how I did it:
pull in the mscorlib
xmlns:s="clr-namespace:System;assembly=mscorlib"
put in your usercontrol/window/page resources
<s:String x:Key="SortString">Sort</s:String>
and use it like this
<ItemsControl ItemsSource="{Binding Source={StaticResource SortString}}" Margin="5,-1,0,0" />
hope it helps!
create a stackpanel with a bunch ot textblocks that take one char
make the text container's max width to allow for one char only and wrap the text:
<TextBlock TextWrapping="Wrap" MaxWidth="8" TextAlignment="Center" Text="stack" />
Make an image and fill the block with the image, use photoshop or something designed to manipulate text instead of fiddling in code ?
This code allows to have vertical text stacking and horizontal centered letters.
<ItemsControl Grid.Row="1"
Grid.Column="0"
ItemsSource="YOUR TEXT HERE"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
HorizontalAlignment="Center"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here's a way to insert a '\n' after every character in the text of the TextBlock, that way making it display vertically:
<TextBlock x:Name="VertTextBlock" Text="Vertical Text" Loaded="VertTextBlock_Loaded"></TextBlock>
Then, in the Loaded event handler, you say:
TextBlock tb = sender as TextBlock;
StringBuilder sb = new StringBuilder(tb.Text);
int len = tb.Text.Length * 2;
for (int i = 1; i < len; i += 2)
{
sb.Insert(i, '\n');
}
tb.Text = sb.ToString();
That solution was proposed by Lette, but I believe my implementation incurs less overhead.
<linebreak/> can be used to show data in two lines
You could also use the "RUN" binding
In the App.xaml file use something like this:
<Application x:Class="Some.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:commands="clr-namespace:Deridiam.Helper.Commands"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
ShutdownMode="OnMainWindowClose"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<commands:HorizontalToVertical x:Key="HorizontalToVertical_Command"></commands:HorizontalToVertical>
<ControlTemplate x:Key="VerticalCell" TargetType="ContentControl">
<TextBlock Text="{TemplateBinding Content}" Foreground="Black"
TextAlignment="Center" FontWeight="Bold" VerticalAlignment="Center"
TextWrapping="Wrap" Margin="0" FontSize="10">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding ConvertToVerticalCmd, Source={StaticResource HorizontalToVertical_Command}}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type TextBlock}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</ControlTemplate>
</Application.Resources>
Create the command class binded to the textblock using i:Interaction.Triggers on the Loaded event in the app.xaml example
namespace Deridiam.Helper.Commands
{
public class HorizontalToVertical
{
private ICommand _convertToVerticalCommand;
public ICommand ConvertToVerticalCmd =>
_convertToVerticalCommand ?? (_convertToVerticalCommand = new RelayCommand(
x =>
{
var tBlock = x as TextBlock;
var horizontalText = tBlock.Text;
tBlock.Text = "";
horizontalText.Select(c => c).ToList().ForEach(c =>
{
if (c.ToString() == " ")
{
tBlock.Inlines.Add("\n");
//tBlock.Inlines.Add("\n");
}
else
{
tBlock.Inlines.Add((new Run(c.ToString())));
tBlock.Inlines.Add(new LineBreak());
}
});
}));
}
}
Finally in the .xaml file where you want the vertical text to be shown
<ContentControl Width="15" Content="Vertical Text" Template="{StaticResource VerticalCell}">
</ContentControl>
Will result in:
Vertical Text
none of the above solutions solved my problem (some come close), so I'm here to post my solution and maybe help someone.
The accepted solution helped me, but the text is not aligned to the center.
<ItemsControl ItemsSource="{Binding SomeStringProperty, FallbackValue=Group 1}" Margin="5"
TextElement.FontSize="16"
TextElement.FontWeight="Bold"
TextBlock.TextAlignment="Center"
HorizontalAlignment="Center"
VerticalAlignment="Center" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding }" HorizontalAlignment="Center" />
</DataTemplate>
</ItemsControl.ItemTemplate>
I will offer a solution based on the converter:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace Converters
{
[ValueConversion(typeof(object), typeof(string))]
public class InsertLineBreakConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null)
value = parameter;
if (value == null)
return null;
if (!(value is string str))
str = value.ToString();
return string.Join(Environment.NewLine, (IEnumerable<char>) str);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public static InsertLineBreakConverter Instance { get; } = new InsertLineBreakConverter();
}
public class InsertLineBreakConverterExtension : MarkupExtension
{
public override object ProvideValue(IServiceProvider serviceProvider)
=> InsertLineBreakConverter.Instance;
}
}
Usage examples:
<TextBlock Text="{Binding Property, Converter={cnvs:InsertLineBreakConverter}}"/>
<TextBlock Text="{Binding Converter={cnvs:InsertLineBreakConverter}, ConverterParameter='Some Text'}"/>

Categories

Resources