WPF datatrigger not firing - wpf

I'm trying to change the background colour of a text box in WPF based on its content.
Using breakpoints, I see my ValueConverter gets constructed, but neither Convert nor ConvertBack methods ever get called, and so the styling doesn't work.
The 'LightBlue' style in the XAML does work at startup.
I tried to use Snoop but I don't know what I'm looking for.
XAML...
<Grid.Resources>
<local:ThreadCreationLimitChanged x:Key="ThreadCreationLimitChanged"></local:ThreadCreationLimitChanged>
</Grid.Resources>
<Label Grid.Row="0" Grid.Column="0" Margin="0, 5, 0, 0" Content="New Thread Limit"></Label>
<TextBox Grid.Row="0" Grid.Column="1" Margin="10, 5, 10, 0" Width="100" Text="{Binding Path=ManagerConfig.ThreadCreationLimit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<TextBox.Style>
<Style>
<Setter Property="TextBox.Background" Value="LightBlue"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text,Converter={StaticResource ThreadCreationLimitChanged}}" Value="false">
<Setter Property="TextBox.Background" Value="Yellow"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
ValueConverter...
public class ThreadCreationLimitChanged : IValueConverter
{
public ThreadCreationLimitChanged()
{
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString() == "120";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
A bit of class ManagerConfig... (NotifyPropertyChanged implements INotifyPropertyChanged)
class ManagerConfig : NotifyPropertyChanged
{
private uint _ThreadCreationLimit;
public uint ThreadCreationLimit
{
get { return _ThreadCreationLimit; }
set
{
_ThreadCreationLimit = value;
OnPropertyChanged("ThreadCreationLimit");
}
}

Why not create a computed property on ManagerConfig which reports a status for the change of the ThreadCreationLimit? That way one doesn't need a converter. Code such as this:
public bool IsOverLimit { get { return ThreadCreationLimit > 120 } }
public uint ThreadCreationLimit
{
get { return _ThreadCreationLimit; }
set
{
_ThreadCreationLimit = value;
OnPropertyChanged("ThreadCreationLimit");
OnPropertyChanged("IsOverLimit");
}
}
Then bind the data trigger to IsOverLimit which will have a change notification sent everytime ThreadCreationLimit is set/changed.
An added plus is that the business logic is on the intended class and not a converter.

Change this {Binding Path=Text,Converter={StaticResource ThreadCreationLimitChanged}} to {Binding Path=Text, RelativeSource={RelativeSource Self} ,Converter={StaticResource ThreadCreationLimitChanged}}

I guess you are having Binding errors.Have checked like below,
1) Accessing TextBox directly for Value,
<Grid.Resources>
<local:ThreadCreationLimitChanged x:Key="ThreadCreationLimitChanged"></local:ThreadCreationLimitChanged>
</Grid.Resources>
<Label Grid.Row="0" Grid.Column="0" Margin="0, 5, 0, 0" Content="New Thread Limit"></Label>
<TextBox x:Name="tBox" Grid.Row="0" Grid.Column="1" Margin="10, 5, 10, 0" Width="100" Text="{Binding Path=ManagerConfig.ThreadCreationLimit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<TextBox.Style>
<Style>
<Setter Property="TextBox.Background" Value="LightBlue"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text, ElementName=tBox,Converter={StaticResource ThreadCreationLimitChanged}}" Value="false">
<Setter Property="TextBox.Background" Value="Yellow"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
2) Accesing proper Model values,
<Grid.Resources>
<local:ThreadCreationLimitChanged x:Key="ThreadCreationLimitChanged"></local:ThreadCreationLimitChanged>
</Grid.Resources>
<Label Grid.Row="0" Grid.Column="0" Margin="0, 5, 0, 0" Content="New Thread Limit"></Label>
<TextBox Grid.Row="0" Grid.Column="1" Margin="10, 5, 10, 0" Width="100" Text="{Binding Path=ManagerConfig.ThreadCreationLimit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<TextBox.Style>
<Style>
<Setter Property="TextBox.Background" Value="LightBlue"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ManagerConfig.ThreadCreationLimit,Converter={StaticResource ThreadCreationLimitChanged}}" Value="false">
<Setter Property="TextBox.Background" Value="Yellow"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

Related

Creating a ListBox style Control with "Show X More"

