Select control style by binding value - wpf

I have an editor view that can be used for multiple edited objects. The view model for multiple objects provides a property like Field1Multiple of type bool for each field that needs to be handled. In this case, it's only ComboBox controls for now. Whenever multiple differing values shall be indicated for that field, a certain style should be applied to that control which is defined in App.xaml. That style changes the background of the control to visualise that there is no single value that can be displayed here.
I've tried with this XAML code:
<ComboBox
ItemsSource="{Binding Project.Field1Values}" DisplayMemberPath="DisplayName"
SelectedItem="{Binding Field1}">
<ComboBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Field1Multiple}" Value="true">
<Setter
Property="ComboBox.Style"
Value="{StaticResource MultiValueCombo}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
But it doesn't work because I cannot set the Style property from inside a Style. If I use triggers directly on the control, there may only be EventTriggers, no DataTriggers, the compiler says.
How can I set the control's style based on a binding value? Or, how can I set a certain style for a control if a binding value is true?

(EDIT to full solution)
You can use converter:
public class AnyIsMultipleToStyle : IValueConverter
{
public Style NormalStyle { get; set; }
public Style MultiStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
IList<SampleClass> list= value as IList<SampleClass>;
if (list!=null)
{
if (list.Any(i => i.Multi))
return MultiStyle;
}
}
return NormalStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in your xaml:(You indicate normal style and multistyle to converter)
<Window.Resources>
<Style x:Key="MultiValueCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Olive" />
</Style>
<Style x:Key="NormalCombo" TargetType="{x:Type ComboBox}">
<Setter Property="Background" Value="Red" />
</Style>
<my:AnyIsMultipleToStyle x:Key="AnyIsMultipleToStyle1" MultiStyle="{StaticResource MultiValueCombo}" NormalStyle="{StaticResource NormalCombo }" />
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Items, ElementName=root}" >
<ComboBox.Style>
<Binding Converter="{StaticResource AnyIsMultipleToStyle1}" Path="Items" ElementName="root" >
</Binding>
</ComboBox.Style>
</ComboBox>
</Grid>

Related

How to hide combobox toggle button if there is only one item?

