TextBox with CurrencyFormat and PropertyChanged trigger doesn't accept text right - wpf

I have a TextBox in a WPF window bound to a dependency property of the window of type double (see below). Whenever the user types in the TextBox when
The TextBox is empty, or
All of the text is selected,
the typed text is accepted incorrectly. For example: If I type a '5' in either of these scenarios, the resulting text is "$5.00", but the caret is located before the '5', after the '$'. If I try to type "52.1", I get "$2.15.00".
<Window x:Class="WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="154" Width="240" Name="ThisWindow"
Background="{StaticResource {x:Static SystemColors.AppWorkspaceBrushKey}}">
<Grid>
<TextBox Text="{Binding ElementName=ThisWindow,
Path=Amount,
StringFormat={}{0:c},
UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
MinWidth="100" />
</Grid>
</Window>
If I remove the UpdateSourceTrigger attribute, it types correctly, but doesn't maintain the currency format.
Any ideas?

This is caused by it trying to apply the formatting after every character press.
As an alternative, I usually just style the TextBox so it only applies formatting when it's not being edited
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding SomeValue, StringFormat=C}" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="Text" Value="{Binding SomeValue, UpdateSourceTrigger=PropertyChanged}" />
</Trigger>
</Style.Triggers>
</Style>

Related

WPF ToggleButton style that changes text of TextBlock within a StackPanel based on IsChecked trigger