I would like to create a type of control that will be able to see a narrowed and expanded list by some button, an expander or just a button, and also display some hidden items
I tried to create a style that would have a ListBox of a certain size (let's say it would be enough for 3 items)
And the Expander or some Button that would be below it would say how many more items there are
And give the option to expand and see everything or reduce and return to see 3 items
What I created here works pretty poorly and doesn't look good.
I would appreciate help on how to improve it or how to do it in a better and more correct way
<Style x:Key="CustomListBoxKey" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsPresenter/>
<Expander x:Name="Expander" Grid.Row="1" Header="Show X More" ExpandDirection="Down"/>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=Expander, Path=IsExpanded}" Value="False">
<Setter Property="MaxHeight" Value="80"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Expander, Path=IsExpanded}" Value="True">
<Setter Property="MaxHeight" Value="Infinity"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public List<string> Items { get; set; } = new() { "A", "B", "C", "D", "E", "F", "G", "H", "I" };
<Grid>
<ListBox ItemsSource="{Binding Items}" Style="{StaticResource CustomListBoxKey}" VerticalAlignment="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
This is an improvement to your code, and it will give you what you asked for
<local:InvertableBooleanToVisibilityConverter x:Key="InvertableBooleanToVisibilityConverterKey"/>
<local:MathValueConverter x:Key="MathValueConverterKey"/>
<Style x:Key="CustomListBoxKey" TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsPresenter x:Name="ItemsPresenter"/>
<Expander
x:Name="Expander"
Grid.Row="1"
ExpandDirection="Down">
<Expander.Header>
<StackPanel
Orientation="Horizontal"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=Expander}, Path=IsExpanded, Converter={StaticResource InvertableBooleanToVisibilityConverterKey}}">
<TextBlock Text="Show "/>
<TextBlock
Text="{Binding RelativeSource={RelativeSource Self}, Path=DataContext.Items.Count,
Converter={StaticResource MathValueConverterKey},
ConverterParameter=-2}">
</TextBlock>
<TextBlock Text=" More"/>
</StackPanel>
</Expander.Header>
</Expander>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=Expander, Path=IsExpanded}" Value="False">
<Setter Property="MaxHeight" Value="80"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Expander, Path=IsExpanded}" Value="True">
<Setter Property="MaxHeight" Value="Infinity"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public class MathValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int.TryParse(parameter.ToString(), out var x);
return (int)value + x;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class InvertableBooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is bool and true ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I would like to present a solution using a UserControl. Actually we have a request for two slightly different presentation. As a first step I build a UserControl called DualDisplay. The DualDisplay gets : "Short Display" and "LongDisplay" as parameters.
The DualDisplay UserControl is hosting the Expander:
<UserControl.Resources>
<local:InvertBooleanToVisibilityConverter x:Key="InvertBooleanToVisibilityConverter"/>
<Style TargetType="Expander" x:Key="ExpanderHeaders">
<Style.Triggers>
<Trigger Property="IsExpanded" Value="true">
<Setter Property="Header" Value="Show Less"/>
</Trigger>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Header" Value="Show More"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<Expander Name="expander" IsExpanded="False" Style="{StaticResource ExpanderHeaders}">
<ContentPresenter Content="{Binding ElementName=ours,Path=LongContent}"/>
</Expander>
<ContentPresenter Content="{Binding ElementName=ours,Path=ShortContent}"
Visibility="{Binding Path=IsExpanded,ElementName=expander,Converter={StaticResource ResourceKey=InvertBooleanToVisibilityConverter}}"/>
</StackPanel>
</Grid>
And the using of the DualDisplay will look as following :
<local:DualDisplay>
<local:DualDisplay.ShortContent>
<ListBox ItemsSource="{Binding Items}" VerticalAlignment="Top" MaxHeight="80">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</local:DualDisplay.ShortContent>
<local:DualDisplay.LongContent>
<ListBox ItemsSource="{Binding Items}" VerticalAlignment="Top" MaxHeight="1000">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</local:DualDisplay.LongContent>
</local:DualDisplay>
When we use this method we can reuse the DualDisplay for different cases and we are free to design the short/long display as we wish.
I attach also the code behind of the DualDisplay:
public object ShortContent
{
get { return (object)GetValue(ShortContentProperty); }
set { SetValue(ShortContentProperty, value); }
}
public static readonly DependencyProperty ShortContentProperty =
DependencyProperty.Register("ShortContent", typeof(object), typeof(DualDisplay),
new PropertyMetadata(null));
public object LongContent
{
get { return (object)GetValue(LongContentProperty); }
set { SetValue(LongContentProperty, value); }
}
public static readonly DependencyProperty LongContentProperty =
DependencyProperty.Register("LongContent", typeof(object), typeof(DualDisplay),
new PropertyMetadata(null));

