Binding ComboBoxItem Value Inside of a Setter Property in WPF MVVM - c#

In my wpf application I have a ComboBox which I want to have the ability to disable the selection of items in the drop-down programmatically. The issue that I am having is that the binding ComboBoxItemIsEnabled is not working as expected inside the setter. If remove the binding and use either True or False it works as expected.
XAML
<ComboBox
ItemsSource="{Binding Path=ConfigItems.Result}"
DisplayMemberPath="Name"
IsEditable="True"
FontSize="14"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsTextSearchEnabled="False"
Text="{Binding Path=ConfigItem,
UpdateSourceTrigger=LostFocus,
TargetNullValue={x:Static sys:String.Empty}}"
b:ComboBoxBehaviors.OnButtonPress="True">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding ComboBoxItemIsEnabled}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
C#
private string _comboBoxItemIsEnabled = "True";
public string ComboBoxItemIsEnabled
{
get
{
return this._comboBoxItemIsEnabled;
}
set
{
this.SetProperty(ref this._comboBoxItemIsEnabled, value);
}
}
public async Task<ConfigItem[]> LoadConfigItemsAsync(string partialName)
{
try
{
if (partialName.Length >= 5)
{
this.ComboBoxItemIsEnabled = "True";
return await this._Service.GetConfigItemsAsync(partialName);
}
this.ComboBoxItemIsEnabled = "False";
return new[] { new ConfigItem("Minimum of 5 characters required", null)};
}
catch (Exception)
{
this.ComboBoxItemIsEnabled = "False";
return new[] { new ConfigItem("No results found", null) };
}
}
I also get the the following error from the debug console when the ComboBoxIsEnabled is being set.
System.Windows.Data Error: 40 : BindingExpression path error: 'ComboBoxItemIsEnabled' property not found on 'object' ''ConfigItem' (HashCode=56037929)'. BindingExpression:Path=ComboBoxItemIsEnabled; DataItem='ConfigItem' (HashCode=56037929); target element is 'ComboBoxItem' (Name=''); target property is 'IsEnabled' (type 'Boolean')
I am using the same mvvm method to target an IsEnabled property for a button else where without an issue. The only difference I can see in the issue above is that I am setting the property within a setter instead.
Many thanks for any wisdom you can part with on how to solve this issue.

After much procrastinating and smashing my head against the keyboard I managed to come to a solution. As it turns out I needed to set the Relative source for the binding. Because I didn't define the DataContext for my solution, every time I pressed a character in the combobox the ItemSource was updated. This meant the ComboBoxItemIsEnabled binding couldn't be found giving me the error above. Below is my updated code, I have added DataContext in front of my binding and added RelativeSource={RelativeSource AncestorType=ComboBox} behind it.
Below is my final code.
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding DataContext.ComboBoxItemIsEnabled, RelativeSource={RelativeSource AncestorType=ComboBox}}" />
</Style>
</ComboBox.ItemContainerStyle>

Related

How to keep Two-Way Binding while updating value through Trigger/Setter in ResourceDictionary / Template Document

<Grid x:Name="RedGrid" Visibility="{Binding Path=IsVisible,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
As you can see the Visibility of this Grid is tied to the IsVisible property.
<DataTemplate.Triggers>
<Trigger SourceName="RedGrid" Property="Opacity" Value="0">
<Setter TargetName="RedGrid" Property="Visibility" Value="Collapsed"/>
</Trigger>
</DataTemplate.Triggers>
This part of code will change the visibility of the RedGrid once the opacity reachs / equals zero
But obviously the second part of the code doesn't work, since the Setter for IsVisible is not called when the Visibility of RedGrid is successfully changed to Collapsed.
public Visibility IsVisible
{
get => _isVisible;
set
{
_isVisible = value;
RaisePropertyChanged("IsVisible");
}
}
What you are trying to do is to update the Visibility property of the Grid using a Setter and maintain the binding at the same time. The problem is that binding is overwritten by the Setter!
When you use Xaml Setters you set properties the same way as you set them in the tag, so when you write:
<Setter TargetName="RedGrid" Property="Visibility" Value="Collapsed"/>
It acts the same way as if you've written:
<Grid x:Name="RedGrid" Visibility="Collapsed">
Notice that the binding Visibility="{Binding Path=IsVisible"} has been overwritten by the value Visibility="Collapsed" and will no longer work.
But Why?
The way that TwoWay binding works can be found inside the controls themselves. When you work with DependencyProperties by default you set them using SetValue() method, which sets the property's value and overwrites any binding in place.
However, in controls like TextBox or CheckBox that take in user input, when the user interacts with them, properties like TextBox.Text and CheckBox.IsChecked are being set by the method SetCurrentValue() which like the documentation says:
The SetCurrentValue method changes the effective value of the property, but existing triggers, data bindings, and styles will continue to work.
You can review this question for more information about the differences between SetValue() and SetCurrentVaule() methods.
What to do:
In your case, I think that you can add a GridOpacity property to the view model, and instead of changing the Visibility in Xaml using a Setter, you can change the view model's IsVisible property directly when you set the GridOpacity property.
Here is a sample code of the view model:
private Visibility _isVisible;
public Visibility IsVisible
{
get => _isVisible;
set
{
if (_isVisible == value)
{
return;
}
_isVisible = value;
RaisePropertyChanged(nameof(IsVisible));
}
}
private double _gridOpacity;
public double GridOpacity
{
get => _gridOpacity;
set
{
if (_gridOpacity == value)
{
return;
}
_gridOpacity = value;
RaisePropertyChanged(nameof(GridOpacity));
// Change the visibility of the grid when opacity hits 0.
if (value == 0)
{
IsVisible = Visibility.Collapsed;
}
else
{
// Change it back.
IsVisible = Visibility.Visible;
}
}
}
And here is the Xaml:
<Grid x:Name="RedGrid" Visibility="{Binding IsVisible}" Opacity="{Binding GridOpacity}">
...
</Grid>

