WPF enable combobox based on another combobox - wpf

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.

Related

Use DataTrigger in DataGridTemplateColumn.CellStyle

I have a datagrid as follows,
<DataGrid Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding SelectedSet.Rows}" IsSynchronizedWithCurrentItem="True" AutoGenerateColumns="False" CanUserAddRows="False" Style="{StaticResource DataGridStyle2}" HeadersVisibility="Column" SelectedItem="{Binding SelectedItem}" EnableRowVirtualization="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="30*" Header="{StaticResource RangeColumnHeader}" HeaderStyle="{StaticResource HeaderStyle2}" SortMemberPath="StartValue">
<DataGridTemplateColumn.CellStyle>
<DataTrigger Binding="{Binding SelectedSet.IsDefault}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedSet.IsDefault}" Value="False">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DatGrid>
I Want use " SelectedSet's " IsDefault property to set cell's IsEnabled property.
I tried above code, which is not working.
Classes are defined as follows,
public class UCSetModel : ViewModelBase
{
private Set _SelectedSet;
public Set SelectedSet
{
get
{
return _SelectedSet;
}
set
{
_SelectedSet = value;
RaisePropertyChanged("SelectedSet");
}
}
}
public class Set
{
private ObservableCollection<Markers> _rows;
public ObservableCollection<Markers> Rows
{
get
{
return _rows;
}
set
{
_rows = value;
RaisePropertyChanged("Rows");
}
}
private bool _isDefault;
public bool IsDefault
{
get
{
return _isDefault;
}
set
{
_isDefault = value;
RaisePropertyChanged("IsDefault");
}
}
}
I want to bind to a property of the same 'SelectedSet' that the rows come from.
The markup you have posted won't even compile. This does:
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDefault}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTemplateColumn.CellStyle>
IsDefault is supposed to be a property of a Row object, i.e. you should remove "Set." from the binding path assuming that a row doesn't have a Set property.
If you want to bind to a property of the same SelectedSet that the rows come from, the binding should be defined like this:
<DataTrigger Binding="{Binding DataContext.SelectedSet.IsDefault, RelativeSource={RelativeSource AncestorType=DataGrid}}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
This worked for me,
<DataTrigger Binding="{Binding Path=DataContext.SelectedSet.IsDefault,ElementName=SetWindow}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>

WPF - DataGrid column binding to attached property not working on context menu

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>

How to apply style to Listbox Item based on Property value in WPF

<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>

multiple binding to IsEnable

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

wpf - validation - how to show tooltips and disable "run" button

Hi
I need to validate some of textboxes in my application. I decied to use validation rule
"DataErrorValidationRule". That's why in my class I implemented IDataErrorInfo interface and wrote aproperiate functions. In my xaml code I added bindings and validation rules to textboxes
<TextBox x:Name="txtName" Grid.Column="3" Grid.Row="1" TextAlignment="Center" >
<TextBox.Text>
<Binding Path="Name" >
<Binding.ValidationRules>
<DataErrorValidationRule></DataErrorValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Validation of this textbox is OK - I mean red frame appears on textbox if data is wrong. However what I need to do is to show tooltip on that textbox, but what is more important I have to disable button "Run" if any textboxes have wrong data. What is the best way to do taht ??
EDIT
First problem was solved, but I have an another. I need to use MultiBindings to validate my Button. So I did sth like that
<Button x:Name="btnArrange" Grid.Column="0" Content="Rozmieść" Click="btnArrange_Click" >
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource BindingConverter}">
<Binding ElementName="txtName" Path="Validation.HasError" />
<Binding ElementName="txtSurname" Path="Validation.HasError"/>
<Binding ElementName="txtAddress" Path="Validation.HasError"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
My Converter looks like that
public class Converters : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(values !=null && values.Length > 0)
{
if (values.Cast<type>().Count(val => val) > 0)
return false;
return true;
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
#endregion
}
However I get invalidCastException in this converter. What is a proper cast in that case? I thoght as if HasError is a bool type so I should cast to bool.
To show the error message in a tool tip put this into your Application.Resources:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
( Example from http://msdn.microsoft.com/en-us/library/system.windows.controls.validation.errortemplate.aspx )
To enable/disable a button you could use something along the line of
<Button x:Name="btnOK" Content="OK" IsDefault="True" Click="btnOK_Click">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txt1, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=txt2, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
or you could implement ICommand and use command binding.
EDIT
Here is a fully working example. It displays a window with two TextBoxes. The Button is enabled if and only if both TextBoxes are non-empty. Create a project called ValidationDemo and put the following files in it:
MainWindow.xaml:
<Window x:Class="ValidationDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="146" Width="223">
<Window.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Label Content="A" Height="28" HorizontalAlignment="Left" Margin="46,7,0,0" Name="label1" VerticalAlignment="Top" />
<TextBox Name="txtA" Text="{Binding Path=TextA, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Height="23" HorizontalAlignment="Left" Margin="69,12,0,0" VerticalAlignment="Top" Width="120" />
<Label Content="B" Height="28" HorizontalAlignment="Left" Margin="46,39,0,0" Name="label2" VerticalAlignment="Top" />
<TextBox Name="txtB" Text="{Binding Path=TextB, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Height="23" HorizontalAlignment="Left" Margin="69,41,0,0" VerticalAlignment="Top" Width="120" />
<Button Name="btnOk" Content="OK" Height="23" HorizontalAlignment="Left" Margin="114,70,0,0" VerticalAlignment="Top" Width="75" Click="btnOk_Click">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=txtA, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=txtB, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace ValidationDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Model model = new Model();
public MainWindow()
{
InitializeComponent();
this.DataContext = this.model;
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
}
}
Model.cs:
using System;
using System.ComponentModel;
namespace ValidationDemo
{
public class Model : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
private string textA = string.Empty;
public string TextA
{
get
{
return this.textA;
}
set
{
if (this.textA != value)
{
this.textA = value;
this.OnPropertyChanged("TextA");
}
}
}
private string textB = string.Empty;
public string TextB
{
get
{
return this.textB;
}
set
{
if (this.textB != value)
{
this.textB = value;
this.OnPropertyChanged("TextB");
}
}
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = string.Empty;
switch (columnName)
{
case "TextA":
if (string.IsNullOrEmpty(this.textA))
{
result = "'A' must not be empty";
}
break;
case "TextB":
if (string.IsNullOrEmpty(this.textA))
{
result = "'B' must not be empty";
}
break;
}
return result;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
<Window.Resources>
<Style x:Key="ElementInError" TargetType="{x:Type FrameworkElement}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- ... -->
<TextBox x:Name="txtName" Style="{StaticResource ElementInError}">
<!-- ... -->
</TextBox>
<!-- ... -->
<Button x:Name="OkButton" Content="Ok" Margin="5" Click="OkButton_Click">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=txtName,Path=(Validation.HasError)}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
If you create a validation summary you can bind the IsEnabled property of your "Run" button to it's HasErrors property.
You'll need to use an intermediate property or converter as you want the IsEnabled to be true when HasErrors is false (and vice versa).

Resources