Enable/Disable Combobox if label content changed

Is it possible to enable/disable the combobox if the label has content in the xaml? (I am looking for a xaml solution.)
<Label x:Name="lbl_AusgewählteEmail" HorizontalAlignment="Left"
Margin="37,132,0,0" VerticalAlignment="Top" Width="607"
Content="{Binding ElementName=combx_UnzustellbarMailAuswahl, Path=SelectedItem}"/>
<ComboBox x:Name="combx_Auswahl" HorizontalAlignment="Left"
Margin="37,219,0,0" VerticalAlignment="Top" Width="318"/>
In pure XAML, no. You can however use an IValueConverter to turn that string into a boolean:
public class NonEmptyStringToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string)
return !String.IsNullOrEmpty((string) value);
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DependencyProperty.UnsetValue;
}
}
<Window.Resources>
<yourNameSpace:NonEmptyStringToBooleanConverter x:Key="StringToBool"/>
</Window.Resources>
<Label x:Name="lbl_AusgewählteEmail" HorizontalAlignment="Left"
Margin="37,132,0,0" VerticalAlignment="Top" Width="607"
Content="{Binding ElementName=combx_UnzustellbarMailAuswahl, Path=SelectedItem}"/>
<ComboBox x:Name="combx_Auswahl" HorizontalAlignment="Left"
Margin="37,219,0,0" VerticalAlignment="Top" Width="318"
IsEnabled="{Binding ElementName=combx_UnzustellbarMailAuswahl, Path=SelectedItem, Converter={StaticResource StringToBool}"/>
You could potentially also do this via a Style but that would be a bit weird to be honest. For the sake of completeness:
Include the following namespace at the top of your containing control/window:
xmlns:system="clr-namespace:System;assembly=mscorlib"
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=combx_UnzustellbarMailAuswahl, Path=SelectedItem}" Value="{x:Static system:String.Empty}">
<Setter Property="IsEnabled" Value="False"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=combx_UnzustellbarMailAuswahl, Path=SelectedItem}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>

WPF: Custom progress-bar color base on value condition

So i have this custom Progress-bar that i put inside ListView column:
<Style x:Key="CustomProgressBar" TargetType="ProgressBar" >
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Border BorderBrush="Gray" BorderThickness="1" Background="Gray" CornerRadius="0" Padding="0" >
<Grid x:Name="PART_Track">
<Rectangle x:Name="PART_Indicator" HorizontalAlignment="Left" Fill="MediumSeaGreen" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ListView
<ListView.Resources>
<DataTemplate x:Key="MyDataTemplate">
<Grid Margin="-6" Height="22">
<ProgressBar
Name="progressBarColumn"
Maximum="100"
Value="{Binding Progress, UpdateSourceTrigger=PropertyChanged}"
Width="{Binding Path=Width, ElementName=ProgressCell}"
Margin="0"
Style="{StaticResource CustomProgressBar}" />
<TextBlock
Text="{Binding Path=Value, ElementName=progressBarColumn, StringFormat={}{0:N1}%}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontSize="11.5"
Foreground="Gainsboro"
Margin="0,1,0,0"/>
</Grid>
</DataTemplate>
</ListView.Resources>
So as you can see my Progress-bar color is Gray and while start to fill the color become MediumSeaGreen.
So i want the color will be Gray and while start to fill i want this color become Yellow and only when its value reach 100% i want it to be MediumSeaGreen.
Is it possible ?
All you need is an implementation for the IValueConverter interface such as:
public class ProgressToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var progress = (double)value;
//your coloring conditions here, just return the color based on the progress value
if (progress == 100d)
{
return Brushes.MediumSeaGreen;
}
if (progress >= 0.01d)
{
return Brushes.Yellow;
}
return Brushes.Gray;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
then add it to your your resources:
<Window.Resources>
<local:ProgressToColorConverter x:Key="ProgressToColorConverter"/>
</Window.Resources>
and use it in your progress bar as
<ProgressBar ...
Foreground="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Value, Converter={StaticResource ProgressToColorConverter}}"/>
it is possible. use a Style on rectanle with default setter for Yellow color and change it to MediumSeaGreen via trigger when ProgressBar.Value reaches 100
<Rectangle x:Name="PART_Indicator" HorizontalAlignment="Left">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Yellow"/>
<Style.Triggers>
<DataTrigger Value="100" Binding="{Binding Path=Value, RelativeSource={RelativeSource AncestorType=ProgressBar}}">
<Setter Property="Fill" Value="MediumSeaGreen"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>

