Say I have a Grid, with a lot of cells on it, and I bind background of this cell to some property of my data class in a style (actually data class property is type of Color, but this is not an issue, because we can use a converter to convert it to a Brush),
Now when some condition in my data class is true I want the background to be red, and if not, I want it to be the default value, data may change, so condition may become true and false, and I should fill Background red or default
I know about Binding.DoNothing and DependencyProperty.UnsetValue both are not case, I tried also Cell.BackgroundProperty.DefaultValue but it is null.
So Is there any value, that I can return from my bound data property, to force the dependency property to reset its value?
Thanks!
If you only have a boolean property that is quite convenient as you can use a DataTrigger and just bind the value if the property is true, that way the property is not always bound.
<Style.Triggers>
<DataTrigger Binding="{Binding MyCondition}" Value="True">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
If you only have the decision between default and red you do not even need the additional property or any binding at all.
(Resetting values is not possible in a binding to my knowledge)
Related
I implemented a small visual indicator (just a textblock with a border), that should be hidden if there is no text to be shown at the moment. The text is bound to the Indicator property, the data context seems to be set correctly.
What I got so far is this (indicator text appears, hide/show doesn't work):
<Border>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Indicator.Length}" Value="0">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding Indicator}" />
</Border>
My problem is that the element is not hidden if the text length is zero.
Do you spot my mistake?
Indicator is part of the corresponding viewmodel:
public string Indicator
{ get; set;}
UPDATE
It works if I change the property above to this:
public const string IndicatorPropertyName = "Indicator";
private string _indicator = "";
public string Indicator
{
get
{ return _indicator;}
set
{
if (_indicator == value) { return;}
RaisePropertyChanged(IndicatorPropertyName);
}
}
Why does it only work, if I raise PropertyChanged event?
I think as the name implies a Trigger only executes or checks its state when an event occurs.
In case of a DataTrigger it is the PropertyChanged-Event of the interface INotifyPropertyChanged.
Wihout raising the Event the DataTrigger doesn´t know that he has to check the binding and if the value meets the trigger condition.
Your source binding is Indicator which is a plain CLR property.
WPF is based heavily on data binding. Bound objects update automatically when the binding source changes because under the hood a change notification is raised. When bounding to a dependency property (that is a property type understood only by WPF) you already have built in change notification for bound objects. No extra work is required.
You can also bound to plain CLR properties but in this case there is no built-in change notification for bound objects. You need to implement the change notification programmatically. This is done by raising the PropertyChanged event. The objects bound to that CLR property will be notified of value change and will update themselves.
In short if you want a CLR property to update a target binding, you need to implement INotifyPropertyChanged and raise the PropertyChanged event.
Following scenario:
<ComboBox ItemsSource="{Binding Path=Names}"
SelectedItem="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}">
</ComboBox>
Names is a string collection which might change through code in the ViewModel. For example Names could be set to either male names (John, Paul, George) or female names (Mary, Jane, Karen).
Let's assume the male collection is active and the user selects "John" making it the SelectedItem. Now the collection changes to female names. By default the SelectedItem will be set to Null and John will not be displayed anymore. Instead an empty string will be displayed.
What I would like to achieve is that "John" is still visible as SelectedItem although it is not in the collection anymore. However as it is not part of the collection I would like to change the color of the SelectedItem to red.
I have played around with the IsEditable property, but didn't like the changing appearance of the ComboBox. The user should not be able to enter text manually anyway.
I tried to use different templates and styles (e.g. Template, ItemTemplate, ItemContainerStyle), but didn't find a way to use it to my favour.
Is it possible to style the provided ComboBox the way I need it or do I have to create my own user control?
EDIT:
I have found a solution I can live with.
<ComboBox IsEditable="True" IsReadOnly="True"
ItemsSource="{Binding Path=Names}"
Text="{Binding Path=Name}">
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsNameInCollection}" Value="False">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Foreground" Value="Black"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
In the ViewModel I make sure that the Name property does not change when the Names collection changes. The boolean property IsNameInCollection will be updated when the Name property or the collection changes.
Note: this no a real, working answer / solution, but just some thoughts you might find worth to consider:
First, the SelectedItem will always change to null if the collection changes (either by replacing it with a new collection or clearing and refilling it). So as you are binding to Name property of some object, can you prevent that property from being overridden with null there internally? That way the value might not change in the ComboBox (maybe you also need TwoWayBinding for Name to make the value stick; or even firing a change event, but that depends on some testing).
Second, the rendering needs to change, so the easiest way would be to use the ItemTemplate and a custom IValueConverter implementation to convert the information of the special entry with different color / style. But the downside of this is, that the ItemTemplate will normally be used for every entry, including the current selected.
One way to deal with this is to use an ItemTemplateSelector like described here: http://www.wpftutorial.net/datatemplates.html (section "How to use a DataTemplateSelector to switch the Template depending on the data")
Another way would be to use a container instead of plain string. That container would also hold a boolean value to indicte a different rendering. That boolean value could be used in the ItemTemplate. But in this scenario you need to wrap all string values.
Third, how should the ComboBox react if the selected value is still the old value that is not in the current list, and then the user selects another value. One logic would be that the old value just disappears, but then the user cannot select it back. If it should be still available in the list, then you must somehow add it to the list of current values, but mark it that it is a special value. In both cases you also need a logic that detects later if a value was chosen that is not valid according to the current available list.
I hope this helps to find a working solution.
I have two DataGrid's that I want do have enabled/disabled based on whether precisely 1 element is selected in another DataGrid. What is the simplest way to accomplish this dependency control in WPF?
You could use a trigger:
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItems.Count,
ElementName=datagrid1}"
Value="1">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
You could:
Create an IValueConverter, perhaps it is called NotEqualToOneBooleanConverter
Bind IsEnabled from one DataGrid to the SelectedItems.Count on the other
Set the Converter on this Binding to be the NotEqualToOneBooleanConverter
This approach is nice since once your converter is created, it can be applied throughout your XAML and to any type and any property (not just DataGrid or SelectedItems.Count). To make it even more flexible, you could have a more generic version of this converter that could compare any two values specified directly from XAML (one from the Binding and one specified as property on the Converter).
The downside to this approach - it's XAML only, and difficult to test especially if what you are trying to achieve is a business requirement and not just a graphical effect.
Hope this helps!
This is my quick hack:
tablesControl.SelectionChanged += (sender, sce) =>
{
var c = tablesControl.SelectedItems.Count;
var orderingPossible = c == 1;
itemsControl.IsEnabled = orderingPossible;
};
In the first Grid have an event or Command that is fired when you click on that cell, in this event you need to have some bool property you can set to false, then bind the Enabled property to this bool. If you are using MVVM this will be very easy, have a look at this to see how - http://www.youtube.com/watch?v=tKfpvs7ZIyo
The object is an ListBoxItem, although I tried with a Panel as well.
I get this error message: Property can not be null on Trigger., within an InvalidOperationException.
Here's the trigger:
<Trigger Property="IsInitialized" Value="true">
<Setter TargetName="MyPanel" Property="Background" Value="AliceBlue">
</Setter>
</Trigger>
Note: I can't use Loaded, because I don't want it to fire whenever the control is rendered. Just at initializiation.
This property exists... why doesn't it work?
This is because the IsInitialized property is not a Dependency Property. Triggers can only be used with dependency properties.
But the question is why would you need such a trigger? Because the same effect can be achieved just by specifying the Background property directly on MyPanel or in the style of `ListBoxItem'.
With DataTriggers in WPF its possible to set properties on controls based on the the object you have bound. For example you could set the Background of a TextBlock based on a IsAlive property on your object.
<DataTrigger Binding="{Binding Path=IsAlive}" Value="true">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
I want to know if its possible to go in reverse. Is it possible to set a property on a databound item based on the state of the control its bound to?
Say I want to set the IsAlive property to true when the control its bound to receives the mouseover event.
Can this be done in WPF & data triggers? Thanks.
I don't know if what you're asking is directly possible, but I suspect it isn't. On the other hand, I think you could make your example scenario work by binding the object's "IsAlive" property directly to the control's "IsMouseOver" dependency property, with Mode=OneWayToSource.
You might want to use EventSetter, then handle the setting by code using the DataContext property of the sender, or with GetBindingExpression.
This gives you an option to set a handler on the style level.