WPF checkbox all not not updating on one checkbox row is unchecked

I had a checkbox all column inside the datagrid in WPF C#.
<DataGridCheckBoxColumn Binding="{Binding IsSelected,UpdateSourceTrigger=PropertyChanged}" CanUserSort="False">
<DataGridCheckBoxColumn.ElementStyle>
<Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridCheckBoxColumn.ElementStyle>
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate x:Name="dtAllChkBx">
<CheckBox Name="cbxAll" HorizontalAlignment="Center" Margin="0,0,5,0" IsEnabled="{Binding Path=DataContext.IsCbxAllEnabled,RelativeSource={RelativeSource AncestorType=DataGrid}}"
IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=DataGrid},UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>
When I check the All checkbox, of course, it will mark all the checkboxes, but once I uncheck one checkbox, the All checkbox is still checked. This should be unchecked. How should I do that using WPF C#.
If I understood you correctly - after any change of IsSelected property inside collection item you should update AllSelected value.
So, you need some callback inside all your items(event or Action or any mechanism you want) and change get logic for AllSelected
Here is some draft for item IsSelected property and constructor:
public bool IsSelected {
get { return isSelected; }
set {
isSelected = value;
OnPropertyChanged();
if (globalUpdate != null) globalUpdate();
}
}
public ItemClass(Action globalUpdate, ...your parameters) {
this.globalUpdate = globalUpdate;
...do smth with your parameters
}
Example of usage:
new ItemClass(() => OnPropertyChanged("AllSelected"))
And of course don't forget about AllSelected getter
public bool AllSelected {
get { return YourGridItemsCollection.All(item => item.IsSelected); }
Now when you check manually all items then AllSelected will be automatically checked, and unchecked when you uncheck any item.

Conditional binding from view model to view in WPF using MVVM pattern

I am developing a WPF application using MVVM pattern. I have a combo in the view and two lists in the viewmodel (projects and organizations). Depending on the organizations list items I have to bind the the name of the organization or not.
For example if the Count property of the organizations list is 1 the combobox item have to be "ProjectName", and if the Count property of the organizations list is greater than 1 the combobox item should look like "ProjectName - OrganizationName".
This is the XAML code I have:
<ComboBox x:Name="textBox3" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" ItemsSource="{Binding Path=Projects}" DisplayMemberPath="Name" IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding Path=SelectedProject}">
</ComboBox>
How should I achieve this purpose. I hope for a little help. Cheers.
I added the property projectFullName in the viewmodel but I got an empty combobox:
public string ProjectFullName
{
get
{
if (this.organizations.ToList().Count > 1)
{
this.projectFullName = string.Format("{}{0} - {1}", this.selectedProject.Name, this.organizations.First(org => org.Id == this.selectedProject.OrganizationId).Name);
}
else if (this.organizations.ToList().Count == 1)
{
this.projectFullName = this.selectedProject.Name;
}
return this.projectFullName;
}
}
XAML code:
<ComboBox x:Name="textBox3" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" ItemsSource="{Binding Path=Projects}" DisplayMemberPath="{Binding Path=ProjectFullName}" IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding Path=SelectedProject}">
</ComboBox>
you have several options to implement this, but in my opinion the best is:
Add a property to your Data Context, the will be called "FullName" or something.
That will return: (Pseudo)
if Projects count > 0 then
return Name + '-' + ProjectName
else return Name
then bind DisplayMemberPath to FullName.
Datatrigger is indeed your friend. Make sure the ComboBox does not set the DisplayMemberPath, because that will override the style setters.
<Style x:Key="MyStyle" TargetType="ComboBox">
<Setter Property="DisplayMemberPath" Value="DefaultName"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count}" Value="1">
<Setter Property="DisplayMemberPath" Value="OtherName"/>
</DataTrigger>
</Style.Triggers>
</Style>

