I'm using IValueConverter to convert a double to string. The string created by the converter is not displayed in a corresponding textbox. For example, if a user enters 1.1 my value converter might format it as '1'. However I still see '1.1' in the textbox. I verified in debugger that the converter's Convert() method is called and that it returns '1'. Am I missing something basic?
The converter's method is as follows:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
{
return string.Empty;
}
if (value is double && targetType == typeof(string))
{
string format = parameter == null ? "{0:F2}" : (string)parameter;
string formatted = string.Format(format, value);
return formatted;
}
return value.ToString();
}
The xaml is as follows:
<TextBox x:Name="balance" Grid.Row="12" Grid.Column="1"
Text="{Binding Balance, Converter={StaticResource nullableConverter}, ConverterParameter=\{0:F0 \}, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" Width="90" TextAlignment="Right" />
Thanks.
I believe this is a known issue with using a converter on TextBox.Text when UpdateSourceTrigger=PropertyChanged
This usually isn't an ideal solution because as the user types, the value gets converted, so this can cause confusion and unexpected results for the user.
For example, if the user types "1.1", and the value keeps getting truncated to "1" after each key press, the sequence of events would be:
type 1
type .
converter changes value to 1
type 1
value is now 11
As a workaround, I usually recommend applying formatting only when the TextBox does not have focus using a Trigger, like this:
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding Balance, Converter={StaticResource nullableConverter}, ConverterParameter=\{0:F0 \}" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="Text" Value="{Binding Balance, ValidatesOnDataErrors=True, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" />
</Trigger>
</Style.Triggers>
</Style>
Your format item (stuff inside curly braces) is malformed.
Instead of
ConverterParameter=\{0:F0 \}
Try
ConverterParameter='{}{0:F0} '
However, I wonder if setting Binding.StringFormat property wouldn't be enough? The conversion will be handled for you (forth and back) and you'll have validation working too (at least for the case when user inputs a non-number text).
Make the binding two way so it flows in both directions.
It's only going to run that converter when the Binding object recognizes that the "Balance" property of your ViewModel has changed.
Does your ViewModel have both a public Getter and Setter?
If so, the Binding should be invoking the property setter on the ViewModel as the data in the textbox is changing.
Next, you need to signal back to the View (and the binding) that the property value has changed. Does your viewmodel implement INotifyPropertyChanged? Is your viewmodel raising the the PropertyChanged event (with PropertyName of "Balance") when the Balance setter is called?
Related
In my main Window I have a MenuItem and a UserControl. I would like to disable/enable the MenuItem if one of the TextBoxes inside the UserControl is empty/not empty respectively.
Given a UserControl named ContactDetails and a TexBox called ContactNameTextBox, here's my xaml code for the MenuItem:
<MenuItem x:Name="DeleteContact"
Header="Delete Contact"
IsEnabled="{Binding ElementName=ContactDetails.ContactNameTextBox,Path=Text.Length, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
For some reason, the MenuItem always stays enabled. What am I missing?
You are binding to the length of the Text but you need a Converter from length to a bool, because IsEnabled property expects a bool.
public class NumToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value!=null && value is int )
{
var val = (int)value;
return (val==0) ? false : true;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value!=null && value is bool )
{
var val = (bool)value; return val ? 1 : 0;
}
return null;
}
}
Add a local xmlns for this and a resource.
xmlns:local="clr-namespace:YourNamespace"
and this is the reference to the converter class.
<local:NumToBoolConverter x:Key="NumToBoolConverter"/>
In your Binding section add this :
Converter={StaticResource NumToBoolConverter}
This can be your final MenuItem definition:
<MenuItem x:Name="DeleteContact"
Header="Delete Contact"
IsEnabled="{Binding ElementName=ContactDetails.ContactNameTextBox,
Path=Text.Length,
Converter={StaticResource NumToBoolConverter},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
There are a couple of problems with your binding. The first is that you specified a two-way binding. That implies that you want to write back to the 'length' property in your textbox. Since it is readonly you can't.
Normally you should get an error for this:
A TwoWay or OneWayToSource binding cannot work on the read-only
property 'Length' of type 'System.String'.
Now strangely enough, the binding does work after that. But that is REALLY not the right way. The magic of .NET is allowing a 0 to be interpreted as 'false'. But it is not a safe binding. As Olaru said in his answer, the length property is an integer and the IsEnabled field is looking for a bool. What if you wanted to bind to the 'visibility' property?
So what is the best way to handle this then? Converters are definitely one choice, and in many cases the best choice. The advantage to converters is that they can be re-used in similar cases. We have a library full of converters that we use very often. Olaru has described how to do that, so I won't repeat what he has already said.
In some cases though, it is beneficial to know a different way. A datatrigger will allow you to do the same kind of thing as a converter. It is a one-way binding. Here is an example.
<MenuItem x:Name="DeleteContact" Header="Delete Contact">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="MenuItem.IsEnabled" Value="true"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text.Length, ElementName=ContactNameTextBox}" Value="0">
<Setter Property="MenuItem.IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
No code necessary!!
There are plenty of arguments about the pros and cons of converters and datatriggers. But the main thing is to know that there are more than one way to do what you are asking.
I am writing a simple program using the MVVM Model on WPF. Basicly when the user clicks a radio button in a group of radio buttons, it will update a property in the View Model with the new Account number. The problem is, when I click a different button the converter is called for the new button IsChecked Binding, and then after that it runs the converter for the previous button IsChecked binding(for losing its checked status).
This is causing a problem, since the new button is updating the value of the property with the correct account number, and then when the old button calls the converter, it gets converted back to the old value. I have hacked it to work by adding a static variable to the class, and if the IsChecked property is false, just return the value in the static variable. Does anyone have a better solution for Short Circuting the Converter Call on the box that loses its checked status. Code is below:
Converter:
class RadioToAccountConverter : IValueConverter
{
static string myValue; //HACK TO MAKE IT WORK
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return parameter.ToString();
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
{
myValue = parameter.ToString(); // Hack to make it work
return parameter.ToString();
}
return myValue; // Hack to make it work
}
}
XAML:
<RadioButton Foreground="HotPink"
Grid.Column="0"
Content="6087721"
Tag="6087721"
IsChecked="{Binding Account, Converter={StaticResource Radio2Value}, Mode=OneWayToSource, ConverterParameter=6087721}">
</RadioButton>
<RadioButton Foreground="HotPink"
Grid.Column="1"
Content="BFSC120"
IsChecked="{Binding Account, Converter={StaticResource Radio2Value}, Mode=OneWayToSource, ConverterParameter='BFSC120'}">
</RadioButton>
<RadioButton Foreground="HotPink"
Grid.Column="2"
Content="BFSC121"
IsChecked="{Binding Account, Converter={StaticResource Radio2Value}, Mode=OneWayToSource, ConverterParameter=BFSC121}">
</RadioButton>
<RadioButton Foreground="HotPink"
Grid.Column="3"
Content="BFSC206"
IsChecked="{Binding Account, Converter={StaticResource Radio2Value}, Mode=OneWayToSource, ConverterParameter=BFSC206}">
</RadioButton>
Property:
public const string AccountPropertyName = "Account";
private string _account;
/// <summary>
/// Sets and gets the Account property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string Account
{
get
{
return _account;
}
set
{
if (_account == value)
{
return;
}
RaisePropertyChanging(AccountPropertyName);
_account = value;
RaisePropertyChanged(AccountPropertyName);
}
}
Any Help Is Greatly Appreciated.
Based on what I understand, you want to give users the ability to select from a list of account numbers. You're choice of presentation (view) is a group of radio buttons.
If that is true, the key part is this: you want to give users the ability to select from a list of account numbers. This means that the control you should use is a ListBox, since users should select one of the appropriate values. Now, since you are looking to use radio buttons visually, you simply have to supply an alternative ItemsSource.ItemContainerStyle.
XAML:
<ListBox ItemsSource="{Binding AccountNumbers, Mode=OneWay">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton Content="{Binding}" IsChecked="{Binding IsSelected, RelativeSource={x:Static RelativeSource.TemplatedParent}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Note that you'll need to add another property on your ViewModel (I named it AccountNumbers). For example:
public IReadOnlyCollection<string> AccountNumbers { ... }
Of course, the underlying collection can be a observable if you need it to be, but that's really up to you.
If you define a GroupName on each RadioButton, WPF will manage the IsChecked states for you.
You could bind the state with a {Binding SomeProperty, Mode=OneWayToSource} if you want the ViewModel to be aware of state.
One way to approach this would be to bind each RadioButton's IsChecked property to the whole ViewModel, just bind it to something like
IsChecked="{Binding WholeViewModel, Mode=OneWayToSource, Converter={StaticResource MyRadioButtonConverter}, ConverterParameter=SomethingReallyUnique}"
...where the public property WholeViewModel is a property that does a return this; in the getter. This would let you have access to the ViewModel and enough information to query the ViewModel to see if the radiobutton should be checked or not. But, only do this if the GroupName DependencyProperty doesn't somehow give you what you want.
To process the clicking on the buttons, then, to actually change the ViewModel state, you'd implement an ICommand in your ViewModel and bind the Command property of the RadioButton to {Binding ClickedCommand} and define a CommandParameter with any string you want. This approach will guarantee a one-way relationship to the IsChecked state, preventing the thing you're describing, I think.
I'll work up a code sample if you think you'd like one.
I have a custom usercontrol with a custom dependency property of type string. It should be bound twoway to a textbox, which is in a surrounding app, like this:
<Textbox Name="TextBoxInHostApp"/>
<ctrl:MyControl
....
MyString = "{Binding ElementName=TextBoxInHostApp, Path=Text, Mode=TwoWay}"
</ctrl:MyControl>
Problem:
The Control should have the functionality to reveal the value of the dependency property also!
In MyControl, I have a simple string property "Name", which should update the MyString dependeny property value.
So normally, if MyString was not bound to the outside world, you would code in the xaml where the control is used:
<Textbox Name="TextBoxInHostApp"/>
<ctrl:MyControl
....
MyString = "{Binding Name}"
</ctrl:MyControl>
But that's not available in my case.
Attempt 1:
I used a trigger inside the usercontrol xaml:
<UserControl.Style>
<Style>
<Style.Setters>
<Setter Property="local:MyControl.MyString" Value="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</Style.Setters>
</Style>
Now this trigger doesn't work, because the "local value" of the dp is already set here:
MyString = "{Binding ...
if i comment out:
<!--<MyString = "{Binding ...
...the trigger works fine, because the "local value" isn't set.
next attempt:
I try the hard way and code in MyControls viewmodel:
public string Name
{
get
{
return _name;
}
set
{
_name = value;
RaisePropertyChanged("Name");
MyOwningUICtrl.SetValue(MyControl.MyStringProperty, value); // ??
}
}
I have to pass a DependencyObject to call a SetValue, which is unpleasant for MVVM. There must be better solutions for this scenario ?
This is by design
Per WPF's Dependency Property Precedence List, values specified locally within a <Tag> will always take precedence over values specified in a Style or a Trigger
A common problem is setting a value in a <Tag>, then trying to change it in a Style.Trigger. The solution is to set the default value as a <Setter> in the <Style> instead of in the <Tag>, which will then allow the triggered value to take precedence over the styled value
<Style>
<!-- Default Value
<Setter Property="local:MyControl.MyString"
Value="{Binding ElementName=TextBoxInHostApp, Path=Text, Mode=TwoWay}"/>
<Style.Triggers>
<Trigger ...>
<!-- Triggered Value -->
<Setter Property="local:MyControl.MyString"
Value="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</Trigger>
</Style.Setters>
</Style>
But I don't actually see a Trigger defined in your post anywhere, so I'm not really sure if this is your problem or not.
I'm a bit unclear about the exact structure of your code, but keep in mind that a Dependency Property is kind of like a pointer to another value. You can set it to point to TextBox.Text, or to point to DataContext.Name, but not both.
If you want to set both TextBox.Text and MyUserControl.MyString to point to DataContext.Name, then you should bind both properties to "{Binding Name}" like Natxo suggests.
Or if you are trying to bind MyString to UserControl.Name if MyString is not specified in the <ctrl:MyControl ...> tag, then you can try using a PriorityBinding (example here), or just expose the Name property to the outside world to let outside elements use the control like <ctrl:MyControl Name="{Binding ...}" />
If you display in both the Textbox and Mycontrol the same value... why dont you bind them to that property? :
<Textbox Name="TextBoxInHostApp" Text="{Binding Name}"/>
<ctrl:MyControl
....
MyString="{Binding Name}"
</ctrl:MyControl>
I've been searching all over for a solution, but found nothing that works.
The problem is simple:
DataGrid (read-only) is bound to a collection of objects (implementing INotifyPropertyChanged)
When certain properties of data objects change, the cell background should animate (eg. from Red to Transparent)
I've tried using styles with EventTrigger (TargetUpdated) to start a Storyboard, but it has side-effects, all cells' background is animated when DataGrid is first populated, and also when it is scrolled or re-sorted.
I know there are few other similar questions, but I didn't see a working solution.
Has anyone been able to achieve this? I'd very much prefer not to have any code-behind, but if it's necessary, I'll live with it...
EDIT:
I've noticed there is some confusion as to what I'm trying to achieve:
Let's say a cell (and it's underlying property on data object) has a value "A". At some point it changes to "B" (e.g. update from a server). At this point the background should 'flash' (e.g. 1 second animation from Red to Transparent). At all other times the background should be Transparent.
I've been finally pointed in right direction on MS forum, the solution is to use attached behavior that registers OnTargetUpdated handler and starts s Storyboard. I've tried this approach earlier, but apparently one must start the Storyboard only if IsLoaded property of the cell is true. That gets rid of side effects I mentioned above.
Here is the link to the forum post.
add a converter something like this :
namespace System.Converters
{
//Converter for cell animation
public class flashConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string cellvalue = value.ToString();
return cellvalue = ("place the condition here");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return false;
}
}
}
in your MainWindow.xaml.cs add the namespace
xmlns:loc="clr-namespace:YourProjectName.Converters"
in your resources add the following :
<DataGrid.Resources>
<loc:flashConverter x:Key="SomeConverter"></loc:flashConverter>
</DataGrid.Resources>
In your DatagridTextColumn add the following :
<DataGridTextColumn Header="yourDatagridHeader" IsReadOnly="True" Binding="{Binding Path=yourDatagridHeader}">
<DataGridTextColumn.ElementStyle>
<!--Style to implement the datagrid cell animation for yourDatagridcell-->
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding yourDatagridHeader}" Value="Give your condition here">
<!-#E6F85050 is the hexadecimal value for RED-->
<Setter Property="Background" Value="#E6F85050"/>
</DataTrigger>
<DataTrigger Binding="{Binding yourDatagridHeader}" Value="Give your condition here">
<Setter Property="Background" Value="give the hexadecimal value for transparent here "/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
hope this helps !
I'm not sure the best way to ask this question (sorry for the ambiguous question title), but essentially I'd like to set the MaxLength property on a TextBox using a value converter that is passed in a property from the data context, and the property on the passed-in property as the converter parameter. I'd like to do all this in a style, as opposed to on a control-by-control basis. Here's an example of doing this in a non-styled manner:
<TextBox Text="{Binding MyPropertyName.TheirPropertyName}" MaxLength="{Binding MyPropertyName, Converter={StatocRespirceMyCoolConverter}, ConverterParameter=TheirPropertyName}" />
(In case you're wondering, TheirPropertyName represents a property on the type of MyPropertyName that has an attribute like [StringMaxLength(15)], which I'd be able to get to and return inside the value converter.)
Additionally, is there any way to pass in the type of MyPropertyName as opposed to the instance? I only need the type to do the StringMaxLength attribute lookup.
Anyway, how could I go about doing something like this in a style? I've gotten as far as:
<Setter Property="MaxLength">
<Setter.Value>
<Binding Converter="{StaticResource textFieldMaxLengthConverter}" />
</Setter.Value>
</Setter>
But that passes the overall datacontext in to the value converter, as opposed to the MyPropertyName object, and I really have no clue if I can have it parse the MyPropertyName.TheirPropertyName part of the binding to pass TheirPropertyName in on the ConverterParameter attribute of the binding.
Any guidance would be really appreciated!
Ok, after some more digging, I've figured this out to my satisfaction. I'm binding to RelativeSource Self and then parsing the binding expression on the Text property (since this is a TextFieldMaxLength converter, I am presuming I'm working against a TextBox.
The styling up in the resource dictionary:
<Style TargetType="TextBox">
<Setter Property="MaxLength">
<Setter.Value>
<Binding Converter="{StaticResource textFieldMaxLengthConverter}" RelativeSource="{RelativeSource Self}" />
</Setter.Value>
</Setter>
</Style>
The usage (basically showing nothing special needs to be done since it's all in the style):
<TextBox Text="{Binding MyPropertyName.TheirPropertyName}" />
The Convert Method for the textFieldMaxLengthConverter:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Control control = value as Control;
BindingExpression be = control.GetBindingExpression(TextBox.TextProperty);
if (be != null)
{
string boundPropertyName = be.ParentBinding.Path.Path;
// .. boundPropertyName here is MyPropertyName.TheirPropertyname, do some parsing and return a value based on that
}
}
(Obviously my actual implementation is a bit more complex/handles unexpected input/uses reflection as per my original question's statement).
Anyway, thought I would post this solution in case anyone else tries to do something similar, or if there might be a better way to do this than I am using.
you can pass in lutiple properties to your converter by using a multi binding, this allows you to do a binding on as may properties as you want, and if any of the properties change (i.e. implent INotifyPropertyChanged) the binding will be reevaluated. for what you are doing you would have to use reflection to find a property on the passed in object with a particular property name that matches your converter parameter. i dont think you will end up using the code below, but it shows you can have multiple parameters to your binding in xaml. including the path, converter, converter parameter. Im not sure about the relative source but however, but i think you might need it to do what you want. have a look at debugging Data Bindings for a good way to debug. this technique is essential. i use it continually.
<Setter
Property="MaxLength">
<Setter.Value>
<Binding
Converter="{StaticResource textFieldMaxLengthConverter}"
RelativeSource="{RelativeSource TemplatedParent}"
Path="MyPropertyName"
ConverterParameter="TheirPropertyName" />
</Setter.Value>
</Setter>