Using binding path=. is disconnecting my binding - c#

I am using radio buttons which has the datacontext set to a observabelcollection of enums. When i am binding my radio buttons with path set to dot as shown below, data binding works for the first time the app comes up but then the data binding fails. If anyone knows why???
<RadioButton Content="No Model" FontSize="16" IsChecked= "{Binding Path=SisoModel, Mode=TwoWay, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:SISOModels.NoModel}}"/>
<RadioButton Content="Prediction Only" FontSize="16" IsChecked="{Binding Path=SisoModel, Mode=TwoWay, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:SISOModels.PredictionOnly}}"/>
<RadioButton Content="Prediction And Control" FontSize="16" IsChecked="{Binding Path=SisoModel, Mode=TwoWay, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:SISOModels.PredictionAndControl}}"/>
conversion code is here:
[ValueConversion(typeof(Enum), typeof(bool))]
public class EnumToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || parameter == null) return false;
string enumValue = value.ToString();
string targetValue = parameter.ToString();
bool outputValue = enumValue.Equals(targetValue, StringComparison.InvariantCultureIgnoreCase);
return outputValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null || parameter == null) return null;
bool useValue = (bool)value;
string targetValue = parameter.ToString();
if (useValue) return Enum.Parse(targetType, targetValue);
return null;
}
}
model and view mode code is here:
public enum SISOModels
{
NoModel,
PredictionOnly,
PredictionAndControl
};
public class SisoModels1 : BindableBase
{
public SisoModels1(SISOModels _SisoModel)
{
SisoModel = _SisoModel;
}
public SISOModels SisoModel { get; set; }
}
within a for loop based on size of grid i have written below code, which will add these radio buttn user control(additionalSetup) into all cells of grid and set the datacontext:
AdditionalSetup a1 = new AdditionalSetup();
a1.DataContext = vm.sisoModelList[ct];
ct++;
reason for going with observable collection is that we have to populate these radio buttons in all the cells of a grid and the grid size is available runtime. compile time am not sure how many radio buttons i am going to populate.

Without being able to test, its hard to say for sure; but I'm pretty confident that the return null at the end of ConvertBack is killing your bindings.
The correct ValueEquals (Or EnumToBoolean) converter is:
public Convert(...)
{
return value.Equals(parameter);
}
public ConvertBack(...)
{
if ((bool)value)
return parameter;
else
return Binding.DoNothing; //Not null!!!!
}
Enum types are value types, so unless you put a nullable wrapper around them, a null check is not usually necessary (feel free to add though).
More importantly, you should not be returning null out of this converter. Use Binding.DoNothing instead.
If you are binding directly against the ObservableCollection I'm guessing that is causing you a few issues as well, since it is obviously not an enum.

Related

Is it possible to fire a converter if and only if some pre-condition is satisfied?

I have a group of radio buttons all connected up to the same variable in the viewmodel
<RadioButton Content="20" Margin="5,0" GroupName="open_rb"
IsChecked="{Binding Path=mOpen.Threshold, Mode=OneWayToSource, Converter={StaticResource RadioBoolToIntConverter}, ConverterParameter=20, UpdateSourceTrigger=PropertyChanged}" />
<RadioButton Content="50" Margin="5,0" GroupName="open_rb"
IsChecked="{Binding Path=mOpen.Threshold, Mode=OneWayToSource, Converter={StaticResource RadioBoolToIntConverter}, ConverterParameter=50, UpdateSourceTrigger=PropertyChanged}"/>
<RadioButton Content="70" Margin="5,0" GroupName="open_rb"
IsChecked="{Binding Path=mOpen.Threshold, Mode=OneWayToSource, Converter={StaticResource RadioBoolToIntConverter}, ConverterParameter=70, UpdateSourceTrigger=PropertyChanged}"/>
And my converter is -
public class RadioBoolToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
//should not hit this part
int integer = (int)value;
if (integer == int.Parse(parameter.ToString()))
return true;
else
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
//bool to int (if bool is true then pass this val)
bool val = (bool)value;
if (val == true)
return UInt32.Parse(parameter as string);
else
{
//when you uncheck a radio button it fires through
//isChecked with a "false" value
//I cannot delete this part, because then all code paths will not return a value
return "";
}
}
}
Basically the idea is that if a certain radio button is clicked, the converter passes 20 or 30 or 70 (depends upon which radio button is clicked) to mOpen.Threshold which is an unsigned int.
Now, the radiobutton "isChecked" event gets triggered if the radio button is checked or not, with values of true and false. Now, if it's false, I return an empty string from the converter which doesn't parse to uint and causes the UI to complain.
Is it possible to only use this converter if the radio button is checked? Which implies that for this group, if I click a radio button, this converter should only be fired once, not twice.
Using this approach it seems like you only want to set the source property when any of the IsChecked properties are set to true. You can then return Binding.DoNothing when val is false:
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
//bool to int (if bool is true then pass this val)
bool val = (bool)value;
if (val == true)
return UInt32.Parse(parameter as string);
return Binding.DoNothing;
}

