I have a checkbox which looks something like this (have removed many things to make it short) -
<CheckBox IsChecked="{Binding functionABC, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding Path=XYZ}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding Path=XYZ}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
Now, I have to map the window title of app to 2 different values depending on checked or unchecked. I could have done it normally, but there is a trigger set already for both states, and I don't know how to work around it.
Use a Style with a DataTrigger
<!-- !!! remove the Title property from the Window declaration !!! -->
<Window
...>
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding functionABC}" Value="True">
<Setter Property="Title" Value="True Title" />
</DataTrigger>
<DataTrigger Binding="{Binding functionABC}" Value="False">
<Setter Property="Title" Value="False Title" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
...
</Window>
Update
As thatguy suggested this style can be simplified by
<!-- !!! remove the Title property from the Window declaration !!! -->
<Window
...>
<Window.Style>
<Style TargetType="Window">
<Setter Property="Title" Value="False Title" />
<Style.Triggers>
<DataTrigger Binding="{Binding functionABC}" Value="True">
<Setter Property="Title" Value="True Title" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
...
</Window>
sample project on github
You can use an special IValueConverter
public class BooleanToCustomConverter : MarkupExtension, IValueConverter
{
public string? TrueValue { get; set; }
public string? FalseValue { get; set; }
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool b)
return b ? TrueValue : FalseValue;
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
and use that within your Binding
<Window
...
Title="{Binding functionABC, Mode=OneWay, Converter={local:BooleanToCustomConverter TrueValue='True Value', FalseValue='False Value'}}"
...>
...
</Window>
sample project on github
Related
I'm trying to style a rectangle inside of a listview item, based on a data-field of the listview item object.
To return a boolean I'm converting the integer from daydata.workload to a boolean uasing a IValueConverter.
I'm getting no exception, the rectangle is just not affected by the DataTrigger. The other style rules are working fine.
<Window.Resources>
<cv:numConverter x:Key="capacityConverter" />
<Window.Resources>
-
<ListView Name="weekView" ItemsSource="{Binding dayList}" ItemTemplate="{StaticResource DefaultTemplate}" >
<ListView.Resources>
<Style TargetType="Rectangle" x:Key="capacityBG">
<Setter Property="Stroke" Value="#FFE2E2E2" />
<Setter Property="Width" Value="180" />
<Setter Property="Height" Value="10" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=dayList.workload, Converter={StaticResource capacityConverter}, ConverterParameter=12}">
<DataTrigger.Value>true</DataTrigger.Value>
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.Resources>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
-
<Rectangle Style="{DynamicResource capacityBG}" VerticalAlignment="Top" Grid.Row="0" />
-
public class numConverter : IValueConverter
{
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((int)value) > val;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public int val { get; set; }
}
-
public class dayData
{
public DateTime date { get; set; }
public int workload { get; set; }
public List<job> jobs { get; set; }
}
The problem here is that values entered for the converter parameter and the data trigger value are treated as string. You need to specify the type for each one of these values like shown below:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<Style.Triggers>
<DataTrigger>
<DataTrigger.Binding>
<Binding Path="WorkLoad" Converter="{StaticResource capacityConverter}">
<Binding.ConverterParameter>
<sys:Int32>12</sys:Int32>
</Binding.ConverterParameter>
</Binding>
</DataTrigger.Binding>
<DataTrigger.Value>
<sys:Boolean>true</sys:Boolean>
</DataTrigger.Value>
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
</Style.Triggers>
Then you can cast converter parameter to an int to make the comparison.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value > (int)parameter;
}
I have DataGridComboBoxColumn that is supposed to show integers or text "Default". When I add row the combobox gets correct value from viewmodel's bound property, but when I change value in user interface, the property's set is not called. I tried both SelectedValueBinding and SelectedItemBinding. Converter's ConvertBack is never called. I don't event know should it be called.
Things that work:
DataGrid SelectedItem binding
Text column binding both ways (omitted here for shortness)
Here is my code:
XAML:
<DataGrid Name="SelectionSetsGrid" CanUserAddRows="False" CanUserResizeColumns="True" CanUserSortColumns="True"
ItemsSource="{Binding SelectionSets}" AutoGenerateColumns="False"
SelectedItem="{Binding SelectedSelectionSet}">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Width" SelectedValueBinding="{Binding LineWidthIndex}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox" BasedOn="{StaticResource Theme.ComboBox.Style}">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.LineWidths}"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Converter={StaticResource IntToIntTextOrDefaultConverter}}" VerticalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox" BasedOn="{StaticResource Theme.ComboBox.Style}">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.LineWidths}"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Converter={StaticResource IntToIntTextOrDefaultConverter}}" VerticalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel (ViewModel implements INotifyPropertyChanged and SetValue raises PropertyChanged):
public class SelectedObjectsViewModel : ViewModel
{
private int[] _lineWidths = { -1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
public ObservableCollection<int> LineWidths { get; private set; };
private ObservableCollection<SelectionSetViewModel> _selectionSets;
public ObservableCollection<SelectionSetViewModel> SelectionSets
{
get { return _selectionSets; }
set { this.SetValue(ref _selectionSets, value); }
}
private SelectionSetViewModel _selectedSelectionSet;
public SelectionSetViewModel SelectedSelectionSet
{
get { return this._selectedSelectionSet; }
set { this.SetValue(ref _selectedSelectionSet, value); }
}
}
ViewModel for DataGrid row (ViewModel implements INotifyPropertyChanged and SetValue raises PropertyChanged):
public class SelectionSetViewModel : ViewModel
{
public SelectionSetViewModel()
{
LineWidthIndex = -1;
}
private int _lineWidthIndex;
public int LineWidthIndex
{
get { return _lineWidthIndex; }
set { SetValue(ref _lineWidthIndex, value); }
}
Converter:
public class IntToIntTextOrDefaultConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if ((int)value == -1)
return Fusion.App.Current.Resources["StrDefault"].ToString();
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}
It seems that on some occasions like after editing the text column and pressing enter or adding new row the property WAS actually updated (set called) after changing combobox value. So I just added UpdateSourceTrigger=PropertyChanged to binding and the update to source property happened immediately (and not after some random operation). Note that changing focus from ComboBox was not enough to update source property so I thought it was never updated.
<DataGrid Name="SelectionSetsGrid" CanUserAddRows="False" CanUserResizeColumns="True" CanUserSortColumns="True"
ItemsSource="{Binding SelectionSets}" AutoGenerateColumns="False"
SelectedItem="{Binding SelectedSelectionSet}">
<DataGridComboBoxColumn Header="{StaticResource XpStrTopologyWidth}" SelectedItemBinding="{Binding LineWidthIndex, UpdateSourceTrigger=PropertyChanged}">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox" BasedOn="{StaticResource Theme.ComboBox.Style}">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.LineWidths}"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Converter={StaticResource IntToIntTextOrDefaultConverter}}" VerticalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox" BasedOn="{StaticResource Theme.ComboBox.Style}">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.LineWidths}"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Converter={StaticResource IntToIntTextOrDefaultConverter}}" VerticalAlignment="Center"/>
</WrapPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
I have a way of doing this now using code-behind and changing visibility of 'panels' , but I wonder if this can be done in a straight xaml way?
You should be able to bind this using ElementName, along with an IValueConverter that converts true/false to Visibility:
<Grid>
<Grid.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</Grid.Resources>
<UserControl Visibility="{Binding ElementName=toggle,
Path=IsChecked,
Converter={StaticResource BoolToVisibilityConverter}}"
/>
<ToggleButton x:Name="toggle" />
</Grid>
And the converter:
public class BoolToVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var isChecked = (bool)value;
return isChecked ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Here you can do it like below.. you can have both your usercontrol and togglebutton as the content of a parent Contentcontrol and use DataTemplate triggers to set visibility of user control depending on checked status of ToggleButton
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<local:myusercontrol x:Name="control"/>
<ToggleButton Content="click" x:Name="toggleBtn"/>
</StackPanel>
<DataTemplate.Triggers>
<Trigger Property="IsChecked" Value="false" SourceName="toggleBtn">
<Setter Property="Visibility" Value="Visible" TargetName="control"/>
</Trigger>
<Trigger Property="IsChecked" Value="true" SourceName="toggleBtn">
<Setter Property="Visibility" Value="Collapsed" TargetName="control"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
I've spend a lot of time trying to change row text color on CheckBox click event.
So we need set color to gray if checkbox is checked and return it to normal color if checkbox is not checked.
Please help to get this result (DataGrid bound to xml file).
Upd. Some code:
XAML for row color changing based on column Checked (Checkbox):
<Window.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Foreground" Value="{Binding Checked, Converter={x:Static local:MainWindow.rowCheckedConverter}}" />
</Style>
</Window.Resources>
...
<Grid.DataContext>
<XmlDataProvider x:Name="userTasksProvider" XPath="UserTasks" />
</Grid.DataContext>
...
<DataGrid Name="dgUserTasks" Grid.Column="1" Margin="1,0,0,0"
AutoGenerateColumns="False" HeadersVisibility="None"
ItemsSource="{Binding XPath=Task}">
<DataGrid.Columns>
<DataGridCheckBoxColumn x:Name="cbUserTasksColumn" Width="20"
Binding="{Binding Checked,
Mode=TwoWay}" Header="">
</DataGridCheckBoxColumn>
<DataGridTextColumn
x:Name="Info" Width="*"
Binding="{Binding Info,
Mode=TwoWay}"
Header="" >
</DataGridTextColumn>
...
C# WPF:
public partial class MainWindow : Window
{
public static RowCheckedConverter rowCheckedConverter = new RowCheckedConverter();
...
}
...
public class RowCheckedConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if ((bool)value) {
return new SolidColorBrush(Colors.Gray);
}
else {
return new SolidColorBrush(Colors.Black);
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new Exception("The method or operation is not implemented.");
}
}
Your question is quite vague but I'll try to help.
Consider this example:
<DataGrid ItemsSource="{Binding DataSet}" AutoGenerateColumns="False">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="False">
<Setter Property="Foreground" Value="Gray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}">
</DataGridTextColumn>
<DataGridCheckBoxColumn Binding="{Binding IsActive, Mode=TwoWay}">
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
and code:
public partial class MainWindow : Window
{
public ObservableCollection<Data> DataSet { get; set; }
public MainWindow()
{
DataSet = new ObservableCollection<Data>();
DataSet.Add(new Data { Name = "First" });
DataSet.Add(new Data { Name = "Second" });
DataSet.Add(new Data { Name = "Third" });
InitializeComponent();
DataContext = this;
}
}
public class Data
{
public string Name { get; set; }
public bool IsActive { get; set; }
}
In this snippet I bound checkbox column to property in Data instance and added style trigger when this property is false. Is this what you where looking for?
Edit after question update
I cannot see in code that your provided where your style is assigned to DataGrid:
<DataGrid RowStyle="{StaticResource myStyle}" ...>
and your style does not declare x:Key property.
If you add this it will work but the static fied/property just smells bad. Consider changing to:
<Style TargetType="DataGridRow" x:Key="myStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Checked}" Value="False">
<Setter Property="Foreground" Value="Gray"/>
</DataTrigger>
</Style.Triggers>
</Style>
If you are determined to use converter consider changning to:
<Style TargetType="{x:Type DataGridRow}" x:Key="myStyle">
<Setter Property="Foreground" >
<Setter.Value>
<Binding Path="Checked">
<Binding.Converter>
<local:RowCheckedConverter />
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
</Style>
This will also eliminate this field.
Use a converter like shown here: http://timheuer.com/blog/archive/2008/07/30/format-data-in-silverlight-databinding-valueconverter.aspx
I set the datacontext dynamically in code. I would like a button on screen to be enabled/disabled depending if DataContext == null or not. I can do it in code when I assign the DataContext but it would be better if I can bind like that :)
You should be able to use a DataTrigger on the button style to disable your button when the DataContext is null. The other option is to bind the IsEnabled property to the DataContext and use a value converter to return false if DataContext is null and true otherwise.
With trigger:
<Button>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
With converter:
Converter:
public class DataContextSetConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And use it
<UserControl.Resources>
<local:DataContextSetConverter x:Key="dataContextSetConverter"/>
</UserControl.Resources>
...
<Button IsEnabled="{Binding Path=DataContext, RelativeSource={RelativeSource Self}, Converter={StaticResource dataContextSetConverter}}"/>
This should do it:
<Button Content="ButtonName">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>