Add Check Box for Treeview parent nodes only Wpf

I want to add the check box for treeview of observable collection. the following code add the check box for all the parent and children nodes. I need only for the parent node Is there any way to achieve this...
<HierarchicalDataTemplate x:Key="LogFolderExplorer" DataType="{x:Type TestAutomationClient:TestArtifact}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" MinWidth="200">
<CheckBox
Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center"
/>
<TextBlock Text="{Binding Name}" FontSize="14"/>
</StackPanel>
</HierarchicalDataTemplate>
There are tons of ways you can achieve this.
One of them:
You can go to the ViewModel for the TestAutomationClient:TestArtifact, and add the bool property here:
public bool IsEmpty { get { return !this.Children.Any() } }
You should update it when the entire collection has changed (if this is your case), i.e. you should make RaisePropertyChanged("IsEmpty"). In case you are using some kind of ICollectionView - you do not need that property on your ViewModel.
<HierarchicalDataTemplate x:Key="LogFolderExplorer"
DataType="{x:Type TestAutomationClient:TestArtifact}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" MinWidth="200">
<CheckBox Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsEmpty}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
<TextBlock Text="{Binding Name}" FontSize="14"/>
</StackPanel>
</HierarchicalDataTemplate>
You need a DataTrigger with a Converter. The Trigger should look like below,
<HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chk" Visibility="Collapsed"/>
<TextBlock Text="{Binding Name}"
VerticalAlignment="Center"
Margin="3 0" />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource TreeLevelFinder}}"
Value="True">
<Setter Property="Visibility"
TargetName="checkBox"
Value="Visible" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
The whole TreeViewItem is being send to the Converter. The Converter will look for a Visual Parent. If it finds a Parent as TreeView, it is the first level. If it finds TreeViewItem, then it is sub levels.
public class TreeLevelFinder : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is FrameworkElement)
{
var element = value as FrameworkElement;
var treeItem = element.TemplatedParent as TreeViewItem;
var parent = treeItem.FindAncestor<ItemsControl>();
if (parent is TreeView)
{
return true;
}
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The extension method FindAncestor<T> is available in this link.

How to reference a resource image in converter?

I have a few images defined in my resource section of my UI :
<Window.Resources>
<!-- Converters -->
<loc:UserStatusToIconConverter x:Key="UserStatusToIconConverter" />
<!-- Images -->
<BitmapImage x:Key="ConnectIcon" UriSource="/WPFClient;component/Images/connect.png" />
<BitmapImage x:Key="ActiveIcon" UriSource="/WPFClient;component/Images/active.png" />
<BitmapImage x:Key="IdleIcon" UriSource="/WPFClient;component/Images/idle.png" />
<BitmapImage x:Key="AwayIcon" UriSource="/WPFClient;component/Images/away.png" />
<BitmapImage x:Key="UnknownIcon" UriSource="/WPFClient;component/Images/unknown.png" />
...
I would like to select one of these for a binding in my converter, I assume this would be more efficient than creating a new image each time (500 times) from the converter.
public class UserStatusToIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string userStatus = value.ToString();
string iconName = ...;
switch (userStatus)
{
case "Active":
// select ActiveIcon;
break;
case "Idle":
// select IdleIcon;
break;
case "Away":
...
break;
}
return iconName;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Here is where I use it :
<ListBox ItemsSource="{Binding Users}">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Image Source="{Binding Status, Converter={StaticResource UserStatusToIconConverter}}" Height="16" Width="16" />
<TextBlock Text="{Binding Nick}" />
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I think you're better off using DataTemplate.Triggers in this case rather than a Converter:
<DataTemplate>
<DockPanel>
<Image x:Name="Img" Height="16" Width="16" />
<TextBlock Text="{Binding Nick}" />
</DockPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Status}" Value="Active">
<Setter TargetName="Img" Property="Source" Value="{StaticResource ActiveIcon}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="Idle">
<Setter TargetName="Img" Property="Source" Value="{StaticResource IdleIcon}"/>
</DataTrigger>
<!-- And So on... -->
</DataTemplate.Triggers>
</DataTemplate>
You may just do the following in your Convert method:
return Application.Current.MainWindow.FindResource(iconName);

Resources