How to fire Property Changed with an Enum Property?

I have a standard Enum that will either be Yes or No:
public enum YesOrNo
{
Yes,
No
}
My base Model Class has a YesOrNo property like this:
public class Group : NotifyPropertyChanged
{
private YesOrNo groupOperator;
public YesOrNo GroupOperator
{
get
{
return this.groupOperator;
}
set
{
this.groupOperator = value;
OnPropertyChanged("GroupOperator");
}
}
In my View, I am using a ToggleSwitch, similar to a slider you would see on a Mobile phone. Sliding back and forth should effectively reassign the value of the Enum. So it will default as Yes and sliding the toggle will set the Enum value to No and alternatively.
If I were to have a test method that reassigns the Enum when the Checked command is hit, the PropertyChanged event is fired so I know that is technically working. I am just wondering how I could go about alternating values in the Enum.
This is the ToggleButton in my XAML:
<ToggleButton Style="{StaticResource ToggleViewSwitch}" Command="{Binding SetOperatorCommand, UpdateSourceTrigger=PropertyChanged}" IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
And this is my Main View Model, where I hold the Command and the test method to assign the value manually:
private bool isChecked = false;
public bool IsChecked
{
get
{
return this.isChecked;
}
set
{
this.isChecked = value;
OnPropertyChanged("IsChecked");
}
}
private RelayCommand setOperatorCommand;
public ICommand SetOperatorCommand
{
get
{
if (this.setOperatorCommand == null)
{
this.setOperatorCommand = new RelayCommand(
x => ToggleGroupOperator());
}
return this.setOperatorCommand;
}
}
private void ToggleGroupOperator()
{
if (IsChecked)
{
TopLevelGroup.GroupOperator = YesNo.No;
}
else
{
TopLevelGroup.GroupOperator = YesNo.Yes;
}
}
First make a Converter...
public class YesOrNoToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> (value is YesOrNo yesOrNo && yesOrNo == YesOrNo.Yes) ? true : false;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> (value is bool isYes && isYes) ? YesOrNo.Yes : YesOrNo.No;
}
Then reference the converter during binding...
<Window.Resources>
<Converters:YesOrNoToBooleanConverter x:Key="YesOrNoToBooleanConverter" />
</Window.Resources>
<Grid>
<CheckBox IsChecked="{Binding GroupOperator, Converter={StaticResource YesOrNoToBooleanConverter}}" />
</Grid>
This will allow your ViewModel to remain using the enum without any overhead and the view to bind without any overhead; leave this binding manipulation work to converters.

WPF Databinding fails at binding entire object