I have a WPF application. In one window there is a combobox..and I want to hide the toggle button and disable the combo box if there is only one item.
How would I achieve this ?
I have tried the below code for hiding the toggle button. But of no luck
Any help would be appreciated. thanks
<ComboBox x:Name="CList" ItemsSource="{Binding Path=C}" >
<Style TargetType="{x:Type ToggleButton}" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count, ElementName=CList}" Value="1">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox>
The better solution is to replace the template of combo box with a control template(which only contain textblock) when the item count is zero.
Here is the xaml for the same.
<ComboBox Name="CList" ItemsSource="{Binding Path=C}"
SelectedItem="{Binding Path=CC}" VerticalAlignment="Center" Margin="0,0,10,0" >
<ComboBox.Style>
<Style TargetType="{x:Type ComboBox}" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count, ElementName=CList}" Value="1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<TextBlock Text="{Binding Items[0], ElementName=CList}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
You would need to change the Template of the ComboBox and implement the trigger inside that. You have no access to the controls in the template from the outside.
(You could copy and modify the existing template, directly modifying a part of the template is practically impossible)
You can always use a Converter also:
(Sorry I didn't fully read your question)
Converters
using System;
using System.Windows;
using System.Windows.Data;
using System.Globalization;
namespace WPFSandbox
{
public class ComboBoxItemCountToEnabledConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == typeof(Int32))
{
if ((int)value > 1)
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ComboBoxItemCountToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == typeof(Int32))
{
if ((int)value > 1)
return Visibility.Visible;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML
<Window
...
...
xmlns:converters="clr-namespace:WPFSandbox">
<Window.Resources>
<converters:ComboBoxItemCountToVisibilityConverter x:Key="ComboBoxItemCountToVisibilityConverter"/>
<converters:ComboBoxItemCountToEnabledConverter x:Key="ComboBoxItemCountToEnabledConverter"/>
</Window.Resources>
<StackPanel>
<ComboBox ItemsSource="{Binding C}" IsEnabled="{Binding Path=C.Count, Converter={StaticResource ComboBoxItemCountToEnabledConverter}}"/>
<ToggleButton Visibility="{Binding Path=C.Count, Converter={StaticResource ComboBoxItemCountToVisibilityConverter}}"/>
</StackPanel>

Return a dynamic resource from a converter

I want to change the color of a WPF control depending on the state of a bool, in this case the state of a checkbox.
This works fine as long as I'm working with StaticResources:
My control
<TextBox Name="WarnStatusBox" TextWrapping="Wrap" Style="{DynamicResource StatusTextBox}" Width="72" Height="50" Background="{Binding ElementName=WarnStatusSource, Path=IsChecked, Converter={StaticResource BoolToWarningConverter}, ConverterParameter={RelativeSource self}}">Status</TextBox>
My converter:
[ValueConversion(typeof(bool), typeof(Brush))]
public class BoolToWarningConverter : IValueConverter
{
public FrameworkElement FrameElem = new FrameworkElement();
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool state = (bool)value;
try
{
if (state == true)
return (FrameElem.TryFindResource("WarningColor") as Brush);
else
return (Brushes.Transparent);
}
catch (ResourceReferenceKeyNotFoundException)
{
return new SolidColorBrush(Colors.LightGray);
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
The problem is that I have several definitions of the Resource "WarningColor" dependant on setting day mode or night mode. These events does not trig the WarningColor to change.
Is there a way to make the return value dynamic or do I need to rethink my design?
You cannot return something dynamic from a converter, but if your only condition is a bool you can easily replace the whole converter with a Style using Triggers:
e.g.
<Style TargetType="TextBox">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=WarnStatusSource}" Value="True">
<Setter Property="Background" Value="{DynamicResource WarningColor}" />
</DataTrigger>
</Style.Triggers>
</Style>
If now the resource with that key is changed the background should change as well.
The way to return a dynamic resource reference is pretty simple using a DynamicResourceExtension constructor and supplying it a resource key.
Usage:
return new DynamicResourceExtension(Provider.ForegroundBrush);
Definition of the Provider class should contains the key:
public static ResourceKey ForegroundBrush
{
get
{
return new ComponentResourceKey(typeof(Provider), "ForegroundBrush");
}
}
And the value for the key would be declared in the resource dictionary:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:theme="clr-namespace:Settings.Appearance;assembly=AppearanceSettingsProvider">
<Color x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type theme:Provider}, ResourceId=ForegroundColor}">#FF0000FF</Color>
<SolidColorBrush x:Key="{ComponentResourceKey {x:Type theme:Provider}, ForegroundBrush}" Color="{DynamicResource {ComponentResourceKey {x:Type theme:Provider}, ForegroundColor}}" />
</ResourceDictionary>
This way, the converter would dynamically assign a DynamicResource to the bound property depending on the resource key supplied.

WPF Data Binding and Formatting

I have a bool property in my ViewModel called IsConnected and I would like to bind it to a TextBlock in my MainWindow. Rather than have the textblock read true or false I need it to say Connected or Disconnected instead. Forgive me because I'm new to WPF. If someone could give me a head start I can take it from there but I'm not sure how to figure out what I need.
Easiest way is probably to create a custom converter which converts your bool value to a string. Search anywhere for IValueConverter and/or WPF.
public class BoolToConnectedConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if((bool)value)
return "Connected";
else
return "Disconnected";
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
add xmlns:
xmlns:converter="clr-namespace:MyProjectNameSpace"
add resource to XAML (change to whatever element needed)
<Window.Resources>
<converter:BoolToConnectedConverter x:Key="connectedConverter" />
</Window.Resources>
in XAML:
<TextBlock Text={Binding IsConnected, Converter={StaticResource connectedConverter}" />
I'd generally prefer to just add a property to the view model (I really dislike value converters), but here's a simple way to accomplish what you're trying to do using a style:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="Connected"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Text" Value="Disconnected"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Edit
Note that once you get used to using data triggers, you can make all kinds of modifications to your view without touching your view model. For instance:
<StackPanel>
<Image Source="images\connected.png">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Image Source="images\disconnected.png">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</StackPanel>
Using ViewModel, you write two property wrap, and notify changes in the real property.
So that whenever the value is changed, you the string representation will update and bind to controls, while you can still use the bool property in the code.
public string IsConnectedStr{
get{
return IsConnected?"Connected":"Disconnected";
}
}
public bool IsConnected{
get{
return _isConnected;
}
set{
_isConnected=value;
PropertyChanged("IsConnected");
PropertyChanged("IsConnectedStr");
}
}
You could do this in two ways
1) Write a converter
2) Change the function in the ViewModel so that it returns the desired string instead of a bool
The easiest way is #2, but if you really need the bool value somewhere else in your code you go with #1 (google converter and wpf)
Take a look at value converters.
http://www.wpftutorial.net/ValueConverters.html
public class BoolToConnectedConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var isConnected = (bool)value;
return isConnected ? "Connected" : "Disconnected";
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException("Not required for read-only values");
}
}
In your XAML:
<Window.Resources>
<l:BoolToConnectedConverter x:Key="boolToConnectedConverter" />
</Window.Resources>
<Grid>
<Label Content="{Binding IsConnected, Converter={StaticResource boolToConnectedConverter}}" />
</Grid>