I have a WPF style for a toggle button that uses a stack panel to achieve stacked, vertical text. I want the button text to change based on the toggle button's IsChecked state.
Additionally, the number of characters changes so I need to hide one of the text blocks in the stack panel. I tried setting the Visibility property of the Letter4 text block to hidden but the text was not vertically centered.
The code below works but it's just a cheesy workaround — I change the font size to 1 so it seems to disappear. (I pulled out all the formatting to make it simpler.) What is the correct way to do what I need?
Thanks.
<Style x:Key="RunStopToggle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<StackPanel VerticalAlignment="Center">
<TextBlock x:Name="Letter1"/>
<TextBlock x:Name="Letter2"/>
<TextBlock x:Name="Letter3"/>
<TextBlock x:Name="Letter4"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="TextBlock.Text" TargetName="Letter1" Value="S"/>
<Setter Property="TextBlock.Text" TargetName="Letter2" Value="T"/>
<Setter Property="TextBlock.Text" TargetName="Letter3" Value="O"/>
<Setter Property="TextBlock.Text" TargetName="Letter4" Value="P"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="TextBlock.Text" TargetName="Letter1" Value="R"/>
<Setter Property="TextBlock.Text" TargetName="Letter2" Value="U"/>
<Setter Property="TextBlock.Text" TargetName="Letter3" Value="N"/>
<Setter Property="TextBlock.Text" TargetName="Letter4" Value=""/>
<Setter Property="TextBlock.FontSize" TargetName="Letter4" Value="1"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
IMHO, the biggest problem with the code is that you're trying to do everything in XAML instead of letting a view model mediate the changeable values. A smaller issue is how you're actually implementing the vertically-stacked text.
There is already another question with good advice about vertically-stacked text. See Vertical Text in Wpf TextBlock
We can combine the advice there, where they use ItemsControl to display the text vertically, along with a view model to provide the actual text, and a placeholder ItemsControl that is hidden, but not collapsed (so that it still takes up space), to display the toggle button much more simply than in the code you have now.
First, the view model:
class ViewModel : INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get => _isChecked;
set => _UpdateField(ref _isChecked, value, _OnIsCheckedChanged);
}
private string _buttonText;
public string ButtonText
{
get => _buttonText;
set => _UpdateField(ref _buttonText, value);
}
public ViewModel()
{
ButtonText = _GetTextForButtonState();
}
public event PropertyChangedEventHandler PropertyChanged;
private void _OnIsCheckedChanged(bool previous)
{
ButtonText = _GetTextForButtonState();
}
private string _GetTextForButtonState()
{
return IsChecked ? "STOP" : "RUN";
}
private void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This view model just provides a property to receive the toggle button's state, as well as to provide the appropriate button text for that state.
Next, the XAML to use this view model:
<Window x:Class="TestSO68091382ToggleVerticalText.MainWindow"
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:l="clr-namespace:TestSO68091382ToggleVerticalText"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:ViewModel/>
</Window.DataContext>
<Grid>
<ToggleButton IsChecked="{Binding IsChecked}"
HorizontalAlignment="Left" VerticalAlignment="Top">
<ToggleButton.Content>
<Grid>
<ItemsControl ItemsSource="STOP" Visibility="Hidden"/>
<ItemsControl ItemsSource="{Binding ButtonText}" VerticalAlignment="Center"/>
</Grid>
</ToggleButton.Content>
</ToggleButton>
</Grid>
</Window>
The ToggleButton.IsChecked property is bound to the IsChecked property in the view model, so that it can update the text as necessary. Then the content of the ToggleButton includes the ItemsControl that will display the text vertically.
Note that button's direct descendent is actually a Grid. This is so that two different ItemsControl elements can be provided: one shows the text itself, and is bound to the ButtonText property; the other has hard-coded the longer of the two strings that might be displayed. This ensures that the ToggleButton's size is always the same, large enough for that longer text. The bound ItemsControl is then centered vertically; you can of course use whatever aligment you like there, but the way your question is worded it sounds like you want the text vertically centered.
For what it's worth, if you really want to do everything in XAML, that's possible to. I personally prefer to avoid this kind of use for triggers, but I admit there's no hard and fast rule that says you can't. My preference mainly has to do with my desire to keep the XAML as simple as possible, as I find it a less readable language, and harder to mentally keep track of all the different related elements, which adding triggers tends to make more complex.
If you do prefer a XAML-only solution, it would look like this:
<Window x:Class="TestSO68091382ToggleVerticalText.MainWindow"
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:l="clr-namespace:TestSO68091382ToggleVerticalText"
xmlns:s="clr-namespace:System;assembly=netstandard"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<s:String x:Key="runText">RUN</s:String>
<s:String x:Key="stopText">STOP</s:String>
</Window.Resources>
<Grid>
<ToggleButton HorizontalAlignment="Left" VerticalAlignment="Top">
<ToggleButton.Content>
<Grid>
<ItemsControl ItemsSource="STOP" Visibility="Hidden"/>
<ItemsControl VerticalAlignment="Center">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="ItemsSource" Value="{StaticResource runText}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ToggleButton}}" Value="True">
<Setter Property="ItemsSource" Value="{StaticResource stopText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
</ItemsControl>
</Grid>
</ToggleButton.Content>
</ToggleButton>
</Grid>
</Window>
Mechanically, this is very similar to the view model-based example above, just using a DataTrigger to respond to the changes in the ToggleButton.IsChecked state instead of doing it in the view model.
Note that you really only need one trigger. You can use a Setter to provide the unchecked state's value, and then use a single trigger to override that value for the checked state.
You need to change Visibility:
<Style x:Key="RunStopToggle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<StackPanel VerticalAlignment="Center">
<TextBlock x:Name="Letter1"/>
<TextBlock x:Name="Letter2"/>
<TextBlock x:Name="Letter3"/>
<TextBlock x:Name="Letter4" Text="P"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="TextBlock.Text" TargetName="Letter1" Value="S"/>
<Setter Property="TextBlock.Text" TargetName="Letter2" Value="T"/>
<Setter Property="TextBlock.Text" TargetName="Letter3" Value="O"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="TextBlock.Text" TargetName="Letter1" Value="R"/>
<Setter Property="TextBlock.Text" TargetName="Letter2" Value="U"/>
<Setter Property="TextBlock.Text" TargetName="Letter3" Value="N"/>
<Setter Property="TextBlock.Visibility" TargetName="Letter4" Value="Collapsed"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But I agree with #Peter Duniho - you'd better use a different approach for vertical text.
For permanent text, to print it one letter on each line, it is enough to insert newline characters & # xA; between the letters.
Example:
<Style x:Key="RunStopToggle" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<StackPanel VerticalAlignment="Center">
<TextBlock x:Name="PART_TextBlock" Text="R
U
N"/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="TextBlock.Text" TargetName="PART_TextBlock" Value="S
T
O
P"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Also see my answer with a converter for vertical text: https://stackoverflow.com/a/68094601/13349759

