I need to bind a TextBox that meets two criteria:
IsEnabled if Text.Length > 0
IsEnabled if user.IsEnabled
Where user.IsEnabled is pulled from a data source. I was wondering if anyone had a easy method for doing this.
Here is the XAML:
<ContentControl IsEnabled="{Binding Path=Enabled, Source={StaticResource UserInfo}}">
<TextBox DataContext="{DynamicResource UserInfo}" Text="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding Path=Text, RelativeSource={RelativeSource Self}, Converter={StaticResource LengthToBool}}"/>
</ContentControl>
As GazTheDestroyer said you can use MultiBinding.
You can also acomplish this with XAML-only solution using MultiDataTrigger
But you should switch the conditions cause triggers support only equality
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text.Length}" Value="0" />
<Condition Binding="{Binding Source=... Path=IsEnabled}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger>
</Style.Triggers>
If one of the condition is not met the value be set to its default or value from the style. But do not set local value as it overrides style's and trigger's values.
Since you only need a logical OR, you just need two Triggers to your each of the properties.
Try this XAML:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=InputText, Path=Text}" Value="" >
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=MyIsEnabled}" Value="False" >
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<Label>MyIsEnabled</Label>
<CheckBox IsChecked="{Binding Path=MyIsEnabled}" />
</StackPanel>
<TextBox Name="InputText">A block of text.</TextBox>
<Button Name="TheButton" Content="A big button.">
</Button>
</StackPanel>
I set DataContext to the Window class which has a DependencyProperty called MyIsEnabled. Obviously you would have to modify for your particular DataContext.
Here is the relevant code-behind:
public bool MyIsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
public static readonly DependencyProperty MyIsEnabledProperty =
DependencyProperty.Register("MyIsEnabled", typeof(bool), typeof(MainWindow), new UIPropertyMetadata(true));
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
Hope that helps!
Bind IsEnabled using a MultiBinding
Related
I have implemented the following xaml code to disable a toolbar button when Validation.HasError based on the text entered into the ComboBox based on the UserAccountValidationRule
The core xaml code is shown here:
<telerik:RadComboBox
x:Name="DataProviderComboBox" Width="120" Height="23" IsEditable="True" DataContext="{StaticResource MainWindowViewModel}">
<telerik:RadComboBox.Text>
<Binding Mode="TwoWay" Path="InputString">
<Binding.ValidationRules>
<validationRules:UserAccountValidationRule/>
</Binding.ValidationRules>
</Binding>
</telerik:RadComboBox.Text>
</telerik:RadComboBox>
<telerik:RadButton
Name="ToolbarButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</telerik:RadToolBar>
<Grid>
<telerik:RadButton
Name="OrdinaryButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button" Margin="252,26,0,0">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</Grid>
What I would like is for the DataTrigger in the ToolBarButton to be invoked, but it is not.
I did a check on the validation by creating a second OrdinarryButton, and hooked this up to the validation, and this worked fine. It seems as if the DataTrigger does not work if the button is a toolbar item.
Can anyone explain how to get this to work?
So, as it turns out, if the button is an OrdinaryButton control, then the DataTrigger will be invoked and the button will be disabled on Validation.HasError = true via the xaml code below:
<Grid>
<telerik:RadButton
Name="OrdinaryButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button" Margin="252,26,0,0">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</Grid>
However, if the button control is placed inside a ToolbarButton, the same xaml code will not disable the Toolbar button:
<telerik:RadComboBox
x:Name="DataProviderComboBox" Width="120" Height="23" IsEditable="True" DataContext="{StaticResource MainWindowViewModel}">
<telerik:RadComboBox.Text>
<Binding Mode="TwoWay" Path="InputString">
<Binding.ValidationRules>
<validationRulesParameterPass:UserAccountValidationRule/>
</Binding.ValidationRules>
</Binding>
</telerik:RadComboBox.Text>
</telerik:RadComboBox>
<telerik:RadButton
Name="ToolbarButton" Width="74" HorizontalAlignment="Left" VerticalAlignment="Top" Content="Button">
<telerik:RadButton.Style>
<Style TargetType="{x:Type telerik:RadButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding (Validation.HasError), ElementName=DataProviderComboBox}" Value="true" >
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadButton.Style>
</telerik:RadButton>
</telerik:RadToolBar>
I find this very peculiar, and it might be a previously undiscovered bug? If anyone knows whether this is expected behavior or not, and has a reason for the observed behavior, please feel free to post.
In the meantime, I spent some time developing an acceptable workaround.
In this case I modified the <validationRules:UserAccountValidationRule/> to take a parameter that passes a single boolean IsEnabled property:
<Binding.ValidationRules>
<validationRulesParameterPass:UserAccountValidationRule>
<validationRulesParameterPass:UserAccountValidationRule.AccountValidationParameter>
<validationRulesParameterPass:AccountValidationParameter IsEnabled="{Binding IsButtonEnabled, Source={StaticResource MainWindowViewModel}}" />
</validationRulesParameterPass:UserAccountValidationRule.AccountValidationParameter>
</validationRulesParameterPass:UserAccountValidationRule>
</Binding.ValidationRules>
I also binded the IsButtonEnabled dependency property property to the ToolBar Button item's IsEnabled property.
Then, inside the UserAccountValidationRule, based on whether the value is valid or not, the IsButtonEnabled property will be set to true or false. As this DependencyProperty is connected to the Toolbar Button's, if the validation sets this true, the button's IsEnabled property will also be set to true, and vice versa.
public class UserAccountValidationRule : ValidationRule
{
private AccountValidationParameter _accountValidationParameter;
public UserAccountValidationRule()
{
}
public AccountValidationParameter AccountValidationParameter
{
get { return _accountValidationParameter; }
set { _accountValidationParameter = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if ( !string.IsNullOrEmpty(value as string) && value as string == "good")
{
AccountValidationParameter.IsEnabled = true;
return new ValidationResult(true, null);
}
else
{
AccountValidationParameter.IsEnabled = false;
return new ValidationResult(false, "bad string.");
}
}
}
Problem solved.
I have ToggleButtons and a DataGrid, each row in DataGridColumn has a ColGroup AttachedProperty set to the name of the column group name.
Attached property:
public class DataGridColumnsGroupProperty {
public static readonly DependencyProperty ColGroupProperty =
DependencyProperty.RegisterAttached("ColGroup", typeof(object), typeof(DataGridColumnsGroupProperty), new FrameworkPropertyMetadata(null));
public static void SetColGroup(DependencyObject element, string value) {
element.SetValue(ColGroupProperty, value);
}
public static string GetColGroup(DependencyObject element) {
return (string)element.GetValue(ColGroupProperty);
}
}
The ToggleButtons has two jobs, on Check/UnCheck show/collapse all columns with the same group name.
and it has a ContextMenu which shows only the DataGridColumns with the same group name.
I've managed to bind all DataGridColumns to the ToggleButton, but couldn't find a way to Collapse the DataGridColumns with different group names.
How to fill context menu with only the columns with the givin group name inside the Style Trigger?
And how to hid all columns that has the group name when un-check toggle button?
XAML:
<ToggleButton.ContextMenu>
<ContextMenu x:Name="ContextMenu" ItemsSource="{Binding Columns, ElementName=ElementDataGrid, Converter={StaticResource TestConverter}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="HeaderTemplate" Value="{Binding HeaderTemplate}"/>
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="StaysOpenOnClick" Value="True" />
<Setter Property="AutomationProperties.Name" Value="{Binding Header}"/>
<Setter Property="IsCheckable" Value="True" />
<Setter Property="IsChecked" Value="{Binding Visibility, Mode=TwoWay, Converter={StaticResource VisibilityToBooleanConverter}}" />
<Style.Triggers>
<Trigger Property="attachedProperties:DataGridColumnsGroupProperty.ColGroup" Value="FirstGroup">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</ToggleButton.ContextMenu>
DataGridColumns:
<DataGridTextColumn x:Name="StoryCol" attachedProperties:DataGridColumnsGroupProperty.ColGroup="FirstGroup" Header="{x:Static p:Resources.Story}" IsReadOnly="True" Binding="{Binding Story}" Visibility="Visible" />
<DataGridTextColumn x:Name="CadIdCol" attachedProperties:DataGridColumnsGroupProperty.ColGroup="SecondGroup" Header="{x:Static p:Resources.CadId}" IsReadOnly="False" Binding="{Binding CadId}" Visibility="Visible" />
Using a DataTrigger should work as far as the binding to the attached property is concerned:
<DataTrigger Binding="{Binding Path=(attachedProperties:DataGridColumnsGroupProperty.ColGroup)}" Value="FirstGroup">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<ListBox ItemsSource="{Binding Customers}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="1" x:Name="BackgroundGrid">
<TextBlock Text="{Binding CustomerName}" />
<TextBlock Text="{Binding CustomerId}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding CustomerId}" Value="9-3453"><Setter TargetName="BackgroundGrid" Property="Background" Value="Red"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In the above code, I need to provide DataTrigger's value based on property value instead of hard code the value.
Thanks in advance,
Dinesh
Supposed your DataTrigger's value based on property CustomerNumber. Then you add another property for binding, like this:
public bool CustomerIdMatchs
{
get{
return CustomerNumber==CustomerId;
}
}
Then you bind CustomerIdMatchs instead of CustomerId, like this:
<DataTrigger Binding="{Binding CustomerIdMatchs}" Value="true">
<Setter TargetName="BackgroundGrid" Property="Background" Value="Red"/>
</DataTrigger>
I know there are already a lot of examples for doing similar things, and I have read these but cannot figure out why my implementation is not working.
I am trying to disable ComboBoxB when ComboBoxA is set to value X. With this implementation, ComboBoxB is always enabled regardless of the value selected in ComboBoxA.
ComboBoxA:
<ComboBox Name="ComboBoxA">
<ComboBoxItem Name="X">X</ComboBoxItem>
<ComboBoxItem Name="Y">Y</ComboBoxItem>
</ComboBox>
ComboBoxB:
<ComboBox Name="ComboBoxB">
<ComboBoxItem Name="Something">Something</ComboBoxItem>
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem,ElementName=ComboBoxA}" Value="X">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
You never performed a databinding to ComboboxA.
Your referencing elementname "ComboboxA" and NOT "ComboboxB"
You have:
<DataTrigger Binding="{Binding Path=SelectedItem,ElementName=ComboBoxB}" Value="X">
This should be:
<DataTrigger Binding="{Binding Path=SelectedItem,ElementName=ComboBoxA}" Value="X">
.
<ComboBox Name="ComboBoxB">
<ComboBoxItem Name="Something">Something</ComboBoxItem>
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem,ElementName=ComboBoxA}" Value="X">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
You could use converters to do this.
public class SelectedItemToDisable : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
var val = (value as ComboBoxItem).Content;
if (val.ToString() == "X") //may be its not string, some object type
return false;
}
return true;
}
define the converter in resources part in Xaml.
<local:SelectedItemToDisable x:Key="DisableConverter"/>
<ComboBox Name="ComboBoxA">
<ComboBoxItem Name="X">X</ComboBoxItem>
<ComboBoxItem Name="Y">Y</ComboBoxItem>
</ComboBox>
<ComboBox Name="ComboBoxB" IsEnabled="{Binding Path=SelectedItem, ElementName=ComboBoxA, Converter={StaticResource DisableConverter}}">
<ComboBoxItem Name="Something">Something</ComboBoxItem>
</ComboBox>
Hope this helps.
let's say we have simple data class:
public class Ex {
public string Prop1 {...} // notify property
public string Prop2 {...} // notify property
}
and an ObservableCollection of objects of this class. I want to have this collection displayed in a ListView with seperated DataTemplated which is distinguished by Ex.Prop2 (if it is null or empty then template01 is used, otherwise template02). This property can be changed in runtime so simple "trick" with ListView.ItemTemplateSelector does not work :(
How to achieve this functionality? Is it possible to achieve it any other way than listening to NotifyPropertyChanged on each object of the collection and than changing manually the template?
Thanks for your help.
Below piece of code which I already have:
<ListView x:Name="lstTerms"
ItemsSource="{Binding Game.Words}"
HorizontalContentAlignment="Stretch"
Grid.IsSharedSizeScope="True">
<ListView.ItemContainerStyle>
<Style>
<Setter Property="Control.Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<!-- checks if element is null or its Prop2 is null or empty. If so, uses NullTemplate -->
<ListView.ItemTemplateSelector>
<local:MySelectTemplate
NormalTemplate="{StaticResource NormalItemTemplate}"
NullTemplate="{StaticResource NullItemTemplate}" />
</ListView.ItemTemplateSelector>
</ListView>
Instead of using a TemplateSelector, you can have a single DataTemplate containing two Grids, which switch visibility dependent on the property values.
Here is an example:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Background="LightBlue" Name="normalGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Prop1}" Value="{x:Null}">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop1}"></TextBlock>
</Grid>
<Grid Background="Green" Name="nullGrid">
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=normalGrid, Path=Visibility}" Value="Visible">
<Setter Property="Grid.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding Prop2}"></TextBlock>
</Grid>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Obviously you could replace the TextBlock elements with UserControls representing your two DataTemplates.
If you want, you can also remove the need for the bulky Styles by binding Grid.Visibility to a property (named, for example, IsVisible) on your ViewModel and using a VisibilityConverter.
I usually just use a ContentControl which changes its ContentTemplate based on a DataTrigger. DataTriggers respond to the value getting changed, while DataTemplateSelectors do not
<Style x:Key="SomeStyleKey" TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource DefaultTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Prop2}" Value="{x:Null}">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Prop2}" Value="">
<Setter Property="ContentTemplate" Value="{StaticResource NullTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
...
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Style="{StaticResource SomeStyleKey}" />
</DataTemplate>
</ListView.ItemTemplate>
You could also use a Converter that returns String.IsNullOrEmpty(value) if you wanted a single DataTrigger