In the following DataTemplate, the first binding doesn't work while the 2nd one works, and I would like to know why.
<local:IsEnabledConverter x:Key="isEnabled"/>
<local:Boolean2TextConverter x:Key="txtConverter"/>
<DataTemplate x:Key="fileinfoTemplate" DataType="{x:Type local:MyFileInfo}">
<StackPanel>
<Label x:Name="1stLabel" Content="{Binding Path=Filename}" IsEnabled="{Binding Path=., Converter={StaticResource isEnabled}}"/> <--- doesn't work
<Label x:Name="2ndLabel" Content="{Binding Path=IfPrint, Converter={StaticResource txtConverter}}" IsEnabled="{Binding Path=IsChecked, ElementName=ckBox}"/> <--- works
<CheckBox x:Name="ckBox" IsChecked="{Binding Path=IfPrint}" IsEnabled="{Binding Path=IsValid}" Style="{StaticResource printCkBox}"/>
</StackPanel>
</DataTemplate>
IsEnabledConverter:
class IsEnabledConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
MyFileInfo f = value as MyFileInfo;
return f.IsValid && f.IfPrint;
}
//... omit ConvertBack NotImplementedException stuff
}
Boolean2TextConverter:
class IsEnabledConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
Boolean b = (Boolean)value;
return b.ToString();
}
//similarly omit ConvertBack here
}
Code for MyFileInfo:
public class MyFileInfo {
public string IfPrint {
get;
set;
}
public string IsValid {
get;
set;
}
...
}
Problem: When the CheckBox is toggled, the 2nd Label grays out and shows "false", or becomes normal and shows "true", as it should. However, the first Label doesn't change at all; its IsEnabled state is supposed be the conjunction of two Booleans, one of which is changed by the CheckBox. What is wrong? (note that the IsEnabledConverter is called once upon GUI initialization, but not called again when its binding source changes.)
There are 2 issues here. First you have to implement INotifyPropertyChanged for the ViewModel MyFileInfo. Secondly you have to use MultiBinding here. Because I don't think we have some way to trigger updating the target (such as when toggling the CheckBox) if you bind the whole view model to the IsEnabled target. So here is how it should be done:
Your view model:
public class MyFileInfo : INotifyPropertyChanged {
bool _ifPrint;
bool _isValid;
public bool IfPrint {
get { return _ifPrint; }
set {
if(_ifPrint != value) {
_ifPrint = value;
OnPropertyChanged("IfPrint");
}
}
}
public bool IsValid {
get { return _isValid; }
set {
if(_isValid != value) {
_isValid = value;
OnPropertyChanged("IsValid");
}
}
}
//Implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string prop){
var handler = PropertyChanged;
if(handler != null) handler(this, new PropertyChangedEventArgs(prop));
}
//.... should do the same for the remaining properties....
//...
}
Here is the converter used for MultiBinding, which should implement IMultiValueConverter (instead of IValueConverter):
class IsEnabledConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture) {
if(values.Length == 2){
return (bool) values[0] && (bool) values[1];
}
return false;
}
//... omit ConvertBack NotImplementedException stuff
}
Here is the modifed XAML (to use MultiBinding instead):
<Label x:Name="firstLabel" Content="{Binding Path=Filename}">
<Label.IsEnabled>
<MultiBinding Converter="{StaticResource isEnabled}">
<Binding Path="IsValid"/>
<Binding Path="IfPrint"/>
</MultiBinding>
</Label.IsEnabled>
</Label>
Now one of IsValid and IfPrint changing will trigger the MultiBinding's Converter. Here you can also bind to IsChecked of the CheckBox directly instead of indirectly via IfPrint.
PS: Note Name used in XAML (as well as in codebehind) must not start with number.
Since the instance of MyFileInfo does not change while you check/uncheck the checkbox hence IsEnabledConverteris not getting called.
In order to Enable/Disable your 1stLabel depending on two properties, either use MultiValueConverter or use MultiDataTrigger by applying Style to your Label.

DataContext property bound to TextBox.Text doesn't update when the TextBox is cleared

In my SilverLight application I have a TextBox.Text bound to a nullable decimal of the DataContext. When I change the value of the TextBox for example from 25 to 6 it works, the property's setter on the DataContext is executed and the backing field updated, but when I clear the TextBox manually it doesn't work, the setter is not called.
[DataMember]
public decimal? Order
{
get { return order; }
set { order = value; }
}
decimal? order;
xaml snippet:
<Input:PraTextBox
Text="{Binding Path=Order, Mode=TwoWay}"
IsEnabled="{Binding Path=IsDefaultVisibleEnabled, Mode=TwoWay}"/>
An ordinary TextBox only updates the binding source on LostFocus by default. I don't know what your PraTextBox does. But I recommend you check whether it updates the source on LostFocus or on PropertyChanged.
Try specifying the UpdateSourceTrigger inside the binding:
Text="{Binding Path=Order, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Next try: use a converter:
public class NullableDecimalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture) { return value; }
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
string decimalString = value as string;
decimal parsedDecimal;
if (decimalString != null && Decimal.TryParse( decimalString,
out parsedDecimal ))
return parsedDecimal;
else
return null;
}
}
the binding:
Text="{Binding Path=Order, Mode=TwoWay,
Converter={StaticResource myNullableDecimalConverter}}"
Apparently Silverlight (and WPF as long as I know) treat an empty TextBox as an empty string and not null.. Also they believe that empty string conversion to a number is undetermined, so if you bind a number to a textbox, once you set value to the number, you cannot clear it anymore.
PropetyChanged doesnt fire simply because the number hasnt changed - get debugger and see it for yourself.
In Silverlight 5 you can use this:
<TextBox Text="{Binding SOMEFIELD, Mode=TwoWay, TargetNullValue=''}"
where SOMEFIELD is Nullable or other number..