DataTriggers : How it works

I want to implement a DataTrigger for say, textBox1. When Text inside textBox1 is "ABC" then I want to display "Data matched!" in another TextBox say, textBox2. I have written below xaml code for this but its not working. I am getting below error message.
'Text' member is not valid because it does not have a qualifying type name
XAML code for this is:
<Window x:Class="ControlTemplateDemo.Animation"
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"
Title="Animation" Height="300" Width="607">
<Grid>
<Border Background="White">
<StackPanel Margin="30" HorizontalAlignment="Left" Width="500" Height="209">
<TextBox Name="textBox1">
<TextBox.Triggers>
<DataTrigger Binding="{Binding Path=Text}">
<DataTrigger.Value>
<sys:String>ABC</sys:String>
</DataTrigger.Value>
<Setter TargetName="textBox2" Property="Text" Value="Data matched!"/>
</DataTrigger>
</TextBox.Triggers>
</TextBox>
<TextBox Name="textBox2">
</TextBox>
</StackPanel>
</Border>
</Grid>
</Window>
Is there any problem in binding?
Thanks,
Hemant
You need to give the DataTrigger in a Style for the second TextBox
something like:
<StackPanel>
<TextBox x:Name="inputBox" />
<TextBox Margin="0 25 0 0">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text"
Value="No Match Found" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=inputBox,
Path=Text}"
Value="ABC">
<Setter Property="Text"
Value="Match Found" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
TextBox.Triggers does not support DataTrigger. I'd guess it's only for EventTriggers as the documentation states
on a side-note, I normally have my bindings in the element that ends up as the target(as much as I can). This way I find it easier to debug at-least personally. If the TextBox has wrong info I instantly check it's binding than every binding in my xaml file to see which element has a wrong binding that ends up updating my TextBox.

Workaround for caret textbox with binding an property changed XAML

I have a lot of textboxes binded in twoway mode ot a different properties with UpdateSourceTrigger=PropertyChanged and I have one annoying problem: when I enter or delete a value - caret moves to the begin of the string.
I found a solution here:
TextBox with CurrencyFormat and PropertyChanged trigger doesn't accept text right
but I cannot implement it correctly. I probably miss something.
Here is my XAMLbinding for one of textboxes:
enter code here <TextBox x:Name="TextBoxFirstPersonCameraMinPosY" Width="150" VerticalAlignment="Center" FontWeight="Normal"
Text="{Binding Path=CameraLimitationParameters.FirstPersonCameraPosition.MinValue.Y, Mode=TwoWay, IsAsync=True, Delay=0, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ToStringConverter}}" Style="{StaticResource {x:Type TextBox}}"
MaxLength="40" Margin="10,0,0,0" MinWidth="150" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled" MaxWidth="150"></TextBox>
and here is my style:
enter code here<Page.Resources >
<ObjectDataProvider x:Key="CameraMode"
MethodName="GetValues" ObjectType="{x:Type sys:Enum}" >
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="settingsManager:CameraBehavior"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<settingsManager:ToStringConverter x:Key="ToStringConverter"></settingsManager:ToStringConverter>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="Text" Value="{Binding UpdateSourceTrigger=PropertyChanged}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Page.Resources>
What did I miss an how to apply this style for all textboxes independently of binding property without duplicating code?
And will this workaround work for my case? Help me please.
Problem was because I set IsAsync=True in TextBox. In this case Async need to be set to false.

Parametrized Style within UserControl?