Xamarin.Forms DataTrigger not working

I'm trying to set DataTrigger on my control in Xamarin.Forms, but I cannot get it working.
I have Property in ViewModel with OnPropertyChange execution for bool IsValid
I have tried:
DataTrigger in Xaml:
<customControls:NumericTextBox
Grid.Row="0" Grid.Column="2"
Text="{Binding StringValue, Mode=TwoWay}"
IsEnabled="{Binding IsEditable}"
XAlign="End">
<customControls:NumericTextBox.Style>
<Style TargetType="customControls:NumericTextBox">
<Style.Triggers>
<DataTrigger TargetType="customControls:NumericTextBox" Binding="{Binding IsValid}" Value="true">
<Setter Property="TextColor" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</customControls:NumericTextBox.Style>
</customControls:NumericTextBox>
Getting exception: The Property TargetType is required to create a Xamarin.Forms.DataTrigger object.
DataTrigger in Control:
_item = new DataTrigger(typeof(NumericTextBox));
_item.Binding = new Binding("IsValid",BindingMode.Default,new NegativeBooleanConverter());
_item.Value = true;
Setter s = new Setter();
s.Property = TextColorProperty;
s.Value = Color.Red;
_item.Setters.Add(s);
this.Style.Triggers.Add(_item);
Exception: Exception has been thrown by the target of an invocation.
I have tried also changing line : this.Style.Triggers.Add(_item); to this.Triggers.Add(_item);. This wasn't raising exception, but it just didn't worked.
In this last try, it even hits converter, but does not change TextColor of Control.
Am I doing something wrong? How to handle that?
I ran into this same issue. As a workaround, instead of using a DataTrigger you could bind the TextColor property to a Color property in your ViewModel. In the IsValid setter you could then set the color based on the value.
public bool IsValid
{
get { return _isValid; }
set
{
_isValid = value;
MyNewTextColorProperty = _isValid ? Color.Blue : Color.Red;
OnPropertyChanged();
}
}

OnPropertyChange not firing IDataErrorInfo.this[]

I'm attempting to catch errors using IDataErrorInfo, but changes in the bound data are not firing IdataErrorInfo.this[]. I believe it's due to the way I'm binding data to the textbox.
My textbox Text is bound to a source as follows:
<TextBox Grid.Row="0" Grid.Column="1" Margin="8 0 0 0"
Text="{Binding LimitsConfiguration.ThisItemMax, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
Style="{StaticResource ValidatableTextBoxStyle}"
HorizontalAlignment="Left" Width="40" Height="25" VerticalAlignment="Bottom" />
The StaticResource is defined by:
<Style x:Key="ValidatableTextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
The source is defined as follows. Note that his object is not created in the same namespace and has about 9 different string members defined in it.
public LimitsConfig LimitsConfiguration
{
get { return _limitsConfiguration; }
set
{
_limitsConfiguration = value;
OnPropertyChanged("LimitsConfiguration");
}
}
And my IDataErrorInfo.this[] implementation is as follows:
string IDataErrorInfo.this[string propertyName]
{
get
{
string result = String.Empty;
string limitsErrorMsg = "Enter a numeric value for ";
int i;
if (propertyName == "LimitsConfiguration")
{
if (propertyName == LimitsConfiguration.ThisItemMax.ToString())
{
string msg = limitsErrorMsg + "Max Itmes";
string field = LimitsConfiguration.ThisItemMax.ToString();
result = ValidateLimit(field, msg);
}
}
return result;
}
}
The implementation of IDataInfo.this[string propertyName] is never hit. However, the IDataErrorInfo implementation works when textbox text is bound to a string type. Therefore, I believe the issue is due to the binding of a member of the source (Binding LimitsConfiguration.WaypointsMax), but I'm not sure how to get around it other than create public memebers for all items in the LimitsConfiguraton object (which I'd rather not do).
I am new to WPF, so any ideas would be appreciated.
The text box does not listen for property changed events on the class that contains the LimitsConfiguration property. Instead it listens on the property changed events of the LimitsConfig class, because this is the class that contains the property that is bound to the text box.

Categories