Simple WPF IValueConverter and DataTrigger not working together

I've been having trouble using a value converter with a data trigger. In some of my code it seems like the DataTrigger's Path is being applied to the root element, rather than the element which the style applies to.
I created a simple test case, and I don't understand its behavior. I'm expecting the Button to turn red or blue depending on which value is being fed to the DataTrigger's converter, but the Button isn't being affected at all!
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SimpleWpfApplication"
x:Class="SimpleWpfApplication.SimpleUserControl"
ToolTip="UserControl ToolTip">
<UserControl.Resources>
<local:SimpleConverter x:Key="SimpleConverter" />
</UserControl.Resources>
<Button ToolTip="Button ToolTip">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=ToolTip, Converter={StaticResource SimpleConverter}}"
Value="Button ToolTip">
<Setter Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger
Binding="{Binding Path=ToolTip, Converter={StaticResource SimpleConverter}}"
Value="UserControl ToolTip">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</UserControl>
And a simple converter:
class SimpleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("SimpleConverter is a OneWay converter.");
}
}
Why isn't Convert being called? Why doesn't the Button turn red or blue?
Found the answer in another StackOverflow question: What’s wrong with my datatrigger binding?
The answer is to add RelativeSource={RelativeSource Self} to the binding:
<DataTrigger Binding="{Binding Path=ToolTip,
RelativeSource={RelativeSource Self},
Converter={StaticResource SimpleConverter}}" />

WPF Datatrigger not firing when expected

I have the following XAML:
<TextBlock Text="{Binding ElementName=EditListBox, Path=SelectedItems.Count}" Margin="0,0,5,0"/>
<TextBlock Text="items selected">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=EditListBox, Path=SelectedItems.Count}" Value="1">
<Setter Property="TextBlock.Text" Value="item selected"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
The first text block happily changes with SelectedItems.Count, showing 0,1,2, etc. The datatrigger on the second block never seems to fire to change the text.
Any thoughts?
Alternatively, you could replace your XAML with this:
<TextBlock Margin="0,0,5,0" Text="{Binding ElementName=EditListBox, Path=SelectedItems.Count}"/>
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="items selected"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=EditListBox, Path=SelectedItems.Count}" Value="1">
<Setter Property="Text" Value="item selected"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Converters can solve a lot of binding problems but having a lot of specialized converters gets very messy.
The DataTrigger is firing but the Text field for your second TextBlock is hard-coded as "items selected" so it won't be able to change. To see it firing, you can remove Text="items selected".
Your problem is a good candidate for using a ValueConverter instead of DataTrigger. Here's how to create and use the ValueConverter to get it to set the Text to what you want.
Create this ValueConverter:
public class CountToSelectedTextConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((int)value == 1)
return "item selected";
else
return "items selected";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Add the namespace reference to your the assembly the converter is located:
xmlns:local="clr-namespace:ValueConverterExample"
Add the converter to your resources:
<Window.Resources>
<local:CountToSelectedTextConverter x:Key="CountToSelectedTextConverter"/>
</Window.Resources>
Change your second textblock to:
<TextBlock Text="{Binding ElementName=EditListBox, Path=SelectedItems.Count, Converter={StaticResource CountToSelectedTextConverter}}"/>

Resources