I used this MSDN tutorial to create an eye candy look for all the Button controls of my window, and that worked fine.
To make it even more reusable, I tried to put all in a UserControl: I created a ImageButton UC, then I encapsulated all that <Style> from <Window.Resources> to <UserControl.Resources>.
Then I changed my Button instances in XAML, from:
<Button Tag="Face.jpg" Content="Foo" />
To:
<uc:ImageButton Tag="Face.jpg" Content="Foo" />
And the style stopped being applied.
Here's the UC code:
<UserControl x:Class="GDTI.UI.Main.View.UserControls.ImageButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style TargetType="Button">
<Setter Property="MaxWidth" Value="250" />
<Setter Property="Margin" Value="5" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" >
<Setter.Value>
<SolidColorBrush Color="Orange" Opacity="0.4" />
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate >
<StackPanel>
<Image Source="{Binding Tag,
RelativeSource={RelativeSource
FindAncestor,
AncestorType='Button'}}" />
<TextBlock Margin="10"
HorizontalAlignment="Center"
Text="{Binding Content,
RelativeSource={RelativeSource
FindAncestor,
AncestorType='Button'}}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Button/>
What am I missing?
Thank you!
The bindings in the button-style target properties on the button, which no longer has the properties set. You need to forward those to the UserControl if you want to retain the style's integrity:
<!-- Inside UserControl declaration -->
<Button Content="{Binding Caption, RelativeSource={RelativeSource AncestorType=UserControl}}"
Tag="{Binding ImageSource, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
Where Caption and ImageSource should be new dependency properties defined on the UserControl (in code-behind).
Note that you can never bind to Content in a UserControl (hence the Caption property), here the Button itself is the Content of the UserControl.
Alternatively you could directly change the targeting in the style by changing the AncestorType to UserControl which bypasses the Button. Binding beyond the templated control is not exactly good practice but your are still inside the UserControl so it may be forgivable.
Either way this is a bit hacky and it might be better to inherit from Button instead.

Accessing ListBox DisplayMemberPath data value in SelectedItem template

I am attempting to create a generic ListBox control to customize edit in place as well as other features.
In the example below, I want to bind the "Text" property of the ListBox "selected item" to the data value of the DisplayMemberPath in the viewed structure. Such XAML binding expression would replace the question marks in the code (Text="{Binding ????????????????").
Using a ContentPresenter instead of binding the text works for display purposes, but I have not been able to bind to the Text component used on the presenter. An alternative to finding the binding expression is to be able to get the Text content from the ContentPresenter.
I can think of a number of ways to accomplish this through code behind, but I am looking for a XAML solution if such thing exists.
I appreciate any ideas. I am almost sure there is a trivial answer to this, but after spending a couple days on it, I admit a nudge in the right direction would greatly help me.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<XmlDataProvider x:Key="NobelLaureatesInPhysics"
XPath="/NobelLaureatesInPhysics">
<x:XData>
<NobelLaureatesInPhysics xmlns="">
<NobelLaureate>
<ID>1</ID>
<Name>Wilhelm Röntgen</Name>
<AwardDate>12/10/1901</AwardDate>
</NobelLaureate>
<NobelLaureate>
<ID>2</ID>
<Name>Hendrik Lorentz</Name>
<AwardDate>12/10/1902</AwardDate>
</NobelLaureate>
<NobelLaureate>
<ID>3</ID>
<Name>Pieter Zeeman</Name>
<AwardDate>12/10/1902</AwardDate>
</NobelLaureate>
</NobelLaureatesInPhysics>
</x:XData>
</XmlDataProvider>
<ControlTemplate x:Key="ItemTemplate"
TargetType="ListBoxItem">
<TextBlock Foreground="Black">
<ContentPresenter />
</TextBlock>
</ControlTemplate>
<ControlTemplate x:Key="SelectedItemTemplate"
TargetType="ListBoxItem">
<TextBox Background="Black"
Foreground="White"
Text="{Binding ????????????????"/>
</ControlTemplate>
<Style TargetType="{x:Type ListBoxItem}"
x:Key="ContainerStyle">
<Setter Property="Template"
Value="{StaticResource ItemTemplate}" />
<Style.Triggers>
<Trigger Property="IsSelected"
Value="True">
<Setter Property="Template"
Value="{StaticResource SelectedItemTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="TestListBoxStyle"
TargetType="{x:Type ListBox}">
<Setter Property="ItemContainerStyle"
Value="{DynamicResource ContainerStyle}" />
</Style>
</Window.Resources>
<Grid>
<ListBox Style="{DynamicResource TestListBoxStyle}"
ItemsSource="{Binding Source={StaticResource NobelLaureatesInPhysics}, XPath=NobelLaureate}"
DisplayMemberPath="Name"/>
</Grid>
{Binding Path=DisplayMemberPath, RelativeSource={RelativeSource Mode=FindAncestor, Type={x:Type ListBox}}
That should work

Resources