I have a customcontrol which render a textbox. I've also a style that set the color of the background based on some conditions as follows:
<Style x:Key="ArtParamStyle" TargetType="av:DC_Base">
<Setter Property="Background" Value="{StaticResource EditableAreaBrush}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Info.Upd.IsAutoCalc}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Forced}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
Initially as the value of my textbox is autocalculated, the background is correctly red. If I also set the Forced as true (by ticking a chebckbox) I've a weird result, the border of textbox is lightgreen but background not.
It seems to be a strange color, a combination of red and lightgreen. As test, if I set the "IsAutoCalc" color as Transparent, the trigger works correctly. How can I solve this?
your code seems to be correct. But I provide you my sample:
XAML:
<Window.Resources>
<Style x:Key="ArtParamStyle" TargetType="TextBox">
<Setter Property="Background" Value="Yellow" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Bool1}" Value="True"/>
<Condition Binding="{Binding Bool2}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="Red" />
<Setter Property="BorderBrush" Value="Red" />
</MultiDataTrigger>
<DataTrigger Binding="{Binding Bool2}" Value="True">
<Setter Property="Background" Value="LightGreen" />
<Setter Property="BorderBrush" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Style="{StaticResource ArtParamStyle}" Height="50" Margin="4"/>
<CheckBox IsChecked="{Binding Bool1}"/>
<CheckBox IsChecked="{Binding Bool2}"/>
</StackPanel>
</Grid>
In this case I used Multidatatrigger to set red background when Bool2(your forced) is not checked.
MainWindow.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChaged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private bool bool1;
public bool Bool1
{
get { return bool1; }
set { bool1 = value; RaisePropertyChaged("Bool1"); }
}
private bool bool2;
public bool Bool2
{
get { return bool2; }
set { bool2 = value; RaisePropertyChaged("Bool2"); }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
}
Probably your problem is related to your custom control.
Related
I have a data grid and one main checkbox. When I check changed in the main checkbox, this will affect all checkbox that inside data grid; I just do it programmatically.
My so-called ViewModel:
foreach (TimeSheetModel value in TimeSheetList.Intersect(selectedlist))
{
if (!chkmain.IsChecked.GetValueOrDefault())
{
value.IsApproved = false;
}
else
{
value.IsApproved = true;
}
}
This is my TimeSheetModel:
public class TimeSheetModel:BaseModel, ICloneable
{
bool _IsApproved;
public bool IsApproved
{
get
{
return _IsApproved;
}
set
{
if (_IsApproved != value)
{
_IsApproved = value;
RaisePropertyChange("IsApproved");
}
}
}
}
This is my BaseModel:
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChange(string prop)
{
try
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
IsUpdated=true
}
}
catch (Exception ex)
{
throw ex;
}
}
}
This is my xaml:
<DataGridTemplateColumn Visibility="{Binding IsShowApprove,Source={x:Static c:TMSHelper.Current}}">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical"
HorizontalAlignment="Right"
Margin="-8">
<TextBlock Text="Approve"
Height="20"
Margin="5,3,0,0" />
<CheckBox Margin="27,0,0,0"
x:Name="chkMainApproved"
Checked="chkApproved_Checked"
Unchecked="chkApproved_Checked">
</CheckBox>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox BorderThickness="1"
Margin="27,0,0,0"
Background="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}},Path=Background}"
IsChecked="{Binding IsApproved,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
KeyDown="DatePicker_KeyDown"
>
<CheckBox.Style>
<Style TargetType="CheckBox" BasedOn="{StaticResource MetroCheckBox}">
<Setter Property="IsEnabled"
Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsLock}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsLock}"
Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled"
Value="False" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsNew}"
Value="True" />
<Condition Binding="{Binding CanAdd}"
Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled"
Value="False" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsNew}"
Value="False" />
<Condition Binding="{Binding CanEdit}"
Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled"
Value="False" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
According to my understanding, if I change the value.IsApproved=true (inside ViewModel) this will raise the IsApproved property inside TimeSheetModel and go to RaisePropertyChanged method and go to PropertyChanged and IsUpdated property will be true.
My problem is My propertyChanged is sometimes null, sometimes has value. Even if IsApproved value was changed.
This is not happening in all checkbox.
Hope you will understand my question and pls let me know anything I can provide.
I've extended Window to add some functionality, and part of this is the ability to specify a specific window size or allow it to size to the content. The codebehind looks like this, currently un-MVVMified.
public partial class DialogWindow : Window
{
public bool HasSize { get; set; }
public Size Size { get; set; }
}
The XAML then looks like this:
<Window ... Name="DialogWindowElement">
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="True">
<Setter Property="Width" Value="{Binding Size.Width, ElementName=DialogWindowElement}" />
<Setter Property="Height" Value="{Binding Size.Height, ElementName=DialogWindowElement}" />
</DataTrigger>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="False">
<Setter Property="SizeToContent" Value="WidthAndHeight" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
<ContentControl ...>
<!-- Content control using DataTemplates to determine content -->
</ContentControl>
</Window>
Resizing to content seems to work okay, but the specified width and height aren't applied. Any large content expands to all the size it needs instead of being constrained and then resizable later.
Snoop and other such tools imply the trigger is fired, but the setters don't seem to be having any effect.
Am I missing something here?
Edit: Added content control to the window to provide some more context
This works for me:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
public bool HasSize { get; set; } = true;
public Size Size { get; set; } = new Size(800, 800);
}
XAML:
<Window x:Class="WpfApplication1.Window21"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Window1"
Name="DialogWindowElement">
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="True">
<Setter Property="Width" Value="{Binding Size.Width, ElementName=DialogWindowElement}" />
<Setter Property="Height" Value="{Binding Size.Height, ElementName=DialogWindowElement}" />
</DataTrigger>
<DataTrigger Binding="{Binding HasSize, ElementName=DialogWindowElement}" Value="False">
<Setter Property="SizeToContent" Value="WidthAndHeight" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
<TextBlock Text="Test..." FontSize="40" FontWeight="Bold" />
</Window>
Make sure that you don't set the Width and Height properties of the window in your XAML because local values take take precedence over values set by style setters.
How can I bind to whether any/none RadioButton of a certain group is checked?
I have, say, 4 RadioButtons. Initially none of them is checked, so the "next" button is disabled. User must make a selection in order to enable the "next" button and be able to proceed.
First solution is not efficient since every new RadioButton entails to add extra DataTrigger:
<StackPanel>
<RadioButton Name="x1"/>
<RadioButton Name="x2"/>
<RadioButton Name="x3"/>
<RadioButton Name="x4"/>
<Button Content="Click">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=x1, Path=IsChecked}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=x2, Path=IsChecked}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=x3, Path=IsChecked}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=x4, Path=IsChecked}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
Latter in turn is based on MVVM. You create property IsChecked in your ViewModel and whenever any RadioButton is checked it sets IsChecked value to True and your Button based on this value sets its IsEnabled property.
XAML:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="RadioButton">
<Setter Property="Command" Value="{Binding RadioButtonCheckedCommand}"/>
</Style>
</StackPanel.Resources>
<RadioButton Name="x1"/>
<RadioButton Name="x2"/>
<RadioButton Name="x3"/>
<RadioButton Name="x4"/>
<Button Content="Click" IsEnabled="{Binding IsChecked}"/>
</StackPanel>
ViewModel:
class MainViewModel : INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged();
}
}
private RelayCommand _radioButtonCheckedCommand;
public RelayCommand RadioButtonCheckedCommand
{
get
{
return _radioButtonCheckedCommand ??
(_radioButtonCheckedCommand = new RelayCommand(() => IsChecked = true));
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
I came across a situation where I want to avoid using converter in multibinding, below is the xaml source snippet from my current code. Below code works perfectly fine, but IS IT POSSIBLE to avoid converter first place ??
ViewModel:
public MainViewModel()
{
Cars = new List<string>() { "Audi", "BMW", "Ferrari", "Ford" };
Models = new List<string>() { "Model 1", "Model 2" };
IsOptionEnable = false;
}
public bool IsOptionEnable { get; private set; }
public List<string> Models { get; private set; }
public List<string> Cars { get; private set; }
Main window xaml:
<Grid>
<ComboBox HorizontalAlignment="Left" VerticalAlignment="Top" Width="120" Margin="87.2,44.8,0,0"
ItemsSource="{Binding Cars}"
SelectedItem="{Binding SelectedItm}"
Style="{StaticResource ModelsComboBox}">
</ComboBox>
</Grid>
Resource dictionary:
<Style x:Key="ModelsComboBox" TargetType="ComboBox">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled">
<Setter.Value>
<MultiBinding Converter="{StaticResource ModelToBoolConverter}">
<Binding/>
<Binding Path="DataContext.IsOptionEnable" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
Multivalue converter:
internal sealed class ModelToBoolConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool enable = true;
if ((values[0] != null && values[0] != DependencyProperty.UnsetValue) &&
(values[1] != null && values[1] != DependencyProperty.UnsetValue))
{
var comboboxItemText = values[0] as string;
if ((comboboxItemText == "Ferrari") && (bool)values[1] == false)
{
enable = false;
}
}
return enable;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
You can use MultiDataTrigger in this case.
<Style TargetType="ComboBox">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding}" Value="Ferrai"/>
<Condition Binding="{Binding Path=DataContext.IsOptionEnable, RelativeSource={RelativeSource AncestorType=ComboBox}}" Value="False" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="IsEnabled" Value="False" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
You can use MultiDataTrigger to achieve the same.
Resource dictionary:
<Style x:Key="ModelsComboBox" TargetType="ComboBox">
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding}" Value="Ferrari"/>
<Condition Binding="{Binding Path=DataContext.IsOptionEnable,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}"
Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="False"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>
<ItemsControl ItemsSource="{Binding Tariffs}" Margin="6">
<ItemsControl.ItemTemplate>
<DataTemplate>
<custControls:RoundButton Name="TariffButton" Margin="3"
Content="{Binding TariffName}" Style="{DynamicResource TariffButton}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="PickUpTariff">
<cal:Parameter Value="{Binding Path=Content,
RelativeSource={RelativeSource AncestorType={x:Type custControls:RoundButton}}}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</custControls:RoundButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Relative source binding works just fine. Content="{Binding TariffName}" works just fint to.
But here is what contained in the Style TariffButton.
<Style x:Key="TariffButton" TargetType="customControls:RoundButton" BasedOn="{StaticResource {x:Type Button}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChosen, Mode=OneWay}" Value="True">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsChosen, Mode=OneWay}" Value="False">
<Setter Property="Background" Value="Blue"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
And it does not work! More accurately, it works just once. So the data trigger is invoked only once.
Here is the ViewModel part:
public class DocumentChoiceViewModel:PropertyChangedBase {
public ObservableCollection<Tariff> Tariffs { get; private set; }
public DocumentChoiceViewModel() {
Tariffs = new ObservableCollection<Tariff> {
new Tariff {IsChosen = true, TariffName = "ПАССАЖИРСКИЙ"},
new Tariff {IsChosen = false, TariffName = "ЭКСПРЕСС"},
new Tariff {IsChosen = false, TariffName = "КОМФОРТ"},
new Tariff {IsChosen = false, TariffName = "ДОП.СК.М-НОГИНСК"}
};
}
public void PickUpTariff(string tariffName) {
if (!IsTariffPlanExists(tariffName))
throw new InvalidOperationException(
"Unexpectable state. User should not be able to choose tariff plan which is not present in Tariffs list");
}
}
public class Tariff:PropertyChangedBase {
private bool isChosen;
public bool IsChosen {
get { return isChosen; }
set {
isChosen = value;
NotifyOfPropertyChange(()=>IsChosen);
}
}
public string TariffName { get; set; }
}
So first if you have some problems debugging bindings - use a converter as an additional layer where you will see all changes.
And second - set binding mode to be TwoWay.
Style Triggers only work with Dependency Properties defined in the TargetType.
Why don't you just make an IsChosenToBackgroundConverter and write this
<RoundButton Background="{Binding IsChosen, Converter={StaticResource IsChosenToBackgroundConverter}"/>
I don't know if this will fix your problem, but you don't need to add a Trigger for both True and False conditions:
<Style x:Key="TariffButton" TargetType="customControls:RoundButton"
BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Background" Value="Blue" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChosen}" Value="True">
<Setter Property="Background" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>