Unable to Set Radio Button value when two windows are opened using the same ViewModel

I'm using the following control template in two windows that are opened at the same time and both using the SAME viewmodel.
Here is the template;
<ControlTemplate x:Key="SecurityTypeSelectionTemplate">
<StackPanel>
<RadioButton GroupName ="SecurityType" Content="Equity"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumBoolConverter}, ConverterParameter=Equity}" />
<RadioButton GroupName ="SecurityType" Content="Fixed Income"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumBoolConverter}, ConverterParameter=FixedIncome}" />
<RadioButton GroupName ="SecurityType" Content="Futures"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumBoolConverter}, ConverterParameter=Futures}" />
</StackPanel>
</ControlTemplate>
Here is the viewmodel property:
private SecurityTypeEnum _securityType;
public SecurityTypeEnum SecurityType
{
get { return _securityType; }
set
{
_securityType = value; RaisePropertyChanged("SecurityType");
}
}
Here's the Enum:
public enum SecurityType { Equity, FixedIncome, Futures }
Here is the converter:
public class EnumToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object enumTarget, CultureInfo culture)
{
string enumTargetStr = enumTarget as string;
if (string.IsNullOrEmpty(enumTargetStr))
return DependencyProperty.UnsetValue;
if (Enum.IsDefined(value.GetType(), value) == false)
return DependencyProperty.UnsetValue;
object expectedEnum = Enum.Parse(value.GetType(), enumTargetStr);
return expectedEnum.Equals(value);
}
public object ConvertBack(object value, Type targetType, object enumTarget, CultureInfo culture)
{
string expectedEnumStr = enumTarget as string;
if (expectedEnumStr == null)
return DependencyProperty.UnsetValue;
return Enum.Parse(targetType, expectedEnumStr);
}
}
The problem is a bit strange. I have two windows that are showing slightly different views of the SAME ViewModel. The same template shown above is reused in both views.
If Equity is initially set as SecurityType, i can change this to FixedIncome by clicking on the relevant radio button. I can not then change it back to Equity.
I can however set it to Futures. But then after that, i can not change it to either FixedIncome or Equity by clicking the relevant radio buttons.
What's happening in the cases where i can not set change it back is that the Setter is called twice. the first time it's setting the value to the correct selected value, but the moment RaisePropertyChanged is fired,
the setter is invoked again, this time with the original value.
It feels like when RaisePropertyChanged, the the setter is being called by the binding from the 2nd window, thus overwriting the value being set in the first window where the user makes the selection.
Does anyone know if this is the case and how to avoid in this scenario?
Here's my version of EnumToBoolConverter:
public class EnumToBoolConverter : BaseConverterMarkupExtension<object, bool>
{
public override bool Convert(object value, Type targetType, object parameter)
{
if (value == null)
return false;
return value.Equals(Enum.Parse(value.GetType(), (string)parameter, true));
}
public override object ConvertBack(bool value, Type targetType, object parameter)
{
return value.Equals(false) ? DependencyProperty.UnsetValue : parameter;
}
}
The default behavior for a RadioButton is to update the source when the property changes so both windows are trying to update the source. One fix is to only update the source only from where the user clicked. To do this use Binding.UpdateSourceTrigger Explict on the binding. Add a click handler in code behind for RadioButton. In it explicity update the source.
<StackPanel>
<RadioButton GroupName ="SecurityType" Content="Equity"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumToBoolConverter}, UpdateSourceTrigger=Explicit, ConverterParameter=Equity}" Click="RadioButton_Click" />
<RadioButton GroupName ="SecurityType" Content="Fixed Income"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumToBoolConverter}, UpdateSourceTrigger=Explicit, ConverterParameter=FixedIncome}" Click="RadioButton_Click"/>
<RadioButton GroupName ="SecurityType" Content="Futures"
IsChecked="{Binding Path=SecurityType, Mode=TwoWay, Converter={StaticResource EnumToBoolConverter}, UpdateSourceTrigger=Explicit, ConverterParameter=Futures}" Click="RadioButton_Click"/>
</StackPanel>
private void RadioButton_Click(object sender, RoutedEventArgs e)
{
BindingExpression be = ((RadioButton)sender).GetBindingExpression(RadioButton.IsCheckedProperty);
be.UpdateSource();
}
You may have to use a UserControl instead of or inside your ControlTemplate to get code behind in your view.

Categories